1. 程式人生 > >「機器學習」徹底搞懂CNN

「機器學習」徹底搞懂CNN

0?wx_fmt=gif&wxfrom=5&wx_lazy=1

640?wx_fmt=jpeg&wxfrom=5&wx_lazy=1

作者:水奈樾

上世紀科學家們發現了幾個視覺神經特點,視神經具有區域性感受眼,一整張圖的識別由多個區域性識別點構成;不同神經元對不同形狀有識別能力,且視神經具有疊加能力,高層複雜的圖案可以由低層簡單線條組成。之後人們發現經過conclusional的操作,可以很好反映視神經處理計算的過程,典型的是1998年LeCun發明的LeNet-5,可以極大地提升識別效果。

本文主要就convolutional layer、pooling layer和整體CNN結構展開

一、Convolutional Layer卷積層

1、原理和引數

可以模擬區域性感受眼的性質,同上一層不是全連線,而是一小塊區域連線,這一小塊就是區域性感受眼(receptive field)。並且通過構造特定的卷積神經元,可以模擬不同神經元對不同形狀刺激不同反應的性質。如下圖所示,一個神經元處理一層會形成一個feature map,多層疊加,層數逐漸加深。

0?wx_fmt=png

感受眼(kernel或filter)的尺寸可以看做fh*fw,由於感受眼本身具有尺寸,feature map會不斷縮小,為了處理方便,使得每層大小不變,於是我們每層加值為0的邊(zero padding),保證經過處理以後的feature map同前一層尺寸一樣。多層之間的卷積運算操作,相當於和原來畫素對應位置做乘法。如下左圖所示,加了邊後可以保證上下層大小一致,右圖表示每層之間convolve的操作(如果不加zero padding)。

0?wx_fmt=png

0?wx_fmt=png

但上圖所示只是簡單例子,一般掃描的是三維影象(RGB),就不是一個矩陣,而是一個立方體,我們用一個三維塊去掃描它,原理同上圖相同。

有時掃描時不是順序去掃,而是跳躍著掃描,每次移動2-3個畫素值(stride),但並非完全分離不會造成資訊丟失,這樣形成的feature map相較於原始圖片縮小,實現資訊聚集的效果。

0?wx_fmt=png

就像如下灰度圖(2d)中所示,左邊只提取豎線(vertical filter),右邊只提取橫線(horizontal filter)可看出橫樑部分變亮,大量不同的這樣的filter(比如可以識別邊角、折線的filter)的疊加,可形成多張feature maps

0?wx_fmt=png

下圖是一個3d的RGB效果,每個kernel(filter)可以掃描出一張feature map,多個filter可以疊加出很厚的feature maps,前一層filter做卷積可以形成後一層的一個畫素點

0?wx_fmt=png

如下圖,可以代表i行j列k深度的一個輸出畫素值,k’代表第k個filter,w代表filter中的值,x代表輸入,b是偏值。

0?wx_fmt=png

2、TensorFlow實現

以下是使用TensorFlow實現的程式碼,主要使用conv2d這個函式

import numpy as np
from sklearn.datasets import load_sample_images
# Load sample images
dataset = np.array(load_sample_images().images, dtype=np.float32)
# 一共4維,channel表示通道數,RGB是3
batch_size, height, width, channels = dataset.shape
# Create 2 filters
# 一般感受眼大小7*7,5*5,3*3,設定2個kernel,輸出2層feature map
filters_test = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
# 第一個(0)filter的設定,7*7矩陣中,3是中間
filters_test[:, 3, :, 0] = 1 # vertical line
# 第二個(1)filter的設定
filters_test[3, :, :, 1] = 1 # horizontal line
# a graph with input X plus a convolutional layer applying the 2 filters
X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
# 雖然輸入是一個四維影象,但是由於batch_size和channel都已經固定,所以使用conv2d
# strides設定,第一個和第四個都是1表示不可以跳過batch_size和channel
# 那兩個2表示橫縱向都縮減2,相當於整張圖片縮減為原來四分之一,做了75%的縮減
convolution = tf.nn.conv2d(X, filters, strides=[1,2,2,1], padding="SAME")
with tf.Session() as sess:
    output = sess.run(convolution, feed_dict={X: dataset})

下面是padding的值SAME和VALID的區別(filter的寬度為6,stride為5),SAME確保所有影象資訊都被convolve新增zero padding,而VALID只新增包含在內的畫素點

0?wx_fmt=png

3、所耗記憶體計算

相比於傳統的全連線層,卷積層只是部分連線,節省了很多記憶體。

