2019年2月11日 星期一

深度學習 - Functional API - Inception & Residual connection

先前練習過多輸入&多輸出的 Functional API,接著來練習由層組成的有向無環的類圖形神經網絡。
  • Inception module :
    Google 的 Christian Szegedy 與其團隊所開發的網絡架構。Inception 模塊最基本的形式包含3~4 個分支,Inception 的進化可以參考博客Bharath Raj 的簡單介紹,下圖為 Inception V3示意圖。
    Christian Szegedy, Wei Liu,. et al. "Going Deeper with Convolutions"
    #分支a
    branch_a = layers.Conv2D(128, 1,activation='relu', strides=2)(x)

    #分支b
    branch_b = layers.Conv2D(128, 1, activation='relu')(x)
    branch_b = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_b)

    #分支c
    branch_c = layers.AveragePooling2D(3, strides=2)(x)
    branch_c = layers.Conv2D(128, 3, activation='relu')(branch_c)

    #分支d
    branch_d = layers.Conv2D(128, 1, activation='relu')(x)
    branch_d = layers.Conv2D(128, 3, activation='relu')(branch_d)
    branch_d = layers.Conv2D(128, 3, activation='relu', strides=2)(branch_d)

    #將分支輸出連接在一起,得到模塊輸出
    output = layers.concatenate([branch_a, branch_b, branch_c, branch_d], axis=-1)


    使用Inception module 來測試在Kaggle - Cat & Dog 上的效能
    修改改程式碼:
    #建立模型網絡
    from keras.models import Model
    from keras import layers, Input
    x = Input( shape =(150,150,3),dtype='float32')
    #分支a
    branch_a = layers.Conv2D(32, 1,activation='relu', strides=2)(x)
    branch_a = layers.Flatten()(branch_a)
    branch_a = layers.Dense(256, activation='relu')(branch_a)
    #分支b
    branch_b = layers.Conv2D(32, 1, activation='relu')(x)
    branch_b = layers.Conv2D(32, 3, activation='relu', strides=2)(branch_b)
    branch_b = layers.Flatten()(branch_b)
    branch_b = layers.Dense(256, activation='relu')(branch_b)
    #分支c
    branch_c = layers.AveragePooling2D(3, strides=2)(x)
    branch_c = layers.Conv2D(32, 3, activation='relu')(branch_c)
    branch_c = layers.Flatten()(branch_c)
    branch_c = layers.Dense(256, activation='relu')(branch_c)
    #分支d
    branch_d = layers.Conv2D(32, 1, activation='relu')(x)
    branch_d = layers.Conv2D(32, 3, activation='relu')(branch_d)
    branch_d = layers.Conv2D(32, 3, activation='relu', strides=2)(branch_d)
    branch_d = layers.Flatten()(branch_d)
    branch_d = layers.Dense(256, activation='relu')(branch_d)
    #將分支輸出連接在一起,得到模塊輸出
    output = layers.concatenate([branch_a, branch_b, branch_c, branch_d],axis=-1)
    #將輸出做加入 sigmoid 分類
    output = layers.Dense(1, activation='sigmoid')(output)
    model = Model(x,output)

    非常遺憾,記憶體不夠,無法增加網絡的特徵維度,因此看不出效能。
    在此先做一個練習,知道如何使用就好...。


  • Residual Connection (殘差連接)
    為 Microsoft 的 Kaiming He 與其團隊在 ILSVRC2015 競賽中獲勝所使用的方法。
    在大規模深度學習的模型中(幾十層layer以上),通常會遇到兩個問題,梯度消失和表示瓶頸。
    梯度消失,在循環神經網絡(RNN)中已經提過,而LSTM則是解決此問題的優化模型,在大規模神經網絡,利用同一概念,允許過去的信息稍後重新進入,從而解決梯度消失問題。將一個純線性的信息攜帶軌道,與主要的層堆疊方向平行,有助於跨越任意深度的層來傳播梯度,解決梯度消失的問題。
    表示瓶頸,在大規模深度學習模型中,只要有其中一層的特徵維度過小,則會導致訊息將永遠被截掉(Loss),就好比類比訊號經過一層層的濾波器(filter 3MHz~1GHz),而其中一個濾波器頻寬只有3MHz~300MHz ,那在這之後的濾波器都只能看到3~300MHz 的訊號,而後面的訊號都流失掉了。藉由殘差連接,將過去的特徵再重新輸入到後面的層,以解決表示瓶頸的問題。
    HE K, ZHANG X, REN S,. et al. "Deep residual learning for image recognition"

    from keras import layers,Input
    #如果特徵圖的尺寸相同,使用恆等殘差連接(identityresidual connection)
    x = Input(shape=(None,),)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.add([y, x])

    #如果特徵圖的尺寸不同,使用線性殘差連接(linear residual connection)
    x = Input(shape=(None,),)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.MaxPooling2D(2, strides=2)(y)
    residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)
    y = layers.add([y, residual])


    完整程式碼:
    #資料夾分類處理後的路徑
    original_dataset_dir ='C:\\Users\\Lido_Lee\\Downloads\\kaggle_original_data'
    base_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small'
    train_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\train'
    validation_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\validation'
    test_dir = 'C:\\Users\\Lido_Lee\\Downloads\\cats_and_dogs_small\\test'

    #使用ImageDataGenerator 數據增強
    from keras.preprocessing.image import ImageDataGenerator
    train_datagen = ImageDataGenerator(rescale=1./255,
                                       rotation_range=40,
                                       width_shift_range=0.2,
                                       height_shift_range=0.2,
                                       shear_range=0.2,
                                       zoom_range=0.2,
                                       horizontal_flip=True,
                                       fill_mode='nearest')

    #測試數據不可使用數據增強
    test_datagen = ImageDataGenerator(rescale=1./255)

    train_generator = train_datagen.flow_from_directory(train_dir,
                                                        target_size=(256, 256),
                                                        batch_size=20,
                                                        class_mode='binary')
    
    validation_generator = test_datagen.flow_from_directory(validation_dir,
                                                            target_size=(256, 256),
                                                            batch_size=20,
                                                            class_mode='binary')

    #建立模型網絡
    from keras.models import Model
    from keras import layers, Input
    x = Input( shape =(256,256,3),dtype='float32')
    #線性殘差連接
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(x)
    y = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    y = layers.MaxPooling2D(2, strides=2)(y)
    residual = layers.Conv2D(128, 1, strides=2, padding='same')(x)
    y = layers.add([y, residual])
    z = layers.Conv2D(128, 3, activation='relu', padding='same')(y)
    z = layers.Conv2D(128, 3, activation='relu', padding='same')(z)
    z = layers.MaxPooling2D(2, strides=2)(z)
    w = layers.Conv2D(128, 3, activation='relu', padding='same')(z)
    w = layers.Conv2D(128, 3, activation='relu', padding='same')(w)
    w = layers.MaxPooling2D(2, strides=2)(w)
    residual_w = layers.Conv2D(128, 1, strides=2, padding='same')(z)
    w = layers.add([w, residual_w])

    #將輸出做加入 sigmoid 分類
    w = layers.Flatten()(w)
    output = layers.Dense(256, activation='relu')(w)
    output = layers.Dense(1, activation='sigmoid')(output)
    model = Model(x,output)

    #選擇優化器RMSprop、損失函數binary_crossentropy、指標acc
    from keras import optimizers
    model.compile(loss='binary_crossentropy',
                  optimizer=optimizers.RMSprop(lr=1e-5),
                  metrics=['acc'])

    #將訓練&驗證資料丟進fit_generator訓練
    history = model.fit_generator(train_generator,
                                  steps_per_epoch=100,
                                  epochs=20,
                                  validation_data=validation_generator,
                                  validation_steps=50)

    test_generator = test_datagen.flow_from_directory(test_dir,
                                                      target_size=(256, 256),
                                                      batch_size=20,
                                                      class_mode='binary')

    #保存訓練模型
    model.save('cats_and_dogs_residual_exercise.h5')

    #在測試數據集上驗證
    test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
    print('test acc:', test_acc)

    #將訓練過程畫出來
    import matplotlib.pyplot as plt
    acc = history.history['acc']
    val_acc = history.history['val_acc']
    loss = history.history['loss']
    val_loss = history.history['val_loss']
    epochs = range(1, len(acc) + 1)
    plt.plot(epochs, acc, 'bo', label='Training acc')
    plt.plot(epochs, val_acc, 'b+', label='Validation acc')
    plt.title('Training and validation accuracy')
    plt.legend()
    plt.figure()
    plt.plot(epochs, loss, 'bo', label='Training loss')
    plt.plot(epochs, val_loss, 'b+', label='Validation loss')
    plt.title('Training and validation loss')
    plt.legend()
    plt.show()

    由於跑太久...只跑了20個 epochs ,可以看到還尚未發生過擬合,有空再來跑個100次看看...

沒有留言:

張貼留言