微架構模型:GoogleNet

在這篇文章中,我們將討論一種新的網絡模型GoogleNet,它和我前面所討論的模型有所不同,表現在:

  1. 移除了全連接層,而採用全局平均池化層(global average pooling)代替,大量減少參數數量,所以相對於AlexNet和VGGNe這種巨型模型,其需要訓練的參數少得多,可以節約大量內存。
  2. 採用了微架構,而到目前爲止,我們接觸到的模型都是序列(sequential)模型,所謂序列,就是前一層的輸出直接輸出到下一層。但GoogleNet卻採用了微架構,來自一個層的輸出可以分成多個不同的路徑並且稍後重新連接到一起。

GoogLeNet模型於2014年的一篇論文《Going Deeper With Convolutions》提出,其最大的貢獻在於Inception模塊(Inception有起初、開端的含義),這是一個適合卷積神經網絡的構建模塊,它選用多個過濾器大小的卷積,將模塊轉換爲多級特徵提取器。

Inception模塊及其變種

Inception模塊是一種微架構模塊,所謂微架構,就是由深度學習從業者設計的小型構建塊,它使得網絡能夠在增加網絡深度的前提下更快地學習,而且更高效。而這些微架構構建塊與諸如CONV、POOL等傳統類型的層堆疊在一起,可以形成宏架構(macro-architecture)。

Inception模塊背後的思想有兩層含義:

  1. 在設計卷積層時,我們可能很難確定過濾器的大小。設計爲5×5過濾器還是3x3過濾器,如果採用1×1過濾器會不會更好?如果我們反過來想,爲什麼不都用上,讓模型來決定呢? 在Inception模塊中,我們學習所有三個5×5、3×3和1×1過濾器(並行計算它們),將所得到的特徵映射沿着通道維度連接起來。GoogLeNet體系結構中的下一層(可能是另一個Inception模塊)接收這些連接的混合過濾器並執行相同的過程。總的來說,這個過程使GoogLeNet能夠通過較小的卷積學習局部特徵,較大卷積來學習抽象特徵。
  2. 通過學習多個過濾器大小,我們可以將模塊轉換爲多級特徵提取器。5×5過濾器具有更大的接收尺寸,可以學習更多抽象功能。根據定義,1×1過濾器學習更多局部特徵,而3×3過濾器在兩者之間保持平衡。

GoogleNet最初引入的Inception模塊如下圖所示:

image

注: 在每個CONV層之後都緊跟一個激活函數(ReLU)。爲節省空間,此激活函數並沒包含在上面的網絡圖中。

從圖中可以看到,輸入層之後有四個不同的路徑分支。Inception模塊中的第一個分支只是從輸入中學習一系列1×1局部特徵。

第二條路徑首先應用1×1卷積,不僅作爲學習局部特徵的一種形式,還可以減少維數。較大的卷積(即3×3和5×5)需要更多的計算。因此,如果我們可以通過應用1×1卷積來減少這些較大過濾器的輸入維數,就可以減少網絡所需的計算量。

第三個分支與第二個分支的邏輯相同,區別在於爲了學習5×5過濾器。我們再次通過1×1卷積降低維數,然後將輸出饋送到5×5過濾器。

Inception模塊的第四個分支以1×1的步幅執行3×3最大池化 - 該分支通常被稱爲池投影分支。

最後,Inception模塊的所有四個分支匯聚在一起,它們沿着通道維度連接在一起。在實現過程中要特別小心(通過零填充)以確保每個分支的輸出具有相同的卷大小,從而允許連接輸出。

Miniception

最初的Inception模塊是爲GoogLeNet設計的,在ImageNet數據集上訓練(其中每個輸入圖像假設爲224×224×3)並獲得最好的精度。對於較小的數據集(具有較小的圖像空間維度),我們可以簡化Inception模塊,只需要較少的網絡參數。比如下圖表示的Miniception:

image

  • :卷積模塊,負責執行卷積、批量正則化和激活。

  • :Miniception模塊執行兩組卷積,一組用於1×1濾波器,另一組用於3×3濾波器,然後連接結果。在3×3濾波器之前不執行降維,因爲我們將使用CIFAR-10數據集,輸入已經很小。

  • :下采樣模塊,它同時應用卷積和最大池化以降低維度,然後在過濾器維度上連接。

將這些模塊堆疊起來,可以組成稱之爲MiniGoogleNet的模型結構,如下圖所示:

image

實現MiniGoogleNet

有了上面的模型定義,接下來我們就可以使用Keras框架來實現之。但在編碼之前,我們先了解一下Keras中的兩種類型的模型。

  • 序列(Sequential)模型: 在我們之前代碼中用到的模型爲序列模型,它是最簡單的線性結構,從頭到尾順序連接,不分叉。其常見操作是 model.add 進行堆疊。比如:
model.add(Dense(32, activation='relu', input_dim=100))
model.add(Dropout(0.25))
  • 函數式API:它比序列模型複雜,可以同時/分階段輸入變量,分階段輸出想要的模型。其常見形式是 output = Layer(parameters)(input) ,輸入像函數參數一樣傳遞進去,比如:
inputs = Input(shape=(784,))
# 輸入inputs,輸出x
x = Dense(64, activation='relu')(inputs)
# 輸入x,輸出x
x = Dense(64, activation='relu')(x)

因爲MiniGoogleNet並不是那種一條路走到黑的模型,所以我們不能選擇序列模型,而應該選擇函數式API來構建,代碼如下:

class MiniGoogleNet:
  @staticmethod
  def conv_module(x, k, kx, ky, stride, channel_dim, padding="same"):
    # define a CONV => BN => RELU pattern
    x = Conv2D(k, (kx, ky), strides=stride, padding=padding)(x)
    x = BatchNormalization(axis=channel_dim)(x)
    x = Activation("relu")(x)

    return x


  @staticmethod
  def inception_module(x, num_k1x1, num_k3x3, channel_dim):
    # define two CONV module, then concatenate across the channel dimension
    conv_1x1 = MiniGoogleNet.conv_module(x, num_k1x1, 1, 1, (1, 1), channel_dim=channel_dim)
    conv_3x3 = MiniGoogleNet.conv_module(x, num_k3x3, 3, 3, (1, 1), channel_dim=channel_dim)
    x = concatenate([conv_1x1, conv_3x3], axis=channel_dim)

    return x


  @staticmethod
  def downsample_module(x, k, channel_dim):
    # define the CONV module and POOL, then concatenate across the channel dimension
    conv_3x3 = MiniGoogleNet.conv_module(x, k, 3, 3, (2, 2), channel_dim=channel_dim, padding="valid")
    pool = MaxPooling2D((3, 3), strides=(2, 2))(x)
    x = concatenate([conv_3x3, pool], axis=channel_dim)

    return x

  @staticmethod
  def build(width, height, depth, classes):
    input_shape = (width, height, depth)
    channel_dim = -1

    if K.image_data_format() == "channels_first":
      input_shape = (depth, width, height)
      channel_dim = 1

    inputs = Input(shape=input_shape)
    x = MiniGoogleNet.conv_module(inputs, 96, 3, 3, (1, 1), channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 32, 32, channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 32, 48, channel_dim=channel_dim)
    x = MiniGoogleNet.downsample_module(x, 80, channel_dim=channel_dim)

    x = MiniGoogleNet.inception_module(x, 112, 48, channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 96, 64, channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 80, 80, channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 48, 96, channel_dim=channel_dim)
    x = MiniGoogleNet.downsample_module(x, 96, channel_dim=channel_dim)

    x = MiniGoogleNet.inception_module(x, 176, 160, channel_dim=channel_dim)
    x = MiniGoogleNet.inception_module(x, 176, 160, channel_dim=channel_dim)
    x = AveragePooling2D((7, 7))(x)
    x = Dropout(0.5)(x)

    # softmax classifier
    x = Flatten()(x)
    x = Dense(classes)(x)
    x = Activation("softmax")(x)

    model = Model(inputs, x, name="googlenet")

    return model

接下來就是訓練和測試模型,這個在前面的文章中介紹過,其步驟都差不多,所以在這裏我也不再羅嗦,有興趣的同學可以參考我在github上的完整代碼。

寫下這篇文章,我完成了《Deep Learning for Computer Vision with Python》的學習,其實後面還有一章節是講殘差網絡(ResNet),但考慮到ResNet也是採用微架構,其實和GoogleNet差不多,就是模塊構建塊有些區別,所以就不打算寫了。

其實這套書還有第三部,稱爲ImageNet Bundle,裏面有更多大型項目的例子,考慮到我這邊的硬件條件有限,就先不去研究這些複雜的例子。在後面的時間裏,我將專注於移動終端上的機器學習,敬請關注。

以上實例均有完整的代碼,點擊閱讀原文,跳轉到我在github上建的示例代碼。 另外,我在閱讀《Deep Learning for Computer Vision with Python》這本書,在微信公衆號後臺回覆“計算機視覺”關鍵字,可以免費下載這本書的電子版。

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