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

卷積神經網路之VGG(2014)

VGG(2014)

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

前言

2014年是個綻放年,出了兩篇重要的論文:VGG、GoogLeNet。

來自牛津大學的VGG網路是第一個在每個卷積層使用更小的3×3卷積核對影象進行卷積,並把這些小的卷積核排列起來作為一個卷積序列。通俗點來講就是對原始影象進行3×3卷積,然後再進行3×3卷積,連續使用小的卷積核對影象進行多次卷積。

或許很多人看到這裡也很困惑為什麼使用那麼小的卷積核對影象進行卷積,並且還是使用連續的小卷積核?VGG一開始提出的時候剛好與LeNet的設計原則相違背,因為LeNet相信大的卷積核能夠捕獲影象當中相似的特徵(權值共享)。AlexNet在淺層網路開始的時候也是使用9×9、11×11卷積核,並且儘量在淺層網路的時候避免使用1×1的卷積核。但是VGG的神奇之處就是在於使用多個3×3卷積核可以模仿較大卷積核那樣對影象進行區域性感知。後來多個小的卷積核串聯這一思想被GoogleNet和ResNet等吸收。

VGG相信如果使用大的卷積核將會造成很大的時間浪費,減少的卷積核能夠減少引數,節省運算開銷。雖然訓練的時間變長了,但是總體來說預測的時間和引數都是減少的了。

概要

卷積網路(ConvNets)近來在大規模影象和視訊識別方面取得了巨大成功(Krizhevsky等,2012;Zeiler&Fergus,2013;Sermanet等,2014;Simonyan&Zisserman,2014)由於大的公開影象儲存庫,例如ImageNet,以及高效能運算系統的出現,例如GPU或大規模分散式叢集(Dean等,2012),使這成為可能。特別是,在深度視覺識別架構的進步中,ImageNet大型視覺識別挑戰(ILSVRC)(Russakovsky等,2014)發揮了重要作用,它已經成為幾代大規模影象分類系統的測試臺,從高維度淺層特徵編碼(Perronnin等,2010)(ILSVRC-2011的獲勝者)到深層ConvNets(Krizhevsky等,2012)(ILSVRC-2012的獲獎者)。

隨著ConvNets在計算機視覺領域越來越商品化,為了達到更好的準確性,已經進行了許多嘗試來改進Krizhevsky等人(2012)最初的架構。例如,ILSVRC-2013(Zeiler&Fergus,2013;Sermanet等,2014)表現最佳的提交使用了更小的感受視窗尺寸和更小的第一卷積層步長。另一條改進措施在整個影象和多個尺度上對網路進行密集地訓練和測試(Sermanet等,2014;Howard,2014)。

在本文中,我們解決了ConvNet架構設計的另一個重要方面——其深度。為此,我們修正了架構的其它引數,並通過新增更多的卷積層來穩定地增加網路的深度,這是可行的,因為在所有層中使用非常小的(3×3)卷積濾波器。

新意

我們的主要貢獻是使用非常小的(3×3)卷積濾波器架構對網路深度的增加進行了全面評估,這表明通過將深度推到16-19加權層可以實現對現有技術配置的顯著改進。

兩個3×3卷積層堆疊(沒有空間池化)有5×5的有效感受野;三個這樣的層具有7×7的有效感受野。

那麼我們獲得了什麼?例如通過使用三個3×3卷積層的堆疊來替換單個7×7層。

  • 首先,我們結合了三個非線性修正層,而不是單一的,這使得決策函式更具判別性。
  • 其次,我們減少引數的數量:假設三層3×3卷積堆疊的輸入和輸出有C個通道,堆疊卷積層的引數為3(32C2)=27C23*(3^2C^2)=27C^2個權重;同時,單個7×7卷積層將需要72C2=49C27^2C^2=49C^2個引數,即引數多81%。

這可以看作是對7×7卷積濾波器進行正則化(減少了引數),迫使它們通過3×3濾波器(在它們之間注入非線性)進行分解。

架構

ConvNet配置(以列顯示)。隨著更多的層被新增,配置的深度從左(A)增加到右(E)(新增的層以粗體顯示)。卷積層引數表示為“conv⟨感受野大小⟩-通道數⟩”。為了簡潔起見,不顯示ReLU啟用功能。

