0%

从梯度下降到优化器

从梯度下降到优化器

从Loss到梯度更新

模型的输出取决于参数,因此参数也间接取决于参数,训练的目标可以表示成:在所有可能的参数组合中,寻找一组能够使Loss尽可能小的参数。

从几何上来说,如果模型只有一个参数w,那么可以将Loss化成一条曲线,训练过程相当于在曲线上寻找最低点。如果模型有两个参数,那么此时Loss类似于一个高低起伏的曲面,参数组合决定我们位于曲面的哪个位置。拓展到高维空间,Loss则是这个高位空间中的一个标量。

总之,我们可以直观感受到,每一组参数都对应一个Loss。训练就是在参数空间中不断移动,寻找Loss更低的位置。

数据集的Loss

真实训练通常不会只用一个样本。假设数据集为,每个样本都对应一个Loss:。模型不能只在某一个样本上表现良好,而应当尽可能适应整个训练集。因此通常对所有样本的Loss取平均

参数更新

我们的训练目标已经明确为,但仍然不知道下一步如何修改。也就是说,优化算法必须确定两件事情:方向(参数应该向哪里移动),步长(参数一次应该移动多少)。如果方向错误,Loss会升高;如果步长过大,可能直接越过较优区域;如果步长过短,训练太缓慢。

梯度

一维导数

假设模型只有一个参数w,Loss是$\mathcal{J}(W)$,当参数发生一个很小的变化$\Delta w$ 时,Loss也会随之变化

对参数求导得到

表示参数 w 在当前位置附近发生微小变化时,Loss会以多快速度变化。额也就是导数定义,这里不再多说了。其中导数的正负决定了优化的方向;导数的绝对值表示Loss对该参数的敏感程度

高维情况

也就是从求导数变成求偏导。参数从左右变成可以沿许多方向移动。优化时主要需要思考这两个问题

  • 每一个参数分别会如何影响Loss?
  • 将所有参数同时考虑时,哪个方向会使Loss下降最快?

对于第一个问题,我们的方法是求偏导。求偏导的本质就是固定其他参数,只改变一个参数,将复杂问题拆解为分别观察每一个参数的局部影响。

对于第二个问题,我们所有偏导数组合成一个向量。这个向量也就是梯度。梯度向量恰好代表Loss上升最快的方向(),所以负梯度也就是最陡的下降方向,可以让Loss在局部下降得最快。

为什么局部,以及相关知识,参考最优化

注意梯度所代表的最陡方向不能脱离度量。通常我们用欧式距离衡量参数更新幅度。如果改变参数空间中的距离定义,所谓的最陡方向也会发生改变

梯度下降

通过梯度我们已经知道优化过程中的方向问题

还剩下一个步长问题。在深度学习中,我们通常管这个步长叫学习率$\eta$

梯度下降不是一次完成的直接求解,而是一个迭代过程。每次移动到一个位置后,必须重新计算梯度,然后更新。

当参数越接近最低点的时候,梯度的绝对值越小,参数更新量也会自然虽小

反向传播

我们解决了参数更新的参数和步长问题,但实际情况是,神经网络通常包含大量参数,而且Loss与前部参数之间隔着许多层运算。举个例子,比如,参数w并不直接出现在Loss中,但会通过一连串的中间变量间接影响Loss。要更新w必须通过链式求导法则。

对于链式法则不过多解释,参照高等数学。直观上来说,上个例子的话,假设z = 3w,J = 2z,说明z每变化一点,Loss会变化两倍,而Loss会变化2 times 3 = 6倍。也就是说,整体影响等同于沿途各个局部影响的乘积。

这里从w到J的过程我们定义为前向传播。而反向传播就是从J根据链式法则求偏导到w。。我们定义等式右边第一项是上游梯度,第二项是局部梯度

为了计算梯度,我们必须缓存前向传播时计算的中间变量,这也是训练比推理更占显存的原因。为了加快反向传播速度,一般我们也会缓存求偏导的中间过程

激活函数的影响

