【深度學習】使用預訓練模型
主要有兩種方法:
- 特徵提取
- 微調模型
特徵提取
特徵提取就是使用已經訓練好的網路在新的樣本上提取特徵,然後將這些特徵輸入到新的分類器,從頭開始訓練的過程。
卷積神經網路分為兩個部分:
- 一系列池化層+卷積層,也叫卷積基
- 全連線層
特徵提取就是去除之前訓練好的網路的分類器,在卷積基之上執行新資料,訓練新的分類器。
我們只是複用卷積基,而不用訓練好的分類器的資料,這樣做的原因是卷積基學到的表示更加通用,而分類器學到的表示則必然是針對模型已經訓練的類別,只包含某個類別出現在整張影象中的概率資訊。
另外,全連線層不包含物體在輸入影象中的位置資訊,因為接入全連線層的資料已經被展平,全連線層拋棄了空間的概念。
而使用卷積基,到底用多少層呢?這要看資料的特徵。越往前,模型提取的特徵越低階,也即是區域性的,更通用的特徵圖,而越往後,則抽取的特徵就越抽象。所以當新的資料集與原始模型訓練的資料集差異較大時,可以只用模型的前幾層來提取特徵。
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》