深度卷積神經網絡演化歷史及結構改進脈絡總結

Table of Contents

CNN基本部件介紹

1. 局部感受野

2. 池化

3. 激活函數

4. 全連接層

深度學習模型火的原因?

網絡模型命名規則

最古老的的CNN

LeNet5

ILSVRC競賽歷年的佼佼者(AlexNet、VGG、GoogLeNet、ResNet)

AlexNet(2012)

VGG(2014)

Network in network(2014)

貢獻

MAP作用/好處

Resnet殘差網絡(2016)

ResNeXt(2017)

ResNet和Inception結合體

一般來說增加網絡表達能力的途徑有三種

谷歌系列 :Inception v1到v4(2015年-2017年)

Inception v1模型(2014)

Inception v2模型(BN-Inception)(2015)

與Inception V1區別

深度網絡爲什麼難訓練?

Inception v3模型(2016)

Inception V3核心思想

Inception V3的目的

Inception V3網絡結構最大的變化

Inception V4(2017)

Inception-ResNet系列(V1和V2)

Inception-Resnet-v1

Inception-Resnet-v2

ResNeXt(2017)

Xception(2017)

基本思想

設計目的

基本步驟

Xception與Inception-v3在結構上有什麼差別?

MobileNet V1(2017)

V1創新點總括

V1創新點詳解

能夠減少參數數量和計算量的原理

標準卷積和深度可分離卷積的區別

MobileNet的弊端,爲以後ShuffleNet提出奠定基礎

TensorFlow 中的代碼實現

Caffe 中的代碼實現

Densenet(2017)

創新點

EffNet(2018)

EffNet是對MobileNet-v1的改進,主要思想

mobilenet-v2(2018)

相比於Mobilenet-V1的三點不同總括:

Mobilenet-V2創新點詳解

網絡優化

MobileNetV2和 MobileNetV1 的區別

相同點

不同點:Linear Bottleneck

對比 MobileNet V2與ResNet 的微結構

Mobilenet-v3(2019)

SqueezeNet(2017ICLR)

命名

創新點

ShuffleNet V1(2018)

ShuffleNet V2(2018ECCV)

SENet(2018)

SKNet(2019CVPR)

博客參考

參考文獻


CNN基本部件介紹

1. 局部感受野

在圖像中局部像素之間的聯繫較爲緊密,而距離較遠的像素聯繫相對較弱。因此,其實每個神經元沒必要對圖像全局進行感知,只需要感知局部信息,然後在更高層局部信息綜合起來即可得到全局信息。卷積操作即是局部感受野的實現,並且卷積操作因爲能夠權值共享,所以也減少了參數量。

2. 池化

池化是將輸入圖像進行縮小,減少像素信息,只保留重要信息,主要是爲了減少計算量。主要包括最大池化和均值池化。

3. 激活函數

激活函數的用是用來加入非線性。常見的激活函數有sigmod, tanh, relu,前兩者常用在全連接層,relu常見於卷積層

4. 全連接層

全連接層在整個卷積神經網絡中起分類器的作用。在全連接層之前需要將之前的輸出展平

深度學習模型火的原因?

  1. 大量數據,Deep Learning領域應該感謝李飛飛團隊搞出來如此大的標註數據集合ImageNet;
  2. GPU,這種高度並行的計算神器確實助了洪荒之力,沒有神器在手,Alex估計不敢搞太複雜的模型;
  3. 算法的改進,包括網絡變深、數據增強、ReLU、Dropout等。

網絡模型命名規則

1.人名。LeNet以其作者名字LeCun命名,這種命名方式類似的還有AlexNet

2.機構名。GoogLeNet、VGG。

3.核心算法命名。ResNet。

最古老的的CNN

LeNet之前其實還有一個更古老的CNN模型。1985年,Rumelhart和Hinton等人提出了後向傳播(Back Propagation,BP)算法[1](也有說1986年的,指的是他們另一篇paper:Learning representations by back-propagating errors),使得神經網絡的訓練變得簡單可行,這篇文章在Google Scholar上的引用次數達到了19000多次。

幾年後,LeCun利用BP算法來訓練多層神經網絡用於識別手寫郵政編碼[2],這個工作就是CNN的開山之作。

LeNet5

模型多處用到了5*5的卷積核,但在這篇文章中LeCun只是說把5*5的相鄰區域作爲感受野,並未提及卷積或卷積神經網絡。關於CNN最原始的雛形感興趣的讀者也可以關注一下文獻[3]。

LeNet5由兩個卷積層,兩個池化層,兩個全連接層組成。卷積核都是5×5,stride=1,池化層使用maxpooling

ILSVRC競賽歷年的佼佼者(AlexNet、VGG、GoogLeNet、ResNet)

下表具體比較AlexNet、VGG、GoogLeNet、ResNet四個模型。


AlexNet(2012)

模型共八層(不算input層),包含五個卷積層、三個全連接層。最後一層使用softmax做分類輸出

AlexNet使用了ReLU做激活函數;防止過擬合使用dropout和數據增強;雙GPU實現;使用LRN

VGG(2014)

論文:《Very Deep Convolutional Networks for Large-scale Image Recognition》

論文鏈接:https://arxiv.org/pdf/1409.1556.pdf%20http://arxiv.org/abs/1409.1556.pdf

全部使用3×3卷積核的堆疊,來模擬更大的感受野,並且網絡層數更深。VGG有五段卷積,每段卷積後接一層最大池化。卷積核數目逐漸增加。

總結:LRN作用不大;越深的網絡效果越好;1×1的卷積也很有效但是沒有3×3好

Network in network(2014)

論文:《Network In Network》

論文鏈接:https://arxiv.org/pdf/1312.4400.pdf

NiN是Shuicheng Yan組ICLR 2014的論文。

貢獻

(1)提出在CONV3x3中插入CONV1x1層。(同等輸入CONV1x1比CONV3x3參數數量少9倍,理論計算量降低9倍)

(2)提出Global Average Pooling (GAP)層。(GAP層沒有參數,計算量可以忽略不計,是壓縮模型的關鍵技術)

雖然論文並沒有強調這兩個組件對減小CNN體積的作用,但它們確實成爲CNN壓縮加速的核心。

MAP作用/好處

Resnet殘差網絡(2016)

論文:《Deep Residual Learning for Image Recognition》