Table 1

我們報告了每個配置的引數數量。儘管深度很大,我們的網路中權重數量並不大於具有更大卷積層寬度和感受野的較淺網路中的權重數量(144M的權重在(Sermanet等人,2014)中)。

引數數量(百萬級別):

Table 2

訓練

  1. 預處理:

    • 在訓練期間,我們的ConvNet的輸入是固定大小的224×224 RGB影象。

      令S是等軸歸一化的訓練影象的最小邊,ConvNet輸入從S中裁剪(我們也將S稱為訓練尺度)。雖然裁剪尺寸固定為224×224,但原則上S可以是不小於224的任何值:

      • 對於S=224,裁剪影象將捕獲整個影象的統計資料,完全擴充套件訓練影象的最小邊;
      • 對於S»224,裁剪影象將對應於影象的一小部分,包含小物件或物件的一部分。在每一次隨機梯度下降迭代中都在歸一化後的圖片上隨機裁剪一個224×224的方塊作為訓練的輸入。

      此外,由於資料集中的影象大小是不一致的,所以需要通過縮放將其變為想要的尺寸S×S。對於尺寸,文中有兩種處理方式來設定訓練尺度S。

      • 第一種是修正對應單尺度訓練的S(注意,取樣裁剪影象中的影象內容仍然可以表示多尺度影象統計)。在我們的實驗中,我們評估了以兩個固定尺度訓練的模型. 256/384.當S=384時,訓練集太大導致訓練時間大大增加,為了減少訓練時間,加快訓練過程,作者直接使用S=256的訓練的結果來初始化S=384的引數,並設定較小的學習率10e-3。

      • 第二種是多尺度訓練,其中每個訓練影象通過從一定範圍[Smin,Smax](我們使用Smin=256和Smax=512)隨機取樣S來單獨進行歸一化。由於影象中的目標可能具有不同的大小,因此在訓練期間考慮到這一點是有益的。這也可以看作是通過尺度抖動進行訓練集增強,其中單個模型被訓練在一定尺度範圍內識別物件。

        為了速度的原因,我們通過對具有相同配置的單尺度模型的所有層進行微調,訓練了多尺度模型,並用固定的S=384進行預訓練

    • 我們唯一的預處理是從每個畫素中減去在訓練集上計算的RGB均值

  2. 優化手段

    • 使用具有動量的小批量梯度下降(基於反向傳播(LeCun等人,1989))優化多項式邏輯迴歸目標函式來進行訓練。批量大小設為256,動量為0.9。

    • 學習率初始設定為10210^{−2},然後當驗證集準確率停止改善時,減少10倍。學習率總共降低3次,學習在37萬次迭代後停止(74個epochs)。

      我們推測,儘管與(Krizhevsky等,2012)相比我們的網路引數更多,網路的深度更大,但網路需要更小的epoch就可以收斂,這是由於

      • 由更大的深度和更小的卷積濾波器尺寸引起的隱式正則化
      • 某些層的預初始化。

      值得注意的是,在提交論文之後,我們發現可以通過使用**Glorot&Bengio(2010)**的隨機初始化程式來初始化權重而不進行預訓練。

  3. 正則化

    • 訓練通過權重衰減(L2懲罰乘子設定為51045*10^{−4})進行正則化.
    • 前兩個全連線層執行Dropout正則化(丟棄率設定為0.5)

測試

預處理

在測試時,給出訓練的ConvNet和輸入影象,它按以下方式分類。

  • 首先,將其等軸地歸一化到預定義的最小影象邊,表示為Q(我們也將其稱為測試尺度)。

    我們注意到,Q不一定等於訓練尺度S.

    縮放後的尺寸為Q×Q大小的影象,Q與S基本無關,文中指出在測試過程中使用了多個Q進行測試,結果取平均。。

  • 我們還通過水平翻轉影象來增強測試集, 將原始影象和翻轉影象的softmax類後驗進行平均,以獲得影象的最終分數。

