1. 程式人生 > >python Deep learning 學習筆記(3)

python Deep learning 學習筆記(3)

本節介紹基於Keras的使用預訓練模型方法

想要將深度學習應用於小型影象資料集,一種常用且非常高效的方法是使用預訓練網路。預訓練網路(pretrained network)是一個儲存好的網路,之前已在大型資料集(通常是大規模影象分類任務)上訓練好

使用預訓練網路有兩種方法:特徵提取(feature extraction)微調模型(fine-tuning)

特徵提取是使用之前網路學到的表示來從新樣本中提取出有趣的特徵。然後將這些特徵輸入一個新的分類器,從頭開始訓練 ,簡言之就是用提取的特徵取代原始輸入影象來直接訓練分類器

影象分類的卷積神經網路包含兩部分:首先是一系列池化層和卷積層,最後是一個密集連線分類器。第一部分叫作模型的卷積基(convolutional base)

。對於卷積神經網路而言,特徵提取就是取出之前訓練好的網路的卷積基,在上面執行新資料,然後在輸出上面訓練一個新的分類器
重複使用卷積基的原因在於卷積基學到的表示可能更加通用,因此更適合重複使用
某個卷積層提取的表示的通用性(以及可複用性)取決於該層在模型中的深度。模型中更靠近底部的層提取的是區域性的、高度通用的特徵圖(比如視覺邊緣、顏色和紋理),而更靠近頂部的層提取的是更加抽象的概念(比如“貓耳朵”或“狗眼睛”)。所以如果你的新資料集與原始模型訓練的資料集有很大差異,那麼最好只使用模型的前幾層來做特徵提取,而不是使用整個卷積基

可以從 keras.applications 模組中匯入一些內建的模型如

  • Xception
  • Inception V3
  • ResNet50
  • VGG16
  • VGG19
  • MobileNet

例項化VGG16卷積基

from keras.applications import VGG16
conv_base = VGG16(weights='imagenet', include_top=False, input_shape=(150, 150, 3))

weights 指定模型初始化的權重檢查點,include_top 指定模型最後是否包含密集連線分類器,input_shape 是輸入到網路中的影象張量的形狀

可以使用conv_base.summary()

來檢視網路結構
可見網路最後一層的輸出特徵圖形狀為 (4, 4, 512),此時我們需要在該特徵上新增一個密集連線分類器,有兩種方法可以選擇

  1. 在你的資料集上運行卷積基,將輸出儲存成硬碟中的 Numpy 陣列,然後用這個資料作為輸入,輸入到獨立的密集連線分類器中

    這種方法速度快,計算代價低,因為對於每個輸入影象只需執行一次卷積基,而卷積基是目前流程中計算代價最高的。但出於同樣的原因,這種方法不允許你使用資料增強

  2. 在頂部新增 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),與特徵提取互為補充。微調是指將其頂部的幾層“解凍”,並將這解凍的幾層和新增加的部分聯合訓練,此處的頂層指的是靠近分類器的一端
此時我們只是微調頂層的原因是

卷積基中更靠底部的層編碼的是更加通用的可複用特徵,而更靠頂部的層編碼的是更專業化的特徵。微調這些更專業化的特徵更加有用,因為它們需要在你的新問題上改變用途
訓練的引數越多,過擬合的風險越大

微調網路的步驟如下

  1. 在已經訓練好的基網路(base network)上新增自定義網路
  2. 凍結基網路
  3. 訓練所新增的部分
  4. 解凍基網路的一些層
  5. 聯合訓練解凍的這些層和新增的部分

凍結直到某一層的方法

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

精度的是損失值的分佈,而不是平均值

python Deep learning 學習筆記(2)