文章目录

  • 前言
  • SGD(随机梯度下降法)
  • Momentum
  • AdaGrad
  • RMSprop
  • Adam
  • MNIST手写数据集四种方法对比
  • 总结
  • 参考

前言

神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。

遗憾的是,神经网络的最优化问题非常难。这是因为参数空间非常复杂,无法轻易找到最优解(无法使用那种通过解数学式一下子就求得最小值的方法)。而且,在深度神经网络中,参数的数量非常庞大,导致最优化问题更加复杂。
优化器的角色就是用来更新模型参数的方法,例如SGD、Adam等都能充当优化器的角色。

首先我们先看这么一个函数:

假设我们需要求解这个函数的最小值。
我们可以用代码先画出这个函数的图像:

import numpy as np
import matplotlib.pyplot as plt #绘图用的模块
from mpl_toolkits.mplot3d import Axes3D #绘制3D坐标的函数
#%matplotlib inline
def fun(x,y): ## 定义XYZ函数return np.power(x,2) / 20  +np.power(y,2)fig1=plt.figure()    ## 展示图片的窗口
ax=Axes3D(fig1) ## 显示3D坐标
X=np.arange(-10,10,0.1) ## xy在底面
Y=np.arange(-10,10,0.1)   #创建了从-2到2,步长为0.1的arange对象
X,Y=np.meshgrid(X,Y)
Z=fun(X,Y)
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=plt.cm.coolwarm)#用取样点(x,y,z)去构建曲面
ax.contourf(X,Y,Z, zdir='z',offset=-2, cmap=plt.cm.coolwarm)## 等高线图,zdir沿着哪个轴压缩
ax.set_xlabel('x label', color='r')
ax.set_ylabel('y label', color='g')
ax.set_zlabel('z label', color='b')
plt.title('fig6-1 left')
plt.show()

结果如下:

这是一个三维的图,可以观察到函数值z的最小值就是0。
我们可以尝试从上往下看,这样就能直观的反应梯度了。

# 在二维平面中画出他的等高线
# 计算x,y坐标对应的高度值
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)# 生成x,y的数据
X = np.arange(-10, 10, 0.1) ## X的取值
Y = np.arange(-10, 10, 0.1) ## Y的取值# 把x,y数据生成mesh网格状的数据,因为等高线的显示是在网格的基础上添加上高度值
X, Y = np.meshgrid(X, Y)# 填充等高线
plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
# 显示图表
plt.title('fig6-1 right')
plt.show()

输出:


我们可以发现这个函数的特征是在y轴上的梯度变化大,在x轴上的梯度变化小。

下面将介绍一些优化器。

SGD(随机梯度下降法)

为了找到最优参数,我们将参数的梯度(导数)作为了线索。使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称SGD。

SGD是一个简单的方法,不过比起胡乱地搜索参数空间,也算是“聪明”的方法。但是,根据不同的问题,也存在比SGD更加聪明的方法。
SGD的数学公式如下:

代码实现如下:

class SGD:def __init__(self, lr=0.01):self.lr = lrdef update(self, params, grads):for key in params.keys():params[key] -= self.lr * grads[key]

由于随机梯度下降法从当前梯度进行更新,我们可以画出各个点的负梯度。

#从pyplot导入MultipleLocator类,这个类用于设置刻度间隔
from matplotlib.pyplot import MultipleLocator#画出负梯度特征图
X = np.arange(-10, 11, 1)
Y = np.arange(-10, 12, 2)
U, V = np.meshgrid(-X/10, -2*Y) ## x的偏导,y的偏导
Y1=np.zeros(X.shape[0])#21个0组成的一维数组
Y2=Y1+4
Y3=Y1-4
fig, ax = plt.subplots()
q = ax.quiver(X, Y, U, V)
#ax.quiverkey(q, X=0.3, Y=1.1, U=10,label='Quiver key, length = 10', labelpos='E')
#plt.plot(X, Y1,'b--')#把横线y=0画出来
#plt.plot(X, Y2,'b--')#把横线y=4画出来
#plt.plot(X, Y3,'b--')#把横线y=-4画出来
plt.xlabel('x')
plt.ylabel('y')
#把x轴的刻度间隔设置为2,并存在变量里
x_major_locator=MultipleLocator(2)
#把y轴的刻度间隔设置为2,并存在变量里
y_major_locator=MultipleLocator(2)
#ax为两条坐标轴的实例
ax=plt.gca()
#把x轴的主刻度设置为2的倍数
ax.xaxis.set_major_locator(x_major_locator)
#把y轴的主刻度设置为2的倍数
ax.yaxis.set_major_locator(y_major_locator)plt.xlim(-10,10)
plt.ylim(-6,6)
plt.grid()
plt.title('fig6-2')
plt.show()

