文章代碼來源:《deep learning on keras》,非常好的一本書,大家如果英語好,推薦直接閱讀該書,如果時間不夠,可以看看此系列文章,文章為我自己翻譯的內容加上自己的一些思考,水平有限,多有不足,請多指正,翻譯版權所有,若有轉載,請先聯系本人。
個人方向為數值計算,日后會向深度學習和計算問題的融合方面靠近,若有相近專業人士,歡迎聯系。
系列文章:
一、搭建屬于你的第一個神經網絡
二、訓練完的網絡去哪里找
三、【keras實戰】波士頓房價預測
四、keras的function API
五、keras callbacks使用
六、機器學習基礎Ⅰ:機器學習的四個標簽
七、機器學習基礎Ⅱ:評估機器學習模型
八、機器學習基礎Ⅲ:數據預處理、特征工程和特征學習
九、機器學習基礎Ⅳ:過擬合和欠擬合
十、機器學習基礎Ⅴ:機器學習的一般流程十一、計算機視覺中的深度學習:卷積神經網絡介紹
十二、計算機視覺中的深度學習:從零開始訓練卷積網絡
十三、計算機視覺中的深度學習:使用預訓練網絡
十四、計算機視覺中的神經網絡:可視化卷積網絡所學到的東西
在這一章,我們會學習卷積神經網絡,一種在計算機視覺中常用的深度學習模型,你將會學著將它們運用到分類問題中。
我們首先會介紹卷積神經網絡背后的一些理論,特別的:
- 什么是卷積和最大池化?
- 什么是卷積網絡?
- 卷積網絡學到了什么東西?
接下來我們會用小的數據集來概括圖像分類問題:
- 從零開始訓練你的小卷積網絡
- 使用數據增加來避免過擬合
- 使用預訓練的卷積網絡來做特征提取
- 對預訓練卷積網絡調參
最后我們會概括幾個可視化的技術來學習如何分類。
接下來是我們的第一節,卷積神經網路的介紹
我們潛入卷積神經網絡的理論,并探尋為什么它在計算機視覺任務中那么成功。首先,我們實際看看一個簡單的卷積網絡的例子。我們將會用卷積網絡來分類MNIST數字,在之前我們以及用全連接做到了97.8%的識別率。盡管我們的卷積網絡很基礎,但是其正確率和原來的全連接比,照樣完勝。
接下來6行代碼將會給你展示最基礎的卷積網絡長什么樣,其實就是一些二維卷積和二維最大池化層的堆疊。我們接下來將會看看他們到底具體做了些什么,一個卷積拿進去的張量的形狀:(長,寬,通道數)不包括批次的維數。在我們的例子中,我們將會處理輸入形狀為(28,28,1)的數據,這就是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'))
接下來讓我們看看卷積網絡的結構:
>>> 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
我們可以看到每一個卷積層和池化層的輸出都是三維的張量。寬度和高度隨著網絡的加深開始收縮。通道的數量由卷積層受到的第一個參數來控制。
接下來就是將我們的輸出張量喂進全連接分類網絡。分類器處理的是一維向量,而我們的輸出是一個三維的張量,所以我們需要把我們的三維輸出壓成一維的,接下來,我們加一層密度層:
model.add(layers.Flatten())
model.add(layers.Dense(64, activation='relu'))
model.add(layers.Dense(10, activation='softmax'))
我們接下來要做一個10分類,我們選用了softmax作為激活函數,輸出維度為10,我們的網絡現在長成這個樣子:
>>> 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
最后我們將使用之前使用過的代碼來訓練:
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
為什么一個簡單的卷積神經網絡能夠比全連接模型效果好那么多呢,我們需要繼續潛入卷積層和最大池化層來理解這個事情。
卷積算子
全連接層學習全局圖案,而卷積層則學習局部的圖案
這個關鍵的特征給了卷積網絡兩個有意思的特性:
- 學習到的圖案具有平移不變性,在全連接中學到的圖案和位置有關,而卷積網絡則有更高的數據效率。
-
能學習圖案的空間層次,如下圖,第一層學習小的局部圖案例如邊緣,但第二個卷積層將會基于第一層學習更大的特征。這將使得卷積網絡更加有效的學習到進一步抽象復雜的特征。
The visual world forms a spatial hierarchy of visual modules: hyperlocal edges combine into local objects such as eyes or ears, which combine into high-level concepts such as "cat"
在三維張量的卷積運算稱為“特征圖”,有兩個空間坐標(寬和高),和一個深度坐標(也稱作通道數)對于RGB圖像來說,深度坐標的維度就是3。對于MNIST來說,黑白圖深度就是1。卷積算子從輸入特征圖提取補丁,并對所有的補丁做某些變換,然后產生我們的輸出特征圖。輸出特征圖仍然是三維張量,不過這里的深度不再代表什么具體的顏色了,而是我們叫做濾波器的東西。濾波器對輸入數據的某一特定方面進行編碼,在高層次,一個簡單的濾波器能編碼輸入中一個面的存在。
卷積由以下兩個關鍵參數所定義: - 從輸入中提取的補丁塊的大小。在我們的例子中一般都是
的。
- 輸出特征映射的深度,即濾波器的數量。在我們的例子中,我們從深度為32開始,深度為64結束。
在keras的卷積層中,第一個傳入的參數就是Conv2D(output_depth, (window_height, window_width))。
一個卷積通過滑動來工作,在每個可能的位置停止,并從周圍的特征里面提取三維補丁。
示意圖如下:
注意到我們得到的輸出的寬度和高度或許和輸入的寬度和高度不一樣,這里有兩個原因:
- 邊框影響,由于輸入特征映射填充造成的。
- 滑動的使用,我們稍后會定義。
讓我們來看一下這些注意點。
理解邊框效應和填充
考慮一個的特征映射,共25個小塊。但那時只有9個不同的小塊,也就說你可以注意力放在
的小窗上。因此輸出的特征映射將會是
的:這縮小了很多,在每個維度上縮小了兩個小塊,你將會在之前的例子中看到“邊界效應”。
如果你想要得到一個和原來的輸入有相同的輸出特征映射,你可以選擇使用填充。填充通過增加合適數量的行列向量,對于
在卷積層中,填充可以通過“填充”參數來配置,“填充”參數中包含兩個值:"valid"和"same",前者意味著不填充,后者意味著讓輸入輸出有相同的寬高,而padding參數的默認值是"valid"。
理解卷積滑動
另一個影響輸出大小的是"stride"。在我們目前為止對于卷積的描述,我們假設卷積窗口的中心塊都配置好了。然而,兩個連續的窗口之間實際上有一個卷積的參數,叫做"stride",默認值為1。在接下來這幅圖你可以看到stride設為2的情況。
使用stride為2,意味著寬和高都通過因子2來下采樣。滑動卷積在實際中很少使用,盡管他們能在很多種模型中派上用場,熟悉這塊內容總是好的。
為了對特征進行下采樣,除了滑動,還可以通過最大池化算子來做到。
最大池化算子
在我們的卷積例子里面,你已經注意到特征映射的數量在經過最大池化以后會減半,就像滑動卷積一樣,對于降采樣非常的積極。
最大池化由從輸入特征來提取窗口以及輸出每個通道的最大值。這就和卷積很相似了。
我們為什么要做池化呢?如果把池化這一步去掉會發生什么呢?
model_no_max_pool = models.Sequential()
model_no_max_pool.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))
model_no_max_pool.add(layers.Conv2D(64, (3, 3), activation='relu'))
輸出結構:
>>> model_no_max_pool.summary()
Layer (type) Output Shape Param #
================================================================
conv2d_4 (Conv2D) (None, 26, 26, 32) 320
________________________________________________________________
conv2d_5 (Conv2D) (None, 24, 24, 64) 18496
________________________________________________________________
conv2d_6 (Conv2D) (None, 22, 22, 64) 36928
================================================================
Total params: 55,744
Trainable params: 55,744
Non-trainable params: 0
這么設置有什么錯嗎?有兩點:
- 這不有利于學習空間層次的特征。
的窗口在第3層將只包含輸入中
的信息。卷積網絡能夠學習到的高級特征還是太小。我們需要最后一層卷積層能夠包含全部輸入的信息。
- 最終的特征的系數太多了。
簡短的說,使用下采樣是為了減少特征系數的數量,同時讓連續的卷積層去處理更大的窗口以減少空間濾波器數量。
注意最大池化不是唯一可以取得下采樣的方法。你也知道stride也可以,你還可以使用平均池化。然而最大池化往往比這些替代方法表現得好。