原文地址:Simplest artificial neural network
原文作者:Givi Odikadze(已授權)
譯者 & 校正:HelloGitHub-小熊熊 & 鹵蛋

導言
我不會機器學習,但上個月我在 GitHub 上發現了一個極簡、入門級的神經網絡教程。它簡潔易懂能用一行公式說明白的道理,不多寫一句廢話,我看后大呼過癮。

這么好的東西得讓更多人看到,但原文是英文的無法直接分享,所以得先聯系作者拿到翻譯的授權,然后由小熊熊翻譯了這個項目,最后才有您看到的這篇文章。過程艱辛耗時一個月實屬不易,如果您看完覺得還不錯,歡迎點贊、分享給更多人。
內容分為兩部分:
-
第一部分: 最簡單的人工神經網絡 -
第二部分: 最基礎的反向傳播算法
人工神經網絡是人工智能的基礎,只有夯實基礎,才能玩轉 AI 魔法!
溫馨提示
:公式雖多但只是看起來唬人,實際耐下心讀並不難懂。下面正文開始!
一、最簡單的人工神經網絡
通過理論和代碼解釋和演示的最簡單的人工神經網絡。
示例代碼:https://github.com/gokadin/ai-simplest-network
理論
模擬神經元
受人腦工作機制的啟發,人工神經網絡有着相互連接的模擬神經元,用於存儲模式和相互溝通。一個模擬神經元最簡單的形式是有一個或多個輸入值 和一個輸出值 ,其中每個 有一個權重 。

拿最簡單的來說,輸出值就是輸入值乘以權重之后的總和。
一個簡單的例子
網絡的作用在於通過多個參數 模擬一個復雜的函數,從而可以在給定一系列輸入值 的時候得到一個特定的輸出值 ,而這些參數 通常是我們自身難以擬定的。
假設我們現在的一個網絡有兩個輸入值 , ,它們對應兩個權重值 和 。
現在我們需要調整權重值,從而使得它們可以產生我們預設的輸出值。
在初始化時,因為我們不知曉最優值,往往是對權重隨機賦值,這里我們為了簡單,將它們都初始化為 1 。

這種情況下,我們得到的就是
誤差值
如果輸出值 與我們期望的輸出值不一致,那就有了誤差。
例如,如果我們希望目標值是 ,那么這里相差值就是
通常我們會采用方差(也就是代價函數)來衡量誤差:
如果有多套輸入輸出值,那么誤差就是每組方差的平均值。
我們用方差來衡量得到的輸出值與我們期望的目標值之間的差距。通過平方的形式就可以去除負偏離值的影響,更加凸顯那些偏離較大的偏差值(不管正負)。
為了糾正誤差,我們需要調整權重值,以使得結果趨近於我們的目標值。在我們這個例子中,將 從 1.0 降到 0.5 就可以達到目標,因為
然而,神經網絡往往涉及到許多不同的輸入和輸出值,這種情況下我們就需要一個學習算法來幫我們自動完成這一步。
梯度下降
現在是要借助誤差來幫我們找到應該被調整的權重值,從而使得誤差最小化。但在這之前,讓我們了解一下梯度的概念。
什么是梯度?
梯度本質上是指向一個函數最大斜率的矢量。我們采用 來表示梯度,簡單說來,它就是函數變量偏導數的矢量形式。
對於一個雙變量函數,它采用如下形式表示:
讓我們用一些數字來模擬一個簡單的例子。假設我們有一個函數是 ,那么梯度將是
什么是梯度下降?
下降則可以簡單理解為通過梯度來找到我們函數最大斜率的方向,然后通過反方向小步幅的多次嘗試,從而找到使函數全局(有時是局部)誤差值最小的權重。
我們采用一個稱為學習率的常量來表示這個反方向的小步幅,在公式中我們用 來進行表征。
如果 取值太大,那有可能直接錯過最小值,但如果取值太小,那我們的網絡就會花費更久的時間來學習,而且也有可能陷入一個淺局部最小值。

