1. 程式人生 > >[Deep-Learning-with-Python]計算機視覺中的深度學習

[Deep-Learning-with-Python]計算機視覺中的深度學習

包括:
- 理解卷積神經網路
- 使用資料增強緩解過擬合
- 使用預訓練卷積網路做特徵提取
- 微調預訓練網路模型
- 視覺化卷積網路學習結果以及分類決策過程
介紹卷積神經網路,convnets,深度學習在計算機視覺方面廣泛應用的一個網路模型。

卷積網路介紹

在介紹卷積神經網路理論以及神經網路在計算機視覺方面應用廣泛的原因之前,先介紹一個卷積網路的例項,整體瞭解卷積網路模型。用卷積網路識別MNIST資料集。

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

卷積網路接收(image_height,image_width,image_channels)形狀的張量作為輸入(不包括batch size)。MNIST中,將圖片轉換成(28,28,1)形狀,然後在第一層傳遞input_shape引數。
顯示網路架構

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

可以看到每個Conv2D和MaxPooling2D網路層輸出都是3D張量,形狀為(height,width,channels).隨著網路層的加深,長度和寬度逐漸減小;通道數通過Conv2D層的引數控制。
下一步連線Dense層,但當前輸出為3D張量,需要將3D張量平鋪成1D,然後新增Dense層。

model.add(layers.Flatten())
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))

因為是10分類,最後一層為10個神經元,啟用函式為softmax。
最後的網路架構

>>> 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,)向量。
網路訓練

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)

測試集上模型評估:

>>> test_loss, test_acc = model.evaluate(test_images, test_labels)
>>> test_acc
0.99080000000000001

在Dense網路上準確率為97.8%,基本卷積網路上準確率到99%.為什麼簡單的卷積網路工作效果這麼好?回答之前,先了解Conv2D和MaxPooling2D層。

卷積操作

全連線網路和卷積網路的區別在於Dense全連線層學習輸入特徵空間的全域性模式特徵,而卷積神經網路學習輸入特徵空間的區域性模式特徵。
卷積網路的兩個關鍵特性:
- 學習具有平移不變性的模式特徵:一旦學習到圖片左上角的模式特徵,可以在任何地方識別,如右下角,這種特性使得圖片處理更加有效,需要的樣本相對減少(實際生活中具有平移不變性)
- 學習模式的空間層次結構:第一個卷積層將學習小的區域性模式,如邊緣,第二個卷積層將學習由第一層特徵構成的更大圖案,等等。這使得卷積網路能夠有效地學習越來越複雜和抽象的視覺概念。(現實生活中許多都是分級的)。

卷積在3D張量上運算,稱為特徵對映,具有兩個空間軸(高度和寬度)以及深度軸(也稱為通道軸).對RGB三原色圖片來說,通道數為3–紅、綠、藍;MNIST資料集中圖片通道數為1–灰度圖。卷積操作在輸入特徵圖上小分片上,然後將多個操作結果生成最後的特徵圖。輸出的特徵圖仍然是3D張量:width、height,深度可以是任意值,因為深度是網路層的一個引數,而且深度值不再代表紅綠藍顏色通道,表示過濾器的個數。過濾器對輸入資料的特定方面進行編碼:比如在高級別,單個過濾器可以編碼“輸入中存在面部”的概念。
卷積定義的兩個引數:
- 卷積核大小:通常為3x3,5x5.
- 卷積核個數:卷積核個數等於本層網路輸出層的深度。

Keras中,Conv2D網路層定義:Conv2D(output_depth, (window_height, window_width)) .
卷積:卷積核在上一層的特徵圖的全通道進行滑動,然後抽取形狀為(window_height,window_width,input_depth)形狀的3D片特徵。每個3D片特徵最後轉換成1D向量(卷積運算–張量點積),形狀(output_depth,),所有的結果向量整合形成最後的3D特徵(height,width,output_depth).

輸出結果的寬度和高度可能和輸入寬度高度不同,由於:
- Padding項;
- Strides 步長

最大池化 MaxPooling

最大池化層的作用在於對特徵圖進行下采樣。最大池化在特徵圖中選擇window,然後每個通道的在視窗內求最大值。概念上與卷積操作類似,卷積操作在小patch 中做線性轉換,最大池化是求最大值,通過tensor的max張量操作。最大池化通常採用2x2視窗,步長為2,特徵圖減半。卷積通常卷積核大小為3x3,步長為1。

下采樣的目的在於減少要處理特徵圖的引數量,通過使連續的卷積層看到越來越大的視窗(就它們所涵蓋的原始輸入的比例而言)來促使空間濾波器層次結構。
最大池化並不是唯一的下采樣方法。可以使用帶步長卷積、或平均池化,但是最大池化的工作效果更好。

小資料集上訓練卷積網路