手段:殘差網絡(Residual Network)[15]用跨層連接(Shortcut Connections)擬合殘差項(Residual Representations)的手段來解決深層網絡難以訓練的問題。

實驗結果:作者在ImageNet數據集上使用了一個152層的殘差網絡,深度是VGG網絡的8倍但複雜度卻更低,在ImageNet測試集上達到3.57%的top-5錯誤率,這個結果贏得了ILSVRC2015分類任務的第一名,另外作者還在CIFAR-10數據集上對100層和1000層的殘差網絡進行了分析。

網絡對比:VGG19網絡和ResNet34-plain及ResNet34-Redisual網絡對比如下:

過擬合和退化問題:之前的經驗已經證明,增加網絡的層數會提高網絡的性能,但增加到一定程度之後,隨着層次的增加,神經網絡的訓練誤差和測試誤差會增大,這和過擬合還不一樣,過擬合只是在測試集上的誤差大,這個問題稱爲退化。

技術1:

爲了解決這個問題,作者設計了一種稱爲深度殘差網絡的結構,這種網絡通過跳層連接和擬合殘差來解決層次過多帶來的問題,這種做法借鑑了高速公路網絡(Highway Networks)的設計思想,與LSTM有異曲同工之妙。這一結構的原理如下圖所示:

對應的代碼:

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(BasicBlock, self).__init__()
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.bn1 = nn.BatchNorm2d(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes, planes)
        self.bn2 = nn.BatchNorm2d(planes)
        self.downsample = downsample
        self.stride = stride

技術2:

ResNet還採用了另一項常用技術:

  • 用CONV/s2(步進2的卷積)代替MaxPool+CONV,將降低分辨率的Pool層合併之後通道數翻倍的CONV層,參數數量不變,省去了MaxPool的計算量。

ResNeXt(2017)

網絡詳解https://blog.csdn.net/hejin_some/article/details/80743818

論文:Aggregated Residual Transformations for Deep Neural Networks

論文鏈接:https://arxiv.org/abs/1611.05431

PyTorch代碼:https://github.com/miraclewkf/ResNeXt-PyTorch

ResNet和Inception結合體

基於ResNet和Inception的split+transform+concate結合。但效果卻比ResNet、Inception、Inception-ResNet效果都要好。可以使用group convolution。

一般來說增加網絡表達能力的途徑有三種

1.增加網絡深度,如從AlexNet到ResNet,但是實驗結果表明由網絡深度帶來的提升越來越小;

2.增加網絡模塊的寬度,但是寬度的增加必然帶來指數級的參數規模提升,也非主流CNN設計;

3.改善CNN網絡結構設計,如Inception系列和ResNeXt等。且實驗發現增加Cardinatity即一個block中所具有的相同分支的數目可以更好的提升模型表達能力。

谷歌系列 :Inception v1到v4(2015年-2017年)

網絡模型的相關論文以及下載地址:
   
[v1] [2015 CVPR] [GoogLeNet / Inception-v1][Going Deeper with Convolutions][6.67% test error]
https://arxiv.org/abs/1409.4842
[v2] [2015 ICML] [BN-Inception / Inception-v2] [Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift][4.8% test error] 
https://arxiv.org/abs/1502.03167
[v3] [2016 CVPR] [Inception-v3][Rethinking the Inception Architecture for Computer Vision][3.5% test error] 
https://arxiv.org/abs/1512.00567
[v4][2017 AAAI] [Inception-v4][Inception-ResNet and the Impact of Residual Connections on Learning][3.08% test error]
https://arxiv.org/abs/1602.07261

CNN結構演化圖:

圖片參考

純粹的增大網絡的缺點:

//1.參數太多,容易過擬合,若訓練數據集有限;
//2.網絡越大計算複雜度越大,難以應用;
//3.網絡越深,梯度越往後穿越容易消失(梯度彌散),難以優化模型

Inception v1模型(2014)

論文:《Going Deeper with Convolutions》

論文鏈接:https://arxiv.org/pdf/1409.4842.pdf

模型命名:即是我們熟知的GoogleNet。

設計初衷:從VGG中我們瞭解到,網絡層數越深效果越好。但是隨着模型越深參數越來越多,這就導致網絡比較容易過擬合,需要提供更多的訓練數據;另外,複雜的網絡意味更多的計算量,更大的模型存儲,需要更多的資源,且速度不夠快。GoogLeNet就是從減少參數的角度來設計網絡結構的。

模型核心思想:GoogLeNet核心思想是通過增加網絡寬度的方式來增加網絡複雜度,讓網絡可以自己去應該如何選擇卷積核。這種設計減少了參數 ,同時提高了網絡對多種尺度的適應性。使用了1×1卷積可以使網絡在不增加參數的情況下增加網絡複雜度。

Inception v1的網絡,將1x1,3x3,5x5的conv和3x3的pooling,堆疊在一起,一方面增加了網絡的width,另一方面增加了網絡對尺度的適應性; 

 

第一張圖是論文中提出的最原始的版本,所有的卷積核都在上一層的所有輸出上來做,那5×5的卷積核所需的計算量就太大了,造成了特徵圖厚度很大。爲了避免這一現象提出的inception具有如下結構,在3x3前,5x5前,max pooling後分別加上了1x1的卷積核起到了降低特徵圖厚度的作用,也就是Inception v1的網絡結構。 

小知識:GoogLeNet是谷歌(Google)研究出來的深度網絡結構,爲什麼不叫“GoogleNet”,而叫“GoogLeNet”,據說是爲了向“LeNet”致敬,因此取名爲“GoogLeNet”

GoogLeNet的模型參數詳細如下:


GoogLeNet的結構模型如下: 

 

需要注意的是,爲了避免梯度消失,網絡額外增加了2個輔助的softmax用於向前傳導梯度。文章中說這兩個輔助的分類器的loss應該加一個衰減係數,實際測試的時候,這兩個額外的softmax會被去掉。


Inception v2模型(BN-Inception)(2015)

論文:《Batch Normalization: Accelerating Deep Network Training b y Reducing Internal Covariate Shift》