输出:

通过图可以发现,(0,0)是最小值的位置,但是很多点的梯度并没有指向(0,0),这说明SGD在面对函数的形状非均向的时候,效率并不高,因为下降的方向并不是最小的方向。

下面用代码实现SGD对此函数的最小值求解:

#假设我们按照SGD的思路,从(-7.0, 2.0)起,步长为0.9,前进
X_SGD = [-7.0]#保存点的横坐标坐标
Y_SGD = [2.0]#保存点的纵坐标坐标
x = - 7.0
y = 2.0
N = 40
lr = 0.95
for i in range (N):x -= x/10*lr#更新x的值y -= 2*y*lr#更新y的值X_SGD.append(x)Y_SGD.append(y)#我们在等高线的基础上,画出点的移动
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)X = np.arange(-10, 10, 0.01)
Y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(X, Y)plt.plot(X_SGD, Y_SGD, color='red', marker='o', linestyle='solid')#把每一步梯度下降的点用折线连接起来画出plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
plt.xlim(-10, 10)
plt.ylim(-10, 10)plt.title('fig6-3')
plt.show()

输出结果:

SGD呈“之”字形向使函数取得最小值的(0,0)处移动。SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。
为了改正SGD的缺点,我们将继续介绍其他的一些方法。

Momentum

Momentum是“动量”的意思,和物理有关。用数学式表示Momentum方法,如下所示:


因为初始速度为0,所以第一次更新时其实和SGD是一样的,当第二次更新时,Momentum也便发挥作用,因为第一次更新留下一个“速度”,会影响第二次更新的幅度。

class Momentum:"""Momentum SGD"""def __init__(self, lr=0.01, momentum=0.9):self.lr = lrself.momentum = momentumself.v = Nonedef update(self, params, grads):if self.v is None:self.v = {}for key, val in params.items():                                self.v[key] = np.zeros_like(val)#存放权重、偏置的数组,数组的形状和对应的权重、偏置形状一致,数组元素值都为0for key in params.keys():self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] params[key] += self.v[key]

用这个方法求解一下函数的最小值:

#假设我们按照momentum的思路,从(-7.0, 2.0)起,前进
X_momentum = [-7.0]#保存点的横坐标坐标
Y_momentum = [2.0]#保存点的纵坐标坐标
x = - 7.0
y = 2.0
N = 40
lr = 0.1
momentum=0.9
vx=0 # 初始速度为0
vy=0
for i in range (N):#对x和y分别更新值vx=momentum*vx-x/10*lrx += vxvy=momentum*vy-2*y*lry += vyX_momentum.append(x)Y_momentum.append(y)#我们在等高线的基础上,画出点的移动
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)X = np.arange(-10, 10, 0.01)
Y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(X, Y)plt.plot(X_momentum, Y_momentum, color='red', marker='o', linestyle='solid')#把每一步梯度下降的点用折线连接起来画出plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.title('fig6-5')
plt.show()

输出结果:

图6-5中,更新路径就像小球在碗中滚动一样。和SGD相比,我们发现“之”字形的“程度”减轻了。这是因为虽然x轴方向上受到的力非常小,但是一直在同一方向上受力,所以朝同一个方向会有一定的加速。反过来,虽然y轴方向上受到的力很大,但是因为交互地受到正方向和反方向的力,它们会互相抵消,所以y轴方向上的速度不稳定。因此,和SGD时的情形相比,可以更快地朝x轴方向靠近,减弱“之”字形的变动程度。

AdaGrad

在神经网络的学习中,学习率(数学式中记为η)的值很重要。学习率过小,会导致学习花费过多时间;反过来,学习率过大,则会导致学习发散而不能正确进行。

在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。

逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。而AdaGrad 进一步发展了这个想法,针对“一个一个”的参数,赋予其“定制”的值。

AdaGrad会为参数的每个元素适当地调整学习率,与此同时进行学习(AdaGrad的Ada来自英文单词Adaptive,即“适当的”的意思)。下面,让我们用数学式表示AdaGrad的更新方法。

