# Neural Networks and Deep Learning - Using Neural Nets to Recognize Handwritten Digits (Chapter 1) ###### tags: `Machine Learning`、`Deep learning`、`Neural Networks` 此筆記為研讀 [此書](http://neuralnetworksanddeeplearning.com/index.html) 後的一些重點摘錄。 [[下一章]]() ## 感知器(Perceptrons) - 運作原理: - 輸入: $x_1,x_2,...∈\{0,1\}$ - 輸出: $0,\ if \sum_jw_jx_j+b \leq 0$ $1,\ if \sum_jw_jx_j+b \gt 0$ 其中 $w_j$為 $x_j$ 邊上的權重,$b$ 為 感知器的 bias (其實就是 $(-1)×$threshold) ![](https://i.imgur.com/JSwC6g5.png) - 致命缺點: - 任何感知器的 weights 或是 biases 的細微改變有時可能導致該感知器上的輸出完全翻轉 (例如 0 變 1),此翻轉也可能進而導致網路其他部分的行為以未知的方式改變。 - 例如假設目前的網路將 9 誤認成 8,因此我們要去微調 weights 以及 biases 使 9 被正確分類。然而,經過修改後雖然 9 已被正確分類,但有可能影響到其他分類的表現。 - 總的來說,感知器就是無法確保輸入端的小改變只會讓輸出端產生小變化。 ## Sigmoid neurons - 運作原理: - 輸入: $\{ x_i∈R\ |\ 0 \leq x_i \leq 1\}$ - 輸出: $\sigma(w⋅x+b)$, 其中 $\sigma(z)=\frac{1}{1+e^{-z}}$, $w=(w_1,w_2,...)$, $x=(x_1,x_2,...)$ - ![](https://i.imgur.com/wismDmf.png) - 根據上圖可知 $\sigma(x)$ 是一個光滑函數 (smooth function),因此代表在 weights 及 biases 上的小變化,分別以 $\Delta(w_j)$, $\Delta(b)$ 表示,將會讓輸出端產生小變化, $\Delta \mbox{output}$。根據微積分可得下列近似值: $\Delta \mbox{output} \approx \sum_j \frac{\partial \, \mbox{output}}{\partial w_j} \Delta(w_j) + \frac{\partial \, \mbox{output}}{\partial b} \Delta(b)$ - 上述告訴我們一件事 - $\Delta \mbox{output}$ 是 $\Delta(w_j)$ 和 $\Delta(b)$ 的一個線性函數,再次證明了 sigmoid function 是有能力達到在輸出端作任何想要的改變。 ## 類神經網路的架構 - 層 (layer) - 輸入層 (input layer): 若是 $28×28$ 灰階圖,則需要 $28×28=784$ 個輸入神經元 - 輸出層 (output layer): 若要辨識 0~9,需要 10 個輸出神經元 - 隱藏層 (hidden layer): 整個神經網路架構中的精隨,也是影響辨識成功率的關鍵 ![](https://i.imgur.com/2iN4Vly.png) - 根據層跟層之間的關係,有下列幾種架構: - 前饋式神經網路 (feedforward neural networks) - 每一層的輸出會是下一層的輸入 - 網路中不允許存在迴圈 (loop) - [循環式神經網路](https://en.wikipedia.org/wiki/Recurrent_neural_network) (Recurrent neural network) - 讓某些神經元在靜止之前觸發一段有限的時間,此觸發刺激其他的神經元,使他們稍後也會觸發一段有限的時間。隨著時間流逝,不斷導致更多的神經元觸發,可以得到一連串觸發的神經元 - 在這裡,迴圈 (loop)不會有任何問題,因為神經元的輸出只會在往後的某個時間影響其輸入而不是當下。 ## 梯度下降法 (gradient descent) - 損失函數 (loss /cost / objective function) - $C(w,b)=\frac{1}{2n} \sum_x \| y(x) - a\|^2$ - $n$: 輸入訓練的總筆數 - $a$: 以 $x$ 作為輸入,透過該網路得到的輸出向量 - $y(x)$: 以 $x$ 作為輸入的正確輸出向量 - 可視為一個評估分類是否準確的函數,值越小,表示分類表現越好,因此我們的目標是要讓損失函數盡可能的小。 - 因為損失函數通常有多個變數,因此單純使用微積分算不出 $C(w,b)$ 的最小值。 - 梯度下降法 - 假設 $C$ 有 $m$ 個變數, $v_1, v_2, ...,v_m$。一樣根據微積分,我們知道 $\Delta C \approx \frac{\partial C}{\partial v_1} \Delta v_1 + \frac{\partial C}{\partial v_2} \Delta v_2 + ... +\frac{\partial C}{\partial v_m} \Delta v_m$ - 令 $\Delta v =(\Delta v_1,...,\Delta v_m)^T$ 且 $\nabla C = (\frac{\partial C}{\partial v_1},...,\frac{\partial C}{\partial v_m})^T$,則上述式子可改寫成 $\Delta C \approx \nabla C \cdot \Delta v$ - 取 $\Delta v = -\eta \nabla C$,則 $\Delta C \approx \nabla C \cdot (-\eta \nabla C) = -\eta \|\nabla C\|^2 \leq 0$,因此可以使 $C$ 越來越小。而該如何讓 $C$ 越來越小呢? 透過修正 $C$ 的變數 $v' = v + \Delta v = v - \eta \nabla C$。(這裡的 $v=(v_1,...,v_m)^T$) - 因此,根據上面推導出來的結果,假設我們目前的"位置"在 $w_k$ 以及 $b_l$,則做以下的參數調整就會使 $C(w,b)$ 變小: - $w_k' = w_k - \eta \frac{\partial C}{\partial w_k}$ - $b_l' = b_l - \eta \frac{\partial C}{\partial b_l}$ 如此不斷地更新,最後我們可以抵達函數的最底部,也就是我們想要找的最小值。 #### 隨機梯度下降法 (stochastic gradient descent) 此為一種用來加速學習的方法。為甚麼要加速呢? 在更新參數的時候,原本的梯度下降法一次用全部訓練集的數據去計算損失函數的梯度,才會更新一次參數。然而,當訓練資料量大的時候,顯得訓練很沒效率,因此引進隨機梯度下降法。 如同其名,此方法從訓練資料中隨機挑選小批次 (mini-batch) 樣本,然後算出小批次的梯度後,就更新一次參數。 假設小批次包含 $m$ 筆資料, $X_1,...,X_m$,則小批次的平均梯度就會大約等於這次的梯度 $\nabla C \approx \frac{1}{m} \sum_{j=1}^m \nabla C_{X_j}$。也就是說,一樣做以下的參數調整就會使 $C(w,b)$ 變小: - $w_k' = w_k - \frac{\eta}{m} \sum_{j=1}^m \frac{\partial C_{X_j}}{\partial w_k}$, - $b_l' = b_l - \frac{\eta}{m} \sum_{j=1}^m \frac{\partial C_{X_j}}{\partial b_l}$ (有時我們會忽略分母的 m,或者是說將 $\frac{\eta}{m}$ 視為新的 $\eta'$) ## 實作 首先,執行以下指令取得程式碼及相關檔案: ``` git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git ``` :::warning :Notes: 複製下來的程式碼有一些地方需要修改 (若在 Python3 執行): 1. `mnist_loader.py` 中,line 71, 73 及 75 ```python training_data = zip(training_inputs, training_results) validation_data = zip(validation_inputs, va_d[1]) test_data = zip(test_inputs, te_d[1]) ``` 改成 ```python training_data = list(zip(training_inputs, training_results)) validation_data = list(zip(validation_inputs, va_d[1])) test_data = list(zip(test_inputs, te_d[1])) ``` 2. `mnist_loader.py` 中,line 42-44 ```python f = gzip.open('../data/mnist.pkl.gz', 'rb') training_data, validation_data, test_data = pickle.load(f) f.close() ``` 改成 ```python f = gzip.open('../data/mnist.pkl.gz', 'rb') training_data, validation_data, test_data = pickle.load(f, encoding = 'latin1') f.close() ``` ::: 接著在 Python shell 中執行下列的指令: 讀取 MNIST 資料 ```python >>> import mnist_loader >>> training_data, validation_data, test_data = \ ... mnist_loader.load_data_wrapper() ``` 設定一個具有 784 個輸入層,30 個隱藏層,以及 10 個輸出層的神經網路 ```python >>> import network >>> net = network.Network([784, 30, 10]) ``` 最後使用 SGD 從訓練資料中學習,並設定 mini-batch size = 10,學習速率 $η=3.0$,總共學習 30 輪。 ```python >>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data) ``` 可能輸出結果: ``` Epoch 0: 9129 / 10000 Epoch 1: 9295 / 10000 Epoch 2: 9348 / 10000 ... Epoch 27: 9528 / 10000 Epoch 28: 9542 / 10000 Epoch 29: 9534 / 10000 ``` 書中還有許多針對不同參數,像是 mini-batch 的大小、學習率 ($\eta$) 等等的調整,甚至使用非類神經網路的方法,像 SVM 或是利用數字間不同的明暗程度去做分類,就不再這裡列出,有興趣者請點 [[這裡](http://neuralnetworksanddeeplearning.com/chap1.html)] 觀看。 ## 延伸到深度學習 儘管類神經網路表現出色,但因為 weights 跟 biases 是自動調整的,因此我們能否了解這個網路背後是依據怎樣的準則來調整 weights 和 biases,並根據這樣的準則來提升表現。 在 AI 研究的初期,我們希望構建 AI 的同時也能幫助我們理解其背後的原理,甚至可以理解人腦的功能。但是也有可能最終我們沒能了解大腦,也不了解人工智慧的運作原理! 為了解決這些問題,現在考慮一個確定人臉偵測的問題。我們當然可以用上面手寫數字的方法來解決此問題- 圖片中的每個像素當作輸入餵給神經網路,然後最後以一個神經元輸出"是"或"否"。 不過這裡假設嘗試想要手動設計網路,選擇適當的權重和偏差。暫時忘掉神經網路,最直覺的方式就是將此問題分解成數個子問題: 圖片的左上角有眼睛嗎? 右上角有眼睛嗎? 中間有鼻子嗎? 下面有嘴巴嗎? 上面有頭髮嗎? 等等。 如果其中幾個問題的回答為“是”,甚至只是“可能是”,那麼我們可以推論該圖片很可能是一張臉。相反,如果大多數問題的答案為“否”,則圖像可能不是一張臉。 然而,這種方法存在著問題,像是也許這個人是禿頭,所以他們沒有頭髮。也許我們只能看到臉部的一部分,或者臉部傾斜,所以某些面部特徵被遮蓋了。因此,這是一種可能的架構,這並不是解決人臉偵測問題的一個實際方法,而是幫助我們了解關於網絡運作方式。如下圖所示: ![](https://i.imgur.com/qZEJfH6.png) 這個子問題又可以再被切成數個子問題,不斷地遞迴切下去,到最後子問題會簡單到只需檢查幾個像素就能回答。 總的來說,我們將一個複雜的問題分解成數個極為簡單的問題,透過前幾層只需回答很簡單、明確的問題,而後幾層則建立起越來越複雜和抽象的概念的層次結構。這種具有多層結構(兩個或多個隱藏層)的網路稱為深度神經網絡(deep neural networks)。