激活函数不仅决定前向传播的非线性,也会直接影响反向传播。比如对于Sigmoid函数

导数为:

当 |z|很大的时候,$\sigma’(z)$ 约等于 0。如果网络很深,多个接近0的导数连续相乘导致梯度可能越来越小,最终消失

残差连接

残差连接有助于梯度更顺畅地通过深层网络。梯度传播时如下

也就是说梯度非常小,接近0地情况下,仍然存在一条近似恒等映射的梯度路径。

一次训练迭代的实现

1
2
3
4
5
6
7
8
for x, y in dataloader:
optimizer.zero_grad()

y_hat = model(x)
loss = loss_fn(y_hat, y)

loss.backward()
optimizer.step()

先清空上一轮积累的梯度,然后执行前向传播,计算loss。然后执行反向传播计算参数梯度,最后使用梯度更新参数。

于PyTorch来说,在执行backward时,会完成自动微分:从Loss出发沿计算图反向遍历,在每个节点应用局部求导规则后,使用链式法则组合梯度,最后将参数梯度保存到 param.grad

注意区分,反向传播的目的是得到所有梯度而并不修改参数,而梯度下降的目的是更新参数。

Batch

Batch决定每次参数更新前,使用哪些样本估计参数。也就是说用一部分样本来近似完整训练集的梯度,然后更新参数。我们采用Mini-batch Gradient Descent的目的是在效率、稳定性和硬件利用率之间折中。

因为只要Batch抽样方式合理,反复抽样后,估计梯度的平均值会接近完整梯度。

优化器

优化器解决的是,当已经计算出当前Batch的梯度以后,应该采用什么规则更新梯度。

SGD

乍一看和梯度下降一模一样,其实区别仅仅在于,梯度下降每次使用完整训练集,SGD每次使用一个随机样本或一批随机样本。

Momentum

SGD的第一个问题是,当前Batch的梯度可能有噪声,为什么每一步都完全只依赖当前Batch梯度。于是Momentum考虑不只看当前梯度,还保留过去一段时间的总体运动趋势。也就是削弱高频震荡,加速稳定方向,让优化过程具有惯性

AdaGrad

SGD的第二个问题是,为什么所有参数都必须共享同一个学习率。不同参数的梯度尺度和更新频率可能差别很大,AdaGrad根据每个参数历史梯度的大小,自动调整它自己的学习率。从而梯度长期较大的参数,步长逐渐减小;梯度较少出现的参数,保持较大步长

RMSProp

AdaGrad存在一个新问题,历史梯度不断累积,学习率可能越来越小,最后几乎无法继续更新。RMSProp不再永久累计全部历史梯度,而是更重视近期梯度,使用梯度平方的指数滑动平均(EMA)

Adam

Adam集合Momentum和RMSProp的优点:一方面估计梯度的总体方向,零一方面根据梯度尺度调整每个参数的跟新幅度

也就是说

  • 一阶矩估计:决定往哪里走
  • 二阶矩估计:决定每个参数走多块
  • 偏差修正:修正训练初期统计量不稳定的问题

AdamW

Adam存在一个问题:将L2正则化加入Loss后,自适应缩放会改变权重衰减的实际效果。Adamw将权重衰减从梯度更新中解耦,单独作用于参数。

学习率调度

这里比较偏trick一点,有Warmup、Step Decay、Cosine Decay、退火、Layer Decay等等。不多解释了

常见优化问题

梯度震荡

学习率过大导致的,参数在最低点两侧反复跳动。模型仍然可能收敛,只不过效率比较低

梯度发散

学习率过大导致的,Loss会持续增大,参数越跳越远 ,模型不能收敛

梯度消失

反向传播中链式法则许多局部导数小于一导致的。比如网络过深(没有残差连接)、激活函数使用不当、参数初始化不当等等都会导致这个问题

梯度爆炸

和上述问题对应,反向传播中许多局部导数大于1导致的。可以通过减小学习率(梯度发散问题)、归一化、梯度裁剪解决

优化器的统一视角