論文鏈接:https://arxiv.org/pdf/1502.03167.pdf%E7%9A%84paper%E9%80%82%E5%90%88%E6%83%B3%E6%B7%B1%E5%85%A5%E4%BA%86%E8%A7%A3%E5%8E%9F%E7%90%86%EF%BC%8C%E8%BF%99%E4%B8%AA%E8%A7%86%E9%A2%91%E5%BE%88%E6%B8%85%E6%A5%9A%E7%9A%84%E8%AE%B2%E4%BA%86bn%E8%B5%B7%E5%88%B0%E7%9A%84%E4%BD%9C%E7%94%A8%E3%80%82

與Inception V1區別

(1)在v1的基礎上加入batch normalization技術,在tensorflow中,使用BN在激活函數之前效果更好;

(2)將5×5卷積替換成兩個連續的3×3卷積,使網絡更深,參數更少

參考博客:https://mp.csdn.net/postedit/86653765

深度網絡爲什麼難訓練?

因爲internal covariate shift。因此提出了一種改善該問題的機制(Batch Normalization(歸一化,即固定每一層數據輸入的均值和方差))。Batch Norm可謂深度學習中非常重要的技術,不僅可以使訓練更深的網絡變容易,加速收斂,還有一定正則化的效果,可以防止模型過擬合。

Q: 如果數據的分佈不發生改變,真的會是的網絡的訓練變得更簡單嗎? 
A: 首先,考慮機器學習領域中的一個很重要的假設:

  • IID獨立同分布假設:如果訓練數據與測試數據滿足相同的分佈,那麼通過神經網絡訓練出來的模型,能夠在測試集上同樣取得很好的效果。
  • 同樣,在訓練的過程中,如果每一層的數據輸入保持穩定的數據分佈,那麼對於網絡的訓練肯定是有幫助的。
  • 在機器學習中,常用的數據處理的方法“白化操作”(whiten),例如PCA降維等方法,可以有效的提升機器學習的效果。而“白化操作”本質上,就是對輸入數據的分佈進行改變,使其變爲均值爲0,單位方差的正態分佈。
  • 從而,作者認爲如果對每一層的數據輸入,固定其分佈,可定能夠加快深度模型的訓練。

BN的思想?

因爲深層神經網絡在做非線性變換前的激活輸入值(就是那個 x=WU+B, U 是輸入) 隨着網絡深度加深或者在訓練過程中,其分佈逐漸發生偏移或者變動,之所以訓練收斂慢,一般是整體分佈逐漸往非線性函數的取值區間的上下限兩端靠近(對於 Sigmoid 函數來說,意味着激活輸入值 WU+B 是大的負值或正值),所以這導致反向傳播時低層神經網絡的梯度消失,這是訓練深層神經網絡收斂越來越慢的本質原因, 而 BN 就是通過一定的規範化手段,把每層神經網絡任意神經元這個輸入值的分佈強行拉回到均值爲 0 方差爲 1 的標準正態分佈,其實就是把越來越偏的分佈強制拉回比較標準的分佈,這樣使得激活輸入值落在非線性函數對輸入比較敏感的區域,這樣輸入的小變化就會導致損失函數較大的變化,意思是這樣讓梯度變大,避免梯度消失問題產生,而且梯度變大意味着學習收斂速度快,能大大加快訓練速度。
經過 BN 後,目前大部分 Activation 的值落入非線性函數的線性區內,其對應的導數遠離導數飽和區,這樣來加速訓練收斂過程。

如果都通過 BN,那麼不就跟把非線性函數替換成線性函數效果相同了?
如果是多層的線性函數變換其實這個深層是沒有意義的,因爲多層線性網絡跟一層線性網絡是等價的。這意味着網絡的表達能力下降了,這也意味着深度的意義就沒有了。 所以 BN 爲了保證非線性的獲得,對變換後的滿足均值爲 0 方差爲 1 的 x 又進行了 scale 加上 shift 操作(y=scale*x+shift),每個神經元增加了兩個參數 scale 和 shift 參數,這兩個參數是通過訓練學習到的,意思是通過 scale 和 shift 把這個值從標準正態分佈左移或者右移一點並長胖一點或者變瘦一點,每個實例挪動的程度不一樣,這樣等價於非線性函數的值從正中心周圍的線性區往非線性區動了動。核心思想應該是想找到一個線性和非線性的較好平衡點,既能享受非線性的較強表達能力的好處,又避免太靠非線性區兩頭使得網絡收斂速度太慢。

Q:Batch Normalization帶來了哪些好處? 

(1)可以使用更高的學習率, BN 有快速收斂的特性。在沒有加 Batch Normalization 的網絡中我們要慢慢的調整學習率時,甚至在網絡訓練到一半的時候,還需要想着學習率進一步調小的比例選擇多少比較合適。現在,我們在網絡中加入 Batch Normalization 時,可以採用初始化很大的學習率,然後學習率衰減速度也很大,因此這個算法收斂很快。
(2)模型中 BN 可以代替 dropout 或者使用較低的 dropout。 dropout 是經常用來防止過擬合的方法,但是模型中加入 BN 減少 dropout,可以大大提高模型訓練速度,提高網絡泛化性能。
(3)減少 L2 權重衰減係數。用了 Batch Normalization 後,可以把 L2 權重衰減係數降低,論文中降低爲原來的 5 倍。
(4)取消 Loacl Response Normalization 層。(局部響應歸一化是 Alexnet 網絡用到的方法),因爲 BN 本身就是一個歸一化網絡層。
(5) BN 本質上解決了反向傳播過程中梯度消失的問題。 BN 算法對 Sigmoid 激活函數的提升非常明顯,解決了困擾學術界十幾年的 sigmoid 過飽和造成梯度消失的問題。在深度神經網絡中,靠近輸入的網絡層在梯度下降的時候,得到梯度值太小,導致深層神經網絡只有靠近輸出層的那幾層網絡在學習。因爲數據使用 BN 後,歸一化的數據僅使用了 sigmoid 線性的部分。
(6)可以把訓練數據徹底打亂。防止了每批訓練的時候,某一個樣本經常被挑選到。論文中指出這個操作可以提高 1%的精度。

從圖上可以看出,增加了BN操作後,Inception模型的效果都得到了提升,收斂速度都很快。


Inception v3模型(2016)

論文:《Rethinking the Inception Architecture for Computer Vision》

論文鏈接:https://www.cv-foundation.org/openaccess/content_cvpr_2016/papers/Szegedy_Rethinking_the_Inception_CVPR_2016_paper.pdf