首先我们先实现一下AdaGrad:

class AdaGrad:"""AdaGrad"""def __init__(self, lr=0.01):self.lr = lrself.h = Nonedef update(self, params, grads):if self.h is None:self.h = {}for key, val in params.items():self.h[key] = np.zeros_like(val)for key in params.keys():self.h[key] += grads[key] * grads[key]params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

这里需要注意的是,最后一行加上了微小值1e-7。这是为了防止当self.h[key]中有0时,将0用作除数的情况。在很多深度学习的框架中,这个微小值也可以设定为参数,但这里我们用的是1e-7这个固定值。现在,让我们试着使用AdaGrad解决函数的最优化问题。

#假设我们按照AdaGrad的思路,从(-7.0, 2.0)起,前进
X_AdaGrad = [-7.0]#保存点的横坐标坐标
Y_AdaGrad = [2.0]#保存点的纵坐标坐标
x = - 7.0
y = 2.0
N = 40
lr = 1.5
hx=0
hy=0
for i in range (N):hx+=x/10*x/10#保存了以前的所有梯度值x/10的平方和x-=x/10*lr/ (np.sqrt(hx) + 1e-7)hy+=2*y*2*y##保存了以前的所有梯度值2*y的平方和y-=2*y*lr/(np.sqrt(hy) + 1e-7)X_AdaGrad.append(x)Y_AdaGrad.append(y)#我们在等高线的基础上,画出点的移动
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)X = np.arange(-10, 10, 0.01)
Y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(X, Y)plt.plot(X_AdaGrad, Y_AdaGrad, color='red', marker='o', linestyle='solid')#把每一步梯度下降的点用折线连接起来画出plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.title('fig6-6')
plt.show()

输出如下:
由图6-6由此可知,函数的取值高效地向着最小值移动。由于y轴方向上的梯度较大,因此刚开始变动较大,但是后面会根据这个较大的变动按比例进行调整,减小更新的步伐。因此,y轴方向上的更新程度被减弱,“之”字形的变动程度有所衰减。

RMSprop

AdaGrad会记录过去所有梯度的平方和。因此,学习越深入,更新的幅度就越小。实际上,如果无止境地学习,更新量就会变为 0,完全不再更新。为了改善这个问题,可以使用 RMSProp方法。RMSProp方法并不是将过去所有的梯度一视同仁地相加,而是逐渐地遗忘过去的梯度,在做加法运算时将新梯度的信息更多地反映出来。这种操作从专业上讲,称为“指数移动平均”,呈指数函数式地减小过去的梯度的尺度。
更新公式如下:

类实现:

class RMSprop:"""RMSprop"""def __init__(self, lr=0.01, decay_rate = 0.99):self.lr = lrself.decay_rate = decay_rateself.h = Nonedef update(self, params, grads):if self.h is None:self.h = {}for key, val in params.items():self.h[key] = np.zeros_like(val)for key in params.keys():self.h[key] *= self.decay_rateself.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

对上述函数最优化求解:

#假设我们按照RMSprop的思路,从(-7.0, 2.0)起,步长为0.9,前进
X_RMSprop = [-7.0]#保存点的横坐标坐标
Y_RMSprop = [2.0]#保存点的纵坐标坐标
x = - 7.0
y = 2.0
N = 40
lr = 0.1
decay_rate = 0.99
hx=0
hy=0
for i in range (N):hx*=decay_ratehx+=(1 - decay_rate)*x/10*x/10#保存了以前的所有梯度值x/10的平方和的衰减值#print(hx)x-=lr * x/10 / (np.sqrt(hx) + 1e-7)hy*=decay_ratehy+=(1 - decay_rate)*2*y*2*y##保存了以前的所有梯度值2*y的平方和的衰减值y-=2*y*lr/(np.sqrt(hy) + 1e-7)X_RMSprop.append(x)Y_RMSprop.append(y)#我们在等高线的基础上,画出点的移动
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)X = np.arange(-10, 10, 0.01)
Y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(X, Y)plt.plot(X_RMSprop, Y_RMSprop, color='red', marker='o', linestyle='solid')#把每一步梯度下降的点用折线连接起来画出plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.title('fig6-6-')
plt.show()

输出结果:

Adam

