1. 程式人生 > >卷積神經網路之AlexNet(2012)

卷積神經網路之AlexNet(2012)

AlexNet(2012)

文章書寫匆忙,有些使用了網上其他朋友的文字以及圖片,但是沒有及時複製對應的連結,在此深表歉意,以及深深的感謝。 如有朋友看到了對應的出處,或者作者發現,可以留言,小弟馬上修改,新增引用。

2012年,Alex Krizhevsky發表了AlexNet,相對比LeNet它的網路層次更加深,從LeNet的5層到AlexNet的7層,更重要的是AlexNet還贏得了2012年的ImageNet競賽的第一。AlexNet不僅比LeNet的神經網路層數更多更深,並且可以學習更復雜的影象高維特徵。

小結

  • 使用ReLU函式作為啟用函式,降低了Sigmoid類函式的計算量
  • 利用dropout技術在訓練期間選擇性地剪掉某些神經元,避免模型過度擬合
  • 引入max-pooling技術
  • 利用雙GPU NVIDIA GTX 580顯著減少訓練時間

img

論文概要

我們訓練了一個最大的卷積神經網路來標記ILSVRC-2010 和 ILSVRC-2012比賽的資料集,並且實現了到目前為止在這些資料集上的最好結果。

在ImageNet上,通常報告兩類錯誤率:top-1和top-5,top5錯誤率表示測試圖片的標籤不在模型所認為的五種標籤之內。

我們寫了一個實現2D卷積的高度優化的GPU和其他的一些公開的訓練卷積神經網路的固有操作。

我們的網路包含大量新的和不尋常特點,這些特點提高了網路的效率並且減少了訓練時間,詳細介紹在第三部分。

我們的網路規模解決了過擬合這個重要問題,即使有1200萬被標記的訓練圖片,我們使用了大量有效的技巧來防止過擬合,這將在第四部分詳細介紹。

我們最終的網路包含5個卷積層和三個全連線層,而且這個深度似乎是非常重要的:我們發現移除任何一個卷積層(每層包含的引數不足整個模型的1%)都會導致非常差的效果。

最後,網路的大小主要由當前GPU的可用記憶體數量和我們所能忍受的訓練時間所限制。我們的網路在兩塊3G的GTX 580GPU上訓練了五六天的時間。所有的實驗表明,我們的結果還能通過更快的GPU和更大的可用資料集來進一步提高。

預處理

ImageNet包含的圖片解析度是變化的,然而我們的系統需要的輸入維數是一個常量。因此,我們取樣這些圖片一個固定的畫素值256X256。給定一張矩形的圖片,我們首先重置這張圖片的短邊長度為256,然後從得到的圖片中裁剪出中心的256X256

。除了從每一個畫素中減去平均值外,我們沒有做任何其他的操作。

原文是subtracting the mean activity over the training set from each pixel,通過谷歌,覺得比較靠譜的解釋是將訓練集每個影象對應畫素的R、G、B三個值分別求平均數,然後每個影象的每個畫素的R值減去R平均,G值減去G平均,B值減去B平均。如有知友知道正確解釋請在評論處指出,非常感謝。

所以,我們在畫素的原始RGB值(裁剪出的中心部分)上訓練我們的網路。

新奇點

啟用函式ReLU

一般的方法是將神經元的輸出作為函式f(x)=tanh(x)f(x)=tanh(x)f(x)=(1+ex)1f(x)=(1+e^{-x})^{-1}的輸入x 。

依據梯度下降的時間,這些飽和非線性函式是比不飽和非線性函式f(x)=max(0,x)f(x)=max(0,x)更慢的。根據Nair和Hinton,我們參考非線性的神經元Rectified Linear Units (ReLUs).

用RELUs訓練的深層卷積神經網路比用tanh等價的神經網路快很多倍

我們不是第一個考慮在CNNs中替換傳統神經模型的。

例如,Jarrett et al. [11]宣稱非線性函式f(x)=tanh(x)f(x)=|tanh(x)|在Caltech-101資料集上對比度歸一化後區域性平均池化的效果是非常好的。

然而,在訓練Caltech-101資料集首要的問題是防止過擬合,所以他們所謂的“良好效果”與我們報告的通過使用Relus來獲得加速擬合訓練集能力的結果是不一樣的。更快的學習對大資料集上的大模型有非常重大的影響。

多GPU處理

採用的並行機制, 基本上每塊GPU設定了一半的核函式(神經元),一個額外的小技巧:GPU 的交流僅僅在某些層。意思是說,例如,第三層神經元的輸入來自第二層的所有神經元。但是,第四層的神經元僅僅來自同一塊GPU上第三層的神經元。選擇這種連線方式對於交叉驗證是一個問題,但是這允許我們精確地調整連線的數量直到計算數值是一個可以接受的值。

