通俗易懂講解梯度下降法


https://zhuanlan.zhihu.com/p/335191534

前言:入門機器學習必須了解梯度下降法,雖然梯度下降法不直接在機器學習里面使用,但是了解梯度下降法的思維是后續學習其他算法的基礎。網上已經有很多篇文章介紹梯度下降法。但大部分文章要么整一堆數學公式,要么就是簡單說一下沿梯度的反方向下降最快就草草了事。本篇文章淺顯易懂講解,擁有高中數學知識即可看懂。

1.引入

我們先從一個案例入手,下圖是一組上海市靜安區的房價信息

(別看了,數據我瞎編的,上海市靜安區的房價不可能這么便宜)

我們用Python在坐標系上面畫出來如下圖:

我們現在想擬合一個線性函數來表示房屋面積和房價的關系。我們初中都學過的一元一次函數表達式為:y=kx+b(k≠0)。很明顯不可能有一對組合(k,b)全部經過上圖7個點,我們只能盡可能地找到一對組合,使得該線性函數離上圖7個點的總距離最近。

如上圖所示,實際值與預測值之間差異的均方差我們把它稱為損失函數,也有叫做成本函數或者代價函數的,意義都一樣。我們希望找到一個組合(k,b)可以使得損失函數的值最小。上述只有一個輸入變量x,如果我們多加入幾個輸入變量,比如卧室的數量、離最近地鐵站的距離。最終目標變量和損失函數我們用下述函數表達式來表達:

現在我們的任務就是求出一組θ,在已知【x,y】的前提下使得損失函數的值最小。那么如何計算出θ了,使用什么方法了?

我們首先回到損失函數表達式本身,損失函數本身是一個y=x^2的形式,高中數學大家應該都學過這是一個開口向上的拋物線方程,大概長下圖這樣:

我們如何找到這個函數的最低點?上圖是一個二維圖,我們很輕松就可以肉眼看出x=0時,y最小。如果維度更多,比如z = (x-10)^2 + (y-10)^2,則得到下圖:

我們如何定位出最小值,特別強調一點,這里的x是一個“大”參數的概念,x應該等於下述公式

大家要明確上圖橫坐標是x和y,函數表達式里的θ已經知道了,所以我們是找到最合適的(x,y)使得函數值最小。如果我們現在是已知樣本(x,y),那么上圖的變量就變為了θ_0和θ_i,並不是x_i,我們是以θ_0和θ_i作為輸入變量做的圖,x_i和y_i都是已知的固定值,這一點必須明確了。上圖的縱坐標的值就變為損失函數的值。

我們的問題是已知樣本的坐標(x,y),來求解一組θ參數,使得損失函數的值最小。我們如何找到上圖中的最低點?因為找到最低點,那么最低點對應的橫坐標所有維度就是我們想得到的θ_0和θ_i,而縱坐標就是損失函數的最小值。找到最低點所有答案就全部解出來了。

現在問題來了?有沒有一種算法讓我們可以慢慢定位出最小值,這個算法就是梯度下降法。

2.梯度下降法簡介

2.1 梯度下降法的思想

我們首先介紹梯度下降法的整體思想。假設你現在站在某個山峰的峰頂,你要在天黑前到達山峰的最低點,那里有食品水源供給站,可以進行能量補充。你不需要考慮下山的安全性,即使選擇最陡峭的懸崖下山,你也可以全身而退,那么如何下山最快了?

最快的方法就是以當前的位置為基准,尋找該位置最陡峭的地方,然后沿該方向往下走。走一段距離后,再以當前位置為基准,重新尋找最陡峭的地方,一直重復最終我們就可以到達最低點。我們需要不停地去重新定位最陡峭的地方,這樣才不會限於局部最優。

那么整個下山過程中我們會面臨兩個問題:

  • 如何測量山峰的“陡峭”程度;
  • 每一次走多長距離后重新進行陡峭程度測量;走太長,那么整體的測量次數就會比較少,可能會導致走的並不是最佳路線,錯過了最低點。走太短,測量次數過於頻繁,整體耗時太長,還沒有到達食品供給站就已經GG了。這里的步長如何設置?
三種不同步長可能導致的后果

Part1里面介紹了如何從一個開口向上的拋物線高點定位到最低點的問題和下山的場景是完全類似的,拋物線就相當於一個山峰,我們的目標就是找到拋物線的最低點,也就是山底。最快的下山方式就是找到當前位置最陡峭的方向,然后沿着此方向向下走,對應到拋物線中,就是計算給定點的梯度,然后朝着梯度相反的方向( Part 2.3里面會解釋為什么是朝着梯度相反的方向),就能讓拋物線值下降的最快。同時我們也要和下山一樣,不停地定位新位置,再計算新位置的梯度,然后按照新方向下降,最后慢慢定位到拋物線的最低點。

2.2 梯度下降法算法