細節

  • 由於全卷積網路被應用在整個影象上,所以不需要在測試時對取樣多個裁剪影象(Krizhevsky等,2012),因為它需要網路重新計算每個裁剪影象,這樣效率較低。

    測試方法首先將網路轉化為全卷積網路,第一個全連線層轉為7×7的卷積層,後兩個全連線層轉化為1×1的卷積層。結果得到的是一個N×N×M的結果,稱其為類別分數圖,其中M等於類別個數,N的大小取決於輸入影象尺寸Q,計算每個類別的最終得分時,將N×N上的值求平均,此時得到1×1×M的結果,此結果即為最終類別分數,這種方法文中稱為密集評估

  • 同時,如Szegedy等人(2014)所做的那樣,使用大量的裁剪影象可以提高準確度,因為與全卷積網路相比,它使輸入影象的取樣更精細。

  • 此外,由於不同的卷積邊界條件,多裁剪影象評估是密集評估的補充:當將ConvNet應用於裁剪影象時,卷積特徵圖用零填充,而在密集評估的情況下,相同裁剪影象的填充自然會來自於影象的相鄰部分(由於卷積和空間池化),這大大增加了整個網路的感受野,因此捕獲了更多的上下文

  • 雖然我們認為在實踐中,多裁剪影象的計算時間增加並不足以證明準確性的潛在收益,但作為參考,我們還在每個尺度使用50個裁剪影象(5×5規則網格,2次翻轉)評估了我們的網路,在3個尺度上總共150個裁剪影象,與Szegedy等人(2014)在4個尺度上使用的144個裁剪影象。

    這種測試方法,和訓練過程類似,不用將網路轉化為全卷積網路,是從Q×Q大小的圖中隨機裁剪224×224的圖作為輸入,文中裁剪了50個影象,而每個影象之前縮放為三個尺寸,所以每個影象的測試影象數目變為150個,最終結果就是在150個結果中取平均。但是作者指出,該方法效率低下,並且不能證明比第一種方法效果好。

    除此之外,本文還採取了全卷機網路和隨機裁剪相結合的方法,即兩者結果取平均。其能大大增加網路的感受野,因此捕獲更多的上下文資訊,實驗中也發現這種方法表現最好。

評估

  • 單尺度評估(固定尺度測試)

    測試影象大小設定如下:對於固定S的Q=S,對於抖動S∈[Smin,Smax],Q=0.5(Smin+Smax)。

    Table 3

    • 結果可以反映出來, LRN對於模型改善並沒有太大效果.在較深的架構中就不再使用.

    • 值得注意的是,儘管深度相同,配置C(包含三個1×1卷積層)比在整個網路層中使用3×3卷積的配置D更差。這表明,雖然額外的非線性確實有幫助(C優於B),但也可以通過使用具有非平凡感受野(D比C好)的卷積濾波器來捕獲空間上下文

    • 我們還將網路B與具有5×5卷積層的淺層網路進行了比較,淺層網路可以通過用單個5×5卷積層替換B中每對3×3卷積層得到(其具有相同的感受野如第2.3節所述)。測量的淺層網路top-1錯誤率比網路B的top-1錯誤率(在中心裁剪影象上)高7%,這證實了具有小濾波器的深層網路優於具有較大濾波器的淺層網路

    • 訓練時的尺度抖動(S∈[256;512])得到了與固定最小邊(S=256或S=384)的影象訓練相比更好的結果,即使在測試時使用單尺度。這證實了通過尺度抖動進行的訓練集增強確實有助於捕獲多尺度影象統計.

  • 多尺度(多尺度測試)

    在單尺度上評估ConvNet模型後,我們現在評估測試時尺度抖動的影響。它包括在一張測試影象的幾個歸一化版本上執行模型(對應於不同的Q值),然後**對所得到的類別後驗進行平均**。

    • 考慮到訓練和測試尺度之間的巨大差異會導致效能下降,用固定S訓練的模型在三個測試影象尺度上進行了評估,接近於訓練一次:Q=S32,S,S+32Q=S−32,S,S+32
    • 同時,訓練時的尺度抖動允許網路在測試時應用於更廣的尺度範圍,所以用變數S[Smin;Smax]S∈[Smin;Smax]訓練的模型在更大的尺寸範圍Q={Smin,0.5(Smin+Smax),Smax}Q = \{S_{min}, 0.5(S_{min} + S_{max}), S_{max}\} 上進行評估。

    Table 4

    測試時的尺度抖動導致了更好的效能(與在單一尺度上相同模型的評估相比,如前表所示)。

    如前所述,最深的配置(D和E)執行最佳,並且訓練時的尺度抖動優於訓練時使用固定最小邊

  • 多剪裁影象評估

    下表中, 在所有的實驗中訓練尺度S從[256, 512]取樣,三個測試尺度Q考慮:{256, 384, 512}。

    Table 5

    在上表中,我們將稠密ConvNet評估與多裁剪影象評估進行比較。我們還通過平均其softmax輸出來評估兩種評估技術的互補性。可以看出,使用多裁剪影象表現比密集評估略好,而且這兩種方法確實是互補的,因為它們的組合優於其中的每一種。如上所述,我們假設這是由於卷積邊界條件的不同處理。

