深度學習Tensorflow 2.4 完成遷移學習和模型微調

前言

本文使用 cpu 的 tensorflow 2.4 完成遷移學習和模型微調,並使用訓練好的模型完成貓狗圖片分類任務。

預訓練模型在 NLP 中最常見的可能就是 BERT 瞭,在 CV 中我們此次用到瞭 MobileNetV2 ,它也是一個輕量化預訓練模型,它已經經過大量的圖片分類任務的訓練,裡面保存瞭一個可以通用的去捕獲圖片特征的模型網絡結構,其可以通用地提取出圖片的有意義特征。這些特征捕獲功能可以輕松遷移到其他圖片任務上幫助其完成特征提取工作。

本文的工作,就是在 MobileNetV2 基礎上加入我們自定義的若幹網絡層,通過使用大量的貓狗數據先對新添加的分類器層進行遷移學習,然後結合基礎模型的最後幾層與我們的自定義分類器一起進行微調訓練,就可以輕松獲得一個效果很好的貓狗圖片分類模型,而不必基於大量數據集從頭開始訓練一個大型模型,這樣會很耗時間。

實現過程

1. 獲取數據

首先我們要使用 tensorflow 的內置函數,從網絡上下載貓狗圖片集,訓練數據中的貓、狗圖片各 1000 張,驗證數據中的貓、狗圖片各 500 張。每張圖片的大小都是(160, 160, 3),每個 batch 中有 32 張圖片。

