尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深度学习炼丹指南:损失函数、优化器与正则化核心概念解析

深度学习炼丹指南:损失函数、优化器与正则化核心概念解析 深度学习圈子里常听到一句话“炼丹不是玄学但有时候比玄学还玄。”所谓“深度炼丹”指的就是训练神经网络模型的过程——你要配数据、调参数、控火候像古代道士炼丹一样反复试错一炉不行再来一炉直到炼出自己想要的那个“丹”也就是一个表现良好的模型。这篇博文就围绕深度炼丹中绕不开的核心概念展开梳理损失函数、优化器、学习率、过拟合、正则化、Batch Size、数据划分等常见术语背后的原理与实操要点给正准备踏入炼丹炉旁的新手一份“防炸炉指南”也让已经有经验的同行能对照查漏补缺。1. 炼丹炉与丹方损失函数和优化器怎么选1.1 损失函数模型学习的方向盘训练神经网络本质上是在做一个“不断降低误差”的循环。误差怎么衡量靠的就是损失函数。你选择什么样的损失函数等于给模型定了一个“什么样算好、什么样算差”的评价标准。如果这个标准定错了模型再努力也是白费。分类任务中最常见的是交叉熵损失。拿图像分类来说模型输出的是一个概率分布交叉熵衡量的是“预测分布”和“真实标签分布”之间的差距。你可能会问为什么不直接用“预测对了没”来当损失因为“预测对不对”是离散的、不可导的没法用梯度下降去优化。交叉熵平滑、可导而且它惩罚“自信地错”当模型把某个类别的置信度打得很高但其实是错的交叉熵的梯度会非常大逼着模型快速纠正。回归任务则多用均方误差MSE。MSE把预测值和真实值的差平方所以它对大误差特别敏感。这种“重罚大错”的特性在某些场景下是优点但如果你的数据里有明显离群点MSE会被这几个点牵着鼻子走模型学出来的结果反而不稳。这时候可以考虑 Huber Loss它在小误差时像 MSE在大误差时切换成线性惩罚兼顾敏感度和鲁棒性。我还想提一个实战中容易忽略的点当你做多标签分类一个样本同时属于多个类别的时候用传统的 Softmax 交叉熵是错的因为 Softmax 强制所有类别概率加起来为 1而多标签场景每个类别是独立的。正确做法是每个类别单独算 Sigmoid 交叉熵再取平均。注意损失函数不是越复杂越好。能用一个简单的 CrossEntropy 解决的任务不要上来就堆 Focal Loss、Dice Loss 这些花活。先跑通 baseline再根据痛点比如类别不均衡、小目标召回率低针对性换损失函数。1.2 优化器从 SGD 到 AdamW 的演化逻辑选定损失函数之后接下来就是“如何沿着损失曲面往下走”的问题这就是优化器的工作。初学者往往直接把优化器当成一个黑盒选 Adam 就完事了。但理解每种优化器背后的思路能帮你在炼丹遇到问题时知道该往哪个方向排查。最朴素的 SGD随机梯度下降做的事情很简单每个 batch 算完梯度后直接沿着梯度的反方向走一步步长就是学习率。SGD 的问题是收敛慢而且容易在峡谷地带来回震荡。后来大家发现给 SGD 加上动量Momentum就好多了——就像下山时带了个惯性遇到小坑能直接冲过去而不是每一步都被坑壁弹回来。Adam 的贡献在于把“动量”和“自适应学习率”合在了一起。它对每个参数单独维护一个梯度累积量一阶矩和梯度平方累积量二阶矩这样大梯度参数的学习率会自动变小小梯度参数的学习率会自动变大。听起来很完美对吧实践中的确如此——Adam 在大多数任务上开箱即用收敛速度明显快于 SGD几乎不需要花太多心思调参。但 Adam 也有一个让人头疼的毛病泛化性能往往不如调好参数的 SGD。为什么呢一个比较主流的解释是Adam 的自适应机制会让权重更新路径更曲折最终落在损失曲面的“平坦区域”或“尖锐区域”的倾向不同而尖锐区域的解往往泛化差。于是 AdamW 出现了——它把权重衰减L2 正则和 Adam 的更新步骤解耦不再把正则项混进梯度里而是单独在参数更新时做衰减。实测下来AdamW 在很多任务上能兼顾 Adam 的快速收敛和接近 SGD 的泛化表现。如果你炼丹有些时日了可能会玩过更进阶的优化器比如 LAMB、Lion。LAMB 主要用于大批量训练能在一个 batch 上万的情况下保持稳定Lion 则是 Google 提出的新配方它在很多视觉任务上效果比 AdamW 好但有个坑——它对学习率非常敏感需要仔细调。优化器核心思路优点典型痛点SGD直接沿负梯度方向更新简单、泛化好、可解释收敛慢、易震荡SGDMomentum梯度累积惯性加速收敛、越过局部坑仍依赖全局学习率Adam自适应学习率动量开箱即用、收敛快泛化不如 SGDAdamW解耦权重衰减兼顾速度和泛化超参数仍敏感Lion符号梯度更新效果强、显存省学习率极敏感1.3 学习率炼丹的火候不能大火一直烧如果说优化器决定了“怎么走”那学习率决定的就是“每一步走多大”。学习率大了步子迈太大损失函数容易震荡甚至直接发散学习率小了训练半天损失都不怎么动。深度学习框架里的学习率默认值往往是 0.001比如 PyTorch 的 Adam 默认就是 lr1e-3但这不是什么“官方推荐值”只是一个对大多数任务不至于搞砸的保守选择。实际操作中我会先用一个小脚本做学习率扫描从一个很小的值比如 1e-6开始每个 step 以指数方式增大到 1比如 1e-2同时记录 loss 的变化曲线看看 loss 在哪个区间下降最快。这个区间基本就是合适的学习率范围。训练过程中学习率也不是一成不变的。常见做法是配合学习率调度器Scheduler使用先用热身Warmup让学习率从一个小值慢慢升到设定值避免模型一开始就在不稳定的方向猛冲到达峰值后再用余弦退火Cosine Annealing或逐步衰减Step Decay把学习率降下来让模型在损失曲面的底部做精细化收敛。实操心得我在训练视觉模型时常用的一条基准线是“warmup 5 个 epoch 到 1e-3再用 cosine 调度降到 1e-5”。这套组合在大多数分类、检测任务上都能跑出不错的效果。如果你想快速验证某个想法不要上来就精调学习率直接用这套默认调度跑一轮先把主要矛盾解决掉。2. 火候与时辰训练过程中的关键参数2.1 Batch Size一把抓还是小口吃Batch Size 可能是炼丹炉前最影响“手感”的一个旋钮——调大调小训练速度和最终效果都会变。它决定了模型每次更新参数前“看一眼多少样本”。Batch Size 为 2 的幂次方8、16、32、64……因为对齐了 GPU 内存的访问模式效率最高这个属于老生常谈但真正值得聊的是它对梯度质量的影响。大 Batch Size 的梯度是更多样本的平均方向更准、更平滑所以可以用更大的学习率训练也更快。但另一方面大批量的梯度容易让模型收敛到“尖锐极小值”——训练误差很低但测试误差偏高泛化能力差。小 Batch Size 的梯度噪声大反而有一种“随机扰动”的效果这种扰动让模型有机会跳出糟糕的局部极小值最终落到更平坦、泛化更好的区域。这个矛盾在实践中的平衡点往往是Batch Size 设成显存能承载的最大值的 1/2 到 1/4配合线性缩放学习率规则Batch Size 翻倍学习率也翻倍来补偿。2.2 Epoch 与 Iteration炼几炉才算够Epoch 是训练集完整过一遍的次数Iteration也叫 Step是参数更新一次的次数。两者之间的关系很简单每个 Epoch 的 Iteration 数 训练集样本数 ÷ Batch Size。假如你有 10000 张图Batch Size 是 100那么每个 Epoch 就有 100 次 Iteration。“炼几炉才算够”没有标准答案——Epoch 太少模型欠拟合Epoch 太多模型过拟合。判断依据主要看验证集的表现曲线验证 loss 不再下降、甚至开始回升说明训练到了该停的时候。这时候再继续炼只是浪费电力和时间。实际工作中我通常的做法是先设定一个比较大的总 Epoch 数比如 300配合 Early Stopping早停机制在验证集不再改善时自动停止训练。这样可以放心大胆让模型多跑一会儿又不会真的把电费烧完。2.3 学习率调度大火转中火再转小火训练的过程像煲汤先大火烧开Warmup 阶段快速提升学习率再中火慢炖主体训练阶段保持较高学习率最后小火收汁退火阶段降低学习率精细调整。三个阶段缺一不可。Warmup 存在的必要性和模型状态有关。训练初期模型的权重还处于随机状态梯度的方向可能非常离谱。如果一上来就用大学习率模型可能被推到损失曲面的一个极端位置后面怎么拉都很难拉回来。Warmup 给了模型一个“先站稳再跑”的机会。退火阶段的意义在于收敛。学习率在训练后期仍然保持高位参数会在极小值附近来回震荡永远走不到“谷底”。把学习率降下来相当于把步幅缩小让模型能慢慢挪到更精确的位置。余弦退火是现在用得最多的方案因为它平滑且无需人为干预Step Decay每隔固定 epoch 缩小学习率更适合你明确知道训练在第几个阶段、需要什么学习率的时候。3. 过拟合与正则化防止丹炉炸裂3.1 过拟合背答案的坏学生过拟合是炼丹过程中最常见的“炸炉”事件——训练集上分数很高一到验证集、测试集立刻露馅。它本质上是模型把训练集里的噪声也当成了规律死记硬背了答案而不是学会了推理。怎么判断过拟合最简单的方法是观察训练 loss 和验证 loss 的差距。如果训练 loss 持续下降但验证 loss 不降反升这就是典型的过拟合信号。此外还可以看训练集准确率和验证集准确率的差值差值越来越大说明模型开始“念答案”了。过拟合一旦出现先不要急着加正则化或者换模型先检查一下数据量是不是太少了、数据划分有没有问题、数据增强有没有做。如果这三个都没问题再考虑用正则化手段把模型的自由度限制住。3.2 正则化给模型戴上镣铐正则化的思路很简单让模型“不要学得那么用力”。常用的手段有这么几种。L2 正则化权重衰减是最常见的。它在损失函数后面加一项权重的平方和训练时不仅要求预测误差小还要求权重本身不要太大。这样模型被迫在“拟合训练集”和“保持简单”之间取平衡。PyTorch 里设置weight_decay参数即可但注意 AdamW 和 SGD 对weight_decay的处理方式不同——AdamW 是解耦衰减可以直接对应“权重衰减”的直觉SGD 里的weight_decay则混在梯度里实际效果接近 L2 正则而不是严格的权重衰减。Dropout 则是在前向传播时随机让一部分神经元“闭嘴”强迫模型不要过度依赖某几个神经元的组合从而让网络的各个部分都能独立学到有用的特征。Dropout 适合用在全连接层附近输入为 0.5 常见中间层 0.3 左右常见。但 Dropout 在卷积层和 Batch Normalization 配合时效果反而会打折——BN 本身就有正则化效果再叠 Dropout 属于重复用力。数据增强是“天然的正则化”。对图像做随机裁剪、翻转、颜色抖动让模型看到同一个样本的多种变换版本相当于免费增加了训练数据的多样性几乎不会损失什么理应成为默认配置。文本领域则可以用 dropout、shuffle 等操作实现类似效果。3.3 早停与模型保存留一条后路早停Early Stopping机制是炼丹人的好帮手。你事先设定一个 patience比如验证 loss 连续 10 个 Epoch 没有改善就停。这样既不会错过最好的模型也不会一直烧钱跑到崩溃。模型保存需要注意是保存“当前 epoch 的模型”还是“最佳验证 loss 的模型”。我的习惯是不只存最后一次的权重而是存 best_model.pt 和 last_model.pt 两个文件。best_model.pt 用于最终评估和部署last_model.pt 则方便你在验证曲线出现异常时回溯排查。注意事项如果你从某个开源仓库拿到一份预训练模型先确认它的归一化方式、输入尺寸、pixel value 范围和你的数据是否一致。很多“换了模型效果反而变差”的案例根因是对不上输入側的处理而不是模型本身不行。4. 炼丹原料的讲究数据与预处理4.1 训练/验证/测试集的划分不能儿戏数据集划分是炼丹的第一步但很多人喜欢随手写个train_test_split(test_size0.2)就算完事。真正做项目时我会分三份训练集、验证集、测试集。三者各司其职——训练集负责训练权重验证集用于调参、调模型、做早停测试集只允许在最终评估时碰一次。验证集和测试集的区别经常被忽略。验证集你每天都在用调参调久了模型会对验证集产生“过拟合”——不是数据意义上的过拟合而是你作为炼丹师已经在心里记住了哪些修改对验证集有效会下意识地朝这个方向调。测试集则是最终的一场考试你有且只能用一次才能得到相对客观的模型能力评估。划分比例常见的是 8:1:1 或 7:2:1如果数据量特别大百万级验证集和测试集各留几千到几万条就够了不需要按比例留一大块。对于分类任务还要注意分层抽样——保证每个类别的样本在训练集、验证集、测试集中的分布大致相近特别是类别不均衡的时候不然后果就是“训练集精度 90%测试集精度 30%”这种惨剧。4.2 数据增强免费午餐也不可乱吃数据增强是提升模型泛化能力性价比最高的手段没有之一。特别在视觉领域一张猫的图片水平翻转之后还是猫这种“人类常识”的变换对模型而言却相当于看到了一张新样本能有效扩大数据分布覆盖范围。但“增强”不能无脑堆。强度拉得太猛模型反而会学到一些奇怪的鲁棒性——比如把明明是一张清晰的狗图识别成猫因为训练集里增强变形后的狗图已经失真到不像狗了。实践中我通常采用“轻到中等的默认增强”组合随机水平翻转 随机裁剪 轻微颜色抖动。如果任务本身对颜色敏感比如医学病理图颜色是诊断依据那颜色抖动这一类增强必须禁用。对文本任务而言数据增强相对受限。简单的做法有同义词替换、随机删除、回译等但这些方法要保持语义不变盲目使用容易引入错误标签。另一种思路是用对抗训练如 FGM在嵌入层加一点对抗扰动既增强鲁棒性又不容易改变语义。4.3 归一化与标准化让模型站在同一起跑线上神经网络对输入数据的尺度非常敏感。如果某个特征的数值范围是 0 到 1000另一个是 0 到 1前者会主导梯度让模型很难学到后者的规律。归一化Normalization把每个特征缩放到 [0,1]标准化Standardization则让均值为 0、标准差为 1两种做法在不同场景下各有优势。图像领域最常见的做法是标准化到 [0,1] 之后再加一个基于 ImageNet 均值和标准差的标准化。很多模型在 ImageNet 上预训练时用的就是这套 mean/std如果你迁移预训练模型最好沿用同样的参数。这里有一个容易踩的坑有些人看到模型的 checkpoint 里 mean 是 [0.485, 0.456, 0.406]std 是 [0.229, 0.224, 0.225]就照抄不误但自己的数据集根本不在这个分布上。迁移学习时沿用这套参数问题不大因为模型已经习惯了这个输入分布但如果你从头训练一个模型应该自己统计一下数据集的 mean 和 std。5. 常见问题与排查技巧实录5.1 Loss 不下降怎么办Loss 不下降是炼丹时最焦虑的时刻。我一般按这个顺序排查第一看看是不是数据出了问题。比如标签有没有错位、数据加载有没有把图像和标签对错。快速验证方法取一小批数据比如 16 张跑一个 step打印预测和标签肉眼对比一下是否对得上。第二看看学习率是否合理。学习率设置过高loss 可能震荡、跳动不下降学习率设置过低loss 可能前几个 epoch 几乎不动。可以用学习率扫描快速找到有效区间。第三看看网络的输出层是否匹配任务。分类任务最后有没有接对维度的全连接层激活函数有没有写错我见过有人做二分类却用了 Softmax 输出导致 loss 计算方式完全对不上。第四检查激活函数有没有“死区”。ReLU 在训练初期如果遇到大量负输入可能大量神经元直接“死亡”梯度永远为 0表现为 loss 一直不变。可以换成 LeakyReLU 或 SELU 试试。5.2 梯度爆炸或消失梯度过大或者过小都会让训练变得极其困难。梯度爆炸常见于深层网络loss 会突然跳到 NaN或者权重变成无穷大。排查方法是配一个 gradient clipping把梯度范数裁剪到一个合理范围比如最大范数 1.0这样可以避免一次异常大步子把整个训练搞崩。梯度消失则表现为深层网络的前几层权重几乎不变网络学不到底层特征。Batch Normalization 是解决这类问题的最强武器它对每一层的输出做归一化让梯度保持在合理范围。此外残差连接Residual Connection也是利器——它给梯度提供了一条“近路”让信号可以跨层直接传播有效缓解深度网络的学习困难。5.3 GPU 显存不足显存溢出的报错信息简直是每个炼丹师的梦魇。除了直接调小 Batch Size有几个方法可以尝试开启混合精度训练AMP在 PyTorch 里一句话的事显存占用能少一半很多张量用半精度存储在 NVIDIA 新的 GPU 上还有额外加速使用梯度累积——小 Batch Size 多次前向把梯度累加后再大更新一次效果接近大 Batch Size检查有没有不必要的中间变量被存下来比如推理时用torch.no_grad()包住。5.4 训练模型误导性指标只看精确率而忽略召回率训练分类模型时常常容易陷入“精度越高模型越好”的错觉。实际上对类别不均衡的数据比如欺诈检测99% 的正常样本、1% 的欺诈样本一个把所有样本都预测为“正常”的模型精度能做到 99%但毫无实用价值。这种时候要关注的是 Precision精确率、Recall召回率、F1-score 这些指标。Precision 回答“模型预测为正例的样本里有多少是真的正例”Recall 回答“真正的正例里有多少被模型找出来了”。两者的权衡取决于业务场景——垃圾邮件过滤可能更看重精准率而癌症筛查更看重召回率宁可误报也不能漏报。写到这回头想想自己从入门到现在的炼丹历程最深的感触是这些概念看起来各自独立实际上串起来就是一个闭环——损失函数定义了目标优化器和学习率决定了怎么逼近目标正则化和数据增强明确了哪些“捷径”不能走而数据划分和评估指标则帮你看清楚到底有没有真的拿到好丹。训练一个模型不像是搭积木按部就班就能成功它更像是“烹饪”——同样的配料、同样的火候不同的人做出来味道就是不一样。差别在哪里恰恰是那些失败过的实验、踩过的坑和调参时的直觉积累。如果你正在炼丹路上不要怕试错每一炉废丹都是下一炉好丹的养料。
返回列表