2026/8/28 12:13:19

Loss看不见基底,Adam看得见:深度学习训练排查指南

Loss看不见基底,Adam看得见:深度学习训练排查指南 训练到第四个多小时loss 停在 0.41 附近再也不动了。我按经验试了几招调低学习率、换 batch size、给数据增强里加一点随机裁剪。曲线依旧像一条被熨平的直线。当时第一个反应是模型容量不够第二个反应是数据有问题。后来把每个 batch 的梯度范数打出来看了一眼才意识到真正的状态是loss 没变但参数的更新量一直在正常范围里波动。模型不是不学了而是在同一个损失量级上反复横跳。想明白这件事是因为后来看到一句话The Loss Does Not See the Basis, but Adam Does。直译过来就是“损失看不见基底但 Adam 看得见”。第一次听到会觉得这是概念绕口令但它其实点破了一个深度学习中非常容易被忽略的事实损失是一个标量把整个高维错误压缩成一个小数而 Adam 优化器不同它对每个参数单独维护梯度的一阶矩、二阶矩等于自己建立了一套坐标系来理解梯度的方向和大小。你只看 loss等于只看一份汇总报表Adam 看的是每个人每天的工作流水。这篇文章会围绕这句话展开。我想先解释清楚“基底”到底指什么再讲 Adam 是怎么看见它的然后给出一套我自己常用的训练排查框架顺便把 AdamW、focal loss、perceptual loss 这些高频概念放到同一个上下文里聊。你会发现很多训练问题之所以难查不是因为信息不够而是因为你盯着的指标天生就不携带那些信息。1. “loss 看不见基底”到底是什么意思1.1 标量本身就是一种高压缩率摘要先回到一个常识任何一次前向传播模型输出一个预测Loss 函数拿它和真实标签比较最后得到一个标量。这个标量是成千上万个误差的汇总。分类任务里一百个类别、几百个样本的误差被平均成一个小数回归任务里每个像素、每个时间步的差距也都被折叠进同一个数值。它设计出来就是为了让你“快速判断当前模型有多离谱”而不是为了告诉你“哪里离谱、为什么离谱”。所以当一个训练者只盯着 loss 曲线时他其实是在看一份被压缩过无数次的报告。报告显示“模型整体还有 0.41 的误差”但这份报告不会告诉你误差主要来自哪些类别哪些样本一直学不会梯度是消失了、爆炸了还是在震荡某个网络层是不是已经饱和模型是不是已经过拟合只是被验证集平均掩盖了。这些问题loss 全部看不见。它只能回答“好不好”不能回答“为什么不好”。1.2 这里的“基底”是梯度所在的那套坐标系“Basis”在数学里的意思是一组基向量。某个向量在坐标系里表示成一组分量这组分量就是它在这套基底下的坐标。换一套基底同一个向量的分量会变但向量本身没变长度也没变。神经网络里的情况非常类似。模型的参数、激活、梯度都可以看成某个空间里的向量。损失函数算出来的标量不依赖坐标变换——你把特征空间做一次旋转loss 大概率还是同一个值但梯度向量在不同坐标系下的分量是不一样的。SGD 这类优化器只乘一个标量学习率不会单独对每个坐标做文章所以在正交变换下它表现得像“不看基底”。Adam 不一样它对每个参数坐标单独做归一化相当于给每个方向分配了不同的步长。换个坐标系它分配步长的依据就变了。换句话说loss 只看“总和”Adam 在逐项看“明细”。这个区别在日常训练里会变成一个非常实在的现象你觉得 loss 应该降低但 Adam 可能认为某个方向的梯度一直很小于是把那个方向的学习率放得很大也可能认为某个方向梯度噪声太大于是把它的更新压得很小。这些判断全部发生在 loss 背后单一标量根本体现不出来。1.3 一个最小例子帮你建立直觉假如你的模型输出一个二维向量 x(1,1)目标值是 (2,2)用均方误差。梯度就是 (2,2)。任何优化器按这个梯度走都能直线逼近目标。现在把坐标轴旋转 45 度。新坐标系下同样的梯度变成了 (2√2, 0)。loss 的值没有变梯度的“总长度”也没有变但分量变了。SGD 使用同一个学习率去更新两个坐标行为等价于在旧坐标系里做同样的更新Adam 却会对这两个分量分别除以各自的梯度平方根估计于是在新坐标系里两个方向的更新步长完全不一样。原本均匀前进的路径在旋转后的坐标里被 Adam 改成了“一个方向大步、一个方向小步”的走法。这个例子说明一个关键结论损失是坐标无关的但自适应优化器的行为是坐标相关的。这句话就是标题的数学版。理解这一点你就不会在 loss 平了的时候只想到调学习率。2. Adam 是怎么“看见”基底的2.1 从 SGD 到 Adam从匀速搜索到按参数分配SGD 的更新可以写成param param - lr * grad所有参数共享同一个学习率。对一维凸函数没问题但真实模型里有嵌入层、卷积核、偏置、归一化层的 scale这些参数天然处于不同量级。同一个学习率对某些层合适对另一些层可能太小或太大。Adam 做了一件很朴素但很关键的事为每个参数单独维护两个统计量。m beta1 * m (1 - beta1) * grad # 一阶矩近似平均梯度 v beta2 * v (1 - beta2) * grad ** 2 # 二阶矩近似梯度平方均值 param param - lr * m / (sqrt(v) eps)每个参数都有一个属于自己的学习率lr / (sqrt(v) eps)。如果一个参数的历史梯度一直很大它的 v 就大实际步长被压缩如果一个参数历史梯度很小它的 v 就小实际步长被放大。这就是所谓的“自适应学习率”。把这个机制翻译成“基底”的语言Adam 不是在一个统一的坐标系里做匀速运动而是把参数空间按每个坐标的历史梯度统计量重新标定了一遍。谁的方向上梯度弱它就多走一点谁的方向上梯度噪声大它就少走一点。它看到的不再是原始梯度向量而是一个被二阶矩归一化后的向量。这正是“看见基底”的工程含义。2.2 梯度消失、梯度爆炸在 Adam 里会被怎么处理假设某一层梯度长期小于 1e-8。SGD 下这个参数几乎不动。Adam 下v 也会非常小但分母有 eps通常 1e-8 量级于是实际步长被控制在 lr 的合理倍数内。这会让原本濒临“消失”的参数继续有更新但也带来一个副作用Adam 对梯度的绝对大小不敏感它更看重梯度在时间上的相对稳定性。这意味着你看到 loss 平稳下降时不代表所有层都健康。一个常见情况是浅层梯度很小深层梯度正常。loss 曲线看不出这种分层问题但把每层的梯度范数或每层的 v 拉出来看立刻就能定位是哪一层在拖后腿。所以想真正判断训练是否健康不能只看 loss还要看 Adam 正在维护的统计量——也就是你“用优化器的视角看训练”。2.3 loss 不动Adam 真的还在动吗很多人在群里问loss 曲线平了是不是收敛了不一定。loss 是训练集整体误差的平均。整个 batch 的误差被平均后单个样本、单个类别的影响会被稀释。比如二分类任务里 95% 的样本早就学好了剩下 5% 完全学不会loss 可能已经很低曲线也很平但模型在这 5% 样本上的表现依然很差。loss 平只说明“平均错误率稳定”不说明“每个方向的错误都在变好”。Adam 的 m 和 v 依然在每步更新。哪怕 loss 数值几乎不变参数的移动可能持续发生。这种移动有时候是有益的比如在平坦区域里探索有时候是无益的比如在两个低谷之间反复跳。判断方法是去看参数范数变化、梯度范数是否稳定以及验证集指标是否同步变化。如果 loss 平、验证指标也平且梯度范数接近于零那大概率是真收敛如果 loss 平但验证指标在震荡那就是另一个问题——过拟合或 loss 与目标指标不一致。3. 别再只盯 loss一套训练排查框架3.1 三层排查法我自己调试训练时会按三层顺序排查现象层、梯度层、数据表示层。第一层现象层。先给 loss 曲线定性。是一直降不下来还是降到一半变平还是先降后升还是中途出现尖刺或 NaN。现象描述得越准后面排查范围越小。第二层梯度层。这一步是重点而它正是“Adam 看得见、loss 看不见”的部分。需要记录的信息包括全局梯度范数、每层梯度范数、参数范数、Adam 实际更新量lr * m / sqrt(v)。当 loss 停滞时如果梯度范数接近零说明梯度消失或已经收敛需要检查激活函数、初始化、数据归一化如果梯度范数巨大且震荡说明学习率过大或数据有异常样本如果梯度范数正常但 loss 不降说明模型在“原地打转”重点检查 loss 设计或数据标签。第三层数据表示层。回到 loss 的测量方式。看每个类别的准确率、每个样本的 loss 分布、logits 的数值范围、预测概率的置信度。如果某个类别的 loss 一直不降说明模型对这类样本的表示能力不够如果 logits 数值极大可能是未做归一化或初始化有问题如果 loss 很低但业务指标很差那么 loss 本身很可能选错了。这三层可以合成一张排查表现象优先检查常见原因loss 下降极慢始终偏高输入归一化、学习率、梯度范数特征尺度差异大、lr 过小loss 快速下降后停滞验证指标、梯度范数、参数范数局部平坦区、容量不足、loss 项失衡loss 训练降、验证升正则化、早停、数据分布过拟合loss 出现 NaNgrad norm、logits、输入数值lr 过大、梯度爆炸、数据异常loss 正常但指标不涨逐类指标、样本 loss 分布loss 与目标指标不在同一基底这张表并不能覆盖所有情况但它提供了一个很实用的排查顺序现象 - 梯度 - 表示。大多数训练问题都能在这个顺序里定位到某一层。3.2 最小可复现的日志记录方案实际项目里我建议在训练循环里至少额外记录四个量# 伪代码 / PyTorch 风格只作示意 optimizer.zero_grad() loss.backward() if step % LOG_INTERVAL 0: grad_norm 0.0 param_norm 0.0 for p in model.parameters(): if p.grad is not None: grad_norm p.grad.norm().item() ** 2 param_norm p.norm().item() ** 2 grad_norm grad_norm ** 0.5 param_norm param_norm ** 0.5 print(fstep{step} loss{loss.item():.4f} grad_norm{grad_norm:.4f} param_norm{param_norm:.4f}) optimizer.step()记录日志不一定要很复杂但至少要保证当训练出问题时你能回答“grad_norm 是多少、param_norm 是多少、loss 是哪一项在涨”。这是我排查训练问题时的默认配置。loss 曲线负责告诉你“现状”grad_norm 负责告诉你“模型还能不能动”param_norm 负责告诉你“参数在往哪个量级走”。三者一起看很多像“loss 平了是不是收敛了”的问题几分钟内就能有结论。3.3 什么时候该看 v 和 m更深一层直接看 Adam 的 v 和 m。在 PyTorch 里Adam 的 state 会保存 exp_avg即 m和 exp_avg_sq即 v。把它们按层统计能看出每个参数方向上的梯度积累情况。v 特别大的层说明这一层梯度波动剧烈Adam 正在压它的步长v 特别小的层说明梯度长期微弱Adam 正在放大它的步长。这是真正意义上“用 Adam 的眼睛看训练”。代价是多一点代码收益是能定位到具体某一层是学习瓶颈。对于超过一两天才能训练完的模型这笔投入非常值。4. 选 loss 函数本质上是在选误差坐标系4.1 换 loss 就是换测量基底loss 函数不是一个无关紧要的指标它决定模型在训练中会优先优化哪种误差。最典型的对比是逐像素损失和感知损失。用 L1 或 L2 做图像重建时模型倾向生成“在像素上平均正确”的结果容易模糊用 perceptual loss 或 VGG loss 时误差在深层特征空间里度量模型更倾向于保留结构、纹理这些人类看得见的内容。同样是“重建一张图”在不同坐标系像素基底 vs 特征基底下测量模型走出来的路完全不一样。所以选 loss 不是简单选一个公式而是决定“模型应该把什么当成错误”。loss 选错了模型就会在一个错误的坐标系里努力优化。这时 loss 曲线再漂亮最终业务指标也可能很差。4.2 focal loss、perceptual loss、VGG loss 各自做了什么Focal Loss常被用在类别极度不平衡的目标检测场景。它的核心改动是在交叉熵前面乘一个 (1-p_t)^γ 的调制因子。p_t 是模型对正确类别的预测概率。p_t 越大说明这个样本越容易调制因子越小它对总损失的贡献越低。训练时模型会把注意力从简单样本转向难样本。本质上它是在“样本维度”上重新分配误差权重——换了一套样本加权坐标系。Perceptual Loss / VGG Loss把误差测量从输出像素空间搬到预训练 VGG 的某个特征层。因为在图像生成任务里像素差和人类感知并不线性对应。两幅图在像素上差异不大但视觉观感完全不同反过来像素级差异大观感却可能接近。用 VGG 特征度量更贴近“人在看什么”。使用注意perceptual loss 依赖预训练网络的输入规范比如图像要 resize 到 VGG 接收的尺寸、像素范围要对齐特征层选择会影响结果浅层特征保细节深层特征保语义不同 loss 项的值域差异很大不能直接相加需要按项调权重。4.3 组合 loss 最常见的坑实际项目很少只用单一 loss。常见做法是 L1 perceptual adversarial 加权求和。这里最容易被忽略的是“不同 loss 的值域和梯度量级不同”。某个 loss 数值可能是另一个的几百倍简单相加后大数值项完全支配梯度小数值项等于失效。判断方法还是看梯度贡献分别记录每个 loss 项对总梯度的贡献占比而不是只看总 loss。这一步做不好就会出现“loss 在降但图像越来越糊”的怪现象。5. AdamW vs Adam权重衰减的位置会改变正则的基底5.1 为什么 Adam 里的 weight decay 会被“稀释”Adam 原始实现里也有 L2 正则做法是把 weight decay 项加进梯度。看起来一样但 Adam 会对梯度做归一化——梯度越大实际更新步长越小。于是权重衰减项也被自适应地缩小了。这是个很微妙的问题L2 正则本来希望所有参数统一向零收缩但 Adam 的逐参数归一化会破坏这种统一性。某些参数梯度大它们的权重衰减被压缩某些参数梯度小它们的权重衰减反而被放大。最终效果和标准 L2 正则并不等价也就是说正则项在一个被 Adam 扭曲过的“基底”上生效而不是在原始参数空间里。5.2 AdamW 到底改了什么AdamW 的做法很直接把权重衰减从梯度计算里剥出来单独对参数本身做衰减不经过二阶矩归一化。param param - lr * m / (sqrt(v) eps) - lr * weight_decay * param这样正则效果不再受梯度尺度影响训练也更稳定。在自然语言处理、Transformer、大模型训练里AdamW 几乎成了默认选择。它并不一定让 loss 降得更快但它的泛化表现通常更可控而且超参数的可预期性更好。5.3 怎么选一个务实判断如果你在训练 Transformer、BERT 类模型、扩散模型优先用 AdamW这是当前最常见和稳妥的默认配置。如果任务对最终泛化能力特别敏感比如小数据集图像分类有时候 SGD Momentum 反而更好因为它的解会更偏向平坦最小值。判断依据还是验证集指标不是训练 loss。不管选哪个先复现一个已知基线再谈调参不要一上来就换优化器。先复现基线再谈调参。优化器不是银弹。这里没有“一定更好的优化器”只有“更适合当前任务的优化器”。但理解 AdamW 为什么被提出来能帮你少踩一类“loss 在降但效果变差”的坑。6. 最终建议让 Adam 看见的你也看得见6.1 最小追踪清单把全文收束成一份可以直接用的清单训练开始时固定一个随机种子记录初始训练 loss 和验证指标日志里至少记录 loss、grad_norm、param_norm、learning_rate每 N 步一次loss 停滞时先看 grad_norm 是否接近零、是否 NaN、是否高频震荡再看逐类和逐样本的 loss 分布确认误差来自哪些样本用验证指标做最终判断不要只用训练 loss 决定是否收敛换 loss 或加 loss 项时逐步添加并分别记录每个 loss 项的梯度贡献使用自适应优化器时默认考虑 AdamW并确认 weight decay 的语义是“解耦”的。这份清单也回答了一个常见问题“loss 曲线怎样算正常”。正常的曲线通常训练 loss 先快速下降然后进入低速下降期验证 loss 在某个点后会趋于平稳或轻微反弹。真正需要警惕的不是“平”而是“训练 loss 和验证指标在方向上不一致”。6.2 适用边界这套思路适合谁适合训练中等以上规模的深度模型、需要排查训练不收敛、需要选择 loss 和优化器的工程师和研究者。也适合刚入门不久、被“loss 为什么下不去”折磨的初学者。不适合谁如果只是做非常简单的线性模型、规则系统或数据量极小的场景不需要 Adam 的自适应机制也不需要这套排查框架。直接看误差和业务指标就够了。还需要提醒一点Adam 不是万能药。它擅长在稀疏梯度、不一致尺度、复杂损失曲面上保持训练“能动起来”但它不保证收敛到更好的最小值也不改变 loss 设计是否合理。工具能让你看到更多方向但选择哪个方向仍然要靠你对任务和数据本身的理解。6.3 回到那个标题“The Loss Does Not See the Basis, but Adam Does”这句话对我最大的启发不是增加对 Adam 的崇拜而是改变了我看训练日志的方式。以前我只盯着一条 loss 曲线现在我会同时看梯度范数、逐层统计、逐类准确率、Adam 的 m 和 v。因为我知道loss 只是平均值的平均值而真正决定训练走向的信息藏在那些没有被平均掉的细节里。下次你的 loss 又停在某个数字不动时别急着换学习率。先问一句Adam 正在看到什么