本文將從一個下山的場景開始,先提出梯度下降算法的基本思想,進而從數學上解釋梯度下降算法的原理,最后實現一個簡單的梯度下降算法的實例!
梯度下降的場景假設
梯度下降法的基本思想可以類比是一個下山的過程。可以假設一個場景:一個人上山旅游,天黑了,需要下山(到達山谷),這時候他看不清路,為了最快的下山,他可以找到所在位置最陡峭的地方,沿着高度下降的位置下山。

梯度下降
我們有一個可微分函數,這個函數就像是這座大山,我們的目標就是找到這個函數的最小值,也就是下山。最快的下山方式就是找到這個山最陡峭的地方,然后下去,對應到函數里就是找到定點的梯度,然后朝着梯度相反的方向,就能讓函數值下降的最快。
微分
看待微分的意義有兩種不同的意義:
函數圖像中,某點切線的斜率。
函數的變化率。
幾個微分的例子:
上面的例子都是單變量的微分,下面舉幾個多變量的微分。
梯度
梯度是微積分中一個很重要的概念,之前提到過梯度的意義
- 在單變量的函數中,梯度其實就是函數的微分,代表着函數在某個給定點的切線的斜率
- 在多變量函數中,梯度是一個向量,向量有方向,梯度的方向就指出了函數在給定點的上升最快的方向
梯度下降算法的數學解釋
上面我們花了大量的篇幅介紹梯度下降算法的基本思想和場景假設,以及梯度的概念和思想。下面我們就開始從數學上解釋梯度下降算法的計算過程和思想!
梯度下降算法的實例
下面我們將用python實現一個簡單的梯度下降算法。場景是一個簡單的線性回歸的例子:假設現在我們有一系列的點,如下圖所示
首先,我們需要定義一個代價函數,在此我們選用均方誤差代價函數

此公示中
- m是數據集中點的個數
- ½是一個常量,這樣是為了在求梯度的時候,二次方乘下來就和這里的½抵消了,自然就沒有多余的常數系數,方便后續的計算,同時對結果不會有影響
- y 是數據集中每個點的真實y坐標的值
-
h 是我們的預測函數,根據每一個輸入x,根據Θ 計算得到預測的y值,即
我們可以根據代價函數看到,代價函數中的變量有兩個,所以是一個多變量的梯度下降問題,求解出代價函數的梯度,也就是分別對兩個變量進行微分

明確了代價函數和梯度,以及預測的函數形式。我們就可以開始編寫代碼了。但在這之前,需要說明一點,就是為了方便代碼的編寫,我們會將所有的公式都轉換為矩陣的形式,python中計算矩陣是非常方便的,同時代碼也會變得非常的簡潔。
為了轉換為矩陣的計算,我們觀察到預測函數的形式

我們有兩個變量,為了對這個公式進行矩陣化,我們可以給每一個點x增加一維,這一維的值固定為1,這一維將會乘到Θ0上。這樣就方便我們統一矩陣化的計算

然后我們將代價函數和梯度轉化為矩陣向量相乘的形式

coding time
首先,我們需要定義數據集和學習率
import numpy as np # Size of the points dataset. m = 20 # Points x-coordinate and dummy value (x0, x1). X0 = np.ones((m, 1)) X1 = np.arange(1, m+1).reshape(m, 1) X = np.hstack((X0, X1)) # Points y-coordinate 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) # The Learning Rate alpha. alpha = 0.01
接下來我們以矩陣向量的形式定義代價函數和代價函數的梯度
def error_function(theta, X, y): '''Error function J definition.''' diff = np.dot(X, theta) - y return (1./2*m) * np.dot(np.transpose(diff), diff) def gradient_function(theta, X, y): '''Gradient of the function J definition.''' diff = np.dot(X, theta) - y return (1./m) * np.dot(np.transpose(X), diff)
最后就是算法的核心部分,梯度下降迭代計算
def gradient_descent(X, y, alpha): '''Perform gradient descent.''' theta = np.array([1, 1]).reshape(2, 1) gradient = gradient_function(theta, X, y) while not np.all(np.absolute(gradient) <= 1e-5): theta = theta - alpha * gradient gradient = gradient_function(theta, X, y) return theta
當梯度小於1e-5時,說明已經進入了比較平滑的狀態,類似於山谷的狀態,這時候再繼續迭代效果也不大了,所以這個時候可以退出循環!
完整的代碼如下 import numpy as np # Size of the points dataset. m = 20 # Points x-coordinate and dummy value (x0, x1). X0 = np.ones((m, 1)) X1 = np.arange(1, m+1).reshape(m, 1) X = np.hstack((X0, X1)) # Points y-coordinate 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) # The Learning Rate alpha. alpha = 0.01 def error_function(theta, X, y): '''Error function J definition.''' diff = np.dot(X, theta) - y return (1./2*m) * np.dot(np.transpose(diff), diff) def gradient_function(theta, X, y): '''Gradient of the function J definition.''' diff = np.dot(X, theta) - y return (1./m) * np.dot(np.transpose(X), diff) def gradient_descent(X, y, alpha): '''Perform gradient descent.''' theta = np.array([1, 1]).reshape(2, 1) gradient = gradient_function(theta, X, y) while not np.all(np.absolute(gradient) <= 1e-5): theta = theta - alpha * gradient 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])
運行代碼,計算得到的結果如下

所擬合出的直線如下

小結
至此,我們就基本介紹完了梯度下降法的基本思想和算法流程,並且用python實現了一個簡單的梯度下降算法擬合直線的案例!
最后,我們回到文章開頭所提出的場景假設:
這個下山的人實際上就代表了反向傳播算法,下山的路徑其實就代表着算法中一直在尋找的參數Θ,山上當前點的最陡峭的方向實際上就是代價函數在這一點的梯度方向,場景中觀測最陡峭方向所用的工具就是微分 。在下一次觀測之前的時間就是有我們算法中的學習率α所定義的。
可以看到場景假設和梯度下降算法很好的完成了對應!