找回密码
 立即注册
查看: 282|回复: 0

梯度下降与优化算法

[复制链接]
发表于 2022-9-27 14:21 | 显示全部楼层 |阅读模式
ps:知乎文本编辑器有点难用,格式排版不太友好,有排版好的pdf版本,如有需要前往gzh私信:别样AI




在深度学习中,通常会先定义损失函数。一旦我们有了损失函数,我们就可以使用优化算法来尝试最小化损失。在优化中,损失函数通常被称为优化问题的目标函数。按照传统和惯则,大多数优化算法都关注的是最小化,而优化算法离不开梯度
方向导数和梯度

方向导数定义

设函数z=f(x,y) 在点P(x,y)的某一邻域U(P) 内有定义,自点P引射线l,自x轴的正向到射线l的转角为 \varphi, P^{\prime}(x+\Delta x, y+\Delta y) \in U(P)为 l上的另一点,
若 \lim _{\rho \rightarrow 0} \frac{f(x+\Delta x, y+\Delta y)-f(x, y)}{\rho}\left(\rho=\sqrt{(\Delta x)^{2}+(\Delta y)^{2}}\right)存在,则称此极限值为 f(x,y)在点P沿方向 l的方向导数,记作\frac{\partial f}{\partial l} 。
其计算公式为:\frac{\partial f}{\partial l}=\frac{\partial f}{\partial x} \cos \varphi+\frac{\partial f}{\partial y} \sin \varphi
三元函数u=f(x,y,z)在点P(x,y,z)沿着方向l(方向角为\alpha ,\beta ,\gamma )的方向导数的定义为 \frac{\partial f}{\partial l}=\lim _{\rho \rightarrow 0} \frac{f(x+\Delta x, y+\Delta y, z+\Delta z)-f(x, y, z)}{\rho}
其中 \rho=\sqrt{(\Delta x)^{2}+(\Delta y)^{2}+(\Delta z)^{2}} 且 P^{\prime}(x+\Delta x, y+\Delta y, z+\Delta z)为 l上的点,
其计算公式为: \frac{\partial f}{\partial l}=\frac{\partial f}{\partial x} \cos \alpha+\frac{\partial f}{\partial y} \cos \beta+\frac{\partial f}{\partial z} \cos \gamma
<hr/>同济版高数教材对方向导数定义:



梯度定义

梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)
设二元函数z=f(x, y)在平面区域 \mathrm{D}上具有一阶连续偏导数, 则对于每一个点 \mathrm{P}(\mathrm{x}, \mathrm{y})都可定出一个向量 \left\{\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right\}=f_{x}(x, y) \bar{i}+f_{y}(x, y) \bar{j},
该函数就称为函数 z=f(x, y) 在点 \mathrm{P}(\mathrm{x}, \mathrm{y}) 的梯度, 记作 \mathrm{gradf}(\mathrm{x}, \mathrm{y}) 或 \nabla f(x, y),
即有: \operatorname{gradf}(\mathrm{x}, \mathrm{y})=\nabla f(x, y)=\left\{\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right\}=f_{x}(x, y) \bar{i}+f_{y}(x, y) \bar{j}
其中 \nabla=\frac{\partial}{\partial x} \bar{i}+\frac{\partial}{\partial y} \bar{j} 称为 (二维的)向量微分算子或Nabla算子, \nabla f=\frac{\partial f}{\partial x} \bar{i}+\frac{\partial f}{\partial y} \bar{j}
设 e=\{\cos \alpha, \cos \beta\} 是方向上的单位向量, 则

\begin{aligned} &\frac{\partial f}{\partial l}=\frac{\partial f}{\partial x} \cos \alpha+\frac{\partial f}{\partial y} \cos \beta=\left\{\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right\}\{\cos \alpha, \cos \beta\} \\ &=\operatorname{gradf}(x, y) e=|\operatorname{gradf}(x, y)||e| \cos [\operatorname{grad} f(x, y), e] \end{aligned}

