
我一直觉得深度学习里最容易被低估的就是优化算法和参数初始化。很多人把精力花在调网络结构、堆数据增强上结果模型跑起来要么 loss 纹丝不动要么震荡得像心电图要么直接 NaN最后只能归咎于“玄学”。但实际排查下来一半以上的问题都出在优化器的参数搭配和初始化的选择上。这篇笔记我想把两件事彻底讲清楚一是梯度下降和动量这些基本优化算法到底在做什么每个参数动了会有什么连锁反应二是参数初始化策略为什么能决定模型是“快速收敛”还是“原地踏步”。内容会配合我复现某图像分类 Demo 和某文本分类模型时的实操记录尽量把推导过程、参数设置的逻辑和踩过的坑都写明白方便你直接照着手调。1. 训练不收敛先从哪找原因优化器和初始化各自管什么1.1 一个“上午没收敛下午收敛”的真实困惑我印象很深的一次经历是在某个跨平台系统的图像分类模块里任务本身不复杂对几十类商品图做分类网络用的是带残差连接的卷积结构数据量也不算小。但奇怪的是用一套自认为“很标准”的配置训练loss 在 0.8 附近卡了两个多小时怎么都降不下去。后来我翻了翻代码发现问题根本不在网络结构上优化器用的还是 SGD但学习率设成了 0.5权重衰减也忘了开初始化用的是默认的随机正态分布标准差设得偏大前几层输出方差直接被放大后面的 BatchNorm 都压不住。改成了更合理的学习率退火策略和 He 初始化之后同一个模型在同样的数据下十分钟内就明显开始收敛。这个案例给了我一个很重要的习惯遇到训练问题先别急着改网络结构。优先检查优化器参数和初始化方式因为这两者决定了梯度能不能有效传播比结构细节更基础。结构决定表达能力的上限优化和初始化决定你能不能逼近这个上限。1.2 优化器解决的是“路怎么走”初始化解决的是“起点站在哪”我用一个比较生活化的类比来解释两者的分工。把训练网络想象成开车下山山就是损失函数曲面车的位置就是当前参数终点是最低点。优化器负责的是“方向盘和油门怎么控制”梯度下降告诉你每次应该朝哪个方向走走多大一步动量给你的车加了惯性让它在遇到小坑洼时不会反复原地转圈自适应学习率的算法比如 Adam 家族则会根据不同方向的坡度自动调整步长。参数初始化解决的是“车一开始停在哪里”。这个影响非常远如果起点在平坦的高原上梯度几乎为零再怎么踩油门车都动不了如果起点在悬崖边梯度爆炸一步跨出去参数就飞了如果所有参数从同一个值出发对称性会导致大量神经元学到完全一样的功能网络容量直接被浪费。所以这两个主题必须放一起学。初始化先决定你在损失面上的哪个区域起步优化器再决定你能不能从那里走出来、以多快的速度走向更低的地方。1.3 我建议的学习顺序如果你想按这次的笔记来排查手头的训练问题我建议按这个顺序走一遍确认数据预处理和标签没有问题这是 0 号问题别跳过检查初始化方式先用小模型跑一次前向传播观察激活输出的均值和方差是否稳定选择优化器并设置基础超参学习率、动量、权重衰减用一个很小的 batch 试跑几十步确认 loss 在下降而不是 NaN 或震荡再放大到正式 batch size配合学习率退火策略做完整训练。这个顺序能帮你把“模型问题”和“训练配置问题”分开。下面我会按优化算法和初始化两个主题逐个展开。2. 梯度下降算法批量大小、学习率与权重衰减的联动逻辑2.1 梯度下降的核心更新公式以及它真正做的事情梯度下降的基本更新公式其实就一行param param - learning_rate * grad参数更新方向是损失函数对参数的负梯度方向学习率决定这一步跨多大。初看觉得非常简单但实际用起来有很多层含义。首先梯度实质上是一个“局部信息”。它只告诉你当前位置哪个方向损失下降最快但并不知道前方是不是悬崖、会不会有拐弯。所以直接大步走很容易越过最低点小步走又会太慢。其次梯度下降在工程实现里不是一个纯粹的理论算法。你在训练时看到的loss是当前 batch 的平均损失grad是参数梯度的平均值所以 batch 大小会直接改变梯度的“噪声程度”。我之前做过一个不严谨但很直观的对比batch size 从 16 换成 64 之后学习率不变收敛曲线明显变平滑了但前期收敛变慢把学习率从 0.1 提到 0.2 后两条曲线的表现就接近了。这说明 batch size 和学习率必须联调不能孤立地看某一个参数。2.2 三种批量模式全量、单个、小批量梯度下降在实现上有三种典型模式它们的差异不只是数据量大小而是影响训练效率和最终收敛位置的。我整理了一个对照表方便直观理解模式每步用到的样本数梯度噪声单步更新频率适用场景全量梯度下降所有样本低低极小数据集、需要精确梯度做调试随机梯度下降单个样本1高高在线学习场景一般实践中很少直接用小批量梯度下降16~512中中绝大部分实际训练场景全量梯度下降的问题是每步计算量太大而且容易停在局部最优点附近。单个样本的纯随机梯度下降虽然更新频率高但梯度噪声太大收敛不稳定。小批量是二者的折中。实际选择批量大小我有几个经验值分类任务常用 32 到 256大模型预训练可能用到上千但并不是越大越好过大的 batch 会降低泛化能力而且超出单卡显存时还要拆分梯度累积工程复杂度会上升。2.3 学习率选择不是越小越稳而是要匹配你的优化器学习率是整个优化过程中最重要、也最容易翻车的超参数。它出现的位置是所有参数更新的乘法因子所以一旦数量级错了其他参数怎么调都很难补偿。我常用的判断思路是这样的如果 loss 持续不降但数值正常可能是学习率过小或初始化有问题如果 loss 剧烈震荡甚至几分钟内变化范围超过一个数量级大概率是学习率过大如果 loss 变成 NaN先不要想是不是学习率的问题先查输入数据里有没有NaN或Inf再查一下梯度里是否出现了异常大值。除了固定学习率我更推荐配合学习率退火策略。最简单的做法是按 epoch 或 step 做余弦退火。公式如下lr lr_min 0.5 * (lr_max - lr_min) * (1 cos(pi * current_step / total_steps))我在训练模型时通常先用线性 warmup 把学习率从 0 升到目标值再用余弦退火降到极小值。这样能兼顾前期稳定性和后期收敛精度。2.4 梯度裁剪和权重衰减什么时候加、加多少梯度裁剪不是所有任务都需要但它是一个非常有效的安全网。我自己通常在两类场景会开使用 RNN 或 Transformer 这类容易产生梯度异常的网络损失曲线在训练后期反复出现尖峰。权重衰减的含义是在原始损失上加一项参数平方和的正则项让参数不会涨得过大。公式上它相当于每次更新时把参数乘以一个略小于 1 的系数。使用 SGD 时我建议显式设置weight_decay常见值是 1e-4 附近。如果使用 AdamW 这类把权重衰减和解耦的优化器权重衰减本身就是一个核心参数通常设置 0.01 或 0.1 这类量级和 SGD 的 1e-4 含义不太一样不要混为一谈。从整体看梯度下降的关键不只是那行更新公式而是学习率、batch size、梯度裁剪、权重衰减四者之间的配合。如果你把学习率调得足够合适很多“模型不收敛”的问题其实已经消失了一半。3. 动量算法为什么能抑制震荡参数怎么调3.1 从物理直觉到公式SGDMomentum 到底改了什么动量是一个非常符合直觉的改进。它的核心思想很简单参数更新时不只依赖当前梯度还要继承前几步的更新方向相当于给参数更新加了“惯性”。更新公式如下以参数 θ 为例velocity momentum * velocity - learning_rate * grad theta theta velocity这里的velocity可以看成历史梯度的指数滑动平均momentum控制历史信息保留多少通常取 0.9。我在某个图像分类 Demo 里做过对比实验同一份代码、同一个初始化纯 SGD 在训练初期 loss 下降时会出现明显波动换到 SGDMomentum 之后曲线明显更平滑收敛也更快。但从原理上要理解动量并不是单纯“加速”。它更像一个低通滤波器把高频震荡滤掉保留稳定的前进方向。这在高曲率、长窄谷形状的损失曲面下尤其有效。3.2 NAG 与自适应动量类优化器SGDMomentum 还有一个常见变体 NAG全称是 Nesterov 加速梯度。它和标准动量的差异在于标准动量先根据累积方向走一步再计算梯度NAG 则先根据累积方向“预看”一步在预看的位置计算梯度。公式上的差别不大但在部分任务上收敛更稳定。到了现代深度学习动量思想也被 Adam、AdamW、NAdam 等自适应优化器吸收。它们不仅维护动量的指数滑动平均还维护梯度平方的滑动平均借此对每个参数做归一化缩放。这样每个参数都能有自己的有效学习率稀疏梯度场景下效果很好。不过实际使用中自适应学习率优化器并不总是万能。尤其是在图像分类这类 dense 梯度场景下调好的 SGDMomentum 往往能取得更好的泛化结果。所以我的建议是日常快速验证模型能否正常收敛用 Adam 系列追求最终精度和泛化性能花时间调 SGDMomentum 或 AdamW如果你的任务涉及大规模预训练AdamW权重衰减解耦是更稳妥的默认选择。3.3 动量超参的常用配置以及如何初始化动量参数beta不好随意改我平时的默认值如下SGDMomentummomentum0.9如果想更稳可以试 0.99但收敛会偏慢Adam/AdamWbetas(0.9, 0.999)其中第二个参数控制梯度平方滑动平均NAdam在 Adam 基础上引入 Nesterov 思想一般沿用 Adam 的默认参数。除了这些通用配置还有一个容易忽略的问题是“动量的冷启动”。训练最开始velocity 是零向量前几步更新其实等价于没有动量的普通 SGD。如果此时外部强制给很大初始学习率早期梯度很容易把参数带飞。所以需要 warmup 阶段用小学习率先跑几步让 velocity 先积累起来再进入高学习率阶段。这在 Transformer 类模型的训练中几乎是标配。3.4 动量与 warmup 配合的套路具体操作上我习惯的 warmup 策略是这样设定总步数和峰值学习率比如 1e-3前 5% 的 step 内把学习率从 0 线性提升到峰值达到峰值后进入余弦退火阶段慢慢降到底如果训练曲线仍有剧烈震荡把动量从 0.9 提到 0.95 或者 0.99而不是急着把学习率降一个数量级。这个套路最典型的收益体现在大语言模型、Transformer 类网络的训练里。我第一次训练某文本分类模型时没有做 warmup前几百步 loss 一直是 4.2 到 5.0 来回跳加了 warmup 之后同样配置下前几百步就能稳定下降到 3.5 以内。差别非常明显。动量的核心价值不是把学习率调大而是把优化过程变稳。稳下来之后你才能进一步尝试更大的学习率、更激进的退火策略这比单纯换网络结构带来的收益更直接。4. 参数初始化策略Xavier 与 He 的原理、推导与代码4.1 初始化为什么能影响训练甚至决定模型能不能“学得动”参数初始化的作用在理论上一句话可以概括让信号在各层之间流动时保持合理的方差尺度。换句话讲每一层输出的方差既不能越来越小也不能越来越大。如果每经过一层输出方差缩小一半那几十层之后信号就趋近于零梯度也会跟着消失反过来如果方差逐层放大训练几步后激活值就会爆炸到无穷大BatchNorm 都不一定能拉回来。我曾经用随机正态分布做过一次不严谨的实验网络只有 20 层全连接层激活函数用 ReLU初始化标准差设为 0.2。结果前向传播的激活值在经过十几层后全部变成 0梯度根本没法定向传播。换成标准差为 2.0 的初始化后激活值又迅速变成超大数。这两组实验直观说明了“初始化尺度”的影响。初始化策略本质上就是回答一个问题给每一层的权重设置多大范围的随机值。它要跟你用的激活函数、网络深度、前向传播和反向传播同时匹配。4.2 常见初始化策略一览从全零到随机缩放先看一个反面教材全零初始化。如果所有参数都设为 0前向传播时每个神经元输入相同反向传播时每个参数的梯度也相同于是所有神经元都在学同一个函数网络的表达能力被完全锁死。即使你用梯度下降也无法打破这种对称性。另一种常见做法是统一初始化成同一个常数。这和全零初始化的问题类似所以一定要引入随机性。最基础的随机初始化是在一个对称区间内做均匀分布或正态分布采样# 不推荐的粗暴方式直接默认标准差采样 w np.random.randn(fan_in, fan_out) * 0.01当网络层数较浅、激活函数比较温和时这类简单初始化勉强能用。但层数一深方差就会被不断放大或缩小。所以后来出现了更严谨的方差标定方法就是下面要讲的 Xavier 和 He。4.3 Xavier 初始化的推导思路以及适用的激活函数业界常用的 Xavier 初始化也叫 Glorot 初始化最早是为了解决 Sigmoid/Tanh 这类饱和激活函数在深网络中的梯度消失问题。它的核心推导思路是希望正向传播时每层输出的方差保持不变反向传播时每层梯度的方差也保持不变。在完全线性、激活函数导数为 1 的假设下能得到这样的方差约束fan_in * var_w 1 fan_out * var_w 1为了让两者都满足通常取两者平均于是权重方差为var_w 2 / (fan_in fan_out)如果使用均匀分布并且要求分布方差等于这个值可以推导出均匀分布的半径范围w ~ U(-sqrt(6/(fan_infan_out)), sqrt(6/(fan_infan_out)))很多框架里直接提供了封装好的函数但理解这个推导过程很有必要。当你换用 Tanh 这类对称激活函数时Xavier 工作得很好如果你用 ReLUXavier 反而不是最佳选择原因在于 ReLU 会把一半的激活置零导致输出方差折半。4.4 He 初始化Kaiming 初始化与 ReLU 网络的适配并手推一遍方差ReLU 网络的问题是经过 ReLU 之后由于负半轴输出被截断为 0实际传播的方差约等于输入方差的一半。为了让输出方差维持住就需要把权重方差扩大一倍。推导过程我简化为三步输入 x 的方差记为 Var(x)ReLU 输出大约保留一半方差所以正向传播后激活值方差约等于 Var(x) * fan_in * Var(w) / 2要让输出方差等于输入方差则fan_in * Var(w) / 2 1得到Var(w) 2 / fan_in。如果你用正态分布采样He 初始化就是w ~ N(0, sqrt(2 / fan_in))这里注意一个细节公式里用的是fan_in也就是输入维度而不是fan_out。因为正向传播时每个神经元接收的是fan_in个输入的加权和。当然某些框架也提供了同时考虑 fan_in 和 fan_out 的变体但默认的 ReLU 场景用fan_in就足够。我在项目里通常这样写一个可复用的初始化函数import torch import torch.nn as nn def init_weights(module, activationrelu): if isinstance(module, nn.Linear): if activation relu: nn.init.kaiming_normal_(module.weight, modefan_in, nonlinearityrelu) elif activation in (tanh, sigmoid): nn.init.xavier_uniform_(module.weight) else: nn.init.xavier_uniform_(module.weight) if module.bias is not None: nn.init.zeros_(module.bias) elif isinstance(module, nn.Conv2d): nn.init.kaiming_normal_(module.weight, modefan_in, nonlinearityrelu) if module.bias is not None: nn.init.zeros_(module.bias)4.5 归一化层、残差结构与预训练权重初始化之外的三重保险虽然初始化和激活函数要配套但实际训练里仅靠初始化并不能完全解决深层模型的稳定性。还需要配合归一化层和残差结构BatchNorm 或 LayerNorm 可以让每一层的输入分布保持稳定对初始化尺度的敏感度明显降低残差连接为梯度提供了一条“高速公路”即使某些分支的信息流减弱主通道仍能传递梯度如果是从公开权重开始做微调通常不需要重新设置初始化直接用预训练权重。除非你要加新的分类头那新加层可以用标准差偏小的初始化或直接零初始化偏置。我处理新模块时常用一个组合策略主干网络用预训练权重新增的预测头用 He 初始化偏置全零。实测下来这样一般能避免“新头一上来就破坏原有特征”的问题。5. 训练崩溃排查实录与参数速查5.1 常见问题速查表下面把我在训练过程中最常遇到的问题整理成表格你可以直接用来逐一排查。现象优先排查方向常用解法loss 不降初始化尺度是否合理学习率是否过小改用 He/Xavier调大学习率并加 warmuploss 震荡剧烈学习率是否过大batch 是否太小降低学习率增大 batch size开启动量loss 出现 NaN数据中是否有含 NaN梯度是否爆炸检查输入梯度裁剪减小学习率降低权重初始化标准差训练早期 loss 巨高初始化标准差过大最后一层偏置初始化不合理用小方差初始化分类头偏置置零CNN 在最后一层出现全零激活ReLU 死亡问题检查激活前的数值范围换 He 初始化考虑使用 LeakyReLU模型很快拟合但泛化差正则项和权重衰减不够加大 weight_decay做数据增强考虑早停这个表适合贴在代码文件旁边每次训练异常时先过一遍。你会发现大部分“调参玄学”其实都是可定位的配置问题。5.2 复盘一个从 loss 不降到稳定收敛的完整调参过程我再完整复盘一个案例方便你理解整个排查流程。项目背景是某图像分类 Demo网络结构包含 4 个卷积阶段和 2 个全连接层激活函数 ReLU。第一次训练时我使用的配置是优化器SGD学习率 0.1初始化默认正态分布标准差 0.05batch size128无 warmup、无梯度裁剪。结果训练到第 3 个 epochloss 一直在 2.3 上下波动准确率不到 60%。我按速查表一步步调整先打印了每一层输出的均值、方差发现第二层卷积输出方差已经膨胀到 5 以上说明初始化的尺度不对把所有卷积层改用 He 初始化方差稳定后在 1 附近学习率从 0.1 调到 0.05并加了 5 个 epoch 的线性 warmup前 5 个 epoch 用小学习率稳定动量打开梯度裁剪阈值设为 5.0防止个别样本带来的尖峰梯度改变全局方向。改动完成后再训练loss 在第 6 个 epoch 就降到了 1.4后续通过余弦退火稳定收敛到 0.5 附近。前后对比问题根本不在网络结构而在于初始化尺度把模型的起点放到了一个梯度很不友好的区域再加上学习率过大导致前几步几乎是在随机游走。5.3 实用经验清单如果你只记住一小部分内容我希望你记住下面几条默认配置并不万能。不同框架的默认初始化不一定适合你的网络深度、激活函数和任务类型尽量显式设置初始化初始化、学习率、动量、warmup、梯度裁剪是一整套方案。单独调其中一个效果往往不明显当你说“模型训练不动”时先观察激活值和梯度的数值尺度而不是盲目加层或者换损失函数复现论文时尽量复现它的优化器配置和初始化细节因为这两项对最终指标的影响常常比网络结构还大。最后再分享一个小技巧每次开始新实验之前先固定一个随机种子再用一份极小的数据子集跑 20 步左右。如果这一步 loss 能稳定下降基本说明你的优化器、初始化和数据管道是通的如果这一步都不通别去调大网络先把基础配置修好再说。这些经验看起来琐碎但确实能让后续的模型迭代省下大量时间。