《Neural Networks and Deep Learning》讀書筆記:最簡單的識別MNIST的神經網絡程序(1)

轉載請註明出處:https://www.codelast.com/

Neural Networks and Deep Learning》一書的中文譯名是《神經網絡與深度學習》,書如其名,不需要解釋也知道它是講什麼的,這是本入門級的好書。
在第一章中,作者展示瞭如何編寫一個簡單的、用於識別MNIST數據的Python神經網絡程序。對於武林高手來說,看懂程序不會有任何困難,但對於我這樣的Python渣則有很多困惑。所以我對做了一些筆記,希望同時也可以幫助有需要的人。

『1』原文及程序
在這裏,先把中譯版部分貼上來,以方便後面的筆記記錄(這只是一部分):

在給出一個完整的清單之前,讓我解釋一下神經網絡代碼的核心特徵,如下。核心是一個Network類,我們用來表示一個神經網絡。這是我們用來初始化一個Network對象的代碼:

class Network(object):
def __init__(self, sizes):
self.num_layers = len(sizes)
self.sizes = sizes
self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
self.weights = [np.random.randn(y, x) 
for x, y in zip(sizes[:-1], sizes[1:])]

在這段代碼中,列表sizes包含各層的神經元的數量。因此舉個例子,如果我們想創建一個在第一層有2個神經元,第二層有3個神經元,最後一層有1個神經元的network對象,我們應這樣寫代碼:
net = Network([2, 3, 1])
Network對象的偏差和權重都是被隨機初始化的,使用Numpy的np.random.randn函數來生成均值爲0,標準差爲1的高斯分佈。隨機初始化給了我們的隨機梯度下降算法一個起點。在後面的章節中我們將會發現更好的初始化權重和偏差的方法,但是現在將採用隨機初始化。注意Network初始化代碼假設第一層神經元是一個輸入層,並對這些神經元不設置任何偏差,因爲偏差僅在之後的層中使用。
同樣注意,偏差和權重以列表存儲在Numpy矩陣中。因此例如net.weights[1]是一個存儲着連接第二層和第三層神經元權重的Numpy矩陣。(不是第一層和第二層,因爲Python列中的索引從0開始)因此net.weights[1]相當冗長, 讓我們就這樣表示矩陣 w 。矩陣中的 wjk 是連接第二層的 kth 神經元和第三層的jth 神經元的權重。

『2』程序解讀
正如上面的代碼示例,創建一個Network對象的時候,傳入的是一個list,例如 [2, 3, 1],list中有幾個元素就表示神經網絡有幾層,從list中的第一個元素開始,每一個元素依次表示第1層、每2層、……第n層的神經元的數量。
這個不難理解,比較難理解的是 bias(偏差)以及 weight(權重)的表示方式。
文章來源:http://www.codelast.com/
我們先來看 bias(偏差):

self.biases = [np.random.randn(y, 1) for y in sizes[1:]]

首先需要明確的是,中括號表明了 biases 是一個list,中括號裏的內容是對這個list進行賦值的代碼,它採用了一個for循環的方式來賦值,例如下面的代碼:

a = [i for i in range(3)]
print(a)

會輸出結果:

[0, 1, 2]

所以,np.random.randn(y, 1) for y in sizes[1:] 這部分代碼表達的就是—— list中的每一個元素都是 np.random.randn(y, 1) 這個表達式的計算結果,而這個表達式是含有變量 y 的,y 必須要有實際的值才能計算,所以用一個for循環來給 y 賦值,y 能取的所有值就是對 sizes[1:] 這個list進行遍歷得到的。前面已經說過了,sizes本身是一個list,而sizes[1:] 表示的是取這個 list 從第2個元素開始的子集,給個例子:

a = [5, 6, 8]
print(a[1:])

會輸出:

[6, 8]

所以,在我們前面用 net = Network([2, 3, 1]) 這樣的代碼來創建了一個對象之後,sizes[1:] 的內容其實就是 [3, 1],所以 y 的取值就是 3 和 1,所以 biases 這個list的第一個元素就是 np.random.randn(3, 1),第二個元素就是 np.random.randn(1, 1)。
文章來源:http://www.codelast.com/
我覺得經過這樣解釋,biases 在結構上看來是什麼東西已經比較清楚了吧?
那麼話說回來,我們雖然知道了 np.random.randn(3, 1) 是 biases 的第一個元素,但 np.random.randn() 又是什麼鬼?
且聽我道來:
np 是這個Python程序 import 進來的Numpy庫的縮寫:

import numpy as np

randn() 是Numpy這個庫中,用於生成標準正態分佈數據的一個函數。其實 randn(3, 1) 生成的是一個3x1的隨機矩陣,我們可以在Python命令行中直接試驗一下:

import numpy as np
np.random.randn(3, 1)

輸出結果如下:

array([[ 1.33160979],
       [ 0.66314905],
       [ 0.27303603]])

