优化器与损失函数——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、默认β10.9、β20.999在绝大多数任务上都能训得动。缺点是它的泛化能力有时候不如SGDMomentum尤其在图像分类这类需要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。模型结构决定了理论上限而优化器和损失函数决定了你离这个上限有多近。很多人只顾着提升上限从来不花力气去逼近上限这是最亏的。