對比的結論

作者在對比各級網路時總結出了以下幾個觀點。

  1. LRN層作用不大。
  2. 越深的網路效果越好。
  3. 1*1的卷積也是很有效的,但是沒有3*3的卷積好,大一些的卷積核可以學習更大的空間特徵。

結論

在這項工作中,我們評估了非常深的卷積網路(最多19個權重層)用於大規模影象分類。已經證明,表示深度有利於分類精度,並且深度大大增加的傳統ConvNet架構(LeCun等,1989;Krizhevsky等,2012)可以實現ImageNet挑戰資料集上的最佳效能。

VGG給我們的啟示

VGGNet 探索的是神經網路的深度(depth)與其效能之間的關係

VGG通過反覆堆疊3×3的小型卷積核和2×2的最大池化層,VGG成功構建了16-19層的卷積神經網路。是當時在論文發表前最深的深度網路。實際上,VGG在探索深度對神經網路影響的同時,其實本身廣度也是很深的。那麼:

神經網路的深度和廣度對其本身的影響是什麼呢?

  • 卷積核的種類對應了網路的廣度,卷積層數對應了網路的深度。這兩者對網路的擬合都有影響。但是在現代深度學習中,大家普遍認為深度比廣度的影響更加高。
  • 寬度即卷積核的種類個數,在LeNet那篇文章裡我們說了,權值共享(每個神經元對應一塊區域性區域,如果區域性區域是10*10,那麼就有100的權重引數,但如果我們把每個神經元的權重引數設定為一樣,相當於每個神經元用的是同一個卷積核去卷積影象,最終兩層間的連線只有 100 個引數 !)可以大大減少我們的訓練引數,但是由於使用了同一個卷積核,最終特徵個數太少,效果也不會好,所以一般神經網路都會有多個卷積核,這裡說明寬度的增加在一開始對網路的效能提升是有效的。但是,隨著廣度的增加,對網路整體的效能其實是開始趨於飽和,並且有下降趨勢,因為過多的特徵(一個卷積核對應發現一種特徵)可能對帶來噪聲的影響。
  • 深度即卷積層的個數,對網路的效能是極其重要的,ResNet已經表明越深的深度網路效能也就越好。深度網路自然集成了低、中、高層特徵。多層特徵可以通過網路的堆疊的數量(深度)來豐富其表達。挑戰imagenet資料集的優秀網路都是採用較深的模型。網路的深度很重要,但是否能夠簡單的通過增加更多的網路層次學習更好的網路?這個問題的障礙就是臭名昭著的梯度消失(爆炸)問題,這從根本上阻礙了深度模型的收斂。
  • 增加更多的卷積核可以發現更多的特徵,但是特徵是需要進行組合的,只有知道了特徵之間的關係才能夠更好的表達圖片內容,而增加深度就是組合特徵的過程。

VGG結構全部都採用較小的卷積核(3×3,部分1×1)

如何選擇卷積核的大小?越大越好還是越小越好?

