計算機視覺中的深度學習

starkbl發表於2021-09-09

包括:

  • 理解卷積神經網路

  • 使用資料增強緩解過擬合

  • 使用預訓練卷積網路做特徵提取

  • 微調預訓練網路模型

  • 視覺化卷積網路學習結果以及分類決策過程
    介紹卷積神經網路,convnets,深度學習在計算機視覺方面廣泛應用的一個網路模型。

卷積網路介紹

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

from keras import layersfrom 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,744Trainable params: 55,744Non-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,322Trainable params: 93,322Non-trainable params: 0

(3,3,64)輸出平攤成(576,)向量。
網路訓練

from keras.datasets import mnistfrom 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') / 255test_images = test_images.reshape((10000, 28, 28, 1))
test_images = test_images.astype('float32') / 255train_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_acc0.99080000000000001

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

卷積操作

全連線網路和卷積網路的區別在於Dense全連線層學習輸入特徵空間的全域性模式特徵,而卷積神經網路學習輸入特徵空間的區域性模式特徵。
卷積網路的兩個關鍵特性:

  • 學習具有平移不變性的模式特徵:一旦學習到圖片左上角的模式特徵,可以在任何地方識別,如右下角,這種特性使得圖片處理更加有效,需要的樣本相對減少(實際生活中具有平移不變性)

  • 學習模式的空間層次結構:第一個卷積層將學習小的區域性模式,如邊緣,第二個卷積層將學習由第一層特徵構成的更大圖案,等等。這使得卷積網路能夠有效地學習越來越複雜和抽象的視覺概念。(現實生活中許多都是分級的)。


    圖片描述

    image

卷積在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).

圖片描述

image

輸出結果的寬度和高度可能和輸入寬度高度不同,由於:

  • 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資料集。可以在上下載。
圖片格式為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 500fnames = ['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 layersfrom 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,121Trainable params: 3,453,121Non-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')

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


圖片描述

image

可以發現模型發生過擬合現象。訓練準確率隨著時間線性增加,直到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後,訓練集、驗證集準確率和損失函式變化。


圖片描述

image


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

使用預訓練卷積網路

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

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

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

特徵提取

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

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

圖片描述

image


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

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

在這種情況下,因為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,688Trainable params: 14,714,688Non-trainable params: 0

最後一層的特徵圖形狀為(4,4,512).之後連線到全連線分類器上。有兩種處理方法:

  • 訓練卷積網路模型部分,將輸出結果儲存在磁碟上,之後讀取磁碟上的資料送到全連線分類器中。優點在於執行高效、快速,因為卷積網路部分針對每張輸入圖片只執行一次,而卷積部分是最耗時、耗費運算能力資源的;但同時不能使用資料增強;

  • 將全連線分類器和卷積部分整合到一起,在輸入資料上端到端的執行;可以使用資料增強,因為每次輸入模型的影像都會透過模型經過卷積部分。

不使用資料增強的特徵提取
使用ImageDataGenerator將磁碟檔案和標籤讀取成張量形式,執行卷積部分的predict提取圖片特徵。

import osimport numpy as npfrom 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 = 20def 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 modelsfrom keras import layersfrom 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))

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

圖片描述

image


驗證集準確率達到90%.但圖示顯示模型從開始就過擬合了。使用資料正增強可以緩解一下。
使用資料增強的特徵提取
和第一種方法相比,運算速度更慢、耗費運算資源更多,通常需要GPU。如果GPU上速度還慢,最好使用第一種方法。


from keras import modelsfrom 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,353Trainable params: 16,812,353Non-trainable params: 0

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

conv_base.trainable = False

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

from keras.preprocessing.image import ImageDataGeneratorfrom 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)

損失值和準確率變化:


圖片描述

image


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

模型微調Fine-tuning

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

圖片描述

image


微調網路模型步驟:

  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, 9, 9, 512)           2359808________________________________________________________________
block5_conv3 (Convolution2D)(None, 9, 9, 512)           2359808________________________________________________________________
block5_pool (MaxPooling2D)  (None, 4, 4, 512)           0================================================================
Total params: 14714688

