别再瞎调学习率了:一套用 Python 搞定“自动调参 + 训练监控”的实战方案
说句实话,很多人训练模型的时候,最玄学的参数是什么?
👉 学习率(learning rate)
你可能经历过这些场景:
- lr=0.1,直接炸了(loss飞天)
- lr=0.0001,训练到天荒地老
- lr=0.01,好像能用,但又不够好
最后你开始:
👉 “凭感觉调一调吧……”
这事儿就很离谱。
今天我想聊点实战的——
如何用 Python 搞一套“自动学习率调整 + 训练监控”的方案,让训练不再靠玄学。
一、学习率本质上在干嘛?
先用人话讲清楚:
👉 学习率 = 每一步“走多远”
- 太大:一步跨过最优解(震荡甚至发散)
- 太小:像乌龟爬(收敛慢)
你可以把训练过程想象成:
👉 在山谷里找最低点
学习率就是你迈步的长度。
二、第一步:别手动调了,用调度器(Scheduler)
最基础也是最有效的方式:
👉 动态学习率
1. StepLR(最简单)
每隔一段时间,学习率下降一次:
import torch
from torch.optim import SGD
from torch.optim.lr_scheduler import StepLR
model = torch.nn.Linear(10, 1)
optimizer = SGD(model.parameters(), lr=0.1)
scheduler = StepLR(optimizer, step_size=10, gamma=0.1)
for epoch in range(50):
# 假装训练
loss = (model(torch.randn(10)) ** 2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
scheduler.step()
print(f"Epoch {epoch}, LR: {scheduler.get_last_lr()[0]}")
👉 核心逻辑:每10轮,lr变成原来的0.1倍。
2. ReduceLROnPlateau(更聪明)
当 loss 不再下降时,自动降低学习率:
from torch.optim.lr_scheduler import ReduceLROnPlateau
scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=3, factor=0.5)
for epoch in range(50):
loss = train_one_epoch()
scheduler.step(loss)
print(f"Epoch {epoch}, Loss: {loss}")
👉 这个就很实用了:
- loss不降 → 自动减小lr
- 避免卡在局部最优
三、进阶玩法:自动学习率搜索(Auto LR Finder)
这一步很关键,很多人不知道。
👉 思路:先“试一圈”,找一个合适的初始学习率。
实现一个简易 LR Finder
import numpy as np
import torch
def lr_finder(model, optimizer, dataloader, min_lr=1e-5, max_lr=1, num_iters=100):
lrs = np.logspace(np.log10(min_lr), np.log10(max_lr), num_iters)
losses = []
for i, (x, y) in enumerate(dataloader):
if i >= num_iters:
break
lr = lrs[i]
for param_group in optimizer.param_groups:
param_group['lr'] = lr
pred = model(x)
loss = ((pred - y) ** 2).mean()
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses.append(loss.item())
return lrs, losses
你可以画一条曲线:
👉 loss vs learning rate
然后选:
👉 loss开始快速下降但还没发散的点
这就是“黄金学习率”。
四、训练监控:别再只看loss了
很多人训练就盯着一行日志:
Epoch 10, Loss=0.234
👉 这远远不够。
你需要一个完整的监控体系。
1. 用 TensorBoard 实时可视化
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter()
for epoch in range(50):
loss = train_one_epoch()
writer.add_scalar("Loss/train", loss, epoch)
writer.add_scalar("LR", optimizer.param_groups[0]['lr'], epoch)
然后启动:
tensorboard --logdir=runs
你能看到:
- loss曲线
- 学习率变化
- 是否震荡
👉 一眼看出问题。
2. 监控梯度(很多人忽略)
如果梯度爆炸/消失:
👉 学习率再调也没用。
def log_gradients(model, writer, step):
for name, param in model.named_parameters():
if param.grad is not None:
writer.add_histogram(name, param.grad, step)
3. 监控 GPU / 资源
现实一点讲:
👉 模型训练不只是算法问题,还有资源问题。
import psutil
import torch
def monitor_system():
print("CPU:", psutil.cpu_percent())
print("Memory:", psutil.virtual_memory().percent)
if torch.cuda.is_available():
print("GPU:", torch.cuda.memory_allocated() / 1024**2, "MB")
五、自动化方案:把一切串起来
我们来搞一个“像样点”的训练框架:
class Trainer:
def __init__(self, model, optimizer, scheduler):
self.model = model
self.optimizer = optimizer
self.scheduler = scheduler
def train(self, dataloader):
for epoch in range(50):
loss = self.train_one_epoch(dataloader)
if isinstance(self.scheduler, ReduceLROnPlateau):
self.scheduler.step(loss)
else:
self.scheduler.step()
print(f"[Epoch {epoch}] Loss={loss:.4f}, LR={self.get_lr():.6f}")
def train_one_epoch(self, dataloader):
total_loss = 0
for x, y in dataloader:
pred = self.model(x)
loss = ((pred - y) ** 2).mean()
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()
total_loss += loss.item()
return total_loss / len(dataloader)
def get_lr(self):
return self.optimizer.param_groups[0]['lr']
👉 这个结构的好处:
- 可扩展
- 可插监控
- 可替换策略
六、我自己的几点经验(踩坑总结)
说点真心话,这部分最值钱。
1. 学习率不是越复杂越好
很多人迷恋:
- Cosine Annealing
- OneCycle
- Warmup + decay
但现实是:
👉 80%场景,ReduceLROnPlateau就够了
2. 先找对“初始学习率”
你调半天scheduler,不如:
👉 先用LR Finder找一个靠谱起点
3. 监控比调参更重要
你连训练过程都看不清:
👉 调参就是盲人摸象。
4. 别忽略“训练不稳定”的本质
很多人以为是学习率问题,其实是:
- 数据脏
- batch太小
- 初始化有问题
七、最后一句话
如果你现在还在:
- 手动改 lr
- 看 loss 猜问题
- 一次次重跑训练
那我建议你:
👉 把训练流程“工程化”,而不是“玄学化”
说白了:
👉 好模型,不只是调出来的,是“监控 + 自动化”跑出来的。