由于当方向I与梯度方向一致时, 有 \cos [\operatorname{grad} f(x, y), e]=1 所以当I与梯度方向一致时,方向导数 \frac{\partial f}{\partial l} 有最大值,也就是说函数沿着梯度的方向增长最快,且最大值为梯度的模, 即 |\operatorname{gradf}(x, y)|=\sqrt{\left(\frac{\partial f}{\partial x}\right)^{2}+\left(\frac{\partial f}{\partial y}\right)^{2}}
因此说, 函数在一点沿梯度方向的变化率最大, 最大值为该梯度的模,换言之:一个函数在某点沿着梯度的方向增长最快,而逆着梯度的方向则减小最快
<hr/>同济版高数教材对梯度定义:



方向导数和梯度下降总结

方向导数是一个值而梯度是一个向量。方向导数是函数在各个方向的斜率,而梯度是斜率最大的方向,梯度的值是方向导数最大的值
\left.\frac{\partial f}{\partial \imath}\right|_{\left(x_{0}, y_{0}\right)}=f\left(x_{0}, y_{0}\right)_{x} \cos \alpha+f\left(x_{0}, y_{0}\right)_{y} \cos \beta=\operatorname{gradf}\left(x_{0}, y_{0}\right) \cdot e_{\iota}=\left|\operatorname{gradf}\left(x_{0}, y_{0}\right)\right| \cos \theta
当 \theta=0 时,e与梯度方向相同时,方向导数最大,函数增加最快 当 \theta=\pi时, e与梯度方向相反时,方向导数最小,函数减少最快 当 \theta=\frac{\pi}{2} 时,e与梯度方向垂直时,方向导数为0, 函数变化率为零
反向传播通过梯度下降的方式来更新参数,以使得loss最小
公式如下: \omega=\omega-\eta \cdot \frac{\partial \operatorname{Loss}}{\partial \omega}
\eta代表学习率,作为参数控制梯度下降的步长。
梯度下降的变种算法

梯度下降是机器学习的常用优化方法,根据每次使用的样本的数据可以将梯度下降分为三种形式:批量梯度下降(Batch Gradient Descent),随机梯度下降(Stochastic Gradient Descent)以及小批量梯度下降(Mini-batch Gradient Descent)。
批量梯度下降(Batch Gradient Descent)