計算機視覺中進場會遇到使用很少的資料集去訓練一個影象分類模型。“小樣本”意味著樣本量在幾百到幾萬張. 比如貓狗分類,共4000張圖片,貓2000張,狗2000張。用2000張圖片來訓練–1000張驗證集,1000張測試集。
首先不做任何正則化處理,直接訓練,得到一個baseline模型,準確率為71%。主要問題在於模型過擬合。之後介紹data augmentation資料增強,減緩過擬合。訓練後為82%。更有效的方法是用已訓練好的模型最特徵提取—準確率90%~96%,或者微調已訓練好的網路做特徵提取(97%)。這三種方法有助於在小資料集上的模型訓練。

深度學習與小資料問題的相關性

可能經常聽說:深度學習只能工作在大資料集上。這種說法部分正確:深度學習的一個重要特性在於深度學習能自己在訓練資料中尋找特徵,而不需要人工干預,而這個特性只有在大資料樣本量上才有效,特別是輸入資料維度特別高時,eg圖片。
但是,對於初學者來說,構成大量樣本的內容與嘗試訓練的網路的大小和深度是相對的。用幾十張圖片訓練卷積網路來解決一個十分複雜的問題是不可能的,但如果模型比較簡單經過正則化處理,同時任務比較簡單,幾百張圖片也能解決問題。因為卷積網路學習區域性的、具有平移不變性的特徵,它們在感知問題上具有很高的資料效率。 儘管相對缺乏資料,但無需額外的特徵工程,即使在非常小的影象資料集上從頭開始訓練,卷積網路仍然會產生合理的結果。

更重要的是,深度學習模型本質上是高度可再利用的:例如,可以採用在大規模資料集上訓練的影象分類或語音到文字模型,只需進行微小的更改,就可以重新用於顯著不同的問題上。具體而言,以計算機視覺為例,許多預先訓練好的模型(通常在ImageNet資料集上訓練)提供公開下載,當樣本量少時,可以用在模型中(做特徵提取使用)提升工作效果。

資料下載

Keras中沒有包括Dogs vs. Cats資料集。可以在Kaggle上下載。
圖片格式為JPEGs.資料集包含25000張貓狗圖片(一半一半)。下載解壓縮後,建立一個新資料集包括3個資料夾:每類1000張的訓練集、每類500張的驗證集和每類500張的測試集。

import os,shutil

#原始資料
original_dataset_dir = '/Users/fchollet/Downloads/kaggle_original_data'
#新資料集目錄
base_dir = '/Users/fchollet/Downloads/cats_and_dogs_small'
os.mkdir(base_dir)
#建立訓練集、驗證集、測試集目錄
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)
#建立對應資料集下不同類別的目錄
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:#將前一千張貓圖片複製到新資料集目錄下
    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張貓圖片複製到驗證集
    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張狗圖片做訓練集
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張狗圖片做驗證集
    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)

構建模型

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

模型架構:

>>> 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優化演算法,binary crossentropy為損失函式。

from keras import optimizers

model.compile(loss='binary_crossentropy',optimizer=optimizers.RMSprop(lr=1e-4),metrics=['acc'])

資料預處理

資料在送到網路模型之前應該轉換成浮點型別的張量。目前資料集中資料格式為JPEG,所以處理步驟大致為:
1. 讀取圖片檔案;
2. 將JPEG格式轉換為RGB畫素值;
3. 轉換成浮點型別張量;
4. 將畫素值(0~255)縮放到[0,1]之間。

針對上述步驟,Keras中有自動化處理方法。Keras中有一個影象處理模組,keras.preprocessing.image. 其中包括一個ImageDataGenerator類,可以將磁碟上的圖片檔案自動轉換成預處理的張量batch批量。使用方法:

from keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(rescale=1./255)
test_datagen = ImageDataGenerator(rescale=1./255)
#將圖片轉換成150x150,類別為2;class_mode 確定返回標籤的型別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的資料結果為150x150 RGB批量圖片,尺寸為(20,150,150,3),二進位制標籤形狀(20,)。每個批量大小為20個樣本(batch_size為20). 注意-生成器無限期地生成這些批次:它在目標資料夾的影象上無休止地迴圈。

使用generator資料生成器對模型進行訓練。使用fit_generator方法,對於資料生成器來說,相當於fit方法。fit_generator第一個引數是Python生成器型別,能不斷地生成輸入和標籤批量。因為資料不斷生成,Keras模型需要知道在宣告一個epoch之前從發生器中抽取多少批量;steps_per_epoch引數:從生成器中生成 steps_per_epoch個批量資料;在經過steps_per_epoch次梯度下降後,在下一個epoch上進行訓練。在這裡,批量大小為20,一個epoch有100個批量,生成2000張圖片樣本。
使用fit_generator方法,可以傳遞validataion_data引數,和fit方法相似。值得注意的是,這個引數可以賦值為資料生成器,也可以是numpy陣列的元組。如果validation_data引數是資料生成器,生成器能不斷地生成資料,所以需要設定validation_steps引數,確定從生成器中生成多少驗證集批量。

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

