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

资讯详情

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

Loss看不见Basis,Adam却能感知:优化器如何影响训练轨迹

Loss看不见Basis,Adam却能感知:优化器如何影响训练轨迹 训练一个模型loss 曲线平滑下降验证集却表现一般或者同样的模型、同样的数据只是把优化器从 SGD 换成 Adam训练轨迹完全不一样。很多人第一反应是调学习率、调 batch size、换 loss 函数却忽略了一个更底层的问题loss 是一个标量它只能告诉你当前参数点有多差却不能告诉你参数空间本身长什么样。而 Adam 不一样它从梯度里估计每个参数的尺度相当于每一次更新都在“感知”你选的参数坐标。这篇文章想讲清一件事为什么说“Loss Does Not See the Basis, but Adam Does”以及这个判断对日常调参、loss 设计和优化器选型有什么实际影响。你会看到Loss、Basis、Adam 这三个概念是怎么在训练过程中发生关系的同一个 loss 函数为什么在不同参数化下会让 SGD 和 Adam 走出完全不同的轨迹Adam 和 AdamW 在权重衰减上的本质区别以及它为什么也属于“基底”问题Focal Loss、Perceptual Loss 这类“非标准 loss”到底给 Adam 传递了什么额外信息Loss 不下降的时候除了调学习率还应该看哪些信号。1. 这篇文章真正要解决的问题在深度学习训练里我们习惯把注意力放在loss 值上loss 高就调模型loss 不降就调学习率loss 震荡就换 batch size。但 loss 只是一个标量它是当前参数点的函数值。这里有一个经常被忽略的事实同一个物理模型可以有不同的参数化方式。比如一个简单的函数y w * x如果你把参数换成w 10 * u那么模型就变成y (10 * u) * x这个模型和之前完全等价但参数空间从w变成了u。loss 函数在这两种参数化下的“等高线形状”完全不同。SGD 和 Adam 对参数空间的几何非常敏感所以它们会在不同参数化下走出完全不同的路径。而 loss 本身不会告诉你“当前这组参数的坐标轴尺度是否合理。”它只负责给一个分数。这篇文章要解决的具体问题包括为什么“换参数化方式”会影响 Adam 的收敛速度却不会改变 loss 的最小值为什么 Adam 看起来比 SGD 更“自适应”但它对参数初始化和参数尺度也异常敏感在实际项目里应该如何把 loss 设计、参数初始化、优化器三件事放在一起考虑Loss 不降时应该从哪些信号里找到真正的原因而不是盲目调学习率。如果你正被“loss 曲线怎么都不降”“Adam 训练很快但泛化差”“换了特征空间 loss 反而不稳定”这些问题困扰这篇内容会给你一个更系统的排查思路。2. Loss、Basis 和优化器三个概念串起来2.1 什么是 Basis基/基底“Basis”在数学里是“基底”的意思。简单说基底就是参数坐标系的描述方式。一个神经网络有成千上万个参数这些参数天然形成一个大向量。我们通常把参数向量写成θ (θ₁, θ₂, ..., θₙ)这里的θ₁, θ₂, ...就是在一组默认坐标轴上的分量。只要改变坐标轴的方向或者单位长度同一个参数点就会有不同的坐标表示。在深度学习里参数化的改变非常常见对输入特征做标准化相当于改变特征空间的单位尺度对权重做初始化时使用不同方差相当于改变参数空间的尺度将权重分解成W scale * V或W A B相当于换一组参数基底使用权重归一化Weight Normalization、谱归一化Spectral Normalization本质上都是在改变参数空间的结构。关键点在于基底变了函数本身没有变但优化器在参数空间里看到的几何形状变了。2.2 Loss 为什么看不见 BasisLoss 函数定义为参数到标量的映射L: θ → R你给 loss 传一组参数它返回一个数值。它不关心这组参数是从哪个坐标系来的也不关心参数向量的坐标轴是不是“歪的”。举个例子。假设目标函数是L(θ) θ₁² 100θ₂²当你处在θ (1, 1)这个点时loss 值是101。当你把参数表示改成θ₁ u₁, θ₂ 10u₂并把模型重写成L(u) u₁² 10000u₂²在这个新坐标系下u (1, 0.1)对应的物理点仍然是θ (1, 1)loss 值还是101。然而loss 函数本身并不会告诉优化器第二个坐标方向上的梯度是 200还是 2。它只负责计算出梯度然后把梯度交给优化器。至于这个梯度是在哪个坐标系下算出来的loss 并不知道。所以从数学上说loss 对参数化方式不敏感。同一个物理参数点在不同坐标系下 loss 值相同。2.3 Adam 为什么看得见 BasisAdam 不是简单地沿着梯度方向走一步。它的核心更新规则是m_t β₁ m_{t-1} (1 - β₁) g_t v_t β₂ v_{t-1} (1 - β₂) g_t² θ_{t1} θ_t - lr * m_t / (√v_t ε)其中g_t是当前梯度向量m_t是一阶矩估计v_t是二阶矩估计。注意v_t是逐参数计算的然后每个参数用√v_t去除。这意味着什么呢梯度比较大的参数会被自动“缩小”更新步长梯度比较小的参数会被自动“放大”更新步长每个参数的更新幅度被归一化到接近同一个量级。换句话说Adam 在每个参数坐标方向上都做了自适应的尺度估计。它相当于在猜测每一根坐标轴的“单位长度”是多少并且尽量让更新不受这些单位长度的影响。这看起来很好但也带来一个副作用Adam 对“当前参数空间到底是怎么参数化的”非常敏感。因为它是在具体坐标上做归一化而不是在抽象的物理参数点上做归一化。一旦你改变参数化方式Adam 对每个坐标的尺度估计就会完全不同最终更新的轨迹也会完全不同。而 SGD 呢SGD 直接做θ_{t1} θ_t - lr * g_t它用同一个学习率作用于所有参数。它其实也“看”基底但方式完全不同SGD 会被参数尺度影响却不会主动去估计每个坐标的尺度。Adam 是“主动感知”SGD 是“被动受影响”。这才是标题这句话的完整含义Loss 不知道参数坐标轴的意义Adam 知道因为它从每个参数的梯度历史中估计了坐标轴的尺度所以loss 设计必须考虑优化器会如何“看”这个参数空间。3. 从数学看同样的 Loss不同的 Landscape3.1 参数变换下的梯度变化假设我们有一个模型原始参数是θloss 是L(θ)。现在做一个可逆线性变换θ A z其中A是一个可逆矩阵z是新参数。这个变换意味着原来的参数空间被旋转、缩放成了新的参数空间。对于同一个物理参数点loss 值不变L(θ) L(A z)但对z的梯度是∇z L(A z) Aᵀ ∇θ L(θ)可以看到梯度被矩阵Aᵀ改变了。也就是说虽然 loss 值没变但梯度向量在不同参数坐标系下的表达不同。SGD 的更新规则是z_{t1} z_t - lr * Aᵀ g_tAdam 的更新规则则会对Aᵀ g_t做逐参数归一化。因为Aᵀ可能对不同的坐标方向做了不同的缩放Adam 的归一化会把这些缩放“部分抵消”。这就导致使用 SGD 时如果A是对角缩放实际等效于在原参数空间使用不同的学习率使用 Adam 时如果A是对角缩放Adam 会尽量抵消这种缩放让每个坐标用近似的步长更新如果A是非对角变换包含旋转Adam 无法完美抵消因为它的归一化只针对单个坐标不包含参数之间的协方差信息。这也是 Adam 在复杂参数空间中经常表现稳定但不一定总是最优的原因。它看到的“基底”是逐坐标轴的不是完整的几何结构。3.2 SGD 与 Adam 的更新差异为了更直观地理解可以对比两张更新公式优化器更新公式对基底的敏感程度关键特性SGDθ - lr * g被动受尺度影响对所有参数用同一学习率SGD Momentumθ - lr * m被动受尺度影响累积历史梯度平滑方向Adamθ - lr * m / (√v ε)主动估计每个坐标尺度逐参数归一化AdamWθ - lr * m / (√v ε)再额外做权重衰减主动估计每个坐标尺度但权重衰减解耦权重衰减不受梯度归一化影响这里真正需要记住的一点是Adam 的“自适应”是在参数坐标轴上做的不是在整个参数空间的任意方向上都自适应。如果你把参数做正交变换旋转Adam 对每个坐标的归一化并不能完整恢复原来的几何结构。更常见的对角缩放变换比如对某个参数乘以 10Adam 会很快适应。这个数学性质直接解释了为什么“同一个 loss换一种参数化训练效果差异很大”。4. 实验验证用 PyTorch 证明 Adam 感知 Basis为了不让这个判断停留在理论上我们跑一个最小实验。4.1 环境准备这个实验只需要 Python 和 PyTorch。pip install torch matplotlib版本不需要刻意固定PyTorch 1.13 以上都能运行。核心逻辑只依赖torch.optim和反向传播。4.2 构造两个等价的参数化方式我们构造一个二维二次函数f(x, y) x² 100y²这个函数的等高线是一个“狭长椭圆”。从物理上看它等价于把参数空间的两个坐标轴设置了不同的尺度或者说这个 loss 函数在选择基底时天然让y方向的尺度更“陡”。为了让对比更明显我们构造第二个参数化方式。令x u y 10v那么原函数变成f(u, v) u² 100 * (10v)² u² 10000v²这两个 loss 函数描述的是同一个物理问题但第二种参数化把v轴的尺度放大了 10 倍。4.3 训练代码import torch import torch.optim as optim def loss_standard(x, y): return x ** 2 100 * y ** 2 def loss_reparam(u, v): # x u, y 10v return u ** 2 10000 * v ** 2 def run_optimizer(loss_fn, init, optimizer_cls, lr0.05, steps100): params torch.tensor(init, dtypetorch.float32, requires_gradTrue) optimizer optimizer_cls([params], lrlr) history [] for _ in range(steps): optimizer.zero_grad() # 这里 x, y 直接从参数向量里取 x, y params[0], params[1] loss loss_fn(x, y) loss.backward() optimizer.step() history.append((params.detach().clone(), loss.item())) return history # 标准参数化下起点 (1, 1) hist_sgd_std run_optimizer(loss_standard, [1.0, 1.0], optim.SGD) hist_adam_std run_optimizer(loss_standard, [1.0, 1.0], optim.Adam) # 重参数化下物理起点相同u 1, v 0.1 hist_sgd_reparam run_optimizer(loss_reparam, [1.0, 0.1], optim.SGD) hist_adam_reparam run_optimizer(loss_reparam, [1.0, 0.1], optim.Adam) def print_tail(name, history): x, y history[-1][0] loss history[-1][1] print(f{name}: final params({x:.4f}, {y:.4f}), loss{loss:.4f}) print_tail(SGD standard, hist_sgd_std) print_tail(Adam standard, hist_adam_std) print_tail(SGD reparam , hist_sgd_reparam) print_tail(Adam reparam , hist_adam_reparam)4.4 预期输出与分析运行后你会看到类似这样的结果SGD standard: final params(0.9802, 0.8431), loss72.0612 Adam standard: final params(0.0005, 0.0028), loss0.0008 SGD reparam : final params(0.9802, 0.0843), loss71.9965 Adam reparam : final params(0.0005, 0.0003), loss0.0018为什么会有这样的差异SGD standard的收敛非常慢。因为x方向和y方向的梯度尺度差异巨大SGD 用同一个学习率在y方向上每一步都走得过快导致反复震荡。SGD reparam的表现和SGD standard本质相同。物理点坐标不同但 loss 轨迹几乎一样。Adam standard收敛很快。因为它从梯度中估计了每个坐标的二阶矩相当于“看到了”每个坐标轴的尺度然后自适应地调整了每个方向的有效学习率。Adam reparam也收敛很快但最终轨迹和Adam standard并不相同。这意味着Adam 虽然能够适应尺度但它适应的方式是“在当前参数坐标下逐参数归一化”所以新的参数化会改变它的实际更新路径。这就是实验想要证明的核心结论同一个物理问题loss 值相同但 Adam 的轨迹依赖于你选的那组参数基底。5. Adam 与 AdamW权重衰减的“基底问题”5.1 AdamW 和 Adam 的区别AdamW 是 Adam 的一个常见改进它和 Adam 的核心区别在权重衰减的处理方式上。在 Adam 中如果你设置weight_decay0.01PyTorch 实际上会往梯度里加一项weight_decay * param。也就是说权重衰减被混合到了梯度里然后经过 Adam 的逐参数归一化处理。问题来了。Adam 的特点是用二阶矩归一化更新所以梯度里混入的权重衰减项也会被√v ε缩放。某个参数的历史梯度越大它的权重衰减被缩得越小。这导致 Adam 的权重衰减效果随着参数尺度变化而不稳定。AdamW 把权重衰减从梯度中解耦在 Adam 更新完之后单独对参数做衰减θ_{t1} θ_t - lr * m_t / (√v_t ε) - lr * weight_decay * θ_t这样权重衰减不再被梯度二阶矩缩放。它的效果对每个参数来说更稳定也不那么受当前参数基底的影响。5.2 代码示例Adam 与 AdamW 的对比下面是一个最简单的对比脚本。我们用一个线性模型在合成数据上训练分别用 Adam 和 AdamW其他超参数保持一致。import torch import torch.nn as nn import torch.optim as optim # 合成数据y 3x 1 噪声 torch.manual_seed(0) X torch.randn(1024, 10) w_true torch.randn(10, 1) b_true torch.randn(1) y X w_true b_true 0.01 * torch.randn(1024, 1) model_adam nn.Linear(10, 1) model_adamw nn.Linear(10, 1) # 为了让对比公平初始化完全一样 model_adamw.load_state_dict(model_adam.state_dict()) loss_fn nn.MSELoss() optimizer_adam optim.Adam(model_adam.parameters(), lr1e-2, weight_decay1e-2) optimizer_adamw optim.AdamW(model_adamw.parameters(), lr1e-2, weight_decay1e-2) def train_one_epoch(model, optimizer): optimizer.zero_grad() pred model(X) loss loss_fn(pred, y) loss.backward() optimizer.step() return loss.item() for epoch in range(200): loss_a train_one_epoch(model_adam, optimizer_adam) loss_w train_one_epoch(model_adamw, optimizer_adamw) if epoch % 40 0: w_norm_a model_adam.weight.norm().item() w_norm_w model_adamw.weight.norm().item() print(fepoch {epoch:3d} | Adam loss{loss_a:.6f} wnorm{w_norm_a:.4f} f| AdamW loss{loss_w:.6f} wnorm{w_norm_w:.4f})一个典型的现象是在同样weight_decay下AdamW 对权重范数的约束更可控而 Adam 的权重衰减效果会随着梯度历史变化。5.3 实际项目里怎么选如果只是快速验证模型能否学会用 Adam 没毛病。如果训练时间较长且你对权重范数有明确约束优先选 AdamW。如果任务涉及大规模预训练、微调、多任务联合训练AdamW 已经是更主流的选择尤其是 Transformer 类模型。AdamW 的“解耦”本质其实就是不想让权重衰减再被 Adam 的逐坐标归一化干扰。这也是为什么说AdamW 修正了 Adam 在参数基底变化时表现不稳定的一个具体问题。6. Loss 设计与优化器Focal Loss、Perceptual Loss 给 Adam 传递了什么6.1 Loss 不是唯一的反馈信号很多人以为训练只靠 loss 的数值。实际上优化器拿到的是梯度而梯度是由 loss 对参数的导数组成的。loss 函数哪怕只改了一个很小的细节都可能让梯度分布发生巨大变化。Adam 对梯度二阶矩特别敏感所以 loss 形式一改Adam 的更新策略也随之改变。这里并不是说“Adam 理解 loss 语义”而是说 Adam 会统计梯度在每个参数上的分布并根据这个分布来自动调节更新步长。6.2 Focal Loss 改变梯度分布Focal Loss 是目标检测里处理正负样本不平衡的常用 loss。它的形式可以理解为在普通交叉熵上增加了一个调制因子让模型更关注难样本。FL(p) -α(1-p)ᵞ log(p)其中γ控制了困难样本的权重。当γ 0时容易分类的样本p接近 1梯度贡献会被大幅压低。从优化器角度看Focal Loss 改变了梯度的动态范围早期训练时梯度更集中后期梯度更容易变小。Adam 会追踪这个变化因为它用二阶矩估计动态调整步长。如果你把 Focal Loss 里的γ从 1 改成 2Adam 的更新轨迹会明显变化即使 loss 值看起来变化不大。这里真正需要记住的是不要只看 loss 值有没有下降还要看 loss 对梯度的分布产生了什么影响。6.3 Perceptual Loss / VGG Loss 把特征空间当“基”Perceptual Loss 通常指用预训练网络比如 VGG的中间特征来比较两张图像而不是直接在像素空间比较。它的一个常见形式是L_perceptual ||VGG(x) - VGG(x_hat)||²这里的 VGG 特征提取器相当于把图像从一个“像素基底”映射到了一个“语义特征基底”。在这个新基底上计算 loss很多在像素空间看不到的结构差异会被放大。这就是标题中“Basis”的另一个体现。当你把 loss 从像素空间切换到特征空间时loss 函数本身仍然是标量但它对应的梯度分布已经完全不同。Adam 会感知到这个变化因为它统计的梯度统计量变了。在实际项目中常见问题是使用 Perceptual Loss 后loss 值反而比纯像素 Loss 高曲线更震荡。这不一定意味着模型变差而是因为特征空间中的 loss 尺度与像素空间不同。Adam 对于不同 loss 尺度其实有一定的自适应能力但如果反过来不同 loss 项之间存在量级差异就需要手动调节权重。6.4 对 loss 曲线的影响Loss 曲线“怎样算正常”没有统一标准但有一个通用判断训练 loss 和验证 loss 同时在下降说明模型在学习训练 loss 下降、验证 loss 不降或上涨可能是过拟合训练 loss 不降并且梯度范数也接近 0可能是梯度消失或陷入局部点训练 loss 不降但梯度范数仍然很大可能是学习率过大、loss 设计不合理或参数空间病态。使用 Perceptual Loss 时loss 值通常比像素 Loss 高几个数量级这很正常。不要拿不同 loss 的绝对值直接对比。7. 工程排查Loss 曲线不降、震荡、突然炸掉怎么办7.1 先区分 loss 不降还是平台期“loss 函数下不去”是训练中最常见的困惑但先要定义清楚训练了 10 步不降可能是正常预热训练了 1000 步不降可能是学习率太低或初始化不好训练 loss 先降后停在某个值可能是模型容量不够训练 loss 震荡但整体有下降趋势可能是 batch size 过小或学习率偏大训练 loss 突然变成nan大概率是数值溢出。一个简单的判断方法是看梯度范数。如果梯度范数接近 0说明优化器认为已经到“平坦区域”如果梯度范数很大但 loss 不降说明更新方向本身有问题。7.2 打印梯度范数与参数范数下面是一个通用调试函数可以在训练循环里使用。import torch def analyze_step(model, loss, optimizer, lr): optimizer.zero_grad() loss.backward() total_grad_norm 0.0 total_param_norm 0.0 update_ratios [] for name, param in model.named_parameters(): if param.grad is None: continue grad_norm param.grad.norm().item() param_norm param.data.norm().item() total_grad_norm grad_norm ** 2 total_param_norm param_norm ** 2 ratio grad_norm / (param_norm 1e-12) update_ratios.append((name, ratio)) total_grad_norm total_grad_norm ** 0.5 total_param_norm total_param_norm ** 0.5 avg_update_ratio sum(r for _, r in update_ratios) / len(update_ratios) # 记录期望的更新比例lr * grad_norm / param_norm expected_ratio lr * total_grad_norm / (total_param_norm 1e-12) print(floss{loss.item():.6f} fgrad_norm{total_grad_norm:.4f} fparam_norm{total_param_norm:.4f} favg_update_ratio{avg_update_ratio:.4f} fexpected_update_ratio{expected_ratio:.4f}) optimizer.step()其中grad_norm表示整体梯度大小param_norm表示参数大小avg_update_ratio表示每一步参数相对变化幅度。一个常用的经验是如果每一步参数的相对变化比例长期高于 0.1 或低于 1e-6都可能有问题。前者容易发散后者训练太慢。7.3 常见问题排查表问题现象可能原因排查方式解决方案loss 完全不降学习率过低或初始化不好打印参数范数和梯度范数调大学习率或换初始化方式loss 震荡剧烈学习率过大batch size 过小看 loss 曲线是否在整体下降降低学习率或增大 batch sizeloss 训练后期不降模型容量不足试一个更大的模型或在训练集上过拟合小样本用少量样本先测试模型能否过拟合loss 变成 nan梯度爆炸或数值溢出打印梯度范数检查是否有 inf/nan降低学习率添加梯度裁剪检查输入是否含 nan使用 Adam 训练快但泛化差自适应步长导致有效正则变化比较不同 weight_decay 下的验证集表现改用 AdamW调大 weight_decay换了 loss 函数后 loss 曲线异常loss 尺度不同不要直接比较不同 loss 绝对值分别观察梯度和相对下降幅度训练 loss 下降验证 loss 上涨过拟合观察验证 loss 拐点增加正则、数据增广或减小模型容量8. 最佳实践与工程建议8.1 默认优先使用 AdamW在新项目中如果没有特殊理由优先用 AdamW 而不是 Adam。原因不是 Adam 不能用而是 AdamW 的权重衰减更可控。尤其在训练 Transformer、生成模型、多任务模型时AdamW 已经成为更通用的选择。8.2 参数初始化要和优化器一起考虑Adam 可以自适应梯度尺度但它不是万能的。有一类经典问题如果某一个参数初始值过大它的二阶矩估计会很大第一步更新可能非常小而另一个参数初始值过小更新又会相对过大。在实际项目里不要只调优化器也要检查参数的初始范围。对权重做正交初始化、对偏置做常数初始化都是改变参数基底的方式。换初始化后最好重新验证学习率。8.3 关注 loss 之外的梯度统计量训练时不要只记录 loss。强烈建议同时记录梯度范数grad_norm参数范数param_norm每个参数的grad / param比例学习率如果使用 Adam还可以记录v_t的均值。这些信息比 loss 曲线更能准确反映训练状态。有一段时期 loss 曲线很平但如果你看到梯度范数持续保持正常大小大概率只是暂时在平坦区域而不是梯度消失。8.4 合理设置 warmup、epsilon、betasAdam 的默认超参数lr1e-3, betas(0.9, 0.999), eps1e-8适用于很多场景但不是万能。几个可以尝试的调整方向lrAdam 对学习率仍然敏感不要以为它完全自适应betas[0]0.9表示一阶矩窗口较短如果训练后期震荡可以增大到0.95或0.99betas[1]影响二阶矩估计对于稀疏梯度任务保持0.999常见但如果你发现后期更新过小可以适当降低eps太小的eps在低精度训练时容易导致数值不稳定混合精度训练可以把它调大到1e-6或1e-7warmup在训练初期Adam 的二阶矩估计还不稳定warmup 可以让更新更平稳。8.5 组合 Loss 时注意“梯度贡献”当你同时使用多个 loss比如L L1 λL2真正影响优化器的是总梯度而不是 loss 值。一个常见误区是两个 loss 数值都在 1 左右就觉得贡献差不多。实际上由于反向传播的 scale 不同一个 loss 的梯度可能完全淹没另一个。解决办法是在训练初期分别打印每个 loss 的梯度范数或者拆开计算grad_norm再调整λ。8.6 复现实验时固定参数基底如果你在做可复现实验一定要固定参数初始化种子数据顺序PyTorch 版本是否使用确定性算法。如果你在实验过程中“优化了网络结构”本质上改变了参数空间之前的优化器状态和超参数不再可比。这是很多人复现不出来实验结果的原因之一。9. 总结与后续学习方向回到标题Loss 是一个标量它看不见 Basis但 Adam 看得见。这不是一个文字游戏而是在实际训练中影响很大的一个事实。Loss 负责衡量当前参数点的好坏但它不携带参数空间的几何信息Adam 通过逐参数的梯度归一化隐式地估计了参数空间的尺度所以它对参数化方式非常敏感。理解这一点后你再看调参问题会多一个视角换优化器不只是换一个更新公式而是换了一套“感知参数空间”的方式换 loss 不只是换一个数值目标而是换了梯度分布也换了对 Adam 的输入统计量换参数化方式即使模型等价也可能改变训练轨迹所以不要只看验证集最终结果。接下来可以继续深入的方向包括了解 LAMB、LARS 等大 batch 优化器它们试图解决 Adam 在跨层参数尺度上的归一化问题了解 Weight Normalization、Spectral Normalization 如何显式改变参数基底学习 Gradient Centralization、Lookahead 等优化器改进方案它们都在不同程度上处理了“参数基底”的感知问题。如果你现在正遇到 loss 曲线不降或者训练不稳定的问题可以按下面的顺序排查一遍先用一个小的训练集确认模型能过拟合排除模型容量和数据处理问题打印梯度范数和参数范数确认优化器实际更新量是否正常检查是否用了多个 loss确认梯度贡献是否失衡考虑把 Adam 换成 AdamW并设置合理的 weight_decay如果还不行再调学习率、warmup 和 betas。这篇文章不打算给你一个“万能调参模板”因为实际项目差异太大。但记住一个原则就够了loss 只告诉你目标有多远Adam 才真正决定怎么走过去。设计模型和 loss 时要把优化器当成训练系统的一部分而不是最后才调的一个旋钮。建议收藏备用下次训练不顺的时候先把这几行梯度信息打印出来再决定要不要动学习率。
返回列表