梯度下降法的原理

假设f(x)是R^n上具有一阶连续偏导数的函数,要求解的无约束最优化问题是

                                                               

梯度下降代码 梯度下降法代码实现_梯度下降代码

梯度下降代码 梯度下降法代码实现_迭代_02

表示目标函数

梯度下降代码 梯度下降法代码实现_梯度下降代码_03

的极小值点。

梯度下降代码 梯度下降法代码实现_梯度下降法_04

梯度下降代码 梯度下降法代码实现_梯度下降法_05

 

梯度下降代码 梯度下降法代码实现_迭代_06

梯度下降代码 梯度下降法代码实现_梯度下降法_07

梯度下降代码 梯度下降法代码实现_梯度下降法_08

虽然上面在求

梯度下降代码 梯度下降法代码实现_梯度下降法_09

使用的tk为当前最小值得到的结果,但在工程中,会设定一定的值,即学习率或步长。意味着我们可以通过步长来控制每一步走的距离其实就是不要走太快,错过了最低点。同时也要保证不要走的太慢,导致收敛很慢。

梯度下降的场景假设

梯度下降法的基本思想可以类比为一个下山的过程。假设这样一个场景:一个人被困在山上,需要从山上下来(i.e. 找到山的最低点,也就是山谷)。但此时山上的浓雾很大,导致可视度很低。因此,下山的路径就无法确定,他必须利用自己周围的信息去找到下山的路径。这个时候,他就可以利用梯度下降算法来帮助自己下山。具体来说就是,以他当前的所处的位置为基准,寻找这个位置最陡峭的地方,然后朝着山的高度下降的地方走,同理,如果我们的目标是上山,也就是爬到山顶,那么此时应该是朝着最陡峭的方向往上走。然后每走一段距离,都反复采用同一个方法,最后就能成功的抵达山谷。

梯度下降代码 梯度下降法代码实现_迭代_10

我们同时可以假设这座山最陡峭的地方是无法通过肉眼立马观察出来的,而是需要一个复杂的工具来测量,同时,这个人此时正好拥有测量出最陡峭方向的能力。所以,此人每走一段距离,都需要一段时间来测量所在位置最陡峭的方向,这是比较耗时的。那么为了在太阳下山之前到达山底,就要尽可能的减少测量方向的次数。这是一个两难的选择,如果测量的频繁,可以保证下山的方向是绝对正确的,但又非常耗时,如果测量的过少,又有偏离轨道的风险。所以需要找到一个合适的测量方向的频率,来确保下山的方向不错误,同时又不至于耗时太多!

梯度下降算法的实例

我们已经基本了解了梯度下降算法的计算过程,那么我们就来看几个梯度下降算法的小实例,首先从单变量的函数开始

单变量函数的梯度下降

我们假设有一个单变量的函数

梯度下降代码 梯度下降法代码实现_迭代_11

函数的微分

梯度下降代码 梯度下降法代码实现_梯度下降_12

初始化,起点为

梯度下降代码 梯度下降法代码实现_迭代_13

学习率为

梯度下降代码 梯度下降法代码实现_梯度下降代码_14

根据梯度下降的计算公式

 

我们开始进行梯度下降的迭代计算过程:

梯度下降代码 梯度下降法代码实现_梯度下降代码_15

如图,经过四次的运算,也就是走了四步,基本就抵达了函数的最低点,也就是山底

梯度下降代码 梯度下降法代码实现_梯度下降法_16

多变量函数的梯度下降

我们假设有一个目标函数

梯度下降代码 梯度下降法代码实现_梯度下降_17

现在要通过梯度下降法计算这个函数的最小值。我们通过观察就能发现最小值其实就是 (0,0)点。但是接下来,我们会从梯度下降算法开始一步步计算到这个最小值!
我们假设初始的起点为:

梯度下降代码 梯度下降法代码实现_迭代_18

初始的学习率为:

梯度下降代码 梯度下降法代码实现_迭代_19

函数的梯度为:

梯度下降代码 梯度下降法代码实现_梯度下降法_20

进行多次迭代:

梯度下降代码 梯度下降法代码实现_迭代_21

我们发现,已经基本靠近函数的最小值点

梯度下降代码 梯度下降法代码实现_梯度下降_22

 

梯度下降算法的实现

下面我们将用python实现一个简单的梯度下降算法。场景是一个简单的线性回归的例子:假设现在我们有一系列的点,如下图所示

梯度下降代码 梯度下降法代码实现_梯度下降代码_23

 

我们将用梯度下降法来拟合出这条直线!

首先,我们需要定义一个代价函数,在此我们选用均方误差代价函数

梯度下降代码 梯度下降法代码实现_梯度下降法_24

