[Deep-Learning-with-Python]計(jì)算機(jī)視覺中的深度學(xué)習(xí)

包括:

  • 理解卷積神經(jīng)網(wǎng)絡(luò)
  • 使用數(shù)據(jù)增強(qiáng)緩解過擬合
  • 使用預(yù)訓(xùn)練卷積網(wǎng)絡(luò)做特征提取
  • 微調(diào)預(yù)訓(xùn)練網(wǎng)絡(luò)模型
  • 可視化卷積網(wǎng)絡(luò)學(xué)習(xí)結(jié)果以及分類決策過程
    介紹卷積神經(jīng)網(wǎng)絡(luò),convnets,深度學(xué)習(xí)在計(jì)算機(jī)視覺方面廣泛應(yīng)用的一個(gè)網(wǎng)絡(luò)模型。

卷積網(wǎng)絡(luò)介紹

在介紹卷積神經(jīng)網(wǎng)絡(luò)理論以及神經(jīng)網(wǎng)絡(luò)在計(jì)算機(jī)視覺方面應(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ò)層的加深,長度和寬度逐漸減小;通道數(shù)通過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)

測試集上模型評(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%.為什么簡單的卷積網(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í)越來越復(fù)雜和抽象的視覺概念。(現(xiàn)實(shí)生活中許多都是分級(jí)的)。


    image

卷積在3D張量上運(yùn)算,稱為特征映射,具有兩個(gè)空間軸(高度和寬度)以及深度軸(也稱為通道軸).對(duì)RGB三原色圖片來說,通道數(shù)為3--紅、綠、藍(lán);MNIST數(shù)據(jù)集中圖片通道數(shù)為1--灰度圖。卷積操作在輸入特征圖上小分片上,然后將多個(gè)操作結(jié)果生成最后的特征圖。輸出的特征圖仍然是3D張量:width、height,深度可以是任意值,因?yàn)樯疃仁蔷W(wǎng)絡(luò)層的一個(gè)參數(shù),而且深度值不再代表紅綠藍(lán)顏色通道,表示過濾器的個(gè)數(shù)。過濾器對(duì)輸入數(shù)據(jù)的特定方面進(jìn)行編碼:比如在高級(jí)別,單個(gè)過濾器可以編碼“輸入中存在面部”的概念。
卷積定義的兩個(gè)參數(shù):

  • 卷積核大?。和ǔ?x3,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).


image

輸出結(jié)果的寬度和高度可能和輸入寬度高度不同,由于:

  • Padding項(xiàng);
  • Strides 步長

最大池化 MaxPooling

最大池化層的作用在于對(duì)特征圖進(jìn)行下采樣。最大池化在特征圖中選擇window,然后每個(gè)通道的在窗口內(nèi)求最大值。概念上與卷積操作類似,卷積操作在小patch 中做線性轉(zhuǎn)換,最大池化是求最大值,通過tensor的max張量操作。最大池化通常采用2x2窗口,步長為2,特征圖減半。卷積通常卷積核大小為3x3,步長為1。

下采樣的目的在于減少要處理特征圖的參數(shù)量,通過使連續(xù)的卷積層看到越來越大的窗口(就它們所涵蓋的原始輸入的比例而言)來促使空間濾波器層次結(jié)構(gòu)。
最大池化并不是唯一的下采樣方法??梢允褂脦Р介L卷積、或平均池化,但是最大池化的工作效果更好。

小數(shù)據(jù)集上訓(xùn)練卷積網(wǎng)絡(luò)

計(jì)算機(jī)視覺中進(jìn)場會(huì)遇到使用很少的數(shù)據(jù)集去訓(xùn)練一個(gè)圖像分類模型。“小樣本”意味著樣本量在幾百到幾萬張. 比如貓狗分類,共4000張圖片,貓2000張,狗2000張。用2000張圖片來訓(xùn)練--1000張驗(yàn)證集,1000張測試集。
首先不做任何正則化處理,直接訓(xùn)練,得到一個(gè)baseline模型,準(zhǔn)確率為71%。主要問題在于模型過擬合。之后介紹data augmentation數(shù)據(jù)增強(qiáng),減緩過擬合。訓(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ù)問題的相關(guān)性