最後我們搭建的架構有一些類似Ciresan等人[5]提出的“柱狀”CNN,不過我們的CNN網路的columns之間是非獨立的。這個機制分別減小了我們的top1錯誤率1.7% 和 top5錯誤率1.2%,和每個卷積層許多神經元在同一塊GPU上訓練像比較起來,兩塊GPU網路比一塊GPU花費更少的時間。

(單GPU模型和雙GPU模型的神經元數量其實差不多,因為神經網路大部分的引數集中在第一個全連線層,其接收的是最後一個卷積層的輸出結果。所以為了使兩種模型具有大致相同數量的引數,我們沒有將最後一個卷積層的規模減半,其後的全連線層也不用。這樣的處理導致兩者的分類效果對比其實是有利於單GPU模型的,因為它的神經元數量比雙GPU模型的“一半”多一些。)

譯者注:columns的解釋我查閱了一篇名為Multi-column Deep Neural Networks for Image Classification的論文,也是Ciresan寫的,裡面有提到一個column就是一個DNNmodel,在此文中我推測是指單個GPU裡的神經網路。而非獨立就是指兩個GPU上的網路之間是有連線層的。

在文中引用的“柱狀”CNN from reference[5] High-Performance Neural Networksfor Visual Object Classification裡沒有直接提到independent column,但估計是指其GPU的implementation是相互獨立的。另外一個推測依據是後文對two-GPU和one-GPU的描述,以及文中提到這是新穎的特徵。

區域性響應歸一化

核函式的順序在開始訓練之前都是任意的而且是確定的。受真實神經元的啟發,響應歸一化的順序實現了單側抑制(lateral inhibition)的形式,為使用不同核函式計算的神經元輸出創造了競爭

lateral inhibition:相近的神經元彼此之間發生的抑制作用,即在某個神經元受指刺激而產生興奮時,再刺激相近的神經元,則後者所發生的興奮對前者產生的抑制作用。

常量knαβk,n,\alpha,\beta,是超引數,它的值使用一個驗證集來確定,我們使k=2,n=5,α=104,β=0.75k=2,n=5,\alpha=10^{-4},\beta=0.75.

我們在一些層應用ReLu非線性變換之後,採用這個歸一化

Normalization是CNN裡一個很重要的層,尤其是運用ReLUs的CNN,因為其沒有boundary。而LRN的一個優點就是文中提到的側抑制。我找到一篇對LRN的解釋比較易懂的文獻。What Is Local Response Normalization In Convolutional Neural Networks。大致意思就是,真實的神經元利用側抑制這一性質可以強化區域性對比度從而強化識別能力。

這個機制和區域性常量歸一化有一些相似。但是我們的更準確的說是“亮度歸一化”,因為我們沒有減去平均值。

1539853083280

公式

1537589022827

公式解釋

這個公式中的a表示卷積層(包括卷積操作和池化操作)後的輸出結果,這個輸出結果的結構是一個四維陣列[batch,height,width,channel]

這裡可以簡單解釋一下,batch就是批次數(每一批為一張圖片),height就是圖片高度,width就是圖片寬度.

channel就是通道數,可以理解成一批圖片中的某一個圖片經過卷積操作後輸出的神經元個數(或是理解成處理後的圖片深度/卷積層的深度/切片數)

a(x,y)ia^{i}_{(x,y)}表示在這個輸出結構(輸出的feature map)中的一個位置[a,b,c,d],可以理解成在某一張圖中的某一個通道下的某個高度和某個寬度位置的點,即第a張圖的第d個通道下的高度為b寬度為c的點。

論文公式中的N表示通道數(channel)

a,n/2,k,α,βa,n/2,k,α,β分別表示函式中的input, depth_radius, bias, alpha, beta,其中n/2,k,α,βn/2,k,α,β都是自定義的,特別注意一下疊加的方向是沿著通道方向的,即每個點值的平方和是沿著feature map的中對應第a批資料的結果的三個維度中的channel方向的,也就是一個點同channel方向的前面n/2個通道(最小為第0個通道)和後n/2個通道(最大為第d-1個通道)的點的平方和(共n+1個點)

這個公式作用的結果就是導致: 若是當前通道的值偏大, 那麼就會相對的減弱相鄰通道的值的大小. 大值會抑制相鄰通道的結果.

1537589241131

實驗程式碼

import tensorflow as tf
import numpy as np
x = np.array([i for i in range(1,33)]).reshape([2,2,2,4])
y = tf.nn.lrn(input=x, depth_radius=2, bias=0, alpha=1, beta=1)