import matplotlib.pyplot as plt
import numpy as np
import os
import tensorflow as tf
URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
BATCH_SIZE = 32
IMG_SIZE = (160, 160)
path_to_zip = tf.keras.utils.get_file('cats_and_dogs.zip', origin=URL, extract=True)
PATH = os.path.join(os.path.dirname(path_to_zip), 'cats_and_dogs_filtered')
train_dir = os.path.join(PATH, 'train')
validation_dir = os.path.join(PATH, 'validation')
train_dataset = tf.keras.utils.image_dataset_from_directory(train_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
validation_dataset = tf.keras.utils.image_dataset_from_directory(validation_dir, shuffle=True, batch_size=BATCH_SIZE, image_size=IMG_SIZE)
class_names = train_dataset.class_names

在這裡我們挑選瞭部分圖片和標簽進行展示。

plt.figure(figsize=(10, 10))
for images, labels in train_dataset.take(1):
    for i in range(3):
        ax = plt.subplot(3, 3, i + 1)
        plt.imshow(images[i].numpy().astype("uint8"))
        plt.title(class_names[labels[i]])
        plt.axis("off")     

2. 數據擴充與數據縮放

(1)由於我們沒有測試集數據,使用 tf.data.experimental.cardinality 確定驗證集中有多少個 batch 的數據,然後將其中的 20% 的 batch 編程測試集。

val_batches = tf.data.experimental.cardinality(validation_dataset)
test_dataset = validation_dataset.take(val_batches // 5)
validation_dataset = validation_dataset.skip(val_batches // 5)

(2)為瞭保證在加載數據的時候不會出現 I/O 不會阻塞,我們在從磁盤加載完數據之後,使用 cache 會將數據保存在內存中,確保在訓練模型過程中數據的獲取不會成為訓練速度的瓶頸。如果說要保存的數據量太大,可以使用 cache 創建磁盤緩存提高數據的讀取效率。另外我們還使用 prefetch 在訓練過程中可以並行執行數據的預獲取。

AUTOTUNE = tf.data.AUTOTUNE
train_dataset = train_dataset.prefetch(buffer_size=AUTOTUNE)
validation_dataset = validation_dataset.prefetch(buffer_size=AUTOTUNE)
test_dataset = test_dataset.prefetch(buffer_size=AUTOTUNE)

(3)因為我們沒有大量的圖片數據,我們將現有的圖片數據進行旋轉或者翻轉操作可以增加樣本的多樣性,這樣也有助於減少過擬合現象。RandomFlip 函數將根據 mode 屬性將圖片進行水平或垂直翻轉圖像。"可選的有 "horizontal" 、 "vertical" 或 "horizontal_and_vertical" 。"horizontal" 是左右翻轉, "vertical" 是上下翻轉。RandomRotation 函數通過設置 factor 來講圖片進行旋轉,假如 factor=0.2 會將圖片在 [-20% * 2pi, 20% * 2pi] 范圍內隨機旋轉。

data_augmentation = tf.keras.Sequential([
  tf.keras.layers.RandomFlip('horizontal'),
  tf.keras.layers.RandomRotation(0.1),
])	

我們這裡將隨便使用一張圖片,使用數據增強的方法對其進行變化,第一張圖是原圖,其他的都是進行瞭變化的圖片

for image, _ in train_dataset.take(1):
    plt.figure(figsize=(10, 10))
    first_image = image[0]
    for i in range(3):
        if  i==0:
            ax = plt.subplot(3, 3, i + 1)
            plt.imshow(first_image/ 255)
            plt.axis('off')
        else:
            ax = plt.subplot(3, 3, i + 1)
            augmented_image = data_augmentation(tf.expand_dims(first_image, 0))
            plt.imshow(augmented_image[0] / 255)
            plt.axis('off')

(4)由於 MobileNetV2 模型的輸入值范圍是在 [-1, 1] 范圍內,但此時我們的圖片數據中的像素值處於 [0, 255] 范圍內,所以要重新縮放這些像素值, Rescaling 函數可以實現該操作。如果將 [0, 255] 范圍的輸入縮放到 [0, 1] 的范圍,我們可以通過設置參數 scale=1./255 來實現。如果將 [0, 255] 范圍內的輸入縮放到 [-1, 1] 范圍內,我們可以通過設置參數 scale=1./127.5, offset=-1 來實現。

rescale = tf.keras.layers.Rescaling(1./127.5, offset=-1)	

3. 遷移學習

(1)我們將 MobileNet V2 模型當做一個基礎模型,此模型已基於 ImageNet 數據集進行完美的預訓練,一般來說最後一層就是一個分類器,我們選擇將在 MobileNet V2 的倒數第二個網絡層上搭建自己的分類器,與最後一層相比倒數第二層能夠保留更豐富的圖片特征。在實際操作中我們實例化 MobileNetV2 模型,通過指定 include_top=False 參數,可以加載不包括最頂層的整個預訓練網絡結果。 該模型的作用就是將(160,160,3)大小的圖片轉換為(5,5,1280)的特征輸出。

(2)第一層是將我們的訓練數據都進行數據增強操作,也就是隨機的翻轉和旋轉。

(3)第二層是接收輸入為 (160,160,3)大小的圖片的輸入層。

(4)第三層是我們直接拿來用的 MobileNetV2 ,因為我們要直接使用基礎模型的圖片特征提取能力,所以為瞭在訓練過程中其權重不發生變化,我們將基礎模型中的權重參數都凍結。

(5)第四層是一個池化層,可以將每個 batch 從 (32,5,5,1280) 壓縮為 (32,1280) 大小的輸出。

(6)第五層是 Dropout ,防止過擬合。

(7)第六層是對該圖片一個預測值,也就是 logit 。如果是正數預測標簽 1 ,如果是負數預測標簽 0 。

(8)我們從模型的 summary 中可以看到,此時我們的模型中的 2259265 個參數被凍結,隻有 1281 個參數是可以訓練的,它們是分屬兩個變量的可訓練參數,即最後一個全連接層即權重 1280 個可訓練參數和偏差 1 個可訓練參數。

IMG_SHAPE = IMG_SIZE + (3,)
inputs = tf.keras.Input(shape=(160, 160, 3))
x = data_augmentation(inputs)
x = tf.keras.applications.mobilenet_v2.preprocess_input(x)
base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, weights='imagenet')
base_model.trainable = False
x = base_model(x, training=False)
x = tf.keras.layers.GlobalAveragePooling2D()(x)
x = tf.keras.layers.Dropout(0.2)(x)
outputs = tf.keras.layers.Dense(1)(x)
model = tf.keras.Model(inputs, outputs)
model.summary()

模型結構如下:

Model: "model_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_8 (InputLayer)        [(None, 160, 160, 3)]     0         
 sequential_2 (Sequential)   (None, 160, 160, 3)       0         
 tf.math.truediv_3 (TFOpLamb  (None, 160, 160, 3)      0         
 da)                                                             
 tf.math.subtract_3 (TFOpLam  (None, 160, 160, 3)      0         
 bda)                                                            
 mobilenetv2_1.00_160 (Funct  (None, 5, 5, 1280)       2257984   
 ional)                                                          
 global_average_pooling2d_3   (None, 1280)             0         
 (GlobalAveragePooling2D)                                        
 dropout_2 (Dropout)         (None, 1280)              0         
 dense_2 (Dense)             (None, 1)                 1281      
=================================================================
Total params: 2,259,265
Trainable params: 1,281
Non-trainable params: 2,257,984

可以看到模型中的可訓練的 2 個變量:

model.trainable_variables

結果如下:

[<tf.Variable 'dense_2/kernel:0' shape=(1280, 1) dtype=float32, numpy=
 array([[ 0.08899798],
        [-0.06681276],
        [ 0.00906871],
        …,
        [-0.00114891],
        [-0.01134416],
        [-0.02000826]], dtype=float32)>,
 <tf.Variable 'dense_2/bias:0' shape=(1,) dtype=float32, numpy=array([0.03746362], dtype=float32)>]    

(9)選擇 Adam 優化器,將我們的學習率設置為 0.0003 。選擇 BinaryCrossentropy 作為損失函數。選擇常規的 accuracy 作為評估指標。此時我們先對基礎模型訓練 10 個 epoch 。

lr = 0.0003
initial_epochs = 10
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=lr),
              loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              metrics=['accuracy'])
