2022年4月27日 星期三

機器學習筆記 : 強化式學習-打造最強通用演算法 (三C)

本篇繼續來整理我讀布留川英一寫的 "強化式學習-打造最強通用演算法 AlphaZero" 這本書的摘要筆記, 本系列之前的筆記參考 : 


以下是我讀完第三章 "深度學習" 第三個演練範例 (卷積神經網路 CNN) 的摘要筆記, 此例以 CIFAR-10 影像資料集為來源訓練由兩個卷積塊與一個密集層組成的 CNN 網路, 目標是要能用訓練集資料訓練得到的模型來預測測試集裡面的圖片是飛機, 汽車, 鳥, ... 等 10 種分類中的哪一類. 

卷積神經網路是在密集神經網路結構上添加用來萃取特徵的卷積塊 (convolutional block), 疊加的卷積層能從圖片中學習到更多資訊進而提升辨識準確度. 本例所使用之卷積神經網路結構如下 : 


卷積塊基本上是卷積層與池化層的組合 (也可以包含丟棄層), 層數與組合方式可自訂, 例如可用 "卷積+卷積 +池化" 或 "卷積 +池化+卷積+池化" 等. 卷積層會配置多個卷積核, 通常是 3x3 或 5x5 方陣, 其內容即為卷積層的權重參數, 卷積核被用來掃描輸入圖片 (以指定步幅由左至右由上至下) 並進行卷積運算 (對應乘積和), 結果就是圖片的特徵圖 (feature map), 即該卷積核所萃取出來的細部特徵. 注意, 以 2D 陣列表示的圖片直接輸入卷積層, 不需要用 reshape() 拉平為向量, 因此能保持圖片中像素間的空間關係. 

卷積運算會使輸出的特徵圖尺寸比輸入圖片小, 為了讓特徵圖形狀不變, 通常會在輸入圖片外圍進行填補 (padding), 即上下左右都補 0, 因此一個 32*32 的圖片經過填補後變成 34*34 圖片 (最外層都是 0), 這樣經過卷積運算後得到的特徵圖仍為 32*32. 卷積網路的權重參數就是卷積核內的數值, 每一批次訓練中用來掃描圖片的各個卷積核都維持不變, 等下一批次時才透過優化器調整.  

池化層的作用是對卷積層輸出的特徵圖進形重點挑選, 它與卷積核類似的池化區方陣 (例如 3x3), 運作方式同樣是以指定步幅由左至右由上至下掃描特徵圖, 但池化區只做挑選運算從與期重疊的特徵圖中選值, 常用的是最大池化 (挑選區內最大值) 與平均池化 (挑選區內平均值). 注意, 池化區僅是一個篩子, 本身沒有權重參數, 其次, 池化會使特徵圖尺寸變小. 

