2022年4月4日 星期一

機器學習筆記 : 深度學習的 16 堂課 (九)

 本篇繼續整理 "深度學習的16堂課" 這本書的讀後筆記, 本系列之前的文章參考 : 

  1. 激活函數 sigmoid 在z=wx+b 過大或過小時會出現輸出值飽和現象, 神經元宛如死掉一樣無反應, 使訓練的結果變得很差. 前人經驗顯示, 若以妥善的方法設定權重初始值, 則神經元飽和的可能性會降低. 故權重初始值的設定對模型的訓練成效有重要的影響. 

  2. tf.keras 預設會自動以隨機值來指定 (偏值 b=0, 權重 w=Glorot 分布), 但也可以在新增 Dense 隱藏層時以下列兩個參數自行指定 :
    (1). kernel_initializer : 指定權重參數 w 初始值
    (2). bias_initializer : 指定偏值參數 b 初始值
    tf.keras.initializer 模組中內建了 Zeros(), RandomNormal(), glorot_normal(), 與 glorot_uniform() 等函數可用來設定權重參數, 匯入方式如下 :
    from tensorflow.keras.initializers import Zeros, RandomNormal, glorot_normal, glorot_uniform   
    其中 :
    Zeros() : 與 Numpy 的 zeros() 一樣會將參數值全設為 0, 用來設定 b 參數
    RandomNormal([stddev=1]) : 將參數設為平均值=0, 標準差為 1 之標準常態分布
    glorot_normal() : 將參數設為 Glorot 常態分布
    glorot_uniform() : 將參數設為 Glorot 均勻分布
    Glorot 分布是 Xavier Glorot 與 Yushua Bengio 專為 w 參數權重初始化而設計的一種機率分布, 目的是希望神經元輸出的激活值不要太極端. Glorot 常態分布是標準常態分布之變體, 其標準差由前後層神經元數量決定, 比標準常態分布要小.
    經實驗比較, 標準常態分布來設定權重初始值, 不論搭配哪種激活函數所產生的激活值都比較極端, 還是用 Glorot 分布較好, 採用 Glorot 常態分布與均勻分布效果差異不大. 參考 :
    Xavier Glorot参数初始化: 理解训练Deep DNN的难点

  3. 下面是將 w 參數設為標準常態分布來訓練第五章的 MNIST 感知器的結果, 主要的差別是匯入模組與模型建構方式.

    (1). 匯入模組與函數 :
    from tensorflow.keras.datasets import mnist     
    from tensorflow.keras.models import Sequential     
    from tensorflow.keras.layers import Dense, Activation   #新增(Activation)
    from tensorflow.keras import optimizers    
    from tensorflow.keras.utils import plot_model    
    from tensorflow.keras.utils import to_categorical    
    import matplotlib.pyplot as plt    
    import numpy as np       
    from tensorflow.keras.initializers import Zeros, RandomNormal   #新增

    (2). 載入 MNIST 資料集 :
    (X_train, y_train), (X_test, y_test)=mnist.load_data()

    (3). 資料預處理 :
    X_train=X_train.reshape(60000, 784).astype('float32')    # 2D 資料展平為 1D
    X_test=X_test.reshape(10000, 784).astype('float32')         # 2D 資料展平為 1D
    X_train /= 255              # 正規化
    X_test /= 255                # 正規化

    (4). 標籤預處理 :
    y_train=to_categorical(y_train, 10)     # 訓練集標籤 one-hot 編碼
    y_test=to_categorical(y_test, 10)          # 測試集標籤 one-hot 編碼

    (5). 建構神經網路 :
    上將中間密集層改用 256 個神經元, 為了與第五章比較, 我改回 64 個神經元. 其次是第五章使用 input_shape 參數, 其值為元組 (784, 0), 此處改用 input_dim, 其值是整數 784, 效果是一樣的. 其三, 為了後續修改程式方便, 中間層的激活函數不在 Dense() 中以 activation 參數設定, 改為從 tf.keras.layers 中匯入 Activation() 函數來設定
    b_init=Zeros()                      #將b參數初始值設為0
    w_init=RandomNormal()    #將w參數初始值設為標準常態分布取樣
    model=Sequential()   
    model.add(Dense(64,      
                                  input_dim=784,      
                                  kernel_initializer=w_init,    
                                  bias_initializer=b_init))     #自訂權重與偏差參數初始值 
    model.add(Activation('sigmoid')                 #添加激活函數
    model.add(Dense(10, activation='softmax'))    

    (6). 編譯模型 :
    model.compile(loss='mean_squared_error',    
                            optimizer=optimizers.SGD(learning_rate=0.01),      
                            metrics='accuracy') 

    (7). 訓練模型 :
    model.fit(X_train, y_train, batch_size=128, epochs=200, verbose=1,
                    validation_data=(X_test, y_test))    結果如下 :


    85.99% 比預設初始值的 86.4% 稍微遜色. 以上測試的 .ipynb 檔參考 :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_1_mnist.ipynb

  4. 將 w 初始值改用 Glorot 常態分布來取樣設定, 則匯入部分最後一列改為 :
    from tensorflow.keras.initializers import Zeros, glorot_normal   
    w 權重初始值設定改為 : 
    w_init=glorot_normal()    #將w參數初始值設為 Glorot 常態分布取樣
    其餘程式碼不變重新訓練, 結果如下 : 



    結果 86.67% 比用標準常態分布 85.99% 好. 完整原始碼參考 GitHub :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_2_mnist.ipynb

  5. 將 w 初始值改用 Glorot 均勻分布來取樣設定, 則匯入部分最後一列改為 :
    from tensorflow.keras.initializers import Zeros, glorot_uniform   
    w 權重初始值設定改為 : 
    w_init=glorot_uniform()    #將w參數初始值設為 Glorot 均勻分布取樣
    其餘程式碼不變重新訓練, 結果如下 : 


    86.41% 比 Glorot 常態分布稍遜, 但比標準常態的 85.99% 好, 原始碼參考 GitHub :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_3_mnist.ipynb

  6. 接下來回到上面使用 Glorot 常態分布, 將中間層神經元從 64 改為 256, 其餘不變, 看看神經元增加對訓練是否有幫助 :
    w_init=glorot_normal()    #將w參數初始值設為 Glorot 常態分布取樣
    model.add(Dense(256     
                                  input_dim=784,      
                                  kernel_initializer=w_init,    
                                  bias_initializer=b_init))     #自訂權重與偏差參數初始值 
    結果如下 :


    結果來到 88.01%, 比同樣是 Glorot 常態但只有 64 個神經元的 86.67% 高. 原始碼參考 :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_4_mnist.ipynb

  7. 訓練神經網路時, 除了權重參數初始值設定的問題外, 還有梯度不穩定 (unstable gradient) 問題, 這可分為下列兩種 :

    (1). 梯度消失 (vanishing gradient) :
    在利用損失值計算反向傳播時, 離輸出層最接近的隱藏層參數對損失值的影響最大, 離輸出層越遠的層, 該層參數對損失值的影響力越會被稀釋, 亦即, 當損失值從最後一層逐層反向傳播到第一個隱藏層時, 損失值對權重參數的梯度越算越小逐漸消失 (趨近於 0), 稱為梯度消失現象. 因此離輸出層最遠的那幾層 (或開頭的那幾層) 會因為梯度消失而學習停滯, 使模型的學習能力無法提升. 由於 sigmoid 的梯度值為 0~0.25 之間, 若中間層過多例如 20 層, 就算每層 sigmoid 梯度為最大值 0.25, 則 0.25**20=1e-12, 已經趨近於 0 了. 
    梯度消失通常來自神經網路層數太多, 再加上中間層使用了 sigmoid 激活函數所致

    (2). 梯度爆炸 (exploding gradient) :
    與梯度消失相反的是, 從最末端往前做損失值的反向傳播計算時, 損失函數對越靠近輸入層的權重參數求出的偏導數越來越大, 同樣不利於神經網路之訓練. 這種情形比起梯度消失較少見 (例如在 RNN 神經網路). 

  8. 解決梯度不穩定問題可使用批次正規化 (batch normalization), 這是一種重訂比例 (rescale) 的方法, 也稱為特徵縮放 (feature scaling), 做法是將每層的激活值減掉各訓練批次所有資料的平均值, 再除以該批資料的標準差, 這樣就可以讓激活值轉成平均值為 0, 標準差為 1 的標準常態分布, 使特徵具有相同的計量標準. 

  9. 訓練神經網路是不只是希望能訓練出可解釋 x 與 y 關聯的模型, 而是希望還原背後母體的潛在分布, 這樣模型不僅能解釋已知的資料點關係, 也能預測從同樣母體分布採樣的新資料點. 若模型只能解釋已知的資料點, 卻無法預測從未見過的新資料, 則此模型有 "過度配適 (over-fitting)" 問題, 若訓練的樣本不足, 而模型設計得太複雜 (參數太多) 就可能出現 over-fitting 現象. 抑制 over-fitting 常使用下列方法 :
    (1). L1 常規化 (regularization) : 又稱 LASSO 迴歸法
    (2). L2 常規化 (regularization) : 又稱 ridge 迴歸法
    (3). 丟棄法 (dropout)
    (4). 資料擴增 (data augmentation)
    這兩種方法都是在損失函數中添加懲罰項 (penalty term), 用來懲罰模型參數之過度擴張. 在訓練網路時, 若參數值越大就給予越大的懲罰, 使其往參數值較小的方向優化. L1 的懲罰項為權重取絕對值之總和; 而 L2 的懲罰項為權重的平方和. 
    丟棄法為深度學習之父 Geofrrey Hinton 等人所提出用來抑制 over-fitting 的方法, 被首次使用於奪得 2012 年 ILSVRC 競賽的 AlexNet 上, 這也是業內最常用的方法. 
    資料擴增是以現有資料來生成大量人造資料, 藉以彌補因資料不足而造成之 over-fitting 問題, 以圖片資料為例, 可透過影像平移, 扭曲, 模糊, 旋轉等處理來產生新資料. 

  10. Dropout 的概念是在每一批次 (batch) 的訓練中, 隨機將該層一定比例的神經元設為 0, 亦即無視於這些神經元的結果, 好像它們不存在一樣. 丟棄法之所以能發揮抑制 over-fitting 作用, 原理在於神經網路會傾向於以極端的邏輯來詮釋訓練資料集, 導致演化出一個只依賴少許神經元組成的前向傳播路徑, 而 dropout 的方法會在這條路徑形成之前因為部分神經元被隨機丟棄而瓦解, 這樣模型便不會過度依賴某幾種特徵來預測結果. 

  11. Dropout 的比率也是超參數, 通常需要參考經驗來設定 :
    (1). 不需要在每一個隱藏層後面加 dropout, 對於稍微深層的網路只要在後面幾層加 dropout 就可以了, 因為前面那幾層也許捕捉不到甚麼特徵. 先在最後一個隱藏層加 dropout, 看看是否能有效抑制 over-fitting, 否則再往前一層加 dropout.
    (2). 如果只加了一點 dropout 就讓驗證損失爆增, 表示 dropout 破壞力太強, 應該拿掉. 
    (3). 哪一層該丟棄多少神經元, 每個神經網路的比例都不同, 需反覆測試. 經驗顯示, 機器視覺應用的 dropout 設 20~50% 可將驗證準確率提到最高; 而自然語言處理應用方面可調小一點, 最佳設定為 20%~30%. 

  12. 優化器的種類 :
    (1). SGD
    (2). 動量 (momentum) : 
    在權重修正公式中添加上一次的權重修正量, 此修正量會先乘以一個超參數 beta (0~1, 一般設為 9) 用來控制前一次的梯度變化對參數之影響力. 
    (3). Nestero 動量 : 
    此法是先預測參數會被動量影響至何處, 亦即估計影響之趨勢點, 以趨勢點為目標求出梯度,再用梯度來調整參數. 
    (4). AdaGrad (Adaptive Grdient) :
    以上兩種動量所用之學習率是固定套用到所有參數的學習上, 而 AdaGrad 則是每個參數都有其獨立之學習率, 已完成優化的參數就停止學習, 否則就繼續學習. 使用 AdaGrad 最大的好處是不用去調整學習率 (它會自動調整各參數學習率), 但缺點是學習率會學著訓練進行而被稀釋, 最終因為值太小而停止學習. 
    (5). AdaDelta :
    為了克服 AdaGrad 的缺點, AdaDelta 完全取消學習率, 但多了一個衰減率超參數 rho (意義與動量之 beta 參數差不多, 建議設為 0.95). 不過 tf.keras 的 AdaGrad 仍保留了學習率參數, 使用時要將其設為 1. 
    (6). RMSprop : 
    此優化器為 Geoffrey Hinton 所提出, 原理與 AdaDelta 類似, 也有一個衰減率超參數 rho, 學習率建議設為 0.001. 
    (7). Adam :
    Adam 集前面所有優化器之大成, 是最常用的優化器, 其原理與 RMSprop 類似, 它有兩個超參數 beta1 (建議設為0.9) 與 beta2 (建議設為 0.99), 學習率建議設為 0.001. 
  13. 以 tf.keras 實作加入 dropout 與 Batch Normalization 之深度神經網路 :
    以下是在第五章 MNIST 資料集測試範例基礎上增加兩層隱藏層, 且都使用批次正規化, 最後一個隱藏層添加丟棄層 :

    (1). 匯入模組 :
    from tensorflow.keras.datasets import mnist     
    from tensorflow.keras.models import Sequential   
    from tensorflow.keras.layers import Dense  
    from tensorflow.keras.layers import Dropout #新增
    from tensorflow.keras.layers import BatchNormalization #新增          
    from tensorflow.keras.utils import to_categorical  
    from tensorflow.keras.optimizers import SGD

    (2). 載入 MNIST 資料集 :
    (X_train, y_train), (X_test, y_test)=mnist.load_data()

    (3). 資料預處理 :
    X_train=X_train.reshape(60000784).astype('float32'
    X_test=X_test.reshape(10000784).astype('float32'
    X_train /= 255 
    X_test /= 255 
     
    (4). 標籤預處理 :
    y_train=to_categorical(y_train, 10
    y_test=to_categorical(y_test, 10

    (5). 建構神經網路 :
    model = Sequential()
    #輸入層+第一隱藏層
    model.add(Dense(64, activation='relu', input_shape=(784,)))
    model.add(BatchNormalization())
    #第二隱藏層
    model.add(Dense(64, activation='relu'))
    model.add(BatchNormalization())
    #第三隱藏層
    model.add(Dense(64, activation='relu'))
    model.add(BatchNormalization()) #批次正規化
    model.add(Dropout(0.2)) #每批次隨機丟棄此層 20% 神經元
    #輸出層
    model.add(Dense(10, activation='softmax')) 檢視模型摘要 :
    model.summary()
    
    

    (6). 編譯模型 :
    model.compile(loss='categorical_crossentropy'optimizer='adam',  metrics=['accuracy'])
    注意, 此處與第五章不同之處為損失函數改用交叉熵而非 MSE, 且優化器改用 Adam 而非 SGD. 

    (7). 訓練模型 :
    model.fit(X_train, y_train, batch_size=128, epochs=20, verbose=1,  validation_data=(X_test, y_test))


    第一輪驗證就上 95.3%, 最終達 97.5%, 效果很不錯. 以上演練筆記本參考 :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_deep_mnist.ipynb

  14. 迴歸問題 : 以波士頓房價預測為例 
    之前的範例皆是分類問題, 此處改做迴歸問題, 以波士頓房價資料集為例 :

    (1). 匯入模組 :
    from tensorflow.keras.datasets import boston_housing
    from tensorflow.keras.models import Sequential   
    from tensorflow.keras.layers import Dense  
    from tensorflow.keras.layers import Dropout 
    from tensorflow.keras.layers import BatchNormalization 
    import numpy as np

    (2). 載入 boston_housing 資料集 :
    (X_train, y_train), (X_test, y_test)=boston_housing.load_data()

    (3). 檢視資料 :
    檢視資料集的形狀 :
    X_train.shape => (404, 13)
    X_test.shape => (102, 13)
    y_train.shape => (404,)
    y_test.shape => (102,)
    X_train[0] => array([ 1.23247, 0. , 8.14 , 0. , 0.538 , 6.142 , 91.7 , 3.9769 , 4. , 307. , 21. , 396.9 , 18.72 ])
    y_train[0] => 15.2
    (4). 建立神經網路 :
    model = Sequential()
    #輸入層+第一隱藏層
    model.add(Dense(32, activation='relu', input_dim=13))
    model.add(BatchNormalization())
    #第二隱藏層
    model.add(Dense(16, activation='relu'))
    model.add(BatchNormalization())
    model.add(Dropout(0.2)) #每批次隨機丟棄此層 20% 神經元
    #輸出層
    model.add(Dense(1activation='linear'))
    此處因是預測連續值, 故輸出層只有一個神經元, 使用 linear 激活函數. (5). 檢視模型摘要 :
    model.summary()


    (6). 編譯網路 :
    model.compile(loss='mean_squared_error', optimizer='adam')
    此處因為是迴歸問題, 故不適用交叉熵, 應該用 MSE. 

    (7). 訓練網路 :
    model.fit(X_train, y_train, batch_size=8, epochs=32, verbose=1,  validation_data=(X_test, y_test))


    最後驗證損失是 33, 似乎還不是很理想, 需要反覆調校超參數以達到較佳結果. 

    (7). 以模型進行預測 :
    model.predict(np.reshape(X_test[42], [113])) => array([[17.234053]], dtype=float32)
    此處是用第 42 筆樣本的 13 個預測變量, 預測前須先將其形狀由 (13,) 改成 (1, 13), 結果預測價格是 17234 美元. 

    (8). 檢視實際房價 :
    y_test[42] => 14.1

    實際房價是 14100 美元, 此處預測結果 17234 比書上的結果 24171 元還準. 
    以上演練之筆記本參考 :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_deep_boston.ipynb

  15. 章末介紹了 TensorBoard 用法, 它可以將訓練結果與神經網路以視覺化方式呈現. 以上面這個 Boston 房價的迴歸預測問題為例, 只要在匯入模組時從 tf.keras.callbacks 匯入一個 TensorBoard 類別 :

    from tensorflow.keras.callbacks import TensorBoard #新增 

    然後在訓練之前選定一個 Google Drive 資料夾當紀錄檔目錄來建立 TensorBoard 物件 :

    tensorboard=TensorBoard(log_dir='/content/drive/MyDrive/Colab Notebooks')

    最後在訓練指令中將此物件傳給 callbacks 參數 :

    model.fit(X_train, y_train, batch_size=8, epochs=32, verbose=1
              validation_data=(X_test, y_test), callbacks=[tensorboard])

    其餘程式碼都一樣. 訓練完成後, 輸入下列指令就會用 TensorBoard 以視覺化方式繪製損失隨訓練週期下降的互動圖形 :

    %load_ext tensorboard
    %tensorboard --logdir '/content/drive/MyDrive/Colab Notebooks'


    以上演練之筆記本參考 :
    https://github.com/tony1966/colab/blob/main/deep_learning_illustrated_ch9_deep_boston_tensorboard.ipynb

沒有留言 :