with tf.Session() as sess:
    print(x)
    print('#############')
    print(y.eval())

1537589316536

結果解釋

這裡要注意一下,如果把這個矩陣變成圖片的格式是這樣的 :

1537589369267 然後按照上面的敘述我們可以舉個例子。比如26對應的輸出結果0.00923952,計算如下

26/(0+1(252+262+272+282))126/(0+1*(25^2+26^2+27^2+28^2))^1

重疊匯聚

在CNN中池化層彙總了同一個核函式下相鄰神經元的輸出。傳統的,相鄰池化單元並不重疊。為了更精確,一個池化層可以被認為是由相鄰s個畫素的池化網格所組成,每次彙總會匯聚池化單元中心畫素的鄰近zXz個單元。

  • 如果我們假設s=z,我們獲得CNN中傳統的區域性池化。
  • 如果設s<z,我們獲得重疊池化。

這裡的s就是匯聚操作的步長

這是我們的網路裡使用的引數,s=2, z=3。這個機制減小了top1錯誤率0.4%,top5錯誤率0.3%,和不重疊機制s=2,z=2比較起來,它減小了等效面積的輸出。我們觀察並發現,在訓練有重疊池化的模型時, 不易過擬合。

分組卷積

群卷積最早出現於AlexNet中。是為了解決視訊記憶體不夠的問題,將網路部署在兩張GTX 580顯示卡上訓練,Alex認為group conv的方式能夠增加 filter之間的對角相關性,而且能夠減少訓練引數,不容易過擬合,這類似於正則的效果。

我們假設上一層的輸出feature map有N個,即通道數channel=N,也就是說上一層有N個卷積核。再假設群卷積的群數目M。那麼該群卷積層的操作就是,先將channel分成M份。每一個group對應N/M個channel,與之獨立連線。然後各個group卷積完成後將輸出疊在一起(concatenate),作為這一層的輸出channel。

整體架構

1537580096580

1537580216315

  • 第2,4,5卷積層的核函式僅僅和GPU上前一層的那些對映結果相連線, 第3層卷積層和第2層所有的對映結果相連線。

  • 全連線層的神經元和前一層所有的神經元相連。

  • 響應歸一化層連線在第1,2卷積層後面。

  • 最大池化層,如第3,4節描述的那樣,連線在響應歸一化層和第5卷基層後面。

  • ReLu非線性函式應用在每一個卷積層和全連線層後面。

  • 第1個卷積層用96個11X11X3的濾波器對224X224X3的影象以步長為4做濾波。

  • 第2層卷積層以第1層卷積層(LRN/池化之後)的結果為輸入,用256個5X5X48的濾波器做濾波。

  • 第3,4,5卷積層互相連線沒有任何池化/歸一的干擾。

  • 第3層卷積層有384個3X3X256 的核函式連線在第二層卷積層LRN/池化之後。

  • 第4層卷積層有384個3X3X192核函式連線

  • 第5層有256個3x3X192的核函式連線

  • 全連線層各有4096個神經元。

  • 最後一個全連線層的輸出結果提供給1000-way softmax,並得出1000個分類標籤的概率分佈。

    上述原文是average across training cases of the log-probability of the correct label under the prediction distribution 用公式表示為argmax_{w}\left{ \frac{1}{N} \sum_{}^{}{}-log\left( p(f(x,w) = y(x) ) \right)\right}

防止過擬合的手段

Data Augmentation(資料擴充)

最簡單也最常見的減少過擬合的方法就是通過保留標籤轉換人為地擴大資料集。