可能經(jīng)常聽說:深度學(xué)習(xí)只能工作在大數(shù)據(jù)集上。這種說法部分正確:深度學(xué)習(xí)的一個(gè)重要特性在于深度學(xué)習(xí)能自己在訓(xùn)練數(shù)據(jù)中尋找特征,而不需要人工干預(yù),而這個(gè)特性只有在大數(shù)據(jù)樣本量上才有效,特別是輸入數(shù)據(jù)維度特別高時(shí),eg圖片。
但是,對(duì)于初學(xué)者來說,構(gòu)成大量樣本的內(nèi)容與嘗試訓(xùn)練的網(wǎng)絡(luò)的大小和深度是相對(duì)的。用幾十張圖片訓(xùn)練卷積網(wǎng)絡(luò)來解決一個(gè)十分復(fù)雜的問題是不可能的,但如果模型比較簡單經(jīng)過正則化處理,同時(shí)任務(wù)比較簡單,幾百張圖片也能解決問題。因?yàn)?strong>卷積網(wǎng)絡(luò)學(xué)習(xí)局部的、具有平移不變性的特征,它們在感知問題上具有很高的數(shù)據(jù)效率。 盡管相對(duì)缺乏數(shù)據(jù),但無需額外的特征工程,即使在非常小的圖像數(shù)據(jù)集上從頭開始訓(xùn)練,卷積網(wǎng)絡(luò)仍然會(huì)產(chǎn)生合理的結(jié)果。

更重要的是,深度學(xué)習(xí)模型本質(zhì)上是高度可再利用的:例如,可以采用在大規(guī)模數(shù)據(jù)集上訓(xùn)練的圖像分類或語音到文本模型,只需進(jìn)行微小的更改,就可以重新用于顯著不同的問題上。具體而言,以計(jì)算機(jī)視覺為例,許多預(yù)先訓(xùn)練好的模型(通常在ImageNet數(shù)據(jù)集上訓(xùn)練)提供公開下載,當(dāng)樣本量少時(shí),可以用在模型中(做特征提取使用)提升工作效果。

數(shù)據(jù)下載

Keras中沒有包括Dogs vs. Cats數(shù)據(jù)集??梢栽?a target="_blank" rel="nofollow">Kaggle上下載。
圖片格式為JPEGs.數(shù)據(jù)集包含25000張貓狗圖片(一半一半)。下載解壓縮后,創(chuàng)建一個(gè)新數(shù)據(jù)集包括3個(gè)文件夾:每類1000張的訓(xùn)練集、每類500張的驗(yàn)證集和每類500張的測試集。

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)證集、測試集目錄
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張貓圖片做測試集
    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張狗圖片做測試集
    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,所以處理步驟大致為:

  1. 讀取圖片文件;
  2. 將JPEG格式轉(zhuǎn)換為RGB像素值;
  3. 轉(zhuǎn)換成浮點(diǎn)類型張量;
  4. 將像素值(0~255)縮放到[0,1]之間。

針對(duì)上述步驟,Keras中有自動(dòng)化處理方法。Keras中有一個(gè)圖像處理模塊,keras.preprocessing.image. 其中包括一個(gè)ImageDataGenerator類,可以將磁盤上的圖片文件自動(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). 注意-生成器無限期地生成這些批次:它在目標(biāo)文件夾的圖像上無休止地循環(huán)。

使用generator數(shù)據(jù)生成器對(duì)模型進(jìn)行訓(xùn)練。使用fit_generator方法,對(duì)于數(shù)據(jù)生成器來說,相當(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)過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)確率、損失值變化:


image

可以發(fā)現(xiàn)模型發(fā)生過擬合現(xiàn)象。訓(xùn)練準(zhǔn)確率隨著時(shí)間線性增加,直到100%,而驗(yàn)證集準(zhǔn)確率在70-72%波動(dòng)。驗(yàn)證集損失在5個(gè)epoch之后達(dá)到最小值,之后開始波動(dòng);訓(xùn)練集損失線性減少直到為0

因?yàn)橛?xùn)練集只有2000張圖片,遇到的第一個(gè)問題就是模型過擬合。Dropout、權(quán)重衰減可以減緩過擬合,還有一個(gè)計(jì)算機(jī)視覺任務(wù)中,經(jīng)常使用的處理方法:數(shù)據(jù)增強(qiáng)data augmentation。

數(shù)據(jù)增強(qiáng)

