包括:
- 理解卷積神經(jīng)網(wǎng)絡(luò)
- 使用數(shù)據(jù)增強(qiáng)緩解過(guò)擬合
- 使用預(yù)訓(xùn)練卷積網(wǎng)絡(luò)做特征提取
- 微調(diào)預(yù)訓(xùn)練網(wǎng)絡(luò)模型
- 可視化卷積網(wǎng)絡(luò)學(xué)習(xí)結(jié)果以及分類決策過(guò)程
介紹卷積神經(jīng)網(wǎng)絡(luò)鳞骤,convnets,深度學(xué)習(xí)在計(jì)算機(jī)視覺(jué)方面廣泛應(yīng)用的一個(gè)網(wǎng)絡(luò)模型黍判。
卷積網(wǎng)絡(luò)介紹
在介紹卷積神經(jīng)網(wǎng)絡(luò)理論以及神經(jīng)網(wǎng)絡(luò)在計(jì)算機(jī)視覺(jué)方面應(yīng)用廣泛的原因之前豫尽,先介紹一個(gè)卷積網(wǎng)絡(luò)的實(shí)例,整體了解卷積網(wǎng)絡(luò)模型顷帖。用卷積網(wǎng)絡(luò)識(shí)別MNIST數(shù)據(jù)集美旧。
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
卷積網(wǎng)絡(luò)接收(image_height,image_width,image_channels)形狀的張量作為輸入(不包括batch size)。MNIST中贬墩,將圖片轉(zhuǎn)換成(28,28榴嗅,1)形狀,然后在第一層傳遞input_shape參數(shù)陶舞。
顯示網(wǎng)絡(luò)架構(gòu)
model.summary()
________________________________________________________________
Layer (type) Output Shape Param #
================================================================
conv2d_1 (Conv2D) (None, 26, 26, 32) 320
________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 13, 13, 32) 0
________________________________________________________________
conv2d_2 (Conv2D) (None, 11, 11, 64) 18496
________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 5, 5, 64) 0
________________________________________________________________
conv2d_3 (Conv2D) (None, 3, 3, 64) 36928
================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
可以看到每個(gè)Conv2D和MaxPooling2D網(wǎng)絡(luò)層輸出都是3D張量嗽测,形狀為(height,width,channels).隨著網(wǎng)絡(luò)層的加深,長(zhǎng)度和寬度逐漸減兄追酢唠粥;通道數(shù)通過(guò)Conv2D層的參數(shù)控制。
下一步連接Dense層停做,但當(dāng)前輸出為3D張量晤愧,需要將3D張量平鋪成1D,然后添加Dense層蛉腌。
model.add(layers.Flatten())
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))
因?yàn)槭?0分類官份,最后一層為10個(gè)神經(jīng)元只厘,激活函數(shù)為softmax。
最后的網(wǎng)絡(luò)架構(gòu)
>>> model.summary()
Layer (type) Output Shape Param #
================================================================
conv2d_1 (Conv2D) (None, 26, 26, 32) 320
________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 13, 13, 32) 0
________________________________________________________________
conv2d_2 (Conv2D) (None, 11, 11, 64) 18496
________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 5, 5, 64) 0
________________________________________________________________
conv2d_3 (Conv2D) (None, 3, 3, 64) 36928
________________________________________________________________
flatten_1 (Flatten) (None, 576) 0
________________________________________________________________
dense_1 (Dense) (None, 64) 36928
________________________________________________________________
dense_2 (Dense) (None, 10) 650
================================================================
Total params: 93,322
Trainable params: 93,322
Non-trainable params: 0
(3,3,64)輸出平攤成(576,)向量舅巷。
網(wǎng)絡(luò)訓(xùn)練
from keras.datasets import mnist
from keras.utils import to_categorical
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28, 28, 1))
train_images = train_images.astype('float32') / 255
test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255
train_labels = to_categorical(train_labels)
test_labels = to_categorical(test_labels)
model.compile(optimizer='rmsprop',loss='categorical_crossentropy',metrics=['accuracy'])
model.fit(train_images, train_labels, epochs=5, batch_size=64)
測(cè)試集上模型評(píng)估:
>>> test_loss, test_acc = model.evaluate(test_images, test_labels)
>>> test_acc
0.99080000000000001
在Dense網(wǎng)絡(luò)上準(zhǔn)確率為97.8%羔味,基本卷積網(wǎng)絡(luò)上準(zhǔn)確率到99%.為什么簡(jiǎn)單的卷積網(wǎng)絡(luò)工作效果這么好?回答之前悄谐,先了解Conv2D和MaxPooling2D層介评。
卷積操作
全連接網(wǎng)絡(luò)和卷積網(wǎng)絡(luò)的區(qū)別在于Dense全連接層學(xué)習(xí)輸入特征空間的全局模式特征,而卷積神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)輸入特征空間的局部模式特征爬舰。
卷積網(wǎng)絡(luò)的兩個(gè)關(guān)鍵特性:
- 學(xué)習(xí)具有平移不變性的模式特征:一旦學(xué)習(xí)到圖片左上角的模式特征们陆,可以在任何地方識(shí)別情屹,如右下角,這種特性使得圖片處理更加有效椅文,需要的樣本相對(duì)減少(實(shí)際生活中具有平移不變性)
-
學(xué)習(xí)模式的空間層次結(jié)構(gòu):第一個(gè)卷積層將學(xué)習(xí)小的局部模式,如邊緣皆刺,第二個(gè)卷積層將學(xué)習(xí)由第一層特征構(gòu)成的更大圖案凌摄,等等羡蛾。這使得卷積網(wǎng)絡(luò)能夠有效地學(xué)習(xí)越來(lái)越復(fù)雜和抽象的視覺(jué)概念痴怨。(現(xiàn)實(shí)生活中許多都是分級(jí)的)。
卷積在3D張量上運(yùn)算爱葵,稱為特征映射钧惧,具有兩個(gè)空間軸(高度和寬度)以及深度軸(也稱為通道軸).對(duì)RGB三原色圖片來(lái)說(shuō),通道數(shù)為3--紅乾颁、綠英岭、藍(lán)罚勾;MNIST數(shù)據(jù)集中圖片通道數(shù)為1--灰度圖尖殃。卷積操作在輸入特征圖上小分片上送丰,然后將多個(gè)操作結(jié)果生成最后的特征圖器躏。輸出的特征圖仍然是3D張量:width、height壁畸,深度可以是任意值,因?yàn)樯疃仁蔷W(wǎng)絡(luò)層的一個(gè)參數(shù)空闲,而且深度值不再代表紅綠藍(lán)顏色通道,表示過(guò)濾器的個(gè)數(shù)跌榔。過(guò)濾器對(duì)輸入數(shù)據(jù)的特定方面進(jìn)行編碼:比如在高級(jí)別,單個(gè)過(guò)濾器可以編碼“輸入中存在面部”的概念担平。
卷積定義的兩個(gè)參數(shù):
- 卷積核大性萋邸:通常為3x3,5x5.
- 卷積核個(gè)數(shù):卷積核個(gè)數(shù)等于本層網(wǎng)絡(luò)輸出層的深度展哭。
Keras中匪傍,Conv2D網(wǎng)絡(luò)層定義:Conv2D(output_depth, (window_height, window_width)) .
卷積:卷積核在上一層的特征圖的全通道進(jìn)行滑動(dòng),然后抽取形狀為(window_height,window_width,input_depth)形狀的3D片特征映挂。每個(gè)3D片特征最后轉(zhuǎn)換成1D向量(卷積運(yùn)算--張量點(diǎn)積),形狀(output_depth,)鞍时,所有的結(jié)果向量整合形成最后的3D特征(height,width,output_depth).
輸出結(jié)果的寬度和高度可能和輸入寬度高度不同,由于:
- Padding項(xiàng)锐极;
- Strides 步長(zhǎng)
最大池化 MaxPooling
最大池化層的作用在于對(duì)特征圖進(jìn)行下采樣。最大池化在特征圖中選擇window翎迁,然后每個(gè)通道的在窗口內(nèi)求最大值。概念上與卷積操作類似揍异,卷積操作在小patch 中做線性轉(zhuǎn)換衷掷,最大池化是求最大值雨涛,通過(guò)tensor的max張量操作。最大池化通常采用2x2窗口蚯根,步長(zhǎng)為2颅拦,特征圖減半。卷積通常卷積核大小為3x3碌秸,步長(zhǎng)為1。
下采樣的目的在于減少要處理特征圖的參數(shù)量允趟,通過(guò)使連續(xù)的卷積層看到越來(lái)越大的窗口(就它們所涵蓋的原始輸入的比例而言)來(lái)促使空間濾波器層次結(jié)構(gòu)涣楷。
最大池化并不是唯一的下采樣方法狮斗≌墼遥可以使用帶步長(zhǎng)卷積两芳、或平均池化怖辆,但是最大池化的工作效果更好。
小數(shù)據(jù)集上訓(xùn)練卷積網(wǎng)絡(luò)
計(jì)算機(jī)視覺(jué)中進(jìn)場(chǎng)會(huì)遇到使用很少的數(shù)據(jù)集去訓(xùn)練一個(gè)圖像分類模型特咆。“小樣本”意味著樣本量在幾百到幾萬(wàn)張. 比如貓狗分類,共4000張圖片荒叶,貓2000張,狗2000張愁茁。用2000張圖片來(lái)訓(xùn)練--1000張驗(yàn)證集,1000張測(cè)試集。
首先不做任何正則化處理菠齿,直接訓(xùn)練,得到一個(gè)baseline模型疾棵,準(zhǔn)確率為71%殉了。主要問(wèn)題在于模型過(guò)擬合。之后介紹data augmentation數(shù)據(jù)增強(qiáng)痕囱,減緩過(guò)擬合。訓(xùn)練后為82%帮掉。更有效的方法是用已訓(xùn)練好的模型最特征提取---準(zhǔn)確率90%~96%,或者微調(diào)已訓(xùn)練好的網(wǎng)絡(luò)做特征提取(97%)涩搓。這三種方法有助于在小數(shù)據(jù)集上的模型訓(xùn)練。
深度學(xué)習(xí)與小數(shù)據(jù)問(wèn)題的相關(guān)性
可能經(jīng)常聽(tīng)說(shuō):深度學(xué)習(xí)只能工作在大數(shù)據(jù)集上。這種說(shuō)法部分正確:深度學(xué)習(xí)的一個(gè)重要特性在于深度學(xué)習(xí)能自己在訓(xùn)練數(shù)據(jù)中尋找特征浇冰,而不需要人工干預(yù),而這個(gè)特性只有在大數(shù)據(jù)樣本量上才有效,特別是輸入數(shù)據(jù)維度特別高時(shí)仅仆,eg圖片。
但是咳榜,對(duì)于初學(xué)者來(lái)說(shuō),構(gòu)成大量樣本的內(nèi)容與嘗試訓(xùn)練的網(wǎng)絡(luò)的大小和深度是相對(duì)的臣樱。用幾十張圖片訓(xùn)練卷積網(wǎng)絡(luò)來(lái)解決一個(gè)十分復(fù)雜的問(wèn)題是不可能的,但如果模型比較簡(jiǎn)單經(jīng)過(guò)正則化處理,同時(shí)任務(wù)比較簡(jiǎn)單飘蚯,幾百?gòu)垐D片也能解決問(wèn)題。因?yàn)?strong>卷積網(wǎng)絡(luò)學(xué)習(xí)局部的、具有平移不變性的特征穴店,它們?cè)诟兄獑?wèn)題上具有很高的數(shù)據(jù)效率。 盡管相對(duì)缺乏數(shù)據(jù),但無(wú)需額外的特征工程呕诉,即使在非常小的圖像數(shù)據(jù)集上從頭開(kāi)始訓(xùn)練贴硫,卷積網(wǎng)絡(luò)仍然會(huì)產(chǎn)生合理的結(jié)果。
更重要的是,深度學(xué)習(xí)模型本質(zhì)上是高度可再利用的:例如税灌,可以采用在大規(guī)模數(shù)據(jù)集上訓(xùn)練的圖像分類或語(yǔ)音到文本模型,只需進(jìn)行微小的更改,就可以重新用于顯著不同的問(wèn)題上。具體而言攻走,以計(jì)算機(jī)視覺(jué)為例,許多預(yù)先訓(xùn)練好的模型(通常在ImageNet數(shù)據(jù)集上訓(xùn)練)提供公開(kāi)下載,當(dāng)樣本量少時(shí)逛裤,可以用在模型中(做特征提取使用)提升工作效果。
數(shù)據(jù)下載
Keras中沒(méi)有包括Dogs vs. Cats數(shù)據(jù)集阳堕。可以在Kaggle上下載。
圖片格式為JPEGs.數(shù)據(jù)集包含25000張貓狗圖片(一半一半)。下載解壓縮后,創(chuàng)建一個(gè)新數(shù)據(jù)集包括3個(gè)文件夾:每類1000張的訓(xùn)練集换途、每類500張的驗(yàn)證集和每類500張的測(cè)試集。
import os,shutil
#原始數(shù)據(jù)
original_dataset_dir = '/Users/fchollet/Downloads/kaggle_original_data'
#新數(shù)據(jù)集目錄
base_dir = '/Users/fchollet/Downloads/cats_and_dogs_small'
os.mkdir(base_dir)
#創(chuàng)建訓(xùn)練集、驗(yàn)證集、測(cè)試集目錄
train_dir = os.path.join(base_dir, 'train')
os.mkdir(train_dir)
validation_dir = os.path.join(base_dir, 'validation')
os.mkdir(validation_dir)
test_dir = os.path.join(base_dir, 'test')
os.mkdir(test_dir)
#創(chuàng)建對(duì)應(yīng)數(shù)據(jù)集下不同類別的目錄
train_cats_dir = os.path.join(train_dir, 'cats')
os.mkdir(train_cats_dir)
train_dogs_dir = os.path.join(train_dir, 'dogs')
os.mkdir(train_dogs_dir)
validation_cats_dir = os.path.join(validation_dir, 'cats')
os.mkdir(validation_cats_dir)
validation_dogs_dir = os.path.join(validation_dir, 'dogs')
os.mkdir(validation_dogs_dir)
test_cats_dir = os.path.join(test_dir, 'cats')
os.mkdir(test_cats_dir)
test_dogs_dir = os.path.join(test_dir, 'dogs')
os.mkdir(test_dogs_dir)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000)]#取前1000張貓圖片
for fname in fnames:#將前一千張貓圖片復(fù)制到新數(shù)據(jù)集目錄下
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1000, 1500)]#取500張貓圖片
for fname in fnames:#500張貓圖片復(fù)制到驗(yàn)證集
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_cats_dir, fname)
shutil.copyfile(src, dst)
fnames = ['cat.{}.jpg'.format(i) for i in range(1500, 2000)]#取500張貓圖片
for fname in fnames:#500張貓圖片做測(cè)試集
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_cats_dir, fname)
shutil.copyfile(src, dst)
#狗圖片
fnames = ['dog.{}.jpg'.format(i) for i in range(1000)]#1000張狗圖片做訓(xùn)練集
for fname in fnames:
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(train_dogs_dir, fname)
shutil.copyfile(src, dst)
fnames = ['dog.{}.jpg'.format(i) for i in range(1000, 1500)]
for fname in fnames:#500張狗圖片做驗(yàn)證集
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(validation_dogs_dir, fname)
shutil.copyfile(src, dst) Copies the next 500
fnames = ['dog.{}.jpg'.format(i) for i in range(1500, 2000)]
for fname in fnames:#500張狗圖片做測(cè)試集
src = os.path.join(original_dataset_dir, fname)
dst = os.path.join(test_dogs_dir, fname)
shutil.copyfile(src, dst)
構(gòu)建模型
from keras import layers
from keras import models
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
模型架構(gòu):
>>> model.summary()
Layer (type) Output Shape Param #
================================================================
conv2d_1 (Conv2D) (None, 148, 148, 32) 896
________________________________________________________________
maxpooling2d_1 (MaxPooling2D) (None, 74, 74, 32) 0
________________________________________________________________
conv2d_2 (Conv2D) (None, 72, 72, 64) 18496
________________________________________________________________
maxpooling2d_2 (MaxPooling2D) (None, 36, 36, 64) 0
________________________________________________________________
conv2d_3 (Conv2D) (None, 34, 34, 128) 73856
________________________________________________________________
maxpooling2d_3 (MaxPooling2D) (None, 17, 17, 128) 0
________________________________________________________________
conv2d_4 (Conv2D) (None, 15, 15, 128) 147584
________________________________________________________________
maxpooling2d_4 (MaxPooling2D) (None, 7, 7, 128) 0
________________________________________________________________
flatten_1 (Flatten) (None, 6272) 0
________________________________________________________________
dense_1 (Dense) (None, 512) 3211776
________________________________________________________________
dense_2 (Dense) (None, 1) 513
================================================================
Total params: 3,453,121
Trainable params: 3,453,121
Non-trainable params: 0
編譯階段,使用RMSProp優(yōu)化算法示血,binary crossentropy為損失函數(shù)。
from keras import optimizers
model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
數(shù)據(jù)預(yù)處理
數(shù)據(jù)在送到網(wǎng)絡(luò)模型之前應(yīng)該轉(zhuǎn)換成浮點(diǎn)類型的張量麸拄。目前數(shù)據(jù)集中數(shù)據(jù)格式為JPEG,所以處理步驟大致為:
- 讀取圖片文件;
- 將JPEG格式轉(zhuǎn)換為RGB像素值主穗;
- 轉(zhuǎn)換成浮點(diǎn)類型張量;
- 將像素值(0~255)縮放到[0,1]之間。
針對(duì)上述步驟闹瞧,Keras中有自動(dòng)化處理方法纵竖。Keras中有一個(gè)圖像處理模塊已脓,keras.preprocessing.image. 其中包括一個(gè)ImageDataGenerator類通殃,可以將磁盤(pán)上的圖片文件自動(dòng)轉(zhuǎn)換成預(yù)處理的張量batch批量度液。使用方法:
from keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
#將圖片轉(zhuǎn)換成150x150,類別為2画舌;class_mode 確定返回標(biāo)簽的類型binary二分類 1D類型
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')
生成器generator的數(shù)據(jù)結(jié)果為150x150 RGB批量圖片堕担,尺寸為(20,150,150,3)曲聂,二進(jìn)制標(biāo)簽形狀(20,)霹购。每個(gè)批量大小為20個(gè)樣本(batch_size為20). 注意-生成器無(wú)限期地生成這些批次:它在目標(biāo)文件夾的圖像上無(wú)休止地循環(huán)。
使用generator數(shù)據(jù)生成器對(duì)模型進(jìn)行訓(xùn)練朋腋。使用fit_generator方法齐疙,對(duì)于數(shù)據(jù)生成器來(lái)說(shuō)膜楷,相當(dāng)于fit方法。fit_generator第一個(gè)參數(shù)是Python生成器類型贞奋,能不斷地生成輸入和標(biāo)簽批量赌厅。因?yàn)閿?shù)據(jù)不斷生成,Keras模型需要知道在聲明一個(gè)epoch之前從發(fā)生器中抽取多少批量轿塔;steps_per_epoch參數(shù):從生成器中生成 steps_per_epoch個(gè)批量數(shù)據(jù)特愿;在經(jīng)過(guò)steps_per_epoch次梯度下降后,在下一個(gè)epoch上進(jìn)行訓(xùn)練勾缭。在這里洽议,批量大小為20,一個(gè)epoch有100個(gè)批量漫拭,生成2000張圖片樣本。
使用fit_generator方法混稽,可以傳遞validataion_data參數(shù)采驻,和fit方法相似。值得注意的是匈勋,這個(gè)參數(shù)可以賦值為數(shù)據(jù)生成器礼旅,也可以是numpy數(shù)組的元組。如果validation_data參數(shù)是數(shù)據(jù)生成器洽洁,生成器能不斷地生成數(shù)據(jù)痘系,所以需要設(shè)置validation_steps參數(shù),確定從生成器中生成多少驗(yàn)證集批量饿自。
history = model.fit_generator(train_generator,steps_per_epoch=100,epoch=30汰翠,validation_data=validation_generator,validation_steps=50)
模型保存:
model.save('cats_and_dogs_small_1.h5')
訓(xùn)練集驗(yàn)證集準(zhǔn)確率、損失值變化:
可以發(fā)現(xiàn)模型發(fā)生過(guò)擬合現(xiàn)象昭雌。訓(xùn)練準(zhǔn)確率隨著時(shí)間線性增加复唤,直到100%,而驗(yàn)證集準(zhǔn)確率在70-72%波動(dòng)烛卧。驗(yàn)證集損失在5個(gè)epoch之后達(dá)到最小值佛纫,之后開(kāi)始波動(dòng);訓(xùn)練集損失線性減少直到為0
因?yàn)橛?xùn)練集只有2000張圖片总放,遇到的第一個(gè)問(wèn)題就是模型過(guò)擬合呈宇。Dropout、權(quán)重衰減可以減緩過(guò)擬合局雄,還有一個(gè)計(jì)算機(jī)視覺(jué)任務(wù)中甥啄,經(jīng)常使用的處理方法:數(shù)據(jù)增強(qiáng)data augmentation。
數(shù)據(jù)增強(qiáng)
過(guò)度擬合是由于樣本太少而無(wú)法學(xué)習(xí)哎榴,導(dǎo)致無(wú)法訓(xùn)練可以推廣到新數(shù)據(jù)的模型型豁。給定無(wú)限的數(shù)據(jù)僵蛛,模型可以學(xué)習(xí)到手頭數(shù)據(jù)分布的每個(gè)可能方面:永遠(yuǎn)不會(huì)過(guò)擬合。數(shù)據(jù)增強(qiáng)采用從現(xiàn)有訓(xùn)練樣本生成更多訓(xùn)練數(shù)據(jù)的方法迎变,通過(guò)大量隨機(jī)變換來(lái)增加樣本充尉,從而產(chǎn)生新的可靠的圖像樣本。
目標(biāo)是在訓(xùn)練時(shí)衣形,模型將永遠(yuǎn)不會(huì)看到兩張完全相同的圖片驼侠。這有助于模型觀察數(shù)據(jù)的更多方面并更好地概括數(shù)據(jù)。
Keras中谆吴,可以通過(guò)實(shí)例化ImageDataGenerator實(shí)例倒源,確定圖片轉(zhuǎn)換方法,從而實(shí)現(xiàn)數(shù)據(jù)增強(qiáng)句狼。
datagen = ImageDataGenerator(
rotation_range=40,#最大旋轉(zhuǎn)角度
width_shift_range=0.2,#水平隨機(jī)平移圖片的范圍笋熬,比例
height_shift_range=0.2,#垂直隨機(jī)平移圖片的范圍
shear_range=0.2,#隨機(jī)應(yīng)用剪切變換
zoom_range=0.2,#隨機(jī)縮放圖片
horizontal_flip=True,#隨機(jī)翻轉(zhuǎn)圖片
fill_mode='nearest')#用于填充新創(chuàng)建的像素的策略,在旋轉(zhuǎn)或?qū)挾?高度偏移后出現(xiàn)
如果使用這樣的數(shù)據(jù)增強(qiáng)配置訓(xùn)練新網(wǎng)絡(luò)腻菇,網(wǎng)絡(luò)將永遠(yuǎn)不會(huì)看到兩張相同的輸入圖片胳螟。但它看到的輸入仍然是嚴(yán)重相互關(guān)聯(lián)的,因?yàn)樗鼈儊?lái)自少量原始圖像 - 無(wú)法生成新信息筹吐,只能重新混合現(xiàn)有信息糖耸。因此,這不可能完全擺脫過(guò)擬合丘薛。為了進(jìn)一步減緩過(guò)擬合嘉竟,需要增加Dropout層罚缕,在全連接層之前沾乘。
新網(wǎng)絡(luò)模型:
model = models.Sequential()
model.add(layers.Conv2D(32, (3, 3), activation='relu',
input_shape=(150, 150, 3)))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(64, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Conv2D(128, (3, 3), activation='relu'))
model.add(layers.MaxPooling2D((2, 2)))
model.add(layers.Flatten())
model.add(layers.Dropout(0.5))
model.add(layers.Dense(512, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])
使用數(shù)據(jù)增強(qiáng)和Dropout訓(xùn)練網(wǎng)絡(luò)请琳。
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,)
test_datagen = ImageDataGenerator(rescale=1./255)
train_generator = train_datagen.flow_from_directory(train_dir,
target_size=(150, 150),batch_size=32,class_mode='binary')
validation_generator = test_datagen.flow_from_directory(validation_dir,
target_size=(150, 150),batch_size=32,class_mode='binary')
history = model.fit_generator(train_generator,steps_per_epoch=100,
epochs=100,validation_data=validation_generator,validation_steps=50)
model.save('cats_and_dogs_small_2.h5')#模型保存
使用數(shù)據(jù)增強(qiáng)和Dropout后弟头,訓(xùn)練集侣签、驗(yàn)證集準(zhǔn)確率和損失函數(shù)變化款青。
模型不再過(guò)擬合:訓(xùn)練集曲線和驗(yàn)證集曲線幾乎相互吻合渔彰。準(zhǔn)確率82%窝趣,提高了15%左右吏够。使用正則化技術(shù)勾给,微調(diào)網(wǎng)絡(luò)超參數(shù),模型準(zhǔn)確率會(huì)進(jìn)一步提高锅知,到86%~87%.但是很難繼續(xù)提高播急,因?yàn)橛?xùn)練數(shù)據(jù)有限,樣本量太少售睹。另一種方法桩警,可以采用預(yù)先訓(xùn)練好的網(wǎng)絡(luò)模型,做特征提取昌妹,提高準(zhǔn)確率捶枢。
使用預(yù)訓(xùn)練卷積網(wǎng)絡(luò)
在小圖像數(shù)據(jù)集上使用深度學(xué)習(xí)的一種常見(jiàn)且高效的方法是使用預(yù)訓(xùn)練網(wǎng)絡(luò)握截。預(yù)訓(xùn)練網(wǎng)絡(luò)是先前在大型數(shù)據(jù)集上訓(xùn)練的已保存網(wǎng)絡(luò),通常是處理大規(guī)模圖像分類任務(wù)烂叔。如果這個(gè)原始數(shù)據(jù)集足夠大且代表性強(qiáng)谨胞,則預(yù)訓(xùn)練網(wǎng)絡(luò)學(xué)習(xí)的特征的空間層次結(jié)構(gòu)可以有效地充當(dāng)視覺(jué)世界的通用模型,因此其特征可以證明對(duì)許多不同的計(jì)算機(jī)視覺(jué)問(wèn)題都有用蒜鸡,甚至這些新問(wèn)題可能涉及與原始任務(wù)完全不同胯努。例如,可以在ImageNet上訓(xùn)練網(wǎng)絡(luò)(其中類主要是動(dòng)物和日常物品)逢防,然后將這個(gè)訓(xùn)練好的網(wǎng)絡(luò)重新用于識(shí)別圖像中的家具物品任務(wù)中叶沛。與許多較舊的淺學(xué)習(xí)方法(傳統(tǒng)機(jī)器學(xué)習(xí)方法)相比,學(xué)習(xí)特征在不同問(wèn)題中的這種可移植性是深度學(xué)習(xí)的關(guān)鍵優(yōu)勢(shì)忘朝,并且它使得深度學(xué)習(xí)對(duì)于小數(shù)據(jù)問(wèn)題非常有效灰署。
比如在ImageNet數(shù)據(jù)集上訓(xùn)練的網(wǎng)絡(luò)模型(140萬(wàn)個(gè)標(biāo)記圖像和1,000個(gè)不同類)。ImageNet包含許多動(dòng)物類別局嘁,包括不同種類的貓和狗氓侧,因此可以期望在狗與貓的分類問(wèn)題上表現(xiàn)良好。
使用VGG16網(wǎng)絡(luò)架構(gòu)导狡,它是ImageNet的簡(jiǎn)單且廣泛使用的convnet架構(gòu)。
使用預(yù)訓(xùn)練網(wǎng)絡(luò)有兩種方法:特征提取和微調(diào)偎痛。
特征提取
特征提取包括使用先前網(wǎng)絡(luò)學(xué)習(xí)的表示從新樣本中提取有趣特征旱捧。然后,這些功能將通過(guò)一個(gè)新的分類器運(yùn)行踩麦,該分類器從頭開(kāi)始訓(xùn)練枚赡。
如前所述,用于圖像分類的網(wǎng)絡(luò)包含兩部分:它們以一系列池化和卷積層開(kāi)始谓谦,并以密集連接的分類器結(jié)束贫橙。第一部分稱為模型的卷積基礎(chǔ)。在卷積網(wǎng)絡(luò)中反粥,特征提取包括獲取先前訓(xùn)練的網(wǎng)絡(luò)的卷積基礎(chǔ)卢肃,通過(guò)它運(yùn)行新數(shù)據(jù),以及在輸出之上訓(xùn)練新的分類器才顿。
為什么只重用卷積網(wǎng)絡(luò)莫湘?是否可以重復(fù)使用全連接分類器?一般來(lái)說(shuō)郑气,應(yīng)該避免這樣做幅垮。原因是卷積網(wǎng)絡(luò)學(xué)習(xí)的表示可能更通用,因此更可重復(fù)使用:特征網(wǎng)絡(luò)的特征圖是圖片上一般概念的存在圖尾组,無(wú)論處理的計(jì)算機(jī)視覺(jué)問(wèn)題是什么忙芒,都可能是有用的示弓。但是,分類器學(xué)習(xí)的表示必然特定于訓(xùn)練模型的類集 - 它們將僅包含關(guān)于整個(gè)圖像中該類或該類的存在概率的信息呵萨。此外奏属,在全連接網(wǎng)絡(luò)層的輸出表示不再包含有關(guān)對(duì)象在輸入圖像中的位置信息:這些表示消除了空間的概念甘桑,而卷積特征圖還可以描述對(duì)象的位置信息拍皮。對(duì)于對(duì)象位置很重要的問(wèn)題铆帽,全連接的特征表示在很大程度上是無(wú)用的。
注意德谅,由特定卷積層提取的表示的一般性(以及因此可重用性)的級(jí)別取決于模型中網(wǎng)絡(luò)層的深度爹橱。模型中較早出現(xiàn)的圖層會(huì)提取局部的,高度通用的特征貼圖(例如可視邊緣窄做,顏色和紋理)愧驱,而較高層的圖層會(huì)提取更抽象的概念(例如“貓耳朵”或“狗眼”) 。因此椭盏,如果訓(xùn)練數(shù)據(jù)集與訓(xùn)練原始模型的數(shù)據(jù)集有很大差異组砚,那么最好只使用模型的前幾層來(lái)進(jìn)行特征提取,而不是使用整個(gè)卷積網(wǎng)絡(luò)的輸出掏颊。
在這種情況下糟红,因?yàn)镮mageNet類集包含多個(gè)dog和cat類,所以重用原始模型的全連接層中包含的信息可能是有益的乌叶。但是我們會(huì)選擇不這樣做盆偿,以便涵蓋新問(wèn)題的類集不與原始模型的類集重疊的更一般情況。通過(guò)使用在ImageNet上訓(xùn)練的VGG16網(wǎng)絡(luò)的卷積網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)這一點(diǎn)准浴,從貓和狗圖像中提取有趣的特征事扭,然后在這些特征之上訓(xùn)練狗與貓的分類器。
Keras中可以直接獲取VGG16模型乐横,包含在keras.applications模塊中求橄。其中還包括其他模型:
- Xception
- Inception V3
- ResNet50
- VGG16
- VGG19
- MobileNet
實(shí)例化VGG16模型:
from keras.application import vgg16
conv_base = VGG16(weights='imagenet',include_top=False,input_shape=(150, 150, 3))
構(gòu)造器的3個(gè)參數(shù):
- weights:讀取權(quán)重保存點(diǎn)文件,初始化模型葡公;
- include_top:是否包含網(wǎng)絡(luò)的全連接層谈撒。模型,全連接層分類類別在ImageNet上的1000類匾南。因?yàn)橐褂米约簞?chuàng)建的全連接分類器啃匿,可以不使用原來(lái)的全連接層;
- input_shape:送到模型中圖片張量的形狀;參數(shù)是可選的:如果不傳遞參數(shù)溯乒,網(wǎng)絡(luò)可以處理任意形狀的輸入夹厌。
VGG16網(wǎng)絡(luò)模型架構(gòu):
>>> 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).之后連接到全連接分類器上。有兩種處理方法:
- 訓(xùn)練卷積網(wǎng)絡(luò)模型部分裆悄,將輸出結(jié)果保存在磁盤(pán)上矛纹,之后讀取磁盤(pán)上的數(shù)據(jù)送到全連接分類器中。優(yōu)點(diǎn)在于運(yùn)行高效光稼、快速或南,因?yàn)榫矸e網(wǎng)絡(luò)部分針對(duì)每張輸入圖片只運(yùn)行一次,而卷積部分是最耗時(shí)艾君、耗費(fèi)運(yùn)算能力資源的采够;但同時(shí)不能使用數(shù)據(jù)增強(qiáng);
- 將全連接分類器和卷積部分整合到一起冰垄,在輸入數(shù)據(jù)上端到端的運(yùn)行蹬癌;可以使用數(shù)據(jù)增強(qiáng),因?yàn)槊看屋斎肽P偷膱D像都會(huì)通過(guò)模型經(jīng)過(guò)卷積部分虹茶。
不使用數(shù)據(jù)增強(qiáng)的特征提取
使用ImageDataGenerator將磁盤(pán)文件和標(biāo)簽讀取成張量形式逝薪,運(yùn)行卷積部分的predict提取圖片特征。
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')#訓(xùn)練數(shù)據(jù)
validation_dir = os.path.join(base_dir, 'validation')#驗(yàn)證數(shù)據(jù)
test_dir = os.path.join(base_dir, 'test')#測(cè)試數(shù)據(jù)
datagen = ImageDataGenerator(rescale=1./255)#
batch_size = 20
def extract_features(directory, sample_count):#讀取文件蝴罪,轉(zhuǎn)換成張量形式董济;
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:#生成對(duì)應(yīng)批量數(shù)據(jù)
features_batch = conv_base.predict(inputs_batch)#卷積特征提取結(jié)果
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)
當(dāng)前提取特征形狀為(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))
定義全連接分類器虏肾,將特征數(shù)據(jù)送到分類器中訓(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))
驗(yàn)證集暂衡、訓(xùn)練集上損失值和準(zhǔn)確率變化情況:
驗(yàn)證集準(zhǔn)確率達(dá)到90%.但圖示顯示模型從開(kāi)始就過(guò)擬合了。使用數(shù)據(jù)正增強(qiáng)可以緩解一下崖瞭。
使用數(shù)據(jù)增強(qiáng)的特征提取
和第一種方法相比狂巢,運(yùn)算速度更慢、耗費(fèi)運(yùn)算資源更多书聚,通常需要GPU唧领。如果GPU上速度還慢,最好使用第一種方法雌续。
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'))
模型架構(gòu)為:
>>> 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
在模型訓(xùn)練之前斩个,需要對(duì)卷積部分進(jìn)行freeze‘凍住’。Freezing網(wǎng)絡(luò)層意味著避免在訓(xùn)練過(guò)程網(wǎng)絡(luò)層的參數(shù)被更新驯杜。如果不做‘freeze’處理受啥,訓(xùn)練過(guò)程中卷積部分提取的特征會(huì)逐漸改變。
在Keras中,可以通過(guò)設(shè)置trainable參數(shù)為False進(jìn)行Freeze處理滚局。
conv_base.trainable = False
注意居暖,為了使這些更改生效,必須首先編譯模型藤肢。如果在編譯后修改了權(quán)重可訓(xùn)練性太闺,則應(yīng)重新編譯模型,否則將忽略這些更改嘁圈。
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)
損失值和準(zhǔn)確率變化:
驗(yàn)證集上準(zhǔn)確率達(dá)到96%.
模型微調(diào)Fine-tuning
另一種廣泛使用的模型重用技術(shù)省骂,對(duì)特征提取的補(bǔ)充,就是模型參數(shù)微調(diào)最住。微調(diào)包括解凍用于特征提取的凍結(jié)模型基礎(chǔ)的一些頂層钞澳,并聯(lián)合訓(xùn)練模型的新添加部分(在這種情況下,全連接的分類器)和這些頂層温学。這稱為微調(diào)略贮,因?yàn)樗晕⒄{(diào)整了重復(fù)使用的模型的抽象表示,以使它們與手頭的問(wèn)題更相關(guān)仗岖。
微調(diào)網(wǎng)絡(luò)模型步驟:
- 在已經(jīng)訓(xùn)練好的網(wǎng)絡(luò)模型上添加自定義網(wǎng)絡(luò)模型逃延;
- Freeze”凍住“訓(xùn)練好的模型;
- 訓(xùn)練添加部分網(wǎng)絡(luò)轧拄;
- Unfreeze”解凍“部分base 網(wǎng)絡(luò)揽祥;
- 重新訓(xùn)練解凍部分和添加部分。
base部分網(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: 14714688
微調(diào)模型的最后3個(gè)卷積層檩电,意味著到block4_pool之前都被”凍住“拄丰,網(wǎng)絡(luò)層block5_conv1,block5_conv2和block5_conv3都是可訓(xùn)練的。
為什么不微調(diào)更多層俐末?為什么不微調(diào)整個(gè)卷積網(wǎng)絡(luò)料按?可以這么做。但是你需要考慮以下幾點(diǎn):
- 卷積網(wǎng)絡(luò)中的前幾層編碼更通用卓箫,可重用的特征载矿,而更高層的編碼更專業(yè)的特征。微調(diào)更專業(yè)的功能更有用烹卒,因?yàn)檫@些功能需要重新用于新問(wèn)題闷盔。微調(diào)下層會(huì)有快速下降的回報(bào)。
- 訓(xùn)練的參數(shù)越多旅急,越有可能過(guò)度擬合逢勾。卷積網(wǎng)絡(luò)模型有1500萬(wàn)個(gè)參數(shù),因此嘗試在小數(shù)據(jù)集上訓(xùn)練它會(huì)有風(fēng)險(xiǎn)藐吮。
一個(gè)很好的策略是只微調(diào)卷積基礎(chǔ)中的前兩個(gè)或三個(gè)層溺拱。
conv_base.trainable = True
set_trainable = False
for layer in conv_base.layers:
if layer.name == 'block5_conv1':#block5_conv1可訓(xùn)練
set_trainable = True#flag可訓(xùn)練
if set_trainable:
layer.trainable = True#block5_conv1網(wǎng)絡(luò)層設(shè)置為可訓(xùn)練逃贝;
else:
layer.trainable = False#其它層不可訓(xùn)練
現(xiàn)在可以開(kāi)始微調(diào)網(wǎng)絡(luò)了。使用RMSProp優(yōu)化器以非常低的學(xué)習(xí)速率執(zhí)行此操作盟迟。使用低學(xué)習(xí)率的原因是希望限制對(duì)正在微調(diào)的三個(gè)網(wǎng)絡(luò)層的表示所做的修改的幅度秋泳。太大的更新可能會(huì)損害這些表示。
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)
驗(yàn)證集攒菠、測(cè)試集上損失函數(shù)和準(zhǔn)確率變化:
請(qǐng)注意迫皱,損失曲線沒(méi)有顯示任何真正的改善(事實(shí)上,它正在惡化)辖众。如果損失沒(méi)有減少卓起,準(zhǔn)確度如何保持穩(wěn)定或改善?答案很簡(jiǎn)單:展示的是指數(shù)損失值的平均值;但是對(duì)于準(zhǔn)確性而言重要的是損失值的分布凹炸,而不是它們的平均值戏阅,因?yàn)榫仁悄P皖A(yù)測(cè)的類概率的二元閾值的結(jié)果。即使沒(méi)有反映在平均損失中啤它,該模型仍可能會(huì)有所改善奕筐。
在測(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)
#97%
小結(jié)
- Convnets是用于計(jì)算機(jī)視覺(jué)任務(wù)的最佳機(jī)器學(xué)習(xí)模型。即使在非常小的數(shù)據(jù)集上也可以從頭開(kāi)始訓(xùn)練变骡,并獲得不錯(cuò)的結(jié)果离赫。
- 在小型數(shù)據(jù)集上,過(guò)度擬合將是主要問(wèn)題塌碌。在處理圖像數(shù)據(jù)時(shí)渊胸,數(shù)據(jù)增強(qiáng)是對(duì)抗過(guò)度擬合的有效方法;
- 通過(guò)重用現(xiàn)有的卷積網(wǎng)絡(luò)模型可以在新數(shù)據(jù)集上做特征提忍ㄗ薄翎猛;這是處理小圖像數(shù)據(jù)集的有用技術(shù)。
- 作為特征提取的補(bǔ)充接剩,可以使用模型微調(diào)切厘,讓模型適應(yīng)新問(wèn)題---以前現(xiàn)有模型可以學(xué)習(xí)新問(wèn)題的特征表示,能進(jìn)一步推動(dòng)性能懊缺。
卷積學(xué)習(xí)結(jié)果可視化
人們常說(shuō)疫稿,深度學(xué)習(xí)模型是“黑匣子”:學(xué)習(xí)表示難以提取以及很難以人類可讀的形式呈現(xiàn)。雖然對(duì)于某些類型的深度學(xué)習(xí)模型來(lái)說(shuō)這是部分正確的桐汤,但對(duì)于convnets來(lái)說(shuō)絕對(duì)不是這樣而克。由convnet學(xué)習(xí)的表示非常適合可視化靶壮,這在很大程度上是因?yàn)樗鼈兪且曈X(jué)概念的表示怔毛。三種常見(jiàn)的可視化方法:
- 可視化中間信號(hào)輸出(中間激活)--有助于了解連續(xù)的convnet層如何轉(zhuǎn)換輸入數(shù)據(jù),以及了解各個(gè)convnet過(guò)濾器的含義腾降;
- 可視化convnets過(guò)濾器---有助于準(zhǔn)確理解convnet中每個(gè)過(guò)濾器可接受的視覺(jué)模式或概念拣度;
- 可視化圖像中類激活的熱圖---有助于了解圖像的哪些部分被識(shí)別為屬于給定的類,從而可以在圖像中本地化對(duì)象。
可視化中間激活值
可視化中間激活包括在給定特定輸入的情況下顯示由網(wǎng)絡(luò)中的各種卷積和池化層輸出的特征映射(層的輸出通常稱為其激活抗果,激活函數(shù)的輸出)筋帖。這給出了如何將輸入分解為網(wǎng)絡(luò)學(xué)習(xí)的不同過(guò)濾器的視圖。希望從三個(gè)維度:寬度冤馏,高度和深度(通道)可視化特征圖日麸。每個(gè)通道編碼相對(duì)獨(dú)立的特征,因此可視化這些特征圖的正確方法是通過(guò)將每個(gè)通道的內(nèi)容獨(dú)立地繪制為2D圖像逮光。
加載保存的模型
from keras.models import load_model
model = load_model('cats_and_dogs_small_2.h5')
img_path = './cats_and_dogs_small/test/cats/cat.1700.jpg'#給定一張圖片
from keras.preprocessing import image
import numpy as np
img = image.load_img(img_path, target_size=(150, 150))
img_tensor = image.img_to_array(img)
img_tensor = np.expand_dims(img_tensor, axis=0)
img_tensor /= 255.
查看所有網(wǎng)絡(luò)層的輸出結(jié)果:
from keras import models
layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model=models.Model(inputs=model.input,outputs=layer_outputs)
輸入圖像輸入時(shí)代箭,此模型返回原始模型中網(wǎng)絡(luò)層激活的值。一個(gè)多輸出模型:到目前為止涕刚,看到的模型只有一個(gè)輸入和一個(gè)輸出嗡综。在一般情況下,模型可以具有任意數(shù)量的輸入和輸出杜漠。這個(gè)有一個(gè)輸入和八個(gè)輸出:每層激活一個(gè)輸出极景。
模型運(yùn)行:
activations = activation_model.predict(img_tensor)#輸出:每層激活值一個(gè)數(shù)組
第一個(gè)卷積層結(jié)果:
first_layer_activation = activations[0]
print(first_layer_activation.shape)
#(1, 148, 148, 32)
import matplotlib.pyplot as plt
plt.matshow(first_layer_activation[0, :, :, 4], cmap='viridis')#第4通道可視化
網(wǎng)絡(luò)中所有激活函數(shù)值可視化,將8個(gè)網(wǎng)絡(luò)層激活函數(shù)值的所有通道結(jié)果顯示出來(lái)驾茴。
layer_names = []
for layer in model.layers[:8]:
layer_names.append(layer.name)
images_per_row = 16
for layer_name, layer_activation in zip(layer_names, activations):
n_features = layer_activation.shape[-1]
size = layer_activation.shape[1]
n_cols = n_features // images_per_row
display_grid = np.zeros((size * n_cols, images_per_row * size))
for col in range(n_cols):
for row in range(images_per_row):
channel_image=layer_activation[0,:,:,col*images_per_row+row]
channel_image -= channel_image.mean()
channel_image /= channel_image.std()
channel_image *= 64
channel_image += 128
channel_image=np.clip(channel_image,0, 255).astype('uint8')
display_grid[col*size:(col+1)*size,row*size:(row+1)*size]=channel_image
scale = 1. / size
plt.figure(figsize=(scale * display_grid.shape[1],
scale * display_grid.shape[0]))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
[圖片上傳失敗...(image-49e53e-1532251766723)]
值得注意的是:
- 第一層充當(dāng)各種邊緣檢測(cè)器的集合盼樟。在那個(gè)階段,激活值幾乎保留了初始圖片中的所有信息沟涨;
- 隨著網(wǎng)絡(luò)層的增加恤批,激活變得越來(lái)越抽象,在視覺(jué)上也不那么容易理解裹赴;開(kāi)始編碼更高級(jí)別的概念喜庞,如“貓耳”和“貓眼∑宸担”更高級(jí)別的表示關(guān)于圖像的視覺(jué)內(nèi)容越來(lái)越少延都,關(guān)于圖像類型的信息越來(lái)越多;
-
激活的稀疏性隨著層的深度而增加:在第一層中睛竣,所有濾波器都由輸入圖像激活;但在以下圖層中晰房,越來(lái)越多的過(guò)濾器為空白。這意味著在輸入圖像中找不到濾鏡編碼的圖案射沟。
剛剛證明了深度神經(jīng)網(wǎng)絡(luò)所學(xué)習(xí)的表征的一個(gè)重要的普遍特征:由層提取的特征隨著層的深度而變得越來(lái)越抽象殊者。更高層的激活越來(lái)越少地顯示關(guān)于所看到的特定輸入的信息,越來(lái)越多關(guān)于目標(biāo)的信息. 深度神經(jīng)網(wǎng)絡(luò)有效地充當(dāng)信息蒸餾管道验夯,原始數(shù)據(jù)進(jìn)入(在這種情況下為RGB圖像)并被重復(fù)變換以便過(guò)濾掉無(wú)關(guān)信息(例如猖吴,圖像的特定視覺(jué)外觀),以及有用的信息被放大和細(xì)化(例如挥转,圖像的類)海蔽。
可視化卷積核
另一種檢查由convnet學(xué)習(xí)的過(guò)濾器的簡(jiǎn)單方法是顯示每個(gè)過(guò)濾器要響應(yīng)的視覺(jué)模式共屈。這可以通過(guò)輸入空間中的漸變上升來(lái)完成:將漸變下降應(yīng)用于convnet的輸入圖像的值空間上;從空白輸入圖像開(kāi)始党窜,最大化特定過(guò)濾器的響應(yīng)拗引。得到的輸入圖像將是所選濾波器最大響應(yīng)的圖像。
過(guò)程很簡(jiǎn)單:您將構(gòu)建一個(gè)損失函數(shù)幌衣,使給定卷積層中給定濾波器的值最大化矾削,然后您將使用隨機(jī)梯度下降來(lái)調(diào)整輸入圖像的值,以便最大化此激活值豁护。例如怔软,這是在VGG16的block3_conv1中激活過(guò)濾器0的損失.
from keras.applications import VGG16
from keras import backend as K
model = VGG16(weights='imagenet',include_top=False)
layer_name = 'block3_conv1'
filter_index = 0
layer_output=model.get_layer(layer_name).output#得到block3_conv1的激活值
loss = K.mean(layer_output[:, :, :, filter_index])
要實(shí)現(xiàn)梯度下降,需要相對(duì)于模型輸入求損失的梯度择镇。
grads = K.gradients(loss, model.input)[0]
使用梯度正則化平滑梯度值
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
計(jì)算損失張量和梯度張量挡逼。使用keras的iterate函數(shù),接收numpy張量腻豌,返回關(guān)于損失和梯度的張量列表家坎。
iterate = K.function([model.input], [loss, grads])
import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])
將張量轉(zhuǎn)換為圖片格式:
def deprocess_image(x):
x -= x.mean()
x /= (x.std() + 1e-5)
x *= 0.1
x += 0.5
x = np.clip(x, 0, 1)
x *= 255
x = np.clip(x, 0, 255).astype('uint8')
return x
整合卷積核可視化函數(shù):
def generate_pattern(layer_name, filter_index, size=150):
layer_output = model.get_layer(layer_name).output
loss = K.mean(layer_output[:, :, :, filter_index])
grads = K.gradients(loss, model.input)[0]
grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)
iterate = K.function([model.input], [loss, grads])
input_img_data = np.random.random((1, size, size, 3)) * 20 + 128.
step = 1.
for i in range(40):
loss_value, grads_value = iterate([input_img_data])
input_img_data += grads_value * step
img = input_img_data[0]
return deprocess_image(img)
這些過(guò)濾器可視化展示了很多關(guān)于如何使用數(shù)字網(wǎng)絡(luò)層來(lái)查看世界:網(wǎng)絡(luò)中的每個(gè)層都學(xué)習(xí)了一組過(guò)濾器,以便它們的輸入可以表示為過(guò)濾器的組合吝梅。
類別激活值heatmap可視化
一種可視化技術(shù):有助于理解給定圖像的哪些部分引導(dǎo)其進(jìn)行最終分類決策的可視化技術(shù)虱疏。
這種通用類別的技術(shù)稱為類激活圖(CAM)可視化,它包括在輸入圖像上生成類激活的熱圖苏携。類激活熱圖是與特定輸出類相關(guān)聯(lián)的分?jǐn)?shù)的2D網(wǎng)格做瞪,針對(duì)任何輸入圖像中的每個(gè)位置計(jì)算,指示每個(gè)位置相對(duì)于所考慮的類的重要程度右冻。
小結(jié)
- Convnets是處理視覺(jué)分類問(wèn)題的最佳工具装蓬;
- Convnets通過(guò)學(xué)習(xí)模塊化模式和概念的層次結(jié)構(gòu)來(lái)表示視覺(jué)世界;
- 現(xiàn)在能夠從頭開(kāi)始訓(xùn)練自己的網(wǎng)絡(luò)以解決圖像分類問(wèn)題纱扭;
- 如何使用數(shù)據(jù)增強(qiáng)牍帚、重用預(yù)訓(xùn)練網(wǎng)絡(luò)、微調(diào)與訓(xùn)練過(guò)網(wǎng)絡(luò)來(lái)緩解過(guò)擬合現(xiàn)象乳蛾;
- 生成由convnet學(xué)習(xí)的過(guò)濾器的可視化等暗赶。