我們運用兩種資料增量方式,計算量都很小,所以轉換得到的新影象不用存在硬碟中。我們的轉換操作是在CPU上用python實現的,而GPU專門用於訓練模型。所以實際訓練中,資料增量操作對我們的CNN訓練的總計算量沒有影響。

  1. 第一種資料增量方式是影象變換(隨機剪裁)和水平翻轉。具體操作是從原本大小為256\times 256的圖象中提取所有大小為224\times 224的子影象(以及他們的水平翻轉影象),然後將這些子圖象作為我們CNN的輸入影象。(這解釋了為什麼圖二中我們模型的輸入層的大小是224 \times 224 \times 3)。經過如此操作,我們的訓練資料集變為了原來的2048倍( (256-224)^2\times2 )。雖然擴大後的資料之間的相關性非常大,但如果不這樣操作,我們的網路會出現嚴重的過擬合現象,可能會迫使我們使用規模更小的網路。在測試的時候,模型對每個輸入影象提取五個224\times 224子影象(四個角落和中心)以及他們分別的水平翻轉影象(總共10個),通過softmax層進行預測,並將10個預測值平均。

  2. 第二種方式是調整訓練影象的RGB各顏色通道強度(光照變換)。具體操作是,對訓練資料集所有影象的每個畫素RGB值分別進行主成分分析(PCA)。然後將原本的影象加上(主成分特徵向量)與(特徵值)和(一個隨機量的乘積)。也就是對於某影象的每一個畫素I_{xy} = \left[ I_{xy} ^{R},I_{xy} ^{G},I_{xy} ^{B}\right]加上以下算式的結果:

    [ \left[\textbf{p}_{1},\textbf{p}_{2},\textbf{p}_{3}\right]\left[\alpha _{1}\lambda _{1},\alpha _{2}\lambda _{2},\alpha _{3}\lambda _{3}\right]^{T} ]

    其中\textbf{p}_{i}\lambda_{i}是影象RGB值計算的3\times 3協方差矩陣的第i特徵向量特徵值,而\alpha_{i}就是前面提到的隨機量,服從均值為0,標準差為0.1的正態分佈。隨機產生的一組\alpha_{i}將用於某張圖的所有畫素,直到該圖再次被訓練時才會重新產生新的。

    這一調整是為了突出自然影象的一個重要性質,就是對物體影象的識別不應該受到其表面色彩的強度和顏色的影響。

    通過該操作,我們CNN的Top1錯誤率降低了1個百分點。

隨機失活

結合多個不同模型的預測結果可以降低測試錯誤率,但對於本身就需要數天時間訓練的大型神經網路而言,這是很奢侈的。然而,還是有很高效的方法能夠結合模型的預測結果,而且只耗費大約兩倍的訓練時間。

因此,每一次訓練一個影象時,神經網路就會隨機生成一個新的架構,但這些架構中使用的權重是一樣的。通過隨機失活減少了神經元之間複雜的互相適應性(co-adaptation),因為通過隨機失活,神經元無法過分依賴於某個有輸出結果的前一神經元(譯者注:因為沒有輸出結果的神經元可能是因為被隨機“失活”了,而不是因為其對輸入特徵解釋能力不佳)。在隨機神經元組的配合下,這個神經元也因此被迫去學習更加魯棒有用的特徵。在測試時,我們使用所有的神經元,將他們的輸出結果乘以0.5,這其實是由極多的經過隨機失活的神經網路產生的平均分類結果的一個合理近似值。

我們在圖中的前兩個全連線層運用隨機失活。否則,神經網路訓練就會出現很嚴重的過擬合。但隨機失活幾乎使得模型收斂所需要的迴圈翻倍

訓練

細節

我們用隨機梯度下降來訓練模型,每一個批量有128個樣本,動量為0.9,權值衰減為0.0005。

我們發現小權值衰減對模型的訓練是很重要的。也就是說,權值衰減在模型中不單單起到正則化作用;它還協助降低模型的訓練錯誤率

權重的更新方法如下: \begin{equation} v_{i+1} := 0.9\cdot v_{i}-0.0005\cdot\epsilon \cdot w_{i}-\epsilon\cdot\left< \frac{\partial L}{\partial w}|_{w_{i}} \right>_{D_{i}} \end{equation} \begin{equation} w_{i+1} := w_i + v_{i+1} \end{equation}

i是迴圈序數,v是動量引數,\epsilon是學習率,\left< \frac{\partial L}{\partial w}|_{w_{i}} \right>_{D_{i}}是第 i個批量樣本D_i(128個)上所有目標函式在w_i處對權重的偏導數的均值

我們將每一個層級的權重初始化為均值0,標準差0.01的正態隨機量。第二、四核五卷積層以及全連線層的偏差係數(bias)設定為1。這樣可以在訓練初期給ReLU單元提供正的輸入值,從而加快訓練速度。其他層級的偏差係數初始設為0。

所有的層級我們都使用相同的學習率,具體數值是我們在訓練過程中不斷調整得出的。主要調整方法是每當模型在當前的學習率下驗證錯誤率不再降低時,我們就把學習率除以10。初始學習率是0.01,在完成訓練過程中總共減少了三次。

結果

另一個探索神經網路視覺識別能力的方法是研究影象在最後一個層級,即維度為4096的隱含層上產生的特徵啟用狀態(feature activation)(譯者注:其實就是通過最後一個隱含層的輸出結果)。

如果兩個影象的特徵啟用狀態向量之間的歐式距離比較小,那麼就代表神經網路內部較高層次認為這兩張圖是類似的。(也就是送到最終輸出的分類層上的輸入近似的時候,也就更容易劃分到一類)