Inception V3核心思想

核心思想是將卷積核分解成更小的卷積,如將7×7分解成1×7和7×1兩個卷積核,使網絡參數減少,深度加深。

Inception V3的目的

研究如何在增加網絡規模的同時保證計算高效率,這篇論文中還提出了一些CNN調參的經驗型規則。 

Rethinking這篇論文中提出了一些CNN調參的經驗型規則,暫列如下:

1. 避免特徵表徵的瓶頸。特徵表徵就是指圖像在CNN某層的激活值,特徵表徵的大小在CNN中應該是緩慢的減小的。

2. 高維的特徵更容易處理,在高維特徵上訓練更快,更容易收斂

3. 低維嵌入空間上進行空間匯聚,損失並不是很大。這個的解釋是相鄰的神經單元之間具有很強的相關性,信息具有冗餘。

4. 平衡的網絡的深度和寬度。寬度和深度適宜的話可以讓網絡應用到分佈式上時具有比較平衡的computational budget。

Inception V3網絡結構最大的變化

用了1*n結合n*1來代替n*n的卷積,結構如下: 


Inception V4(2017)

論文:《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning

論文鏈接:https://www.aaai.org/ocs/index.php/AAAI/AAAI17/paper/viewFile/14806/14311

Inception v4 和 Inception -ResNet 在同一篇論文《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning》中介紹。爲清晰起見,我們分成兩個部分來介紹。

詳情參考:https://www.jiqizhixin.com/articles/2018-05-30-7

網絡說明:

Inception v4沒有使用殘差學習的思想,Inception v4基本延續了Inception v2/v3的結構。

出自同一篇論文的Inception-Resnet-v1和Inception-Resnet-v2纔是Inception module與殘差學習的結合產物。

Inception-ResNet和Inception v4網絡結構都是基於Inception v3的改進。

V4整個網絡結構:

左圖爲Inception v4的網絡結構,右圖爲Inception v4的Stem模塊

v4中的三個基本模塊:

對上圖進行說明:

1. 左圖是基本的Inception v2/v3模塊,使用兩個3x3卷積代替5x5卷積,並且使用average pooling,該模塊主要處理尺寸爲35x35的feature map;

2. 中圖模塊使用1xn和nx1卷積代替nxn卷積,同樣使用average pooling,該模塊主要處理尺寸爲17x17的feature map;

3. 右圖在原始的8x8處理模塊上將3x3卷積用1x3卷積和3x1卷積。 

總的來說,Inception v4中基本的Inception module還是沿襲了Inception v2/v3的結構,只是結構看起來更加簡潔統一,並且使用更多的Inception module,實驗效果也更好。


Inception-ResNet系列(V1和V2)

本節將介紹和Inception v4同一篇文章的兩個Inception-ResNet結構:Inception-Resnet-v1和Inception-Resnet-v2。

論文:《Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning

博客:https://blog.csdn.net/zzc15806/article/details/83504130

殘差連接是指淺層特徵通過另外一條分支加到高層特徵中,達到特徵複用的目的,同時也避免深層網路的梯度彌散問題。下圖爲一個基本的殘差結構:

Inception-Resnet-v1

Inception-Resnet-v1內部網絡的基本模塊:

對上圖進行說明:

1. Inception module都是簡化版,沒有使用那麼多的分支,因爲identity部分(直接相連的線)本身包含豐富的特徵信息;

2. Inception module每個分支都沒有使用pooling;

3. 每個Inception module最後都使用了一個1x1的卷積(linear activation),作用是保證identity部分和Inception部分輸出特徵維度相同,這樣才能保證兩部分特徵能夠相加。

Inception-Resnet-v1網絡結構(左),右圖爲Inception-Resnet-v1的Stem模塊:

Inception-Resnet-v2

Inception-Resnet-v2基本模塊:

Inception-Resnet-v2網絡結構同Inception-Resnet-v1(如上上圖左側圖所示),Stem模塊同Inception v4(如下圖右側圖所示)。

ResNeXt(2017)

論文:Aggregated Residual Transformations for Deep Neural Networks

論文鏈接:https://arxiv.org/abs/1611.05431

PyTorch代碼:https://github.com/miraclewkf/ResNeXt-PyTorch

博客:https://blog.csdn.net/hejin_some/article/details/80743818

提出ResNeXt的原因:

傳統模型要提高準確率,都是加深或加寬網絡,但是隨着超參數數量的增加(比如channels數,filter size等等),網絡設計的難度和計算開銷也會增加。爲了解決上述問題,提出 ResNeXt 結構。在不增加參數複雜度的前提下提高準確率,同時還減少了超參數的數量(得益於子模塊的拓撲結構一樣,後面會講)。

ResneXt與VGG和Inception的互鑑:

作者在論文中提到,VGG主要採用堆疊網絡來實現,之前的 ResNet 也借用了這樣的思想。然後提到 Inception 系列網絡,簡單講Inception就是 split-transform-merge 的策略,但是 Inception 系列網絡有個問題:網絡的超參數設定的針對性比較強,當應用在別的數據集上時需要修改許多參數,因此可擴展性一般。

重點來了:

作者提出的 ResNeXt網絡,同時採用 VGG 堆疊的思想和 Inception 的 split-transform-merge 思想,但是可擴展性比較強,可以認爲是在增加準確率的同時基本不改變或降低模型的複雜度。

名詞解釋cardinality:

原文的解釋是the size of the set of transformations,如下圖 Fig1 右邊是 cardinality=32 的樣子,這裏注意每個被聚合的拓撲結構都是一樣的(這也是和 Inception 的差別,減輕設計負擔)

ResNeXt優越性佐證1:

附上原文比較核心的一句話,點明瞭增加 cardinality 比增加深度和寬度更有效,這句話的實驗結果在後面有展示:

ResNeXt優越性佐證2:

Table1 列舉了 ResNet-50 和 ResNeXt-50 的內部結構.

附:最後兩行說明二者之間的參數複雜度差別不大。

接下來作者要開始講本文提出的新的 block,舉全連接層(Inner product)的例子來講,我們知道全連接層的就是以下這個公式:

再配上這個圖就更容易理解其splitting,transforming和aggregating的過程。

然後作者的網絡其實就是將其中的 wixi替換成更一般的函數,這裏用了一個很形象的詞:Network in Neuron,式子如下:

其中C就是 cardinality,Ti有相同的拓撲結構(本文中就是三個卷積層的堆疊)。

然後看看fig 3。這裏作者展示了三種相同的 ResNeXt blocks。fig3.a 就是前面所說的aggregated residual transformations。 fig3.b 則採用兩層卷積後 concatenate,再卷積,有點類似 Inception-ResNet,只不過這裏的 paths 都是相同的拓撲結構。fig 3.c採用的是grouped convolutions,這個 group 參數就是 caffe 的 convolusion 層的 group 參數,用來限制本層卷積核和輸入 channels 的卷積,最早應該是 AlexNet 上使用,可以減少計算量。這裏 fig 3.c 採用32個 group,每個 group 的輸入輸出 channels 都是4,最後把channels合併。這張圖的 fig3.c 和 fig1 的左邊圖很像,差別在於fig3.c的中間 filter 數量(此處爲128,而fig 1中爲64)更多。作者在文中明確說明這三種結構是嚴格等價的,並且用這三個結構做出來的結果一模一樣,在本文中展示的是 fig3.c 的結果,因爲 fig3.c 的結構比較簡潔而且速度更快。

表2列舉了一些參數,來說明 fig1 的左右兩個結構的參數複雜度差不多。第二行的d表示每個path的中間channels數量,最後一行則表示整個block的寬度,是第一行C和第二行d的乘積。

在實驗中作者說明ResNeXt和ResNet-50/101的區別僅僅在於其中的block,其他都不變。

貼一下作者的實驗結果:

相同層數的ResNet和ResNeXt的對比:(32*4d表示32個paths,每個path的寬度爲4,如fig3)。實驗結果表明ResNeXt和ResNet的參數複雜度差不多,但是其訓練誤差和測試誤差都降低了。

另一個實驗結果的表格,主要說明增加Cardinality和增加深度或寬度的區別。

增加寬度就是簡單地增加filter channels。

第一個是基準模型,增加深度和寬度的分別是第三和第四個,可以看到誤差分別降低了0.3%和0.7%。但是第五個加倍了Cardinality,則降低了1.3%,第六個Cardinality加到64,則降低了1.6%。顯然增加Cardianlity比增加深度或寬度更有效。

接下來這個表一方面證明了residual connection的有效性,也證明了aggregated transformations的有效性,控制變量的證明方式,比較好理解。

全文看下來,作者的核心創新點就在於提出了 aggregrated transformations,用一種平行堆疊相同拓撲結構的blocks代替原來 ResNet 的三層卷積的block,在不明顯增加參數量級的情況下提升了模型的準確率,同時由於拓撲結構相同,超參數也減少了,便於模型移植

Xception(2017)

https://imlogm.github.io/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/inception/

基本思想

在Inception-v3的基礎上提出,基本思想是通道分離式卷積,但是又有區別。模型參數稍微減少,但是精度更高。

設計目的

Xception的目標是設計出易遷移、計算量小、能適應不同任務,且精度較高的模型。

基本步驟

Xception先做1×1卷積再做3×3卷積,即先將通道合併,再進行空間卷積。depthwise正好相反,先進行空間3×3卷積,再進行通道1×1卷積。核心思想是遵循一個假設:卷積的時候要將通道的卷積與空間的卷積進行分離。而MobileNet-v1用的就是depthwise的順序,並且加了BN和ReLU。Xception的參數量與Inception-v3相差不大,其增加了網絡寬度,旨在提升網絡準確率,而MobileNet-v1旨在減少網絡參數,提高效率。

 

Xception與Inception-v3在結構上有什麼差別?

如圖1爲Inception-v3的模塊結構,依據化繁爲簡的思想,把模塊結構改造爲圖2。

圖1

圖2

依據depthwise separable convolution的思想,可以進一步把圖2改造到圖3。

圖3

什麼是depthwise separable convolution呢?MobileNet等網絡爲了減少計算量都有用到這個方法,不過Xception在這裏的用法和一般的depthwise separable convolution還有點不同,所以爲了防止大家搞糊塗,我就不介紹一般性的用法了,直接介紹Xception中的用法。

對於112×112×64的輸入做一次普通的3×3卷積,每個卷積核大小爲3×3×64,也就是說,每一小步的卷積操作是同時在面上(3×3的面)和深度上(×64的深度)進行的。

那如果把面上和深度上的卷積分離開來呢?這就是圖3所要表達的操作。依舊以112×112×64的輸入來作例子,先進入1×1卷積,每個卷積核大小爲1×1×64,有沒有發現,這樣每一小步卷積其實相當於只在深度上(×64的深度)進行。

然後,假設1×1卷積的輸出爲112×112×7,我們把它分爲7份,即每份是112×112×1,每份後面單獨接一個3×3的卷積(如圖3所示,畫了7個3×3的框),此時每個卷積核爲3×3×1,有沒有發現,這樣每一小步卷積其實相當於只在面上(3×3的面)進行。

最後,把這7個3×3的卷積的輸出疊在一起就可以了。根據Xception論文的實驗結果,Xception在精度上略低於Inception-v3,但在計算量和遷移性上都好於Inception-v3。

MobileNet V1(2017)

《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision》

來自CVPR2017,由Google團隊提出

代碼鏈接:https://github.com/Zehaos/MobileNet

V1創新點總括

使用depthwise separable convolutions;放棄pooling層,而使用stride=2的卷積。標準卷積的卷積核的通道數等於輸入特徵圖的通道數;而depthwise卷積核通道數是1;還有兩個參數可以控制,a控制輸入輸出通道數;p控制圖像(特徵圖)分辨率。

V1創新點詳解

https://blog.csdn.net/derteanoo/article/details/81160521

假設輸入爲Df*Df*M,卷積核爲Dk*Dk*N,傳統卷積計算量爲Df*Df*M*Dk*Dk*N。mobilenet採用深度可分離卷積(depthwise separable concolution),將其分爲深度卷積和1*1的卷積(如下圖所示),深度卷積的卷積核個數與輸入的通道數相同,1*1卷積核用來改變輸出通道數,計算量分別爲Dk*Dk*M*DF*DF和M*N*DF*DF,實現了spatial與channels間的解耦,減少了計算量。

能夠減少參數數量和計算量的原理

