优化器简单概述

文章目录

  • 优化算法
    • 前言
    • SGD理解1
    • SGD理解2
      • 原理:
    • Adam 优化算法
      • 原理:

————————————————————————————————————

优化算法

前言

参考: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)=(x0f,x1f,...,xnf)
梯度的提出只为回答一个问题:
 函数在变量空间的某一点处,沿着哪一个方向有最大的变化率?(针对这一点,我们都知道对于神经网络而言,神经元节点可看做变量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

  • 1 计算目标函数关于当前参数的梯度(可以理解为计算损失函数关于当前网络层参数的梯度): g t = ∇ f ( w t ) g_t = \nabla f(w_t) gt=f(wt)
  • 2 根据历史梯度计算一阶动量和二阶动量: m t = ϕ ( g 1 , g 2 . . . . g t ) m_t = \phi(g_1,g_2....g_t) mt=ϕ(g1,g2....gt) V t = φ ( g 1 , g 2 . . . . g t ) V_t = \varphi(g_1,g_2....g_t) Vt=φ(g1,g2....gt)
  • 3 计算当前时刻的下降梯度: η t = l r × m t V t \eta_t = \frac{l_r \times m_t}{\sqrt V_t} ηt=V tlr×mt
  • 4 根据下降梯度进行最优参数的更新: w t + 1 = w t − η t w_{t+1} = w_t - \eta_t wt+1=wtηt
    掌握了这个框架,你可以轻轻松松设计自己的优化算法。

SGD理解1

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=wtlr×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×mt1+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=wtV tlr(×β1×mt1+(1β1)×gt)

SGD理解2

原理:

模型每次反向传导都会给各个可学习参数 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=wlr×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} vt1和当前的偏导数 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=vt1×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=vt1×momentum+gt×(1dampening)。注意: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+(gt1×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=vt1×momentum+gt×(1dampening) 此时的 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=wtlr×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+(1000.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×(1dampening)=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=wlr×v1=1000.01210=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.90.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×(1dampening)=2100.9+205.59(10.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=wlr×v2=97.90.01291.795=97.92.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()   #梯度更新

Adam 优化算法

原理:

模型每次反向传导都会给各个可学习参数 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=wlr×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×mt1+(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×vt1+(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=wlr×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.90+(10.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.9990+(10.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=wlr×vt^ +ϵmt^=1000.011=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.920+0.1199.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.99940+(10.999)199.98199.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=wlr×vt^ +ϵmt^=99.990.013799.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 非常厉害的一位博主,跟着他的博客和视频学习到很多,再次表示感谢❤❤❤

你可能感兴趣的:(毕业or总结,深度学习专栏,机器学习,算法,人工智能)