Part2.1里面已經介紹了梯度下降法的思想,遺留了兩個問題。第一就是如何計算“陡峭”程度,我們這里把它叫做梯度,我們用∇J_θ來代替。第二個也就是步長問題,我們用一個α學習率來代表這個步長,α越大代表步長越大。知道了這兩個值,我們如何去得到θ參數的更新表達式了?

J是關於θ的一個函數,假設初始時我們在θ_1這個位置,要從這個點走到J的最小值點,也就是山底。首先我們先確定前進的方向,也就是梯度的反向“-∇J_θ”,然后走一段距離的步長,也就是α,走完這個段步長,就到達了θ_2這個點了。表達式如下圖:

我們按照上述表達式一直不停地更新θ的值,一直到θ收斂不變為止,當我們到達山底,此時函數的梯度就是0了,θ值也就不會再更新了,因為表達式的后半部分一直是0了。整個下降過程中損失函數的值是一定在減少,但是我們想學習出來的參數值θ不一定一直在減小。因為我們需要找到損失函數最小時的坐標點,這個坐標點的坐標不一定是原點,很可能是(2,3)甚至是(4,6),我們找到的是最合適的θ值使得損失函數最小。下圖我們用一個例子來進行說明:

上圖的最低點很明顯就是原點,我們通過梯度下降法來逼近這個最低點。我們可以看到損失函數的值在一直減少,θ的值也在往0這個值進行收斂。

2.3 梯度下降法數學計算

Part2.1和2.2介紹了梯度下降的思想和θ更新的表達式,現在我們從數學層面進行解釋:

  • 為什么是向梯度相反的方向下降:

上圖應該很形象地顯示為什么要朝着梯度的反方向了。梯度是一個向量,梯度的方向是函數在指定點上升最快的方向,那么梯度的反方向自然是下降最快的方向了。

  • 泛化的θ參數更新公式:

Part2.2里面的例子我們選擇的是一個最簡單的函數表達式,θ參數分為兩種,一種是和輸入變量x配對的參數θ_i,一種是固定的偏差θ_0。我們用已知的樣本數據(x,y)來求解出使得損失函數最小的一組θ參數。下面我們來計算一個通用泛化的θ參數更新表達式。我們只需要用到高中數學中的導數知識即可,朋友們相信我真的很easy。

下圖是對和輸入變量x配對的參數θ_i更新表達式:

下圖是對固定的偏差θ_0的更新表達式:

上面的數學過程也就是高中我們學習導數里面最簡單的求導過程了。那么至此我們也就將梯度下降算法的思想和數學解釋全部介紹完了。

2.4 梯度下降法分類

Part2.3里面的公式大家也看到了我們要借助樣本的(x,y)的數據來進行參數θ的更新,如果現在樣本有100條數據,我們如何來更新。正常情況下,我們更新的方式有兩種:

  • 隨機梯度下降(Stochastic Gradient Descent)

我們每次只使用單個訓練樣本來更新θ參數,依次遍歷訓練集,而不是一次更新中考慮所有的樣本。就像開頭介紹那7條房價數據,我們一個一個來計算,計算一次更新一次θ,直到θ收斂或者達到后期更新幅度已經小於我們設置的閥值。

  • 批量梯度下降(Batch Gradient Descent)

我們每次更新都遍歷訓練集中所有的樣本,以它們的預測誤差之和為依據更新。我們會一次性將7條樣本數據的預測誤差都匯總,然后進行一次更新。更新完以后,繼續以7條樣本數據的預測誤差之和進行匯總,再更新,直到θ收斂或者達到后期更新幅度已經小於我們設置的閥值。

當訓練樣本數很大時,批量梯度下降的每次更新都會是計算量很大的操作,而隨機梯度下降可以利用單個訓練樣本立即更新,因此隨機梯度下降 通常是一個更快的方法。但隨機梯度下降也有一個缺點,那就是θ可能不會收斂,而是在最小值附近振盪,但在實際中也都會得到一個足夠好的近似。所以實際情況下,我們一般不用固定的學習率,而是讓它隨着算法的運行逐漸減小到零,也就是在接近“山底”的時候慢慢減小下降的“步幅”,換成用“小碎步”走,這樣它就更容易收斂於全局最小值而不是圍繞它振盪了。

3. 梯度下降法Python實踐

以下代碼全部使用Python3環境

3.1 單變量:y = x^2求最低點

import matplotlib.pyplot as plt
import numpy as np
# fx的函數值
def fx(x):
    return x**2

#定義梯度下降算法
def gradient_descent():
    times = 10 # 迭代次數
    alpha = 0.1 # 學習率
    x =10# 設定x的初始值
    x_axis = np.linspace(-10, 10) #設定x軸的坐標系
    fig = plt.figure(1,figsize=(5,5)) #設定畫布大小
    ax = fig.add_subplot(1,1,1) #設定畫布內只有一個圖
    ax.set_xlabel('X', fontsize=14)
    ax.set_ylabel('Y', fontsize=14)
    ax.plot(x_axis,fx(x_axis)) #作圖
    
    for i in range(times):
        x1 = x          
        y1= fx(x)  
        print("第%d次迭代:x=%f,y=%f" % (i + 1, x, y1))
        x = x - alpha * 2 * x
        y = fx(x)
        ax.plot([x1,x], [y1,y], 'ko', lw=1, ls='-', color='coral')
    plt.show()