可見,它輸出的是一個3行,1列的隨機數矩陣——你看這輸出多體貼,爲了表明“3行1列”,它沒有把數字都排在一行,而是特意放在了3行裏。
好了,現在我們已經徹底瞭解了 biases 的結構,那麼再來看看,爲什麼它的第一個元素是3x1的矩陣,第二個元素是1x1的矩陣呢?
這跟要創建的神經網絡層的結構有關。
文章來源:http://www.codelast.com/
如作者書中所說,“假設第一層神經元是一個輸入層,並對這些神經元不設置任何偏差,因爲偏差僅在之後的層中使用”,所以 biases 只有兩個元素,而不是3個。但知道了這一點並不能解決我們心中的疑惑:爲什麼 biases[0] 是一個 3x1 的矩陣,biases[1] 是一個 1x1 的矩陣呢?

這就跟weight(權重)有關了,所以,我們不妨先來看看代碼中,weight是如何定義的:

self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]

這個冗長的實現需要“細細品味”。
首先,中括號表明 weights 是一個list,中括號裏的代碼對這個list的每一個元素進行賦值,list中的每一個元素都是一個 np.random.randn(y, x) ——這個東西我們剛纔在解釋 biases 的時候已經說過了,它是一個y行x列的隨機數矩陣。那麼y和x的具體值又是什麼呢?它們是由for循環定義的:

for x, y in zip(sizes[:-1], sizes[1:])

首先要注意,這裏是按 x, y 的順序來賦值的,而不是 y, x,這和 np.random.randn(y, x) 中的順序相反。
其中,zip()是Python的一個內建函數,它接受一系列可迭代的對象(例如,在這裏是兩個list)作爲參數,將對象中對應的元素打包成一個個tuple(元組),然後返回由這些tuples組成的list。
爲了形象地說明zip()的作用,我們來看看這句簡單的代碼:

zip([3, 4], [5, 9])

它的輸出是:

[(3, 5), (4, 9)]

可見,zip() 分別取出 [3, 4] 以及 [5, 9] 這兩個 list 的第一個、第二個元素,然後合成了兩個 tuple:(3, 5) 和 (4, 9),然後再把這兩個tuple組成一個list:[(3, 5), (4, 9)]。所以,假設我們有如下代碼:

for x, y in zip([3, 4], [5, 9])

那麼 x, y 的取值就有兩組了:3, 5 和 4, 9。
有了這樣直觀的對比,我們已經可以理解 for x, y in zip(sizes[:-1], sizes[1:]) 是什麼含義了。其實 sizes 就是一個含有3個元素的list:[2, 3, 1],因此 sizes[:-1] 就是去掉最後一個元素的子list,即 [2, 3];而 sizes[1:] 就是去掉第一個元素的子list,即 [3, 1]。
所以現在真相大白:x, y 的取值有兩組,一組是 2, 3,另一組是 3, 1。
再回去看 weights 的賦值代碼,於是可以秒懂:weights 的第一個元素 weights[0] 是一個 3x2 的隨機數矩陣,weights 的第二個元素 weights[1] 是一個 1x3 的隨機數矩陣
文章來源:http://www.codelast.com/
現在總結一下:
biases[0]:3x1 的矩陣
biases[1]:1x1 的矩陣
weights[0]:3x2 的矩陣
weights[1]:1x3 的矩陣

雖然我們已經精確分析出了那段代碼的含義,但有人可能還是要問:爲什麼創建的bias和weight是這些維度的?
爲了能幫助理解,我們畫出這個神經網絡的結構(第一層有2個神經元,第二層有3個神經元,最後一層有1個神經元):
three layer neural network
文章來源:http://www.codelast.com/
從圖上我們可以一眼看出,第一層的輸入向量(也就是  wx+b 中的  x )是一個2行1列的向量,或者說是一個 2x1 的矩陣;第二層的  x 是一個3行1列的向量,或者說是一個 3x1 的矩陣。
我們知道,除了輸出層(output)之外,每一層的輸入 x 都要經過一個 wx+b 的運算(這裏忽略了激勵函數),得到一個矩陣,作爲下一層的輸入。式中既然有weight(w)和  x  向量的點乘,weight矩陣的列數就必須和 x 向量的行數相等,所以這裏是不是恰好符合這個規則呢?
來看看:
第一層→第二層的 wx+b 運算就是 weight[0]矩陣 x + biases[0] 矩陣,即 (3x2矩陣)  (2x1矩陣) + (3x1矩陣),結果是一個 3x1 的矩陣,這個矩陣,作爲下一層的輸入,實際上就是下一層的  x 。前面我們分析過,第二層的  x 應該是一個 3x1 的矩陣,這與運算結果完全相符。
第二層→第三層的  wx+b 運算就是 weight[1]矩陣 x + biases[1] 矩陣,即 (1x3矩陣)  (3x1矩陣) + (1x1矩陣),結果是一個 1x1 的矩陣,其實就是一個標量,由於後面已經沒有其他層,所以這個標量就是整個神經網絡的output。

通過以上不厭其煩的分析,相信任何人都能搞明白那僅有不到10行的代碼是如何巧妙地定義了一個神經網絡,搞定!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章