history = model.fit(train_dataset, validation_data=test_dataset, epochs=initial_epochs)

訓練過程如下:

Epoch 1/10
63/63 [==============================] - 24s 352ms/step - loss: 0.5517 - accuracy: 0.6835 - val_loss: 0.2720 - val_accuracy: 0.8958
Epoch 2/10
63/63 [==============================] - 21s 327ms/step - loss: 0.2792 - accuracy: 0.8865 - val_loss: 0.1499 - val_accuracy: 0.9531
...
Epoch 9/10
63/63 [==============================] - 20s 321ms/step - loss: 0.1075 - accuracy: 0.9530 - val_loss: 0.0766 - val_accuracy: 0.9740
Epoch 10/10
63/63 [==============================] - 21s 329ms/step - loss: 0.1040 - accuracy: 0.9560 - val_loss: 0.0742 - val_accuracy: 0.9740

(10)使用測試數據對模型進行評估。

model.evaluate(validation_dataset)

結果如下:

loss: 0.0664 – accuracy: 0.9765

4. 微調

(1)在上面的操作中,我們僅僅在 MobileNetV2 基礎模型的頂部添加瞭一層池化層、一層 Dropout、一層全連接層作為我們的自定義分類器。預訓練模型 MobileNetV2 中權重在訓練過程中未曾發生過更新。

(2)我們還有一種被稱為微調的方法,可以在訓練基礎模型權重的同時,同時訓練我們上面自定義添加的用於分類的分類器。這個微調的訓練過程可以將基礎模型的通用的圖片特征提取能力調整為專門提取本任務中貓狗數據集特征的能力。這個微調的操作隻能在我們進行瞭上面的遷移學習操作之後才能進行,否則如果一開始直接將基礎模型和我們自定義的若幹層分類器一起進行訓練,則會由於隨機初始化的分類器導致整個模型的更新梯度太大,從而使得基礎模型喪失瞭其預訓練的有效能力。其次在微調過程中我們應該選擇性地去微調少量頂部的網絡層而不是整個 MobileNet 模型,因為在卷積神經網絡中,低層的網絡層一般捕獲到的是通用的圖片特征,這個能力可以泛化應用到幾乎所有類型的圖片,但是越往頂部的網絡層,越來越聚焦於捕獲訓練時所用到的訓練數據的特征,而微調的目標正是是讓這個模型更加適用於所用的專門的數據集,也就是本次進行的貓狗圖片。

