【Machine Learning】梯度下降算法介紹_02

前言

此篇文章大體介紹了一下關於梯度下降算法的一些基本概念性知識,接下來我們繼續更深層次的來了解一下梯度下降算法。

一、梯度

       梯度的本意是一個向量(矢量),表示一個函數在該點出的方向導數的取得的最大值,或者說函數在該點出沿着該方法(此梯度的方向也就是求導的方向)變化率最大、最快;亦是在微積分裏面,對多元函數參數求偏導,把求的各參數的偏導數以向量形式寫出來,就是梯度。
       從幾何意義上講,梯度向量就是函數變化最快的地方,沿着梯度方向就能得到函數的最大值,相反的話就能獲取梯度下降的最小值。
       梯度下降和梯度上升是可以互相轉化。求損失函數f(θ)f(θ)的最小值,用梯度下降法迭代;亦可反過來求損失函數f(θ)-f(θ)的最大值,用梯度上升法。
       【百度百科】梯度下降是迭代法的一種,可以用於求解最小二乘問題(線性和非線性都可以)。在求解機器學習算法的模型參數,即無約束優化問題時,梯度下降(Gradient Descent)是最常採用的方法之一,另一種常用的方法是最小二乘法。在求解損失函數的最小值時,可以通過梯度下降法來一步步的迭代求解,得到最小化的損失函數和模型參數值。反過來,如果我們需要求解損失函數的最大值,這時就需要用梯度上升法來迭代了。在機器學習中,基於基本的梯度下降法發展了兩種梯度下降方法,分別爲隨機梯度下降法和批量梯度下降法


       梯度是一個向量,表示某一函數在該點處的方向導數 ,沿着該方向取最大值,即函數在該點處沿着該方向變化最快,變化率最大(即該梯度向量的模);當函數爲一維函數的時候,梯度就是導數

1.1 導數

提到導數,相比大家都應該不陌生吧,它代表的含義就是反應出曲線變化快慢。
一階導數:就是曲線的斜率,是曲線變化快慢的一個反應
二階導數:是斜率變化的反應,表現曲線的凹凸性
y=f(x)y=f(x)=dydx=limΔx0ΔyΔx=limΔx0f(x0+Δx)f(x0)Δx\begin{array}{c}{y=f(x)} \\ {y^{\prime}=f^{\prime}(x)=\frac{d y}{d x}=\lim _{\Delta x \rightarrow 0} \frac{\Delta y}{\Delta x}=\lim _{\Delta x \rightarrow 0} \frac{f\left(x_{0}+\Delta x\right)-f\left(x_{0}\right)}{\Delta x}}\end{array}

高等數學中專門有關於導數的學習

1.2 偏導數

       導數是針對單一變量的,當函數是多變量的,偏導數就是關於其中一個變量的導數而保持其他變量恆定不變(固定一個變量求導數)。
       假定一個二元函數點z=f(x,y)z=f(x,y),點(x0,y0)(x_0,y_0)是其定義域內的一個點,將yy固定在y0y_0上,而xxx0x_0上增量Δx\Delta x,相應的函數zz有增量Δz=f(x0+Δx,y0)f(x0,y0)\Delta z=f\left(x_{0}+\Delta x, y_{0}\right)-f\left(x_{0}, y_{0}\right);Δz\Delta zΔx\Delta x的比值當Δx\Delta x的值趨向於0的時候,如果極限存在,那麼此極限稱爲函數z=f(x,y)z=f(x, y)在點(x0,y0)(x_0,y_0)處對xx的偏導數,記作:fx(x0,y0)f_{x}^{\prime}\left(x_{0}, y_{0}\right)

xx的偏導數:

fx(x=x0,y=y0)\frac{\partial f}{\partial x} |\left(x=x_{0}, y=y_{0}\right)

yy的偏導數:

fy(x=x0,y=y0)\frac{\partial f}{\partial y} |\left(x=x_{0}, y=y_{0}\right)


二、舉例梯度下降

舉例解釋梯度下降
比如我們在山上的某處,我們不知道如何下山,於是就決定走一步算一步(船到橋頭自然直思想)。

