什么是梯度下降优化算法?

梯度下降是一种非常常见的优化算法。作为机器学习的基础知识,这是必须掌握的算法。借助这篇文章,让我们进一步了解这个算法。

这篇文章的代码可以从我的Github获得:

/paulQuei/gradient_descent

本文的算法实例是用Python语言实现的,实现中使用了numpy和matplotlib。如果对这两个工具不熟悉,请自行在网上搜索教程。

关于优化

大多数学习算法都涉及某种形式的优化。优化是指改变x来最小化或最大化一个函数的任务。

我们通常称最小化为大多数优化问题。最大化可以通过最小化来实现。

我们把要最小化或最大化的函数视为目标函数或标准。

我们通常用一个上标*来表示一个函数最小化或最大化的x值。记住这一点:

[x^* = arg;minf(x)]

优化本身就是一个非常大的话题。有兴趣的话可以从数值优化和运筹学的书上学习一下。

模型和假设函数

所有的模型都是错的,但有些是有用的。乔治·爱德华·佩勒姆盒子

模型是对要分析的数据的一种假设,是从数据中学习来解决一个具体问题的,所以是机器学习的核心概念。

对于一个问题,通常有大量的模型可供选择。

本文就不深入讨论这方面了。各种模型请参考机器学习的相关书籍。本文只讨论基于最简单线性模型的梯度下降算法。

这里我们首先介绍监督学习中的三个常用符号:

m,描述训练样本的数量

x,描述输入变量或特征。

y,描述输出变量或目标值。

请注意,一个样本可能有很多特征,所以x和y通常是一个向量。但是在学习之初,为了便于理解,可以暂时理解为这是一个具体的数值。训练集包含许多样本,我们用来表示第I个样本。

x是数据样本的特征,y是它的目标值。比如预测房价的模型中,X是房子的各种信息,如面积、楼层、位置等。,Y是房子的价格。在图像识别的任务中,X是图的所有像素数据,Y是图像中包含的目标对象。

我们想找一个把X映射到Y的函数,这个函数应该好到可以预测对应的Y,由于历史原因,这个函数叫做假设函数。

学习过程如下图所示。也就是我们先根据已有的数据(称为训练集)来训练我们的算法模型,然后根据模型的假设函数来预测新的数据。

线性模型,顾名思义,是想用直线的形式来描述模式。线性模型的假设函数如下:

[h _ { \ theta }(x)= \ theta _ { 0 }+\ theta _ { 1 } * x]

这个公式对大家来说应该很简单。如果画出来,其实是一条直线。

下图是一个具体的例子,即:

在实际的机器学习项目中,你会有很多数据。这些数据将来自一个数据源。它们存储在csv文件中或以其他形式打包。

但本文是作为演示,我们通过一些简单的代码自动生成所需的数据。为了方便计算,演示的数据量也很小。

将numpy作为np导入

max_x = 10

data_size = 10

θ_ 0 = 5

theta_1 = 2

定义获取数据:

x = np.linspace(1,max_x,data_size)

noise = np.random.normal(0,0.2,len(x))

y = theta_0 + theta_1 * x +噪声

返回x,y

这段代码非常简单。我们生成了10条数据,其x范围是[1,10]的整数。对应的y是以线性模型的形式计算出来的,它的作用是:。现实中的数据往往会受到各种因素的干扰,所以我们故意在y中加入一些高斯噪声,因此,最终的y值会和原来略有不同。

最后,我们的数据如下:

x = [1,2,3,4,5,6,7,8,9,10]

y = [6.66,9.11,11.08,12.67,15.12,16.76,18.75,21.35,22.77,24.56]

我们可以把这10条数据画出来,这样可以有一个直观的认识,如下图所示:

虽然演示中使用的数据是用我们的公式计算出来的。但在实际工程中,模型的参数需要通过数据来学习。所以我们假设这里不知道线性模型的两个参数是什么,但是我们以算法的形式得到它们。

最后,我们将其与已知参数进行比较,以验证我们的算法是否正确。

有了上面的数据,我们可以试着画一条直线来描述我们的模型。

例如,画一条水平直线,如下所示:

很明显,这条水平线离数据太远了,非常不匹配。

然后我们可以画另一条对角线。

