栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > Python

优化器简单概述

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

优化器简单概述

文章目录
  • 优化算法
    • 前言
    • 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​)=(∂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:

  • 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 ​t​lr​×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_rtimes g_t ηt​=lr​×gt​
代入步骤4,可以得到梯度更新公式为: w t + 1 = w t − l r × g t w_{t+1} = w_t - l_rtimes g_t wt+1​=wt​−lr​×gt​
SGD最大的缺点是下降速度慢,而且可能会在沟壑的两边持续震荡,停留在一个局部最优点。
g t g_t gt​是当前batch的梯度,所以 l r × g t l_rtimes 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_1times 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_1times m_{t-1}+(1-beta_1)times g_t)}{sqrt V_t} wt+1​=wt​−V ​t​lr​(×β1​×mt−1​+(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_rtimes hat{g_t} w=w−lr​×gt​^​。
在pytorch中,SGD定义为:

torch.optim.SGD(params,
                lr=,
                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 + (wtimes 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_rtimes 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 + (wtimes 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_rtimes 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 + (wtimes 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_rtimes 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()   #梯度更新
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=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 + (wtimes 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_1times 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_2times 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_1times 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_2times 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_1times 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_2times 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 非常厉害的一位博主,跟着他的博客和视频学习到很多,再次表示感谢❤❤❤

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/1037290.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号