1、也就是每走到一個位置的時候,計算當前位置的梯度,沿着梯度的負方向,也就是當前最陡峭的位置向下走一步
2、繼續求解當前位置梯度,向這一步所在位置沿着最陡峭易下山的位置走一步。
3、如此一步步的走下去,一直走到我們自己認爲到了山腳。(有可能這樣一直走下去到不了山腳,而是到了某一個局部的山峯低處)。

也就是說,梯度下降並不一定能get到全局的最優解,有時候可能獲取到局部最優解。當然,如果損失函數是凸函數,梯度下降得到的解就一定是全局最優解。
借圖如下所示:
在這裏插入圖片描述


梯度下降法常用於求解無約束情況下凸函數的極小值,是一種迭代類型的算法,因爲凸函數只有一個極值點,故求解出來的極小值就是函數的最小值點

J(θ)=12mi=1m(hθ(x(i))y(i))2θ=argminθJ(θ)\begin{array}{c}{J(\theta)=\frac{1}{2 m} \sum_{i=1}^{m}\left(h \theta\left(x^{(i)}\right)-y^{(i)}\right)^{2}} \\ {\theta^{*}=\arg \min _{\theta} J(\theta)}\end{array}
梯度下降優化思想

用當前位置的負梯度方向作爲搜索方向,因爲該方向爲當前位置下降最快的方向,所以梯度下降法也被稱爲“最速下降法”。梯度下降法中越接近目標值,變化量越小

計算公式如下(迭代公式):
θk+1=θkαΔf(θk)\theta^{k+1}=\theta^{k}-\alpha \Delta f\left(\theta^{k}\right)

α被稱爲步長或者學習率,表示自變量 x每次迭代變化的大小
收斂條件:當目標函數的函數值變化非常小的時候或者達到最大迭代次數的時候,就結束循環。

三、訓練樣本

我們從1>n1—>n的方式擴展訓練樣本,以此,來觀察訓練樣本的變化。
求解使得J(θ)J(\theta)最小時,θ\theta值的基本思想:

1:首先隨便給θ\theta一個初始值
2:然後改變θ\theta 值讓J(θ)J(\theta)的取值變小
3:不斷重複θ\theta使得J(θ)J(\theta)變小的過程直至J(θ)J(\theta)達到最小值