Vanilla梯度下降法,又称为批梯度下降法(batch gradient descent),在整个训练数据集上计算损失函数关于参数\theta的梯度: \theta=\theta-\eta \cdot \nabla_{\theta} \mathrm{J}(\theta) 批量梯度下降法(Batch Gradient Descent,BGD)更新一次参数需要计算整个数据集所有样本的梯度,因此更新速度非常慢,对于凸优化问题会收敛到全局最优点、而非凸优化问题则会收敛到局部最优点,这样计算的梯度比较稳定,相比随机梯度下降不那么容易震荡;但非常慢且无法将大量数据放到内存中计算,更无法应用于在线学习系统,即不能在线更新模型,即在运行的过程中,不能增加新的样本
批量梯度下降代码:
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
对于给定的迭代次数,首先,我们利用全部数据集计算损失函数关于参数向量params的梯度向量params_grad。注意,最新的深度学习库中提供了自动求导的功能,可以有效地计算关于参数梯度。如果你自己求梯度,那么,梯度检查是一个不错的主意(关于如何正确检查梯度的一些技巧可以参见http://cs231n.github.io/neural-networks-3/)。 利用梯度的方向和学习率更新参数,学习率决定我们将以多大的步长更新参数。对于凸误差函数,批梯度下降法能够保证收敛到全局最小值,对于非凸函数,则收敛到一个局部最小值。
随机梯度下降(Stochastic Gradient Descent)

相反,随机梯度下降法(stochastic gradient descent, SGD)根据每一条训练样本x^{(i)}和标签y^{(i)}更新参数: \theta=\theta-\eta \cdot \nabla_{\theta} \mathrm{J}\left(\theta ; \mathrm{x}^{(\mathrm{i})} ; \mathrm{y}^{(\mathrm{i})}\right)
随机梯度下降(Stochastic Gradient Descent, SGD)每次从训练样本中随机抽取一个样本计算loss和梯度并对参数进行更新,训练时每个epoch结束后注意必须要shuffle训练集;但这种优化算法比较弱,容易走偏,有时可用于在线学习(Online Learning)系统,可使系统快速学到新变化。 对于大数据集,因为批梯度下降法在每一个参数更新之前,会对相似的样本计算梯度,所以在计算过程中会有冗余。而SGD在每一次更新中只执行一次,从而消除了冗余。因而,通常SGD的运行速度更快,同时,可以用于在线学习。SGD以高方差频繁地更新,导致目标函数出现剧烈波动。
随机梯度下降代码:
for i in range(nb_epochs):
np.random.shuffle(data)#在每一次循环中,我们打乱训练样本。
for example in data:
  params_grad = evaluate_gradient(loss_function, example, params)
  params = params - learning_rate * params_grad
与批梯度下降法的收敛会使得损失函数陷入局部最小相比,由于SGD的波动性,一方面,波动性使得SGD可以跳到新的和潜在更好的局部最优。另一方面,这使得最终收敛到特定最小值的过程变得复杂,因为SGD会一直持续波动。然而,已经证明当我们缓慢减小学习率,SGD与批梯度下降法具有相同的收敛行为,对于非凸优化和凸优化,可以分别收敛到局部最小值和全局最小值。与批梯度下降的代码相比,SGD的代码片段仅仅是在对训练样本的遍历和利用每一条样本计算梯度的过程中增加一层循环。
小批量梯度下降(Mini-batch Gradient Descent)

介于随机梯度下降和批量梯度下降之间的是小批量梯度下降(Mini-Batch Gradient Descent,MBGD),即每次随机抽取m个样本,以它们的梯度均值作为梯度的近似估计。它可以降低参数更新时的方差,收敛更稳定,另一方面可以充分地利用深度学习库中高度优化的矩阵操作来进行更有效的梯度计算。 小批量梯度下降法最终结合了上述两种方法的优点,在每次更新时使用n个小批量训练样本:
\theta=\theta-\eta \cdot \nabla_{\theta} \mathrm{J}\left(\theta ; \mathrm{x}^{(\mathrm{i}: \mathrm{i}+\mathrm{n})} ; \mathrm{y}^{(\mathrm{i}: \mathrm{i}+\mathrm{n})}\right)
这种方法:

  • 减少参数更新的方差,这样可以得到更加稳定的收敛结果;
  • 可以利用最新的深度学习库中高度优化的矩阵优化方法,高效地求解每个小批量数据的梯度。通常,小批量数据的大小在50到256之间,也可以根据不同的应用有所变化。当训练神经网络模型时,小批量梯度下降法是典型的选择算法,当使用小批量梯度下降法时,也将其称为SGD。
小批量梯度下降代码:
#在代码中,不是在所有样本上做迭代,现在只是在大小为50的小批量数据上做迭代:
for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
  params_grad = evaluate_gradient(loss_function, batch, params)
  params = params - learning_rate * params_grad
总结

上面介绍的梯度下降有以下的难点:

  • 学习率的设置
  • 极小值点,鞍点,局部最小值
(1)梯度下降优化中的第一个难点就是上面提到的,学习率的设置问题。学习速率过小时收敛速度慢,而过大时导致训练震荡,而且可能会发散。
(2)而非凸误差函数普遍出现在神经网络中,在优化这类函数时,另一个难点是梯度下降的过程中有可能陷入到局部极小值中。也有研究指出这种困难实际上并不是来自局部最小值,而更多的来自鞍点,即那些在一个维度上是递增的,而在另一个维度上是递减的。这些鞍点通常被具有相同误差的点包围,因为在任意维度上的梯度都近似为0,所以SGD很难从这些鞍点中逃开。(SGD尤甚,因为鞍点处所有维度梯度趋近于0,只收敛到局部最优点。)
鞍点(Saddle point)在微分方程中,沿着某一方向是稳定的,另一条方向是不稳定的奇点,叫做鞍点。对于拥有两个以上变量的曲线,它的曲面在鞍点好像一个马鞍,在某些方向往上曲,在其他方向往下曲。由于鞍点周围的梯度都近似为0,梯度下降如果到达了鞍点就很难逃出来。下图是z=x^{2}-y^{2}图形,在x轴方向向上曲,在y轴方向向下曲,像马鞍,鞍点为(0,0)




(3)可以通过诸如退火annealing,即根据预先确定的学习率或当两次epoch间发生的变化低于一个阈值时,降低学习率的值。这些规划和阈值必须提前定义,因此无法动态适应数据集的特征
(4)由于所有参数的更新使用相同的学习率,对于稀疏数据且出现频率差别非常大的特征,我们可能不想让它们更新相同的程度,如对于很少出现的特征进行较大的更新(较大学习率)、对经常出现的特征进行较小的更新(较小学习率),这样以上的方法都不能满足要求
梯度下降的优化算法

动量梯度下降(Momentum,MGD)

介绍:

梯度下降的问题: 对于上面的梯度下降也存在一定的问题,考虑一个二维输入[x_{1},x_{2}],输出的损失函数 L : R^2 \rightarrow R,使用梯度下降法,每次都会朝着目标函数下降最快的方向,这也称为最速下降法,以函数\small{f(x)=0.1x_{1}^{2}+2x_{2}^{2}}为例(后面演示代码均以此函数为例),得到的梯度下降示意图如下图所示:
学习率eta=0.4时梯度下降:
(较小的学习率导致水平方向上参数在更新的时候太过于缓慢,最终收敛非常慢)


学习率eta=0.6时梯度下降:
(较大的学习率使得水平收敛有所改善,但竖直方向上参数更新太过,解的质量更差)


由此可见,对于函数\small{f(x)=0.1x_{1}^{2}+2x_{2}^{2}}在竖直方向上(x_{2}),梯度非常大,在水平方向上(x_{1}),梯度相对较小,所以我们在设置学习率的时候就不能设置太大,为了防止竖直方向上参数更新太过了,这样一个较小的学习率又导致了水平方向上参数在更新的时候太过于缓慢,所以就导致最终收敛起来非常慢
解决思路(动量法提出): 对于上面梯度下降的问题,动量法应用而生,Momentum是模拟物理学动量的概念,累积之前的动量加入梯度,能够在相关方向加速SGD,抑制震荡,从而加快收敛速度



Momentum梯度下降算法在与原有梯度下降算法的基础上,引入了动量的概念,使网络参数更新时的方向会受到前一次梯度方向的影响,换句话说,每次梯度更新都会带有前几次梯度方向的惯性,使梯度的变化更加平滑,这一点上类似一阶马尔科夫假设;Momentum梯度下降算法能够在一定程度上减小权重优化过程中的震荡问题。

Momentum使用了一种动量的思想,使得loss曲线下降时方向抖动不那么严重。更新公式如下(\eta_{t}为学习率,g_{t}为梯度向量): \begin{aligned} &v_{t} \leftarrow \gamma v_{t-1}+\eta_{t} g_{t} \\ &x_{t} \leftarrow x_{t-1}-v_{t} \end{aligned} 在GD中,参数更新量 v_{t} 的值是 \eta_{t}g_{t}, 只是根据当前的位置,沿梯度下降最大的方向更新,不考虑之前迭代下降的方向,这可能会使梯度方向抖动严重。
代码演示:

动量法代码:
def momentum_2d(x1, x2, v1, v2):
    v1 = beta * v1 + 0.2 * x1*eta
    v2 = beta * v2 + 4 * x2*eta
    return x1 - v1, x2 - v2, v1, v2

eta, beta = 0.6, 0.5 # 学习率,和gamma
d2l.show_trace_2d(f_2d, d2l.train_2d(momentum_2d))
动量法梯度下降示意图:


而 Momentum 动量法则会同时考虑之前的梯度方向v_{t-1}与该位置的梯度方向 g_{t} ,保留了部分之前的梯度,使得下一次的梯度方向不会跟前一次相差太远,从而使得方向抖动不那么严重。如上式,\gamma为动量超参数,用于控制前一轮梯度方向信息所占的比例,当 \gamma=0时退化成GD。
Momentum的缺陷:
计算梯度时,所有参数的更新都是使用同一个学习率,即 g_{t}向量中所有的梯度都共享学习率 \eta_{t}。 如果有的参数梯度较大,则需要使用小学习率保证不会梯度爆炸,但又会导致梯度较小的参数更新太慢,所以需要每个参数拥有自己的学习率
pytorch中使用:

from torch import optim
...
model = Model()
optimizer = optim.SGD(model.parameters(), lr=0.005, momentum=0.9)
...
AdaGrad算法

介绍:

在基本的梯度优化算法中,有个常见问题是,要优化的变量对于目标函数的依赖是不相同的。有些变量,已经优化到极小值附近,但是有些变量仍然离极小值很远,位于梯度较大的地方。这时候如果对所有的变量都使用同一个全局的优学习速率就有可能出现问题,学习率太小,则梯度很大的变量就会收敛的很慢;如果梯度很大,已经优化的差不多的变量可能会不稳定。 针对这个问题,Jhon Duchi提出了AdaGrad(Adaptive Gradient),自适应学习速率。AdaGrad的基本思想是对每个变量使用不同的学习率。在最初,学习速率较大,用于快速下降。随着优化过程的进行,对于已经下降很多的变量,则减小学习率;对于没有怎么下降的变量,则仍保持大的学习率
只有在这些不常见的特征出现时,与其相关的参数才会得到有意义的更新。 鉴于学习率下降,我们可能最终会面临这样的情况:常见特征的参数相当迅速地收敛到最佳值,而对于不常见的特征,我们仍缺乏足够的观测以确定其最佳值。 换句话说,学习率要么对于常见特征而言降低太慢,要么对于不常见特征而言降低太快。
AdaGrad 解决了所有参数共享同一学习率的问题。
如下式所示:
\begin{gathered} s_{t}=s_{t-1}+g_{t} \odot g_{t} \\ x_{t}=x_{t-1}-\frac{\eta}{\sqrt{s_{i}+\varepsilon}} \odot g_{t} \end{gathered}
对于每次迭代,用 S_{t}向量记录之前所有梯度 g_{t}平方值的累加 (二阶动量),初始时刻S_{t}为0。然后在计算更新量时,根据参数之前累积的各自的梯度平方值计算对应的学习率 (\varepsilon是为了避免除数为0而添加的常数),梯度累加值大的参数对于小的学习率,反之对于大的学习率。
代码演示:

AdaGrad示意代码:
def adagrad_2d(x1, x2, s1, s2):
    eps = 1e-6
    g1, g2 = 0.2 * x1, 4 * x2
    s1 += g1 ** 2
    s2 += g2 ** 2
    x1 -= eta / math.sqrt(s1 + eps) * g1
    x2 -= eta / math.sqrt(s2 + eps) * g2
    return x1, x2, s1, s2
AdaGrad算法梯度下降示意图:





AdaGrad的缺陷:
如果某个参数的梯度直都较大,那么该参数的学习率将下降较快,更新也会越来越慢;反之,如果某权重的梯度直都较小,那么该元素的学习率将下降较慢,维持大的学习率所以更新较快。AdaGrad算法因为 S_{t} 一直累加,导致学习率不断下降,在迭代后期可能由于学习率过小,更新缓慢
pytorch中使用:

torch.optim.Adagrad(params, lr=0.01, lr_decay=0, weight_decay=0, initial_accumulator_value=0)
# params (iterable) – iterable of parameters to optimize or dicts defining parameter groups模型参数,可迭代类型
# lr (float, optional) – learning rate (default: 1e-2)学习率(默认: 1e-2)
# lr_decay (float, optional) – learning rate decay (default: 0)学习率衰减(默认: 0)
# weight_decay (float, optional) – weight decay (L2 penalty) (default: 0)权重衰减(L2惩罚)(默认: 0)
RMSProp算法(均方根)

介绍:

RMSprop (root mean square propagation) 是Hinton在他的课程上讲到的,算是对Adagrad算法的改进,主要是解学习速率过快衰减的问题。其思路引入了动量(指数加权移动平均数)的方法,引入了超参数\gamma,在累积梯度的平方项近似衰减。 RMSProp 解决了学习率不断减小,后期学习缓慢的问题。
更新公式如下: \begin{gathered} s_{t}=\gamma s_{t-1}+(1-\gamma) g_{t} \odot g_{t} \\ x_{t}=x_{t-1}-\frac{\eta}{\sqrt{s_{t+\varepsilon}}} \odot g_{t} \end{gathered}
RMSProp将动量思想引入梯度的累加计算中,使用超参数\gamma 控制当前梯度值与历史累加的梯度值各自所占的权重,每次只累加前面S_{t}的部分值,这样就不会导致S_{t}一直累加增大,导致学习率一直下降的问题,比如**某参数此次梯度值较小,那么 S_{t}对应位置的累加值也会缩小,不会一直增大
代码演示:

RMSProp示意代码:
def rmsprop_2d(x1, x2, s1, s2):
    g1, g2, eps = 0.2 * x1, 4 * x2, 1e-6
    s1 = gamma * s1 + (1 - gamma) * g1 ** 2
    s2 = gamma * s2 + (1 - gamma) * g2 ** 2
    x1 -= eta / math.sqrt(s1 + eps) * g1
    x2 -= eta / math.sqrt(s2 + eps) * g2
    return x1, x2, s1, s2

RMSProp梯度下降示意图:


RMSProp的缺陷:
同AdaGrad,都只是解决了参数对应同一学习率的问题,就是将参数x_{t}更新公式中 g_{t}  前面的学习率改进为自适应学习率,并没有对梯度g_{t}进行改进, 丢弃了Momentum动量的做法,所以仍会出现loss曲线方向抖动的问题
pytorch中使用:

torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)
# params (iterable) – iterable of parameters to optimize or dicts defining parameter groups
# lr (float, optional) – learning rate (default: 1e-2)
# momentum (float, optional) – momentum factor (default: 0)
# alpha (float, optional) – smoothing constant (default: 0.99)
# eps (float, optional) – term added to the denominator to improve numerical stability (default: 1e-8)
# centered (bool, optional) – if True, compute the centered RMSProp, the gradient is normalized by an estimation of its variance
# weight_decay (float, optional) – weight decay (L2 penalty) (default: 0)
AdaDelta

