梯度下降算法

1 导数

  导数的几何意义:导数又叫微分,是函数曲线的斜率。
  偏导数的几何意义:多元函数表示的是高维空间中的曲面,而曲面上的每一个点有无穷多条切线,求偏导就是选择其中一条切线。

2 梯度

  梯度的本意是一个向量(矢量),表示某一函数在该点处的方向导数沿着该方向取得最大值,即函数在该点处沿着该方向(此梯度的方向)变化最快,变化率最大(为该梯度的模)在单变量的实值函数的情况,梯度只是导数,或者,对于一个线性函数,也就是线的斜率。(—百度百科)
  一元函数y=f(x)y=f(x)在点x0x_0处的梯度是:dfdxx=x0\frac{d_f}{d_x}|_{x=x_0}
  二元函数z=f(x,y)z=f(x,y)在点(x0,y0)(x_0,y_0)处的梯度是:(fx(x0,y0),fy(x0,y0))(\frac{\partial{f}}{\partial{x}}|_{(x_0,y_0)},\frac{\partial{f}}{\partial{y}}|_{(x_0,y_0)})
  简而言之,对多元函数的的各个自变量求偏导数,并把求得的这些偏导数写成向量的形式,就是梯度。常把函数ff的梯度简记为:f\nabla{f}或者gradf\text{grad}f

例子:
函数φ=2x+3y2+sin(x)\varphi=2x+3y^2+sin(x)的梯度是:
φ=(φx,φy,φz)=(2,6y,cos(z))\nabla\varphi=(\frac{\partial{\varphi}}{\partial{x}},\frac{\partial{\varphi}}{\partial{y}},\frac{\partial{\varphi}}{\partial{z}})=(2,6y,-cos(z))

(注意:梯度是一个向量)
  要明确梯度是一个向量,是一个n元函数 ff 关于n个变量的偏导数,比如三元函数 ff 的梯度为 (fx,fy,fz)(fx,fy,fz) ,二元函数f的梯度为 (fx,fy)(fx,fy) ,一元函数 ff 的梯度为 fxfx。然后要明白梯度的方向是函数f增长最快的方向,梯度的反方向是f降低最快的方向。

3 梯度下降的场景假设

