文章代碼來(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)器。
為什么僅僅重用卷積基呢?我們能重用全連接分類(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()
我們?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é)果:
如你所見(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)。
我們?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à)出圖:
這個(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()
現(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ù)集。