比如:一個具有5*5大小filter的卷積層,輸出200張150*100大小的feature maps,stride取1(即不跳躍),padding為SAME。輸入是150*100大小的RGB影象(channel=3),總共的引數個數是200*(5*5*3+1)=15200,其中+1是bias;如果輸出採用32-bits float表示(np.float32),那麼每張圖片會佔據200*150*100*32=9600000bits(11.4MB),如果一個training batch包含100張圖片(mini-batch=100),那麼這一層卷積層就會佔據1GB的RAM。

可以看出,訓練卷積神經網路是非常消耗記憶體的,但是使用時,只用到最後一層的輸出即可。

二、Pooling Layer池化層

1、原理和引數

當圖片大小很大時記憶體消耗巨大,而Pooling Layer所起的作用是濃縮效果,緩解記憶體壓力。

即選取一定大小區域,將該區域用一個代表元素表示。具體的Pooling有兩種,取平均值(mean)和取最大值(max)。如下圖所示是一個取最大值的pooling layer,kernel大小為2*2,stride大小取決於kernel大小,這裡是2,即剛好使得所有kernel都不重疊的值,因而實現高效的資訊壓縮,將原始影象橫縱壓縮一半,如右圖所示,特徵基本都完全保留了下來。

0?wx_fmt=png

pooling這個操作不影響channel數,在feature map上也一般不做操作(即z軸一般不變),只改變橫縱大小。

2、TensorFlow實現

# Create a graph with input X plus a max pooling layer
X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
# 選用取最大值的max_pool方法
# 如果是取平均值,這裡是mean_pool
# ksize就是kernel大小,feature map和channel都是1,橫向縱向是2
max_pool = tf.nn.max_pool(X, ksize=[1,2,2,1], strides=[1,2,2,1],padding="VALID")

with tf.Session() as sess:
    output = sess.run(max_pool, feed_dict={X: dataset})

三、整體CNN框架

典型CNN architecture

0?wx_fmt=png

有名的CNN架構:

LeNet(MISIT上)-1998:輸入32*32(在28*28影象上加了zero padding)。第一層kernel用了6個神經元,kernel大小5*5,stride取1,輸出就是28*28;第二層做了average pooling,2*2的kernel,stride是2,輸出就變為原來的一半,不改變feature map數目;第三層放了16個神經元,其他同理;第五層用了120個神經元,5*5的kernel對5*5的輸入做卷積,沒法再滑動,輸出為1*1;F6用120個1*1的輸出全連線84個神經元,Out全連線10個神經元,對應手寫體識別輸出的10個數字。

啟用函式前面都用的tanh,是傳統CNN中常用的,輸出層用了RBF比較特殊,是一個計算距離的方式去判斷和目標輸出間距離做lost。。

0?wx_fmt=png

AlexNet-2012:最早應用於競賽中,近10%的提高了準確度

輸入224*224的彩色影象,C1是個很大的11*11的filter,stride=4。。最後連做3層convolution。。最後輸出1000個類的分類結果。

啟用函式使用ReLU,這在現今很流行,輸出層用的softmax

0?wx_fmt=png

AlexNet使用了一個小技巧是Local Response Normalization(LRN區域性響應歸一化)

這種操作可以在傳統輸出上加一個bias,考慮到近鄰的一些輸出影響。即一個輸出旁邊有很牛掰的輸出的話,它的輸出就會慫了,收到抑制,可以看到,含β的整個項都在分母上。但後來發現,這個技術對分類器的提升也不是很明顯,有的就沒有用。

0?wx_fmt=png

GoogleLeNet-2014:

大量應用Inception module,一個輸入進來,直接分四步進行處理,這四步處理完後深度直接進行疊加。在不同的尺度上對圖片進行操作。大量運用1*1的convolution,可以靈活控制輸出維度,可以降低引數數量。

如右圖所示,輸入是192,使用了9層inception module,如果直接用3*3,5*5引數,可以算一下,之後inception引數數目是非常大的,深度上可以調節,可以指定任意數目的feature map,通過增加深度把維度減下來。inception模組6個引數剛好對應這6個convolution,上面4個引數對應上面4個convolution,加入max pool不會改變feature map數目(如480=128+192+96+64)。

0?wx_fmt=png

0?wx_fmt=png

將正確率升高到95-96%,超過人類解析度,因為image net中但是狗的種類就有很多,人類無法完全一一分辨出。

ReSNet殘差網路-2015:

不再直接學習一個目標函式,輸入直接跳過中間層直接連到輸出上,要學習的是殘差f(x),輸入跳過中間層直接加到輸出上。

好處是:深度模型路徑依賴的限制,即gradient向前傳導時要經過所有層,如果中間有層死掉了,前面的層就無法得到訓練。殘差網路不斷跳躍,即使中間有的層已經死掉,資訊仍舊能夠有效流動,使得訓練訊號有效往回傳導。

0?wx_fmt=png

0?wx_fmt=png

0?wx_fmt=jpeg