訓練集驗證集準確率、損失值變化:

可以發現模型發生過擬合現象。訓練準確率隨著時間線性增加,直到100%,而驗證集準確率在70-72%波動。驗證集損失在5個epoch之後達到最小值,之後開始波動;訓練集損失線性減少直到為0

因為訓練集只有2000張圖片,遇到的第一個問題就是模型過擬合。Dropout、權重衰減可以減緩過擬合,還有一個計算機視覺任務中,經常使用的處理方法:資料增強data augmentation。

資料增強

過度擬合是由於樣本太少而無法學習,導致無法訓練可以推廣到新資料的模型。給定無限的資料,模型可以學習到手頭資料分佈的每個可能方面:永遠不會過擬合。資料增強採用從現有訓練樣本生成更多訓練資料的方法,通過大量隨機變換來增加樣本,從而產生新的可靠的影象樣本。
目標是在訓練時,模型將永遠不會看到兩張完全相同的圖片。這有助於模型觀察資料的更多方面並更好地概括資料。
Keras中,可以通過例項化ImageDataGenerator例項,確定圖片轉換方法,從而實現資料增強。

datagen = ImageDataGenerator(
    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')#用於填充新建立的畫素的策略,在旋轉或寬度/高度偏移後出現

如果使用這樣的資料增強配置訓練新網路,網路將永遠不會看到兩張相同的輸入圖片。但它看到的輸入仍然是嚴重相互關聯的,因為它們來自少量原始影象 - 無法生成新資訊,只能重新混合現有資訊。因此,這不可能完全擺脫過擬合。為了進一步減緩過擬合,需要增加Dropout層,在全連線層之前。
新網路模型:

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

使用資料增強和Dropout訓練網路。

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')#模型儲存

使用資料增強和Dropout後,訓練集、驗證集準確率和損失函式變化。

模型不再過擬合:訓練集曲線和驗證集曲線幾乎相互吻合。準確率82%,提高了15%左右。使用正則化技術,微調網路超引數,模型準確率會進一步提高,到86%~87%.但是很難繼續提高,因為訓練資料有限,樣本量太少。另一種方法,可以採用預先訓練好的網路模型,做特徵提取,提高準確率。

使用預訓練卷積網路

在小影象資料集上使用深度學習的一種常見且高效的方法是使用預訓練網路。預訓練網路是先前在大型資料集上訓練的已儲存網路,通常是處理大規模影象分類任務。如果這個原始資料集足夠大且代表性強,則預訓練網路學習的特徵的空間層次結構可以有效地充當視覺世界的通用模型,因此其特徵可以證明對許多不同的計算機視覺問題都有用,甚至這些新問題可能涉及與原始任務完全不同。例如,可以在ImageNet上訓練網路(其中類主要是動物和日常物品),然後將這個訓練好的網路重新用於識別影象中的傢俱物品任務中。與許多較舊的淺學習方法(傳統機器學習方法)相比,學習特徵在不同問題中的這種可移植性是深度學習的關鍵優勢,並且它使得深度學習對於小資料問題非常有效。

比如在ImageNet資料集上訓練的網路模型(140萬個標記影象和1,000個不同類)。ImageNet包含許多動物類別,包括不同種類的貓和狗,因此可以期望在狗與貓的分類問題上表現良好。

使用VGG16網路架構,它是ImageNet的簡單且廣泛使用的convnet架構。
使用預訓練網路有兩種方法:特徵提取和微調。

特徵提取

特徵提取包括使用先前網路學習的表示從新樣本中提取有趣特徵。然後,這些功能將通過一個新的分類器執行,該分類器從頭開始訓練。

如前所述,用於影象分類的網路包含兩部分:它們以一系列池化和卷積層開始,並以密集連線的分類器結束。第一部分稱為模型的卷積基礎。在卷積網路中,特徵提取包括獲取先前訓練的網路的卷積基礎,通過它執行新資料,以及在輸出之上訓練新的分類器。

為什麼只重用卷積網路?是否可以重複使用全連線分類器?一般來說,應該避免這樣做。原因是卷積網路學習的表示可能更通用,因此更可重複使用:特徵網路的特徵圖是圖片上一般概念的存在圖,無論處理的計算機視覺問題是什麼,都可能是有用的。但是,分類器學習的表示必然特定於訓練模型的類集 - 它們將僅包含關於整個影象中該類或該類的存在概率的資訊。此外,在全連線網路層的輸出表示不再包含有關物件在輸入影象中的位置資訊:這些表示消除了空間的概念,而卷積特徵圖還可以描述物件的位置資訊。對於物件位置很重要的問題,全連線的特徵表示在很大程度上是無用的。

注意,由特定卷積層提取的表示的一般性(以及因此可重用性)的級別取決於模型中網路層的深度。模型中較早出現的圖層會提取區域性的,高度通用的特徵貼圖(例如可視邊緣,顏色和紋理),而較高層的圖層會提取更抽象的概念(例如“貓耳朵”或“狗眼”) 。因此,如果訓練資料集與訓練原始模型的資料集有很大差異,那麼最好只使用模型的前幾層來進行特徵提取,而不是使用整個卷積網路的輸出。

在這種情況下,因為ImageNet類集包含多個dog和cat類,所以重用原始模型的全連線層中包含的資訊可能是有益的。但是我們會選擇不這樣做,以便涵蓋新問題的類集不與原始模型的類集重疊的更一般情況。通過使用在ImageNet上訓練的VGG16網路的卷積網路來實現這一點,從貓和狗影象中提取有趣的特徵,然後在這些特徵之上訓練狗與貓的分類器。
Keras中可以直接獲取VGG16模型,包含在keras.applications模組中。其中還包括其他模型:
- Xception
- Inception V3
- ResNet50
- VGG16
- VGG19
- MobileNet

例項化VGG16模型:

from keras.application import vgg16

conv_base = VGG16(weights='imagenet',include_top=False,input_shape=(150, 150, 3))

構造器的3個引數:
- weights:讀取權重儲存點檔案,初始化模型;
- include_top:是否包含網路的全連線層。模型,全連線層分類類別在ImageNet上的1000類。因為要使用自己建立的全連線分類器,可以不使用原來的全連線層;
- input_shape:送到模型中圖片張量的形狀;引數是可選的:如果不傳遞引數,網路可以處理任意形狀的輸入。
VGG16網路模型架構:

>>> 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).之後連線到全連線分類器上。有兩種處理方法:
- 訓練卷積網路模型部分,將輸出結果儲存在磁碟上,之後讀取磁碟上的資料送到全連線分類器中。優點在於執行高效、快速,因為卷積網路部分針對每張輸入圖片只執行一次,而卷積部分是最耗時、耗費運算能力資源的;但同時不能使用資料增強;
- 將全連線分類器和卷積部分整合到一起,在輸入資料上端到端的執行;可以使用資料增強,因為每次輸入模型的影象都會通過模型經過卷積部分。

