torch.optim.SGD
torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)
:随机梯度下降
-
【我的理解】虽然叫做“随机梯度下降”,但是本质上还是还是实现的批量梯度下降,即用全部样本梯度的均值更新可学习参数。
这里所说的全部样本可以是全部数据集,也可以是一个batch,为什么这么说?因为计算梯度是调用backward函数计算的,而backward函数又是通过损失值张量调用的,损失值的计算和样本集的选取息息相关。如果每次都使用全部样本计算损失值,那么很显然调用SGD时也就是用全部的样本梯度的均值去更新可学习参数,如果每次使用一个batch的样本计算损失值,再调用backward,那么调用SGD时就会用这个batch的梯度均值更新可学习参数。
-
params:要训练的参数,一般我们传入的都是
model.parameters()
。 -
lr:learning_rate学习率,会梯度下降的应该都知道学习率吧,也就是步长。
-
weight_decay(权重衰退)和learning_rate(学习率)的区别
learning_rate就是我们熟知的更新权重的方式,假设可学习参数为 θ \theta θ ,学习率由 γ \gamma γ表示,梯度均值为 g g g;计算出这批样本对应的梯度为 g t g_t gt,迭代完第 t − 1 t-1 t−1次可学习参数的值为 θ t − 1 \theta_{t-1} θt−1,当前第 t t t次迭代的更新方式为 θ t = θ t − 1 − γ g t \theta_t = \theta_{t-1}-\gamma g_t θt=θt−1−γgt,即梯度下降法。
weight_decay是在L2正则化理论中出现的概念。
在损失函数中加入L2正则化项后(大致)变为
J ( θ ) = 1 2 m [ ∑ i = 1 m ( h θ ( x ( i ) ) − y ( i ) ) 2 + λ ∑ j = 1 n θ j 2 ] J(\theta)=\frac{1}{2m}[\sum_{i=1}^{m} (h_{\theta}(x^{(i)})-y^{(i)})^2+\lambda\sum_{j=1}^{n}\theta^2_j] J(θ)=2m1[i=1∑m(hθ(x(i))−y(i))2+λj=1∑nθj2]
其中, λ ∑ j = 1 n θ j 2 \lambda\sum_{j=1}^{n}\theta^2_j λ∑j=1nθj2就是正则化项, λ \lambda λ就是weight_decay。可是梯度不是由backward计算的吗,而backward中可未指明存在正则化项,那不成还要由优化器算法再算一遍新梯度?
先看看官方文档是如何操作的:
只看权重衰退和学习率,描述一下过程,计算使用第 t − 1 t-1 t−1次训练到的可学习参数的模型在这一批次中的梯度,赋值给 g t g_t gt,再用权重衰退乘上第 t − 1 t-1 t−1次训练到的可学习参数,重新赋值给 g t g_t gt,再用这个 g t g_t gt和学习率更新权重,从而实现训练参数的目的。也就是说正则化的实现是在原梯度的基础上加上一个系数乘以可学习参数值作为新梯度,用新梯度更新可学习参数值。并没有重新计算加入正则项后的损失函数的梯度。
本质上是对 ∂ J ( θ i ) ∂ θ i \frac{\partial{J(\theta_i)}}{\partial\theta_i} ∂θi∂J(θi)进行一下变形的结果。
为了推导的方便,假设加入了正则化项后的损失函数为
R e g u l a r i z e d C o s t = C o s t + λ ∑ i θ i 2 RegularizedCost=Cost+\lambda\sum_i\theta_i^2 RegularizedCost=Cost+λi∑θi2加入正则化项后的梯度为:
∂ R e g u l a r i z e d C o s t ∂ θ i = ∂ C o s t ∂ θ i + 2 λ θ i \frac{\partial{RegularizedCost}}{\partial{\theta_i}} = \frac{\partial Cost}{\partial \theta_i} + 2\lambda\theta_i ∂θi∂RegularizedCost=∂θi∂Cost+2λθi可以看出 ∂ C o s t ∂ θ i \frac{\partial Cost}{\partial \theta_i} ∂θi∂Cost的值就是未加正则化项求出的梯度,即backward计算出的梯度。
∂ R e g u l a r i z e d C o s t ∂ θ i \frac{\partial{RegularizedCost}}{\partial{\theta_i}} ∂θi∂RegularizedCost将会作为新的梯度值用于更新权重,即
U p d a t e d θ i = θ i − γ ∂ R e g u l a r i z e d C o s t ∂ θ i ⇒ U p d a t e d θ i = γ ( ∂ C o s t ∂ θ i + 2 λ θ i ) Updated\space\space\theta_i = \theta_i - \gamma \frac{\partial{RegularizedCost}}{\partial{\theta_i}} \\ \space \\ \Rightarrow Updated\space\space\theta_i = \gamma (\frac{\partial Cost}{\partial \theta_i}+2\lambda\theta_i) Updated θi=θi−γ∂θi∂RegularizedCost ⇒Updated θi=γ(∂θi∂Cost+2λθi)
求导数得到的系数2可以扔掉,所以就得到了官方文档中的过程。 -
SGD方法的一个缺点是,其更新方向完全依赖于当前的batch,因而其更新十分不稳定。解决这一问题的一个简单的做法便是引入momentum。
momentum即动量,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力。
从中可以看出,如果我们设置momentum参数不为0,则判断一下是否是第一次迭代,如果是第一次迭代,那么直接将 g t g_t gt 赋值给 b t b_t bt (将 b t b_t bt理解为临时变量吧),如果不是第一次迭代,那么就用前一次的 b t − 1 b_{t-1} bt−1 更新 b t b_t bt,更新方程为 b t = μ b t − 1 + ( 1 − τ ) g t b_t=\mu b_{t-1} + (1-\tau)g_t bt=μbt−1+(1−τ)gt(反正就是个公式,其中 τ \tau τ 为抑制参数)。由于不考虑nesterov参数,即为False,所以直接将计算得到的 b t b_t bt 赋值回 g t g_t gt 。 -
如果对Nesterov Momentum(牛顿动量)感兴趣可以看看Hinton这篇论文
参考
[1] How does SGD weight_decay work? - autograd - PyTorch Forums
[2] L1、L2正则化知识详解 - 简书
[3] L1,L2正则化的原理与区别 - CSDN博客
[4] 各种优化方法总结比较(sgd/momentum/Nesterov/adagrad/adadelta) - 博客园