zoukankan      html  css  js  c++  java
  • 讲透机器学习中的梯度下降

    本文始发于个人公众号:TechFlow,原创不易,求个关注


    在之前的文章当中,我们一起推导了线性回归的公式,今天我们继续来学习上次没有结束的内容。

    上次我们推导完了公式的时候,曾经说过由于有许多的问题,比如最主要的复杂度问题。随着样本和特征数量的增大,通过公式求解的时间会急剧增大,并且如果特征为空,还会出现公式无法计算的情况。所以和直接公式求解相比,实际当中更倾向于使用另外一种方法来代替,它就是今天这篇文章的主角——梯度下降法。

    梯度下降法可以说是机器学习和深度学习当中最重要的方法,可以说是没有之一。尤其是在深度学习当中,几乎清一色所有的神经网络都是使用梯度下降法来训练的。那么,梯度下降法究竟是一种什么样的方法呢,让我们先从梯度的定义开始。


    梯度的定义


    我们先来看看维基百科当中的定义:梯度(gradient)是一种关于多元导数的概括。平常的一元(单变量)函数的导数是标量值函数,而多元函数的梯度是向量值函数。多元可微函数({displaystyle f})在点({displaystyle P})上的梯度,是以({displaystyle f})({displaystyle P})上的偏导数为分量的向量。

    这句话很精炼,但是不一定容易理解,我们一点一点来看。我们之前高中学过导数,但是高中时候计算的求导往往针对的是一元函数。也就是说只有一个变量x,求导的结果是一个具体的值,它是一个标量。而多元函数在某个点求导的结果是一个向量,n元函数的求导的结果分量就是n,导数的每个分量是对应的变量在该点的偏导数。这个偏导数组成的向量,就是这个函数在该点的梯度。

    那么,根据上面的定义,我们可以明确两点,首先梯度是一个向量,它既有方向,也有大小。


    梯度的解释


    维基百科当中还列举了两个关于梯度的例子,帮助我们更好的理解。

    第一个例子是最经典的山坡模型,假设我们当下站在一个凹凸不平的山坡上,我们想要以最快的速度下山,那么我们应该该从什么方向出发呢?很简单,我们应该计算一下脚下点的梯度,梯度的方向告诉我们下山最快的方向,梯度的大小代表这点的坡度。

    第二个例子是房间温度模型,假设我们对房间建立坐标系,那么房间里的每一个点都可以表示成({displaystyle (x,y,z)}),该点的温度是(phi(x,y,z))。如果假设房间的温度不随时间变化,那么房间里每个点的梯度表示温度变热最快的方向,梯度的大小代表温度变化的速率。

    通过这两个例子,应该很容易理解梯度的方向和大小这两个概念。


    举例


    假设f是一个定义在三维空间里的函数,那么,f在某一点的梯度,可以写成:

    [ abla f=(frac{partial f}{partial x}, frac{partial f}{partial y}, frac{partial f}{partial z})=frac{partial f}{partial x}i+frac{partial f}{partial y}j + frac{partial f}{partial z}k ]

    这里的(i, j, k)都是标准单位向量,表示坐标轴(x, y, z)的方向。

    我们举个例子:

    [f=3x^2+4y-sinz ]

    套入刚才的梯度公式,可以得到:

    [ abla f = 6xcdot i + 4cdot j - cos zcdot k ]

    如果我们知道(x, y, z)的坐标,代入其中,就可以知道对应的梯度了。


    梯度下降法


    理解了梯度的概念之后,再来看梯度下降法其实就是一张图的事。请看下面这张图。

    这里的黑色的曲线表示我们损失函数的函数曲线,我们要做的,就是找到这个最佳的参数x,使得损失函数的值最小。损失函数的值达到最小,也就说明了模型的效果达到了极限,这也就是我们预期的。

    我们一开始的时候显然是不知道最佳的x是多少的(废话,知道了还求啥),所以我们假设一开始的时候在一个随机的位置。就假设是图中的(x_1)的位置。接着我们对(x_1)求梯度。我们之前说了,梯度就是该点下降最陡峭的方向,梯度的大小就是它的陡峭程度。我们既然知道了梯度的方向之后,其实就很简单了,我们要做的就是朝着梯度下降,也就是最陡峭的方向向前走一小步。

    我们假设,(x_1)处的梯度是(s_1),那么我们根据(s_1)通过迭代的方法优化损失函数。说起来有些空洞,我写出来就明白了。

    [egin{aligned} x_2 &= x_1 + eta cdot s_1 \ x_3 &= x_2 + eta cdot s_2 \ vdots \ x_n &= x_{n-1} + eta cdot s_{n-1} end{aligned} ]

    从上面这个公式可以看出来,这是一个迭代公式。也就是说我们通过不停地迭代,来优化参数。理论上来说,这样的迭代是没有穷尽的,我们需要手动终止迭代。什么时候可以停止呢?我们可以判断每一次迭代的梯度,当梯度已经小到逼近于0的时候,就说明模型的训练已经收敛了,这个时候可以停止训练了。

    这里的(eta)是一个固定的参数,称为学习率,它表示梯度对于迭代的影响程度。学习率越大,说明梯度对于参数变化的影响越大。如果学习率越小,自然每一次迭代参数的变化也就越小,说明到收敛需要的迭代次数也就越多,也可以单纯理解成,收敛需要的时间也就越长。

    那么是不是学习率越大越好呢?显然也不是的。因为如果学习率过大,很有可能会导致在迭代的过程当中错过最优点。就好像油门踩猛了,一下子就过头了,于是可能会出现永远也无法收敛的情况。比如我们可以参考下面这张图:

    从这张图上可以看到,变量一直在最值附近震荡,永远也达不成收敛状态。

    如果学习率设置得小一些是不是就没事了?也不是,如果设置的学习率过小,除了会导致迭代的次数非常庞大以至于训练花费的时间过久之外,还有可能由于小数的部分过大,导致超出了浮点数精度的范围,以至于出现非法值Nan这种情况出现。同样,我们可以参考一下下图:

    这张图画的是学习率过小,导致一直在迭代,迟迟不能收敛的情况。

    从上面这两张图,我们可以看得出来,在机器学习领域学习率的设置非常重要。一个好的参数不仅可以缩短模型训练的时间,也可以使模型的效果更好。但是设置学习率业内虽然有种种方法,但是不同的问题场景,不同的模型的学习率设置方法都略有差别,也正因此,很多人才会调侃自己是调参工程师。

    我们来看一下一个合适的学习率的迭代曲线是什么样的。

    到这里还没有结束,好的学习率并不能解决所有的问题。在有些问题有些模型当中,很有可能最优解本身就是无法达到的,即使用非常科学的方法,设置非常好的参数。我们再来看一张图:

    这张图有不止一个极值点,如果我们一开始的时候,参数落在了区间的左侧,那么很快模型就会收敛到一个极值,但是它并不是全局最优解,只是一个局部最优解。这时候无论我们如何设置学习率,都不可能找到右侧的那个全局最优解。同样,如果我们一开始参数落在了区间右侧,那里的曲线非常平坦,使得每次迭代的梯度都非常小,非常接近0.那么虽然最终可以到达全局最优解,但是需要经过漫长的迭代过程。

    所以,模型训练、梯度下降虽然方法简单,但是真实的使用场景也是非常复杂的。我们不可以掉以轻心,不过好在,对于线性回归的最小二乘法来说,损失函数是一个凸函数,意味着它一定有全局最优解,并且只有一个。随着我们的迭代,一定可以达到收敛。


    代码实战


    Talk is cheap, show me the code.

    光说不练假把式,既然我们已经学习到了梯度下降的精髓,也该亲身用代码体验一下了。我们还是用之前线性回归的问题。如果有遗忘的同学可以点击下方的链接回顾一下之前的内容:

    一文讲透线性回归模型

    还是和之前一样,我们先生成一批点:

    import numpy as np
    X = 2 * np.random.rand(100, 1)
    y = 4 + 3 * X + np.random.randn(100, 1)
    

    这是根据函数(y = 3x + 4)随机出来的,我们接下来就要通过梯度下降的方法来做线性回归。首先,我们来推导一下梯度公式:

    在使用梯度下降算法的时候,我们其实计算当前( heta)下的梯度。这个量反应的是当我们的( heta)发生变化的时候,整个的损失函数MSE(mean square error 均方差)会变化多少。而梯度,可以通过对变量求偏导得到。写成:(frac {partial}{partial heta_j}MSE( heta))

    我们单独计算( heta_j)的损失函数偏导,写成:(frac {partial}{partial heta_j}MSE( heta)),带入之前的损失函数公式,计算化简可以得到:

    [frac {partial}{partial heta_j}MSE( heta)=frac {1}{m}sum_{i=1}^m( heta^T cdot x_i-y_i)x_j ]

    这只是( heta_j)的偏导数,我们可以把向量( heta)中每一个变量的偏导数合在一起计算。标记为:

    [ abla_ heta MSE( heta)=egin{pmatrix} frac {partial}{partial heta_0}MSE( heta) \ frac {partial}{partial heta_1}MSE( heta) \ vdots\ frac {partial}{partial heta_n}MSE( heta) \ end{pmatrix}=frac {1}{m}X^Tcdot(X cdot heta-y)]

    我们不难看出,在这个公式当中,我们涉及了全量的训练样本X。因此这种方法被称为批量梯度下降。因此,当我们的训练样本非常大的时候,会使得我们的算法非常缓慢。但是使用梯度下降算法,和特征的数量成正比,当特征数量很大的时候,梯度下降要比方程直接求解快得多。

    需要注意一点,我们推导得到的梯度是向上的方向。我们要下降,所以需要加一个负号,最后再乘上学习率,得到的公式如下:

    [ heta^{next step}= heta-eta abla_ heta MSE( heta) ]

    根据公式,写出代码就不复杂了:

    eta = 0.1 # 学习率
    n_iterations = 1000 # 迭代次数
    m = 100
    
    theta = np.random.randn(2,1) # 随机初始值
    X = np.c_[np.ones(100).T, X]
    for iteration in range(n_iterations):
        gradients = 1/m * X.T.dot(X.dot(theta) - y) # 根据梯度公式迭代
        theta = theta - eta * gradients
    

    我们调用一下这段代码,来查看一下结果:

    和我们设置的参数非常接近,效果算是很不错了。如果我们调整学习率和迭代次数,最后的效果可能会更好。

    观察一下代码可以发现,我们在实现梯度下降的时候,用到了全部的样本。显然,随着样本的数量增大,梯度下降会变得非常慢。为了解决这个问题,专家们后续推出了许多优化的方法。不过由于篇幅的限制,我们会在下一篇文章当中和大家分享,感兴趣的同学可以小小地期待一下。

    梯度下降非常重要,可以说是机器学习领域至关重要的基础之一,希望大家都能学会。

    今天的文章就到这里,如果觉得有所收获,请顺手点个关注或者转发吧,你们的支持是我最大的动力。

  • 相关阅读:
    670. Maximum Swap
    653. Two Sum IV
    639. Decode Ways II
    636. Exclusive Time of Functions
    621. Task Scheduler
    572. Subtree of Another Tree
    554. Brick Wall
    543. Diameter of Binary Tree
    535. Encode and Decode TinyURL
    博客园自定义背景图片
  • 原文地址:https://www.cnblogs.com/techflow/p/12297753.html
Copyright © 2011-2022 走看看