1.深度学习——优化算法
2.常用梯度优化算法5分钟快速复习梯度优化算法 | SGD | AdaGrad | RMSprop | Adam
3.路遥知马力——Momentum
4.4加根号10的算法算法整数部分?
5.学界深度学习优化入门:Momentum、RMSProp
6.深度模型优化算法SGD、源码Momentum、算法算法NAG、源码AdaGrad、算法算法RMSProp及Adam等
深度学习——优化算法
深度学习中的源码java 源码 分析 检查优化算法,如AdaGrad、算法算法Momentum、源码RMSprop、算法算法Adam和AmsGrad,源码都是算法算法为了改进梯度下降的效率和稳定性。1. AdaGrad
AdaGrad通过自适应地调整每个参数的源码学习率来平衡梯度差异。它利用历史梯度平方的算法算法累加来决定学习率,使得梯度大的源码参数学习率减小,小的算法算法参数学习率增大。然而,随着迭代次数增加,学习率会快速衰减,可能导致梯度消失。2. Momentum
Momentum利用动量概念,考虑了过去梯度的方向,减缓震荡,加速收敛。它在梯度方向一致时提供加速,方向改变时则降低更新速度。RMSprop
RMSprop改进了AdaGrad,解决学习率衰减过快问题。它结合了动量的思想,对历史梯度平方求平均,使得学习率不会无限减小。Adam
Adam结合了RMSprop和动量,同时考虑梯度和学习率的历史影响,避免了学习率上升的问题,是深度学习中的常用优化器。AmsGrad
AmsGrad在Adam的基础上,修正了可能导致学习率上升的问题,确保了学习率的递减性。选择策略
在实际应用中,Adam和RMSprop通常效果良好,但在特定问题上,如Adam和RMSprop效果不佳,可以考虑Nesterov梯度下降或根据具体数据集调整其他算法。实验研究表明,衰减学习率的算法在多种数据集上表现优秀。常用梯度优化算法5分钟快速复习梯度优化算法 | SGD | AdaGrad | RMSprop | Adam
在神经网络训练中,梯度优化算法扮演着关键角色,它们通过调整权重来最小化损失函数,提高模型性能。本文将快速回顾四个常用的优化算法:SGD、AdaGrad、RMSprop以及它们的变种Adam。1. SGD (随机梯度下降)
torch.optim.SGD封装了基本的SGD,其中momenutm=0的公式为: <公式1> 当momentum不为0时,公式为: <公式2> 如果启用Nesterov加速,公式变为: <公式3>2. AdaGrad
Adagrad使用累积平方梯度,其公式如下: <公式4> 实现方法如下:3. RMSprop
RMSprop通过加权移动平均来平滑梯度,公式如下: <公式5> <公式6>4. Adam (Adaptive Moment Estimation)
Adam结合了RMSprop和SGD,其公式包括: <公式7> <公式8> <公式9> <公式> 实现代码见torch.optim.Adam:总结
以上是当前常用的一些梯度优化算法,其他方法相对较少。如需了解更多信息或有疑问,欢迎留言讨论。路遥知马力——Momentum
本文收录于《无痛的机器学习第一季》。
前言:感谢@夏龙对本篇文章的审阅和提出的宝贵意见,期待更多专家的指导。
上一篇文章介绍了梯度下降的相关内容,而今天我们要探讨的是梯度下降过程中的另一个关键要素——动量(momentum)。
动量是一个神秘的概念,我仅能以最简单的方式理解它。在优化求解过程中,动量就像是一种推动剂,使之前优化量持续发挥作用。一个已经完成的梯度+步长的组合不会立即消失,而是以某种形式衰减,剩余的能量将继续发挥余热。以下是基于动量的梯度下降代码示例:
与之前的梯度下降法相比,这个算法的唯一区别是多了一个pre_grad*discount,这正是进程切换源码动量发挥作用的地方。
动量究竟有什么作用呢?今天主要探讨其中一个作用——帮助穿越“山谷”。为了理解这一点,我们先来一个待优化函数。这次的问题相对复杂,是一个二元二次函数:
该函数在等高线图上的形状如下:
其中,中心的蓝色点表示了最优值。我们根据这个图发挥想象力,这个函数在y轴上非常陡峭,而在x轴上相对平缓。现在,我们用朴素梯度下降来尝试一下:
经过轮迭代后,其优化过程图如下所示:
可以看出,我们从某个点出发,整体趋势向着最优点前进,这没有问题。但前进的速度似乎有些缓慢,是不是步长又设小了?在之前的经验基础上,这次我们在设置步长时变得小心谨慎:
效果似乎并不明显,而且在优化过程中出现了左右来回震荡的情况。看着这个曲线,我仿佛看到了一个极限运动场景:
(来自网络,如侵权请联系删除)
没错,算法眼中的这个函数就像这张图一样,而算法也确实没有让大家失望,选择了一条艰难的道路进行优化——就像从一边的高台滑下,然后滑到另一边,这样艰难地前进。没办法,这就是梯度下降法。在它的眼中,这样走是最快的,而事实上,每个优化点所对应的梯度方向确实是那个方向。
这时,专家们可能会讨论特征值的问题,关于这些问题我们以后再说。现在,我们只能继续调整步长,说不定步长再大一点,“滑板少年”还能再快一些!
然而,我们的“滑板少年”已经彻底失控了……这已经是我们能设的最大的步长了(上一次关于步长和函数之间的关系在这里依然适用),再大一点,我们的“滑板少年”就飞出去了。由于两个坐标轴方向的函数属性不同,为了防止在优化过程中发散,步长只能根据最陡峭的方向设定。当然,解决快速收敛问题还有其他方法,这里我们看看动量如何解决这个问题。
很自然地,我们想到,如果“滑板少年”能把行动的力量集中在向前而不是两边晃动就好了。这个想法分为两个步骤:首先是集中力量向前走,然后是尽量不要在两边晃动。这时,动量就闪亮登场了。我们发现“滑板少年”每次的行动只会在以下三个方向进行:
我们可以想象到,当使用动量后,实际上沿-y和+y方向的两个力可以相互抵消,而-x方向的力则会一直加强,这样“滑板少年”会在y方向打转,但y方向的力量会越来越小,而在-x方向的速度会比之前快得多!
现在,让我们看看加了动量技能的“滑板少年”的实际表现:
终于没有让大家失望,尽管“滑板少年”还是很贪玩,但在轮迭代后,他还是来到了最优点附近。可以说,我们的任务基本完成了。当然,由于动量的原因,前几轮迭代他在y轴上的表现似乎比以前还活跃,这个问题我们以后会讨论。但不管怎样,驾培源码我们的目标已经实现了。
后来,又有高人发明了一种解决前面动量未解决问题的新算法,那就是传说中的Nesterov算法。这里就不详细介绍了,有时间我们会详细讨论。下面给出代码和结果:
“滑板少年”已经哭晕在厕所……
经过这么多讨论,我们终于完成了穿越“山谷”的话题。接下来,我们还要讨论一个数值问题。在CNN的训练中,我们的开山祖师已经给出了动量的建议配置——0.9(之前例子中全部是0.7),那么0.9的动量究竟有多大呢?下面我们来点公式……
我们用G表示每一轮的更新量,g表示当前一步的梯度量(方向*步长),t表示迭代轮数,[公式]表示冲量的衰减程度,那么对于时刻t的梯度更新量有:
我们可以计算一下对于梯度g0从G0到GT的总贡献量为:
我们发现它的贡献是一个等比数列,如果[公式]=0.9,那么根据等比数列的极限运算方法,我们知道在极限状态下,它一共贡献了自身倍的能量。如果[公式]=0.呢?那就是倍了。
那么在实际中我们需要多少倍的能量呢?
本文相关代码详见:github.com/hsmyy/zhihuz...
广告时间:更多精彩内容尽在《深度学习轻松学:核心算法与视觉实践》!
4加根号的整数部分?
梯度下降是非常常用的优化算法。作为机器学习的基础知识,这是一个必须要掌握的算法。借助本文,让我们来一起详细了解一下这个算法。前言
本文的代码可以到我的Github上获取:
/paulQuei/gradient_descent
本文的算法示例通过Python语言实现,在实现中使用到了numpy和matplotlib。如果你不熟悉这两个工具,请自行在网上搜索教程。
关于优化
大多数学习算法都涉及某种形式的优化。优化指的是改变x以最小化或者最大化某个函数的任务。
我们通常以最小化指代大多数最优化问题。最大化可经由最小化来实现。
我们把要最小化或最大化的函数成为目标函数(objective function)或准则(criterion)。
我们通常使用一个上标*表示最小化或最大化函数的x值,记做这样:
[x^* = arg; min; f(x)]
优化本身是一个非常大的话题。如果有兴趣,可以通过《数值优化》和《运筹学》的书籍进行学习。
模型与假设函数
所有的模型都是错误的,但其中有些是有用的。– George Edward Pelham Box
模型是我们对要分析的数据的一种假设,它是为解决某个具体问题从数据中学习到的,因此它是机器学习最核心的概念。
针对一个问题,通常有大量的模型可以选择。
本文不会深入讨论这方面的内容,关于各种模型请参阅机器学习的相关书籍。本文仅以最简单的线性模型为基础来讨论梯度下降算法。
这里我们先介绍一下在监督学习(supervised learning)中常见的三个符号:
m,描述训练样本的数量
x,描述输入变量或特征
y,描述输出变量或者叫目标值
请注意,一个样本可能有很多的特征,因此x和y通常是一个向量。不过在刚开始学习的时候,为了便于理解,你可以暂时理解为这就是一个具体的数值。训练集会包含很多的样本,我们用 表示其中第i个样本。
x是数据样本的特征,y是其目标值。例如,在预测房价的模型中,x是房子的各种信息,例如:面积,楼层,位置等等,y是房子的价格。在图像识别的任务中,x是图形的所有像素点数据,y是图像中包含的目标对象。
我们是希望寻找一个函数,将x映射到y,小白源码窝这个函数要足够的好,以至于能够预测对应的y。由于历史原因,这个函数叫做假设函数(hypothesis function)。
学习的过程如下图所示。即:首先根据已有的数据(称之为训练集)训练我们的算法模型,然后根据模型的假设函数来进行新数据的预测。
线性模型(linear model)正如其名称那样:是希望通过一个直线的形式来描述模式。线性模型的假设函数如下所示:
[h_{ \theta}(x) = \theta_{ 0} + \theta_{ 1} * x]
这个公式对于大家来说应该都是非常简单的。如果把它绘制出来,其实就是一条直线。
下图是一个具体的例子,即: 的图形:
在实际的机器学习工程中,你会拥有大量的数据。这些数据会来自于某个数据源。它们存储在csv文件中,或者以其他的形式打包。
但是本文作为演示使用,我们通过一些简单的代码自动生成了需要的数据。为了便于计算,演示的数据量也很小。
import numpy as np
max_x =
data_size =
theta_0 = 5
theta_1 = 2
def get_data:
x = np.linspace(1, max_x, data_size)
noise = np.random.normal(0, 0.2, len(x))
y = theta_0 + theta_1 * x + noise
return x, y
这段代码很简单,我们生成了x范围是 [1, ] 整数的条数据。对应的y是以线性模型的形式计算得到,其函数是:。现实中的数据常常受到各种因素的干扰,所以对于y我们故意加上了一些高斯噪声。因此最终的y值为比原先会有轻微的偏离。
最后我们的数据如下所示:
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, ]
y = [6., 9., ., ., ., ., ., ., ., .]
我们可以把这条数据绘制出来这样就有一个直观的了解了,如下图所示:
虽然演示用的数据是我们通过公式计算得到的。但在实际的工程中,模型的参数是需要我们通过数据学习到的。所以下文我们假设我们不知道这里线性模式的两个参数是什么,而是通过算法的形式求得。
最后再跟已知的参数进行对比以验证我们的算法是否正确。
有了上面的数据,我们可以尝试画一条直线来描述我们的模型。
例如,像下面这样画一条水平的直线:
很显然,这条水平线离数据太远了,非常的不匹配。
那我们可以再画一条斜线。
我们初次画的斜线可能也不贴切,它可能像下面这样:
最后我们通过不断尝试,找到了最终最合适的那条,如下所示:
梯度下降算法的计算过程,就和这种本能式的试探是类似的,它就是不停的迭代,一步步的接近最终的结果。
代价函数
上面我们尝试了几次通过一条直线来拟合(fitting)已有的数据。
二维平面上的一条直线可以通过两个参数唯一的确定,两个参数的确定也即模型的确定。那如何描述模型与数据的拟合程度呢?答案就是代价函数。
代价函数(cost function)描述了学习到的模型与实际结果的偏差程度。以上面的三幅图为例,最后一幅图中的红线相比第一条水平的绿线,其偏离程度(代价)应该是更小的。
很显然,我们希望我们的假设函数与数据尽可能的贴近,也就是说:希望代价函数的结果尽可能的小。这就涉及到结果的优化,而梯度下降就是寻找最小值的方法之一。
代价函数也叫损失函数。对于每一个样本,假设函数会依据计算出一个估算值,我们常常用来表示。即 。
很自然的,我们会想到,通过下面这个公式来描述我们的模型与实际值的偏差程度:
[(h_\theta(x^i) - y^i)^2 = (\widehat{ y}^{ i} - y^i)^2 = (\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i})^2]
请注意, 是实际数据的值, 是我们的模型的估算值。前者对应了上图中的离散点的y坐标,后者对应了离散点在直线上投影点的y坐标。
每一条数据都会存在一个偏差值,而代价函数就是生活商城源码对所有样本的偏差求平均值,其计算公式如下所示:
[L(\theta) = \frac { 1}{ m} \sum_{ i=1}^{ m}(h_\theta(x^i) - y^i)^2 = \frac { 1}{ m} \sum_{ i=1}^{ m}(\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i})^2]
当损失函数的结果越小,则意味着通过我们的假设函数估算出的结果与真实值越接近。这也就是为什么我们要最小化损失函数的原因。
不同的模型可能会用不同的损失函数。例如,logistic回归的假设函数是这样的:。其代价函数是这样的:借助上面这个公式,我们可以写一个函数来实现代价函数:
def cost_function(x, y, t0, t1):
cost_sum = 0
for i in range(len(x)):
cost_item = np.power(t0 + t1 * x[i] - y[i], 2)
cost_sum += cost_item
return cost_sum / len(x)
这个函数的代码应该不用多做解释,它就是根据上面的完成计算。
我们可以尝试选取不同的 和 组合来计算代价函数的值,然后将结果绘制出来:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
theta_0 = 5
theta_1 = 2
def draw_cost(x, y):
fig = plt.figure(figsize=(, 8))
ax = fig.gca(projection='3d')
scatter_count =
radius = 1
t0_range = np.linspace(theta_0 - radius, theta_0 + radius, scatter_count)
t1_range = np.linspace(theta_1 - radius, theta_1 + radius, scatter_count)
cost = np.zeros((len(t0_range), len(t1_range)))
for a in range(len(t0_range)):
for b in range(len(t1_range)):
cost[a][b] = cost_function(x, y, t0_range[a], t1_range[b])
t0, t1 = np.meshgrid(t0_range, t1_range)
ax.set_xlabel('theta_0')
ax.set_ylabel('theta_1')
ax.plot_surface(t0, t1, cost, cmap=cm.hsv)
在这段代码中,我们对 和 各自指定了一个范围进行次的采样,然后以不同的 组合对来计算代价函数的值。
如果我们将所有点的代价函数值绘制出来,其结果如下图所示:
从这个图形中我们可以看出,当 越接近 [5, 2]时其结果(偏差)越小。相反,离得越远,结果越大。
直观解释
从上面这幅图中我们可以看出,代价函数在不同的位置结果大小不同。
从三维的角度来看,这就和地面的高低起伏一样。最高的地方就好像是山顶。
而我们的目标就是:从任意一点作为起点,能够快速寻找到一条路径并以此到达图形最低点(代价值最小)的位置。
而梯度下降的算法过程就和我们从山顶想要快速下山的做法是一样的。
在生活中,我们很自然会想到沿着最陡峭的路往下行是下山速度最快的。如下面这幅图所示:
针对这幅图,细心的读者可能很快就会有很多的疑问,例如:
对于一个函数,怎么确定下行的方向?
每一步该往前走多远?
有没有可能停留在半山腰的平台上?
这些问题也就是本文接下来要讨论的内容。
算法描述
梯度下降算法最开始的一点就是需要确定下降的方向,即:梯度。
我们常常用 来表示梯度。
对于一个二维空间的曲线来说,梯度就是其切线的方向。如下图所示:
而对于更高维空间的函数来说,梯度由所有变量的偏导数决定。
其表达式如下所示:
[\nabla f({ \theta}) = ( \frac{ \partial f({ \theta})}{ \partial \theta_1} , \frac{ \partial f({ \theta})}{ \partial \theta_2} , ... , \frac{ \partial f({ \theta})}{ \partial \theta_n} )]
在机器学习中,我们主要是用梯度下降算法来最小化代价函数,记做:
[\theta ^* = arg min L(\theta)]
其中,L是代价函数,是参数。
梯度下降算法的主体逻辑很简单,就是沿着梯度的方向一直下降,直到参数收敛为止。
记做:
[\theta ^{ k + 1}_i = \theta^{ k}_i - \lambda \nabla f(\theta^{ k})]
这里的下标i表示第i个参数。 上标k指的是第k步的计算结果,而非k次方。在能够理解的基础上,下文的公式中将省略上标k。这里有几点需要说明:
收敛是指函数的变化率很小。具体选择多少合适需要根据具体的项目来确定。在演示项目中我们可以选择0.或者0.这样的值。不同的值将影响算法的迭代次数,因为在梯度下降的最后,我们会越来越接近平坦的地方,这个时候函数的变化率也越来越小。如果选择一个很小的值,将可能导致算法迭代次数暴增。
公式中的 称作步长,也称作学习率(learning rate)。它决定了每一步往前走多远,关于这个值我们会在下文中详细讲解。你可以暂时人为它是一个类似0.或0.的固定值。
在具体的项目,我们不会让算法无休止的运行下去,所以通常会设置一个迭代次数的最大上限。
线性回归的梯度下降
有了上面的知识,我们可以回到线性模型代价函数的梯度下降算法实现了。
首先,根据代价函数我们可以得到梯度向量如下:
[\nabla f({ \theta}) = (\frac{ \partial L(\theta)}{ \partial\theta_{ 0}}, \frac{ \partial L(\theta)}{ \partial\theta_{ 1}}) = (\frac { 2}{ m} \sum_{ i=1}^{ m}(\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i}) , \frac { 2}{ m} \sum_{ i=1}^{ m}(\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i}) x^{ i})]
接着,将每个偏导数带入迭代的公式中,得到:
[\theta_{ 0} := \theta_{ 0} - \lambda \frac{ \partial L(\theta_{ 0})}{ \partial\theta_{ 0}} = \theta_{ 0} - \frac { 2 \lambda }{ m} \sum_{ i=1}^{ m}(\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i}) \ \theta_{ 1} := \theta_{ 1} - \lambda \frac{ \partial L(\theta_{ 1})}{ \partial\theta_{ 1}} = \theta_{ 1} - \frac { 2 \lambda }{ m} \sum_{ i=1}^{ m}(\theta_{ 0} + \theta_{ 1} * x^{ i} - y^{ i}) x^{ i}]
由此就可以通过代码实现我们的梯度下降算法了,算法逻辑并不复杂:
learning_rate = 0.
def gradient_descent(x, y):
t0 =
t1 =
delta = 0.
for times in range():
sum1 = 0
sum2 = 0
for i in range(len(x)):
sum1 += (t0 + t1 * x[i] - y[i])
sum2 += (t0 + t1 * x[i] - y[i]) * x[i]
t0_ = t0 - 2 * learning_rate * sum1 / len(x)
t1_ = t1 - 2 * learning_rate * sum2 / len(x)
print('Times: { }, gradient: [{ }, { }]'.format(times, t0_, t1_))
if (abs(t0 - t0_) < delta and abs(t1 - t1_) < delta):
print('Gradient descent finish')
return t0_, t1_
t0 = t0_
t1 = t1_
print('Gradient descent too many times')
return t0, t1
这段代码说明如下:
我们随机选择了 都为作为起点
设置最多迭代次
收敛的范围设为0.
学习步长设为0.
如果我们将算法迭代过程中求得的线性模式绘制出来,可以得到下面这幅动态图:
最后算法得到的结果如下:
Times: , gradient: [5., 1.]
Times: , gradient: [5., 1.]
Times: , gradient: [5., 1.]
Times: , gradient: [5., 1.]
Gradient descent finish
从输出中可以看出,算法迭代了次就收敛了。这时的结果[5., 1.],这已经比较接近目标值 [5, 2]了。如果需要更高的精度,可以将delta的值调的更小,当然,此时会需要更多的迭代次数。
高维扩展
虽然我们举的例子是二维的,但是对于更高维的情况也是类似的。同样是根据迭代的公式进行运算即可:
[\theta_{ i} = \theta_{ i} - \lambda \frac { \partial L(\theta)}{ \partial \theta_i} = \theta_{ i} - \frac{ 2\lambda}{ m} \sum_{ i=1}^{ m}(h_\theta(x^{ k})-y^k)x_i^k]
这里的下标i表示第i个参数,上标k表示第k个数据。
梯度下降家族BGD
在上面的内容中我们看到,算法的每一次迭代都需要把所有样本进行遍历处理。这种做法称为之Batch Gradient Descent,简称BGD。作为演示示例只有条数据,这是没有问题的。
但在实际的项目中,数据集的数量可能是几百万几千万条,这时候每一步迭代的计算量就会非常的大了。
于是就有了下面两个变种。
SGD
Stochastic Gradient Descent,简称SGD,这种算法是每次从样本集中仅仅选择一个样本来进行计算。很显然,这样做算法在每一步的计算量一下就少了很多。
其算法公式如下:
[\theta_{ i} = \theta_{ i} - \lambda \frac { \partial L(\theta)}{ \partial \theta_i} = \theta_{ i} - \lambda(h_\theta(x^k)-y^k)x_i^k]
当然,减少算法计算量也是有代价的,那就是:算法结果会强依赖于随机取到的数据情况,这可能会导致算法的最终结果不太令人满意。
MBGD
以上两种做法其实是两个极端,一个是每次用到了所有数据,另一个是每次只用一个数据。
我们自然就会想到两者取其中的方法:每次选择一小部分数据进行迭代。这样既避免了数据集过大导致每次迭代计算量过大的问题,也避免了单个数据对算法的影响。
这种算法称之为Mini-batch Gradient Descent,简称MBGD。
其算法公式如下:
[\theta_{ i} = \theta_{ i} - \lambda \frac { \partial L(\theta)}{ \partial \theta_i} = \theta_{ i} - \frac{ 2\lambda}{ m} \sum_{ i=a}^{ a + b}(h_\theta(x^k)-y^k)x_i^k]
当然,我们可以认为SGD是Mini-batch为1的特例。
针对上面提到的算法变种,该如何选择呢?
下面是Andrew Ng给出的建议:
如果样本数量较小(例如小于等于),选择BGD即可。
如果样本数量很大,选择 来进行MBGD,例如:,,,。
下表是 Optimization for Deep Learning 中对三种算法的对比
方法准确性更新速度内存占用在线学习BGD好慢高否SGD好(with annealing)快低是MBGD好中等中等是
算法优化
式7是算法的基本形式,在这个基础上有很多人进行了更多的研究。接下来我们介绍几种梯度下降算法的优化方法。
Momentum
Momentum是动量的意思。这个算法的思想就是借助了动力学的模型:每次算法的迭代会使用到上一次的速度作为依据。
算法的公式如下:
[v^t = \gamma v^{ t - 1} + \lambda \nabla f(\theta) \ \theta = \theta - v_t]
对比式7可以看出,这个算法的主要区别就是引入了,并且,每个时刻的受前一个时刻的影响。
从形式上看,动量算法引入了变量 v 充当速度角色——它代表参数在参数空间移动的方向和速率。速度被设为负梯度的指数衰减平均。名称动量来自物理类比,根据牛顿运动定律,负梯度是移动参数空间中粒子的力。动量在物理学上定义为质量乘以速度。在动量学习算法中,我们假设是单位质量,因此速度向量 v 也可以看作是粒子的动量。
对于可以取值0,而是一个常量,设为0.9是一个比较好的选择。
下图是momentum算法的效果对比:
对原来的算法稍加修改就可以增加动量效果:
def gradient_descent_with_momentum(x, y):
t0 =
t1 =
delta = 0.
v0 = 0
v1 = 0
gamma = 0.9
for times in range():
sum1 = 0
sum2 = 0
for i in range(len(x)):
sum1 += (t0 + t1 * x[i] - y[i])
sum2 += (t0 + t1 * x[i] - y[i]) * x[i]
v0 = gamma * v0 + 2 * learning_rate * sum1 / len(x)
v1 = gamma * v1 + 2 * learning_rate * sum2 / len(x)
t0_ = t0 - v0
t1_ = t1 - v1
print('Times: { }, gradient: [{ }, { }]'.format(times, t0_, t1_))
if (abs(t0 - t0_) < delta and abs(t1 - t1_) < delta):
print('Gradient descent finish')
return t0_, t1_
t0 = t0_
t1 = t1_
print('Gradient descent too many times')
return t0, t1
以下是该算法的输出:
Times: , gradient: [4., 2.]
Times: , gradient: [4., 1.]
Times: , gradient: [4., 1.]
Times: , gradient: [4., 1.]
Times: , gradient: [4., 1.]
Gradient descent finish
从结果可以看出,改进的算法只用了次迭代就收敛了。速度比原来次快了很多。
同样的,我们可以把算法计算的过程做成动态图:
对比原始的算法过程可以看出,改进算法最大的区别是:在寻找目标值时会在最终结果上下跳动,但是越往后跳动的幅度越小,这也就是动量所产生的效果。
Learning Rate 优化
至此,你可能还是好奇该如何设定学习率的值。
事实上,这个值的选取需要一定的经验或者反复尝试才能确定。
《深度学习》一书中是这样描述的:“与其说是科学,这更像是一门艺术,我们应该谨慎地参考关于这个问题的大部分指导。”。关键在于,这个值的选取不能过大也不能过小。
如果这个值过小,会导致每一次迭代的步长很小,其结果就是算法需要迭代非常多的次数。
那么,如果这个值过大会怎么样呢?其结果就是:算法可能在结果的周围来回震荡,却落不到目标的点上。下面这幅图描述了这个现象:
事实上,学习率的取值未必一定要是一个常数,关于这个值的设定有很多的研究。
下面是比较常见的一些改进算法。
AdaGrad
AdaGrad是Adaptive Gradient的简写,该算法会为每个参数设定不同的学习率。它使用历史梯度的平方和作为基础来进行计算。
其算法公式如下:
[\theta_i = \theta_i - \frac{ \lambda}{ \sqrt{ G_t + \epsilon}} \nabla f(\theta)]
对比式7,这里的改动就在于分号下面的根号。
根号中有两个符号,第二个符号比较好理解,它就是为了避免除0而人为引入的一个很小的常数,例如可以设为:0.。
第一个符号的表达式展开如下:
[G_t = \sum_{ i = 1}^{ t} \nabla f(\theta){ i}\nabla f(\theta){ i}^{ T}]
这个值其实是历史中每次梯度的平方的累加和。
AdaGrad算法能够在训练中自动的对learning rate进行调整,对于出现频率较低参数采用较大的学习率;相反,对于出现频率较高的参数采用较小的学习率。因此,Adagrad非常适合处理稀疏数据。
但该算法的缺点是它可能导致学习率非常小以至于算法收敛非常的慢。
关于这个算法的直观解释可以看李宏毅教授的视频课程:ML Lecture 3-1: Gradient Descent。
RMSProp
RMS是Root Mean Square的简写。RMSProp是AI教父Geoff Hinton提出的一种自适应学习率方法。AdaGrad会累加之前所有的梯度平方,而RMSProp仅仅是计算对应的平均值,因此可缓解Adagrad算法学习率下降较快的问题。
该算法的公式如下:
[E[\nabla f(\theta_{ i})^2]^{ t} = \gamma E[\nabla f(\theta_{ i})^2]^{ t - 1} + (1-\gamma)(\nabla f(\theta_{ i})^{ t})^{ 2} \ \theta_i = \theta_i - \frac{ \lambda}{ \sqrt{ E[g^2]^{ t+1} + \epsilon}} \nabla f(\theta_{ i})]
类似的,是为了避免除0而引入。 是衰退参数,通常设为0.9。
这里的 是t时刻梯度平方的平均值。
Adam
Adam是Adaptive Moment Estimation的简写。它利用梯度的一阶矩估计和二阶矩估计动态调整每个参数的学习率。
Adam的优点主要在于经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。
该算法公式如下:
[m^{ t} = \beta_{ 1} m^{ t-1} + (1-\beta_{ 1}) \nabla f(\theta) \ v^{ t} = \beta_{ 2} v^{ t-1} + (1-\beta_{ 2}) \nabla f(\theta)^2 \ \widehat{ m}^{ t} = \frac{ m^{ t}}{ 1 - \beta^{ t}_1} \ \widehat{ v}^{ t} = \frac{ v^{ t}}{ 1 - \beta^{ t}_2} \ \theta = \theta - \frac{ \lambda}{ \sqrt{ \widehat{ v}^{ t}} + \epsilon}\widehat{ m}^{ t}]
,分别是对梯度的一阶矩估计和二阶矩估计。, 是对,的校正,这样可以近似为对期望的无偏估计。
Adam算法的提出者建议 默认值为0.9,默认值为0.,默认值为 。
在实际应用中 ,Adam较为常用,它可以比较快地得到一个预估结果。
优化小结
这里我们列举了几种优化算法。它们很难说哪种最好,不同的算法适合于不同的场景。在实际的工程中,可能需要逐个尝试一下才能确定选择哪一个,这个过程也是目前现阶段AI项目要经历的工序之一。
实际上,该方面的研究远不止于此,如果有兴趣,可以继续阅读 《Sebastian Ruder: An overview of gradient descent optimization algorithms》 这篇论文或者 Optimization for Deep Learning 这个Slides进行更多的研究。
由于篇幅所限,这里不再继续展开了。
算法限制
梯度下降算法存在一定的限制。首先,它要求函数必须是可微分的,对于不可微的函数,无法使用这种方法。
除此之外,在某些情况下,使用梯度下降算法在接近极值点的时候可能收敛速度很慢,或者产生Z字形的震荡。这一点需要通过调整学习率来回避。
另外,梯度下降还会遇到下面两类问题。
局部最小值
局部最小值(Local Minima)指的是,我们找到的最小值仅仅是一个区域内的最小值,而并非全局的。由于算法的起点是随意取的,以下面这个图形为例,我们很容易落到局部最小值的点里面。
这就是好像你从上顶往下走,你第一次走到的平台未必是山脚,它有可能只是半山腰的一个平台的而已。
算法的起点决定了算法收敛的速度以及是否会落到局部最小值上。
坏消息是,目前似乎没有特别好的方法来确定选取那个点作为起点是比较好的,这就有一点看运气的成分了。多次尝试不同的随机点或许是一个比较好的方法,这也就是为什么做算法的优化这项工作是特别消耗时间的了。
但好消息是:
对于凸函数或者凹函数来说,不存在局部极值的问题。其局部极值一定是全局极值。
最近的一些研究表明,某些局部极值并没有想象中的那么糟糕,它们已经非常的接近全局极值所带来的结果了。
鞍点
除了Local Minima,在梯度下降的过程中,还有可能遇到另外一种情况,即:鞍点(Saddle Point)。鞍点指的是我们找到点某个点确实是梯度为0,但它却不是函数的极值,它的周围既有比它小的值,也有比它大的值。这就好像马鞍一样。
如下图所示:
多类随机函数表现出以下性质:在低维空间中,局部极值很普遍。但在高维空间中,局部极值比较少见,而鞍点则很常见。
不过对于鞍点,可以通过数学方法Hessian矩阵来确定。关于这点,这里就不再展开了,有兴趣的读者可以以这里提供的几个链接继续探索。
参考资料与推荐读物
Wikipeida: Gradient descent
Sebastian Ruder: An overview of gradient descent optimization algorithms
吴恩达:机器学习
吴恩达:深度学习
Peter Flach:机器学习
李宏毅 - ML Lecture 3-1: Gradient Descent
PDF: 李宏毅 - Gradient Descent
Intro to optimization in deep learning: Gradient Descent
Intro to optimization in deep learning: Momentum, RMSProp and Adam
Stochastic Gradient Descent – Mini-batch and more
刘建平Pinard - 梯度下降(Gradient Descent)小结
多元函数的偏导数、方向导数、梯度以及微分之间的关系思考
[Machine Learning] 梯度下降法的三种形式BGD、SGD以及MBGD
作者:阿Paul https://paul.pub/gradient-descent学界深度学习优化入门:Momentum、RMSProp
在另一篇文章中,我们讨论了随机梯度下降的具体细节,以及如何解决诸如卡在局部极小值或鞍点上的问题。在这篇文章中,我们讨论另外一个困扰神经网络训练的问题,病态曲率。
病态曲率是指在训练过程中遇到的损失函数曲率异常,使得梯度下降算法在寻找最小值时速度异常缓慢,甚至可能让训练过程看起来停滞不前,给人造成已达到次优解的错觉。我们深入探讨了病态曲率的概念以及其对训练效率的影响。
为了解决病态曲率问题,我们引入了二阶导数的概念,通过考虑梯度变化的速率,我们引入了牛顿法。牛顿法通过计算Hessian矩阵来实现,Hessian矩阵描述了损失函数在某一点的曲率。通过Hessian矩阵,我们可以调整学习步骤,使得在病态曲率区域的搜索更有效率。
然而,计算Hessian矩阵需要大量的计算资源,对于现代网络中庞大的参数量,这变得非常困难。因此,我们引入了动量方法,它通过积累之前的梯度信息来改善梯度下降的效率,从而更快速地朝向极小值方向移动。
动量方法不仅使用当前梯度,还考虑了之前梯度的信息,通过调整学习步骤来缓解梯度下降中的锯齿现象。这种方法还可以提高收敛速度,但在越过极小值时可能需要配合模拟退火算法。
另一种解决病态曲率的方法是RMSProp算法。与动量方法相比,RMSProp算法自动调整学习率,为每个参数选择不同的学习率,从而在不手动配置学习率超参数的情况下抑制梯度的锯齿下降。RMSProp算法通过计算梯度平方的指数平均值来动态调整学习率,使得在病态曲率区域的搜索更为平滑。
结合动量和RMSProp方法的优点,我们引入了Adam算法。Adam算法将动量和RMSProp方法结合,通过计算梯度的指数平均和梯度平方的指数平均值,动态调整学习率,从而实现对病态曲率的高效搜索。
总结而言,病态曲率是神经网络训练中的一个挑战,它影响了梯度下降算法的效率。通过引入牛顿法、动量方法、RMSProp算法和Adam算法,我们可以有效地解决病态曲率问题,提高训练效率并找到更平滑的极小值。实践表明,在特定的损失函数下,这些算法都能收敛到不同的局部最优极小值,而带有动量的梯度下降法通常能找到更平坦的极小值,这是优于尖锐极小值的理想结果。
除了研究更好的优化方法,还有研究致力于构建产生更平滑损失函数的网络架构。Batch-Normalization和残差连接是其中的解决方法,这些技术在减少梯度消失、提高网络训练效率方面表现出显著效果。我们也将尽快发布有关这些技术的详细介绍。欢迎随时在评论中提问。
深度模型优化算法SGD、Momentum、NAG、AdaGrad、RMSProp及Adam等
深度模型训练中的优化算法如SGD、Momentum、NAG、AdaGrad、RMSProp和Adam各有其特点。SGD,即随机梯度下降,每次迭代使用单个样本或小批量,引入随机性以减小整体优化方向的噪声。Momentum通过累积过去梯度的指数衰减移动平均,加速学习过程,减少震荡。Nesterov动量提前考虑下一步的梯度,提供更快的收敛速度。
AdaGrad对每个参数独立调整学习率,适应性地处理不同梯度的更新,解决学习率不变的问题,但可能在后期学习能力减弱。Adadelta是对AdaGrad的改进,通过近似平均而非累积历史梯度,减少了对全局学习率的依赖,但仍有学习率逐渐减小的挑战。
RMSprop作为Adadelta的变体,是梯度平方和平均的近似,对非平稳目标有良好效果。Adam优化器综合一阶矩估计和二阶矩估计,平衡了学习率的动态调整和稳定性能,尤其适合大规模数据和复杂目标函数。
总的来说,这些优化算法通过不同的策略,如动量、梯度平方平均、自适应学习率等,优化模型的训练过程,提高收敛速度和稳定性。选择哪种算法取决于具体问题和模型需求。
为什么优化算法中的Momentum会真的有效?
在优化算法领域,动量(momentum)在梯度下降优化中扮演重要角色。形象地,梯度下降可以视为一个人沿着最陡峭的山坡找到最低点的过程,该过程虽然稳定但速度较慢。动量则类比为从同一山顶滚下的重球,球体拥有惯性,既能平滑路径,又能加速前进,减少震荡,跨越狭窄山谷和局部极小值。然而,动量的有效性并不仅仅源自直观描述,而是能通过凸二次优化模型来解释其局部动力学行为。
从梯度下降到引入动量,主要解决了梯度下降算法的效率问题。在优化平滑目标函数时,虽然梯度下降确保了每步迭代的单调递增,但速度过慢,尤其是在病态曲率条件下,算法表现不稳定或失败。动量通过引入记忆功能,使得迭代速度在某些情况下能够加速,这与经典梯度下降算法形成对比。动量的引入,使得算法在达到最优状态的过程中更为流畅,减少了在局部极小值区域的停滞。
对于梯度下降算法的数学理解,凸二次优化目标函数提供了一个基础框架。通过求解梯度并应用梯度下降公式,我们可以得到最优解。在矩阵特征值和特征向量的帮助下,将问题转换为基于对角矩阵的独立参数更新,使得每个参数的优化过程相互独立。通过分析迭代公式,我们发现参数更新速度与特征值大小密切相关,较大的特征值导致较快的收敛速度。
动量优化的引入有助于加速收敛过程,特别是在参数空间中快速跨越狭窄的山谷或局部极小值。通过分解误差并分析特征值与特征向量,我们能够理解为什么动量能够有效提高优化效率。特征值比值决定了优化问题的收敛速率,这一值不仅反映了矩阵的奇异性质,还衡量了目标函数相对于参数空间的鲁棒性。
综上所述,动量在优化算法中的有效性源于它能够通过引入记忆功能,加速收敛过程,减少震荡,并在不同参数空间中实现更为高效和稳定的优化。通过数学分析和直观解释,我们能够深刻理解动量在优化算法中的作用机制。