深度學習經典網絡:Inception系列網絡(Inception v1 & Inception v2(BN))

Inception v1(GoogleNet): https://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43022.pdf
Inception v2(BN): https://arxiv.org/pdf/1502.03167.pdf
tensorflow 代碼:https://github.com/conan7882/GoogLeNet-Inception

一、Inception v1(GoogleNet)

Motivation:
  2014年之前標準的CNN結構通常將數層卷積層,最大池化層和一個以上的全連接層進行堆疊。而提高網路性能的方式就是增加網絡的深度和寬度,寬度即各層神經元的個數,對於卷積操作可以理解爲卷積核的個數,但這會帶來一些侷限:

  • 巨大的內存集計算資源的消耗
  • 網絡參數量的增加,容易造成過擬合
  • 梯度消失和爆炸

作者認爲解決上述兩個缺點的根本方法是將全連接甚至一般的卷積都轉化爲稀疏連接。 所以,現在的問題是有沒有一種方法,既能保持網絡結構的稀疏性,又能利用密集矩陣的高計算性能。Inception Net的主要目標就是找到最優的稀疏結構單元,最後Inception model通過4個分支中不同尺寸的1×1、3×3、5×5等小型卷積將相關性很高的節點連接在一起,就完成了其設計初衷,構建出了很高效的符合Hebbian原理的稀疏結構。

Inception的結構:
如圖1(a)爲最初的Inception 模塊, 對該模塊可以進行如下理解:

  • 1 . 採用不同大小的卷積核意味着不同大小的感受野,最後拼接意味着不同尺度特徵的融合;
  • 2 .之所以卷積核大小採用1、3和5,主要是爲了方便對齊。設定卷積步長stride=1之後,只要分別設定pad=0、1、2,那麼卷積之後便可以得到相同維度的特徵,然後這些特徵就可以直接拼接在一起了;
  • 3 . 文章說很多地方都表明pooling挺有效,所以Inception裏面也嵌入了。
  • 4 . 網絡越到後面,特徵越抽象,而且每個特徵所涉及的感受野也更大了,因此隨着層數的增加,3x3和5x5卷積的比例也要增加。但是,使用5x5的卷積核仍然會帶來巨大的計算量。 爲此,文章借鑑NIN2,採用1x1卷積核來進行降維。
    ee
    圖 1 Inception 模塊示意圖

  一層block就包含1x1卷積,3x3卷積,5x5卷積,3x3池化(使用這樣的尺寸不是必需的,可以根據需要進行調整)。這樣,網絡中每一層都能學習到“稀疏”(3x3、5x5)或“不稀疏”(1x1)的特徵,既增加了網絡的寬度,也增加了網絡對尺度的適應性;通過多個卷積核提取圖像的不同尺度的信息,最後進行融合,可以得到圖像更好的表徵。比如,假設我們提取貓臉在圖像中佔比不同的圖像的特徵,那麼我們就可以用不卷積核提取不同信息。信息分佈比較全局性的圖像採用大的卷積核,信息分佈比較局部性的圖像採用小卷積核。
  按照這樣的結構來增加網絡的深度,雖然可以提升性能,但是還面臨計算量大(參數多)的問題。爲改善這種現象,GooLeNet借鑑Network-in-Network的思想,使用1x1的卷積覈實現降維操作(也間接增加了網絡的深度),以此來減小網絡的參數量(這裏就不對兩種結構的參數量進行定量比較了)。1×1的卷積可以理解爲learned pooling in depth. 如圖1(b)所示。在3×3、5×5卷積前加入1×1卷積降維。而把1×1卷積放在3×3最大池化之後,相比較放在前面,也是爲了減少參數量。

作者指出了Inception的優點:

  • 顯著增加了每一步的單元數目,也就是網絡的寬度,計算複雜度不會不受限制,尺度較大的塊卷積之前先降維

  • 視覺信息在不同尺度上進行處理聚合,這樣下一步可以從不同尺度提取特徵

Inception v1 網絡結構:
對圖2做如下說明:
1 . 顯然GoogLeNet採用了模塊化的結構,方便增添和修改,其實網絡結構就是疊加Inception Module.
2 . 網絡最後採用了average pooling來代替全連接層,想法來自NIN,事實證明可以將TOP1 accuracy提高0.6%。但是,實際在最後還是加了一個全連接層, 是爲了做微調。
3 . 雖然移除了全連接,但是網絡中依然使用了Dropout ;
4 . 爲了避免梯度消失,網絡額外增加了2個輔助的softmax用於向前傳導梯度。此外,實際測試的時候,這兩個額外的softmax會被去掉。softmax分支除了避免梯度消失的作用,另一個是將中間某一層輸出用作分類,起到模型融合的作用。
注:給定深度相對較大的網絡,有效傳播梯度反向通過所有層的能力是一個問題。在這個任務上,更淺網絡的強大性能表明網絡中部層產生的特徵應該是非常有識別力的。通過將輔助分類器添加到這些中間層,可以期望較低階段分類器的判別力。這被認爲是在提供正則化的同時克服梯度消失問題。這些分類器採用較小卷積網絡的形式,放置在Inception (4a)和Inception (4d)模塊的輸出之上。在訓練期間,它們的損失以折扣權重(輔助分類器損失的權重是0.3)加到網絡的整個損失上。在推斷時,這些輔助網絡被丟棄。後面的控制實驗表明輔助網絡的影響相對較小(約0.5),只需要其中一個就能取得同樣的效果。
包括輔助分類器在內的附加網絡的具體結構如下:
1)一個濾波器大小5×5,步長爲3的平均池化層,導致(4a)階段的輸出爲4×4×512,(4d)的輸出爲4×4×528。
2)具有128個濾波器的1×1卷積,用於降維和修正線性激活。
3)一個全連接層,具有1024個單元和修正線性激活。
4)丟棄70%輸出的丟棄層。
5)使用帶有softmax損失的線性層作爲分類器(作爲主分類器預測同樣的1000類,但在推斷時移除)。

在這裏插入圖片描述

圖 2 Inception v1 網絡結構

在這裏插入圖片描述
圖 3 Inception v1具體網絡參數

二、Inception v2(BN-Inception)

google這邊對於inception v2是屬於哪篇論文有些不同觀點, 該系列博客是以下面第二種解釋爲準:

  • 在《Rethinking the Inception Architecture for Computer Vision》中認爲:基於inception v1進行結構的改進是inception v2;在inception v2上加上BN是inception v3;
  • 在《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning》中將《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》認爲是inception v2(即inception v1 上進行小改動再加上BN);《Rethinking the Inception Architecture for Computer Vision》認爲是inception v3

從google實現的Inception V1源碼可以看出V2的改進主要是以下兩點:

  • 使用了Batch Normalization. BN帶來的好處有: 對每一層的輸入做了類似標準化處理,能夠預防梯度消失和梯度爆炸,加快訓練的速度;減少了前面一層參數的變化對後面一層輸入值的影響,每一層獨立訓練,有輕微正則化效果

  • 用兩個3x3Convolution替代一個5x5Convolution。

另外一些細微的改變有:

  • Inception 3模塊的數量從原來啊的兩個變爲三個.
  • 在Inception模塊內部有的使用Max Pooling 有的使用Max Pooling.
  • 兩個Inception模塊羣之間沒有明顯的池化層,採用步長爲2 的卷積代替,例如3c 和4e 層concatenate之前。

在這裏插入圖片描述

圖 4 Inception v1(BN)網絡具體參數

在這裏插入圖片描述

圖 5 Inception v1(BN) 網絡結構

3 Inception v2(BN)代碼實現

採用tensorflow2.0, tf.keras編程實現。

"""
Inception v2 models for tensorflow 2.0, tf.keras.
# Reference
- [Batch Normalization: Accelerating Deep Network Training by
Reducing Internal Covariate Shift](https://arxiv.org/pdf/1502.03167.pdf))

"""

import os
import tensorflow as tf
import tensorflow.keras as keras
from tensorflow.keras import Sequential, layers

os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'


class BasicCon2D(keras.Model):
    """This is the basic con2d operation, i.e.conv+bn+relu"""
    def __init__(self, filter_nums, **kwargs):
        super(BasicCon2D, self).__init__()
        self.conv = layers.Conv2D(filter_nums, use_bias=False, **kwargs)
        self.bn = layers.BatchNormalization()
        self.relu = layers.Activation('relu')

    def call(self, inputs, training=None):
        out = self.conv(inputs)
        out = self.bn(out)
        out = self.relu(out)
        return out


class InceptionA(keras.Model):
    """This inception has no downsampling operation and stride=1"""
    def __init__(self, n1x1, n3x3_reduce, n3x3, n5x5_reduce, n5x5, pool_proj, pool_way='average'):
        super(InceptionA, self).__init__()
        # 1×1convolution branch
        self.branch1x1 = BasicCon2D(n1x1, kernel_size=(1, 1))
        self.branch3x3_1 = BasicCon2D(n3x3_reduce, kernel_size=(1, 1))
        self.branch3x3_2 = BasicCon2D(n3x3, kernel_size=(3, 3), padding='same')
        self.branch5x5_1 = BasicCon2D(n5x5_reduce, kernel_size=(1, 1))
        self.branch5x5_2 = BasicCon2D(n5x5, kernel_size=(3, 3), padding='same')
        self.branch5x5_3 = BasicCon2D(n5x5, kernel_size=(3, 3), padding='same')
        if pool_way == 'average':
            self.branch_pool_1 = layers.AveragePooling2D((3, 3), strides=1, padding='same')
            self.branch_pool_2 = BasicCon2D(pool_proj, kernel_size=(1, 1))
        elif pool_way == 'max':
            self.branch_pool_1 = layers.MaxPool2D((3, 3), strides=1, padding='same')
            self.branch_pool_2 = BasicCon2D(pool_proj, kernel_size=(1, 1))
        else:
            raise ValueError('The pool_way belongs to "average" and "max"')


    def call(self, inputs, training=None):
        # 1x1 convolution branch
        branch1x1 = self.branch1x1(inputs)
        # 3x3 convolution branch
        branch3x3 = self.branch3x3_1(inputs)
        branch3x3 = self.branch3x3_2(branch3x3)
        # 5x5 convolution branch
        branch5x5 = self.branch5x5_1(inputs)
        branch5x5 = self.branch5x5_2(branch5x5)
        branch5x5 = self.branch5x5_3(branch5x5)
        # pool convolution branch
        branch_pool = self.branch_pool_1(inputs)
        branch_pool = self.branch_pool_2(branch_pool)

        out = layers.concatenate([branch1x1, branch3x3, branch5x5, branch_pool], axis=-1)
        return out


