1. 程式人生 > >卷積神經網路CNN理解

卷積神經網路CNN理解

雖然在機器學習和深度學習這塊投入了不少精力,可是近一段時間在看論文的過程中,對一些細節方面和演算法深層次的原因方面總感覺有些不得其意,所以回過頭來,將基礎部分查漏補缺。

首先,我們解釋一下為什麼卷積神經網路要比傳統的神經網路要好。影象是由一個個畫素點組成的,以灰色28*28大小的影象為例,如果使用全連線的網路結構,網路中的神經元與相鄰層上的每個神經元均連線,如下圖所示,那麼輸入層就會有28*28=784個神經元,假若隱含層採用15個神經元,大約會需要784*15=11760個權重,加上輸出層的10個神經元,整個三層神經網路總共需要784*15*10=117600個權重(除去偏置項),引數是如此巨大,計算複雜,使得調參變得很困難,所以不建議使用傳統神經網路。而CNN採用了區域性連線,權值共享和下采樣

三種方法減少引數量。

卷積神經網路(CNN)是一種多層的監督學習神經網路,包括卷積層(Conv)、池化層(Pooling),全連線層(FC),其中卷積層和池化層是實現卷積網路特徵提取功能的核心模組。通常情況下,CNN的常見架構模式為:

INPUT-->[[Conv]*N-->Pool?]*M-->[FC]*K

也就是N個卷積層疊加,然後可選疊加一個Pooling層,重複這個子結構M次,最後疊加K個全連線層。

卷積神經網路包含了一個由卷積層和池化層構成的特徵提取器,在卷積層中,一個神經元只與部分鄰層神經元相連【區域性連線】。在CNN中的卷積層中包含多個feature map, 每個feature map 由一些矩形排列的神經元組成(例如某個feature map的大小為m*n,那麼該map共有m*n個神經元),同一個feature map的神經元通共享權值(卷積核)【權值共享

】。共享權值帶來的直接好處就是減少網路各層之間的連線,同時降低過擬合的風險。子取樣也是池化,通常有均值子取樣(mean pooling)和最大值子取樣(max pooling)兩種形式,通過池化來降低卷積層輸出的特徵向量【池化】。卷積和池化大大簡化了模型複雜度,減少了模型的引數。

卷積運算

對於一個尺寸大小為N*N的feature map,如果步幅大小為S,filter的尺寸大小為F*F,Zero Padding的數量是P,那麼經過卷積運算得到M*M大小的feature map,其中。為了更好的描述卷積計算過程,我們假設輸入map為X,filter為W,輸出的map為A,用表示X中第i行第j列的元素,用

表示W中第m行第n列的權重,用表示filter的偏置項,用表示A中第p行第q列中的元素,f為啟用函式,則計算卷積的公式為。舉一個小例子,設有一個5*5的feature map,使用一個3*3的filter進行卷積,步幅為S=1,無Zero Padding,那麼就得到一個3*3的feature map, 下面的動圖很好的展示了計算過程。

前面講的是深度為1的卷積層的計算方法,如果深度大於1,卷積的運算應該是什麼樣的?我也一直都很糾結這個問題,其實只要把上面的公式進行擴充套件一下即可。,其中D是深度。每個卷積層有多個filter,每個filter和原始影象進行卷積後,就可以得到一個feature map,卷積後的feature map的深度和卷積層的filter的個數是相同的。下面的動圖展示了包含兩個filter的卷積層的計算。輸入map為7*7*3,步幅S=2, 在輸入元素的周圍補了一圈0,P=1,經過兩個3*3*3filter的卷積,得到了3*3*2的輸出map,輸出map的深度是和卷積核的個數是一致的。

Tensorflow程式碼實現

tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None)

函式作用:對輸入資料進行二維卷積操作

引數說明:

  • input:輸入,A 4-D tensor,shape為[batch_size, input_height, input_width, input_channels],資料型別必須為float32或者float64
  • filter: 卷積核,A 4-D tensor,shape為[filter_height, filter_width, input_channels, output_channels], 和input有相同的資料型別
  • strides: 步長,一個長度為4的一維列表,每一位對應input中每一位對應的移動步長,在使用中,由於input的第一維和第四維不參與卷積運算,所以strides一般為[1, X, X, 1]
  • padding: 填充方式,一個字串,取值‘SAME’或者‘VALID’. 當padding = ‘SAME’時,tensorflow會自動對原影象進行補零操作,從而使得輸入和輸出的影象大小一致;當padding = ‘VALID’時, 採用不填充的方式,會根據計算公式縮小原影象的大小,計算公式為(W-F+2P)/S+1, W是影象大小,F為卷積核大小,P是填充數量,S是步長。
  • use_cudnn_on_gpu=None : 可選布林值,預設為True

