文章和代碼都在這兒,騙 歡迎Star GitHub repo
簡介
梯度下降是機器學習中較為基本也比較常見的一類優化算法的總稱。在這里,我假設你已經知道了什么是 sigmoid 函數,掌握求導時的鏈式法則,和一些基礎的矩陣乘法。
如果你對以上三點有疑問的話,可以參考老朋友 Wikipedia 和 Google 給出的意見 。
度量錯誤
梯度下降是一種從“錯誤”中學習的算法。你也許意識到我們需要找到一種度量預測的錯誤程度的方法(metric)。通常情況下我們會選擇均方誤差(MSE),但也有一些其他的選擇,比如誤差平方和(SSR)或是平均絕對誤差(MAE)。本文中將統一使用均方誤差。

圖中的 y 以及 y_hat 的上角標 μ 指代的是訓練數據集中第 μ 個數據,而非表示冪乘。
Google 和 Wikipedia 永遠是最好的幫手,如果你想權衡使用 MSE 和 MAE 的利弊,我推薦你自己去探索一下。之前在 StackExchange 上看到過一個相關的問題,留在這里供你參考。一言以蔽之,MSE 對偏離實際值越多的預測值“懲罰”的越多(你想想,都給誤差值平方了),有利于我們的模型更好的趨近最優情況。關于 MSE 和 MAE 的介紹,也可以參考我之前的一篇文章。
批量梯度下降
這里需要插一句,梯度下降有很多不同的變種(稍后討論),我們上面中給出的均方誤差公式和接下來的算法,是對應批量梯度下降法(Batch Gradient Descent,簡稱BGD)的,這是梯度下降法最原始的形式,它的具體思路是在更新每一參數時都使用所有的樣本來進行更新。我們將繼續使用 sigmoid 函數作為激活函數。


我們先給出從 Udacity 深度學習課程中截取的算法描述,我們進行逐步分析這個算法的實現。

預設:
這一部分沒有寫在上圖的算法中,但是是不可缺少的。我們需要初始化一些參數。分別為訓練次數 e,學習率(learning rate)η 和預設權重(weight)w。關于預設權重的設定,請參考下面這段話。
First, you'll need to initialize the weights. We want these to be small such that the input to the sigmoid is in the linear region near 0 and not squashed at the high and low ends. It's also important to initialize them randomly so that they all have different starting values and diverge, breaking symmetry. So, we'll initialize the weights from a normal distribution centered at 0. A good value for the scale is 1/√
?n where n is the number of input units. This keeps the input to the sigmoid low for increasing numbers of input units.
第一步將初始化變化的權重 Δw,這一步實際上是便于我們稍后將算法轉為代碼。接下來第二步,針對訓練數據集中的每一組數據我們執行以下三個小步驟 (公式已略去,參見上圖):
- 將數據集在神經網絡中進行一次正向傳遞(可參考我的上篇博客或 Google),得到預測結果 y_hat
- 計算輸出層神經元的誤差梯度(error gradient)δ
- 更新權重變化 Δw_i
在完成了一次對整個數據集的遍歷之后,我們將進行第三步,將Δw_i (權重變化值)和 w_i (預設的權重)相加,得到新的 w_i。 這樣,我們便完成了一次對權重的更新。之后我們只需要重復 e 次第二、三步。
梯度下降的整個過程可以用下圖來進行理解。最開始我們預設的權重在最外側深紅色圓環上,經過一次一次的迭代逐漸靠近中心的最優點(optima)。

接下來我們來一起構建一個 Python 完成的梯度下降算法。完整的數據和代碼可以在我的 GitHub Repo 找到,這里就不貼出數據和準備數據的代碼了。
哦對了,你可以試著更改預設部分提到的學習率和訓練次數,看看它們會如何影響我們的訓練結果。
import numpy as np
from data_prep import features, targets, features_test, targets_test
def sigmoid(x):
"""Calculate sigmoid"""
return 1 / (1 + np.exp(-x))
np.random.seed(42)
n_records, n_features = features.shape
last_loss = None
# 預設權重
weights = np.random.normal(scale=1 / n_features ** .5, size=n_features)
# 設定循環次數和學習率
epochs = 1000
learnrate = 0.5
for e in range(epochs):
# 第一步,設定預設變化的權重為0
del_w = np.zeros(weights.shape)
# 遍歷全部數據集
for x, y in zip(features.values, targets):
# 正向傳遞計算y_hat
output = sigmoid(np.dot(weights, x))
# 計算誤差梯度
error = (y - output) * output * (1 - output)
# 更新權重變化
del_w += error * x
# 對預設權重的更新
weights += learnrate * del_w / n_records
# 打印出運算過程的一些數據
if e % (epochs / 10) == 0:
out = sigmoid(np.dot(features, weights))
loss = np.mean((out - targets) ** 2)
if last_loss and last_loss < loss:
print("Train loss: ", loss, " WARNING - Loss Increasing")
else:
print("Train loss: ", loss)
last_loss = loss
# 驗證我們的算法,在測試數據上進行測試
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
如果你對第三步中更新預設權重的部分有疑惑,這里有一行代碼需要你額外注意,
# 對預設權重的更新
weights += learnrate * del_w / n_records
由于我們是將所有訓練數據都遍歷了一遍之后得到的變化權重 del_w
, 所以需要將它除以訓練數據集的數量。這也是和我們即將提到的另一種算法有差異的部分。
其他梯度下降方法
可以從參考資料的第一篇文章中得知,除了批量梯度下降法(Batch Gradient Descent,簡稱BGD)外,還有隨機梯度下降法(Stochastic Gradient Descent,簡稱SGD)以及更進一步的小批量梯度下降法(Mini-batch Gradient Descent,簡稱MBGD)。這幾種不同方法的優劣對比可以單開一篇文章來探討(或者參見參考資料第一篇),這里只通過介紹性的知識進行簡單總結。
隨機梯度下降法和批量梯度下降法不同的是,在后者的訓練過程中,每一次的訓練都需要遍歷全部的訓練數據集,這種算法的確可以保證達到全局最優解(global optimal)。然而,如果我們的數據量較大,或者訓練數據的維度較高(特征數量多)的時候,巨大的計算量會極大的拖慢我們模型的訓練速度。所以這里提出一種改進的算法——隨機梯度下降法。唯一一點和批量梯度下降法不同的是,我們每次選取一個訓練數據,計算誤差梯度后,直接在預設權重上進行更新。這樣就避免了遍歷全部數據后再求平均變化權重的計算過程。極大的減少了計算量,對訓練速度有著明顯的提高。美中不足的是,這種算法只能達到一個和全局最優解極為接近的數值,而且不利于并行實現。
下面給出隨機梯度下降法的實現
import numpy as np
from data_prep import features, targets, features_test, targets_test
def sigmoid(x):
"""Calculate sigmoid"""
return 1 / (1 + np.exp(-x))
np.random.seed(42)
n_records, n_features = features.shape
last_loss = None
# 預設權重
weights = np.random.normal(scale=1 / n_features ** .5, size=n_features)
# 設定學習率
learnrate = 0.5
# 遍歷全部數據集
for x, y in zip(features.values, targets):
# 正向傳遞計算y_hat
output = sigmoid(np.dot(weights, x))
# 計算誤差梯度
error = (y - output) * output * (1 - output)
# 更新預設權重
weights += error * x
# 驗證我們的算法,在測試數據上進行測試
tes_out = sigmoid(np.dot(features_test, weights))
predictions = tes_out > 0.5
accuracy = np.mean(predictions == targets_test)
print("Prediction accuracy: {:.3f}".format(accuracy))
可以看到,除了少去了整體循環的過程和更新權重的部分有變化,其余的地方并沒有太多改動過。
小批量梯度下降法(MBGD),是一種結合了以上兩種梯度下降法的新想法。其思路非常簡單,在 BGD 方法中,每次循環都將遍歷整個數據集,而在 SGD 方法中,沒有額外循環,只遍歷每個數據一次即可。MBGD 則保留了 BGD 中循環的思路,但每次循環中并不會遍歷全部數據,而是有選擇的隨機選取少量數據。具體的思路可以參考 Google。
BGD | SGD | MBGD | |
---|---|---|---|
全局最優 | 是 | 近似 | 比SGD更接近最優 |
訓練速度 | 很慢 | 很快 | 比SGD稍慢 |
這塊有一點我記得不是非常清楚了,希望有明白的朋友指點一下。忘記在哪本書里看到過SGD也可以達到全局最優(也可能我記錯了)。
參考資料
- All formulas are generated by HostMath
- Some figures are taken from the Udacity deep learning course
- 梯度下降法的三種形式BGD、SGD以及MBGD
- The Gradient Descent - Wikipedia
- Neural Networks Tutorial Slides by Andrew Moore