if __name__ == "__main__":
    gradient_descent()
2D梯度下降圖示

3.2 多變量:z = (x-10)^2 + (y-10)^2求最低點

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
#求fx的函數值
def fx(x, y):
    return (x - 10) ** 2 + (y - 10) ** 2

def gradient_descent():
    times = 100 # 迭代次數
    alpha = 0.05 # 學習率
    x = 20 # x的初始值
    y = 20 # y的初始值

    fig = Axes3D(plt.figure()) # 將畫布設置為3D
    axis_x = np.linspace(0, 20, 100)#設置X軸取值范圍
    axis_y = np.linspace(0, 20, 100)#設置Y軸取值范圍
    axis_x, axis_y = np.meshgrid(axis_x, axis_y) #將數據轉化為網格數據
    z = fx(axis_x,axis_y)#計算Z軸數值
    fig.set_xlabel('X', fontsize=14)
    fig.set_ylabel('Y', fontsize=14)
    fig.set_zlabel('Z', fontsize=14)
    fig.view_init(elev=60,azim=300)#設置3D圖的俯視角度,方便查看梯度下降曲線
    fig.plot_surface(axis_x, axis_y, z, rstride=1, cstride=1, cmap=plt.get_cmap('rainbow')) #作出底圖
    
    for i in range(times):
        x1 = x        
        y1 = y         
        f1 = fx(x, y)  
        print("第%d次迭代:x=%f,y=%f,fxy=%f" % (i + 1, x, y, f1))
        x = x - alpha * 2 * (x - 10)
        y = y - alpha * 2 * (y - 10)
        f = fx(x, y)
        fig.plot([x1, x], [y1, y], [f1, f], 'ko', lw=2, ls='-')
    plt.show()

if __name__ == "__main__":
    gradient_descent()

3.3 根據給定樣本求解出最佳θ組合

import numpy as np
import matplotlib.pyplot as plt   
#樣本數據
x = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
y = [3,4,5,5,2,4,7,8,11,8,10,11,13,13,16,17,16,17,18,20]
m = 20 #樣本數量
alpha = 0.01#學習率
θ_0 = 1 #初始化θ_0的值
θ_1 = 1 #初始化θ_1的值

#預測目標變量y的值
def predict(θ_0,θ_1, x):
    y_predicted = θ_0 + θ_1*x
    return y_predicted

#遍歷整個樣本數據,計算偏差,使用批量梯度下降法
def loop(m,θ_0,θ_1,x,y):
    sum1 = 0
    sum2 = 0
    error = 0
    for i in range(m):
        a = predict(θ_0,θ_1, x[i]) - y[i]
        b = (predict(θ_0,θ_1, x[i]) - y[i])* x[i]
        error1 = a*a
        sum1 = sum1 + a
        sum2 = sum2 + b
        error = error + error1
    return sum1,sum2,error

#批量梯度下降法進行更新θ的值
def batch_gradient_descent(x, y,θ_0,θ_1, alpha,m):
    gradient_1 = (loop(m,θ_0,θ_1,x,y)[1]/m)
    while abs(gradient_1) > 0.001:#設定一個閥值,當梯度的絕對值小於0.001時即不再更新了
        gradient_0 = (loop(m,θ_0,θ_1,x,y)[0]/m)
        gradient_1 = (loop(m,θ_0,θ_1,x,y)[1]/m)
        error = (loop(m,θ_0,θ_1,x,y)[2]/m)
        θ_0 = θ_0 - alpha*gradient_0
        θ_1 = θ_1 - alpha*gradient_1
    return(θ_0,θ_1,error)

θ_0 = batch_gradient_descent(x, y,θ_0,θ_1, alpha,m)[0]
θ_1 = batch_gradient_descent(x, y,θ_0,θ_1, alpha,m)[1]
error = batch_gradient_descent(x, y,θ_0,θ_1, alpha,m)[2]
print ("The θ_0 is %f, The θ_1 is %f, The The Mean Squared Error is %f " %(θ_0,θ_1,error))

plt.figure(figsize=(6, 4))# 新建一個畫布
plt.scatter(x, y, label='y')# 繪制樣本散點圖
plt.xlim(0, 21)# x軸范圍
plt.ylim(0, 22)# y軸范圍
plt.xlabel('x', fontsize=20)# x軸標簽
plt.ylabel('y', fontsize=20)# y軸標簽

x = np.array(x)
y_predict = np.array(θ_0 + θ_1*x)
plt.plot(x,y_predict,color = 'red')#繪制擬合的函數圖
plt.show()

本篇文章前半部分通俗易懂地將整個梯度下降算法全面地講解了一遍,后半部分通過Python將整個算法實現了一遍,大家可自行下載運行,歡迎大家溝通交流~

參考資料:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM