2014年的Imagenet不太尋常孝情,GoogLeNet(我們這兒先來討論GoogLeNet Incepetion V1)與VGG一道成為當(dāng)年的冠軍更卒。當(dāng)然了,他們都做到了more deeper(Alexnet只有8層)吨娜,但是也有一些不同的地方问拘,VGG貌似更為直接的繼承了AlexNet的框架,但是GoogLeNet卻做了更加大膽的嘗試压鉴,不僅僅做到了more deeper崖咨,而且more wider,雖然層數(shù)也只有22層油吭,但是相對(duì)于AlexNet與VGG而言击蹲,在大小上還是小了很多署拟。下面我們來看看它是怎么go deeper and wider but more smaller的。
說點(diǎn)題外話歌豺,雖然GoogLeNet取名為“GoogLeNet”而非“GoogleNet”推穷,是向LeNet致敬,但不得不承認(rèn)的一點(diǎn)就是網(wǎng)絡(luò)結(jié)構(gòu)里很難看到LeNet的影子类咧。:)
我們大致清楚馒铃,增加網(wǎng)絡(luò)的深度和寬度是直接提升網(wǎng)絡(luò)性能的兩個(gè)方法,而文章中也提出:獲得高質(zhì)量模型最保險(xiǎn)的做法就是增加模型的深度(層數(shù))或者是其寬度(層核或者神經(jīng)元數(shù))轮听。但是不管哪一種方法帶來的都是參數(shù)的增加骗露,帶來的后果就是計(jì)算量的增加,而且復(fù)雜的模型也將容易過擬合血巍、難以優(yōu)化萧锉。
我們先來將GoogLeNet的結(jié)構(gòu)可視化一把:
不好意思這個(gè)有點(diǎn)長,但是我也沒辦法笆龉选(誰叫它本身就張那樣)柿隙,不過請(qǐng)大家注意了,整個(gè)網(wǎng)絡(luò)是由很多個(gè)紅框框這樣的就夠重復(fù)組合而成的鲫凶,現(xiàn)在再回去看一波禀崖,是不是對(duì)這個(gè)結(jié)構(gòu)了就一目了然了O(∩_∩)O。
我們把紅色方框的部分抽離出來螟炫,另外波附,再給它起個(gè)名字(當(dāng)然只有Szegedy才有資格咯):Incepetion
可以看到Inception里有四個(gè)并行的線路。
- 單個(gè) 1×1卷積昼钻。
- 1×1 卷積接上 3×3 卷積掸屡。通常前者的通道數(shù)少于輸入通道,這樣減少后者的計(jì)算量然评。后者加上了padding=1使得輸出的長寬的輸入一致(看不懂仅财?沒關(guān)系的,我在后面說明一下讓你秒懂)
- 同2碗淌,但換成了 5×5 卷積
- 和1類似盏求,但卷積前用了最大池化層
最后將這四個(gè)并行線路的結(jié)果在通道這個(gè)維度上Concat(就是矩陣拼接)在一起。
大家可能就會(huì)有疑問了亿眠,為何要在input出來的時(shí)候加個(gè)1×1卷積碎罚,1×1的kennel去卷積不是啥事兒都沒做嘛?不急纳像,聽我慢慢分析魂莫。我們先來做個(gè)假設(shè),假設(shè)這樣干:
就是先把那個(gè)1×1卷積給他抹掉爹耗,那么就是直接使用1×1耙考、3×3以及5×5的卷積核對(duì)input進(jìn)行操作,但是這樣仍然會(huì)帶來巨大的計(jì)算量(還不明白潭兽?接著看)倦始。我們最開始提到過,GoogLeNet取名為“GoogLeNet”而非“GoogleNet”山卦,是向LeNet致敬鞋邑,但不得不承認(rèn)的一點(diǎn)就是網(wǎng)絡(luò)結(jié)構(gòu)里很難看到LeNet的影子,其實(shí)它是Draw lessons from NIN账蓉,具體就是采用1×1卷積核來進(jìn)行降維枚碗,這樣,1x1的卷積核起到了降低feature map厚度铸本、減少計(jì)算量的作用肮雨。
大家可能還是不太明白吧,沒關(guān)系箱玷,咋來舉個(gè)例子:
我們以3×3這個(gè) path 為例怨规,假設(shè) input(也就是上層的 output)為128×100×100(channel×height×width,batch size略去锡足,沒寫波丰,事實(shí)上,batch size是不影響計(jì)算的):
- 不使用1×1的卷積舶得,那么經(jīng)過 kennel = 3×3掰烟,channel = 256,stride=1沐批,pad=1 的卷積之后纫骑,其 output:256×100×100,那么其參數(shù)數(shù)量128×256×3×3
- 若是先將 output 經(jīng)過 kennel = 1×1珠插,channel = 64惧磺,的卷積經(jīng)之后,再將 1×1 Conv 的 output 拋進(jìn) 3×3 卷積層捻撑,還是設(shè)3×3 Conv 的kennel = 3×3磨隘,channel = 256,stride=1顾患,pad=1番捂。但參數(shù)數(shù)量將減少為128×64×1×1+64×256×3×3,參數(shù)將近減少了一半江解,而你把這個(gè)放到5×5 的那條path上面去推算一下设预,可能會(huì)更明顯!你說膩害不膩害
現(xiàn)在大家明白了這個(gè)1×1的卷積層是拿來干啥的了吧
大家都 get 到這個(gè) point 以后犁河,我們現(xiàn)在再來對(duì)這個(gè) “Incepetion模塊” 進(jìn)行一波總結(jié):
- 采用了1×1鳖枕、3×3以及5×5的不同的kennel魄梯,那么神經(jīng)元將看到不同尺度的信息,最后的 concat 也就將不同尺度的features加以結(jié)合了宾符;
- 1×1 卷積接上 3×3 卷積酿秸。將前者的通道數(shù)設(shè)置為少于輸入通道,這樣減少后者的計(jì)算量魏烫。而后者加上了padding=1使得輸出的長寬與輸入一致辣苏,5×5 的那個(gè)是一樣的道理,padding=2 而已哄褒。輸出的長寬與輸入一致確保最后能 concat 在一起
- 文章說很多地方都表明pooling挺有效稀蟋,所以Inception里面也嵌入了。至于pooling嘛呐赡,原文解釋得不是很詳細(xì)退客,只說了效果好,所以放上去了 罚舱,大家有什么看法也可以留言交流
- 最后井辜,網(wǎng)絡(luò)使用的是average pooling,而不是全連接管闷,結(jié)果當(dāng)然要好不然人家也不這么干粥脚,而在最后它還是使用linear線性層,這么干是為了方便fine-tuning模型包个。
這樣說大家就不會(huì)打我臉了吧(●ˇ?ˇ●)
我們?cè)賮砘貧w原點(diǎn)刷允,討論整個(gè)網(wǎng)絡(luò)的思路go more deeper,go more wider:
- 深度碧囊,層數(shù)更深树灶,文章采用了22層,為了避免上述提到的梯度消失問題(在反向傳播更新參數(shù)時(shí)糯而,越靠近輸入梯度越刑焱ā),GoogLeNet 巧妙的在不同深度處增加了兩個(gè)loss來保證梯度回傳消失的現(xiàn)象熄驼。
-
寬度像寒,增加了多種核1x1,3x3瓜贾,5x5诺祸,還有直接max pooling的。
但是如果簡單的將這些應(yīng)用到feature map上的話祭芦,concat起來的feature map厚度將會(huì)很大筷笨,所以在googlenet中為了避免這一現(xiàn)象提出的inception具有如下結(jié)構(gòu),在3x3前,5x5前胃夏,max pooling后分別加上了1x1的卷積核起到了降低feature map厚度的作用
以上我們其實(shí)都是介紹的是more wider轴或,那么這兒再來說說如何解決more deeper的一些問題权烧,我們看到GoogLeNet一共堆砌了多個(gè)Incepetion模塊使得網(wǎng)絡(luò)加深挣输,因此在BP(Back Propagation )的時(shí)候梯度彌散的情況是存在的,而GoogLeNet里面的softmax0、softmax1就是來解決這個(gè)問題的具體就是在訓(xùn)練的時(shí)候悼瘾,他們的損失誤差乘以一個(gè)權(quán)值(GoogLeNet里設(shè)置為0.3)加到整體損失中。在應(yīng)用的時(shí)候审胸,這兩個(gè)輔助分類器會(huì)被丟掉亥宿。GoogLeNet的實(shí)驗(yàn)表明,只需要一個(gè)輔助分類器就可以達(dá)到同樣的效果(提升0.5%)砂沛。
補(bǔ)充一下烫扼,以后我還會(huì)接著介紹它的其他幾個(gè)版本
下面我們就來看看大家最關(guān)心的實(shí)踐吧(Mxnet+Gluon)O(∩_∩)O
首先我們定義Inception模塊
from mxnet.gluon import nn
from mxnet import nd
class Inception(nn.Block):
def __init__(self, n1_1, n2_1, n2_3, n3_1, n3_5, n4_1, **kwargs):
# n*_* : the output of every path, for exzample
# n2_1 is the output of Conv 1*1 of path 2 & n2_3 is the output of Conv 3*3 of path 2
super(Inception, self).__init__(**kwargs)
# path 1
self.p1_conv_1 = nn.Conv2D(n1_1, kernel_size=1,
activation='relu')
# path 2
self.p2_conv_1 = nn.Conv2D(n2_1, kernel_size=1,
activation='relu')
self.p2_conv_3 = nn.Conv2D(n2_3, kernel_size=3, padding=1,
activation='relu')
# path 3
self.p3_conv_1 = nn.Conv2D(n3_1, kernel_size=1,
activation='relu')
self.p3_conv_5 = nn.Conv2D(n3_5, kernel_size=5, padding=2,
activation='relu')
# path 4
self.p4_pool_3 = nn.MaxPool2D(pool_size=3, padding=1,
strides=1)
self.p4_conv_1 = nn.Conv2D(n4_1, kernel_size=1,
activation='relu')
# define the forward propagation
def forward(self, x):
p1 = self.p1_conv_1(x)
p2 = self.p2_conv_3(self.p2_conv_1(x))
p3 = self.p3_conv_5(self.p3_conv_1(x))
p4 = self.p4_conv_1(self.p4_pool_3(x))
# concatenate the output of four paths together at the first dimension (channel)
return nd.concat(p1, p2, p3, p4, dim=1)
接著,我們就可以用這個(gè)Inception模塊去構(gòu)造GoogLeNet了
class GoogLeNet(nn.Block):
def __init__(self, num_classes, verbose=False, **kwargs):
super(GoogLeNet, self).__init__(**kwargs)
self.verbose = verbose
# add name_scope on the outer most Sequential
with self.name_scope():
# block 1
b1 = nn.Sequential()
b1.add(
nn.Conv2D(64, kernel_size=7, strides=2,
padding=3, activation='relu'),
nn.MaxPool2D(pool_size=3, strides=2)
)
# block 2
b2 = nn.Sequential()
b2.add(
nn.Conv2D(64, kernel_size=1),
nn.Conv2D(192, kernel_size=3, padding=1),
nn.MaxPool2D(pool_size=3, strides=2)
)
# block 3
b3 = nn.Sequential()
b3.add(
Inception(64, 96, 128, 16,32, 32),
Inception(128, 128, 192, 32, 96, 64),
nn.MaxPool2D(pool_size=3, strides=2)
)
# block 4
b4 = nn.Sequential()
b4.add(
Inception(192, 96, 208, 16, 48, 64),
Inception(160, 112, 224, 24, 64, 64),
Inception(128, 128, 256, 24, 64, 64),
Inception(112, 144, 288, 32, 64, 64),
Inception(256, 160, 320, 32, 128, 128),
nn.MaxPool2D(pool_size=3, strides=2)
)
# block 5
b5 = nn.Sequential()
b5.add(
Inception(256, 160, 320, 32, 128, 128),
Inception(384, 192, 384, 48, 128, 128),
nn.AvgPool2D(pool_size=2)
)
# block 6
b6 = nn.Sequential()
b6.add(
nn.Flatten(),
nn.Dense(num_classes)
)
# chain blocks together
self.net = nn.Sequential()
self.net.add(b1, b2, b3, b4, b5, b6)
# define the forward propagation,
def forward(self, x):
out = x
for i, b in enumerate(self.net):
out = b(out)
if self.verbose:
print('Block %d output: %s'%(i+1, out.shape))
return out
這就是整個(gè)GoogLeNet的Structure了