不使用資料增強的特徵提取
使用ImageDataGenerator將磁碟檔案和標籤讀取成張量形式,運行卷積部分的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')#訓練資料
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:
            break
    return features, labels

train_features, train_labels = extract_features(train_dir, 2000)
validation_features,validation_labels=extract_features(validation_dir, 1000)
test_features, test_labels = extract_features(test_dir, 1000)

當前提取特徵形狀為(samples,4,4,512),在送到全連線層之前,需要先平鋪成(samples,8192),。

train_features = np.reshape(train_features, (2000, 4 * 4 * 512))
validation_features=np.reshape(validation_features, (1000, 4 * 4 * 512))
test_features = np.reshape(test_features, (1000, 4 * 4 * 512))

定義全連線分類器,將特徵資料送到分類器中訓練。

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

驗證集、訓練集上損失值和準確率變化情況:

驗證集準確率達到90%.但圖示顯示模型從開始就過擬合了。使用資料正增強可以緩解一下。
使用資料增強的特徵提取
和第一種方法相比,運算速度更慢、耗費運算資源更多,通常需要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'))

模型架構為:

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

在模型訓練之前,需要對卷積部分進行freeze‘凍住’。Freezing網路層意味著避免在訓練過程網路層的引數被更新。如果不做‘freeze’處理,訓練過程中卷積部分提取的特徵會逐漸改變。
在Keras中,可以通過設定trainable引數為False進行Freeze處理。

conv_base.trainable = False

注意,為了使這些更改生效,必須首先編譯模型。如果在編譯後修改了權重可訓練性,則應重新編譯模型,否則將忽略這些更改。

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)

損失值和準確率變化:

驗證集上準確率達到96%.

模型微調Fine-tuning

另一種廣泛使用的模型重用技術,對特徵提取的補充,就是模型引數微調。微調包括解凍用於特徵提取的凍結模型基礎的一些頂層,並聯合訓練模型的新新增部分(在這種情況下,全連線的分類器)和這些頂層。這稱為微調,因為它稍微調整了重複使用的模型的抽象表示,以使它們與手頭的問題更相關。

微調網路模型步驟:
1. 在已經訓練好的網路模型上新增自定義網路模型;
2. Freeze”凍住“訓練好的模型;
3. 訓練新增部分網路;
4. Unfreeze”解凍“部分base 網路;
5. 重新訓練解凍部分和新增部分。

base部分網路模型:

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