优化器与损失函数——SGD、Adam、学习率调度,那些调参的血泪史

📅 2026/7/27 0:31:46 👁️ 阅读次数 📝 编程学习
优化器与损失函数——SGD、Adam、学习率调度,那些调参的血泪史

前面聊了神经网络的结构,这篇聊聊"怎么让它学得动"——优化器和损失函数

这两样东西在教科书里的地位就像螺丝刀,人人都会用,但没人觉得值得写一整章。可我要告诉你的是——在真实的工程项目里,优化器和损失函数的选择对最终结果的影响,有时候比换一个更复杂的网络架构还大。

损失函数——你在优化什么东西?

损失函数就是"模型预测错了多少"的量化表达。你选什么损失函数,就相当于告诉模型"什么是重要的、什么是可以容忍的"。

均方误差(MSE)——回归任务最常用的损失。对误差的平方求均值,大误差的惩罚远大于小误差,所以模型会拼命去"消灭"那些特别离谱的预测值。缺点是受异常值影响极大——一个价格预测错了10倍,MSE能把整个梯度带歪。

平均绝对误差(MAE)——同样用在回归里,但用的是绝对值而不是平方。对异常值不那么敏感,但梯度在误差接近0的时候不连续,优化起来不如MSE平滑。

交叉熵(Cross-Entropy)——分类任务的标配。衡量"预测的概率分布"和"真实标签(one-hot)"之间的"距离"。交叉熵跟最大似然估计等价,有坚实的统计学理论基础。

Huber Loss——MSE和MAE的混合体,在误差小时用MSE(平滑收敛快),误差大时用MAE(不被异常值带偏)。这是工业界最常用的回归损失之一,尤其在有异常值的数据集上,比纯MSE稳定太多了。

Focal Loss——我在火焰识别那系列里提过,这是处理极度类别不平衡任务的神器。通过给易分类的样本降权、给难分类的样本升权,把模型注意力拽到那些"模棱两可"的样本上。

对比损失、三元组损失——人脸识别、度量学习用的损失函数,目标是让同类样本在特征空间里靠近、异类样本远离。训练过程比分类损失复杂得多,因为需要精心采样正负样本对。

优化器——用什么样的步幅下山

有了损失函数,你要做的就是"让损失值尽量小"。损失函数在参数空间里是一个高维曲面,你要从某个随机初始点出发,沿着"最陡的下坡方向"一步一步走下去,直到到达一个低点。这个"怎么走""走多快"就是优化器在做的事情。

SGD(随机梯度下降)——最原始的方法。每次随机选一个或一小批样本,算梯度,沿着梯度方向更新权重。学习率是一个固定值,你手动设好了就不变了。

SGD最大的问题是"锯齿震荡"——如果你在狭长的峡谷地形里,梯度方向在峡谷两侧来回摆动,前进效率极低。而且学习率一旦设得不好——太大就反复横跳甚至发散,太小就原地踏步几个月不动。

Momentum(动量)——给SGD加了一个"惯性"。更新的时候不只是看当前梯度,还保留了一部分之前的更新方向。就像下山的时候带上了"冲劲",在峡谷地形里能沿着谷底快速前进,而不是在两侧来回撞墙。

Adagrad——每个参数有自己的学习率,频繁更新的参数学习率逐步降低,稀疏更新的参数保持较大学习率。这在稀疏特征(比如推荐系统的用户ID特征)上特别好用。缺点是学习率会单调递减到接近0,训练后期基本学不动了。

Adam(自适应矩估计)——目前最流行的优化器,没有之一。它结合了Momentum(一阶矩估计)和RMSProp(二阶矩估计),每个参数有自适应的学习率,而且有"偏置矫正"机制让训练初期的更新更稳定。

Adam的优点是几乎不需要调参——默认学习率0.001、默认β1=0.9、β2=0.999,在绝大多数任务上都能训得动。缺点是它的泛化能力有时候不如SGD+Momentum,尤其在图像分类这类需要"sharp minima"(尖锐的极小值)的任务上——Adam倾向于找到"平坦的极小值",而平坦极小值的泛化能力通常不如尖锐极小值。

多阶段学习率调度——比你想象的重要得多

不少新手调模型只知道设一个初始学习率,然后一直用到底。这种做法在简单任务上能跑通,但在深层网络和复杂任务上几乎必败。

Step Decay——每隔N个epoch把学习率乘以一个衰减因子(比如0.1)。这是最古老的调度方式,简单但在很多任务上依然有效。

Exponential Decay——每个epoch都指数级衰减,比Step Decay平滑,适合长期训练。

Cosine Annealing——按照余弦曲线从初始学习率逐渐降到接近0,然后在某个点"重启动"回到初始值。这种重启机制能帮模型跳出局部极小,在多个深度学习竞赛中被验证过极其有效。

Warmup(预热)——训练刚开始的几个epoch用很小的学习率(比如初始学习率的1/100),然后逐步升到目标学习率。这个操作对Transformer类模型是"铁律"——因为训练的早期,Batch Normalization或者LayerNorm的统计量还没稳定,如果直接上大学习率,这些统计量会飘飞,后期怎么调都救不回来。

我自己常用的策略是:"Adam + Cosine Annealing with Warmup",先在5个epoch内从0线性上升到预设学习率,然后按余弦曲线衰减。这套组合在Transformer训练里几乎是黄金标准。

学习率与Batch Size的耦合——被你忽略的"平方根法则"

很多人不知道的一个冷知识——学习率跟Batch Size是耦合的。你把Batch Size翻倍,在同样的数据和同样的epoch数下,最优学习率理论上应该乘以sqrt(2)(平方根2)。

原因是Batch Size越大,梯度估计越准确、噪声越小,所以可以承受更大的学习率来加速收敛。有些论文里报告"我们用学习率0.001训得很好",你搬到自己的实验里,如果Batch Size跟人家不一样,直接套用这个学习率是错的。

所以最佳实践是:先固定一个合理的Batch Size,然后做学习率的网格搜索;或者参考"线性缩放法则"——Batch Size变为原来的k倍,学习率也乘以k,或者保守一点乘以sqrt(k)。

一个颠覆你认知的结论

有个做了多年AI架构的朋友跟我说过一句话,我深以为然——"在工业界,你花80%时间跑的数据和损失函数,只有20%的时间是在调模型结构。"

我十年前刚开始做模型的时候,总觉得"换个更好的模型、更深的层数"才是进步的方向。后来被现实教育了——大部分情况下,换一个更合适的损失函数(比如Focal Loss替代Cross-Entropy),或者把SGD换成Adam并配上恰当的warmup和衰减策略,带来的性能提升远大于把ResNet-50换成ResNet-101。

模型结构决定了"理论上限",而优化器和损失函数决定了"你离这个上限有多近"。很多人只顾着提升上限,从来不花力气去逼近上限,这是最亏的。