[譯] 如何用 Python 從零開始構建你自己的神經網路
一個幫助初學者理解深度神經網路內部工作機制的指南
寫作動機:為了使我自己可以更好地理解深度學習,我決定在沒有像 TensorFlow 這樣的深度學習庫的情況下,從零開始構建一個神經網路。我相信,理解神經網路的內部工作原理對任何有追求的資料科學家來說都很重要。
這篇文章包含了我所學到的東西,希望對你們也有用。
什麼是神經網路?
大多數介紹神經網路的文章在描述它們時都會與大腦做類比。在不深入研究與大腦類似之處的情況下,我發現將神經網路簡單地描述為給定輸入對映到期望輸出的數學函式更容易理解一些。
神經網路由以下幾個部分組成:
- 一個 輸入層 , x
- 任意數量的 隱含層
- 一個 輸出層 , ŷ
- 層與層之間的一組 權重 和 偏差 , W 和 b
- 每個隱含層中所包含的一個可選的 啟用函式 , σ 。在本教程中,我們將使用 Sigmoid 啟用函式。
下圖展示了 2 層神經網路的架構( 注:在計算神經網路中的層數時,輸入層通常被排除在外 )

2 層神經網路的架構
在 Python 中建立一個神經網路的類很簡單。
class NeuralNetwork: def __init__(self, x, y): self.input= x self.weights1= np.random.rand(self.input.shape[1],4) self.weights2= np.random.rand(4,1) self.y= y self.output= np.zeros(y.shape) 複製程式碼
訓練神經網路
一個簡單的 2 層神經網路的輸出 ŷ 如下:

你可能注意到了,在上面的等式中,只有權重 W 和偏差 b 這兩個變數會對輸出 ŷ 產生影響。
當然,合理的權重和偏差會決定預測的準確程度。將針對輸入資料的權重和偏差進行微調的過程就是 訓練神經網路 的過程。
訓練過程的每次迭代包括以下步驟:
- 計算預測輸出的值 ŷ ,即 前饋
- 更新權重和偏差,即 反向傳播
下面的序列圖展示了這個過程。

前饋過程
正如我們在上面的序列圖中看到的,前饋只是一個簡單的計算過程,對於一個基本的 2 層神經網路,它的輸出是:

讓我們在 Python 程式碼中新增一個前饋函式來實現這一點。注意,為了簡單起見,我們假設偏差為 0。
class NeuralNetwork: def __init__(self, x, y): self.input= x self.weights1= np.random.rand(self.input.shape[1],4) self.weights2= np.random.rand(4,1) self.y= y self.output= np.zeros(self.y.shape) def feedforward(self): self.layer1 = sigmoid(np.dot(self.input, self.weights1)) self.output = sigmoid(np.dot(self.layer1, self.weights2)) 複製程式碼
但是,我們仍然需要一種方法來評估預測的“精準程度”(即我們的預測有多好)?而 損失函式 能讓我們做到這一點。
損失函式
可用的損失函式有很多,而我們對損失函式的選擇應該由問題本身的性質決定。在本教程中,我們將使用簡單的 平方和誤差 作為我們的損失函式。

這就是說,平方和誤差只是每個預測值與實際值之差的總和。我們將差值平方後再計算,以便我們評估誤差的絕對值。
訓練的目標是找到能使損失函式最小化的一組最優的權值和偏差。
反向傳播過程
現在我們已經得出了預測的誤差(損失),我們還需要找到一種方法將誤差 傳播 回來,並更新我們的權重和偏差。
為了得出調整權重和偏差的合適的量,我們需要計算 損失函式對於權重和偏差的導數 。
回憶一下微積分的知識,計算函式的導數就是計算函式的斜率。

梯度下降演算法
如果我們已經算出了導數,我們就可以簡單地通過增大/減小導數來更新權重和偏差(參見上圖)。這就是所謂的 梯度下降 。
然而,我們無法直接計算損失函式對於權重和偏差的導數,因為損失函式的等式中不包含權重和偏差。 因此,我們需要 鏈式法則 來幫助我們進行計算。

為了更新權重使用鏈式法則求解函式的導數。注意,為了簡單起見,我們只展示了假設為 1 層的神經網路的偏導數。
哦!這真難看,但它讓我們得到了我們需要的東西 —— 損失函式對於權重的導數(斜率),這樣我們就可以相應地調整權重。
現在我們知道要怎麼做了,讓我們向 Pyhton 程式碼中新增反向傳播函式。
class NeuralNetwork: def __init__(self, x, y): self.input= x self.weights1= np.random.rand(self.input.shape[1],4) self.weights2= np.random.rand(4,1) self.y= y self.output= np.zeros(self.y.shape) def feedforward(self): self.layer1 = sigmoid(np.dot(self.input, self.weights1)) self.output = sigmoid(np.dot(self.layer1, self.weights2)) def backprop(self): # 應用鏈式法則求出損失函式對於 weights2 和 weights1 的導數 d_weights2 = np.dot(self.layer1.T, (2*(self.y - self.output) * sigmoid_derivative(self.output))) d_weights1 = np.dot(self.input.T,(np.dot(2*(self.y - self.output) * sigmoid_derivative(self.output), self.weights2.T) * sigmoid_derivative(self.layer1))) # 用損失函式的導數(斜率)更新權重 self.weights1 += d_weights1 self.weights2 += d_weights2 複製程式碼
如果你需要更深入地理解微積分和鏈式法則在反向傳播中的應用,我強烈推薦 3Blue1Brown 的教程。
觀看 視訊教程
融會貫通
現在我們已經有了前饋和反向傳播的完整 Python 程式碼,讓我們將神經網路應用到一個示例中,看看效果如何。

我們的神經網路應該通過學習得出一組理想的權重來表示這個函式。請注意,僅僅是求解權重的過程對我們來說也並不簡單。
讓我們對神經網路進行 1500 次訓練迭代,看看會發生什麼。觀察下圖中每次迭代的損失變化,我們可以清楚地看到損失 單調遞減至最小值 。這與我們前面討論的梯度下降演算法是一致的。

讓我們看一下經過 1500 次迭代後神經網路的最終預測(輸出)。

1500 次訓練迭代後的預測結果
我們成功了!我們的前饋和反向傳播演算法成功地訓練了神經網路,預測結果收斂於真實值。
請注意,預測值和實際值之間會存在細微的偏差。我們需要這種偏差,因為它可以防止 過擬合 ,並允許神經網路更好地 推廣 至不可見資料中。
後續的學習任務
幸運的是,我們的學習旅程還未結束。關於神經網路和深度學習,我們還有 很多 內容需要學習。例如:
- 除了 Sigmoid 函式,我們還可以使用哪些 啟用函式 ?
- 在訓練神經網路時使用 學習率
- 使用 卷積 進行影象分類任務
我將會就這些主題編寫更多內容,請在 Medium 上關注我並留意更新!
結語
當然,我也在從零開始編寫我自己的神經網路的過程中學到了很多。
雖然像 TensorFlow 和 Keras 這樣的深度學習庫使得構建深度神經網路變得很簡單,即使你不完全理解神經網路內部工作原理也沒關係,但是我發現對於有追求的資料科學家來說,深入理解神經網路是很有好處的。
這個練習花費了我大量的時間,我希望它對你們也有幫助!
如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為掘金 上的英文分享文章。內容覆蓋 Android 、 iOS 、 前端 、 後端 、 區塊鏈 、 產品 、 設計 、 人工智慧 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃 、官方微博、 知乎專欄 。