[譯] 如何用 Python 從零開始構建你本身的神經網絡

一個幫助初學者理解深度神經網絡內部工做機制的指南前端

寫做動機: 爲了使我本身能夠更好地理解深度學習,我決定在沒有像 TensorFlow 這樣的深度學習庫的狀況下,從零開始構建一個神經網絡。我相信,理解神經網絡的內部工做原理對任何有追求的數據科學家來講都很重要。python

這篇文章包含了我所學到的東西,但願對大家也有用。android

什麼是神經網絡?

大多數介紹神經網絡的文章在描述它們時都會與大腦作類比。在不深刻研究與大腦相似之處的狀況下,我發現將神經網絡簡單地描述爲給定輸入映射到指望輸出的數學函數更容易理解一些。ios

神經網絡由如下幾個部分組成:git

  • 一個輸入層x
  • 任意數量的隱含層
  • 一個輸出層ŷ
  • 層與層之間的一組權重誤差Wb
  • 每一個隱含層中所包含的一個可選的激活函數σ。在本教程中,咱們將使用 Sigmoid 激活函數。

下圖展現了 2 層神經網絡的架構(注:在計算神經網絡中的層數時,輸入層一般被排除在外github

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 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索