Momentum参照小球在碗中滚动的物理规则进行移动,AdaGrad为参数的每个元素适当地调整更新步伐。如果将这两个方法融合在一起会怎么样呢?这就是Adam[8]方法的基本思路。Adam是2015年提出的新方法。它的理论有些复杂,直观地讲,就是融合了Momentum和AdaGrad的方法。通过组合前面两个方法的优点,有望实现参数空间的高效搜索。此外,进行超参数的“偏置校正”也是Adam的特征。

其更新公式如下:

代码实现Adam:

class Adam:"""Adam (http://arxiv.org/abs/1412.6980v8)"""def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):self.lr = lrself.beta1 = beta1self.beta2 = beta2self.iter = 0self.m = Noneself.v = Nonedef update(self, params, grads):if self.m is None:self.m, self.v = {}, {}for key, val in params.items():self.m[key] = np.zeros_like(val)self.v[key] = np.zeros_like(val)self.iter += 1lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         for key in params.keys():#self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]#self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)#unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias#unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias#params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

使用Adam求解函数最优化问题:

#假设我们按照Adam的思路,从(-7.0, 2.0)起,步长为0.9,前进
X_Adam = [-7.0]#保存点的横坐标坐标
Y_Adam = [2.0]#保存点的纵坐标坐标
x = - 7.0
y = 2.0
N = 40
iterr = 0
lr = 0.3
beta1 = 0.9
beta2 = 0.999mx=0
vx=0
my=0
vy=0
for i in range (N):iterr += 1lr_t  = lr * np.sqrt(1.0 - beta2**iterr) / (1.0 - beta1**iterr)     mx+= (1 - beta1) * (x/10 - mx)vx+= (1 - beta2) * ((x/10)**2 - vx)x-= lr_t * mx / (np.sqrt(vx) + 1e-7)my+= (1 - beta1) * (2*y - mx)vy+= (1 - beta2) * ((2*y)**2 - vx)y-= lr_t * my / (np.sqrt(vy) + 1e-7)X_Adam.append(x)Y_Adam.append(y)#我们在等高线的基础上,画出点的移动
def f(x, y):return np.power(x,2) / 20  + np.power(y,2)X = np.arange(-10, 10, 0.01)
Y = np.arange(-5, 5, 0.01) X, Y = np.meshgrid(X, Y)plt.plot(X_Adam, Y_Adam, color='red', marker='o', linestyle='solid')#把每一步梯度下降的点用折线连接起来画出plt.contourf(X, Y, f(X, Y), cmap=plt.cm.coolwarm)
plt.xlabel('x')
plt.ylabel('y')
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.title('fig6-7')
plt.show()

输出结果:

在图6-7中,基于Adam的更新过程就像小球在碗中滚动一样。虽然Momentun也有类似的移动,但是相比之下,Adam的小球左右摇晃的程度有所减轻。这得益于学习的更新程度被适当地调整了。Adam会设置 3个超参数。一个是学习率(论文中以α出现),另外两个是一次momentum系数β1和二次momentum系数β2。根据论文,标准的设定值是β1为 0.9,β2 为 0.999。设置了这些值后,大多数情况下都能顺利运行。

MNIST手写数据集四种方法对比

这个实验以一个5层神经网络为对象,其中每层有100个神经元。激活
函数使用的是ReLU,直接上结果。

实验结果表明,在这个实验当中除了SGD,另外三种都蛮不错的。

总结

上面我们介绍了SGD、Momentum、AdaGrad、Adam这4种方法,那么用哪种方法好呢?非常遗憾,(目前)并不存在能在所有问题中都表现良好的方法。这4种方法各有各的特点,都有各自擅长解决的问题和不擅长解决的问题。很多研究中至今仍在使用SGD。Momentum和AdaGrad也是值得一试
的方法。最近,很多研究人员和技术人员都喜欢用Adam。

确实,Adam用的比较多。

参考

上课深度学习的课件