答案是小而深,單獨較小的卷積核也是不好的,只有堆疊很多小的卷積核,模型的效能才會提升。

  • 如上圖所示,CNN的卷積核對應一個感受野,這使得每一個神經元不需要對全域性影象做感受,每個神經元只感受區域性的影象區域,然後在更高層,將這些感受不同區域性的神經元綜合起來就可以得到全域性資訊。這樣做的一個好處就是可以減少大量訓練的引數。
  • VGG經常出現多個完全一樣的3×3的卷積核堆疊在一起的情況,這些多個小型卷積核堆疊的設計其實是非常有效的。兩個3×3的卷積層串聯相當於1個5×5的卷積層,即一個畫素會和周圍5×5的畫素產生關聯,可以說感受野是5×5。同時,3個串聯的3×3卷積層串聯的效果相當於一個7×7的卷積層。除此之外,3個串聯的3×3的卷積層擁有比一個7×7更少的引數量,只有後者的 (3×3×3) / (7×7) = 55%。最重要的是3個3×3的卷積層擁有比一個7×7的卷積層更多的非線性變換(前者可以使用三次ReLu啟用,而後者只有一次)。

程式碼

架構

其中slim.conv2d預設的啟用函式是relu.這個啟用函式作用於輸出的特徵圖.

net = slim.repeat(inputs, 2, slim.conv2d, 64, [3, 3], scope='conv1')
net = slim.max_pool2d(net, [2, 2], scope='pool1')
net = slim.repeat(net, 2, slim.conv2d, 128, [3, 3], scope='conv2')
net = slim.max_pool2d(net, [2, 2], scope='pool2')
net = slim.repeat(net, 3, slim.conv2d, 256, [3, 3], scope='conv3')
net = slim.max_pool2d(net, [2, 2], scope='pool3')
net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv4')
net = slim.max_pool2d(net, [2, 2], scope='pool4')
net = slim.repeat(net, 3, slim.conv2d, 512, [3, 3], scope='conv5')
net = slim.max_pool2d(net, [2, 2], scope='pool5')

# Use conv2d instead of fully_connected layers.
net = slim.conv2d(net, 4096, [7, 7],
                  padding=fc_conv_padding, 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,
                      scope='fc8')

預處理

這裡的關於隨機翻轉的預處理操作, slim 實現在訓練裡, 但是論文是說在測試裡

訓練

def preprocess_for_train(image,
                         output_height,
                         output_width,
                         resize_side_min=_RESIZE_SIDE_MIN,
                         resize_side_max=_RESIZE_SIDE_MAX):
  """Preprocesses the given image for training.

  Note that the actual resizing scale is sampled from
    [`resize_size_min`, `resize_size_max`].

  Args:
    image: A `Tensor` representing an image of arbitrary size.
    output_height: The height of the image after preprocessing.
    output_width: The width of the image after preprocessing.
    resize_side_min: The lower bound for the smallest side of the image for
      aspect-preserving resizing.
    resize_side_max: The upper bound for the smallest side of the image for
      aspect-preserving resizing.

  Returns:
    A preprocessed image.
  """
  resize_side = tf.random_uniform(
      [], minval=resize_side_min, maxval=resize_side_max+1, dtype=tf.int32)
  # 放縮
  image = _aspect_preserving_resize(image, resize_side)
  # 隨機裁剪
  image = _random_crop([image], output_height, output_width)[0]
  # 重新調整形狀
  image.set_shape([output_height, output_width, 3])
  image = tf.to_float(image)
  # 隨機左右翻轉
  image = tf.image.random_flip_left_right(image)
  # 減去均值
  return _mean_image_subtraction(image, [_R_MEAN, _G_MEAN, _B_MEAN])

測試

相比訓練階段,只是少了隨機翻轉.

def preprocess_for_eval(image, output_height, output_width, resize_side):
  """Preprocesses the given image for evaluation.

  Args:
    image: A `Tensor` representing an image of arbitrary size.
    output_height: The height of the image after preprocessing.
    output_width: The width of the image after preprocessing.
    resize_side: The smallest side of the image for aspect-preserving resizing.

  Returns:
    A preprocessed image.
  """
  image = _aspect_preserving_resize(image, resize_side)
  image = _central_crop([image], output_height, output_width)[0]
  image.set_shape([output_height, output_width, 3])
  image = tf.to_float(image)
  return _mean_image_subtraction(image, [_R_MEAN, _G_MEAN, _B_MEAN])