也可以觀察到,在畫素層面上,返回的幾張訓練影象其實與第一列的測試影象的L2距離不是特別接近。

計算4096維實向量之間的歐式距離是很低效的,但可以通過訓練一個自動編碼器將向量壓縮成較短的二進位制碼,從而提高效率。這應該產生一個相比把自編碼器直接應用到原始的畫素上更好的影象檢索方法.因為直接計算畫素而不使用其標籤會使得計算偏向於在影象邊緣尋找模式的相似性,不管它們實際圖片內容上是否相似。

程式碼

def alexnet_v2(inputs,
               num_classes=1000,
               is_training=True,
               dropout_keep_prob=0.5,
               spatial_squeeze=True,
               scope='alexnet_v2',
               global_pool=False):
  """AlexNet version 2.

  Described in: http://arxiv.org/pdf/1404.5997v2.pdf
  Parameters from:
  github.com/akrizhevsky/cuda-convnet2/blob/master/layers/
  layers-imagenet-1gpu.cfg

  Note: All the fully_connected layers have been transformed to conv2d layers.
        To use in classification mode, resize input to 224x224 or set
        global_pool=True. To use in fully convolutional mode, set
        spatial_squeeze to false.
        The LRN layers have been removed and change the initializers from
        random_normal_initializer to xavier_initializer.

  Args:
    inputs: a tensor of size [batch_size, height, width, channels].
    num_classes: the number of predicted classes. If 0 or None, the logits layer
    is omitted and the input features to the logits layer are returned instead.
    is_training: whether or not the model is being trained.
    dropout_keep_prob: the probability that activations are kept in the dropout
      layers during training.
    spatial_squeeze: whether or not should squeeze the spatial dimensions of the
      logits. Useful to remove unnecessary dimensions for classification.
    scope: Optional scope for the variables.
    global_pool: Optional boolean flag. If True, the input to the classification
      layer is avgpooled to size 1x1, for any input size. (This is not part
      of the original AlexNet.)

  Returns:
    net: the output of the logits layer (if num_classes is a non-zero integer),
      or the non-dropped-out input to the logits layer (if num_classes is 0
      or None).
    end_points: a dict of tensors with intermediate activations.
  """
  with tf.variable_scope(scope, 'alexnet_v2', [inputs]) as sc:
    end_points_collection = sc.original_name_scope + '_end_points'
    # Collect outputs for conv2d, fully_connected and max_pool2d.
    with slim.arg_scope([slim.conv2d, slim.fully_connected, slim.max_pool2d],
                        outputs_collections=[end_points_collection]):
      net = slim.conv2d(inputs, 64, [11, 11], 4, padding='VALID',
                        scope='conv1')
      net = slim.max_pool2d(net, [3, 3], 2, scope='pool1')
      net = slim.conv2d(net, 192, [5, 5], scope='conv2')
      net = slim.max_pool2d(net, [3, 3], 2, scope='pool2')
      net = slim.conv2d(net, 384, [3, 3], scope='conv3')
      net = slim.conv2d(net, 384, [3, 3], scope='conv4')
      net = slim.conv2d(net, 256, [3, 3], scope='conv5')
      net = slim.max_pool2d(net, [3, 3], 2, scope='pool5')

      # Use conv2d instead of fully_connected layers.
      with slim.arg_scope([slim.conv2d],
                          weights_initializer=trunc_normal(0.005),
                          biases_initializer=tf.constant_initializer(0.1)):
        net = slim.conv2d(net, 4096, [5, 5], padding='VALID',
                          scope='fc6')
        net = slim.dropout(net, dropout_keep_prob, is_training=is_training,
                           scope='dropout6')
        net = slim.conv2d(net, 4096, [1, 1], scope='fc7')
        # Convert end_points_collection into a end_point dict.
        end_points = slim.utils.convert_collection_to_dict(
            end_points_collection)
        if global_pool:
          net = tf.reduce_mean(net, [1, 2], keep_dims=True, name='global_pool')
          end_points['global_pool'] = net
        if num_classes:
          net = slim.dropout(net, dropout_keep_prob, is_training=is_training,
                             scope='dropout7')
          net = slim.conv2d(net, num_classes, [1, 1],
                            activation_fn=None,
                            normalizer_fn=None,
                            biases_initializer=tf.zeros_initializer(),
                            scope='fc8')
          if spatial_squeeze:
            net = tf.squeeze(net, [1, 2], name='fc8/squeezed')
          end_points[sc.name + '/fc8'] = net
      return net, end_points
alexnet_v2.default_image_size = 224