我们第一次画的对角线可能也不合适。它可能看起来像这样:

最后,我们试了又试,找到了最合适的,如下图:

梯度下降算法的计算过程类似于这种本能的尝试,就是不断迭代,一步步逼近最终结果。

价值函数

我们尝试了几次通过直线来拟合拟合数据。

二维平面上的直线可以由两个参数唯一确定,两个参数的确定也是模型的确定。那么如何描述模型与数据的拟合程度呢?答案是成本函数。

成本函数描述了学习模型和实际结果之间的偏差程度。以上面三张图为例。与第一条水平绿线相比,最后一张图中红线的偏离程度(代价)应该更小。

显然,我们希望我们的假设函数尽可能接近数据,也就是说,我们希望成本函数的结果尽可能小。这就涉及到结果的优化,梯度下降法是求最小值的方法之一。

成本函数也称为损失函数。对于每个样本,假设函数都会计算出一个估计值,我们经常用这个估计值来表示。即。

自然,我们会想到下面的公式来描述我们的模型和实际值之间的偏差程度:

[(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]

当损失函数的结果更小时,意味着我们假设的函数估计的结果更接近真实值。这就是为什么我们应该最小化损失函数。

不同的模型可能使用不同的损失函数。例如,逻辑回归的假设函数如下:成本函数如下:借助上述公式,我们可以编写一个函数来实现成本函数:

定义成本_函数(x,y,t0,t1):

cost_sum = 0

对于范围内的I(len(x)):

cost _ item = NP . power(t0+t 1 * x[I]-y[I],2)

成本合计+=成本项目

返回成本_总和/贷款(x)

这个函数的代码应该不用解释了,就是根据上面的计算出来的。

我们可以尝试选择不同的求和组合来计算成本函数的值,然后得出结果:

将numpy作为np导入

将matplotlib.pyplot作为plt导入

从matplotlib导入cm

从mpl_toolkits.mplot3d导入Axes3D

θ_ 0 = 5

theta_1 = 2

def draw_cost(x,y):

fig = PLT . figure(figsize =(10,8))

ax = fig.gca(投影='3d ')

分散计数= 100

半径= 1

t0 _ range = NP . Lin space(θ_ 0-半径,θ_ 0+半径,散射_计数)

t 1 _ range = NP . Lin space(theta _ 1-半径,theta_1 +半径,散射_计数)

cost = np.zeros((len(t0_range),len(t1_range)))

对于范围内的(len(t0_range)):

对于最佳范围(len(t1_range)):

成本[a][b] =成本_函数(x,y,t0 _范围[a],t 1 _范围[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)

在这段代码中,我们对一个范围的和分别进行100次采样,然后计算不同组合对的成本函数值。

如果我们画出所有点的成本函数值,结果如下图所示:

从这个图可以看出,越接近[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最小L(\theta)]

其中l是成本函数和参数。

梯度下降算法的主要逻辑很简单,就是沿着梯度方向下降,直到参数收敛。

记得做:

[\西塔^{k+1 } _ I = \theta^{k}_i-\拉姆达\纳布拉f(\theta^{k})]

这里的下标I代表第I个参数。上标k指的是第k步的计算结果,而不是k次方。在理解的基础上,下面的公式将省略上标k。这里有几点需要说明:

收敛意味着函数的变化率非常小。选择多少合适,要看具体项目。在示范项目中,我们可以选择值0.01或0.001。不同的值会影响算法的迭代次数,因为在梯度下降的最后,我们会越来越接近平坦的地方,此时函数的变化率会越来越小。如果选择较小的值,可能会导致算法的迭代次数急剧增加。

在公式中,它被称为步长,也称为学习率。它决定了每一步走多远,我们将在下面详细解释这个值。可以暂时假设它是0.01或者0.001这样的固定值。

在具体项目中,我们不会让算法无休止地运行,所以我们通常会设定一个迭代次数的最大上限。

线性回归的梯度下降

有了以上知识,我们就可以回到线性模型代价函数的梯度下降算法的实现上来了。

首先,根据成本函数,我们可以得到梯度向量如下:

[\ 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 }-\ 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

由此可以通过代码实现我们的梯度下降算法,算法的逻辑并不复杂:

learning_rate = 0.01

def gradient_descent(x,y):

t0 = 10

t1 = 10

delta = 0.001

对于范围内的时间(1000):

sum1 = 0

sum2 = 0

对于范围内的I(len(x)):

sum 1+=(t0+t 1 * x[I]-y[I])

sum 2+=(t0+t 1 * x[I]-y[I])* x[I]

t0 _ = t0-2 * learning _ rate * sum 1/len(x)

t 1 _ = t 1-2 * learning _ rate * sum 2/len(x)

打印('次数:{},渐变:[{},{}]'。格式(times,t0_,t1_)

if(ABS(t0-t0 _)& lt;delta和ABS(t 1-t 1 _)& lt;△):

打印(“梯度下降完成”)

return t0_,t1_

t0 = t0_

t1 = t1_

打印(“梯度下降太多次”)

返回t0,t1

该代码解释如下:

我们随机选择10作为起点。

设置最大1000次迭代。

收敛范围设置为0.001。

学习步长设置为0.01。

如果把算法迭代过程中得到的线性模式画出来,可以得到如下动态图:

最后,算法得到的结果如下:

次数:657,渐变:[5.196562662718697,1.952931052920264]

次数:658,渐变:[5.195558390180733,1.9530753071808193]

次数:659,渐变:[5.194558335124868,1.9532189556399233]

次数:660,渐变:[5.193562479839619,1.953620008416623]

梯度下降整理

从输出可以看出,该算法在660次迭代后收敛。此时的结果[5.19356247939619,1.95362000416623]接近目标值[5,2]。如果需要更高的精度,可以将delta的值调得更小,当然这时候就需要更多的迭代。

高维扩展

虽然我们的例子是二维的,但对于更高维的情况也是类似的。同样,可以根据迭代公式计算:

[\ theta _ { I } = \ theta _ { I }-\sum_{i=1}^{m}(h_\theta(x^{k})-y^k)x_i^k]

这里下标I代表第I个参数,上标k代表第k个数据。

梯度下降家族BGD

在上面的内容中,我们可以看到算法的每次迭代都需要遍历所有的样本。这种做法被称为批量梯度下降或简称BGD。作为演示例子,只有10条数据,没问题。

但是在实际项目中,数据集的数量可能是几百万,也可能是几千万,此时每次迭代的计算量会非常大。

所以有以下两种变体。

签名于

历史梯度下降(简称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

以上两种做法其实是两个极端。一种是一次使用所有数据,另一种是一次只使用一个数据。

我们自然会想到取两者的方法:每次选择一小部分数据进行迭代。这样既避免了数据集过大导致每次迭代计算量过大的问题,又避免了单个数据对算法的影响。

这种算法被称为小批量梯度下降,简称MBGD。

算法公式如下:

[\ theta _ { I } = \ theta _ { I }-\sum_{i=a}^{a+b}(h_\theta(x^k)-y^k)x_i^k]

当然,我们可以认为SGD是Mini-batch 1的特例。

上面说的算法变体怎么选?

以下是吴恩达的建议:

如果样本数量较少(例如,小于或等于2000),可以选择BGD。

如果样本数量较多,选择MBGD,例如:64,128,256,512。

下表是深度学习优化中三种算法的对比。

方法准确率,更新速度,内存占用,在线学习,BGD好,慢,高或低,SGD好,带个公告,MBGD好,中中等,有。

算法优化

方程7是算法的基本形式,很多人对它做了更多的研究。接下来,我们介绍一下梯度下降算法的几种优化方法。

动量效应

气势就是气势。这个算法的思想是使用动态模型:算法的每次迭代都会以最后的速度为基础。

该算法的公式如下:

[v^t = \伽马v^{t-1 }+\ lambda \ nabla f(\ theta)\ theta = \ theta-v _ t]

从对比式7可以看出,这种算法的主要区别在于引入了,每一个时刻都受到前一个时刻的影响。

形式上,动量算法引入了变量V作为速度角色——它代表了参数在参数空间中运动的方向和速度。速度设置为负梯度的指数衰减平均值。动量这个名称来自物理类比。根据牛顿运动定律,负梯度是使粒子在参数空间运动的力。动量在物理学中被定义为质量乘以速度。在动量学习算法中,我们假设它是一个单位质量,所以速度矢量V也可以看作是粒子的动量。

将值设置为0到0.9是一个不错的选择,这是一个常数。

下图是动量算法的效果对比:

可以通过稍微修改原始算法来增加动量效应:

def gradient _ descent _ with _ momentum(x,y):

t0 = 10

t1 = 10

delta = 0.001

v0 = 0

v1 = 0

伽马= 0.9

对于范围内的时间(1000):

sum1 = 0

sum2 = 0

对于范围内的I(len(x)):

sum 1+=(t0+t 1 * x[I]-y[I])

sum 2+=(t0+t 1 * x[I]-y[I])* x[I]

v0 =伽玛* v0 + 2 *学习率* sum1 / len(x)

v 1 = gamma * v 1+2 * learning _ rate * sum 2/len(x)

t0_ = t0 - v0

t1_ = t1 - v1

打印('次数:{},渐变:[{},{}]'。格式(times,t0_,t1_)

if(ABS(t0-t0 _)& lt;delta和ABS(t 1-t 1 _)& lt;△):

打印(“梯度下降完成”)

return t0_,t1_

t0 = t0_

t1 = t1_

打印(“梯度下降太多次”)

返回t0,t1

以下是该算法的输出:

次数:125,渐变:[4.955453758569991,2.00005017897775]

次数:126,渐变:[4.955309381126545,1.996928964532015]

次数:127,渐变:[4.9542964317327005,1.98674828684156]

次数:128,渐变:[4.9536358220657,1.9781180992510465]

次数:129,渐变:[4.95412496254411,1.978858350530971]

梯度下降整理

从结果可以看出,改进后的算法只需要129次迭代就能收敛。速度比原来的660倍快了很多。

同样,我们可以把算法的计算过程做成一个动态图:

改进后的算法与原算法相比,最大的区别是在寻找目标值时会在最终结果中上下跳动,但越往后跳动幅度越小,这就是动量产生的效果。

学习率优化

此时,你可能还会好奇学习率是怎么设定的。

事实上,这个值的选择需要一些经验或反复尝试来确定。

《深度学习》这本书是这样描述的:“它更像是一门艺术,而不是一门科学,我们应该仔细参考关于这个问题的大部分指南。”。关键是这个值不能太大也不能太小。

如果这个值太小,每次迭代的步长就会很小,结果就是算法需要迭代很多次。

那么,如果这值得国会呢?结果就是算法可能会围绕结果来回振荡,但是无法到达目标点。下图描述了这种现象:

事实上,学习率的值并不一定是一个常数,关于这个值的设定也有很多研究。

以下是一些常见的改进算法。

阿达格拉德

AdaGrad是Adaptive Gradient的简称,算法会为每个参数设置不同的学习速率。它使用历史梯度的平方和作为计算基础。

算法公式如下:

[\ theta _ I = \ theta _ I-\ frac { \ lambda }

对比7,这里的变化在于分号下面的根号。

根符号有两个符号,第二个符号很好理解。它是人为引入的避免被0除的小常数,比如可以设置为:0.005438+0。

第一个符号的表达式展开如下:

[g _ t = \ sum _ { I = 1}^{t} \纳布拉f(\ theta){ I } \纳布拉f(\theta){i}^{T}]

这个值实际上是历史上每个梯度的平方和的累积。

AdaGrad算法可以在训练中自动调整学习率,对频率较低的参数采用较高的学习率;相反,对于频率较高的参数采用较小的学习率。所以Adagrad非常适合处理稀疏数据。

但是这种算法的缺点是可能导致学习率非常小,从而使算法的收敛速度非常慢。

这个算法的直观解释可以在李宏毅教授的视频课程中找到:ML讲座3-1:梯度下降。

RMSProp

RMS是均方根的缩写。RMSProp是人工智能教父Geoff Hinton提出的一种自适应学习率方法。Adagrad会累加所有之前的梯度平方,而RMSProp只计算对应的平均值,所以可以缓解AdaGrad算法学习速率下降快的问题。

该算法的公式如下:

[e[\纳布拉f(\theta_{i})^2]^{t} = \伽马e[\纳布拉f(\theta_{i})^2]^{t-1 }+(1-\伽马)(\纳布拉f(\theta_{i})^{t})^{2} \ \ theta _ I = \ theta _ I-\frac{\lambda}{\sqrt{e[g^2]^{t+1}+\ epsilon } } \纳布拉f(\theta_{i})]

同样,引入它是为了避免被0除。是衰减参数,通常设定为0.9。

这是t时刻梯度平方的平均值。

圣经》和《古兰经》传统中)亚当(人类第一人的名字

Adam是自适应矩估计的缩写。它利用梯度的一阶矩估计和二阶矩估计来动态调整每个参数的学习速率。

Adam的优点是经过偏差修正后,每次迭代的学习速率都有一定的范围,使得参数更加稳定。

该算法的公式如下:

[m^{t} = \ beta _ { 1 } m^{t-1}+(1-\ beta _ { 1 })\纳布拉f(\ theta)\ v^{t} = \ beta _ { 2 } v^{t-1}+(1-\ beta _ { 2 })\纳布拉f(\ theta)^2 \ \widehat{m}^{t} = \frac{m^{t}}{1-\beta^{t}_1} \ \ wide hat { v } { t } = \ frac { v { t } } { 1-\ beta { t } _ 2 } \ \ n

,分别是梯度的一阶矩估计和二阶矩估计。,是的修正,可以近似为期望值的无偏估计。

Adam算法的提出者建议默认值为0.9,默认值为0.999,默认值为。

在实际应用中,Adam是常用的,它可以很快得到一个预测结果。

优化摘要

这里我们列举几种优化算法。很难说哪个最好,不同的算法适合不同的场景。在实际工程中,可能需要逐一尝试才能确定选择哪一个。这个过程也是目前AI项目要经历的过程之一。

其实这方面的研究远不止于此。如果你有兴趣,可以继续阅读论文Sebastian Ruder:梯度下降优化算法概述或深度学习优化的幻灯片进行更多研究。

限于篇幅,此处不再赘述。

算法限制

梯度下降算法有一些限制。首先,它要求函数必须可微,这种方法不能用于不可微函数。

另外,在某些情况下,梯度下降算法在接近极值点时可能会收敛缓慢,或者产生Z形振荡。这一点需要通过调整学习率来避免。

另外,梯度下降会遇到以下两类问题。

局部极小

局部极小意味着我们找到的极小值只是一个区域的极小值,而不是全局的极小值。因为算法的起点是任意的,以下图为例,我们很容易陷入局部极小的点。

这就好比从山顶往下走,你第一次走到的平台可能不是山脚,而可能只是半山腰的平台。

算法的起点决定了算法的收敛速度和是否会陷入局部极小。

坏消息是,似乎没有特别好的方法来确定哪个点是一个好的起点,这有点运气。多次尝试不同的随机点可能是个不错的方法,这也是为什么优化算法特别耗时的原因。

但好消息是:

对于凸函数或凹函数,不存在局部极值的问题。它的局部极值必定是全局极值。

最近的一些研究表明,一些局部极小值并没有想象中的那么糟糕,它们与全局极小值带来的结果非常接近。

鞍点

除了局部极小值,在梯度下降的过程中,还可能出现另一种情况,即鞍点。鞍点是指我们找到一个梯度为0的点,但它不是函数的极值,在其周围既有较小的值,也有较大的值。这就像一个马鞍。

如下图所示:

多种随机函数表现出以下性质:在低维空间中,局部极值是很常见的。然而,在高维空间中,局部极小值是罕见的,而鞍点是常见的。

但对于鞍点,可以用数学方法Hessian矩阵来确定。至此,这里不再展开,有兴趣的读者可以通过这里提供的几个链接继续探索。

参考资料和推荐阅读材料

维基百科:梯度下降

Sebastian Ruder:梯度下降优化算法综述

吴恩达:机器学习。

吴恩达:深度学习

彼得·弗莱克:机器学习

李宏毅-ml讲座3-1:梯度下降

PDF:李宏毅梯度下降

深度学习中的优化介绍:梯度下降

深度学习中的优化介绍:Momentum、RMSProp和Adam

随机梯度下降–小批量和更多

刘建平比纳尔梯度下降法概述

多元函数的偏导数、方向导数、梯度和微分之间关系的思考

三种形式的【机器学习】梯度下降法BGD,SGD和MBGD。

作者:保罗·https://paul.pub/gradient-descent/