此公示中

  • m是数据集中点的个数
  • ½是一个常量,这样是为了在求梯度的时候,二次方乘下来就和这里的½抵消了,自然就没有多余的常数系数,方便后续的计算,同时对结果不会有影响
  • y 是数据集中每个点的真实y坐标的值
  • h 是我们的预测函数,根据每一个输入x,根据Θ 计算得到预测的y值,即

我们可以根据代价函数看到,代价函数中的变量有两个,所以是一个多变量的梯度下降问题,求解出代价函数的梯度,也就是分别对两个变量进行微分

梯度下降代码 梯度下降法代码实现_梯度下降_25

明确了代价函数和梯度,以及预测的函数形式。我们就可以开始编写代码了。但在这之前,需要说明一点,就是为了方便代码的编写,我们会将所有的公式都转换为矩阵的形式,python中计算矩阵是非常方便的,同时代码也会变得非常的简洁。

为了转换为矩阵的计算,我们观察到预测函数的形式

梯度下降代码 梯度下降法代码实现_梯度下降法_26

我们有两个变量,为了对这个公式进行矩阵化,我们可以给每一个点x增加一维,这一维的值固定为1,这一维将会乘到Θ0上。这样就方便我们统一矩阵化的计算

梯度下降代码 梯度下降法代码实现_梯度下降代码_27

然后我们将代价函数和梯度转化为矩阵向量相乘的形式

梯度下降代码 梯度下降法代码实现_梯度下降法_28

import numpy as np

m=20

X0=np.ones((m,1))
X1=np.arange(1,m+1).reshape(m,1)
X=np.hstack((X0,X1))

y=np.array([3,4,5,5,2,4,7,8,11,8,12,
            11,13,13,16,17,18,17,19,21]).reshape((m,1))

alpha=0.01

def error_function(theta,X,y):
    diff=np.dot(X,theta)-y
    return (1./2*m)*np.dot(np.transpose(diff),diff)

def gradient_function(theta,X,y):
    diff=np.dot(X,theta)-y
    return (1./m)*np.dot(np.transpose(X),diff)

def gradient_descent(X,y,alpha):
    theta=np.array([1,1]).reshape((2,1))
    gradient=gradient_function(theta,X,y)
    while not all(np.absolute(gradient)<=1e-5):#all函数用于判断数组中所有的元素是否都为true
        theta=theta-gradient*alpha
        gradient=gradient_function(theta,X,y)
    return theta

optimal=gradient_descent(X,y,alpha)
print('optimal:',optimal)
print('error function:',error_function(optimal,X,y)[0,0])


梯度下降法的三种形式BGD、SGD以及MBGD

在应用机器学习算法时,我们通常采用梯度下降法来对采用的算法进行训练。其实,常用的梯度下降法还具体包含有三种不同的形式,它们也各自有着不同的优缺点。

下面我们以线性回归算法来对三种梯度下降法进行比较。

一般线性回归函数的假设函数为:

梯度下降代码 梯度下降法代码实现_梯度下降代码_29

对应的损失函数为:

 

梯度下降代码 梯度下降法代码实现_梯度下降代码_30

这里的1/2是为了后面求导计算方便

梯度下降代码 梯度下降法代码实现_迭代_31

梯度下降代码 梯度下降法代码实现_梯度下降_32

 

下面我们来分别讲解三种梯度下降法

批量梯度下降法BGD

我们的目的是要误差函数尽可能的小,即求解weights使误差函数尽可能小。首先,我们随机初始化weigths,然后不断反复的更新weights使得误差函数减小,直到满足要求时停止。这里更新算法我们选择梯度下降算法,利用初始化的weights并且反复更新weights:

梯度下降代码 梯度下降法代码实现_迭代_33

梯度下降代码 梯度下降法代码实现_梯度下降_34

 

梯度下降代码 梯度下降法代码实现_梯度下降法_35

 

则对所有数据点,上述损失函数的偏导(累和)为:

 

梯度下降代码 梯度下降法代码实现_梯度下降代码_36

再最小化损失函数的过程中,需要不断反复的更新weights使得误差函数减小,更新过程如下:

 

 

梯度下降代码 梯度下降法代码实现_梯度下降_37

那么好了,每次参数更新的伪代码如下:

梯度下降代码 梯度下降法代码实现_迭代_38

由上图更新公式我们就可以看到,我们每一次的参数更新都用到了所有的训练数据(比如有m个,就用到了m个),如果训练数据非常多的话,是非常耗时的。

下面给出批梯度下降的收敛图:

梯度下降代码 梯度下降法代码实现_梯度下降法_39

从图中,我们可以得到BGD迭代的次数相对较少。

随机梯度下降法SGD

由于批梯度下降每跟新一个参数的时候,要用到所有的样本数,所以训练速度会随着样本数量的增加而变得非常缓慢。随机梯度下降正是为了解决这个办法而提出的。它是利用每个样本的损失函数对θ求偏导得到对应的梯度,来更新θ:

梯度下降代码 梯度下降法代码实现_梯度下降_40

更新过程如下:

梯度下降代码 梯度下降法代码实现_梯度下降法_41

 随机梯度下降是通过每个样本来迭代更新一次,对比上面的批量梯度下降,迭代一次需要用到所有训练样本(往往如今真实问题训练数据都是非常巨大),一次迭代不可能最优,如果迭代10次的话就需要遍历训练样本10次。但是,SGD伴随的一个问题是噪音较BGD要多,使得SGD并不是每次迭代都向着整体最优化方向。

 

随机梯度下降收敛图如下:

梯度下降代码 梯度下降法代码实现_迭代_42

我们可以从图中看出SGD迭代的次数较多,在解空间的搜索过程看起来很盲目。但是大体上是往着最优值方向移动。

min-batch 小批量梯度下降法MBGD

我们从上面两种梯度下降法可以看出,其各自均有优缺点,那么能不能在两种方法的性能之间取得一个折衷呢?即,算法的训练过程比较快,而且也要保证最终参数训练的准确率,而这正是小批量梯度下降法(Mini-batch Gradient Descent,简称MBGD)的初衷。

我们假设每次更新参数的时候用到的样本数为10个(不同的任务完全不同,这里举一个例子而已

更新伪代码如下:

梯度下降代码 梯度下降法代码实现_梯度下降代码_43

import numpy as np
import random

def gradient_function(theta,X,y,m):
    diff=np.dot(X,theta)-y
    return (1./m)*np.dot(np.transpose(X),diff)

def error_function(theta,X,y):
    diff=np.dot(X,theta)-y
    return diff

#下面实现的是批量梯度下降法
def batchGradientDescent(x, y, theta, alpha, m, maxIterations):
    for i in range(0,maxIterations):
        gardient=gradient_function(theta,x,y,m)
        theta=theta-alpha*gardient
    return theta

#下面实现的是随机梯度下降法
def StochasticGradientDescent(x, y, theta, alpha, m, maxIterations):
    data = []
    for i in range(10):
        data.append(i)
    xTrains = x.transpose()     #变成3*10,没一列代表一个训练样本
    # 这里随机挑选一个进行更新点进行即可(不用像上面一样全部考虑)
    for i in range(0,maxIterations):
        diff=error_function(theta,x,y)
        index=random.sample(data,1)
        index1=index[0]
        gradient=diff[index1]*x[index1]
        theta=theta-alpha*np.transpose(gradient)
    return theta

#下面实现小批量梯度下降法
def MiniBatchGradientDescent(x,y,theta,alpha,m,maxIterations):
    #假设我们每次一个batch为4
    data = []
    for i in range(10):
        data.append(i)
    for i in range(0,maxIterations):
        diff = error_function(theta, x, y)
        gradient1=np.array([0,0,0])
        for i in range(4):
            index = random.sample(data, 1)
            index1 = index[0]
            gradient = diff[index1] * x[index1]
            gradient1=gradient1+gradient
        gradient1=gradient1/4
        theta = theta - alpha * np.transpose(gradient1)
    return theta

def predict(x, theta):
    m, n = np.shape(x)
    xTest = np.ones((m, n+1))                     #在这个例子中,是第三列放1
    xTest[:, :-1] = x                             #前俩列与x相同
    res = np.dot(xTest, theta)                    #预测这个结果
    return res

trainData = np.array([[1.1,1.5,1],[1.3,1.9,1],[1.5,2.3,1],[1.7,2.7,1],[1.9,3.1,1],[2.1,3.5,1],[2.3,3.9,1],[2.5,4.3,1],[2.7,4.7,1],[2.9,5.1,1]])
trainLabel = np.array([2.5,3.2,3.9,4.6,5.3,6,6.7,7.4,8.1,8.8])
m, n = np.shape(trainData)
theta = np.ones(n)
alpha = 0.1
maxIteration = 5000
#下面返回的theta就是学到的theta
theta = batchGradientDescent(trainData, trainLabel, theta, alpha, m, maxIteration)
print("theta = ",theta)
x = np.array([[3.1, 5.5], [3.3, 5.9], [3.5, 6.3], [3.7, 6.7], [3.9, 7.1]])
print(predict(x, theta))
theta = StochasticGradientDescent(trainData, trainLabel, theta, alpha, m, maxIteration)
print("theta = ",theta)
x = np.array([[3.1, 5.5], [3.3, 5.9], [3.5, 6.3], [3.7, 6.7], [3.9, 7.1]])
print(predict(x, theta))
theta = MiniBatchGradientDescent(trainData, trainLabel, theta, alpha, m, maxIteration)
print("theta = ",theta)
x = np.array([[3.1, 5.5], [3.3, 5.9], [3.5, 6.3], [3.7, 6.7], [3.9, 7.1]])
print(predict(x, theta))

以上除小批量梯度下降法的代码外