梯度下降法的基本思想可以类比为一个下山的过程。假设这样一个场景:一个人被困在山上,需要从山上下来(i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后每走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷。
在这里插入图片描述
我们同时可以假设这座山最陡峭的地方是无法通过肉眼立马观察出来的,而是需要一个复杂的工具来测量,同时,这个人此时正好拥有测量出最陡峭方向的能力。所以,此人每走一段距离,都需要一段时间来测量所在位置最陡峭的方向,这是比较耗时的。那么为了在太阳下山之前到达山底,就要尽可能的减少测量方向的次数。这是一个两难的选择,如果测量的频繁,可以保证下山的方向是绝对正确的,但又非常耗时,如果测量的过少,又有偏离轨道的风险。所以需要找到一个合适的测量方向的频率,来确保下山的方向不错误,同时又不至于耗时太多!

  引自:六尺帐篷  链接:https://www.jianshu.com/p/c7e642877b0e

4 微分

  看待微分的意义,可以有不同的角度,最常用的两种是:

  • 函数图像中,某点的切线的斜率
  • 函数的变化率

5 梯度下降实例分析

  梯度下降的基本过程就和下山的场景很类似。
  首先,我们有一个可微分的函数。这个函数就代表着一座山。我们的目标就是找到这个函数的最小值,也就是山底。根据之前的场景假设,最快的下山的方式就是找到当前位置最陡峭的方向,然后沿着此方向向下走,对应到函数中,就是找到给定点的梯度 ,然后朝着梯度相反的方向,就能让函数值下降的最快!因为梯度的方向就是函数之变化最快的方向。所以,我们重复利用这个方法,反复求取梯度,最后就能到达局部的最小值,这就类似于我们下山的过程。而求取梯度就确定了最陡峭的方向,也就是场景中测量方向的手段。那么为什么梯度的方向就是最陡峭的方向呢?
  通过实例说明:
  对于Rosenbrock函数:f(x,y)=(1x)2+100(yx2)2f(x,y)=(1-x)^2+100(y-x^2)^2,使用梯度下降算法求解最小值,函数模型:
在这里插入图片描述
  模型的代码:

# -*- coding: utf-8 -*-
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import animation as amat

"this function: f(x,y) = (1-x)^2 + 100*(y - x^2)^2"


def Rosenbrock(x, y):
    return np.power(1 - x, 2) + np.power(100 * (y - np.power(x, 2)), 2)


def show(X, Y, func=Rosenbrock):
    fig = plt.figure()
    ax = Axes3D(fig)
    X, Y = np.meshgrid(X, Y, sparse=True)
    Z = func(X, Y)
    plt.title("gradeAscent image")
    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow', )
    ax.set_xlabel('x label', color='r')
    ax.set_ylabel('y label', color='g')
    ax.set_zlabel('z label', color='b')
    amat.FuncAnimation(fig, Rosenbrock, frames=200, interval=20, blit=True)
    plt.show()

if __name__ == '__main__':
    X = np.arange(-2, 2, 0.1)
    Y = np.arange(-2, 2, 0.1)
    Z = Rosenbrock(X, Y)
    show(X, Y, Rosenbrock)

   我们求解出它的梯度方向 grad(f(x,y))=(2(1x)400(yxx)xgrad(f(x,y)) = ( -2*( 1 - x ) -400( y - x*x )*x200(yxx))200(y - x*x)) 沿着该梯度的反方向就可以快速确定 xxyy 位置的最小点。即最小值 f(1,1)min=0f(1,1)_{min} = 0
在这里插入图片描述
  数据变化1:
在这里插入图片描述
  效果图2:
在这里插入图片描述
  数据变化截图:
在这里插入图片描述
  代码:

# -*- coding: utf-8 -*-
import random

import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import animation as amat

"this function: f(x,y) = (1-x)^2 + 100*(y - x^2)^2"


def Rosenbrock(x, y):
    return np.power(1 - x, 2) + np.power(100 * (y - np.power(x, 2)), 2)


def show(X, Y, func=Rosenbrock):
    fig = plt.figure()
    ax = Axes3D(fig)
    X, Y = np.meshgrid(X, Y, sparse=True)
    Z = func(X, Y)
    plt.title("gradeAscent image")
    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow', )
    ax.set_xlabel('x label', color='r')
    ax.set_ylabel('y label', color='g')
    ax.set_zlabel('z label', color='b')
    plt.show()


def drawPaht(px, py, pz, X, Y, func=Rosenbrock):
    fig = plt.figure()
    ax = Axes3D(fig)
    X, Y = np.meshgrid(X, Y, sparse=True)
    Z = func(X, Y)
    plt.title("gradeAscent image")
    ax.set_xlabel('x label', color='r')
    ax.set_ylabel('y label', color='g')
    ax.set_zlabel('z label', color='b')
    ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap='rainbow', )
    ax.plot(px, py, pz, 'r.')  # 绘点
    plt.show()


def gradeAscent(X, Y, Maxcycles=10000, learnRate=0.0008):
    # x, Y = np.meshgrid(X, Y, sparse=True)
    new_x = [X]
    new_Y = [Y]
    g_z=[Rosenbrock(X, Y)]
    current_x = X
    current_Y = Y
    for cycle in range(Maxcycles):
        "为了更好的表示grad,我这里对表达式不进行化解"
        current_Y -= learnRate * 200 * (Y - X * X)
        current_x -= learnRate * (-2 * (1 - X) - 400 * X * (Y - X * X))
        X = current_x
        Y = current_Y
        new_x.append(X)
        new_Y.append(Y)
        g_z.append(Rosenbrock(X, Y))
    return new_x, new_Y, g_z


if __name__ == '__main__':
    X = np.arange(-3, 4, 0.1)
    Y = np.arange(-3, 4, 0.1)
    x = random.uniform(-3, 4)
    y = random.uniform(-3, 4)
    print x,y
    x, y, z = gradeAscent(x, y)
    print len(x),x
    print len(y),y
    print len(z),z
    drawPaht(x, y, z, X, Y, Rosenbrock)

  在实际过程中,需要使用电脑处理一些数据的时候,在此过程中,需要找到数据之间的关系,我们会使用生成函数方式来构造一个,我们称之为生成函数,或者母函数或者其他。
  电脑处理数据的过程是:需要我们手动给予一个通用表达式,比如线性的,我们需要设定它为 y=kx+by=kx+b,然后在给电脑这些数据,告诉它说,这些个数据是线性相关的,你去找到一个 kbk,b,使这些点尽可能的满足这个方程吧!而这个过程我们又将它称之为拟合过程。
  所以呢?面对一堆数据,而我们给定了一个通用的表达式比如:
hθ(x)=θ0+θ1x1+θ2x2 h_\theta(x) = \theta_0+\theta_1x_1+\theta_2x_2
  其中,θ\theta 为每一个特征变量的权重,比如特征 x1x_1 的权重为 θ1\theta_1,我们设定 x0=1x_0=1,然后我们将其简化为:
h(x)=i=0nθixi h(x)=\sum_{i=0}^n\theta_ix_i
  如果我们在将其转化成多维空间的话,其实可以使用还可以这样:
h(x)=i=0nθixi=θTx h(x)=\sum_{i=0}^n\theta_ix_i=\theta^Tx,
  但是到这里,这里依旧还只是一个通试而已,那么我们该如何使用其那些数据呢?
  这时候,我们需要来引入一个新的函数,来评估这个通式(我们可以随机给这个式子权重赋值)与实际的值是否在可接收的范围!
  但是到这里,这里依旧还只是一个通试而已,那么我们该如何使用其那些数据呢?
  这时候,我们需要来引入一个新的函数,来评估这个通式(我们可以随机给这个式子权重赋值)与实际的值是否在可接收的范围!
J(θ)=12mi=0m(hθ(x(i)y(i))2 J(\theta)=\frac{1}{2m}\sum_{i=0}^{m}(h_\theta(x^{(i)}-y^{(i)})^2,
  这个函数称谓有两种,一种是损失函数(Loss function),一种是误差函数(Error function)。

6 代价函数(Cost Function)

注:文中使用的符号 :=:= 是赋值运算符,它本身是计算机语言中的符号,屋恩达使用这个符号,所以网上很多资料也为了保持一致使用这个符号,但是在数学中,并没有见过这个符号!!!!!!
  代价函数(有的地方也叫损失函数,Loss Function),因为训练模型的过程就是优化代价函数的过程,代价函数对每个参数的偏导数就是梯度下降中提到的梯度,防止过拟合时添加的正则化项也是加在代价函数后面的。
  我们可以用成本函数来衡量假设函数的准确性。它取输入x后的假设值和实际输出y的所有结果的平均值差(实际上是平均值的更漂亮的版本)。
J(θ0,θ1)=12mi=1m(y^iyi)2=12mi=1m(hθ(xi)yi)2J(\theta_0, \theta_1) = \dfrac {1}{2m} \displaystyle \sum _{i=1}^m \left ( \hat{y}_{i}- y_{i} \right)^2 = \dfrac {1}{2m} \displaystyle \sum _{i=1}^m \left (h_\theta (x_{i}) - y_{i} \right)^2
  说明一下这个式子的含义:
  mm 是数据集中点的个数;1/21/2这样是为了在求梯度的时候,二次方乘下来就和这里的 ½½抵消了,自然就没有多余的常数系数,方便后续的计算,同时对结果不会有影响;yy 是数据集中每个点的真实值;hh 是预测值。
  这个函数因此也称为 “Squared error function”,或者"Mean squared error"。
  JJ 表示给定的函数预测值和实际值Y的均方差,它反映的是预测值与实际值的一个偏离的程度。
  我们是否可以用梯度下降算法来快速的无限逼近 θ\theta,使得 JJ 达到最小,当 JJ 达到最小的时候,那么我们这个时候的 θ\theta,不就是无限接近真实且理想的的那个权重 θ\theta 么?

7 批量梯度下降算法(BGD)

  然后我们再按照梯度的方向逐步的移动,慢慢的逼近收敛值。用表达式表示为:
θj:=θjαθjJ(θ). \theta_j := \theta_j - \alpha\frac{\partial}{\partial\theta_j}J(\theta).
  其中,α\alpha 表示一个学习率(详解见后文),之所以添加这个学习率,是因为我们使用的是均方差,如果我们随机的方程预测的值与实际的值偏差比较大的话,均方差的值将会非常巨大,这样的话,可能造成我们的这个损失函数出现大幅度的偏移,我们称之为摆钟行为,所以为了避免出现这种情况,这个值就这么的诞生了,这个值的大小,表示每次向着 JJ 最陡峭的方向迈步的大小,可以用来调整我们移动的的步子大小。还需要解释的是:
θjJ(θ) \frac{\partial}{\partial\theta_j}J(\theta)
  表示的是损失函数的权重梯度,那么对于这个梯度,简化为:
θjJ(θ)=θj12(hθ(x)y)2=212(hθ(x)y)θj(hθ(x)y)=(hθ(x)y)θj(i=0nθixiy)=(hθ(x)y)xi \begin{aligned} \frac{\partial}{\partial\theta_j}J(\theta)&=\frac{\partial}{\partial\theta_j}\frac{1}{2}(h_\theta(x)-y)^2 \\ &=2*\frac{1}{2}(h_\theta(x)-y)*\frac{\partial}{\partial\theta_j}(h_\theta(x)-y) \\ &= (h_\theta(x)-y)*\frac{\partial}{\partial\theta_j}(\sum_{i=0}^n\theta_ix_i-y) \\ &= (h_\theta(x)-y)x_i \end{aligned}
  得到上面的推导之后,所以可以用 (hθ(x)y)xi(h_\theta(x)-y)x_i 替换掉 θj:=θjαθjJ(θ)\theta_j := \theta_j - \alpha\frac{\partial}{\partial\theta_j}J(\theta) 中的 θjJ(θ)\frac{\partial}{\partial\theta_j}J(\theta),然后得到:
θj:=θj+α(y(i)hθ(xi))xj(i). \theta_j := \theta_j+\alpha(y^{(i)}-h_\theta(x^{i}))x_j^{(i)}.
  方程中的,ii 表示的是样本数据中第 i 组训练数据,jj 其实就是我们的对应的第 j 个权重。伪代码表示:

"这里权重用W表示 , trainingSet 表示训练数据集合 "
for i in range(len(trainingSet)):
    "n 表示有多少个特征Xj (j属于[1,n])"
    for j in range(n):
       w -= a*(yi - h(xi))Xij

  而这种方式,是将所有的样本M都参与进去训练,然后得到一个权重值w。这种方式,我们称之为批量梯度下降算法,也就是BGD。
  但是这个算法有个缺点,算法时间复杂度为 O(n2)O(n^2),当样本量比较大的话,计算量就会变得很大,所以这种方式适用的范围,仅是对那些样本较小的数据而言,对于大数据量样本而言,这个还是不太好的。

8 随机梯度下降算法(SGD)

  它的大体思路就是:在给定的样本集合M中,随机取出副本N代替原始样本M来作为全集,对模型进行训练。这种训练由于是抽取部分数据,所以有较大的机率得到的是,一个局部最优解。但是一个明显的好处是,如果在样本抽取合适范围内,既会求出结果,而且速度还快。

9 梯度下降存在的问题

  一:参数调整缓慢
  梯度下降算法走到接近极小值的时候,由于谷底很平,梯度很小,参数调整会变得缓慢。
  在曲线/曲面的平坦区、或者鞍点,也会有这个问题。
  二:收敛于局部极小值
  没有收敛到全局最小值,只收敛到局部最小值。

10 学习率

  对于梯度下降算法,这应该是一个最重要的超参数。如果学习速率设置得非常大,那么训练可能不会收敛,就直接发散了;如果设置的比较小,虽然可以收敛,但是训练时间可能无法接受;如果设置的稍微高一些,训练速度会很快,但是当接近最优点会发生震荡,甚至无法稳定。不同学习速率的选择影响可能非常大,如图:

  理想的学习速率是:刚开始设置较大,有很快的收敛速度,然后慢慢衰减,保证稳定到达最优点。所以,前面的很多算法都是学习速率自适应的。除此之外,还可以手动实现这样一个自适应过程,如实现学习速率指数式衰减:
η(t)=η010tr \eta(t)=\eta_0*10^{\frac{-t}{r}}
  在TensorFlow中,你可以这样实现:

initial_learning_rate = 0.1
decay_steps = 10000
decay_rate = 1/10
global_step = tf.Variable(0, trainable=False)
learning_rate = tf.train.exponential_decay(initial_learning_rate,                           
                            global_step, decay_steps, decay_rate)
# decayed_learning_rate = learning_rate *
#                decay_rate ^ (global_step / decay_steps)
optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
training_op = optimizer.minimize(loss, global_step=global_step)

  
  
  
  
  

参考文章

  【机器学习】代价函数(cost function):https://www.cnblogs.com/Belter/p/6653773.html
  学习率(Learning rate)的理解以及如何调整学习率:https://www.cnblogs.com/lliuye/p/9471231.html
  深入浅出–梯度下降法及其实现:https://www.jianshu.com/p/c7e642877b0e
  关于梯度下降算法的的一些总结:https://www.cnblogs.com/gongxijun/p/5890548.html
  最清晰的讲解各种梯度下降法原理与Dropout:https://baijiahao.baidu.com/s?id=1613121229156499765&wfr=spider&for=pc
  一文看懂常用的梯度下降算法:https://blog.csdn.net/u013709270/article/details/78667531

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章