步驟

  • 目標函數θ\theta求解:
    J(θ)=12i=1m(hθ(x(i)y(i))2J(\theta)=\frac{1}{2} \sum_{i=1}^{m}\left(h_{\theta}\left(x^{(i)}-y^{(i)}\right)^{2}\right.

  • 初始化θ\theta(隨機初始化,可以初始化爲0滴)

  • 沿着負梯度方向迭代,更新後的θ\theta使得J(θ)J(\theta)更小:

    θj=θjαJ(θ)θj\theta_{j}=\theta_{j}-\alpha \frac{\partial J(\theta)}{\partial \theta_{j}}

      其中,α\alpha是學習率、步長;它控制θ\theta每次向J(θ)J(\theta)變小的方向迭代時的變化幅度。J(θ)J(\theta)θ\theta偏導表示J(θ)J(\theta)變化最大的方向。由於求的是極小值,因此梯度方向是偏導數的反方向。


求解偏導:
θjJ(θ)=θj12(hθ(x)y)2\frac{\partial}{\partial \theta_{j}} J(\theta)=\frac{\partial}{\partial \theta_{j}} \frac{1}{2}\left(h_{\theta}(x)-y\right)^{2}

=212(hθ(x)y)θj(hθ(x)y)=(hθ(x)y)θj(i=0nθixjy)=(hθ(x)y)xj\begin{array}{c}{=2 * \frac{1}{2}\left(h_{\theta}(x)-y\right) * \frac{\partial}{\partial \theta_{j}}\left(h_{\theta}(x)-y\right)} \\ {=\left(h_{\theta}(x)-y\right) * \frac{\partial}{\partial \theta_{j}}\left(\sum_{i=0}^{n} \theta_{i} x_{j}-y\right)} \\ {=\left(h_{\theta}(x)-y\right) x_{j}}\end{array}

其中:hθ(x)=i=0nθixjh_{\theta}(x)=\sum_{i=0}^{n} \theta_{i} x_{j};那麼θ\theta的迭代公式就是:
θj=θj+α(yhθ(x))xj\theta_{j}=\theta_{j}+\alpha\left(y-h_{\theta}(x)\right) x_{j}

四、梯度下降

4.1 量梯度下降(Batch Gradient Descent,BGD)

批量梯度下降法,是梯度下降法最常用的形式,具體做法也就是在更新參數時使用所有的樣本來進行更新。

  • 處理一個樣本公式:θj=θj+α(yhθ(x))xj\theta_j = \theta_j + \alpha(y-h_\theta(x))x_j
  • 處理多個樣本公式:
    θjJ(θ)=i=1m(hθ(x(i))y(i))xj(i)θj=θj+αi=1m(y(i)hθ(x(i)))xj(i) \begin{array}{c}{\frac{\partial}{\partial \theta_{j}} J(\theta)=\sum_{i=1}^{m}\left(h_{\theta}\left(x^{(i)}\right)-y^{(i)}\right) x_{j}^{(i)}} \\ {\theta_{j}=\theta_{j}+\alpha \sum_{i=1}^{m}\left(y^{(i)}-h_{\theta}\left(x^{(i)}\right)\right) x_{j}^{(i)}}\end{array}

每一步都是計算全部訓練集的數據,所以稱之爲批量梯度下降。由於每次迭代的時候都要對所有的數據集計算求和,計算量就會特別的大,尤其是訓練數據集特別大的時候。此外,我們就可用隨機梯度下降。

4.2 隨機梯度下降(Stochastic Batch Gradient Descent,SGD)

      隨機梯度下降在計算下降最快的方向時,隨機選一個數據進行計算,而不是掃描全部訓練數據集,這樣就加快了迭代速度。
SGD下降並不是沿着J(θ)J(\theta)下降最快的方法收斂,而是震盪的方式趨向極小點。
借圖如下所示:

在這裏插入圖片描述
迭代公式:

Loop{

for i = 1 to m {

θj:=wj+α(yiθiTxi)xj\theta_{j} :=w_{j}+\alpha\left(y_{i}-\theta_{i}^{T} x_{i}\right) x_{j}
}
}

4.3 BGD和SGD算法比較

1、BGD速度比SGD速度慢(迭代次數多)
2、SGD在某些情況下(全局存在多個相對最優解),SGD有可能跳出某些小的局部最優解,所以不會比BGD壞。
3、BGD一定能夠得到一個局部最優解(在線性迴歸模型中一定是得到一個全局最優解),SGD由於隨機性的存在可能導致最終結果比BGD的差。

4.3 小批梯度下降(Mini Batch Gradient Descent,MBGD)

      如果既要保證算法的訓練過程比較快,又需要保證最終參數訓練的準確率,可以選擇小批梯度下降。MBGD中不是每拿一個樣本就更新一次梯度,而是拿bb個樣本(bb一般爲10)的平均梯度作爲更新方向。
迭代公式:

Repeat{

for i = 1, 11, 21,31,…m{

θj:=θj+α110ii+9(yiθiTxi)xj\theta_{j} :=\theta_{j}+\alpha \frac{1}{10} \sum_{i}^{i+9}\left(y_{i}-\theta_{i}^{T} x_{i}\right) x_{j}

}

}

到了這裏,基本算是對於BGD、SGD、MBGD有了基本的瞭解了,那接下來咋們就簡單實際操作一丟丟。

五、梯度下降的簡單實現

5.1 批量梯度下降(BGD)

"""
 author:jjk
 datetime:2019/8/19
 coding:utf-8
 project name:Pycharm_workstation
 Program function:
 
"""

import numpy as np
# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
m = len(x_train)
x0 = np.full((m,1),1)
# 構造一個每個數據第一維特徵都是1的矩陣
input_data = np.hstack([x0,x_train])
m,n = input_data.shape
theta1 = np.array([[2,3,4]]).T
# 構建標籤數據集,後面的np.random.randn是將數據加一點噪聲,儀表模擬數據集
#y_train = (input_data.dot(np.array([1, 2, 3, 4]).T)).T
y_train = x_train.dot(theta1) + np.array([[2],[2],[2],[2],[2]])

# 設置兩個終止條件
loop_max = 1000000
epsilon = 1e-5
# 初始theta
np.random.seed(0) # 設置隨機種子
theta = np.random.randn(n,1) # 隨機去一個1維列向量初始化theta

# 初始化步長/學習率
alpha = 0.00001
# 初始化誤差,每個維度的theta都應該有一個誤差,所以誤差是4維
error = np.zeros((n,1)) # 列向量

# 初始化偏導數
diff = np.zeros((input_data.shape[1],1))
# 初始化循環次數
count = 0

while count < loop_max:
    count += 1
    sum_m = np.zeros((n,1))
    for i in range(m):
        for j in range(input_data.shape[1]):
            diff[j] = (input_data[i].dot(theta)-y_train[i])*input_data[i,j]
        # 求每個維度的梯度的累加和
        sum_m = sum_m + diff
    # 利用這個累加和更新梯度
    theta = theta - alpha*sum_m
    # else中將前一個theta賦值給error,theta-error便表示前後兩個梯度的變化,當梯度變化小(在接收的範圍內)時,便停止迭代

    if np.linalg.norm(theta-error) < epsilon:
        break
    else:
        error = theta
print(theta) # 輸出梯度:真實的應該是2234

在這裏插入圖片描述
源碼獲取:此鏈接下的BGD.py文件


5.2 隨機梯度下降(SGD)

"""
 author:jjk
 datetime:2019/9/21
 coding:utf-8
 project name:Pycharm_workstation
 Program function:
 
"""

import numpy as np
# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
# 構建一個權重作爲數據集的真正的權重,theta1主要是用來構建y_train,然後通過模型計算
# 擬合的theta,這樣可以比較兩者之間的差異,驗證模型。
theta1 = np.array([[2,3,4]]).T

# 構建標籤數據集,y=t1*x1+t2*x2+t3*x3+b即y=向量x_train乘向量theta+b, 這裏b=2
y_train = (x_train.dot(theta1) + np.array([[2],[2],[2],[2],[2]])).ravel()

# 構建一個5行1列的單位矩陣x0,然它和x_train組合,形成[x0, x1, x2, x3],x0=1的數據形式,
# 這樣可以將y=t1*x1+t2*x2+t3*x3+b寫爲y=b*x0+t1*x1+t2*x2+t3*x3即y=向量x_train乘向
# 量theta其中theta應該爲[b, *, * , *],則要擬合的theta應該是[2,2,3,4],這個值可以
# 和算出來的theta相比較,看模型的是否達到預期

x0 = np.ones((5,1))
input_data = np.hstack([x0,x_train])
m,n = input_data.shape

# 設置兩個終止條件
loop_max = 10000000
epsilon = 1e-6
# 初始化theta(權重)
np.random.seed(0)
theta = np.random.rand(n).T # 隨機生成10以內的,n維1列的矩陣
# 初始化步長/學習率
alpha = 0.000001
# 初始化迭代誤差(用於計算梯度兩次迭代的差)
error = np.zeros(n)

# 初始化偏導數矩陣
diff = np.zeros(n)
# 初始化循環次數
count = 0

while count<loop_max:
    count += 1 # 每運行一次count+1,以此來總共記錄運行的次數
    # 計算梯度
    for i in  range(m):
        # 計算每個維度theta的梯度,並運算一個梯度更新它,也就是迭代啦
        diff = input_data[i].dot(theta)-y_train[i]
        theta = theta - alpha * diff*(input_data[i])
    # else中將前一個theta賦值給error,theta - error便表示前後兩個梯度的變化,當梯度
    #變化很小(在接收的範圍內)時,便停止迭代。
    if np.linalg.norm(theta-error) < epsilon:
        break
    else:
        error = theta
print(theta) # 理論上theta = [2,2,3,4]

在這裏插入圖片描述
源碼獲取:此鏈接下的SGD.py文件

5.3 小批量梯度下降(MBGD)

"""
 author:jjk
 datetime:2019/9/21
 coding:utf-8
 project name:Pycharm_workstation
 Program function:

"""

import numpy as np

# 構造訓練數據集
x_train = np.array([[2, 0., 3], [3, 1., 3], [0, 2., 3], [4, 3., 2], [1, 4., 4]])
m = len(x_train)
x0 = np.full((m, 1), 1)
# 構造一個每個數據第一維特徵都是1的矩陣
input_data = np.hstack([x0, x_train])
m, n = input_data.shape
theta1 = np.array([[2, 3, 4]]).T
# 構建標籤數據集,後面的np.random.randn是將數據加一點噪聲,儀表模擬數據集
# y_train = (input_data.dot(np.array([1, 2, 3, 4]).T)).T
y_train = x_train.dot(theta1) + np.array([[2], [2], [2], [2], [2]])

# 設置兩個終止條件
loop_max = 1000000
epsilon = 1e-5
# 初始theta
np.random.seed(0)  # 設置隨機種子
theta = np.random.randn(n, 1)  # 隨機去一個1維列向量初始化theta

# 初始化步長/學習率
alpha = 0.00001
# 初始化誤差,每個維度的theta都應該有一個誤差,所以誤差是4維
error = np.zeros((n, 1))  # 列向量

# 初始化偏導數
diff = np.zeros((input_data.shape[1], 1))
# 初始化循環次數
count = 0
# 設置小批量的樣本數
minibatch_size = 2

while count < loop_max:
    count += 1
    sum_m = np.zeros((n, 1))
    for i in range(1, m, minibatch_size):
        for j in range(i - 1, i + minibatch_size - 1, 1):
            # 計算每個維度的theta
            diff[j] = (input_data[i].dot(theta) - y_train[i]) * input_data[i, j]
        # 求每個維度的梯度的累加和
        sum_m = sum_m + diff
    # 利用這個累加和更新梯度
    theta = theta - alpha * (1.0 / minibatch_size) * sum_m
    # else中將前一個theta賦值給error,theta-error便表示前後兩個梯度的變化,當梯度變化小(在接收的範圍內)時,便停止迭代

    if np.linalg.norm(theta - error) < epsilon:
        break
    else:
        error = theta
print(theta)  # 輸出梯度:真實的應該是2234

在這裏插入圖片描述
源碼獲取:此鏈接下的MBGD.py文件

5.4 梯度下降—一元二次函數

"""
 author:jjk
 datetime:2019/9/21
 coding:utf-8
 project name:Pycharm_workstation
 Program function: 一元二次函數-梯度下降變化:0.5, 1.5,2.0,2.5-變化率
"""

import numpy as np
import matplotlib as mpl # 畫圖
import matplotlib.pyplot as plt
import math # 數學公式
from mpl_toolkits.mplot3d import Axes3D
# 解決中文顯示問題
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False

# 一維原始圖像
def f1(x):
    return  0.5 * (x-0.25) ** 2 # 1/2*(x-0.25)^2
# 導函數
def h1(x):
    return 0.5 * 2 * (x-0.25) # 原函數求導

# 使用梯度下降法求解
GD_X = []
GD_Y = []
x = 4  # 起始位置
alpha = 0.5  # 學習率

f_change = f1(x) # 調用原始函數
f_current = f_change

GD_X.append(x)
GD_Y.append(f_current)
iter_num = 0
# 變化量大於1e-10並且迭代次數小於100時執行循環體

while f_change >1e-10 and iter_num<100:
    iter_num += 1
    x = x - alpha * h1(x)
    tmp = f1(x)
    f_change = np.abs(f_current-tmp) # 變化量
    f_current = tmp # 此時的函數值
    GD_X.append(x)
    GD_Y.append(f_current)
print(u'最終結果爲:(%.5f,%.5f)' % (x,f_current))
print(u"迭代過程中x的取值,迭代次數:%d" % iter_num)
print(GD_X)

# 構建數據
X = np.arange(-4, 4.5, 0.05)  # 隨機生成-4到4.5,步長爲0.05的數
Y = np.array(list(map(lambda t: f1(t), X)))  # X對應的函數值

# 畫圖
plt.figure(facecolor='w')
plt.plot(X,Y,'r-',linewidth=2) # 函數原圖像
plt.plot(GD_X,GD_Y,'bo--',linewidth=2) # 梯度迭代圖
plt.title(u'函數$y=0.5 * (θ - 0.25)^2$; \n學習率:%.3f; 最終解:(%.3f, %.3f);迭代次數:%d' % (alpha, x, f_current, iter_num))
plt.show()

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

  • 學習率比較低的時候,不會出現“之”字型
  • 學習率過大,結果不收斂,找不到最小值
    源碼獲取:此鏈接下的GD.py文件

5.5 梯度下降—二元二次函數

"""
 author:jjk
 datetime:2019/9/21
 coding:utf-8
 project name:Pycharm_workstation
 Program function:
 
"""
import numpy as np
import  math
import  matplotlib.pyplot as plt
import  matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D

# 解決中文顯示問題
mpl.rcParams['font.sans-serif'] = [u'SimHei']
mpl.rcParams['axes.unicode_minus'] = False


def f2(x,y):
    return  0.6 * (x + y) ** 2 - x * y # 二元函數原型

# 二元函數求導
def hx2(x,y):
    return 0.6 * 2 * (x + y) - y
def hy2(x,y):
    return 0.6 * 2 * (x + y) - x

# 使用梯度下降法求解
GD_X1 = []
GD_X2 = []
GD_Y = []
x1 = 4 # 起始位置
x2 = 4
alpha = 0.5 # 學習率

f_change = f2(x1,x2)
f_current = f_change

GD_X1.append(x1)
GD_X2.append(x2)
GD_Y.append(f_current)
iter_num = 0 # 統計循環次數

while f_change > 1e-10 and iter_num <100:
    iter_num += 1
    prex1 = x1
    prex2 = x2
    x1 = x1 - alpha * hx2(prex1,prex2)
    x2 = x2 - alpha * hy2(prex1,prex2)

    tmp = f2(x1,x2)
    f_change = np.abs(f_current-tmp)
    f_current = tmp
    GD_X1.append(x1) # 迭代
    GD_X2.append(x2)
    GD_Y.append(f_current)

print(u"最終結果爲:(%.5f, %.5f, %.5f)" % (x1, x2, f_current))
print(u"迭代過程中X的取值,迭代次數:%d" % iter_num)
print(GD_X1)

# 構建數據
X1 = np.arange(-4,4.5,0.2)
X2 = np.arange(-4,4.5,0.2)
X1,X2 = np.meshgrid(X1,X2)
Y = np.array(list(map(lambda t: f2(t[0], t[1]), zip(X1.flatten(), X2.flatten()))))
Y.shape = X1.shape

# 畫圖
fig = plt.figure(facecolor='w')
ax = Axes3D(fig)
ax.plot_surface(X1,X2,Y,rstride=1,cstride=1,cmap=plt.cm.jet)
ax.plot(GD_X1,GD_X2,GD_Y,'bo--')

ax.set_title(u'函數$y=0.6 * (θ1 + θ2)^2 - θ1 * θ2$;\n學習率:%.3f; 最終解:(%.3f, %.3f, %.3f);迭代次數:%d' % (alpha, x1, x2, f_current, iter_num))
plt.show()

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

學習率較小時,迭代次數比較小
學習率增大,迭代次數增加
學習率過大,結果不收斂

源碼獲取:此鏈接下的GD_02.py文件

好嘛,到了此處關於梯度下降算法的瞭解和學習就結束了,重點是還得回顧,不然還是會忘~~~
另外,也大量參考了詞鏈接內容

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