微調模型的最後3個卷積層,意味著到block4_pool之前都被”凍住“,網路層block5_conv1,block5_conv2和block5_conv3都是可訓練的。
為什麼不微調更多層?為什麼不微調整個卷積網路?可以這麼做。但是你需要考慮以下幾點:

  • 卷積網路中的前幾層編碼更通用,可重用的特徵,而更高層的編碼更專業的特徵。微調更專業的功能更有用,因為這些功能需要重新用於新問題。微調下層會有快速下降的回報。

  • 訓練的引數越多,越有可能過度擬合。卷積網路模型有1500萬個引數,因此嘗試在小資料集上訓練它會有風險。

一個很好的策略是隻微調卷積基礎中的前兩個或三個層。

conv_base.trainable = Trueset_trainable = Falsefor layer in conv_base.layers:    if layer.name == 'block5_conv1':#block5_conv1可訓練
        set_trainable = True#flag可訓練
    if set_trainable:
        layer.trainable = True#block5_conv1網路層設定為可訓練;
    else:
        layer.trainable = False#其它層不可訓練

現在可以開始微調網路了。使用RMSProp最佳化器以非常低的學習速率執行此操作。使用低學習率的原因是希望限制對正在微調的三個網路層的表示所做的修改的幅度。太大的更新可能會損害這些表示。

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)

驗證集、測試集上損失函式和準確率變化:


圖片描述

image


請注意,損失曲線沒有顯示任何真正的改善(事實上,它正在惡化)。如果損失沒有減少,準確度如何保持穩定或改善?答案很簡單:展示的是指數損失值的平均值;但是對於準確性而言重要的是損失值的分佈,而不是它們的平均值,因為精度是模型預測的類機率的二元閾值的結果。即使沒有反映在平均損失中,該模型仍可能會有所改善。
在測試集上評估:

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%

小結

  • Convnets是用於計算機視覺任務的最佳機器學習模型。即使在非常小的資料集上也可以從頭開始訓練,並獲得不錯的結果。

  • 在小型資料集上,過度擬合將是主要問題。在處理影像資料時,資料增強是對抗過度擬合的有效方法;

  • 透過重用現有的卷積網路模型可以在新資料集上做特徵提取;這是處理小影像資料集的有用技術。

  • 作為特徵提取的補充,可以使用模型微調,讓模型適應新問題---以前現有模型可以學習新問題的特徵表示,能進一步推動效能。

卷積學習結果視覺化

人們常說,深度學習模型是“黑匣子”:學習表示難以提取以及很難以人類可讀的形式呈現。雖然對於某些型別的深度學習模型來說這是部分正確的,但對於convnets來說絕對不是這樣。由convnet學習的表示非常適合視覺化,這在很大程度上是因為它們是視覺概念的表示。三種常見的視覺化方法:

  • 視覺化中間訊號輸出(中間啟用)--有助於瞭解連續的convnet層如何轉換輸入資料,以及瞭解各個convnet過濾器的含義;

  • 視覺化convnets過濾器---有助於準確理解convnet中每個過濾器可接受的視覺模式或概念;

  • 視覺化影像中類啟用的熱圖---有助於瞭解影像的哪些部分被識別為屬於給定的類,從而可以在影像中本地化物件。

視覺化中間啟用值

視覺化中間啟用包括在給定特定輸入的情況下顯示由網路中的各種卷積和池化層輸出的特徵對映(層的輸出通常稱為其啟用,啟用函式的輸出)。這給出瞭如何將輸入分解為網路學習的不同過濾器的檢視。希望從三個維度:寬度,高度和深度(通道)視覺化特徵圖。每個通道編碼相對獨立的特徵,因此視覺化這些特徵圖的正確方法是透過將每個通道的內容獨立地繪製為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 imageimport 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.

檢視所有網路層的輸出結果:

from keras import models

layer_outputs = [layer.output for layer in model.layers[:8]]
activation_model=models.Model(inputs=model.input,outputs=layer_outputs)