https://blog.csdn.net/mzpmzk/article/details/82976871#_3

(1)變化1:使用深度可分離卷積(Depthwise separable convolution)

組成:Depthwise separable convolution are made up of two layers: depthwise convolutions and pointwise convolutions。

特徵提取:depthwise convolution時只使用了一種維度爲in_channels的卷積核進行特徵提取(沒有進行特徵組合)。

特徵組合:pointwise convolution時只使用了output_channels維度爲in_channels 1*1 的卷積核進行特徵組合。普通卷積不同 depth 層的權重是按照 1:1:1…:1的比例進行相加的,而在這裏不同 depth 層的權重是按照 不同比例(可學習的參數) 進行相加的。

參數量:參數數量由原來的p1 = F*F*in_channels*output_channels 變爲了p2 = F*F*in_channels*1 + 1*1*in_channels*output_channels,減小爲原來的p2/p1 = 1/output_channels + 1/F*F,其中 F 爲卷積核的尺寸,若 F=3,參數量大約會減少到原來的 1/8→1/9。

論文中上述參數量前後變化的寫法:

Note: 原論文中對第一層沒有用此卷積,深度可分離卷積中的每一個後面都跟 BN 和 RELU。

(2)變化2:用 CONV/s2(步進2的卷積)代替 MaxPool+CONV:使得參數數量不變,計算量變爲原來的 1/4 左右,且省去了MaxPool 的計算量。

Note:採用 depth-wise convolution 會有一個問題,就是導致信息流通不暢 ,即輸出的 feature map 僅包含輸入的 feature map 的一部分,在這裏,MobileNet 採用了 point-wise(1*1) convolution 幫助信息在通道之間流通。

(3)變化3:論文提供了兩個用戶定製參數

https://zhuanlan.zhihu.com/p/37074222

框架圖

標準卷積和深度可分離卷積的區別

上面右圖也證明了(2)中的Note結論,即:

採用 depth-wise convolution 會有一個問題,就是導致信息流通不暢 ,即輸出的 feature map 僅包含輸入的 feature map 的一部分,在這裏,MobileNet 採用了 point-wise(1*1) convolution 幫助信息在通道之間流通。

MobileNet的弊端,爲以後ShuffleNet提出奠定基礎

https://zhuanlan.zhihu.com/p/37074222

論文統計了不同類型層的計算量和參數數量,僅一個CONV一個FC,計算量可以忽略,前面也提到FC層參數佔比較大,重點關注,大量使用的DWCONV3\times 3計算量僅3%,參數數量僅1%,其貌不揚的CONV1x1竟然是罪魁禍首,CONV1x1計算量佔95%,參數數量佔75%,所以MobileNet速度快不快,與inferece framework中CONV1x1的優化程度密切相關。

Block分析:MobileNet雖然沒有明確指出,Depthwise Separable Convolution也可以看做block

TensorFlow 中的代碼實現

可使用 tensorflow 中的tf.nn.separable_conv2d() 來實現, 參數depthwise_filter中的 channel_multiplier 設爲 1 即可。

# 使用 slim 來實現
def _depthwise_separable_conv(inputs,
                              num_pwc_filters,
                              kernel_width,
                              phase,
                              sc,
                              padding='SAME',
                              width_multiplier=1,
                              downsample=False):
    """ Helper function to build the depth-wise separable convolution layer.
    """
    num_pwc_filters = round(num_pwc_filters * width_multiplier)
    _stride = 2 if downsample else 1

    # skip pointwise by setting num_outputs=None
    depthwise_conv = slim.separable_convolution2d(inputs,
                                                  num_outputs=None,
                                                  stride=_stride,
                                                  depth_multiplier=1,
                                                  kernel_size=[kernel_width, kernel_width],
                                                  padding=padding,
                                                  activation_fn=None,
                                                  scope=sc + '/depthwise_conv')

    bn = slim.batch_norm(depthwise_conv, activation_fn=tf.nn.relu, is_training=phase, scope=sc + '/dw_batch_norm')
    pointwise_conv = slim.convolution2d(bn,
                                        num_pwc_filters,
                                        kernel_size=[1, 1],
                                        activation_fn=None,
                                        scope=sc + '/pointwise_conv')
    bn = slim.batch_norm(pointwise_conv, activation_fn=tf.nn.relu, is_training=phase, scope=sc + '/pw_batch_norm')
    return bn

Caffe 中的代碼實現

Densenet(2017)

論文:《Densely Connected Convolutional Networks》

原文鏈接:https://arxiv.org/pdf/1608.06993.pdf

代碼鏈接:https://github.com/liuzhuang13/DenseNet

https://github.com/miraclewkf/DenseNet

CVPR 2017最佳論文,作者是康奈爾大學博士後黃高博士、清華大學本科生劉壯、Facebook 人工智能研究院研究科學家 Laurens van der Maaten 及康奈爾大學計算機系教授 Kilian Q. Weinberger 

創新點

(1)採用DenseBlock的結構。如圖,將網絡中所有層都連起來,每一層的輸入來自前面所有層的輸出,保證網絡中信息的最大傳遞,和RNN的感覺類似。這樣,一減輕了vanishing-gradient(梯度消失)(梯度消失的原因是由於梯度信息在很多層之間傳遞所以丟失),二加強了feature的傳遞 ,三更有效地利用了feature ,四每一層的通道數更窄(小於100),一定程度上較少了參數數量,因此過擬合現象也會減輕。

每個Densenet由多個dense block組成,每個dense block的feature map的size一樣,便於concat。

(2)DenseBlock的輸入開始,頻繁使用Bottleneck Layer先進行降維,以減少feature map的層數。在兩個Dense Block中間,使用transition layer,通過設置參數reduction(範圍是0到1),用1*1的卷積核以一定比例進行降維。

(3)Densenet在concat過程會重新開闢內存,因此很佔顯存,可以通過共享內存的方式去優化部署。

EffNet(2018)

EffNet是對MobileNet-v1的改進,主要思想

將MobileNet-1的dw層分解層兩個3×1和1×3的dw層,這樣 第一層之後就採用pooling,從而減少第二層的計算量。EffNet比MobileNet-v1和ShuffleNet-v1模型更小,進度更高。

mobilenet-v2(2018)

博客:https://zhuanlan.zhihu.com/p/37919669

