轉載請註明出處: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:])]
net = Network([2, 3, 1])
『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個神經元):
文章來源: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行的代碼是如何巧妙地定義了一個神經網絡,搞定!