輸入影像輸入時,此模型返回原始模型中網路層啟用的值。一個多輸出模型:到目前為止,看到的模型只有一個輸入和一個輸出。在一般情況下,模型可以具有任意數量的輸入和輸出。這個有一個輸入和八個輸出:每層啟用一個輸出。
模型執行:

activations = activation_model.predict(img_tensor)#輸出:每層啟用值一個陣列

第一個卷積層結果:

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


網路中所有啟用函式值視覺化,將8個網路層啟用函式值的所有通道結果顯示出來。

layer_names = []for layer in model.layers[:8]:
    layer_names.append(layer.name)
images_per_row = 16for 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)]
值得注意的是:

  • 第一層充當各種邊緣檢測器的集合。在那個階段,啟用值幾乎保留了初始圖片中的所有資訊;

  • 隨著網路層的增加,啟用變得越來越抽象,在視覺上也不那麼容易理解;開始編碼更高階別的概念,如“貓耳”和“貓眼。”更高階別的表示關於影像的視覺內容越來越少,關於影像型別的資訊越來越多;

  • 啟用的稀疏性隨著層的深度而增加:在第一層中,所有濾波器都由輸入影像啟用;但在以下圖層中,越來越多的過濾器為空白。這意味著在輸入影像中找不到濾鏡編碼的圖案。
    剛剛證明了深度神經網路所學習的表徵的一個重要的普遍特徵:由層提取的特徵隨著層的深度而變得越來越抽象。更高層的啟用越來越少地顯示關於所看到的特定輸入的資訊,越來越多關於目標的資訊. 深度神經網路有效地充當資訊蒸餾管道,原始資料進入(在這種情況下為RGB影像)並被重複變換以便過濾掉無關資訊(例如,影像的特定視覺外觀),以及有用的資訊被放大和細化(例如,影像的類)。

視覺化卷積核

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

過程很簡單:您將構建一個損失函式,使給定卷積層中給定濾波器的值最大化,然後您將使用隨機梯度下降來調整輸入影像的值,以便最大化此啟用值。例如,這是在VGG16的block3_conv1中啟用過濾器0的損失.

from keras.applications import VGG16from keras import backend as K

model = VGG16(weights='imagenet',include_top=False)
layer_name = 'block3_conv1'filter_index = 0layer_output=model.get_layer(layer_name).output#得到block3_conv1的啟用值loss = K.mean(layer_output[:, :, :, filter_index])

要實現梯度下降,需要相對於模型輸入求損失的梯度。

grads = K.gradients(loss, model.input)[0]

使用梯度正則化平滑梯度值

grads /= (K.sqrt(K.mean(K.square(grads))) + 1e-5)

計算損失張量和梯度張量。使用keras的iterate函式,接收numpy張量,返回關於損失和梯度的張量列表。

iterate = K.function([model.input], [loss, grads])import numpy as np
loss_value, grads_value = iterate([np.zeros((1, 150, 150, 3))])

將張量轉換為圖片格式:

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

整合卷積核視覺化函式:

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)

這些過濾器視覺化展示了很多關於如何使用數字網路層來檢視世界:網路中的每個層都學習了一組過濾器,以便它們的輸入可以表示為過濾器的組合。

類別啟用值heatmap視覺化

一種視覺化技術:有助於理解給定影像的哪些部分引導其進行最終分類決策的視覺化技術。
這種通用類別的技術稱為類啟用圖(CAM)視覺化,它包括在輸入影像上生成類啟用的熱圖。類啟用熱圖是與特定輸出類相關聯的分數的2D網格,針對任何輸入影像中的每個位置計算,指示每個位置相對於所考慮的類的重要程度。

圖片描述

image


小結

  • Convnets是處理視覺分類問題的最佳工具;

  • Convnets透過學習模組化模式和概念的層次結構來表示視覺世界;

  • 現在能夠從頭開始訓練自己的網路以解決影像分類問題;

  • 如何使用資料增強、重用預訓練網路、微調與訓練過網路來緩解過擬合現象;

  • 生成由convnet學習的過濾器的視覺化等。



作者:七八音
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3705/viewspace-2812175/,如需轉載,請註明出處,否則將追究法律責任。

相關文章