class InceptionB(keras.Model):
    """This inception has downsampling operation and stride=2"""
    def __init__(self, n3x3_reduce, n3x3, n5x5_reduce, n5x5):
        super(InceptionB, self).__init__()
        self.branch3x3_1 = BasicCon2D(n3x3_reduce, kernel_size=(1, 1))
        self.branch3x3_2 = BasicCon2D(n3x3, kernel_size=(3, 3), strides=2, padding='same')
        self.branch5x5_1 = BasicCon2D(n5x5_reduce, kernel_size=(1, 1))
        self.branch5x5_2 = BasicCon2D(n5x5, kernel_size=(3, 3), padding='same')
        self.branch5x5_3 = BasicCon2D(n5x5, kernel_size=(3, 3), strides=2, padding='same')
        self.branch_pool = layers.MaxPool2D((3, 3), strides=2, padding='same')

    def call(self, inputs, training=None):
        # 3x3 convolution branch
        branch3x3 = self.branch3x3_1(inputs)
        branch3x3 = self.branch3x3_2(branch3x3)
        # 5x5 convolution branch
        branch5x5 = self.branch5x5_1(inputs)
        branch5x5 = self.branch5x5_2(branch5x5)
        branch5x5 = self.branch5x5_3(branch5x5)
        # pool branch
        branch_pool = self.branch_pool(inputs)

        out = layers.concatenate([branch3x3, branch5x5, branch_pool], axis=-1)
        return out


class Inception2(keras.Model):
    """Applying the InceptionV2 network"""
    def __init__(self, num_classes):
        super(Inception2, self).__init__()
        self.conv1 = BasicCon2D(64, kernel_size=(7, 7), strides=2, padding='same')
        self.max_pool1 = layers.MaxPool2D((3, 3), strides=2, padding='same')
        self.conv2_1 = BasicCon2D(64, kernel_size=(1, 1))
        self.conv2_2 = BasicCon2D(192, kernel_size=(3, 3), strides=1, padding='same')
        self.max_pool2 = layers.MaxPool2D((3, 3), strides=2, padding='same')
        # inception 3
        self.inception3a = InceptionA(64, 64, 64, 64, 96, 32)
        self.inception3b = InceptionA(64, 64, 96, 64, 96, 64)
        self.inception3c = InceptionB(128, 160, 64, 96)
        # inception 4
        self.inception4a = InceptionA(224, 64, 96, 96, 128, 128)
        self.inception4b = InceptionA(192, 96, 128, 96, 128, 128)
        self.inception4c = InceptionA(160, 128, 160, 128, 160, 96)
        self.inception4d = InceptionA(96, 128, 192, 160, 192, 96)
        self.inception4e = InceptionB(128, 192, 192, 256)
        # inception 5
        self.inception5a = InceptionA(352, 192, 320, 160, 224, 128)
        self.inception5b = InceptionA(352, 192, 320, 192, 224, 128, pool_way='max')
        # global average pooling
        self.avg_pool = layers.GlobalAveragePooling2D()
        self.fc = layers.Dense(num_classes)

    def call(self, inputs, training=None):
        out = self.conv1(inputs)
        out = self.max_pool1(out)
        out = self.conv2_1(out)
        out = self.conv2_2(out)
        out = self.max_pool2(out)
        out = self.inception3a(out)
        out = self.inception3b(out)
        out = self.inception3c(out)
        out = self.inception4a(out)
        out = self.inception4b(out)
        out = self.inception4c(out)
        out = self.inception4d(out)
        out = self.inception4e(out)
        out = self.inception5a(out)
        out = self.inception5b(out)
        out = self.avg_pool(out)
        out = self.fc(out)
        return out


if __name__ == '__main__':
    model = Inception2(10)
    model.build(input_shape=(None, 224, 224, 3))
    model.summary()
    print(model.predict(tf.ones((10, 224, 224, 3))).shape)

References

1、卷積神經網絡的網絡結構——GoogLeNet
2、Inception Network Overview

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