最簡單的人工神經網路

HelloGitHub發表於2021-08-25

原文地址: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 -

相關文章