入門神經網絡:梯度下降

上一篇傳送門

文章和代碼都在這兒, 歡迎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 函數作為激活函數。

Sigmoid 函數
Sigmoid 函數
Sigmoid 函數的導數
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也可以達到全局最優(也可能我記錯了)。

參考資料

獲取授權

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容