1. 程式人生 > >【深度學習】使用預訓練模型

【深度學習】使用預訓練模型

主要有兩種方法:

  • 特徵提取
  • 微調模型

特徵提取

特徵提取就是使用已經訓練好的網路在新的樣本上提取特徵,然後將這些特徵輸入到新的分類器,從頭開始訓練的過程。

卷積神經網路分為兩個部分:

  • 一系列池化層+卷積層,也叫卷積基
  • 全連線層

特徵提取就是去除之前訓練好的網路的分類器,在卷積基之上執行新資料,訓練新的分類器

在這裡插入圖片描述

我們只是複用卷積基,而不用訓練好的分類器的資料,這樣做的原因是卷積基學到的表示更加通用,而分類器學到的表示則必然是針對模型已經訓練的類別,只包含某個類別出現在整張影象中的概率資訊。

另外,全連線層不包含物體在輸入影象中的位置資訊,因為接入全連線層的資料已經被展平,全連線層拋棄了空間的概念。

而使用卷積基,到底用多少層呢?這要看資料的特徵。越往前,模型提取的特徵越低階,也即是區域性的,更通用的特徵圖,而越往後,則抽取的特徵就越抽象。所以當新的資料集與原始模型訓練的資料集差異較大時,可以只用模型的前幾層來提取特徵。

from keras.applications import VGG16
conv_base = VGG16(weights='imagenet',
                 include_top=False,
                 input_shape=(150, 150, 3))
conv_base.summary()
'''
Layer (type)                 Output Shape              Param #   
=================================================================
input_1 (InputLayer)         (None, 150, 150, 3)       0         
_________________________________________________________________
block1_conv1 (Conv2D)        (None, 150, 150, 64)      1792      
_________________________________________________________________
block1_conv2 (Conv2D)        (None, 150, 150, 64)      36928     
_________________________________________________________________
block1_pool (MaxPooling2D)   (None, 75, 75, 64)        0         
_________________________________________________________________
block2_conv1 (Conv2D)        (None, 75, 75, 128)       73856     
_________________________________________________________________
block2_conv2 (Conv2D)        (None, 75, 75, 128)       147584    
_________________________________________________________________
block2_pool (MaxPooling2D)   (None, 37, 37, 128)       0         
_________________________________________________________________
block3_conv1 (Conv2D)        (None, 37, 37, 256)       295168    
_________________________________________________________________
block3_conv2 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_conv3 (Conv2D)        (None, 37, 37, 256)       590080    
_________________________________________________________________
block3_pool (MaxPooling2D)   (None, 18, 18, 256)       0         
_________________________________________________________________
block4_conv1 (Conv2D)        (None, 18, 18, 512)       1180160   
_________________________________________________________________
block4_conv2 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_conv3 (Conv2D)        (None, 18, 18, 512)       2359808   
_________________________________________________________________
block4_pool (MaxPooling2D)   (None, 9, 9, 512)         0         
_________________________________________________________________
block5_conv1 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv2 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_conv3 (Conv2D)        (None, 9, 9, 512)         2359808   
_________________________________________________________________
block5_pool (MaxPooling2D)   (None, 4, 4, 512)         0         
=================================================================
Total params: 14,714,688
Trainable params: 14,714,688
Non-trainable params: 0
'''

可以看出,最後的輸出特徵圖的形狀是(4,4,512),現在我們在這個基礎上新增全連線層。現在有兩種方式可選:

  • 在自己的資料集上運行卷積基,將輸出儲存在硬碟上,然後用這個資料作為輸入,輸入到獨立的全連線層分類器。這種方法速度快,代價低,但是不允許使用資料增強。
  • 在卷積基的頂部新增Dense層來擴充套件已有模型,輸入資料端到端執行整個模型,可以使用資料增強,但是計算代價更高。
# 使用預訓練模型的卷積基提取特徵
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator

base_dir =
'./data/cats_and_dogs_small' train_dir = os.path.join(base_dir, 'train') validation_dir = os.path.join(base_dir, 'validation') test_dir = os.path.join(base_dir, 'test') datagen = ImageDataGenerator(rescale=1./255) batch_size = 3 def extract_features(directory, sample_count): features = np.zeros(shape=(sample_count, 4, 4, 512)) labels = np.zeros(shape=sample_count) generator = datagen.flow_from_directory( directory, target_size=(150, 150), batch_size=batch_size, class_mode='binary' ) i = 0 for inputs_batch, labels_batch in generator: features_batch = conv_base.predict(inputs_batch) features[i * batch_size: (i+1) * batch_size] = features_batch labels[i * batch_size:(i+1) *batch_size] = labels_batch i += 1 if i * batch_size >= sample_count: break return features, labels train_features, train_labels = extract_features(train_dir, 2000) validation_features, validation_labels = extract_features(validation_dir, 1000) test_features, test_labels = extract_features(test_dir,1000) # 將卷積基的輸出展平 train_features = np.reshape(train_features, (2000,4 * 4 * 512)) validation_features = np.reshape(validation_features, (1000, 4 * 4 * 512)) test_features = np.reshape(test_features,(1000, 4 * 4 * 512)) # 定義自己的全連線分類器 from keras import models from keras import layers from keras import optimizers model = models.Sequential() model.add(layers.Dense(256, activation='relu', input_dim=4 * 4 * 512)) model.add(layers.Dropout(0.5)) model.add(layers.Dense(1, activation='sigmoid')) model.compile(optimizer=optimizers.RMSprop(lr=2e-5), loss='binary_crossentropy', metrics=['acc']) history = model.fit(train_features, train_labels, epochs=30, batch_size=20, validation_data=(validation_features, validation_labels) ) # 繪製訓練過程中的損失曲線和精度曲線 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, 'r', 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, 'r', label='Validation loss') plt.title('Training and Validation loss') plt.legend() # plt.show() # python2環境下不需要

擴展卷積基

from keras import models
from keras import layers

model = models.Sequential()
model.add(conv_base) # 將卷積基整個加進來
model.add(layers.Flatten())
model.add(layers.Dense(256, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))

model.summary()
'''
Layer (type)                 Output Shape              Param #   
=================================================================
vgg16 (Model)                (None, 4, 4, 512)         14714688  
_________________________________________________________________
flatten_1 (Flatten)          (None, 8192)              0         
_________________________________________________________________
dense_7 (Dense)              (None, 256)               2097408   
_________________________________________________________________
dense_8 (Dense)              (None, 1)                 257       
=================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0
'''
conv_base.trainable = False # 凍結卷積基
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers

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=(150, 150),
    batch_size=20,
    class_mode='binary'
)

validation_generator = test_datagen.flow_from_directory(
    validation_dir,
    target_size=(150, 150),
    batch_size=20,
    class_mode='binary'
)

# 編譯訓練模型
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=2e-5),
              metrics=['acc']
             )
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=30,
    validation_data=validation_generator,
    validation_steps=50
)

# 繪製訓練過程中的損失曲線和精度曲線
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, 'r', 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, 'r', label='Validation loss')
plt.title('Training and Validation loss')
plt.legend()

plt.show()

在編譯和訓練模型之前,一定要先凍結卷積基。

凍結的目的就是使得訓練過程中提取特徵的卷積基權重不變。因為全連線層的引數是隨機初始化的,訓練時在網路中傳播的權重更新會很大,如果不把卷積基凍結,卷積基引數會被破壞掉。

Keras中凍結網路的方法很簡單:將其trainable屬性設定為False即可。

微調模型

用於特徵提取的卷積基是需要被凍結的,而微調則是將頂部的幾層解凍,將解凍的幾層和新增的部分,如全連線層聯合訓練。之所以稱之為微調,是因為我們只略微調整了複用的模型的更加抽象的表示部分,使得模型與當前求解問題更加相關。

在這裡插入圖片描述

需要注意的是,在微調這裡,也需要先將卷積基全部凍結來訓練自己的分類器,只有分類器訓練完畢後,才能解凍卷積基的頂部幾層,進行微調,如果分類器沒有訓練好,訓練期間的傳播誤差訊號很大,待微調的卷積部分會被破壞。

所以,微調的步驟總結如下:

  • 在卷積基上(已訓練好的網路)新增自定義網路
  • 凍結基網路
  • 訓練所新增的部分
  • 解凍基網路的一些層
  • 聯合訓練解凍的層和新增的層
# conv_base.summary()
# 凍結知道某一層的所有層
conv_base.trainable = True
set_trainable = False

for layer in conv_base.layers:
    if layer.name == 'block5_conv1': # 從這一層開始往後均可訓練
        set_trainable = True
        if set_trainable:
            layer.trainable = True
        else:
            layer.trainable = False
# 微調模型
model.compile(loss='binary_crossentropy',
              optimizer=optimizers.RMSprop(lr=1e-5),
              metrics=['acc']
             )
history = model.fit_generator(
    train_generator,
    steps_per_epoch=100,
    epochs=100,
    validation_data=validation_generator,
    validation_steps=50
)

總結

  • CNN是用於處理計算機視覺任務的最佳機器學習模型,即使在非常小的資料集上從零開始訓練一個CNN模型,得到的結果都還不錯
  • 在小型資料集上的主要問題是過擬合,可用資料增強技術來降低過擬合
  • 利用特徵提取,可以很容易將現有的CNN網路用於新的資料集
  • 微調技術是特徵提取的補充,使用微調可以進一步提升模型的效能

END.

參考:

《Deep Learning with Python》