深度學習三人行(第9期)----卷積神經網絡實戰進階(附代碼)

上一期,我們一起學習了深度學習中卷積神經網絡的通俗原理,

深度學習三人行(第8期)----卷積神經網絡通俗原理

接下來我們一起學習下關於CNN的代碼實現,內存計算和池化層等相關知識,我們多多交流,共同進步。本期主要內容如下:

  • CNN實現(TensorFlow)
  • CNN之內存計算
  • CNN之池化層
  • 小結

公衆號內回覆關鍵字,即可下載代碼,關鍵字見文末!


一. CNN實現(TensorFlow)

在TensorFlow中,每一個圖像都有一個3D的tensor,shape爲[height, width, channels]。每一個mini-batch是以一個shape爲[min-batch size, height, width, channels]的4Dtensor來表示的。卷積層的權重是以shape爲[f_h, f_w, f_n, f_n'],其中偏置項是以1D tensor[f_n]表示。接下來,我們一起看下,在TensorFlow中是怎麼實現的:首先,代碼中用sklearn中的load_sample_images()來加載圖片。然後手工創建了兩個7x7的卷積核,一個爲水平直線,一個爲豎直直線。接着讓圖像通過一個卷積層conv2d()(邊界擴充0,stride = 2),最後畫出一個特徵圖。如下:

 1import numpy as np
 2from sklearn.datasets import load_sample_images
 3# Load sample images
 4dataset = np.array(load_sample_images().images, dtype=np.float32)
 5batch_size, height, width, channels = dataset.shape
 6# Create 2 filters
 7filters_test = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
 8filters_test[:, 3, :, 0] = 1 # vertical line
 9filters_test[3, :, :, 1] = 1 # horizontal line
10# Create a graph with input X plus a convolutional layer applying the 2 filters
11X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
12convolution = tf.nn.conv2d(X, filters, strides=[1,2,2,1], padding="SAME")
13with tf.Session() as sess:
14    output = sess.run(convolution, feed_dict={X: dataset})
15plt.imshow(output[0, :, :, 1]) # plot 1st image's 2nd feature map
16plt.show()

其中X是一個mini-batch(4D的tensor),filters也是一個4D的tensor,stride是一個有4個元素的1D tensor其中中間連個值爲豎直和水平的stride,第一個元素和第四個元素必須是1,這個是備用的,以後可能會用在batch和channel上。padding必須設置爲“VALID”或“SAME”,當設爲“VALID”的時候,卷積層不進行邊界擴充,但是可能會根據stride來忽略圖像下面的某些行或右側的某些列。如果設置爲“SAME”,卷積層會進行對邊界擴充0.這種情況下,輸出神經元個數等於輸入神經元個數除以stride,在下面的例子中輸出層神經元爲3,其中stride爲5.如下:

所以,卷積層有一些參數要設置,比如:卷積核的個數,卷積核的高,卷積核的寬,以及stride,padding的類型。有時候,可以通過交叉驗證來找最優的參數組合,但是這樣往往是比較耗時的。後面我們會介紹一些通用的網絡結構,可能會給我們一些啓發,在實踐中什麼樣的參數組合通常會得出最優的性能。


二. CNN之內存計算

卷積神經網絡的一個問題就是需要大量的內存來處理數據,特別是在training階段,因爲反向傳輸需要保留前向傳輸的數據。舉個例子,比方一個卷積層,其中卷積核爲5x5,輸出200個特徵圖,每一個特徵圖大小爲150x100, stride爲1,padding爲"SAME",如果輸入爲一個150x100的RGB(3通道)圖像的話,那麼權重參數的個數爲:

(5x5x3+1)x200 = 15200

其中+1爲考慮到偏置項,相對來說,參數比全連接層要少。然而,每一個特徵圖包括150x100個神經元,每一個神經元需要計算5x5x3=75個權重,那麼總共就有225million個浮點型數據相乘,雖然沒有比全連接更糟,但是仍然是一個巨大的計算。如果特徵圖用32位float表示的話,那麼一個卷積層將會佔用:

200x150x100x32 = 96million bits

約11.4M內存,然而這只是一個樣本,如果每個batch有100張圖的話,那麼單單這一層卷積層就要耗費超過一個G的內存。

在預測的時候,當一個卷積層計算的時候,就會將上一層所佔用的內存釋放掉,所以僅僅需要兩個連續卷積層所佔的內存即可。但是在計算的時候,每一次向前傳輸的數據都要爲向後傳輸而保留,所以所需要的內存至少爲全部層所佔用的內存總和。

如果在training的時候,由於內存的問題導致crash,那麼可以通過減少mini-batch的size來進行降低內存佔有。當然也可以通過stride降維,或者減少一些層,甚至可以用16bit的float代替32bit的float或者多個設備來跑。

接下來,我們一起看一下CNN的另一個重要的構成:池化層。


三. CNN之池化層

一旦我們理解了卷積層的工作原理之後,池化層就相對來說比較簡單了,池化層的目標就是爲了降低計算負載,內存使用,參數數量,也可以降低過擬合的風險,而對輸入進行的下采樣。原理和卷積層一樣,池化層的每一個神經元和上一層中有限區域(一個矩形的感受野)的神經元相連接。我們也必須定義感受野的size, stride,以及padding類型。然而不同的是,池化層的神經元沒有權重,它的輸出僅僅是輸入區域的最大值或均值,下圖顯示了一個最大值的池化層,也是最常用的池化層,這個例子中用了一個2x2的池化核,stride=2,沒有padding,也就是說,僅僅輸出上一層中池化核位置的最大值到下一層。其他的輸入都丟掉。

一般池化層在每一個輸入通道上單獨工作,所以經過池化層後,輸入輸出通道數不變。

在TensorFlow中實現池化還是蠻簡單的,下面的代碼創建一個2x2的池化核的池化層,stride爲2,沒有padding,然後應用到所有圖像上。

1[...] # load the image dataset, just like above
2# Create a graph with input X plus a max pooling layer
3X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
4max_pool = tf.nn.max_pool(X, ksize=[1,2,2,1], strides=[1,2,2,1],padding="VALID")
5with tf.Session() as sess:
6    output = sess.run(max_pool, feed_dict={X: dataset})
7plt.imshow(output[0].astype(np.uint8)) # plot the output for the 1st image
8plt.show()

上面參數ksize包括了池化核的4D參數[batch size, height, width, channels].TensorFlow目前暫時不支持跨樣本池化,所以第一個數據必須是1,由於目前也不支持跨通道池化,所以最後一個參數也得是1.當然,想創建一個均值的池化層的話,僅僅將上面的max_pool替換爲avg_pool即可。


四. 小結

今天,我們從TensorFlow上實現卷積層,以及卷積層的內存計算和池化層的相關原理知識等方面,更進一步的瞭解了CNN的知識。在學習的路上,我們共同進步,多謝有你。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章