《mobilenet-v2:Inverted Residuals and Linear Bottlenecks: Mobile Networks for Classification, Detection and Segmentation》

論文鏈接:https://arxiv.org/abs/1801.04381

代碼鏈接:https://github.com/miraclewkf/MobileNetV2-PyTorch

https://github.com/suzhenghang/MobileNetv2/tree/master/.gitignore

https://github.com/austingg/MobileNet-v2-caffe

降低DWCONV-bottleneck block中CONV1x1的佔比,除了ShuffleNet和IGCV2用GCONV+shuffle/permutation方法,還有其他好辦法嗎?MobileNet採用了兩種新技術來解決這個問題,按照論文來說,即創新點如下:

相比於Mobilenet-V1的三點不同總括:

1.引入了殘差結構;

2.在dw之前先進行1×1卷積增加feature map通道數,與一般的residual block是不同的;

3.pointwise結束之後棄用ReLU,改爲linear激活函數,來防止ReLU對特徵的破環。這樣做是因爲dw層提取的特徵受限於輸入的通道數,若採用傳統的residual block,先壓縮那dw可提取的特徵就更少了,因此一開始不壓縮,反而先擴張。但是當採用擴張-卷積-壓縮時,在壓縮之後會碰到一個問題,ReLU會破環特徵,而特徵本來就已經被壓縮,再經過ReLU還會損失一部分特徵,應該採用linear。

Mobilenet-V2創新點詳解

(1)Inverted Residuals 逆殘差。把原來兩頭大中間小的bottleneck block變成兩頭小中間大的形式,強行降低了CONV1x1與DWCONV3x3的比例,這裏有個超參數expansion factor擴展因子t,推薦是5~10。小網絡使用小的擴張係數(expansion factor),大網絡使用大一點的擴張係數(expansion factor),推薦是5~10,論文中 t=6

Inverted residual block使用 RELU6(最高輸出爲 6)激活函數,使得模型在低精度計算下具有更強的魯棒性。

原文對此模塊解釋:

(2)Linear Bottlenecks 線性瓶頸:去掉了第二個CONV1x1後面的ReLU,改爲線性神經元,其實就是沒有非線性激活函數,論文解釋是在低維度空間ReLU會破壞信息。

網絡優化

bottleneck的輸出通道數非常小[24 32 64-96 160-320],這在以前的結構中容量是遠遠不夠的。再來看經expand layer擴展以後的DWCONV3x3層,baseline版本通道數是[144 192 384-576 960-1920],比較接近ShuffleNetx2的配置,略高於V1,從這個角度來看,MobileNetV2設計上是保持DWCONV3x3層通道數基本不變或輕微增加,將bottleneck的輸入和輸出通道數直接減小t倍,擴展因子其實更像是壓縮因子,將CONV1x1層的參數數量和計算量直接減小t倍,輕微增加DWCONV3x3的通道數以保證的網絡容量。ShuffleNet中標準卷積更多、深度分離卷積更少的優化思路完全相反,MobileNetV2中標準卷積更少、深度分離卷積更多(相對的,計算量佔比CONV1x1依然絕對優勢)。

  • baseline版本MobileNetV2,參數數量3.4M,計算量300M,top-1 72%,這個理論計算量與SqueezeNet是同一等級,比MobileNetV1更好更小更快
  • MobileNetV2-1.4版本(1.4**2=1.96,大約是baseline的2倍)參數數量6.9M,計算量585M,top-1 74.7%,這個計算量與1.0-MobileNet-224和ShuffleNetx2g3同一等級,性能超過ShuffleNet 1%
  • 甚至超過了NASNet,是目前最好的高效小網絡結構,更重要的是開源了模型和代碼,github上有各個平臺的模型復現

結構方面,每個bottleneck裏面兩個ReLU6,方便量化;輸入圖像以s2方式逐階平滑降維;用戶定製依然是寬度乘子和分辨率乘子,同MobileNetV1;通道數量分佈前期少後期多,在14x14和7x7階段通道數量額外增加了一次,分別上升50%和100%。

ncnn-benchmark中MobileNetV2是caffe復現的baseline,但由於標準卷積比深度分離卷積優化更好,且CONV3x3比CONV1x1優化更好,導致部分ARM上MobileNetV2與MobileNet速度接近,但性能差距還是存在的,期待進一步優化後能接近SqueezeNetv1.1速度。

每個stage的通道數量:【24 32 64-96 160-320】*6

每個stage的block數量:【2 3 7 4】

Block分析:MobileNetV2 block,類似MobileNet沒有加shotcut

MobileNetV2網絡結構:

MobileNetV2和 MobileNetV1 的區別

https://zhuanlan.zhihu.com/p/33075914

相同點

都採用 Depth-wise (DW) 卷積搭配 Point-wise (PW) 卷積的方式來提特徵。這兩個操作合起來也被稱爲 Depth-wise Separable Convolution,之前在 Xception 中被廣泛使用。這麼做的好處是理論上可以成倍的減少卷積層的時間複雜度和空間複雜度。由下式可知,因爲卷積核的尺寸K通常遠小於輸出通道數C_{out},因此標準卷積的計算複雜度近似爲DW+PW組合卷積的 K^2倍。

不同點:Linear Bottleneck

(1)V2 在 DW 卷積之前新加了一個 PW 卷積。這麼做的原因,是因爲 DW 卷積由於本身的計算特性決定它自己沒有改變通道數的能力,上一層給它多少通道,它就只能輸出多少通道。所以如果上一層給的通道數本身很少的話,DW 也只能很委屈的在低維空間提特徵,因此效果不夠好。現在 V2 爲了改善這個問題,給每個 DW 之前都配備了一個 PW,專門用來升維,定義升維繫數t=6,這樣不管輸入通道數C_{in}是多是少,經過第一個 PW 升維之後,DW 都是在相對的更高維 (t\cdot C_{in})進行着辛勤工作的。

(2)論文作者稱其爲 Linear Bottleneck。這麼做的原因,是因爲作者認爲激活函數在高維空間能夠有效的增加非線性,而在低維空間時則會破壞特徵,不如線性的效果好。由於第二個 PW 的主要功能就是降維,因此按照上面的理論,降維之後就不宜再使用 ReLU6 了。

對比 MobileNet V2與ResNet 的微結構

