本節介紹基於Keras的使用預訓練模型方法html
想要將深度學習應用於小型圖像數據集,一種經常使用且很是高效的方法是使用預訓練網絡。預訓練網絡(pretrained network)是一個保存好的網絡,以前已在大型數據集(一般是大規模圖像分類任務)上訓練好數組
使用預訓練網絡有兩種方法:特徵提取(feature extraction)和微調模型(fine-tuning)網絡
特徵提取是使用以前網絡學到的表示來重新樣本中提取出有趣的特徵。而後將這些特徵輸入一個新的分類器,從頭開始訓練 ,簡言之就是用提取的特徵取代原始輸入圖像來直接訓練分類器app
圖像分類的卷積神經網絡包含兩部分:首先是一系列池化層和卷積層,最後是一個密集鏈接分類器。第一部分叫做模型的卷積基(convolutional base)。對於卷積神經網絡而言,特徵提取就是取出以前訓練好的網絡的卷積基,在上面運行新數據,而後在輸出上面訓練一個新的分類器
重複使用卷積基的緣由在於卷積基學到的表示可能更加通用,所以更適合重複使用
某個卷積層提取的表示的通用性(以及可複用性)取決於該層在模型中的深度。模型中更靠近底部的層提取的是局部的、高度通用的特徵圖(好比視覺邊緣、顏色和紋理),而更靠近頂部的層提取的是更加抽象的概念(好比「貓耳朵」或「狗眼睛」)。因此若是你的新數據集與原始模型訓練的數據集有很大差別,那麼最好只使用模型的前幾層來作特徵提取,而不是使用整個卷積基學習
能夠從 keras.applications 模塊中導入一些內置的模型如優化
實例化VGG16卷積基編碼
from keras.applications import VGG16 conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))
weights 指定模型初始化的權重檢查點,include_top 指定模型最後是否包含密集鏈接分類器,input_shape 是輸入到網絡中的圖像張量的形狀rest
可使用conv_base.summary()
來查看網絡結構
可見網絡最後一層的輸出特徵圖形狀爲 (4, 4, 512),此時咱們須要在該特徵上添加一個密集鏈接分類器,有兩種方法能夠選擇code
在你的數據集上運行卷積基,將輸出保存成硬盤中的 Numpy 數組,而後用這個數據做爲輸入,輸入到獨立的密集鏈接分類器中htm
這種方法速度快,計算代價低,由於對於每一個輸入圖像只需運行一次卷積基,而卷積基是目前流程中計算代價最高的。但出於一樣的緣由,這種方法不容許你使用數據加強
在頂部添加 Dense 層來擴展已有模型(即 conv_base),並在輸入數據上端到端地運行整個模型
這樣你可使用數據加強,由於每一個輸入圖像進入模型時都會通過卷積基。但出於一樣的緣由,這種方法的計算代價比第一種要高不少
如下將使用在 ImageNet 上訓練的 VGG16 網絡的卷積基從貓狗圖像中提取有趣的特徵,而後在這些特徵上訓練一個貓狗分類器
第一種方法,保存你的數據在 conv_base 中的輸出,而後將這些輸出做爲輸入用於新模型
不使用數據加強的快速特徵提取
import os import numpy as np from keras.preprocessing.image import ImageDataGenerator from keras.applications import VGG16 from keras import models from keras import layers from keras import optimizers import matplotlib.pyplot as plt conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3)) base_dir = 'C:\\Users\\fan\\Desktop\\testDogVSCat' 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 = 20 # 圖像及其標籤提取爲 Numpy 數組 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) # 將(samples, 4, 4, 512)展平爲(samples, 8192) 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)) 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)) 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()
結果
可見,在訓練集上的表現要比以前好不少,不過仍是出現了必定程度的過擬合
第二種方法
使用數據加強的特徵提取
注:擴展 conv_base 模型,而後在輸入數據上端到端地運行模型
由於咱們要使用的卷積基不須要從新訓練,因此咱們須要將卷積基凍結
在 Keras 中,凍結網絡的方法是將其 trainable 屬性設爲 False
conv_base.trainable = False
使用len(model.trainable_weights)
能夠查看能夠訓練的權重張量個數,此時應該注意每一層有兩個張量(主權重矩陣和偏置向量)
Demo以下
import os from keras.preprocessing.image import ImageDataGenerator from keras.applications import VGG16 from keras import models from keras import layers from keras import optimizers import matplotlib.pyplot as plt conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3)) base_dir = 'C:\\Users\\fan\\Desktop\\testDogVSCat' 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) test_datagen = ImageDataGenerator(rescale=1./255) batch_size = 20 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') train_generator = train_datagen.flow_from_directory(train_dir, target_size=(150, 150), batch_size=batch_size, class_mode='binary') validation_generator = test_datagen.flow_from_directory(validation_dir, target_size=(150, 150), batch_size=batch_size, class_mode='binary') conv_base.trainable = False 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.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) 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()
結果
可見,此時沒有出現明顯的過擬合現象,在驗證集上出現了更好的結果
此處應該可使用數據加強的方式擴充咱們的數據集,而後再經過第一種方法來訓練分類器
模型微調
另外一種普遍使用的模型複用方法是模型微調(fine-tuning),與特徵提取互爲補充。微調是指將其頂部的幾層「解凍」,並將這解凍的幾層和新增長的部分聯合訓練,此處的頂層指的是靠近分類器的一端
此時咱們只是微調頂層的緣由是
卷積基中更靠底部的層編碼的是更加通用的可複用特徵,而更靠頂部的層編碼的是更專業化的特徵。微調這些更專業化的特徵更加有用,由於它們須要在你的新問題上改變用途
訓練的參數越多,過擬合的風險越大
微調網絡的步驟以下
凍結直到某一層的方法
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
微調網絡時可使用學習率很是小的 RMSProp 優化器來實現,太大的權重更新可能會對咱們的網絡形成很大的破壞
爲了讓圖像更具可讀性,能夠將每一個損失和精度都替換爲指數移動平均值,從而讓曲線變得平滑
def smooth_curve(points, factor=0.8): smoothed_points = [] for point in points: if smoothed_points: previous = smoothed_points[-1] smoothed_points.append(previous * factor + point * (1 - factor)) else: smoothed_points.append(point) return smoothed_points
精度的是損失值的分佈,而不是平均值
Deep learning with Python 學習筆記(4)
Deep learning with Python 學習筆記(2)