過度擬合是由于樣本太少而無法學(xué)習(xí),導(dǎo)致無法訓(xùn)練可以推廣到新數(shù)據(jù)的模型。給定無限的數(shù)據(jù),模型可以學(xué)習(xí)到手頭數(shù)據(jù)分布的每個(gè)可能方面:永遠(yuǎn)不會(huì)過擬合。數(shù)據(jù)增強(qiáng)采用從現(xiàn)有訓(xùn)練樣本生成更多訓(xùn)練數(shù)據(jù)的方法,通過大量隨機(jī)變換來增加樣本,從而產(chǎn)生新的可靠的圖像樣本。
目標(biāo)是在訓(xùn)練時(shí),模型將永遠(yuǎn)不會(huì)看到兩張完全相同的圖片。這有助于模型觀察數(shù)據(jù)的更多方面并更好地概括數(shù)據(jù)。
Keras中,可以通過實(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)樗鼈儊碜陨倭吭紙D像 - 無法生成新信息,只能重新混合現(xiàn)有信息。因此,這不可能完全擺脫過擬合。為了進(jìn)一步減緩過擬合,需要增加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ù)變化。


image

模型不再過擬合:訓(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í)的一種常見且高效的方法是使用預(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)視覺世界的通用模型,因此其特征可以證明對(duì)許多不同的計(jì)算機(jī)視覺問題都有用,甚至這些新問題可能涉及與原始任務(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í)特征在不同問題中的這種可移植性是深度學(xué)習(xí)的關(guān)鍵優(yōu)勢,并且它使得深度學(xué)習(xí)對(duì)于小數(shù)據(jù)問題非常有效。

比如在ImageNet數(shù)據(jù)集上訓(xùn)練的網(wǎng)絡(luò)模型(140萬個(gè)標(biāo)記圖像和1,000個(gè)不同類)。ImageNet包含許多動(dòng)物類別,包括不同種類的貓和狗,因此可以期望在狗與貓的分類問題上表現(xiàn)良好。

使用VGG16網(wǎng)絡(luò)架構(gòu),它是ImageNet的簡單且廣泛使用的convnet架構(gòu)。
使用預(yù)訓(xùn)練網(wǎng)絡(luò)有兩種方法:特征提取和微調(diào)。

特征提取

特征提取包括使用先前網(wǎng)絡(luò)學(xué)習(xí)的表示從新樣本中提取有趣特征。然后,這些功能將通過一個(gè)新的分類器運(yùn)行,該分類器從頭開始訓(xùn)練。

如前所述,用于圖像分類的網(wǎng)絡(luò)包含兩部分:它們以一系列池化和卷積層開始,并以密集連接的分類器結(jié)束。第一部分稱為模型的卷積基礎(chǔ)。在卷積網(wǎng)絡(luò)中,特征提取包括獲取先前訓(xùn)練的網(wǎng)絡(luò)的卷積基礎(chǔ),通過它運(yùn)行新數(shù)據(jù),以及在輸出之上訓(xùn)練新的分類器。

image

為什么只重用卷積網(wǎng)絡(luò)?是否可以重復(fù)使用全連接分類器?一般來說,應(yīng)該避免這樣做。原因是卷積網(wǎng)絡(luò)學(xué)習(xí)的表示可能更通用,因此更可重復(fù)使用:特征網(wǎng)絡(luò)的特征圖是圖片上一般概念的存在圖,無論處理的計(jì)算機(jī)視覺問題是什么,都可能是有用的。但是,分類器學(xué)習(xí)的表示必然特定于訓(xùn)練模型的類集 - 它們將僅包含關(guān)于整個(gè)圖像中該類或該類的存在概率的信息。此外,在全連接網(wǎng)絡(luò)層的輸出表示不再包含有關(guān)對(duì)象在輸入圖像中的位置信息:這些表示消除了空間的概念,而卷積特征圖還可以描述對(duì)象的位置信息。對(duì)于對(duì)象位置很重要的問題,全連接的特征表示在很大程度上是無用的。

注意,由特定卷積層提取的表示的一般性(以及因此可重用性)的級(jí)別取決于模型中網(wǎng)絡(luò)層的深度。模型中較早出現(xiàn)的圖層會(huì)提取局部的,高度通用的特征貼圖(例如可視邊緣,顏色和紋理),而較高層的圖層會(huì)提取更抽象的概念(例如“貓耳朵”或“狗眼”) 。因此,如果訓(xùn)練數(shù)據(jù)集與訓(xùn)練原始模型的數(shù)據(jù)集有很大差異,那么最好只使用模型的前幾層來進(jìn)行特征提取,而不是使用整個(gè)卷積網(wǎng)絡(luò)的輸出。

