Python 上的單神經元神經網絡——具有數學直覺

已發表: 2021-06-21

讓我們構建一個簡單的網絡——非常簡單,但是一個完整的網絡——只有一層。 只有一個輸入——一個神經元(也是輸出),一個權重,一個偏差。

我們先運行代碼,然後逐個分析

克隆 Github 項目,或者在您喜歡的 IDE 中簡單地運行以下代碼。

如果您在設置 IDE 時需要幫助,我在此處描述了該過程。

如果一切順利,您將獲得以下輸出:

華氏和攝氏圖

問題——攝氏華氏度

我們將訓練我們的機器從攝氏溫度預測華氏溫度。 正如您從代碼(或圖表)中可以理解的那樣,藍線是實際的攝氏-華氏關係。 紅線是我們的嬰兒機器在沒有任何訓練的情況下預測的關係。 最後我們訓練機器,綠線是訓練後的預測。

查看第 65–67 行——在訓練之前和之後,它使用相同的函數( get_predicted_fahrenheit_values() )進行預測。 那麼神奇的 train() 在做什麼呢? 讓我們來了解一下。

網絡結構

網絡結構

輸入:代表攝氏度的數字

重量:代表重量的浮點數

偏差:代表偏差的浮點數

輸出:代表預測華氏溫度的浮點數

所以,我們總共有 2 個參數——1 個權重和 1 個偏差

代碼分析

代碼分析

在第 9 行中,我們生成了一個包含 -50 和 +50 之間的 100 個數字的數組(不包括 50 — range 函數不包括上限值)。

在第 11-14 行中,我們為每個攝氏度值生成華氏度。

在第 16 行和第 17 行,我們正在初始化權重和偏差。

火車()

火車()

我們在這裡運行 10000 次訓練迭代。 每次迭代由以下部分組成:

  1. 向前(第 57 行)傳球
  2. 向後(第 58 行)傳球
  3. 更新參數(第 59 行)

如果你是 python 新手,你可能會覺得有點奇怪——python 函數可以返回多個值作為tuple

請注意, update_parameters是我們唯一感興趣的東西。我們在這裡所做的其他一切都是評估這個函數的參數,它們是我們的權重和偏差的梯度(我們將在下面解釋梯度是什麼)。

  1. grad_weight:表示權重梯度的浮點數
  2. grad_bias:表示偏差梯度的浮點數

我們通過向後調用獲得這些值,但它需要輸出,我們通過在第 57 行調用向前獲得。

向前()

向前()

請注意,這裡celsius_valuesfahrenheit_values是 100 行的數組:

celsius_values 和 fahrenheit_values

執行第 20–23 行後,對於攝氏值,例如 42

輸出 = 42 * 權重 + 偏差

因此,對於celsius_values中的 100 個元素,輸出將是每個對應的 celsius 值的 100 個元素的數組。

第 25-30 行正在使用均方誤差 (MSE) 損失函數計算損失,它只是所有差異平方除以樣本數(在本例中為 100)的一個花哨名稱。

小損失意味著更好的預測。 如果您在每次迭代中保持打印損失,您將看到它隨著訓練的進行而減少。

最後,在第 31 行,我們返回預測的輸出和損失。

落後

落後

我們只對更新我們的權重和偏差感興趣。 要更新這些值,我們必須知道它們的梯度,這就是我們在這裡計算的。

注意梯度是按相反的順序計算的。 首先計算輸出的梯度,然後計算權重和偏差,因此得名“反向傳播”。 原因是,為了計算權重和偏差的梯度,我們需要知道輸出的梯度——這樣我們就可以在鍊式法則公式中使用它。

現在讓我們看看什麼是梯度和鍊式法則。

坡度

為簡單起見,假設我們只有一個值celsius_valuesfahrenheit_values ,分別為42107.6

現在,第 30 行中的計算分解變為:

損失 = (107.6 — (42 * 權重 + 偏差))² / 1

如您所見,損失取決於兩個參數——權重和偏差。 考慮重量。 想像一下,我們用一個隨機值初始化它,比如 0.8,在評估上面的等式後,我們得到 123.45 作為loss的值。 根據這個損失值,您必須決定如何更新體重。 你應該把它設為 0.9 還是 0.7?

您必須以某種方式更新權重,以便在下一次迭代中獲得較低的損失值(請記住,最小化損失是最終目標)。 所以,如果增加體重增加了損失,我們就會減少它。 如果增加體重會減少損失,我們會增加它。