對於我們例子中的兩個權重值 和 ,我們需要找到這兩個權重值相較於誤差函數的梯度
還記得我們上面的公式 和 嗎?對於 和 ,我們可以將其帶入並通過微積分中的鏈式求導法則來分別計算其梯度
簡潔起見,后面我們將采用 這個術語來表示 。
一旦我們有了梯度,將我們擬定的學習率 帶入,可以通過如下方式來更新權重值:
然后重復這個過程,直到誤差值最小並趨近於零。
代碼示例
附帶的示例采用梯度下降法,將如下數據集訓練成有兩個輸入值和一個輸出值的神經網絡:
一旦訓練成功,這個網絡將會在輸入兩個 1 時輸出 ~0,在輸入 1 和 0 時,輸出 ~1 。
怎么運行?
Go
PS D:\github\ai-simplest-network-master\src> go build -o bin/test.exe
PS D:\github\ai-simplest-network-master\bin> ./test.exe
err: 1.7930306267024234
err: 1.1763080417089242
……
err: 0.00011642621631266815
err: 0.00010770190838306002
err: 9.963134967988221e-05
Finished after 111 iterations
Results ----------------------
[1 1] => [0.007421243532258703]
[1 0] => [0.9879921757260246]
Docker
docker build -t simplest-network .
docker run --rm simplest-network
二、最基礎的反向傳播算法
反向傳播(英語:Backpropagation,縮寫為 BP)是“誤差反向傳播”的簡稱,是一種與最優化方法(如梯度下降法)結合使用的,用來訓練人工神經網絡的常見方法。
反向傳播技術可以用來訓練至少有一個隱藏層的神經網絡。下面就來從理論出發結合代碼拿下反向傳播算法。
示例代碼:https://github.com/gokadin/ai-backpropagation
理論
感知機介紹
感知機是這樣一個處理單元:它接受輸入 , 采用激活函數 對其進行轉換,並輸出結果 。
在一個神經網絡,輸入值是前一層節點輸出值的權重加成總和,再加上前一層的誤差:
如果我們把誤差當作層中另外的一個常量為 -1 的節點,那么我們可以簡化這個公式為