在這種情況下,因?yàn)镮mageNet類集包含多個(gè)dog和cat類,所以重用原始模型的全連接層中包含的信息可能是有益的。但是我們會(huì)選擇不這樣做,以便涵蓋新問題的類集不與原始模型的類集重疊的更一般情況。通過使用在ImageNet上訓(xùn)練的VGG16網(wǎng)絡(luò)的卷積網(wǎng)絡(luò)來實(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)建的全連接分類器,可以不使用原來的全連接層;
  • 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é)果保存在磁盤上,之后讀取磁盤上的數(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ì)通過模型經(jīng)過卷積部分。

不使用數(shù)據(jù)增強(qiáng)的特征提取
使用ImageDataGenerator將磁盤文件和標(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')#測試數(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)確率變化情況:

image

驗(yàn)證集準(zhǔn)確率達(dá)到90%.但圖示顯示模型從開始就過擬合了。使用數(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)練過程網(wǎng)絡(luò)層的參數(shù)被更新。如果不做‘freeze’處理,訓(xùn)練過程中卷積部分提取的特征會(huì)逐漸改變。
在Keras中,可以通過設(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)確率變化:


image

驗(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ù)使用的模型的抽象表示,以使它們與手頭的問題更相關(guān)。

image

微調(diào)網(wǎng)絡(luò)模型步驟:

  1. 在已經(jīng)訓(xùn)練好的網(wǎng)絡(luò)模型上添加自定義網(wǎng)絡(luò)模型;
  2. Freeze”凍住“訓(xùn)練好的模型;
  3. 訓(xùn)練添加部分網(wǎng)絡(luò);
  4. Unfreeze”解凍“部分base 網(wǎng)絡(luò);
  5. 重新訓(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)檫@些功能需要重新用于新問題。微調(diào)下層會(huì)有快速下降的回報(bào)。
  • 訓(xùn)練的參數(shù)越多,越有可能過度擬合。卷積網(wǎng)絡(luò)模型有1500萬個(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)在可以開始微調(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)證集、測試集上損失函數(shù)和準(zhǔn)確率變化:


image

請(qǐng)注意,損失曲線沒有顯示任何真正的改善(事實(shí)上,它正在惡化)。如果損失沒有減少,準(zhǔn)確度如何保持穩(wěn)定或改善?答案很簡單:展示的是指數(shù)損失值的平均值;但是對(duì)于準(zhǔn)確性而言重要的是損失值的分布,而不是它們的平均值,因?yàn)榫仁悄P皖A(yù)測的類概率的二元閾值的結(jié)果。即使沒有反映在平均損失中,該模型仍可能會(huì)有所改善。
在測試集上評(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ī)視覺任務(wù)的最佳機(jī)器學(xué)習(xí)模型。即使在非常小的數(shù)據(jù)集上也可以從頭開始訓(xùn)練,并獲得不錯(cuò)的結(jié)果。
  • 在小型數(shù)據(jù)集上,過度擬合將是主要問題。在處理圖像數(shù)據(jù)時(shí),數(shù)據(jù)增強(qiáng)是對(duì)抗過度擬合的有效方法;
  • 通過重用現(xiàn)有的卷積網(wǎng)絡(luò)模型可以在新數(shù)據(jù)集上做特征提?。贿@是處理小圖像數(shù)據(jù)集的有用技術(shù)。
  • 作為特征提取的補(bǔ)充,可以使用模型微調(diào),讓模型適應(yīng)新問題---以前現(xiàn)有模型可以學(xué)習(xí)新問題的特征表示,能進(jìn)一步推動(dòng)性能。

卷積學(xué)習(xí)結(jié)果可視化

人們常說,深度學(xué)習(xí)模型是“黑匣子”:學(xué)習(xí)表示難以提取以及很難以人類可讀的形式呈現(xiàn)。雖然對(duì)于某些類型的深度學(xué)習(xí)模型來說這是部分正確的,但對(duì)于convnets來說絕對(duì)不是這樣。由convnet學(xué)習(xí)的表示非常適合可視化,這在很大程度上是因?yàn)樗鼈兪且曈X概念的表示。三種常見的可視化方法:

  • 可視化中間信號(hào)輸出(中間激活)--有助于了解連續(xù)的convnet層如何轉(zhuǎn)換輸入數(shù)據(jù),以及了解各個(gè)convnet過濾器的含義;
  • 可視化convnets過濾器---有助于準(zhǔn)確理解convnet中每個(gè)過濾器可接受的視覺模式或概念;
  • 可視化圖像中類激活的熱圖---有助于了解圖像的哪些部分被識(shí)別為屬于給定的類,從而可以在圖像中本地化對(duì)象。