池化

池化層的主要目的是通過降取樣的方式,在不影響影象質量的情況下,壓縮圖片,減少引數。子取樣有兩種形式,一種是均值子取樣,即是對一個區域裡的元素求平均值;另一種就是最大值取樣,即是在一個區域中尋找最大值,如下圖所示。

# 平均池化
tf.nn.avg_pool(value, ksize, strides, padding, name=None)
# 最大化池化
tf.nn.max_pool(value, ksize, strides, padding, name=None)

函式作用:對輸入進行池化操作

引數說明:

  • value:池化輸入,A 4-D tensor,shape為[batch_size, input_height, input_width, input_channels],資料型別為float32,float64,qint8,quint8,qint32
  • ksize:池化視窗大小,A 4-D tensor,一般為[1, X, X, 1]
  • strides:步長,一般為[1, X, X, 1],同上述卷積運算的strides
  • padding:填充,一個字串取值‘SAME’或者‘VALID’,同上述卷積運算的padding

區域性響應歸一化LRN(Local Response Normalization)

區域性歸一的動機:在神經生物學中有一個概念叫做側抑制,指的是被刺激的神經元抑制相鄰神經元。歸一化(normalization)的目的是“抑制”,區域性響應歸一化就是借鑑側抑制的思想來實現區域性抑制,尤其當我們使用ReLU時這種側抑制很有效。

在AlexNet論文中,Hinton給出的具體的計算公式如下:

a_{x,y}^{i}表示第i個卷積核在位置(x,y)運用啟用函式ReLU後的輸出,n是同一個 位置上臨近的kernal map 的數目,N是kernal的總數,在論文中,設定k=2,n=5,\alpha =10^{-4}\beta =0.75

tf.nn.lrn(input, depth_radius, bias, alpha, beta, name=None)

官方API給出如下解釋:

The 4-D input tensor is treated as a 3-D array of 1-D vector(along the last dimension), and each vector is normalized independently. Within a given vector, each component is divided by the weighted, squared sum of inputs within depth_radius.

具體細節如下:

sqrt_sum[a, b, c, d] = sum(input[a, b, c, d-depth_radius : d + depth_radius + 1] ** 2)

output = input / (bias + alpha * sqrt_sum) ** beta

Batch Normalization

機器學習領域有個很重要的假設:IID獨立同分布假設,就是假設訓練資料和測試資料是滿足相同分佈的,這是通過訓練資料獲得的模型能夠在測試集上取得好效果的一個基本保障。而BatchNorm的作用就是在深度神經網路訓練過程中使得每一層的輸入保持相同分佈。

BN來自論文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》。深度學習包含多個隱含層的網路結構,在訓練過程中,因為各層引數不停在變化,所以每個隱含層都會面臨covariate shift的問題,也就是在訓練過程中,隱含層的輸入分佈會變來變去,BN的提出就是解決‘Internal Covariate Shift’的。BN通過一定的規範化手段,把每層神經網路任意神經元輸入值的分佈強行拉回到均值為0方差為1的標準正態分佈,使得啟用函式的輸入值落在非線性函式對輸入值敏感的區域,這樣輸入小的變化將會導致損失函式較大的變化,通俗點講就是梯度變大,所以收斂速度變快,大大加快訓練速度。

那麼如何對每個隱層神經元的啟用值做BN?BN操作位於權重加權求和之後,啟用函式之前,相當於在每個隱含層中加上了一層BN操作層。

# x輸入,[batch_size, height, width, channel], axes表示在哪個維度上求解,是個list
batch_mean, batch_var = tf.nn.moments(x, axes, name=None, keep_dims=False)
# offset和scale一般需要訓練,其中offset一般初始為0,scale一般初始為1。
# variance_epsilon 是一個很小的浮點數避免避免分母為0
tf.nn.batch_normalization(x, mean=batch_mean, variance=batch_var, offset, scale, variance_epsilon, name=None)

對某一隱含層進行BN處理,示例程式碼如下:

conv2_w = tf.Variable(w_initial) # 引數
hidden_layer2 = tf.matmul(hidden_layer1, conv2_w) # 加權求和
batch_mean2, batch_var2 = tf.nn.moments(hidden_layer2, [0])
offset2 = tf.Variable(tf.ones([100]))
scale2 = tf.Variable(tf.zeros([100]))
BN2 = tf.nn.batch_normalization(hidden_layer2, batch_mean2, batch_var2, offset2, scale2, epsilon) # BN處理
layer2_output = tf.nn.relu(BN2) # 啟用函式

卷積神經網路的架構,可以參考我的另一篇部落格,點這裡,介紹了最常用的一些CNN網路架構,在這裡就不在贅述。