https://zhuanlan.zhihu.com/p/33075914

Mobilenet-v3(2019)

論文:《Searching for mobilenetv3》

論文鏈接:https://arxiv.org/pdf/1905.02244.pdf

互補搜索技術組合:由資源受限的NAS執行模塊集搜索,NetAdapt執行局部搜索;網絡結構改進:將最後一步的平均池化層前移並移除最後一個卷積層,引入h-swish激活函數,修改了開始的濾波器組。

V3綜合了v1的深度可分離卷積,v2的具有線性瓶頸的反殘差結構,SE結構的輕量級注意力模型。

SqueezeNet(2017ICLR

論文:《SQUEEZENET: ALEXNET-LEVEL ACCURACY WITH 50X FEWER PARAMETERS AND <0.5MB MODEL SIZE》

論文鏈接:https://arxiv.org/pdf/1602.07360.pdf

詳細參考:https://baijiahao.baidu.com/s?id=1589005428414488177&wfr=spider&for=pc

命名

從名字——SqueezeNet 就知道,本文的新意是 squeeze,squeeze 在 SqueezeNet 中表示一個 squeeze 層,該層採用 1*1 卷積覈對上一層 feature map 進行卷積,主要目的是減少 feature map 的維數(維數即通道數,就是一個立方體的 feature map,切成一片一片的,一共有幾片)。

創新點

1. 採用不同於傳統的卷積方式,提出 fire module;fire module 包含兩部分:squeeze 層+expand 層

創新點與 inception 系列的思想非常接近!首先 squeeze 層,就是 1*1 卷積,其卷積核數要少於上一層 feature map 數,這個操作從 inception 系列開始就有了,並美其名曰壓縮,個人覺得「壓縮」更爲妥當。

Expand 層分別用 1*1 和 3*3 卷積,然後 concat,這個操作在 inception 系列裏面也有。

SqueezeNet 的核心在於 Fire module,Fire module 由兩層構成,分別是 squeeze 層+expand 層,如下圖 1 所示,squeeze 層是一個 1*1 卷積核的卷積層,expand 層是 1*1 和 3*3 卷積核的卷積層,expand 層中,把 1*1 和 3*3 得到的 feature map 進行 concat。

ShuffleNet V1(2018)

論文:《ShuffleNet: An Extremely Efficient Convolutional Neural Network for Mobile Devices》

論文鏈接:http://openaccess.thecvf.com/content_cvpr_2018/papers/Zhang_ShuffleNet_An_Extremely_CVPR_2018_paper.pdf

詳情參考:https://baijiahao.baidu.com/s?id=1589005428414488177&wfr=spider&for=pc

通過分組卷積與1×1的逐點羣卷積核來降低計算量,通過重組通道來豐富各個通道的信息。Xception和ResNeXt在小型網絡模型中效率較低,因爲大量的1×1卷積很耗資源,因此提出逐點羣卷積來降低計算複雜度,但是使用逐點羣卷積會有副作用,故在此基礎上提出通道shuffle來幫助信息流通。雖然dw可以減少計算量和參數量,但是在低功耗設備上,與密集的操作相比,計算、存儲訪問的效率更差,故shufflenet上旨在bottleneck上使用深度卷積,儘可能減少開銷。

ShuffleNet V2(2018ECCV)

論文:《ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design》

論文鏈接:http://openaccess.thecvf.com/content_ECCV_2018/papers/Ningning_Light-weight_CNN_Architecture_ECCV_2018_paper.pdf

詳情參考:https://baijiahao.baidu.com/s?id=1589005428414488177&wfr=spider&for=pc

使神經網絡更加高效的CNN網絡結構設計準則:

輸入通道數與輸出通道數保持相等可以最小化內存訪問成本

分組卷積中使用過多的分組會增加內存訪問成本

網絡結構太複雜(分支和基本單元過多)會降低網絡的並行程度

element-wise的操作消耗也不可忽略

SENet(2018)

論文:《Squeeze-and-Excitation Networks》
      論文鏈接:https://arxiv.org/abs/1709.01507 

      代碼地址:https://github.com/hujie-frank/SENet 

      PyTorch代碼地址:https://github.com/miraclewkf/SENet-PyTorch
詳情參考:http://www.sohu.com/a/303625107_500659

https://blog.csdn.net/weixin_41923961/article/details/88983505

SKNet(2019CVPR)

《Selective Kernel Networks》

論文:https://arxiv.org/pdf/1903.06586.pdf

代碼:https://github.com/implus/SKNet

詳情參考:https://cloud.tencent.com/developer/article/1420865

https://blog.csdn.net/qixutuo6087/article/details/88822428

博客參考


深入淺出——網絡模型中Inception的作用與結構全解析:https://blog.csdn.net/u010402786/article/details/52433324

Inception in CNN:https://blog.csdn.net/stdcoutzyx/article/details/51052847

Inception-v3:"Rethinking the Inception Architecture for Computer Vision":https://blog.csdn.net/cv_family_z/article/details/50789805

論文筆記 | Inception-v4, Inception-ResNet and the Impact of Residual Connections on Learning:https://blog.csdn.net/bea_tree/article/details/51784026

談談Tensorflow的Batch Normalization:https://www.jianshu.com/p/0312e04e4e83

TensorFlow中文社區:http://www.tensorfly.cn/tfdoc/api_docs/python/nn.html

什麼是批標準化 (Batch Normalization):https://zhuanlan.zhihu.com/p/24810318

深度卷積神經網絡演化歷史及結構改進脈絡-40頁長文全面解讀:http://www.dataguru.cn/article-13532-1.html

大話CNN經典模型:GoogLeNet(從Inception v1到v4的演進:https://my.oschina.net/u/876354/blog/1637819

參考文獻

[1] DE Rumelhart, GE Hinton, RJ Williams, Learning internal representations by error propagation. 1985 – DTIC Document.

[2] Y. LeCun , B. Boser , J. S. Denker , D. Henderson , R. E. Howard , W. Hubbard and L. D. Jackel, “Backpropagation applied to handwritten zip code recognition”, Neural Computation, vol. 1, no. 4, pp. 541-551, 1989.

[3] K. Fukushima. Neocognitron: A self-organizing neural network model for a mechanism of pattern recognition unaffected by shift in position. Biological Cybernetics, 36(4): 93-202, 1980.

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