深度学习基础之优化器(optimizer)的介绍相关推荐

  1. 【深度学习】超强优化器如何与网络有机结合

    [深度学习]超强优化器如何与网络有机结合 1 Ranger优化器 2 一个例子(基于CNN和pytorch) 3 剪枝(减小优化器压力) 1 Ranger优化器 RAdam + Lookahead + ...

  2. 深度学习算法(第5期)----深度学习中的优化器选择

    欢迎关注微信公众号"智能算法" – 原文链接(阅读体验更佳): 深度学习算法(第5期)----深度学习中的优化器选择 上一期,我们一起学习了TensorFlow在训练深度网络的时候 ...

  3. 【深度学习】协同优化器和结构化知识蒸馏

    [深度学习]协同优化器和结构化知识蒸馏 文章目录 1 概述 2 什么是RAdam(Rectified Adam) 3 Lookahead - 探索损失面的伙伴系统=更快,更稳定的探索和收敛. 4 Ra ...

  4. 深度学习训练营之优化器对比

    深度学习训练营之优化器对比 原文链接 环境介绍 前置工作 设置GPU 数据处理 导入数据 数据集处理 数据集可视化 模型构造 模型训练 结果可视化 原文链接

  5. 手撕深度学习中的优化器

    深度学习中的优化算法采用的原理是梯度下降法,选取适当的初值params,不断迭代,进行目标函数的极小化,直到收敛.由于负梯度方向时使函数值下降最快的方向,在迭代的每一步,以负梯度方向更新params的 ...

  6. 深度学习中常用优化器算法Optimizer详解(BGD、SGD、MBGD、Momentum、NAG、Adagrad、Adadelta、RMSprop、Adam)

    本文转载自:https://www.cnblogs.com/guoyaohua/p/8542554.html 在机器学习.深度学习中使用的优化算法除了常见的梯度下降,还有 Adadelta,Adagr ...

  7. 【深度学习】常见优化器的PyTorch实现

    这里主要讲不同常见优化器代码的实现,以及在一个小数据集上做一个简单的比较. 备注:pytorch需要升级到最新版本 其中,SGD和SGDM,还有Adam是pytorch自带的优化器,而RAdam是最近 ...

  8. 深度学习三人行(第5期)----深度学习中的优化器选择

    上一期,我们一起学习了TensorFlow在训练深度网络的时候怎么解决梯度消失或梯度爆炸的问题,以及怎么尽可能的减少训练时间. 深度学习三人行(第4期)---- TF训练DNN之进阶 这期我们继续学习 ...

  9. 【深度学习 学习率,优化器】——深刻解读训练网络时各种学习率,优化器的区别,learning rate, Momentum

    机梯度下降及各种更新方法 普通更新 最简单的更新形式是沿着负梯度方向改变参数(因为梯度指向的是上升方向,但是我们通常希望最小化损失函数).假设有一个参数向量x及其梯度dx,那么最简单的更新的形式是: ...

最新文章

  1. java强引用弱引用_Java 的强引用、弱引用、软引用、虚引用
  2. 非常方便的node内置的调试方法
  3. oracle解除锁表【原】
  4. [USACO 1.3.3]Calf Flac
  5. [转载] Java:获取数组中的子数组的多种方法
  6. vm虚拟机联网最简单的方式
  7. vba下拉框实现记忆功能_VBA | 这个二级下拉菜单挺有趣!
  8. 李清照 —— 别是一家
  9. word顶部有一道线_为什么word文档上面有一条横线
  10. 大数据翻页_大数据分页实现与性能优化
  11. Cesium基础知识-粒子应用-汽车冒烟
  12. 微信公众号扫场景二维码后,出现“该公众号提供的服务出现故障,请稍后再试“
  13. 怎么删除日历每日重复提醒事项
  14. CSDN博客大神汇总
  15. 极狐公司官方澄清声明
  16. GPU Profiling
  17. mysql中1146提示_Mysql数据库的使用总结之ERROR 1146 (42S02)
  18. 亚马逊云科技Build On-技术报告
  19. 5W30和5W40原来的区别这么大!
  20. Gatsby静态网站导航高亮

热门文章

  1. python证书考试报名时间,python证书考试报名条件
  2. systemUI之statusBar
  3. matlab菜鸡学习日志--------使用matlab解析NMEA-0183 协议获取经纬度以及航向角
  4. 计算机应用文摘版面费,数值计算与计算机应用发表论文期刊级别
  5. Pycharm启动后总 indices... indexing的问题
  6. excel打开密码忘记了_如何设置EXCEL文件打开和编辑密码
  7. C/C++面试感受和经验以及面试题收藏(转)
  8. XML中DTD,XSD的区别与应用
  9. Echars使用指南
  10. 智能停车场ARM工控主板应用