卷積神經網路後半段與多層感知器使用的全連接密集網路相同, 因此最後一個卷積塊會先經過一個展平層將最後的 2D 特徵圖拉平為 1D 向量給密集層. 注意, 展平層也沒有權重參數. 以 CNN 網路辨識 CIFAR-10 資料集圖片的演練如下 : 
  1. 設定 Matplotlib 繪圖內嵌 :
    %matplotlib inline

  2. 匯入套件模組 :
    from tensorflow.keras.datasets import cifar10
    from tensorflow.keras.layers import Activation, Dense, Dropout
    from tensorflow.keras.layers import Conv2DMaxPool2D, Flatten
    from tensorflow.keras.models import Sequential, load_model
    from tensorflow.keras.optimizers import Adam
    from tensorflow.keras.utils import to_categorical
    import numpy as np
    import matplotlib.pyplot as plt

  3. 載入資料集 :
    (train_images, train_labels), (test_images, test_labels)= cifar10.load_data()
     

    載入的 cifar10 影像資料集包含 60000 張 RGB 全彩圖片, 每張圖片尺寸為 32x32, 每張圖片都有一個 0~9 的數字標籤 (答案), 代表十種圖片分類 :

     標籤 (label) 分類
     0 airplane (飛機)
     1 automobile (汽車)
     2 bird (鳥)
     3 cat (貓)
     4 deer (鹿)
     5 dog (狗)
     6 frog (青蛙)
     7 horse (馬)
     8 ship (船)
     9 truck (卡車)

  4. 檢視資料集 : 
    print(type(train_images)) # <class 'numpy.ndarray'>
    print(type(train_labels)) # <class 'numpy.ndarray'>
    print(type(test_images)) # <class 'numpy.ndarray'>
    print(type(test_labels)) # <class 'numpy.ndarray'>

    可見訓練集與測試集都是 Numpy 陣列, 檢視其形狀 :

    print(train_images.shape) # (50000, 32, 32, 3)
    print(train_labels.shape) # (50000, 1)
    print(test_images.shape) # (10000, 32, 32, 3)
    print(test_labels.shape) # (10000, 1)

    訓練集有 5 萬筆, 測試集有 1 萬筆, 合計 6 萬筆. 圖片尺寸是 32x32, 有 RGB 三個通道, 標籤則是 0~9 的一個數值, 代表圖片的分類. 用 Matplotlib 檢視訓練集的前 10 筆圖片 :

    for i in range(10):
        plt.subplot(25, i+1)
        plt.imshow(train_images[i])
    plt.show()


    檢視這十張圖片的標籤 (答案) 並對照上面的分類 :

    print(train_labels[0:10])
    [[6]     # 青蛙
     [9]     # 卡車
     [9]     # 卡車
     [4]     # 鹿
     [1]     # 汽車
     [1]     # 汽車
     [2]     # 鳥
     [7]     # 馬
     [8]     # 船
     [3]     # 貓
    ]

  5. 資料預處理 :
    圖片的 RGB 數值均為 0~255 的數值, 為了提升模型的訓練表現, 通常會先將資料先做正規化 (normaliztion) 處理後再餵給模型, 此處使用 Min-Max 正規化 (x-min)/(max-min), 其中 min=0, max-255, 亦即將所有值都除以最大值 255 即可 :

    print("轉換前")
    print(train_images[0][0][0])
    train_images=train_images.astype('float32')/255.0
    test_images=test_images.astype('float32')/255.0
    print("轉換後")
    print(train_images[0][0][0])

    結果如下 : 

    轉換前 [59 62 63]
    轉換後 [0.23137255 0.24313726 0.24705882]

    此處檢驗訓練集第一張圖片的第一個像素, 轉換後變成 0~1 的浮點數.

  6. 標籤預處理 :
    原始標籤是 0~9 的單一整數, 因為神經網路的輸出層是用 Softmax 激活函數輸出的 10 個機率值 (one-hot 編碼), 為了與此輸出匹配比較, 必須將原始標籤經 to_categorical() 處理轉成 one-hot 編碼 :

    print("轉換前")
    print(train_labels[0]) 
    train_labels=to_categorical(train_labels, 10
    test_labels=to_categorical(test_labels, 10)
    print("轉換後"
    print(train_labels [0])

    這裡以訓練集第一個標籤為例檢視轉換前後結果 :
    轉換前 [6]
    轉換後 [0. 0. 0. 0. 0. 0. 1. 0. 0. 0.]

  7. 建構 CNN 網路模型 :
    依照上面的 CNN 網路結構圖建立神經網路 : 

    model = Sequential()
    # 第一個卷積塊 
    model.add(Conv2D(32, (33), activation='relu'
            padding='same', input_shape=(32323)))
    model.add(Conv2D(32, (33), activation='relu', padding='same'))
    model.add(MaxPool2D(pool_size=(22)))
    model.add(Dropout(0.25))
    # 第二個卷積塊
    model.add(Conv2D(64, (33), activation='relu', padding='same'))
    model.add(Conv2D(64, (33), activation='relu', padding='same'))
    model.add(MaxPool2D(pool_size=(22)))
    model.add(Dropout(0.25))
    # 密集層
    model.add(Flatten())
    model.add(Dense(512, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(10, activation='softmax'))

    此處卷積塊裡 Conv2D() 的前兩個參數 32, (3, 3) 表示使用 32 個 3x3 的卷積核, padding='same' 表示要添加填補使輸出圖片尺寸與輸入相同, 都是 32x32. 可見每個卷積塊最後都隨機丟棄 25% 神經元, 而密集層最後面則隨機丟棄 50% 神經元. 

  8. 編譯模型與檢視模型摘要 :
    model.compile(loss='categorical_crossentropy'
          optimizer=Adam(learning_rate=0.001), metrics=['acc'])
    model.summary()



  9. 訓練模型 :
    因為計算量大, 故執行前請點選 "執行階段/變更執行類型" 選擇使用 GPU 來執行 :

    history=model.fit(train_images, train_labels, batch_size=128,
        epochs=20, validation_split=0.1)


    可見最終第 20 輪時達到 79.16% 準確率. 

  10. 繪製訓練過程 :
    plt.plot(history.history['acc'], label='acc')
    plt.plot(history.history['val_acc'], label='val_acc')
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(loc='best')
    plt.show()


  11. 用測試集來評估模型表現 :
    test_loss, test_acc=model.evaluate(test_images, test_labels)
    print('loss: {:.3f}\nacc: {:.3f}'.format(test_loss, test_acc ))

    313/313 [==============================] - 2s 7ms/step - loss: 0.6860 - acc: 0.7827 loss: 0.686 acc: 0.783   

    準確率是 78.3%, 與訓練集結果差不多. 

  12. 比較測試集前十筆標籤與模型預測結果 :

    # 將測試集前十筆標籤從 ONE-HOT 編碼轉回單一數字
    test_ans=np.argmax(test_labels[:10], axis=1)
    print(test_ans)
    # 以模型預測前十筆測試集圖片
    test_predictions=model.predict(test_images[0:10])
    test_predictions=np.argmax(test_predictions, axis=1)
    print(test_predictions)

    結果完全正確 (書上有一個預測錯誤) : 
    [3 8 8 0 6 6 1 6 3 1]    # 標籤
    [3 8 8 0 6 6 1 6 3 1]    # 預測

  13. 將前十筆測試集標籤與預測結果轉成分類 :
    labels=['airplane''automobile''bird''cat''deer',
            'dog''frog''horse''ship''truck']
    print('前 10 筆預測標籤:',[labels[n] for n in test_predictions])
    test_ans=np.argmax(test_labels[:10], axis=1)
    print('前 10 筆原始標籤:',[labels[n] for n in test_ans])

    前 10 筆預測標籤: ['cat', 'ship', 'ship', 'airplane', 'frog', 'frog', 'automobile', 'frog', 'cat', 'automobile']
    前 10 筆原始標籤: ['cat', 'ship', 'ship', 'airplane', 'frog', 'frog', 'automobile', 'frog', 'cat', 'automobile']

    可見前十筆預測結果完全正確. 

  14. 繪製測試集前十張圖片 :
    for i in range(10):
        plt.subplot(25, i+1)
        plt.imshow(test_images[i])
    plt.show()


    以上演練筆記本參考 :
    https://github.com/tony1966/colab/blob/main/reinforcement_learning_ch3_cifar10.ipynb
    此書作者的筆記本參考 :
    https://github.com/tony1966/colab/blob/main/3_2_convolution.ipynb
 

沒有留言 :