Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

自适应矩估计优化器

到目前为止,我们一直使用随机梯度下降(SGD)优化器更新参数。它的算法逻辑非常简单:给梯度乘上一个固定的学习率,就完事了。

但是它也有一个毛病:一刀切。所有参数共用一个学习率,而且从头到尾不变。可是实际上,不同的参数、不同的训练阶段,需要的步幅是不一样的:

  • 有的参数梯度一直很大,有的则一直很小。用同一个学习率,大的容易冲过头,小的又挪不动步;

  • 训练初期通常可以大胆迈步,接近收敛时则需要小心微调。

这一章,我们将引入一个更聪明的优化器:自适应矩估计(Adaptive Moment Estimation,Adam)优化器。它能给每个参数单独地调整有效步幅。简单地讲:就是当梯度长时间偏小的时候,Adam 会适当调大有效步幅;当梯度长时间偏大的时候,Adam 会适当调小有效步幅。Adam 是当前训练神经网络模型最常用的优化器。

张量

数据集

抽象类

字符编码数据集

模型

抽象层

线性层

组合抽象层

顺序层

嵌入层

激活函数

双曲正切函数(Tanh)

损失函数

抽象类

交叉熵损失函数

优化器

在引入新的优化器之前,我们也先提炼出一个优化器的抽象类。

抽象类

抽象类定义了两个函数:

  • step(步进函数):这是一个抽象函数,根据梯度更新所有参数;

  • zero_grad(清零函数):每次迭代前把所有参数的梯度归零。

随机梯度下降优化器

同时将现有的随机梯度下降优化器改为从抽象类继承,只需要实现步进函数即可。

自适应矩估计优化器

自适应矩估计优化器名字里的矩(Moment)指的是梯度的两种统计量。Adam 为每个参数单独维护两个矩:

  • mm(一阶矩,First Moment):梯度的指数加权移动平均(Exponentially Weighted Moving Average,EWMA)。它像是给梯度加了惯性,让更新方向更平稳,不会被单次的随机波动带偏;

  • vv(二阶矩,Second Moment):梯度平方的指数加权移动平均,反映梯度的大小尺度。对于梯度一直很大的参数,vv 就很大,实际步幅会被自动调小;反之,对于梯度一直很小的参数,vv 就很小,实际步幅会被自动放大。

指数加权移动平均

加权移动平均的目的是让梯度(或者梯度平方)的变化更加平缓,公式为:

mt=β1mt−1+(1−β1)gtm_t = \beta_1 m_{t-1} + (1 - \beta_1)g_t
vt=β2vt−1+(1−β2)gt2v_t = \beta_2 v_{t-1} + (1 - \beta_2){g_t}^2

这里:

  • mtm_t,vtv_t:当前第 tt 步的指数加权平均值;

  • mt−1m_{t-1},vt−1v_{t-1}:上一步(第 t−1t-1 步)的指数加权平均值;

  • gtg_t,gt2g_t^2:当前的梯度、梯度平方;

  • β1\beta_1,β2\beta_2:梯度的衰减率(超参数,通常为 0.9 和 0.999),决定了过去信息所占的权重。

步幅缩放

更新时,用 mm 决定方向,再除以 v\sqrt{v} 来缩放步幅:

p  −=  lr⋅m^v^+ϵp \;{-}{=}\; lr \cdot \frac{\hat{m}}{\sqrt{\hat{v}} + \epsilon}

这里:

  • pp:参数;

  • lrlr:学习率;

  • m^\hat{m}:梯度的指数加权平均值;

  • v^\hat{v}:梯度平方的指数加权平均值;

  • ϵ\epsilon:极小的数值,防止除以 0。

这里的 m^\hat{m} 和 v^\hat{v} 都需要偏差修正(Bias Correction)。

偏差修正

在训练初期,m0m_0 和 v0v_0 通常被初始化为 0。这会导致在最初的几步中,mtm_t 和 vtv_t 的值严重偏小(接近 0)。为了消除这个误差,通常需要进行偏差修正,以 mm 为例,公式为:

mt^=mt1−β1t\hat{m_t} = \frac{m_t}{1 - {\beta_1}^t}

这里:

  • mt^\hat{m_t}:修正后的指数加权平均值;

  • mtm_t:指数加权平均值;

  • β1\beta_1:梯度的衰减率;

  • tt:优化器已经更新的步数。

当 tt 越来越大时,β1t{\beta_1}^t 会趋近于 0,分母趋近于 1,修正公式就会自然失效。

⚠️ 注意:这里的步数 tt 是优化器更新参数的次数,和上一章 RNN 里读取一个字符的“时间步”不是一回事。

梯度裁剪

自适应矩估计优化器里还有一个梯度裁剪(Gradient Clipping)函数 clip_grad_norm。

RNN 的梯度计算要穿过很多个时间步,偶尔会突然变得极大(梯度爆炸),把参数一下冲飞。梯度裁剪的作用就是:如果所有参数的梯度合在一起的总范数超过了阈值,就整体按比例缩小,把步幅限制在安全范围内。

权重衰减自适应矩估计优化器

权重衰减自适应矩估计(Adaptive Moment Estimation with Weight Decay,AdamW)优化器是 Adam 的一个小改进,也是训练 GPT 这类大模型的标配。它在 Adam 的基础上加了权重衰减(Weight Decay):每次更新时,把权重本身也往 0 的方向拉一点点。

这样做的目的是防止过拟合:不让任何权重长得太大,模型就不容易“死记硬背”训练数据的细节。

要注意只对矩阵(ndim >= 2)做衰减,偏置等一维参数不衰减。这也是实践中的经验总结。

循环神经网络

循环单元

循环神经网络模型

训练器

抽象类

循环神经网络训练器

在训练器里,我们在步进函数 step 前先调用梯度裁剪函数 clip_grad_norm,用来防止梯度爆炸。

超参数

学习率

批大小

轮数

上下文尺寸

嵌入尺寸

建模

我们把优化器改成权重衰减自适应矩估计(AdamW)优化器。

训练

评估

prediction:	32 steps, each (6970, 65)
total loss:	Tensor(65.93744561104684)

推理

ROMEO:
I not beting you at sheenes hew so ene I's have boo?

GRUMPIO:
Westeesilv bees
tha my sapin, it and ofte, couther me it,
Ane sceat, bot, mance?

Prevees live hem?

GOMYe leald het emen, Beran: I but graed cruith, quep a you geed, I
Anthing will sunf a Sor

这一章,我们把优化器从“一刀切”的 SGD 升级到“量身定制”的 AdamW:

  • Adam:给每个参数单独地调整有效步幅(方向看动量,步幅看尺度);

  • 梯度裁剪:防止梯度爆炸;

  • 权重衰减:防止过拟合。

💡 提示:总体来说,训练初期 Adam、AdamW 的收敛速度通常比 SGD 更快,但是最终的泛化能力可能不如经过精心调参的 SGD。在 Transformer 类模型上,AdamW 仍然是最常用的选择。

课后练习

  1. 试试把梯度裁剪函数 clip_grad_norm 的阈值 max_norm 改大或者改小,观察训练过程有什么不同?