参考:https://zhuanlan.zhihu.com/p/376387915
首先应该知道优化算法的目的是什么:既然在变量空间的某一点处,函数沿梯度方向具有最大的变化率,那么在优化目标函数的时候,沿着负梯度方向去减小函数值,以此达到我们的优化目标。
梯度的定义如下:
g r a d f ( x 0 , x 1 , . . . , x n ) = ( ∂ f ∂ x 0 , ∂ f ∂ x 1 , . . . , ∂ f ∂ x n ) gradf(x_0,x_1,...,x_n) = (\frac{\partial f}{\partial x_0},\frac{\partial f}{\partial x_1},...,\frac{\partial f}{\partial x_n}) gradf(x0,x1,...,xn)=(∂x0∂f,∂x1∂f,...,∂xn∂f)
梯度的提出只为回答一个问题:
函数在变量空间的某一点处,沿着哪一个方向有最大的变化率?(针对这一点,我们都知道对于神经网络而言,神经元节点可看做变量x,而每个变量x都有自己的参数 w w w,在某一隐藏层,可能存在256的神经元节点,每个神经元节点对于损失函数来说都有属于自己的方程,我们实质性求得梯度,是基于损失函数在该神经元节点的导数,可能又出现一个问题是众多的偏导数和梯度有什么关系,这也就回到了梯度的定义上,一个空间中存在多个变量,而沿着哪一方向有最大的变化率,则为梯度,而这个变化率就是所求的导)
梯度定义如下:
函数在某一点的梯度是一个向量,它的方向与取得最大方向导数的方向一致,而它的模为方向导数的最大值。
这里注意三点:
1)梯度是一个向量,即有方向有大小
2)梯度的方向是最大方向导数的方向
3)梯度的值是最大方向导数的值
参考文章:https://zhuanlan.zhihu.com/p/32230623
首先定义:待优化的参数: w w w,目标函数: f ( w ) f(w) f(w),初始学习率: l r l_r lr
而后,开始进行迭代优化。在每个 e p o c h epoch epoch:
SGD是每一次迭代计算mini-batch的梯度。
SGD没有动量的概念,也就是说: m t = g t m_t = g_t mt=gt, V t = I 2 V_t = I^2 Vt=I2
代入步骤3,可以看到下降梯度就是最简单的: η t = l r × g t \eta_t = l_r\times g_t ηt=lr×gt
代入步骤4,可以得到梯度更新公式为: w t + 1 = w t − l r × g t w_{t+1} = w_t - l_r\times g_t wt+1=wt−lr×gt
SGD最大的缺点是下降速度慢,而且可能会在沟壑的两边持续震荡,停留在一个局部最优点。
g t g_t gt是当前batch的梯度,所以 l r × g t l_r\times g_t lr×gt可理解为允许当前batch的梯度多大程度影响参数更新
SGD with Momentum
为了抑制SGD的震荡,SGDM(SGD with momentum)认为梯度下降过程可以加入惯性。下坡的时候,如果发现是陡坡,那就利用惯性跑的快一些。在SGD基础上引入了一阶动量: m t = β 1 × m t − 1 + g t m_t = \beta_1\times m_{t-1}+g_t mt=β1×mt−1+gt
也就是说,t时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。 β 1 \beta_1 β1的经验值为0.9,这就意味着下降方向主要是此前累积的下降方向,并略微偏向当前时刻的下降方向。
代入步骤4,可以得到梯度更新公式为: w t + 1 = w t − l r ( × β 1 × m t − 1 + ( 1 − β 1 ) × g t ) V t w_{t+1} = w_t - \frac {l_r(\times \beta_1\times m_{t-1}+(1-\beta_1)\times g_t)}{\sqrt V_t} wt+1=wt−Vtlr(×β1×mt−1+(1−β1)×gt)
模型每次反向传导都会给各个可学习参数 w w w计算出一个偏导数 g t g_{t} gt,用于更新对应的参数 w w w。通常偏导数 g t g_{t} gt不会直接作用到对应的可学习参数 w w w上,而是通过优化器做一下处理,得到一个新的值 g ^ t \hat{g}_t g^t,处理过程用函数F表示(不同的优化器对应的F的内容不同),即 g t ^ = F ( g t ) \hat{g_t}=F(g_{t}) gt^=F(gt),然后和学习率 l r l_r lr一起用于更新可学习参数 w w w,即 w = w − l r × g t ^ w = w - l_r\times \hat{g_t} w=w−lr×gt^。
在pytorch中,SGD定义为:
torch.optim.SGD(params,
lr=<required parameter>,
momentum=0,
dampening=0,
weight_decay=0,
nesterov=False)
params
:模型中需要被更新的可学习参数
lr
:学习率
momentum
:动量—通过上一次的 v t − 1 v_{t-1} vt−1和当前的偏导数 g t g_t gt,得到本次的 v t v_t vt,即 v t = v t − 1 × m o m e n t u m + g t v_{t}=v_{t-1}\times momentum+g_{t} vt=vt−1×momentum+gt,这个就是上述的函数F。
dampening
:是乘到偏导数g上的一个数,即: v t = v t − 1 × m o m e n t u m + g t × ( 1 − d a m p e n i n g ) v_{t}=v_{t-1}\times momentum+g_{t}\times (1-dampening) vt=vt−1×momentum+gt×(1−dampening)。注意:dampening在优化器第一次更新时,不起作用。
weight_decay
:权重衰减参数:其直接作用于 g t g_t gt,即: g t = g t + ( g t − 1 × w e i g h t d e c a y ) g_t = g_t + (g_{t-1}\times weight_{decay}) gt=gt+(gt−1×weightdecay)
流程计算
:
1.先计算: g t = g t + ( w × w e i g h t d e c a y ) g_t = g_t + (w\times weight_{decay}) gt=gt+(w×weightdecay) 得到当前轮关于 w w w的导数 g t g_t gt,在第一轮训练中, w w w为初始化的随机梯度值,或者加载的预训练权值。
2.在计算: v t = v t − 1 × m o m e n t u m + g t × ( 1 − d a m p e n i n g ) v_{t}=v_{t-1}\times momentum+g_{t}\times (1-dampening) vt=vt−1×momentum+gt×(1−dampening) 此时的 v 0 = 0 ; v 1 = g 1 v_0=0 ; v_1=g_1 v0=0;v1=g1
3.参数更新: w t = w t − l r × v t w_t = w_t - l_r\times v_t wt=wt−lr×vt
例:
假设函数 Y = W 2 Y = W^2 Y=W2, W W W的随机梯度初始值为100,则 Y Y Y的导为 2 W 2W 2W,假设 w e i g h t d e c a y weight_{decay} weightdecay为0.1,假设 m o m e n t u m momentum momentum为0.9,假设 d a m p e n i n g dampening dampening为0.5, l r l_r lr为0.01。第一次更新SGD默认不使用 d a m p e n i n g dampening dampening。
第一轮:
g 1 = g 1 + ( w × w e i g h t d e c a y ) = 200 + ( 100 ∗ 0.1 ) = 210 g_1 = g_1 + (w\times weight_{decay}) = 200 + (100*0.1)= 210 g1=g1+(w×weightdecay)=200+(100∗0.1)=210
v 1 = v 0 × m o m e n t u m + g 1 × ( 1 − d a m p e n i n g ) = 210 v_{1}=v_{0}\times momentum+g_{1}\times (1-dampening) = 210 v1=v0×momentum+g1×(1−dampening)=210
w = w − l r × v 1 = 100 − 0.01 ∗ 210 = 97.9 w = w - l_r\times v_1 = 100 - 0.01*210 = 97.9 w=w−lr×v1=100−0.01∗210=97.9
第二轮:
g 2 = g 2 + ( w × w e i g h t d e c a y ) = 195.8 + ( 97.9 ∗ 0.1 ) = 205.59 g_2 = g_2 + (w\times weight_{decay}) = 195.8 + (97.9*0.1)= 205.59 g2=g2+(w×weightdecay)=195.8+(97.9∗0.1)=205.59
v 2 = v 1 × m o m e n t u m + g 2 × ( 1 − d a m p e n i n g ) = 210 ∗ 0.9 + 205.59 ∗ ( 1 − 0.5 ) = 189 + 102.795 = 291.795 v_{2}=v_{1}\times momentum+g_{2}\times (1-dampening) = 210*0.9+205.59*(1-0.5)= 189+102.795=291.795 v2=v1×momentum+g2×(1−dampening)=210∗0.9+205.59∗(1−0.5)=189+102.795=291.795
w = w − l r × v 2 = 97.9 − 0.01 ∗ 291.795 = 97.9 − 2.92 = 94.98 w = w - l_r\times v_2 = 97.9- 0.01*291.795 = 97.9 -2.92=94.98 w=w−lr×v2=97.9−0.01∗291.795=97.9−2.92=94.98
为了验证这一流程的正确项,使用pytorch定义这个函数,并反向传播验证所计算得到的梯度值:
import torch
def test_sgd():
# 定义一个可学习参数w,初值是100
w = torch.tensor(data=[100], dtype=torch.float32, requires_grad=True)
# 定义SGD优化器,nesterov=False,其余参数都有效
optimizer = torch.optim.SGD(params=[w], lr=0.01, momentum=0.9, dampening=0.5, weight_decay=0.1, nesterov=False)
# 进行5次优化
for i in range(5):
y = w ** 2 # 优化的目标是让w的平方,即y尽可能小
optimizer.zero_grad() # 让w的偏导数置零
y.backward() # 反向传播,计算w的偏导数
optimizer.step() # 根据上述两个公式,计算一个v,然后作用到w
print('grad=%.2f, w=%.2f' % (w.grad, w.data)) # 查看w的梯度和更新后的值
if __name__=="__main__":
test_sgd()
'''
输入日志如下:
grad=200.00, w=97.90
grad=195.80, w=94.98
grad=189.96, w=91.36
grad=182.72, w=87.14
grad=174.28, w=82.42
'''
#------------------神经网络模型损失优化器定义---------------------
model = MyModel()
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
for epoch in range(1, epochs):
for i, (inputs, labels) in enumerate(train_loader):
output= model(inputs) #---这就是上面的 y=w*2 函数
loss = criterion(output, labels) #---损失计算
optimizer.zero_grad() #---梯度清零
loss.backward() #---误差反向传播
optimizer.step() #梯度更新
模型每次反向传导都会给各个可学习参数 w w w计算出一个偏导数 g t g_t gt,用于更新对应的参数 w w w。通常偏导数 g t g_t gt不会直接作用到对应的可学习参数 w w w上,而是通过优化器做一下处理,得到一个新的值 g t ^ \hat{g_t} gt^,处理过程用函数 F F F表示(不同的优化器对应的F的内容不同),即 g t ^ = F ( g t ) \hat{g_t}=F(g_t) gt^=F(gt),然后和学习率 l r l_r lr一起用于更新可学习参数 w w w,即 w = w − l r × g t ^ w = w - l_r \times \hat{g_t} w=w−lr×gt^。
在pytorch中,Adam定义为:
torch.optim.Adam(params,
lr=0.001,
betas=(0.9, 0.999),
eps=1e-08,
weight_decay=0,
amsgrad=False)
params
:模型里需要被更新的可学习参数
lr
:学习率
betas
:平滑常数 β 1 \beta_1 β1和 β 2 \beta_2 β2
eps
: ϵ \epsilon ϵ非常小的数,预防分母为0
weight-decay
:权值(可学习参数)衰减系数,用于修改偏导数: g t = g t + ( w × w e i g h t d e c a y ) g_t = g_t + (w\times weight_{decay}) gt=gt+(w×weightdecay),其中 g t g_t gt为可学习参数 w w w的偏导数。
amsgrad
:amsgrad和Adam并无直接关系。
计算过程如下:
初始1:学习率 lr
初始2:平滑常数(或者叫做衰减速率) β 1 , β 2 \beta_1,\beta_2 β1,β2,分别用于平滑 m m m和 v v v。
初始3:可学习参数 w w w
初始4: m 0 = 0 ; v 0 = 0 ; t = 0 m_0 = 0;v_0 = 0;t = 0 m0=0;v0=0;t=0
while 是否进行训练:
训练次数更新:t = t + 1
计算梯度: g t g_t gt(所有的可学习参数都有自己的梯度,因此 g t g_t gt表示的是全部梯度的集合)
累计梯度: m t = β 1 × m t − 1 + ( 1 − β 1 ) × g t m_t = \beta_1\times m_{t-1}+(1-\beta_1)\times g_t mt=β1×mt−1+(1−β1)×gt(每个导数对应一个m,因此m也是个集合)
累计梯度的平方: v t = β 2 × v t − 1 + ( 1 − β 2 ) × ( g t ) 2 v_t = \beta_2\times v_{t-1}+(1-\beta2)\times(g_t)^2 vt=β2×vt−1+(1−β2)×(gt)2(每个导数对应一个v,因此v也是个集合)
偏差纠正 m ^ \hat{m} m^: m t ^ = m t 1 − ( β 1 ) t \hat{m_t}=\frac{m_t}{1-(\beta_1)^t} mt^=1−(β1)tmt
偏差纠正 v ^ \hat{v} v^: v t ^ = v t 1 − ( β 2 ) t \hat{v_t}=\frac{v_t}{1-(\beta_2)^t} vt^=1−(β2)tvt
更新参数 w w w: w = w − l r × m t ^ v t ^ + ϵ w = w - l_r \times \frac{\hat{m_t}} {\sqrt{\hat{v_t}}+\epsilon} w=w−lr×vt^+ϵmt^
end while
例:
假设函数 Y = W 2 Y = W^2 Y=W2, W W W的随机梯度初始值为100,则 Y Y Y的导为 2 W 2W 2W,假设 w e i g h t d e c a y weight_{decay} weightdecay为0.1,假设 b e t a s = ( β 1 , β 2 ) betas= (\beta_1,\beta_2) betas=(β1,β2)为(0.9,0.999), l r l_r lr为0.01。
第一轮:t=1
w = 100 , g 1 = 2 w = 200 w=100,g_1 = 2w = 200 w=100,g1=2w=200, m 1 = β 1 × m 0 + ( 1 − β 1 ) × g 1 = 0.9 ∗ 0 + ( 1 − 0.9 ) 200 = 20 m_1 = \beta_1\times m_{0}+(1-\beta_1)\times g_1=0.9*0+(1-0.9)200=20 m1=β1×m0+(1−β1)×g1=0.9∗0+(1−0.9)200=20
v 1 = β 2 × v 0 + ( 1 − β 2 ) × ( g 1 ) 2 = 0.999 ∗ 0 + ( 1 − 0.999 ) ∗ 40000 = 40 v_1 = \beta_2\times v_{0}+(1-\beta2)\times(g_1)^2=0.999*0+(1-0.999)*40000=40 v1=β2×v0+(1−β2)×(g1)2=0.999∗0+(1−0.999)∗40000=40
m 1 ^ = m 1 1 − ( β 1 ) t = 20 / 0.1 = 200 \hat{m_1}=\frac{m_1}{1-(\beta_1)^t} = 20/0.1=200 m1^=1−(β1)tm1=20/0.1=200
v 1 ^ = v 1 1 − ( β 2 ) t = 40000 \hat{v_1}=\frac{v_1}{1-(\beta_2)^t} = 40000 v1^=1−(β2)tv1=40000
w = w − l r × m t ^ v t ^ + ϵ = 100 − 0.01 ∗ 1 = 99.99 w = w - l_r \times \frac{\hat{m_t}} {\sqrt{\hat{v_t}}+\epsilon}=100 - 0.01*1=99.99 w=w−lr×vt^+ϵmt^=100−0.01∗1=99.99
第二轮:t = 2
w = 99.99 , g 2 = 2 w = 199.98 w=99.99,g_2 = 2w = 199.98 w=99.99,g2=2w=199.98, m 2 = β 1 × m 1 + ( 1 − β 1 ) × g 2 = 0.9 ∗ 20 + 0.1 ∗ 199.98 = 37.998 m_2 = \beta_1\times m_{1}+(1-\beta_1)\times g_2=0.9*20+0.1*199.98=37.998 m2=β1×m1+(1−β1)×g2=0.9∗20+0.1∗199.98=37.998
v 2 = β 2 × v 1 + ( 1 − β 2 ) × ( g 2 ) 2 = 0.999 ∗ 40 + ( 1 − 0.999 ) ∗ 199.98 ∗ 199.98 = 79.68 v_2 = \beta_2\times v_{1}+(1-\beta2)\times(g_2)^2=0.999*40+(1-0.999)*199.98*199.98=79.68 v2=β2×v1+(1−β2)×(g2)2=0.999∗40+(1−0.999)∗199.98∗199.98=79.68
m 2 ^ = m 2 1 − ( β 1 ) t = 37.998 / 0.01 = 3799.8 \hat{m_2}=\frac{m_2}{1-(\beta_1)^t} = 37.998/0.01=3799.8 m2^=1−(β1)tm2=37.998/0.01=3799.8
v 2 ^ = v 2 1 − ( β 2 ) t = 79.68 / 0.000001 = 79680000 \hat{v_2}=\frac{v_2}{1-(\beta_2)^t} = 79.68/0.000001=79680000 v2^=1−(β2)tv2=79.68/0.000001=79680000
w = w − l r × m t ^ v t ^ + ϵ = 99.99 − 0.01 ∗ 3799.8 / 8926 = 99.98 w = w - l_r \times \frac{\hat{m_t}} {\sqrt{\hat{v_t}}+\epsilon}=99.99-0.01*3799.8/8926=99.98 w=w−lr×vt^+ϵmt^=99.99−0.01∗3799.8/8926=99.98
实践是检验真理的唯一标准:
import torch
def test_sgd():
# 定义一个可学习参数w,初值是100
w = torch.tensor(data=[100], dtype=torch.float32, requires_grad=True)
# 定义SGD优化器,nesterov=False,其余参数都有效
#optimizer = torch.optim.SGD(params=[w], lr=0.01, momentum=0.9, dampening=0.5, weight_decay=0.1, nesterov=False)
optimizer = torch.optim.Adam(params=[w],lr=0.01,betas=(0.9, 0.999),eps=1e-08,weight_decay=0.1,amsgrad=False)
# 进行5次优化
for i in range(5):
y = w ** 2 # 优化的目标是让w的平方,即y尽可能小
optimizer.zero_grad() # 让w的偏导数置零
y.backward() # 反向传播,计算w的偏导数
optimizer.step() # 根据上述两个公式,计算一个v,然后作用到w
print('grad=%.2f, w=%.2f' % (w.grad, w.data)) # 查看w的梯度和更新后的值
if __name__=="__main__":
test_sgd()
'''
输入日志如下:
grad=200.00, w=99.99
grad=199.98, w=99.98
grad=199.96, w=99.97
grad=199.94, w=99.96
grad=199.92, w=99.95
'''
代码例子来自:https://blog.csdn.net/qq_37541097 非常厉害的一位博主,跟着他的博客和视频学习到很多,再次表示感谢❤❤❤