contributed by < jeremy3951
>
綠色代表延伸問題題目
藍色代表引用文章的話
紅色代表觀念更正
(2020q3)進階電腦系統理論與實作
延伸問題 :
問題 1 : sigmoid function 是對應神經元中的什麼行為 ?
引用參考網站的一段話 :
激活函數模擬神經傳導的運作方式,當輸出神經元接受刺激的總和
經過激活函式的運算大於臨界值時,會傳遞至下一個神經元。
sigmoid function 就是模擬神經元中訊號是否傳遞的機制(開關)
問題 2 : 為什麼要挑 sigmoid 當作 activate function ?
參考資料: Neural Networks (一)
若要模仿神經元中全開全關的模式用一般的 step function 比較好,可是 step function 不可微分不能用 gradient descent 來算梯度。
基於下列原因讓大部分的類神經網路都用 sigmoid function 作為該神經網路的激勵函數 :
問題 3 : 為什麼要使用 back-propagation ? (跟 gradient descent 有關)
首先要提到 gradient descent :
(參考)【Python】淺談梯度下降與實作(上):為什麼是你
(參考台大李宏毅教授的影片)ML Lecture 7: Backpropagation
backpropagation 利用 chain rule 加速 gradient descent 的運算,提升神經網路的學習速度
初始化神經網路 : ann_init
ann_init(3,1,2,1) : New network with 3 inputs, 1 hidden layer of 2 neurons , and 1 output.
神經網路圖如下 :
記憶體配置以及指標指向的記憶體位址 :
ret->weight = (double *) ((char *) ret + sizeof(ann_t));
右邊的括號已經寫好了要指向的記憶體位置了,那為什麼需要 (double *)?
(將其拿掉也可以正常執行程式碼)
Ans : 不加也可以,這樣寫是為了方便觀察。
權重初始化 : ann_randomize(ret);
total_weight 裡面不只有神經元運算需要的 weight ,連運算完要加上的 bias 也順便放在這個變數裡面了 (double sum = *w++ * (-1.0);
就是先算 bias)
探討 activation 加速 :
連續轉離散…
訓練神經網路 :
第 3~6 行設定執行次數 ,次數內沒完成任務就重來
第 11~12 初始化權重
第 15~16 計算 error
第 19~24 行 => ( 當下 model 是否為 best model )? 保存 : 棄掉
該神經網路透過將權重隨機更新並且計算 error 來保留最佳 model
延伸問題 :
2. 使用 back-propagation (反向傳遞) 改寫上述程式碼,並探討激勵函數的特性
針對此次的類神經網路 (ann_init(3,1,2,1)),總共有 11 個 weights 且 loss function 為 MSE , 若用 gradient descent ,就要把 MSE 對 11個 weights 個別做偏微分。
若想加速運算,可以使用 back-propagation …
這次的實作參考的文章 : 專為程式人寫的神經網路導論 (以反傳遞演算法為入門磚)
將要偏微分的函式分別 "拆分成一個個的運算單元",然後再從結果開始算梯度逆推回去
舉例 :
將 x 拆分成 x、x1、x2 後,再從結果逆推得到各個變數的梯度(d->c->a、b)
然後利用剛剛得到的梯度再乘上 learning rate 來更新權重。
回到題目,
本次要更新的權重有 11 個,所以在算 bp 的時候,只要算這 11 個和 output 的梯度再不斷的更新權重就可以了。
初步結果 :
網路架構如同此次題目 ann_init(3,1,2,1)
input = { 1 , 0 , 1 } , output = 1.0 , train 50 次
w11 是 output , learning rate = 0.01,可以看到經過 R1 後 output 從 0.48 變成 0.49 ,更接近 output(=1) 了,不過 learning rate 太小所以更新的幅度就會比較小,
透過調整 lr 成 0.1 :
很明顯地更接近 output 了
但是!!
如果將 input 多組放入神經網路用上述的方式 training 會很難 train
train 完 10000個 epoch 值依然還差很遠…
後來我試著把此神經網路當作分類問題, output 因為 sigmoid function 的關係值域在 0 到 1 之間,我讓 output >=0.5 就變成 1 反之變成 0。
雖然過了 1000 epoch 還是沒能訓練完畢,感覺繼續訓練下去就能 train 出來了
實作結果 :
當初以為再多幾個 epoch 訓練就能得到正確解答了。不過,事情出乎我意料之外,我就算訓練 40 萬個 epoch 也訓練不出正確解答…
可能的原因 :
在 training 的過程中每組 input 得到的 output 值都很接近,但是每組 input 對應的答案卻是截然不同的 (0 和 1),當某個 predict 錯誤而導致梯度算出來後正準備要更新 weight 的時候,其他的 weight 也會因為這個 gradient 變動而跟著更新,這也導致有些原本是對的 output 因為別組答錯,它也更著被更新,原本就答對了,更新完後又錯了,一直這樣循環下去,所以才遲遲訓練不出精準的答案
程式碼如下 :
https://github.com/jeremy3951/NeronNetwork/blob/main/bp-2
流程說明 :
check :
理解到本題是分類問題,用 MSE 來當 loss function 不適合
为什么均方差(MSE)不适合分类问题?
解釋 : 梯度會互相消掉 ! !
用 binary cross entropy 當 loss function 就沒事了嗎?
神經網路(3,1,2,1)本身有什麼問題? ( underfitting )
零基礎自學深度學習 :(二)優化神經網路
加深網路就好了嗎 ? (梯度消失問題)
回到前面的疑問,到底是網路深度問題還是 loss function 的問題?
若 loss function 實作沒問題的話,那這題就不是 loss function 選用的問題
實作觀察到的點 :
如果把資料改成5筆以下就可以 train 得出來 (underfitting)
延伸問題 :
3. 引入其他激勵函數,例如 ReLU,並確保針對其特性進行適度初始化
若將原本的程式碼的 sigmoid function 直接改成 ReLU 執行所需要的迴圈數會變得非常多 …
觀察這兩個 function,
solve function 回傳 bool 值
solveSudoku 不回傳值,且在最後一行呼叫 solve
由上述觀察可得知 solve 會一直被呼叫,而 solveSudoku 執行完就結束了
為了先了解該程式的架構,所以先從 solveSudoku 開始解讀 !
先看第一個迴圈 :
還有定義 :
ALL_NUMS 裡面裝著 9 個 1 並且分別賦值給 row 、 col 、 box
第一個二層迴圈 :
觀察這個迴圈裡面的操作,第 4、5、6 行一起看 :
board 裡面的字元減掉 '1' 得到這個字元所代表的數字,然後將這個數字用 bitwise 的方式來表示 (9個都是1的bits中,把該數字的位置改成0)
然後將此 k 值分別寫入 row 、 col 、 box 中
執行完第一個二層迴圈後可以知道每個 row、column、box 裡面有什麼數字 !
接下來觀察第二個二層迴圈 :
由第 3 、 4 行可以知道這個二層迴圈是針對空白的格子。
接下來的第 5 行用畫圖比較好理解,
. | 1 | 2 |
---|---|---|
3 | 4 | 5 |
7 | 8 | 9 |
假設 (x,y)=(0,0)
mask = row[0]&col[0]&box[0]
此行蒐集了 row[0]、col[0]、box[0] 裡面的所有數字,也就是說 mask 裡面的數字是空格不能填的數字!
接下來先跳過 if 的條件敘述看裡面的程式行為 :
第 7 行的 n 裡面裝的是 空格可以填的最小數字!
第 8、9 行則是把最小數字填進去 board 裡面
第 10 行則是把這個最小數字寫入 row、col、box 裡面
後來回頭想了一下,如果照我上面說的,每個空格都用可以填的最小數字來填,那這個數獨填到一半就填不下去了!
後來我發現,數獨只有一種狀況不用分析就可以直接填數字進去,那就是只剩一個數字還沒填的時候! 為了方便我簡稱為 trivial 數
已知 mask 裡面放著空格不能填的數字,如果這個空格有 trvial 數,則 mask 裡面的 bits 只有一個 bit 是 1 !
所以 if 裡面的 MMM 應該要填 ( e ) mask - 1
(選項 b 在 mask = 00…01 的時候會出錯)
認知修正 : 前面對第 7~9 行的理解不準確應修改為以下
第 7 行的 n 裡面裝的是 trvial 數
第 8、9 行則是把 trvial 數 填進去 board 裡面
第 10 行則是把 trvial 數 寫入 row、col、box 裡面
所以第二個二層迴圈用途是把 trivial 數給填上去而已
在填完所有 trivial 數就要真正解 board 了 (solve function)
solve 的引數有 :
board : input
masks_t : 每(行、列、box)裡面已有的數字,用 bitwise 的方式表示,0代表空格不能填的數字,1代表空格可以填的數字
第 9 行的 posmoves 裡面裝著該空格可以填的數字
由第 10、20、21 行的行為(當 posmoves == 0 時,把該空格歸零,並且回傳錯誤)可以得知當 posmoves == 0 時, 一定有其他格子填錯!
第 11、12、13 這個形式前面有分析過了,其程式行為就是 "把能填的數字中最小的數字填進 board 中"
第 14 行把已填的數字放進 board 中
第 15、16 行繼續迭代下去來看看這次的數字是否填錯
第 17 行代表該數字填錯了,把 m 還原成填此數字前的狀態
第 18 行,如果執行到此行,代表當下的填字造成後面填字錯誤
舉例 : (狀態為填完2後準備往下填)(填字順序為 2->3->4->5->6)
. | 6(guess) | 5(g) | 4(g) | 3(g) | 2(g) | 7(input) | 8(in) | 9(in) |
---|---|---|---|---|---|---|---|---|
1(input) |
空格(0,0) ,此時 posmoves = 000000000 ,
posmoves &= posmoves-1
, posmoves = 0, return false
-> 空格 (0,1) ,此時 posmoves = 000100000 ,
posmoves &= posmoves-1
, posmoves = 0 , return false
-> 空格 (0,2) ,此時 posmoves = 000110000 ,
posmoves &= posmoves-1
, posmoves = 000100000 , posmoves != 0 , 換個值填入繼續迭代下去 !
此時狀態為 :
. | . | 6(new) | 4(g) | 3(g) | 2(g) | 7(input) | 8(in) | 9(in) |
---|---|---|---|---|---|---|---|---|
1(input) |
然後繼續迭代到解出來為止…
缺失 :
改進 :
2.重寫不同於上述的程式碼,效能表現應優於上述
原本的程式碼 submit 結果 :