可視化中間激活值

可視化中間激活包括在給定特定輸入的情況下顯示由網(wǎng)絡(luò)中的各種卷積和池化層輸出的特征映射(層的輸出通常稱為其激活,激活函數(shù)的輸出)。這給出了如何將輸入分解為網(wǎng)絡(luò)學(xué)習(xí)的不同過濾器的視圖。希望從三個(gè)維度:寬度,高度和深度(通道)可視化特征圖。每個(gè)通道編碼相對(duì)獨(dú)立的特征,因此可視化這些特征圖的正確方法是通過將每個(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通道可視化
image

網(wǎng)絡(luò)中所有激活函數(shù)值可視化,將8個(gè)網(wǎng)絡(luò)層激活函數(shù)值的所有通道結(jié)果顯示出來。

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)各種邊緣檢測器的集合。在那個(gè)階段,激活值幾乎保留了初始圖片中的所有信息;
  • 隨著網(wǎng)絡(luò)層的增加,激活變得越來越抽象,在視覺上也不那么容易理解;開始編碼更高級(jí)別的概念,如“貓耳”和“貓眼?!备呒?jí)別的表示關(guān)于圖像的視覺內(nèi)容越來越少,關(guān)于圖像類型的信息越來越多;
  • 激活的稀疏性隨著層的深度而增加:在第一層中,所有濾波器都由輸入圖像激活;但在以下圖層中,越來越多的過濾器為空白。這意味著在輸入圖像中找不到濾鏡編碼的圖案。
    剛剛證明了深度神經(jīng)網(wǎng)絡(luò)所學(xué)習(xí)的表征的一個(gè)重要的普遍特征:由層提取的特征隨著層的深度而變得越來越抽象。更高層的激活越來越少地顯示關(guān)于所看到的特定輸入的信息,越來越多關(guān)于目標(biāo)的信息. 深度神經(jīng)網(wǎng)絡(luò)有效地充當(dāng)信息蒸餾管道,原始數(shù)據(jù)進(jìn)入(在這種情況下為RGB圖像)并被重復(fù)變換以便過濾掉無關(guān)信息(例如,圖像的特定視覺外觀),以及有用的信息被放大和細(xì)化(例如,圖像的類)。

可視化卷積核

另一種檢查由convnet學(xué)習(xí)的過濾器的簡單方法是顯示每個(gè)過濾器要響應(yīng)的視覺模式。這可以通過輸入空間中的漸變上升來完成:將漸變下降應(yīng)用于convnet的輸入圖像的值空間上;從空白輸入圖像開始,最大化特定過濾器的響應(yīng)。得到的輸入圖像將是所選濾波器最大響應(yīng)的圖像。

過程很簡單:您將構(gòu)建一個(gè)損失函數(shù),使給定卷積層中給定濾波器的值最大化,然后您將使用隨機(jī)梯度下降來調(diào)整輸入圖像的值,以便最大化此激活值。例如,這是在VGG16的block3_conv1中激活過濾器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ān)于如何使用數(shù)字網(wǎng)絡(luò)層來查看世界:網(wǎng)絡(luò)中的每個(gè)層都學(xué)習(xí)了一組過濾器,以便它們的輸入可以表示為過濾器的組合。

類別激活值heatmap可視化

一種可視化技術(shù):有助于理解給定圖像的哪些部分引導(dǎo)其進(jìn)行最終分類決策的可視化技術(shù)。
這種通用類別的技術(shù)稱為類激活圖(CAM)可視化,它包括在輸入圖像上生成類激活的熱圖。類激活熱圖是與特定輸出類相關(guān)聯(lián)的分?jǐn)?shù)的2D網(wǎng)格,針對(duì)任何輸入圖像中的每個(gè)位置計(jì)算,指示每個(gè)位置相對(duì)于所考慮的類的重要程度。

image

小結(jié)

  • Convnets是處理視覺分類問題的最佳工具;
  • Convnets通過學(xué)習(xí)模塊化模式和概念的層次結(jié)構(gòu)來表示視覺世界;
  • 現(xiàn)在能夠從頭開始訓(xùn)練自己的網(wǎng)絡(luò)以解決圖像分類問題;
  • 如何使用數(shù)據(jù)增強(qiáng)、重用預(yù)訓(xùn)練網(wǎng)絡(luò)、微調(diào)與訓(xùn)練過網(wǎng)絡(luò)來緩解過擬合現(xiàn)象;
  • 生成由convnet學(xué)習(xí)的過濾器的可視化等。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容