全文共9940字,預計學習時長20分鐘或更長
不同神經網絡結構各有所長。本文主要介紹如何在Python中使用TensorFlow和Keras構建卷積神經網絡。
卷積神經網絡是過去十年中深度學習成為一大熱點的部分原因。今天將使用TensorFlow的eager API來訓練圖像分類器,以辨別圖像內容是狗還是貓。
人工神經網絡在許多領域都展現出了其強大功能,最近已經應用到很多行業中。然而,不同深度學習結構各有以下優勢:
· 圖像分類(卷積神經網絡)。
· 圖像、音頻和文本生成(GANS,RNN)。
· 時間序列預測(RNNS,LSTM)。
· 推薦系統(波爾茲曼機)。
· 等等 (如,回歸)。
本文將集中討論其中的第一項。
卷積神經網絡的概念
在多層感知器(Multilayer Perceptrons,簡稱MLP)中,每一層的神經元都連接到下一層的所有神經元。一般稱這種類型的層為完全連接。
多層感知器示例。圖片來源: astroml
卷積神經網絡則不同:它們包含卷積層。
在完全連接層上,每個神經元的輸出將是前一層的線性變換,由非線性激活函數(如ReLu或Sigmoid)組成。
相反,卷積層中每個神經元的輸出僅僅是前一層神經元的子集(通常很小)的函數。
圖片來源: Brilliant
卷積層上的輸出是對前一層神經元的子集進行卷積的結果,然後得出激活函數。
卷積的概念
如果給定輸入矩陣A(通常是前一層的值)以及稱為卷積核或濾波器K的權值矩陣(通常小得多),卷積運算後將輸出新的矩陣B。
圖片來自@RaghavPrabhu
如果K是C×C矩陣,則B中的第一個元素的計算方法為:
· 取A的第一個C×C子矩陣。
· 將每個元素乘以K中相應的權值。
· 將所有結果相加。
最後兩步相當於將A的子矩陣和K的子矩陣平面化,並計算結果的向量的點積。
然後向右滑動K以獲取下一個元素。依此類推,對A的每一行重複此過程。
卷積圖例 圖片來自 @RaghavPrabhu
根據需要,只能從以C排和C列為中心的卷積核開始,以避免「越界」,或者假設「A之外」的所有元素都有一個默認值(通常為0)——這將決定B究竟是小於A還是等於A。
可以看到,如果A是一個N×M矩陣,那麼B中每個神經元的值將不取決於N×M權重,而只取決於其中的C×C(更少)。
這使得卷積層比完全連接層更輕便,幫助卷積模型更快地學習。
最終將在每一層上將使用大量卷積核(獲取一個矩陣疊層作為每一層的輸出)。然而,它仍然比曾經的MLP要輕便得多。
工作原理
為什麼每個神經元對其他大多數神經元的影響可以忽略不計呢?整個系統的前提是,每個神經元都受到它的「鄰域」的強烈影響。距離較遠的神經元卻對此只有很小的影響。
這一假設直觀地表現在圖像中——說到輸入層,就想到每個神經元將是一個像素或像素的RGB值。這也是卷積神經網絡方法在圖像分類中如此有效的部分原因。
舉個例子,如果抓取一張藍天的照片的局域,附近的區域可能也會用類似的色調顯示天空。
像素的鄰域通常具有與其相似的RGB值。如果沒有,那麼可能意味著它是一個圖形或物體的邊緣。
如果用紙筆(或計算器)做一些卷積,就會意識到,如果是在某種邊緣上,某些卷積核會增加輸入的強度。在其他邊緣,則會減少強度。
下面是卷積核V和H的示例:
垂直和水平邊緣的濾波器
V過濾垂直邊緣(上面的顏色與下面的顏色非常不同),H過濾水平邊緣。注意其中一個是另一個的轉置。
卷積示例
以下是一組未經過濾的貓咪照片:
如果分別應用水平和垂直邊緣濾波器,會得出以下結果:
可以看到某些特徵是變得更加顯著的,而另一些特徵逐漸消失。有趣的是,每個過濾器都展示了不同的特徵。
這就是卷積神經網絡學習識別圖像特徵的方法。
讓它們適應自己的卷積核權值比任何手動方法都容易得多。手動表達像素之間的關係是難以實現的。難以想像人應該如何徒手釐清像素之間的關係!
想要真正理解每一個卷積對圖片的作用,強烈推薦此網站:http://setosa.io/ev/image-kernels/。它比任何一本書或教程的幫助都大。
之前已經介紹到了一些理論。現在進入到實踐環節。
如何在TensorFlow中訓練卷積神經網絡
TensorFlow是Python最流行的深度學習框架。
筆者也聽說過PyTorch很好用,但從來沒有機會嘗試過。
筆者已經編寫了一個如何使用TensorFlow的KerasAPI來訓練神經網絡的教程,著重介紹了自動編碼器:http://www.datastuff.tech/machine-learning/autoencoder-deep-learning-tensorflow-eager-api-keras/
本文將嘗試三種不同的體系結構,優中選優。
和往常一樣,所有的代碼都可以GitHub上找到(https://github.com/StrikingLoo/Cats-and-dogs-classifier-tensorflow-CNN),所以可以自己進行嘗試,或者參考本文示例。當然,本文還會進行Python代碼段的展示。
數據集
下面將訓練一個神經網絡來預測一幅圖像包含的是一隻狗還是一隻貓。為此,將使用Kaggle的相關數據集,其中包含不同解析度的12500隻貓和12500隻狗的圖片。
用NumPy加載和預處理圖像數據
具有固定維數的特徵向量或矩陣輸入至神經網絡中。那麼它是如何從圖片中生成的呢?
幸運的是,Python的圖像庫提供了一種簡單方法,可將圖像加載為NumPy數組,一個RGB值的高×寬矩陣。
在Python中進行圖像過濾時,已經完成這個操作了,所以接下來將再次使用這段代碼。
但仍然需要對固定維度進行修復。那麼如何選擇輸入層的維度呢?
這一點很重要,因為必須根據所選解析度調整每個圖片的大小。縱橫比不能扭曲太多,以免給網絡帶來太多噪聲。
下面是數據集中最常見的尺寸。
customAdam = keras.optimizers.Adam(lr=0.001)
model.compile(optimizer=customAdam, # Optimizer
# Loss function to minimize
loss="mean_squared_error",
# List of metrics to monitor
metrics=["binary_crossentropy","mean_squared_error"])
print( # Fit model on training data )
history = model.fit(x_train,
labels_train,
batch_size=32,
shuffle = True, #important since we loaded cats first, dogs second.
epochs=3,
validation_data=(x_valid, labels_valid))
#Train on 4096 samples, validate on 2048 samples
#loss: 0.5000 - binary_crossentropy: 8.0590 - mean_squared_error: 0.5000 - val_loss: 0.5000 - val_binary_crossentropy: 8.0591 - val_mean_squared_error: 0.5000
這是對1000張照片進行抽樣的結果。對象增加至5000張時,結果不變。
最常見的尺寸是375×500,但筆者決定將其除以4作為網絡的輸入。
這就是目前的圖像加載代碼。
IMG_SIZE = (94, 125)
def pixels_from_path(file_path):
im = Image.open(file_path)
im = im.resize(IMG_SIZE)
np_im = np.array(im)
#matrix of pixel RGB values
return np_im
最後可以使用此代碼段加載數據。由於筆者的電腦內存有限,選擇4096張圖片作為訓練集,1024張圖片作為驗證集。
讀者試驗時可隨意將這些數字增加到最大值(如10000用於訓練集,2500用於驗證集)。
SAMPLE_SIZE = 2048
print("loading training cat images...")
cat_train_set = np.asarray([pixels_from_path(cat) for cat in glob.glob( cats/* )[:SAMPLE_SIZE]])
print("loading training dog images...")
dog_train_set = np.asarray([pixels_from_path(dog) for dog in glob.glob( dogs/* )[:SAMPLE_SIZE]])
valid_size = 512
print("loading validation cat images...")
cat_valid_set = np.asarray([pixels_from_path(cat) for cat in glob.glob( cats/* )[-valid_size:]])
print("loading validation dog images...")
dog_valid_set = np.asarray([pixels_from_path(dog) for dog in glob.glob( dogs/* )[-valid_size:]])
# generate X and Y (inputs and labels).
x_train = np.concatenate([cat_train_set, dog_train_set])
labels_train = np.asarray([1 for _ in range(SAMPLE_SIZE)]+[0 for _ in range(SAMPLE_SIZE)])
x_valid = np.concatenate([cat_valid_set, dog_valid_set])
labels_valid = np.asarray([1 for _ in range(valid_size)]+[0 for _ in range(valid_size)])
訓練神經網絡
首先展示一個標準MLP的完成程度作為基準。筆者希望MLP的表現比較糟糕,這樣才能顯示出卷積神經網絡具有如此大的開創性。
下面是一個隱藏層,為完全連接的神經網絡。
from tensorflow import keras
from tensorflow.keras import layers
total_pixels = img_size[0] *img_size[1] * 3
fc_size = 512
inputs = keras.Input(shape=(img_size[1], img_size[0],3), name= ani_image )
x = layers.Flatten(name = flattened_img )(inputs) #turn image to vector.
x = layers.Dense(fc_size, activation= relu , name= first_layer )(x)
outputs = layers.Dense(1, activation= sigmoid , name= class )(x)
model = keras.Model(inputs=inputs, outputs=outputs)
本文的所有訓練都使用AdamOptimizer,因為它速度最快。筆者只調整了每個模型的學習速率(原速率為1e-5)。
筆者對本模型進行了10次訓練,基本會進行隨機推測。
已確定對訓練數據進行置亂,因為它是按順序加載的,可能會使模型有偏差。
customAdam = keras.optimizers.Adam(lr=0.001)
model.compile(optimizer=customAdam, # Optimizer
# Loss function to minimize
loss="mean_squared_error",
# List of metrics to monitor
metrics=["binary_crossentropy","mean_squared_error"])
print( # Fit model on training data )
history = model.fit(x_train,
labels_train,
batch_size=32,
shuffle = True, #important since we loaded cats first, dogs second.
epochs=3,
validation_data=(x_valid, labels_valid))
#Train on 4096 samples, validate on 2048 samples
#loss: 0.5000 - binary_crossentropy: 8.0590 - mean_squared_error: 0.5000 - val_loss: 0.5000 - val_binary_crossentropy: 8.0591 - val_mean_squared_error: 0.5000
使用MSE作為損失函數,因為它通常展示的較為直觀。如果MSE在二進位分類中是0.5,那麼就可以像往常一樣預測為0。然而,層數更多的或不同損失函數的MLP並沒有發揮出較好的性能。
其他一些成熟的監督學習算法,如Boosted Trees (使用 XGBoost)等,在圖像分類上更為糟糕。
訓練卷積神經網絡
單單一個卷積層究竟有多優秀?下面在模型中添加一個卷積層,然後進行觀察。
fc_layer_size = 128
img_size = IMG_SIZE
conv_inputs = keras.Input(shape=(img_size[1], img_size[0],3), name= ani_image )
conv_layer = layers.Conv2D(24, kernel_size=3, activation= relu )(conv_inputs)
conv_layer = layers.MaxPool2D(pool_size=(2,2))(conv_layer)
conv_x = layers.Flatten(name = flattened_features )(conv_layer) #turn image to vector.
conv_x = layers.Dense(fc_layer_size, activation= relu , name= first_layer )(conv_x)
conv_x = layers.Dense(fc_layer_size, activation= relu , name= second_layer )(conv_x)
conv_outputs = layers.Dense(1, activation= sigmoid , name= class )(conv_x)
conv_model = keras.Model(inputs=conv_inputs, outputs=conv_outputs)
customAdam = keras.optimizers.Adam(lr=1e-6)
conv_model.compile(optimizer=customAdam, # Optimizer
# Loss function to minimize
loss="binary_crossentropy",
# List of metrics to monitor
metrics=["binary_crossentropy","mean_squared_error"])
#Epoch 5/5 loss: 1.6900 val_loss: 2.0413 val_mean_squared_error: 0.3688
在本網絡中添加一個卷積層(具有24個卷積核),然後添加兩個完全連接的層。
所有的最大池化都將四個神經元減少到一個,最高值取自四個神經元中。
在僅僅5次訓練之後,它的表現已經比以前的網絡要好得多。
驗證MSE為0.36,已經比隨機推測要好得多。但是請注意,必須使用一個小得多的學習率。
儘管它在短短幾次之內就掌握了,但每次訓練所花的時間都要長得多,模型的體積也要大得多(200+MB)。
筆者還測量了預測值和驗證值之間的皮爾遜相關係數。本模型的得分為15.2%。
preds = conv_model.predict(x_valid)
preds = np.asarray([pred[0] for pred in preds])
np.corrcoef(preds, labels_valid)[0][1] # 0.15292172
帶有兩個卷積層的神經網絡
既然神經網絡模型的完成度如此之高,筆者決定擴大體積——添加了另一個卷積層,使兩者都增大(每個48個卷積核)。
這意味著模型可以從圖像中學習更複雜的特徵。然而,可以預見的是,這也意味著內存幾乎要爆炸了。而且訓練花費的時間要長得多(15次需要半小時)。
fc_layer_size = 256
img_size = IMG_SIZE
conv_inputs = keras.Input(shape=(img_size[1], img_size[0],3), name= ani_image )
#first convolutional layer.
conv_layer = layers.Conv2D(48, kernel_size=3, activation= relu )(conv_inputs)
conv_layer = layers.MaxPool2D(pool_size=(2,2))(conv_layer)
#second convolutional layer.
conv_layer = layers.Conv2D(48, kernel_size=3, activation= relu )(conv_layer)
conv_layer = layers.MaxPool2D(pool_size=(2,2))(conv_layer)
conv_x = layers.Flatten(name = flattened_features )(conv_layer) #turn image to vector.
conv_x = layers.Dense(fc_layer_size, activation= relu , name= first_layer )(conv_x)
conv_x = layers.Dense(fc_layer_size, activation= relu , name= second_layer )(conv_x)
conv_outputs = layers.Dense(1, activation= sigmoid , name= class )(conv_x)
conv_model = keras.Model(inputs=conv_inputs, outputs=conv_outputs)
#Epoch 15/15
#4096/4096 [==============================] - 154s 38ms/sample -
#loss: 0.8806 - binary_crossentropy: 0.8806 - mean_squared_error: 0.2402
#val_loss: 1.5379 - val_binary_crossentropy: 1.5379 - val_mean_squared_error: 0.3302
#Labels vs predictions correlation coefficient 0.2188213
結果極佳。預測值與標記值之間的皮爾遜相關係數達到0.21,驗證MSE達到0.33。
那麼來測量一下網絡的準確度。如果1代表一隻貓,0代表一隻狗,那麼就可以說「如果模型預測的值高於某個閾值t,那麼就預測貓,否則就預測狗。」
在試驗了10個簡單的閾值後,該網絡的最大準確率為61%。
cat_quantity = sum(labels_valid)
for i in range(1,10):
print( threshold : +str(.1*i))
print(sum(labels_valid[preds > .1*i])/labels_valid[preds > .1*i].shape[0])
更大的卷積神經網絡
由於明顯地增加模型的大小使它學習得更好,所以嘗試使兩個卷積層都增大,每個層有128個濾波器。
模型的其他部分不變,學習速率也不變。
fc_layer_size = 256
img_size = IMG_SIZE
conv_inputs = keras.Input(shape=(img_size[1], img_size[0],3), name= ani_image )
conv_layer = layers.Conv2D(128, kernel_size=3, activation= relu )(conv_inputs)
conv_layer = layers.MaxPool2D(pool_size=(2,2))(conv_layer)
conv_layer = layers.Conv2D(128, kernel_size=3, activation= relu )(conv_layer)
conv_layer = layers.MaxPool2D(pool_size=(2,2))(conv_layer)
conv_x = layers.Flatten(name = flattened_features )(conv_layer) #turn image to vector.
conv_x = layers.Dense(fc_layer_size, activation= relu , name= first_layer )(conv_x)
conv_x = layers.Dense(fc_layer_size, activation= relu , name= second_layer )(conv_x)
conv_outputs = layers.Dense(1, activation= sigmoid , name= class )(conv_x)
huge_conv_model = keras.Model(inputs=conv_inputs, outputs=conv_outputs)
#epoch 13
# 445s loss: 0.3749 - binary_crossentropy: 0.3749 - mean_squared_error: 0.1190
# val_loss: 0.8217 - val_binary_crossentropy: 0.8217 - val_mean_squared_error: 0.2470
# Pearson coerrcoef : 0.3037038
該模型的相關係數最終達到了30%!最佳準確度為67%,這意味著它的預測準確次數恰好是預測總數的三分之二。
筆者仍然認為一個更大的模型可以更好地擬合數據。
正因為如此,在下一次訓練中,筆者決定將完全連接層的大小增加一倍至512個神經元。
但是,筆者確實將第一個卷積層的大小減少了一半,僅用了64個濾波器。
由此發現如果減小第一個卷積層,並且隨著它們的深入增加它們的大小,將會獲得更好的模型性能。
幸運的是,這個結論是正確的!
尺寸大一倍的完全連接層的模型的確認損失為0.75,相關係數為42%。
它的準確度是75%,這意味著它正確預測數可以達到預測總數的四分之三!
這清楚地表明它已經完成學習,即使分數不是最高的(更不用說打敗人類了)。
這表明,至少在這種情況下,增加完全連接層的尺寸比增加卷積濾波器的數量更有效。
筆者本可以繼續嘗試越來越大的模型,但收斂過程已經花了大約一個小時。
通常,需要在模型的大小和時間限制之間進行權衡。
大小限制了網絡對數據的擬合程度(小模型會欠擬合),但也要考慮時間成本。
如果截止時間明確,也要考慮這種情況。
結論
可以看到,卷積神經網絡在圖像分類任務上明顯優於普通的結構。本文還試驗了不同方法來度量模型的性能(相關係數、準確性)。
本文還介紹了模型的大小(防止欠擬合)和收斂速度之間的權衡。
最後,使用了TensorFlow的eager API來輕鬆地訓練一個深度神經網絡,並使用NumPy進行圖像預處理(儘管很簡單)。
對於以後的文章,相信可以用不同的池化層、濾波器大小、跨步和預處理對同一任務進行更多的實驗。
留言 點讚 關注
我們一起分享AI學習與發展的乾貨
編譯組:楊月、夏伊凡
相關連結:
https://www.kdnuggets.com/2019/07/convolutional-neural-networks-python-tutorial-tensorflow-keras.html
如需轉載,請後臺留言,遵守轉載規範