介绍:

除了RMSProp算法以外,另一个常用优化算法AdaDelta算法也针对AdaGrad算法在迭代后期可能较难找到有用解的问题做了改进 。不一样的是,AdaDelta算法没有学习率这个超参数。 它通过使用有关自变量更新量平方的指数加权移动平均的项来替代RMSProp算法中的学习率
AdaDelta算法也像RMSProp算法一样,使用了小批量随机梯度\boldsymbol{g}_t 按元素平方的指数加权移动平均变量\boldsymbol{s}_t

  • 在时间步0,它的所有元素被初始化为0。给定超参数0 \leq \rho < 1(对应RMSProp算法中的\gamma)
  • 在时间步t > 0,同RMSProp算法一样计算
\boldsymbol{s}_{\mathrm{t}} \leftarrow \rho \boldsymbol{s}_{\mathrm{t}-1}+(1-\rho) \boldsymbol{g}_{\mathrm{t}} \odot \boldsymbol{g}_{\mathrm{t}} . 与RMSProp算法不同的是,AdaDelta算法还维护一个额外的状态变量\Delta\boldsymbol{x}_t,其元素同样在时间步0时被初始化为0。我们使用\Delta\boldsymbol{x}_{t-1}来计算自变量的变化量: \boldsymbol{g}_{\mathrm{t}}^{\prime} \leftarrow \sqrt{\frac{\Delta \boldsymbol{x}_{\mathrm{t}-1}+\epsilon}{\boldsymbol{s}_{\mathrm{t}}+\epsilon}} \odot \boldsymbol{g}_{\mathrm{t}} 其中\epsilon是为了维持数值稳定性而添加的常数,如10^{-5} 。接着更新自变量: {x}_{t} \leftarrow {x}_{t-1}-{g}_{t}^{\prime} 最后,我们使用\Delta\boldsymbol{x}_t来记录自变量变化量\boldsymbol{g}'_t按元素平方的指数加权移动平均: \Delta \boldsymbol{x}_{\mathrm{t}} \leftarrow \rho \Delta \boldsymbol{x}_{\mathrm{t}-1}+(1-\rho) \boldsymbol{g}_{\mathrm{t}}^{\prime} \odot \boldsymbol{g}_{\mathrm{t}}^{\prime}
可以看到,如不考虑\epsilon的影响,AdaDelta算法跟RMSProp算法的不同之处在于使用\sqrt{\Delta\boldsymbol{x}_{t-1}} 来替代学习率\eta。
代码演示:

PS:AdaDelta这个演示效果暂时不太理想,先占坑。。。。。。
AdaDelta算法代码:
def adadelta_2d(x1,x2,s1,s2,delta1,delta2):
    eps = 1e-5
    s1 = rho * s1 + (1 - rho) * ((0.2 * x1) ** 2)
    s2 = rho * s2 + (1 - rho) * ((4 * x2) ** 2)
    g1 = math.sqrt((delta1 + eps)/(s1+eps))*0.2*x1
    g2 = math.sqrt((delta2 + eps)/(s2+eps))*4*x2
   
    x1 -= g1
    x2 -= g2
   
    delta1 = rho*delta1 + (1-rho)*g1*g1
    delta2 = rho*delta2 + (1-rho)*g2*g2
    #print("x1:{},x2:{},s1:{},s2:{},d1:{},d2:{}".format(x1,x2,s1,s2,delta1,delta2))
    return x1,x2,s1,s2,delta1,delta2
   
AdaDelta梯度下降示意图:



pytorch中使用:

torch.optim.adadelta(params, lr=1.0, rho=0.9, eps=1e-6, weight_decay=0)
# params:模型参数,可迭代类型lr:学习率,必须参数,浮点型
# rho:平滑常数,浮点型
# eps:防止分母为0的参数,增加算法稳定性
# weight_decay:权重衰减参数,可选参数,浮点型,默认为0
Adam