(3)MobileNetV2 模型一共有 154 層結構,我們將模型的前 100 層的參數進行凍結,對頂部的 54 層網絡結構中的參數與我們自定義的分類器的若幹層一起進行訓練,我們通過打印模型的 summary 可以看到,此時共有 54 個可以訓練的變量,這些變量中共有可訓練的參數 1862721 個。

base_model.trainable = True
fine_tune_at = 100
for layer in base_model.layers[:fine_tune_at]:
    layer.trainable = False
print("%d trainable variables "%len(model.trainable_variables))
model.summary()

結果如下:

56 trainable variables 
Model: "model_2"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 input_8 (InputLayer)        [(None, 160, 160, 3)]     0         
 sequential_2 (Sequential)   (None, 160, 160, 3)       0         
 tf.math.truediv_3 (TFOpLamb  (None, 160, 160, 3)      0         
 da)                                                             
 tf.math.subtract_3 (TFOpLam  (None, 160, 160, 3)      0         
 bda)                                                            
 mobilenetv2_1.00_160 (Funct  (None, 5, 5, 1280)       2257984   
 ional)                                                          
 global_average_pooling2d_3   (None, 1280)             0         
 (GlobalAveragePooling2D)                                        
 dropout_2 (Dropout)         (None, 1280)              0         
 dense_2 (Dense)             (None, 1)                 1281      
=================================================================
Total params: 2,259,265
Trainable params: 1,862,721
Non-trainable params: 396,544

(4)之前我們對基礎模型訓練瞭 10 個 epoch ,現在我們進行微調過程中再對模型訓練 10 個 epoch ,且我們將從上面訓練結束的 epoch 開始恢復並進行現在的訓練過程。

(5)這裡我們改用 RMSprop 優化器,且因為此時我們是要對整體模型進行微調,所以設置的學習率比之前降低 10 倍,否則如果較大會很快產生過擬合。

fine_tune_epochs = 10
total_epochs =  initial_epochs + fine_tune_epochs
model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
              optimizer = tf.keras.optimizers.RMSprop(learning_rate=lr/10),
              metrics=['accuracy'])
history_fine = model.fit(train_dataset,  epochs=total_epochs,  initial_epoch=history.epoch[-1],  validation_data=validation_dataset)	

訓練結果輸出:

Epoch 10/20
63/63 [==============================] – 39s 561ms/step – loss: 0.1073 – accuracy: 0.9570 – val_loss: 0.1017 – val_accuracy: 0.9592
Epoch 11/20
63/63 [==============================] – 34s 538ms/step – loss: 0.0688 – accuracy: 0.9725 – val_loss: 0.0448 – val_accuracy: 0.9827

Epoch 19/20
63/63 [==============================] – 34s 537ms/step – loss: 0.0244 – accuracy: 0.9900 – val_loss: 0.0709 – val_accuracy: 0.9777
Epoch 20/20
63/63 [==============================] – 33s 528ms/step – loss: 0.0220 – accuracy: 0.9905 – val_loss: 0.0566 – val_accuracy: 0.9851

(6)使用測試數據對模型進行評估。

model.evaluate(test_dataset)

結果輸出:

loss: 0.0544 – accuracy: 0.9792

之前模型最後的驗證準確率為 0.9740 ,測試準確率為 0.9765 ,而經過微調的模型,最後餓驗證準確率為 0.9851 ,測試準確率為 0.9792 ,都有所提升。

5. 預測

我們隨機挑選一個 batch 進行預測,並將圖片與預測標簽進行顯示,結果表明預測全都正確。

image_batch, label_batch = test_dataset.as_numpy_iterator().next()
predictions = model.predict_on_batch(image_batch).flatten()
predictions = tf.nn.sigmoid(predictions)
predictions = tf.where(predictions < 0.5, 0, 1)
plt.figure(figsize=(10, 10))
for i in range(9):
    ax = plt.subplot(3, 3, i + 1)
    plt.imshow(image_batch[i].astype("uint8"))
    plt.title(class_names[predictions[i]])
    plt.axis("off")

以上就是深度學習Tensorflow 2.4 完成遷移學習和模型微調的詳細內容,更多關於Tensorflow 遷移學習模型微調的資料請關註WalkonNet其它相關文章!

推薦閱讀: