十三、計(jì)算機(jī)視覺(jué)中的深度學(xué)習(xí):使用預(yù)訓(xùn)練網(wǎng)絡(luò)


文章代碼來(lái)源:《deep learning on keras》,非常好的一本書(shū),大家如果英語(yǔ)好,推薦直接閱讀該書(shū),如果時(shí)間不夠,可以看看此系列文章,文章為我自己翻譯的內(nèi)容加上自己的一些思考,水平有限,多有不足,請(qǐng)多指正,翻譯版權(quán)所有,若有轉(zhuǎn)載,請(qǐng)先聯(lián)系本人。
個(gè)人方向?yàn)閿?shù)值計(jì)算,日后會(huì)向深度學(xué)習(xí)和計(jì)算問(wèn)題的融合方面靠近,若有相近專(zhuān)業(yè)人士,歡迎聯(lián)系。


系列文章:
一、搭建屬于你的第一個(gè)神經(jīng)網(wǎng)絡(luò)
二、訓(xùn)練完的網(wǎng)絡(luò)去哪里找
三、【keras實(shí)戰(zhàn)】波士頓房?jī)r(jià)預(yù)測(cè)
四、keras的function API
五、keras callbacks使用
六、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅰ:機(jī)器學(xué)習(xí)的四個(gè)標(biāo)簽
七、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅱ:評(píng)估機(jī)器學(xué)習(xí)模型
八、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅲ:數(shù)據(jù)預(yù)處理、特征工程和特征學(xué)習(xí)
九、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅳ:過(guò)擬合和欠擬合
十、機(jī)器學(xué)習(xí)基礎(chǔ)Ⅴ:機(jī)器學(xué)習(xí)的一般流程十一、計(jì)算機(jī)視覺(jué)中的深度學(xué)習(xí):卷積神經(jīng)網(wǎng)絡(luò)介紹
十二、計(jì)算機(jī)視覺(jué)中的深度學(xué)習(xí):從零開(kāi)始訓(xùn)練卷積網(wǎng)絡(luò)
十三、計(jì)算機(jī)視覺(jué)中的深度學(xué)習(xí):使用預(yù)訓(xùn)練網(wǎng)絡(luò)
十四、計(jì)算機(jī)視覺(jué)中的神經(jīng)網(wǎng)絡(luò):可視化卷積網(wǎng)絡(luò)所學(xué)到的東西


一個(gè)常用、高效的在小圖像數(shù)據(jù)集上深度學(xué)習(xí)的方法就是利用預(yù)訓(xùn)練網(wǎng)絡(luò)。一個(gè)預(yù)訓(xùn)練網(wǎng)絡(luò)只是簡(jiǎn)單的儲(chǔ)存了之前在大的數(shù)據(jù)集訓(xùn)練的結(jié)果,通常是大的圖像分類(lèi)任務(wù)。如果原始的數(shù)據(jù)集已經(jīng)足夠大,足夠一般,通過(guò)預(yù)訓(xùn)練學(xué)習(xí)到的空間上的特征層次結(jié)構(gòu)就能有效地在我們的模型中工作,因此這些特征對(duì)許多計(jì)算機(jī)視覺(jué)問(wèn)題都很有用,盡管這些新問(wèn)題和原任務(wù)相比可能涉及完全不同的類(lèi)別。例如,一個(gè)人或許在ImageNet上訓(xùn)練了一個(gè)網(wǎng)絡(luò)(里面的類(lèi)別大多是動(dòng)物和日常物體)然后重新定義這個(gè)訓(xùn)練有素的網(wǎng)絡(luò),讓它去識(shí)別圖像里面的家具。這樣學(xué)習(xí)特征在不同問(wèn)題的可移植性是深度學(xué)習(xí)和其它一些淺學(xué)習(xí)方法比起來(lái)最關(guān)鍵的好處,這讓深度學(xué)習(xí)在小數(shù)據(jù)問(wèn)題中十分高效。
在我們的例子中,我們考慮了一個(gè)大的在ImageNet數(shù)據(jù)集上訓(xùn)練的卷積網(wǎng)絡(luò)。圖像網(wǎng)絡(luò)包含很多動(dòng)物,包括不同種的貓和狗,我們可以期望這會(huì)在我們的cat vs. dog分類(lèi)問(wèn)題中表現(xiàn)得很好。
我們將使用VGG16結(jié)構(gòu),由Karen Simonyan 和Andrew Zisserman 于2014年改進(jìn),簡(jiǎn)單且在ImageNet中廣泛使用卷積網(wǎng)絡(luò)結(jié)構(gòu)。盡管這是一個(gè)有點(diǎn)老的模型,和現(xiàn)在最先進(jìn)的相比相差不少,且比現(xiàn)在的模型要笨重一些,我們選擇它是因?yàn)樗慕Y(jié)構(gòu)和你熟悉的非常相近,而且不需要引入任何新的內(nèi)容。這或許是你的第一次遇到這些可愛(ài)的模型名字——VGG,ResNet,Inception,Inception-ResNet,Xception...你將會(huì)習(xí)慣他們,因?yàn)樗鼈儠?huì)出現(xiàn)的非常頻繁,如果你持續(xù)在用深度學(xué)習(xí)做計(jì)算機(jī)視覺(jué)的話(huà)。
這里有兩種方法去引入一個(gè)預(yù)訓(xùn)練的網(wǎng)絡(luò):特征提取和良好調(diào)參。我們將兩個(gè)都講,讓我們從特征提取開(kāi)始。

特征提取

特征提取使用通過(guò)之前網(wǎng)絡(luò)學(xué)習(xí)到的表示來(lái)從新的樣本中提取有趣的特征。這些特征通過(guò)新的分類(lèi)器,這個(gè)分類(lèi)器是從零開(kāi)始訓(xùn)練的。
正如我們之前看到的,卷積網(wǎng)絡(luò)用來(lái)圖像分類(lèi)包括兩部分:它們從一系列的池化和卷積層開(kāi)始,以全連接層分類(lèi)器結(jié)束。第一部分叫做模型的“卷積基”。在卷積網(wǎng)絡(luò)的情況,“特征提取”將會(huì)簡(jiǎn)單的包含拿卷積基作為預(yù)訓(xùn)練網(wǎng)絡(luò),在這個(gè)上面跑一些新數(shù)據(jù),在輸出層最上面訓(xùn)練一個(gè)新的分類(lèi)器。


Swapping classifiers while keeping the same convolutional base

為什么僅僅重用卷積基呢?我們能重用全連接分類(lèi)器嗎?一般來(lái)說(shuō),這個(gè)應(yīng)當(dāng)避免。這個(gè)原因是卷積基學(xué)習(xí)到的表示可能更通用,所以也更能復(fù)用:卷積網(wǎng)絡(luò)的特征映射在圖像中間是存在通用的,這就使得忽視手頭的計(jì)算機(jī)視覺(jué)問(wèn)題很有用了。在另一端,分類(lèi)器學(xué)習(xí)到的表示對(duì)于我們要訓(xùn)練的模型的類(lèi)別是非常特別的——它們將只包含整幅圖在這一類(lèi)或那一類(lèi)的概率。此外,在全連接層的表示不再包含任何物體處于輸入圖像的哪個(gè)位置的信息:這些層擺脫了空間的概念,盡管物體的位置仍然有卷積特映射來(lái)描述。對(duì)于物體位置有關(guān)的問(wèn)題,全連接特征就顯得相當(dāng)?shù)臒o(wú)用。
注意:某一層表示提取的共性或是說(shuō)復(fù)用能力,取決于模型的深度。早期的層會(huì)提取一些局部的,共度通用的特征比如邊緣、顏色和紋理,同時(shí)后期的層會(huì)提取一些高度抽象的例如貓眼和狗眼。因此如果你的新數(shù)據(jù)集和原來(lái)的數(shù)據(jù)集很不一樣,你最好只用前面的幾層模型來(lái)做特征提取,而不是使用整個(gè)卷積集。
在我們的例子中,由于ImageNet分類(lèi)基的確包含大量的貓、狗的類(lèi),可能我們直接復(fù)用全連接層都會(huì)得到一些好處。然而,我們選擇不這樣做,為了包含一些更加一般的情況,這些情況中需要分類(lèi)的新問(wèn)題和原來(lái)的模型類(lèi)別沒(méi)有重疊。
讓我們使用VGG16網(wǎng)絡(luò)來(lái)實(shí)踐一下吧,在ImageNet中提取了貓狗的圖像的有趣特征,最后在cat vs. dog中分類(lèi)器訓(xùn)練這些特征。
VGG16模型,還有一些別的,都被keras提前打包好了。你能直接從keras.applications里面調(diào)用模型。這里是圖像分類(lèi)模型的的一個(gè)列表,全部都是在ImageNet上面預(yù)訓(xùn)練過(guò)了的,作為kera.applications的一部分:

  • Xception
  • Inception V3
  • ResNet50
  • VGG16
  • VGG19
  • MobileNet
    讓我們實(shí)例教學(xué)VGG16模型:
from keras.applications import VGG16
conv_base = VGG16(weights='imagenet',
 include_top=False,
 input_shape=(150, 150, 3))

我們給結(jié)構(gòu)傳入了三個(gè)參數(shù):

  • weights,用來(lái)初始化權(quán)重的checkpoint
  • include_top,是否包含最高的一層全連接層。默認(rèn)的話(huà),全連接分類(lèi)器將會(huì)遵從ImgeNet中的1000類(lèi)。因?yàn)槲覀冊(cè)囍褂梦覀兊娜B接層(只有兩類(lèi),貓和狗)我們不需要把原始的全連接層包含進(jìn)來(lái)。
  • input_shape,我們需要放進(jìn)網(wǎng)絡(luò)中的圖像張量的形狀。這個(gè)參數(shù)完全可選:如果我們不傳這個(gè)進(jìn)去,網(wǎng)絡(luò)就能處理任何形狀的輸入。
    這里有一些VGG16卷積基結(jié)構(gòu)的細(xì)節(jié):這和你熟悉的簡(jiǎn)單卷積網(wǎng)絡(luò)很相似。
>>> conv_base.summary()
Layer (type) Output Shape Param #
================================================================
input_1 (InputLayer) (None, 150, 150, 3) 0
________________________________________________________________
block1_conv1 (Convolution2D) (None, 150, 150, 64) 1792
________________________________________________________________
block1_conv2 (Convolution2D) (None, 150, 150, 64) 36928
________________________________________________________________
block1_pool (MaxPooling2D) (None, 75, 75, 64) 0
________________________________________________________________
block2_conv1 (Convolution2D) (None, 75, 75, 128) 73856
________________________________________________________________
block2_conv2 (Convolution2D) (None, 75, 75, 128) 147584
________________________________________________________________
block2_pool (MaxPooling2D) (None, 37, 37, 128) 0
________________________________________________________________
block3_conv1 (Convolution2D) (None, 37, 37, 256) 295168
________________________________________________________________
block3_conv2 (Convolution2D) (None, 37, 37, 256) 590080
________________________________________________________________
block3_conv3 (Convolution2D) (None, 37, 37, 256) 590080
________________________________________________________________
block3_pool (MaxPooling2D) (None, 18, 18, 256) 0
________________________________________________________________
block4_conv1 (Convolution2D) (None, 18, 18, 512) 1180160
________________________________________________________________
block4_conv2 (Convolution2D) (None, 18, 18, 512) 2359808
________________________________________________________________
block4_conv3 (Convolution2D) (None, 18, 18, 512) 2359808
________________________________________________________________
block4_pool (MaxPooling2D) (None, 9, 9, 512) 0
________________________________________________________________
block5_conv1 (Convolution2D) (None, 9, 9, 512) 2359808
________________________________________________________________
block5_conv2 (Convolution2D) (None, 9, 9, 512) 2359808
________________________________________________________________
block5_conv3 (Convolution2D) (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)。這就是我們要接入全連接層的特征。
在這個(gè)時(shí)候,我們有兩種方法來(lái)進(jìn)行:

  • 通過(guò)我們的數(shù)據(jù)集來(lái)跑卷積基,記錄其輸出為數(shù)組類(lèi)型,然后使用這些數(shù)據(jù)作為輸入來(lái)訓(xùn)練單獨(dú)一個(gè)全連接分類(lèi)器,就像我們第一次舉得那個(gè)例子一樣。這個(gè)方法很快很容易去運(yùn)行,因?yàn)檫@對(duì)于每一個(gè)輸入的圖像只需要運(yùn)行卷積基一次,卷積基目前來(lái)說(shuō)是管道中最貴的。然而,同樣的原因,這種方法不允許我們利用數(shù)據(jù)增加。
  • 給我們有的卷積基增加一層全連接層,然后端到端的運(yùn)行整個(gè)輸入數(shù)據(jù)。這允許我們使用數(shù)據(jù)增加,因?yàn)槊總€(gè)輸入圖像都通過(guò)了整個(gè)卷積基。然而,相同的原因,這種方法也比較昂貴。
    我們將包含兩種方法。讓我們第一個(gè)看看所需的代碼:在我們的數(shù)據(jù)中記錄輸出層conv_base并使用這些輸出作為新模型的輸入。
    我們將會(huì)從之前介紹的ImageDataGenerator實(shí)例來(lái)通過(guò)數(shù)組的形式提取圖像和它們的標(biāo)簽。我們將會(huì)從這些圖像中使用predict方法來(lái)提取特征。
import os
import numpy as np
from keras.preprocessing.image import ImageDataGenerator
base_dir = '/Users/fchollet/Downloads/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 = 20
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:
 # Note that since generators yield data indefinitely in a loop,
 # we must `break` after every image has been seen once.
 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)

在把之前的輸出丟進(jìn)全連接分類(lèi)器之前,我們需要先把它們拉平:

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))

此時(shí),我們能夠定義我們的全連接分類(lèi)器了(使用dropout正則化),并在我們所記錄的數(shù)據(jù)和標(biāo)簽上進(jìn)行訓(xùn)練:

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))

訓(xùn)練很快,我們可以解決兩個(gè)全連接層,每一批次在CPU上都只需要不到一秒的時(shí)間。
讓我們看一下訓(xùn)練的損失和準(zhǔn)確率曲線(xiàn):

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, '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()
Training and validation accuracy for simple feature extraction (training values as dots, validation values as solid lines)

Training and validation loss for simple feature extraction (training values as dots, validation values as solid lines)

我們?cè)隍?yàn)證集上面的準(zhǔn)確率到了約90%,比我們之前從零開(kāi)始訓(xùn)練的小模型要好得多。然而,我們的圖同樣揭示了我們幾乎從一開(kāi)始就過(guò)擬合,盡管對(duì)dropout用了很高的rate。這是因?yàn)檫@些方法都沒(méi)有利用數(shù)據(jù)增多,這對(duì)于小樣本來(lái)說(shuō)預(yù)防過(guò)擬合相當(dāng)?shù)闹匾?br> 現(xiàn)在,讓我們回顧第二種我們提到的做特征提取的方法,這將慢得多,貴得多,但這允許我們?cè)谟?xùn)練的時(shí)候使用數(shù)據(jù)增多的方式:擴(kuò)展卷積基模型,然后端到端的運(yùn)行輸入。注意到這個(gè)技術(shù)實(shí)際上非常貴,所以你只能在GPU上面試:這在CPU上跑幾乎僵化。如果你無(wú)法在GPU上跑代碼,那么你就用第一種方法吧。
因?yàn)槟P捅憩F(xiàn)得像個(gè)層,你能在sequential模型上加模型:

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'))

讓我們看一下最后長(zhǎng)啥樣

>>> model.summary()
Layer (type) Output Shape Param #
================================================================
vgg16 (Model) (None, 4, 4, 512) 14714688
________________________________________________________________
flatten_1 (Flatten) (None, 8192) 0
________________________________________________________________
dense_1 (Dense) (None, 256) 2097408
________________________________________________________________
dense_2 (Dense) (None, 1) 257
================================================================
Total params: 16,812,353
Trainable params: 16,812,353
Non-trainable params: 0

正如你看到的,VGG16的卷積基有14714688個(gè)參數(shù),非常大。我們?cè)陧斏霞拥姆诸?lèi)器有2000個(gè)參數(shù)。
在我們編譯和訓(xùn)練模型之前,一個(gè)很重要的事情就是凍結(jié)卷積基。凍結(jié)一層或多層,意味著阻止其權(quán)重在訓(xùn)練的時(shí)候發(fā)生變化。如果你不這樣做,之前訓(xùn)練好的卷積基將會(huì)在訓(xùn)練的過(guò)程中發(fā)生改變。因?yàn)轫斏系娜B接層隨機(jī)初始化,很多的權(quán)重升級(jí)就會(huì)通過(guò)網(wǎng)絡(luò)傳播,有效的毀壞之前學(xué)習(xí)到的表示。
在keras里面,凍結(jié)網(wǎng)絡(luò)是通過(guò)將trainable屬性設(shè)置為False來(lái)做到的:

>>> print('This is the number of trainable weights '
 'before freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights before freezing the conv base: 30
>>> conv_base.trainable = False
>>> print('This is the number of trainable weights '
 'after freezing the conv base:', len(model.trainable_weights))
This is the number of trainable weights after freezing the conv base: 4

通過(guò)這個(gè)設(shè)置,只有兩個(gè)加進(jìn)去的全連接層能夠被訓(xùn)練。總共只有兩個(gè)權(quán)重張量:每層兩個(gè)(主要的權(quán)重矩陣和偏置向量)。注意為了讓這些改變更有效,我們必須首先將模型編譯。如果你曾經(jīng)修改了權(quán)重的可訓(xùn)練性,在編譯之后,你應(yīng)當(dāng)重新編譯模型,不然這些改變會(huì)被忽略。
現(xiàn)在我們開(kāi)始訓(xùn)練我們的模型,使用之前我們用過(guò)的數(shù)據(jù)增多方法。

from keras.preprocessing.image import ImageDataGenerator
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')
# Note that the validation data should not be augmented!
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(
 # This is the target directory
 train_dir,
 # All images will be resized to 150x150
 target_size=(150, 150),
 batch_size=20,
 # Since we use binary_crossentropy loss, we need binary labels
 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)

再次畫(huà)出結(jié)果:


Training and validation accuracy for feature extraction with data augmentation (training values as dots, validation values as solid lines)

Training and validation loss for feature extraction with data augmentation (training values as dots, validation values as solid lines)

如你所見(jiàn),我們?cè)隍?yàn)證集的準(zhǔn)確率到達(dá)了大約96%。這比我們之前在小卷積網(wǎng)絡(luò)上從零訓(xùn)練的結(jié)果要好的多。

好的調(diào)參

另一個(gè)在模型復(fù)用中廣泛應(yīng)用的和特征提取互補(bǔ),是好調(diào)參。好調(diào)參是最后幾層都不凍結(jié),使得可以在最后比較抽象的層次也可以復(fù)用調(diào)參,使得它們對(duì)于手頭的問(wèn)題來(lái)說(shuō)更加相關(guān)。


Fine-tuning the last convolutional block of the VGG16 network

我們?cè)?jīng)聲明過(guò)為了能夠訓(xùn)練頂層隨機(jī)初始化的分類(lèi)器,凍結(jié)VGG16的卷積基非常重要。相同的原因,只有當(dāng)卷積基中的分類(lèi)器被訓(xùn)練過(guò)以后,才有可能調(diào)參。如果分類(lèi)器還沒(méi)有被訓(xùn)練過(guò),傳給網(wǎng)絡(luò)的錯(cuò)誤的信號(hào)就太大了,這個(gè)表示之前學(xué)到的將會(huì)被毀掉。因此調(diào)參的步驟如下:

  • 1)在已經(jīng)訓(xùn)練的基礎(chǔ)網(wǎng)絡(luò)頂層添加你自定義的網(wǎng)絡(luò)
  • 2)凍結(jié)基礎(chǔ)網(wǎng)絡(luò)
  • 3)訓(xùn)練你添加的部分
  • 4)解凍基礎(chǔ)網(wǎng)絡(luò)的某些層
  • 5)將這些原來(lái)的層和你加的層一起訓(xùn)練

在做特征提取的時(shí)候,我們已經(jīng)結(jié)束了前三步。讓我們接下來(lái)做第四步:我們將會(huì)解凍我們的conv_base層,然后凍結(jié)其中的一些單獨(dú)的層。
作為提醒,下面是我們的卷積基的樣子:

>>> conv_base.summary()
Layer (type) Output Shape Param #
================================================================
input_1 (InputLayer) (None, 150, 150, 3) 0
________________________________________________________________
block1_conv1 (Convolution2D) (None, 150, 150, 64) 1792
________________________________________________________________
block1_conv2 (Convolution2D) (None, 150, 150, 64) 36928
________________________________________________________________
block1_pool (MaxPooling2D) (None, 75, 75, 64) 0
________________________________________________________________
block2_conv1 (Convolution2D) (None, 75, 75, 128) 73856
________________________________________________________________
block2_conv2 (Convolution2D) (None, 75, 75, 128) 147584
________________________________________________________________
block2_pool (MaxPooling2D) (None, 37, 37, 128) 0
________________________________________________________________
block3_conv1 (Convolution2D) (None, 37, 37, 256) 295168
________________________________________________________________
block3_conv2 (Convolution2D) (None, 37, 37, 256) 590080
________________________________________________________________
block3_conv3 (Convolution2D) (None, 37, 37, 256) 590080
________________________________________________________________
block3_pool (MaxPooling2D) (None, 18, 18, 256) 0
________________________________________________________________
block4_conv1 (Convolution2D) (None, 18, 18, 512) 1180160
________________________________________________________________
block4_conv2 (Convolution2D) (None, 18, 18, 512) 2359808
________________________________________________________________
block4_conv3 (Convolution2D) (None, 18, 18, 512) 2359808
________________________________________________________________
block4_pool (MaxPooling2D) (None, 9, 9, 512) 0
________________________________________________________________
block5_conv1 (Convolution2D) (None, 9, 9, 512) 2359808
________________________________________________________________
block5_conv2 (Convolution2D) (None, 9, 9, 512) 2359808
________________________________________________________________
block5_conv3 (Convolution2D) (None, 9, 9, 512) 2359808
________________________________________________________________
block5_pool (MaxPooling2D) (None, 4, 4, 512) 0
================================================================
Total params: 14714688

我們將會(huì)調(diào)最后三層的參數(shù),意味著直到block4_pool的層都應(yīng)該被凍結(jié),最后幾層,block5_conv1,block5_conv2和block5_conv3應(yīng)當(dāng)被訓(xùn)練。
為什么不調(diào)更多層的參數(shù)?為什么不調(diào)整個(gè)卷積基的參數(shù)?我們可以。然而我們需要考慮下面這些:

  • 卷積基中早期的層編碼了一些通用的特征,后期的層編碼了一些比較專(zhuān)業(yè)的特征,這些才是我們?cè)谛碌膯?wèn)題中需要重新設(shè)計(jì)的。在早期層數(shù)里面調(diào)參會(huì)導(dǎo)致結(jié)果急劇下降。
  • 我們訓(xùn)練的參數(shù)愈多,我們過(guò)擬合的風(fēng)險(xiǎn)越大。卷積基有15M個(gè)參數(shù),所以在我們小樣本上訓(xùn)練有很大的風(fēng)險(xiǎn)。

因此,在我們的情況下,好的策略是訓(xùn)練最后一兩層卷積基。
讓我們結(jié)束之前剩下的小尾巴:

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

很巧妙的把之前的層全凍了,現(xiàn)在我們能夠開(kāi)始給我們的網(wǎng)絡(luò)調(diào)參了。我們將會(huì)用RMSprop優(yōu)化器,使用很低的學(xué)習(xí)率。我們選擇使用低學(xué)習(xí)率的原因是我們想要限制我們對(duì)這三層的修改的大小。改動(dòng)太大會(huì)損害原有的表示。
現(xiàn)在讓我們開(kāi)始訓(xùn)練吧:

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)

用之前的代碼畫(huà)出圖:


Training and validation accuracy for fine-tuning (training values as dots, validation values as solid lines)

Training and validation loss for fine-tuning (training values as dots, validation values as solid lines)

這個(gè)曲線(xiàn)看起來(lái)噪聲很大。為了讓它們更加具有可讀性,我們可以光滑它們通過(guò)滑動(dòng)平均:

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
plt.plot(epochs,
 smooth_curve(acc), 'bo', label='Smoothed training acc')
plt.plot(epochs,
 smooth_curve(val_acc), 'b', label='Smoothed validation acc')
plt.title('Training and validation accuracy')
plt.legend()
plt.figure()
plt.plot(epochs,
 smooth_curve(loss), 'bo', label='Smoothed training loss')
plt.plot(epochs,
 smooth_curve(val_loss), 'b', label='Smoothed validation loss')
plt.title('Training and validation loss')
plt.legend()
plt.show()
Smoothed curves for training and validation accuracy for fine-tuning (training values as dots, validation values as solid lines)

Smoothed curves for training and validation loss for fine-tuning (training values as dots, validation values as solid lines)

現(xiàn)在曲線(xiàn)看起來(lái)更加干凈穩(wěn)定了。我們看到了1%的提升。
注意到損失曲線(xiàn)并沒(méi)有展現(xiàn)出真實(shí)的提升,實(shí)際上它惡化了。你或許想知道,準(zhǔn)確率是如何提升的,如果損失沒(méi)有下降的化?答案很簡(jiǎn)單:當(dāng)我們看平均的損失值時(shí),實(shí)際上影響準(zhǔn)確率的是損失值,不是平均,因?yàn)闇?zhǔn)確率是模型分類(lèi)預(yù)測(cè)的二進(jìn)閾值的結(jié)果。模型或許仍然在在變好,只不過(guò)沒(méi)有在平均損失中體現(xiàn)出來(lái)。
我們最后能在測(cè)試集上評(píng)估模型:

test_generator = test_datagen.flow_from_directory(
 test_dir,
 target_size=(150, 150),
 batch_size=20,
 class_mode='binary')
test_loss, test_acc = model.evaluate_generator(test_generator, steps=50)
print('test acc:', test_acc)

到這里為止,我們得到了測(cè)試準(zhǔn)確率為97%。在原來(lái)的Kaggle競(jìng)賽中關(guān)于這個(gè)數(shù)據(jù)集,這是最頂尖的幾個(gè)結(jié)果之一。然而,使用現(xiàn)代深度學(xué)習(xí)技術(shù),我們可以使用只用很少的訓(xùn)練數(shù)據(jù)(大約百分之十)來(lái)達(dá)到這個(gè)結(jié)果。在20000個(gè)樣本和2000個(gè)樣本之間有著巨大的區(qū)別。

外賣(mài):在小的數(shù)據(jù)集上使用卷積網(wǎng)絡(luò)

這里有一些你需要打包帶走的東西,通過(guò)之前兩部分的學(xué)習(xí):

  • 卷積網(wǎng)絡(luò)對(duì)于計(jì)算機(jī)視覺(jué)任務(wù)來(lái)說(shuō)是最好的機(jī)器學(xué)習(xí)模型。使用少量數(shù)據(jù)訓(xùn)練得到體面的結(jié)果是可能的。
  • 在小數(shù)據(jù)集上,過(guò)擬合將會(huì)是一個(gè)主要問(wèn)題。數(shù)據(jù)增加是一個(gè)有力的對(duì)抗過(guò)擬合的方法,當(dāng)我們面臨圖像數(shù)據(jù)時(shí)。
  • 這很容易在存在的卷積網(wǎng)絡(luò)上復(fù)用數(shù)據(jù)集,通過(guò)特征提取。這對(duì)于在小數(shù)據(jù)集上工作十分有價(jià)值。
  • 作為特征提取的補(bǔ)充,使用調(diào)參,適應(yīng)存在的模型表示的一些新問(wèn)題。這將表現(xiàn)推得更遠(yuǎn)。

現(xiàn)在你已經(jīng)有了一個(gè)有力的工具來(lái)解決圖像分類(lèi)問(wèn)題,特別是小的數(shù)據(jù)集。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,071評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,409評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,569評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,360評(píng)論 6 404
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,895評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,123評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,643評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,559評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,742評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,981評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,363評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,622評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,354評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,707評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容