激活函數
為什么我們需要激活函數呢?如果沒有,我們每個節點的輸出都會是線性的,從而讓整個神經網絡都會是基於輸入值的一個線性運算后的輸出。因為線性函數組合仍然是線性的,所以必須要引入非線性函數,才能讓神經網絡有區別於線性回歸模型。
針對 ,典型的激活函數有以下形式:
Sigmoid 函數 :
線性整流函數:
tanh 函數:
反向傳播
反向傳播算法可以用來訓練人工神經網絡,特別是針對具有多於兩層的網絡。
原理是采用 forward pass 來計算網絡輸出值和誤差,再根據誤差梯度反向更新輸入層的權重值。
術語
-
分別是 I, J, K 層節點的輸入值。 -
分別是 I, J, K 層節點的輸出值。 -
是 K 輸出節點的期望輸出值。 -
分別是 I 到 J 層和 J 到 K 層的權重值。 -
代表 T 組關聯中當前的一組關聯。
在下面的示例中,我們將對不同層節點采用以下激活函數:
-
輸入層 -> 恆等函數 -
隱藏層 -> Sigmoid 函數 -
輸出層 -> 恆等函數
The forward pass
在 forward pass 中,我們在輸入層進行輸入,在輸出層得到結果。
對於隱藏層的每個節點的輸入就是輸入層輸入值的加權總和:
因為隱藏層的激活函數是 sigmoid,那么輸出將會是:
同樣,輸出層的輸入值則是
因為我們賦予了恆等函數做為激活函數,所以這一層的輸出將等同於輸入值。
一旦輸入值通過網絡進行傳播,我們就可以計算出誤差值。如果有多套關聯,還記得我們第一部分學習的方差嗎?這里,我們就可以采用平均方差來計算誤差。
The backward pass
現在我們已經得到了誤差,就可以通過反向傳輸,來用誤差來修正網絡的權重值。
通過第一部分的學習,我們知道對權重的調整可以基於誤差對權重的偏導數乘以學習率,即如下形式
我們通過鏈式法則計算出誤差梯度,如下:
因此,對權重的調整即為
對於多個關聯,那么對權重的調整將為每個關聯的權重調整值之和
類似地,對於隱藏層之間的權重調整,繼續以上面的例子為例,輸入層和第一個隱藏層之間的權重調整值為
那么,基於所有關聯的權重調整即為每次關聯計算得到的調整值之和
計算
這里, 我們對 可以再做進一步的探索。上文中,我們看到 。
對前半部分的 ,我們可以有
對后半部分的 ,因為我們在這一層采用了 sigmoid 函數,我們知道,sigmoid 函數的導數形式是 ,因此,有
綜上,便可以得到 的計算公式如下
算法總結
首先,對網絡權重值賦予一個小的隨機值。
重復以下步驟,直到誤差為0 :
-
對每次關聯,通過神經網絡向前傳輸,得到輸出值 -
計算每個輸出節點的誤差 ( ) -
疊加計算每個輸出權重的梯度 ( ) -
計算隱藏層每個節點的 ( ) -
疊加計算每個隱藏層權重的梯度 ( )
-
-
更新所有權重值,重置疊加梯度 ( )
圖解反向傳播
在這個示例中,我們通過真實數據來模擬神經網絡中的每個步驟。輸入值是[1.0, 1.0],輸出期望值為 [0.5]。為了簡化,我們將初始化權重設為 0.5 (雖然在實際操作中,經常會采用隨機值)。對於輸入、隱藏和輸出層,我們分別采用恆等函數、 sigmoid 函數 和恆等函數作為激活函數,學習率 則定為 0.01 。
Forward pass
運算一開始,我們將輸入層的節點輸入值設為 。
因為我們對輸入層采用的是恆等函數作為激活函數,因此有 。

接下來,我們通過對前一層的加權總和將網絡向前傳遞到 J 層,如下


然后,我們將 J 層節點的值輸入到 sigmoid 函數( ,將 代入,得到 0.731)進行激活。

最后,我們將這個結果傳遞到最后的輸出層。
因為我們輸出層的激活函數也是恆等函數,因此

Backward pass
反向傳播的第一步,是計算輸出節點的 ,

采用 計算 J 和 K 兩層節點間的權重梯度:

接下來,以同樣的方法計算每個隱藏層的 值(在本示例中,僅有一個隱藏層):

針對 I 和 J 層節點權重計算其梯度為:

最后一步是用計算出的梯度更新所有的權重值。注意這里如果我們有多於一個的關聯,那么便可以針對每組關聯的梯度進行累計,然后更新權重值。

可以看到這個權重值變化很小,但如果我們用這個權重再跑一遍 forward pass,一般來說將會得到一個比之前更小的誤差。讓我們現在來看下……
第一遍我們得到的 ,采用新的權重值計算得到 。
由此, ,而 。
可見,誤差得到了減小!盡管減少值很小,但對於一個真實場景也是很有代表性的。按照該算法重復運行,一般就可以將誤差最終減小到0,那么便完成了對神經網絡的訓練。
代碼示例
本示例中,將一個 2X2X1 的網絡訓練出 XOR 運算符的效果。

這里,f 是針對隱藏層的 sigmoid 激活函數。
注意,XOR 運算符是不能通過第一部分中的線性網絡進行模擬的,因為數據集分布是非線性的。也就是你不能通過一條直線將 XOR 的四個輸入值正確划分到兩類中。如果我們將 sigmoid 函數換為恆等函數,這個網絡也將是不可行的。
講完這么多,輪到你自己來動手操作啦!試試采用不同的激活函數、學習率和網絡拓撲,看看效果如何?
最后
恭喜看完本文!你學會了嗎?
關注 HelloGitHub 公眾號 第一時間收到更新。
還有更多開源項目的介紹和寶藏項目等待你的發現。
- END -