現在的問題是,我們如何知道增加權重會增加還是減少損失。 這就是漸變的用武之地。 從廣義上講,梯度是由導數定義的。 請記住,從您的高中微積分中,∂y/∂x(它是 y 相對於 x 的偏導數/梯度)表示 y 將如何隨著 x 的微小變化而變化。

如果 ∂y/∂x 為正數,則意味著 x 的小幅增量將增加 y。

如果 ∂y/∂x 為負數,則意味著 x 的小幅增量會減小 y。

如果 ∂y/∂x 很大,那麼 x 的微小變化就會導致 y 的巨大變化。

如果 ∂y/∂x 很小,x 的微小變化會導致 y 的微小變化。

因此,從梯度中,我們得到 2 個信息。 參數必須更新的方向(增加或減少)以及多少(大或小)。

鍊式法則

通俗地說,鍊式法則說:

連鎖規則 01

考慮上面重量的例子。 我們需要計算grad_weight來更新這個權重,計算公式為:

連鎖規則 02

使用鍊式法則公式,我們可以推導出它:

連鎖規則 03

同樣,偏差梯度:

連鎖規則 04

讓我們畫一個依賴關係圖。

依賴關係圖

查看所有計算取決於輸出的梯度(∂損失/∂輸出) 。 這就是為什麼我們首先在回傳中計算它(第 34-36 行)。

事實上,在高級 ML 框架中,例如在 PyTorch 中,您不必為回傳編寫代碼! 在前向傳播過程中,它創建計算圖,在反向傳播過程中,它通過圖中的相反方向並使用鍊式法則計算梯度。

∂損失/∂輸出

我們在代碼中通過grad_output定義這個變量,我們在第 34-36 行計算。 讓我們找出我們在代碼中使用的公式背後的原因。

請記住,我們將機器中的所有 100 個celsius_values一起輸入。 因此, grad_output 將是一個包含 100 個元素的數組,每個元素包含celsius_values中相應元素的輸出梯度。 為簡單起見,讓我們考慮一下, celsius_values中只有 2 個項目。

所以,分解第 30 行,

損失

在哪裡,

output_1 = 第一個攝氏度值的輸出值

output_2 = 第二攝氏度值的輸出值

fahreinheit_values_1 = 1 攝氏度值的實際華氏溫度值

fahreinheit_values_1 = 第二攝氏度值的實際華氏度值

現在,結果變量 grad_output 將包含 2 個值 - output_1 和 output_2 的梯度,這意味著:

畢業輸出

讓我們只計算 output_1 的梯度,然後我們可以對其他的應用相同的規則。

微積分時間!

微積分時間!

這與第 34-36 行相同。

權重梯度

想像一下,我們在 celsius_values 中只有一個元素。 現在:

攝氏度值

這與第 38-40 行相同。 對於 100 celsius_values,將匯總每個值的梯度值。 一個明顯的問題是我們為什麼不按比例縮小結果(即除以 SAMPLE_SIZE)。 由於我們在更新參數之前將所有梯度乘以一個小因子,因此沒有必要(參見最後一節更新參數)。

偏置梯度

偏置梯度

這與第 42 行相同。 與權重梯度一樣,100 個輸入中的每一個的這些值都被求和。 同樣,這很好,因為在更新參數之前梯度乘以一個小因子。

更新參數

更新參數

最後,我們正在更新參數。 請注意,梯度在減去之前乘以一個小因子(LEARNING_RATE),以使訓練穩定。 LEARNING_RATE 的大值會導致過衝問題,而極小的值會使訓練變慢,這可能需要更多的迭代。 我們應該通過反複試驗找到它的最佳值。 上面有很多在線資源,包括這個,以了解更多關於學習率的信息。

請注意,我們調整的確切數量並不是非常關鍵。 例如,如果您稍微調整 LEARNING_RATE, descent_grad_weightdescent_grad_bias變量(第 49-50 行)將被更改,但機器可能仍然工作。 重要的是確保這些數量是通過使用相同因子(在本例中為 LEARNING_RATE)按比例縮小梯度得出的。 換句話說,“保持梯度下降成比例”比“下降多少”更重要。

另請注意,這些梯度值實際上是為 100 個輸入中的每一個評估的梯度的總和。 但由於這些是用相同的值縮放的,所以如上所述。

為了更新參數,我們必須用 global 關鍵字聲明它們(在第 47 行)。

從這往哪兒走

通過以pythonic方式用列表理解替換for循環,代碼會小得多。 現在看一下 - 不會花費超過幾分鐘的時間來理解。

如果您到目前為止了解了所有內容,那麼現在可能是了解具有多個神經元/層的簡單網絡內部結構的好時機——這是一篇文章。