介绍:

Adaptive Moment Estimation(Adam) 也是一种不同参数自适应不同学习速率方法,与Adadelta与RMSprop区别在于,它计算历史梯度衰减方式不同,不使用历史平方衰减,其衰减方式类似动量考虑了自适应学习率以及动量梯度,相当于是RMSProp和Momentum的结合,效果一般较好
相关公式如下:
\begin{gathered} m_{t}=\beta_{1} m_{t-1}+\left(1-\beta_{1}\right) g_{t} \\ v_{t}=\beta_{2} v_{t-1}+\left(1-\beta_{2}\right) g_{t}^{2} \\ \end{gathered}
m_{t}与v_{t}分别是梯度的带权平均和带权有偏方差,初始为0向量,Adam的作者发现他们倾向于0向量(接近于0向量),特别是在衰减因子(衰减率) \beta_{1}, \beta_{2}接近于1时。
为了改进这个问题,对m_{t}与v_{t}进行偏差修正(bias-corrected):
\begin{gathered} \hat{m}_{t}=\frac{m_{t}}{1-\beta_{1}^{t}} \\ \hat{v}_{t}=\frac{v_{t}}{1-\beta_{2}^{t}} \\ \end{gathered}
以非常类似于RMSProp算法的方式重新缩放梯度以获得:
{g}_{t}^{\prime}=\frac{\eta}{\sqrt{\hat{v}_{t}}+\epsilon} \hat{m}_{t}
与RMSProp不同,更新使用动量\hat{m}_{t}而不是梯度本身。 此外,由于使用\tiny{\frac{1}{\sqrt{\hat{v}_{t}}+\epsilon}}而不是\tiny{\frac{1}{\sqrt{\hat{v}_{t}+\epsilon}}}进行缩放,两者会略有差异。 前者在实践中效果略好一些,因此与RMSProp算法有所区分。
进行更新: {x}_{t} \leftarrow {x}_{t-1}-{g}_{t}^{\prime}
建议默认值: \beta_{1}=0.9, \beta_{2}=0.999, \epsilon=10^{-8}
m_{t}是一阶动量的累加,保留了前一轮梯度方向信息的动量梯度,类似Momentum的做法;v_{t}是二阶动量的累加,用于计算自适应学习率,类似于RMSProp的做法。更新参数 x_{t+1}时同时使用动量梯度与自适应学习率,效果要优于之前的几种方法。
代码演示:

Adam示意代码:
def adam_2d(x1,x2,m1,m2,v1,v2,t=1):
    beta1, beta2, eps = 0.9, 0.999, 1e-6
    m1 = beta1 * m1 + (1 - beta1) * (0.2 * x1)
    m2 = beta1 * m2 + (1 - beta1) * (4 * x2)
    v1 = beta2 * v1 + (1 - beta2) * ((0.2 * x1) ** 2)
    v2 = beta2 * v2 + (1 - beta2) * ((4 * x2) ** 2)
    m1_bias = m1 / (1-beta1**t)
    m2_bias = m2 / (1-beta1**t)
    v1_bias = v1 / (1-beta2**t)
    v2_bias = v2 / (1-beta2**t)
    g1 = eta*m1_bias/(math.sqrt(v1_bias)+eps)
    g2 = eta*m2_bias/(math.sqrt(v2_bias)+eps)
    x1 -= g1
    x2 -= g2
    t += 1
    return x1,x2,m1,m2,v1,v2,t
Adam梯度下降图:



pytorch中使用:

torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
# params (iterable) – iterable of parameters to optimize or dicts defining parameter groups
# lr (float, optional) – learning rate (default: 1e-3)
# betas (Tuple[float, float], optional) – coefficients used for computing running averages of gradient and its square (default: (0.9, 0.999))
# eps (float, optional) – term added to the denominator to improve numerical stability (default: 1e-8)
# weight_decay (float, optional) – weight decay (L2 penalty) (default: 0)
# amsgrad (boolean, optional) – whether to use the AMSGrad variant of this algorithm from the paper On the Convergence of Adam and Beyond (default: False)
总结:

以上方法可以分为两大类:

  • 一大类方法是SGD及其改进(加Momentum);
  • 另外一大类是Per-parameter adaptive learning rate methods(逐参数适应学习率方法,也就是对不同参数使用不同学习率),包括AdaGrad、RMSProp、AdaDelta,Adam等。
损失函数的等高线:



在鞍点处的学习情况,注意SGD很难突破对称性,一直卡在顶部。而RMSProp之类的方法能够看到马鞍方向有很低的梯度。因为在RMSProp更新方法中的分母项,算法提高了在该方向的有效学习率,使得RMSProp能够继续前进:



附录

优化器动态学习率设置(scheduler):

可以让学习率随着epoch的增大而减小,此处以ExponentialLR为例 使用示例:
from torch.optim.lr_scheduler import ExponentialLR
...
optimizer = optim.SGD(catp.parameters(), lr=0.005, momentum=0.9)
scheduler = ExponentialLR(optimizer, gamma=0.99)
for epoch in range(epochs):
    for i, batch_data in enumerate(dataloader):
        loss = ...
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    scheduler.step()
...
参考

[1]百度百科:方向导数 [2]百度百科:梯度
[3]方向导数(Directional derivatives)
[4]一文搞懂深度学习中的梯度下降
[5]An overview of gradient descent optimization algorithms
[6]梯度下降优化算法综述
[7]https://ruder.io/optimizing-gradient-descent/
[8]https://blog.csdn.net/qq_42255269/article/details/112094166
[9]https://secsilm.blog.csdn.net/article/details/78177781

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-7-4 10:40 , Processed in 0.091674 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表