微架構模型:GoogleNet

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

  1. 移除了全鏈接層,而採用全局平均池化層(global average pooling)代替,大量減小參數數量,因此相對於AlexNet和VGGNe這種巨型模型,其須要訓練的參數少得多,能夠節約大量內存。
  2. 採用了微架構,而到目前爲止,咱們接觸到的模型都是序列(sequential)模型,所謂序列,就是前一層的輸出直接輸出到下一層。但GoogleNet卻採用了微架構,來自一個層的輸出能夠分紅多個不一樣的路徑而且稍後從新鏈接到一塊兒。

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

Inception模塊及其變種

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

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

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

GoogleNet最初引入的Inception模塊以下圖所示:網絡

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

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

第二條路徑首先應用1×1卷積,不只做爲學習局部特徵的一種形式,還能夠減小維數。較大的卷積(即3×3和5×5)須要更多的計算。所以,若是咱們能夠經過應用1×1卷積來減小這些較大過濾器的輸入維數,就能夠減小網絡所需的計算量。機器學習

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

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

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

Miniception

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

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

  • :Miniception模塊執行兩組卷積,一組用於1×1濾波器,另外一組用於3×3濾波器,而後鏈接結果。在3×3濾波器以前不執行降維,由於咱們將使用CIFAR-10數據集,輸入已經很小。

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

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

實現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》這本書,在微信公衆號後臺回覆「計算機視覺」關鍵字,能夠免費下載這本書的電子版。

image
相關文章
相關標籤/搜索