【深度學(xué)習(xí)】基于PyTorch的卷積神經(jīng)網(wǎng)絡(luò)經(jīng)典BackBone(骨干網(wǎng)絡(luò))復(fù)現(xiàn)
導(dǎo)讀
?本文基于代碼實(shí)戰(zhàn)復(fù)現(xiàn)了經(jīng)典的Backbone結(jié)構(gòu)Inception v1、ResNet-50和FPN,并基于PyTorch分享一些網(wǎng)絡(luò)搭建技巧,很詳細(xì)很干貨!
文章目錄
前言
卷積神經(jīng)網(wǎng)絡(luò)的發(fā)展,從上個(gè)世紀(jì)就已經(jīng)開(kāi)始了,讓時(shí)間回到1998年,在當(dāng)時(shí),Yann LeCun 教授提出了一種較為成熟的卷積神經(jīng)網(wǎng)絡(luò)架構(gòu)LeNet-5,現(xiàn)在被譽(yù)為卷積神經(jīng)網(wǎng)絡(luò)的“HelloWorld”,但由于當(dāng)時(shí)計(jì)算機(jī)算力的局限性以及支持向量機(jī)(核學(xué)習(xí)方法)的興起,CNN方法并不是當(dāng)時(shí)學(xué)術(shù)界認(rèn)可的主流方法。時(shí)間推移到14年后,隨著AlexNet以高出第二名約10%的accuracy rate成為了2012年ImageNet圖像識(shí)別競(jìng)賽的第一名,深度學(xué)習(xí)以及卷積神經(jīng)網(wǎng)絡(luò)的研究熱潮被徹底引爆,從此CNN進(jìn)入了飛速發(fā)展的階段,從無(wú)人問(wèn)津到一度成為計(jì)算機(jī)視覺(jué)的主流框架,在此之后,各種基于CNN的圖像識(shí)別網(wǎng)絡(luò)開(kāi)始大放異彩。各種CNN網(wǎng)絡(luò)層出不窮。
本次博客將介紹如今圖像識(shí)別領(lǐng)域十分經(jīng)典的一些CNN網(wǎng)絡(luò),雖然現(xiàn)在卷積網(wǎng)絡(luò)框架也隨著研究的深入變得越來(lái)越復(fù)雜,但我們?nèi)匀豢梢栽谝恍┳钚碌木W(wǎng)絡(luò)結(jié)構(gòu)中發(fā)現(xiàn)它們的身影,這些經(jīng)典CNN網(wǎng)絡(luò)有時(shí)候是整個(gè)算法提取特征的骨架(特征的質(zhì)量往往直接影響到分類結(jié)果的準(zhǔn)確度,表達(dá)能力更強(qiáng)的特征也能給模型帶來(lái)更強(qiáng)的分類能力),因此又稱為“Backbone”(骨干網(wǎng)絡(luò))。
本次博客基于代碼實(shí)戰(zhàn)復(fù)現(xiàn)經(jīng)典的Backbone結(jié)構(gòu),并基于PyTorch分享一些網(wǎng)絡(luò)搭建技巧。
1.VGG
網(wǎng)絡(luò)架構(gòu):
VGG16網(wǎng)絡(luò)由13層卷積層+3層全連接層構(gòu)成。

1.1改進(jìn):
更小的卷積核,對(duì)比AlexNet,VGG網(wǎng)絡(luò)使用的卷積核大小不超過(guò)3x3,這種結(jié)構(gòu)相比于大卷積核有一個(gè)優(yōu)點(diǎn),就是兩個(gè)3x3的卷積核堆疊對(duì)于原圖提取特征的感受野(特征圖一個(gè)像素融合了輸入多少像素的信息決定了感受野的大小)相當(dāng)于一個(gè)5x5卷積核(如圖),并且在同等感受野的條件下,兩個(gè)3x3卷積之間加入激活函數(shù),其非線性能力比單個(gè)5x5卷積要強(qiáng)。

更深的網(wǎng)絡(luò)結(jié)構(gòu),相比于AlexNet只有5層卷積層,VGG系列加深了網(wǎng)絡(luò)的深度,更深的結(jié)構(gòu)有助于網(wǎng)絡(luò)提取圖像中更復(fù)雜的語(yǔ)義信息。
1.2 PyTorch復(fù)現(xiàn)VGG19
class?VGG19(nn.Module):
?def?__init__(self,?num_classes?=?1000):?#?num_classes?預(yù)分類數(shù)
??super(VGG19,?self).__init__()
??#構(gòu)造特征提取層:
??feature_layers?=?[]?#?將卷積層存儲(chǔ)在list中
??in_dim?=?3?#?輸入是三通道圖像
??out_dim?=?64?#?輸出特征的深度為64
??#采用循環(huán)構(gòu)造的方式,對(duì)于深度網(wǎng)絡(luò)能避免代碼形式冗余:
??for?i?in?range(16):#?vgg19共16層卷積
???feature_layers?+=?[nn.Conv2d(in_dim,?out_dim,3,1,1),?nn.ReLU(inplace?=?True)]?#?基本結(jié)構(gòu):卷積+激活函數(shù)
???in_dim?=?out_dim
???#?在第2,4,8,12,16,層卷積后增加最大池化:
???if?i?==?1?or?i?==?3?or?i?==?7?or?i?==?11?or?i?==?15?:
????feature_layers?+=?[nn.MaxPool2d(2)]
????if?i?11:
?????out_dim?*=?2
????self.features?=?nn.Sequential(*feature_layers)?
????????????????#?*表示傳入?yún)?shù)的數(shù)量不固定,否則報(bào)錯(cuò)list?is?not?a?Module?subclass
??#全連接層分類:
??self.classifier?=?nn.Sequential(
???nn.Linear(512?*?7?*?7,?4096),
???nn.ReLU(inplace?=?True),
???nn.Dropout(),
???nn.Linear(4096,4096),
???nn.ReLU(inplace?=?True),
???nn.Dropout(),
???nn.Linear(4096,num_classes),
???)
?#前向傳播
?def?forward(self,?x):
??x?=?self.features(x)
??x?=?x.view(x.size(0),?-1)
??x?=?self.classifier(x)
??return?x
1.2.1 小Tips:
當(dāng)網(wǎng)絡(luò)的結(jié)構(gòu)重復(fù)時(shí),使用for循環(huán)構(gòu)造避免代碼形式冗余
將不同功能的網(wǎng)絡(luò)各自封裝到一個(gè)大的Sequential模塊中,結(jié)構(gòu)分明
卷積操作輸出尺寸計(jì)算公式:Out=(In-Kernel+2Padding)/Stride+1 (Kernel:卷積核尺寸,Stride:步長(zhǎng),Padding:邊界填充) 若要保證輸出尺寸和原尺寸一致,Padding可以設(shè)置為:Padding = (kernel-1)/2)
池化操作輸出尺寸計(jì)算公式同卷積操作一致
在實(shí)際深度學(xué)習(xí)框架實(shí)現(xiàn)卷積和全連接的計(jì)算中,本質(zhì)都是矩陣運(yùn)算:
若輸入的特征圖深度是N,輸出特征圖深度是M,則卷積核的維度是:NxMxKxK(K為卷積核大小)。因此全卷積網(wǎng)絡(luò)對(duì)輸入圖像的尺寸沒(méi)有要求。
全連接層的尺寸和輸入的特征尺寸相關(guān)(將特征圖展平成為一維向量),若輸入的特征向量是1xN,輸出是1xM,則全連接層的維度是:MxN。
1.2.2 打印網(wǎng)絡(luò)信息:
使用torch.summary輸出網(wǎng)絡(luò)架構(gòu):
vgg19?=?VGG19()
#print(vgg19)?#輸出網(wǎng)絡(luò)的架構(gòu)
summary(vgg19,?input_size?=?[(3,?224,?224)])?#輸出網(wǎng)絡(luò)架構(gòu),每一層的輸出特征尺寸,及網(wǎng)絡(luò)參數(shù)情況
輸出網(wǎng)絡(luò)每一層的尺寸:
for?param?in?vgg19.parameters():?#?輸出每一層網(wǎng)絡(luò)的尺寸
?print(param.size())
batch_size?=?16
input?=?torch.randn(batch_size,?3,?224,?224)?#構(gòu)建一個(gè)隨機(jī)數(shù)據(jù),模擬一個(gè)batch_size
output?=?vgg19(input)
print(output.shape)??#?torch.Size([16,?1000])
2.Inception(GoogLeNet)
2.1改進(jìn)(Inception v1)
以往網(wǎng)絡(luò)的不足:
加深深度導(dǎo)致的網(wǎng)絡(luò)參數(shù)增加 深層網(wǎng)絡(luò)需要更多的訓(xùn)練數(shù)據(jù),容易產(chǎn)生過(guò)擬合 深層網(wǎng)絡(luò)在訓(xùn)練過(guò)程中容易導(dǎo)致梯度消失
改進(jìn):
引入了Inception模塊作為網(wǎng)絡(luò)的基礎(chǔ)模塊,整體的網(wǎng)絡(luò)基于基礎(chǔ)模塊的堆疊;在模塊中使用了通道拼接(Concat)的方法對(duì)不同卷積核提取的特征進(jìn)行拼接
Inception基礎(chǔ)的模塊如圖所示,使用3個(gè)不同尺寸的卷積核進(jìn)行卷積運(yùn)算,同時(shí)還包括一個(gè)最大池化,最后將這四個(gè)部分輸出的結(jié)果進(jìn)行通道拼接,傳給下一層:

使用1x1卷積進(jìn)行數(shù)據(jù)降維(減少深度),減少訓(xùn)練的參數(shù)量。
上圖這個(gè)結(jié)構(gòu)有一個(gè)弊端,即模塊中一個(gè)分支的輸入通道數(shù)就是前一個(gè)模塊所有分支輸出通道數(shù)之和(通道合并),在多個(gè)模塊堆疊后計(jì)算的參數(shù)量將會(huì)變得十分巨大,為了解決這個(gè)問(wèn)題,作者在每一個(gè)分支的卷積層之前單獨(dú)加了一個(gè)1x1卷積,來(lái)進(jìn)行通道數(shù)的降維

我們或許會(huì)有一個(gè)疑問(wèn),為什么不在3x3或5x5卷積輸出上直接降維特征,而非得使用1x1卷積呢,(作者認(rèn)為這樣做能夠增加網(wǎng)絡(luò)的非線性能力,因?yàn)榫矸e和卷積之間有激活函數(shù))
引入輔助分類器(在不同深度計(jì)算分類最后一并回傳計(jì)算損失)
作者發(fā)現(xiàn)網(wǎng)絡(luò)中間層的特征和較深層的特征有很大的不同,因此在訓(xùn)練時(shí)額外在中間層增加了兩個(gè)輔助分類器。輔助分類器的結(jié)果同輸出結(jié)果一并計(jì)算損失,并且輔助分類器的損失為網(wǎng)絡(luò)總損失的0.3。作者認(rèn)為這樣的結(jié)構(gòu)有利于增強(qiáng)網(wǎng)絡(luò)在較淺層特征的分類能力,**相當(dāng)于給網(wǎng)絡(luò)加了一個(gè)額外的約束(正則化)**,并且在推理時(shí)這些輔助網(wǎng)絡(luò)的結(jié)構(gòu)將被舍棄。
2.2.2改進(jìn)(Inception v2)
卷積分解
Inception v2較Inception v1將5x5的大卷積分解成兩個(gè)3x3的小卷積(效仿VGG網(wǎng)絡(luò)的處理方式,減少了參數(shù)量同時(shí)增加網(wǎng)絡(luò)的非線性能力),并加入了BN層:

進(jìn)一步的,Inception v2將nxn卷積分解為兩個(gè)1xn和nx1卷積(空間可分離卷積Spatially Separable Convolution),在感受野相當(dāng)?shù)那闆r下,進(jìn)一步減少了網(wǎng)絡(luò)的參數(shù):


參考:
Inception系列之Inception_v2-v3:https://www.cnblogs.com/wxkang/p/13955363.html
[論文筆記](méi) Xception:https://zhuanlan.zhihu.com/p/127042277
2.2 PyTorch復(fù)現(xiàn)Inception v1:
2.2.1 網(wǎng)絡(luò)的整體框架:

2.2.2 各層的參數(shù)情況:
紅色框表示用于特征降維的1x1卷積

2.2.3 pytorch復(fù)現(xiàn)Inception基礎(chǔ)模塊
將卷積+激活函數(shù)作為一個(gè)基礎(chǔ)的卷積組:
#?將Conv+ReLU封裝成一個(gè)基礎(chǔ)類:
class?BasicConv2d(nn.Module):
????def?__init__(self,?in_channel,?out_channel,?kernel_size,?stride=1,?padding=0):
????????super(BasicConv2d,?self).__init__()
????????self.conv?=?nn.Sequential(
????????????nn.Conv2d(in_channel,?out_channel,?kernel_size,
??????????????????????stride=stride,?padding=padding),
????????????nn.ReLU(True)
????????)
????def?forward(self,?x):
????????x?=?self.conv(x)
????????return?x
構(gòu)造一個(gè)Inception模塊:

#?構(gòu)造Inception基礎(chǔ)模塊:
class?Inception(nn.Module):
????def?__init__(self,?in_dim,?out_1x1,?out_3x3_reduce,?out_3x3,?out_5x5_reduce,?out_5x5,?out_pool):
????????super(Inception,?self).__init__()
????????#?分支1:
????????self.branch_1x1?=?BasicConv2d(in_dim,?out_1x1,?1)
????????#?分支2:
????????self.branch_3x3?=?nn.Sequential(
????????????BasicConv2d(in_dim,?out_3x3_reduce,?1),
????????????BasicConv2d(out_3x3_reduce,?out_3x3,?3,?padding=1),
????????)
????????#?分支3:
????????self.branch_5x5?=?nn.Sequential(
????????????BasicConv2d(in_dim,?out_5x5_reduce,?1),
????????????BasicConv2d(out_5x5_reduce,?out_5x5,?5,?padding=2),
????????)
????????#?分支4:
????????self.branch_pool?=?nn.Sequential(
????????????nn.MaxPool2d(3,?stride=1,?padding=1),
????????????BasicConv2d(in_dim,?out_pool,?1),
????????)
????def?forward(self,?x):
????????b1?=?self.branch_1x1(x)
????????b2?=?self.branch_3x3(x)
????????b3?=?self.branch_5x5(x)
????????b4?=?self.branch_pool(x)
????????output?=?torch.cat((b1,?b2,?b3,?b4),?dim=1)??#?四個(gè)模塊沿特征圖通道方向拼接
????????return?output
搭建完整的Inceptionv1:
#?構(gòu)建Inceptionv1:
class?Inception_v1(nn.Module):
????def?__init__(self,?num_classes=1000,?state="test"):
????????super(Inception_v1,?self).__init__()
????????self.state?=?state
????????self.block1?=?nn.Sequential(
????????????BasicConv2d(3,?64,?7,?stride=2,?padding=3),
????????????nn.MaxPool2d(kernel_size=3,?stride=2,?padding=1),
????????????nn.LocalResponseNorm(64),
????????????BasicConv2d(64,?64,?1),
????????????BasicConv2d(64,?192,?3,?padding=1),
????????????nn.LocalResponseNorm(192),
????????????nn.MaxPool2d(kernel_size=3,?stride=2,?padding=1),
????????)
????????self.block2?=?nn.Sequential(
????????????Inception(192,?64,?96,?128,?16,?32,?32),
????????????Inception(256,?128,?128,?192,?32,?96,?64),
????????????nn.MaxPool2d(kernel_size=3,?stride=2,?padding=1),
????????????Inception(480,?192,?96,?208,?16,?48,?64),
????????)
????????self.block3?=?nn.Sequential(
????????????Inception(512,?160,?112,?224,?24,?64,?64),
????????????Inception(512,?128,?128,?256,?24,?64,?64),
????????????Inception(512,?112,?144,?288,?32,?64,?64),
????????)
????????self.block4?=?nn.Sequential(
????????????nn.MaxPool2d(kernel_size=3,?stride=2,?padding=1),
????????????Inception(528,?256,?160,?320,?32,?128,?128),
????????????Inception(832,?256,?160,?320,?32,?128,?128),
????????????Inception(832,?384,?192,?384,?48,?128,?128),
????????????nn.AvgPool2d(3,?stride=1),
????????)
????????self.classifier?=?nn.Linear(4096,?num_classes)
????????if?state?==?"train":
????????????#?兩個(gè)輔助分類器:
????????????self.aux_classifier1?=?Inception_classify(
????????????????192?+?208?+?48?+?64,?num_classes)
????????????self.aux_classifier2?=?Inception_classify(
????????????????112?+?288?+?64?+?64,?num_classes)
????def?forward(self,?x):
????????x?=?self.block1(x)
????????x?=?self.block2(x)
????????#?插入輔助分類層1
????????if?self.state?==?'train':
????????????aux1?=?self.aux_classifier1(x)
????????x?=?self.block3(x)
????????#?插入輔助分類層2
????????if?self.state?==?'train':
????????????aux2?=?self.aux_classifier2(x)
????????x?=?self.block4(x)
????????x?=?x.view(x.size(0),?-1)
????????out?=?self.classifier(x)
????????if?self.state?==?'train':
????????????return?aux1,?aux2,?out
????????else:
????????????return?out
2.2.4 小Tips
在構(gòu)建比較復(fù)雜的網(wǎng)絡(luò)時(shí),將網(wǎng)絡(luò)重疊使用的一些基礎(chǔ)模塊封裝為一個(gè)基礎(chǔ)的類(層次分明)。
3.ResNet
在以往的經(jīng)驗(yàn)上,人們普遍認(rèn)為通過(guò)加深網(wǎng)絡(luò)的層數(shù)能夠使得網(wǎng)絡(luò)具有更強(qiáng)的學(xué)習(xí)能力,即使網(wǎng)絡(luò)容易產(chǎn)生過(guò)擬合/梯度消失的問(wèn)題,在現(xiàn)有方法下也可以通過(guò)增加數(shù)據(jù)集,Dropout或者正則化/加入BN層解決。但是通過(guò)實(shí)驗(yàn)數(shù)據(jù)發(fā)現(xiàn),即使加入有效的措施抑制網(wǎng)絡(luò)產(chǎn)生過(guò)擬合或者梯度消失,網(wǎng)絡(luò)的精度也會(huì)隨著深度的增加而下降,并且還不是由于過(guò)擬合引起的(實(shí)驗(yàn)數(shù)據(jù)表明越深的網(wǎng)絡(luò)training loss反而越高)

事實(shí)上,阻礙網(wǎng)絡(luò)向深度發(fā)展的一個(gè)主要因素就是梯度不能得到有效的傳播,越深的網(wǎng)絡(luò)反傳過(guò)程中的梯度相關(guān)性會(huì)越來(lái)越差,接近于白噪聲,導(dǎo)致梯度的更新也相當(dāng)于隨機(jī)擾動(dòng)。
Resnet到底在解決一個(gè)什么問(wèn)題呢?https://www.zhihu.com/question/64494691
打個(gè)形象的比喻,就如我們小時(shí)候玩過(guò)的口傳悄悄話游戲,隨著參與人數(shù)的增多,最后一個(gè)人口中說(shuō)出的信息往往早已和原先紙條上的信息大相徑庭。
3.1 改進(jìn)
以往的瓶頸:深度網(wǎng)絡(luò)不可控的梯度消失,深層網(wǎng)絡(luò)與淺層網(wǎng)絡(luò)的梯度相關(guān)性下降,網(wǎng)絡(luò)難以訓(xùn)練。
ResNet的改進(jìn):引入了一個(gè)殘差映射的結(jié)構(gòu)來(lái)解決網(wǎng)絡(luò)退化的問(wèn)題:

何為殘差映射?
假設(shè)輸入的特征為x,期望輸出的特征為H(x)。我們知道,對(duì)于一般的神經(jīng)網(wǎng)絡(luò)而言,每一層的目的無(wú)非就是對(duì)輸入x進(jìn)行非線性變換,將特征x映射到盡量趨近H(x),即,網(wǎng)絡(luò)需要直接擬合輸出H(x).
而對(duì)于殘差映射,模塊中通過(guò)引入一個(gè)shortcut分支(恒等映射),將網(wǎng)絡(luò)需要擬合的映射變?yōu)闅埐頕(x):F(x) = H(x) - x.
作者在論文中假設(shè):相較于直接優(yōu)化H(x),優(yōu)化殘差映射F(x)能有效緩解反向傳播過(guò)程中的梯度消失問(wèn)題,解決了深度網(wǎng)絡(luò)不可訓(xùn)練的困難:[Resnet-50網(wǎng)絡(luò)結(jié)構(gòu)詳解]https://www.cnblogs.com/qianchaomoon/p/12315906.html
3.2 PyTorch 復(fù)現(xiàn) ResNet-50
3.2.1 ResNet-50網(wǎng)絡(luò)整體架構(gòu)

3.2.2 Bottleneck結(jié)構(gòu)
論文中將Resnet-50分成了4個(gè)大的卷積組,每一個(gè)大的卷積組叫做一個(gè)Bottleneck(瓶頸)模塊(輸入和輸出的特征圖通道較多,中間的卷積層特征深度較淺,類似瓶頸的中間小兩頭大的結(jié)構(gòu))。卷積組與卷積組之間會(huì)通過(guò)一個(gè)shortcut相連。
左:非瓶頸結(jié)構(gòu),右:瓶頸結(jié)構(gòu)

值得注意的是,ResNet使用Bottleneck結(jié)構(gòu)主要是是為了減小網(wǎng)絡(luò)的參數(shù)量(特征降維),在實(shí)際中作者注意到,瓶頸結(jié)構(gòu)的使用同樣出現(xiàn)了普通網(wǎng)絡(luò)的退化問(wèn)題:

3.2.3 ResNet-50圖解及各層參數(shù)細(xì)節(jié)
對(duì)于F(x)+x,ResNet采取的是逐通道相加的形式,因此在相加時(shí)需要考慮兩者的通道數(shù)是否相同,相同的情況直接相加即可(圖實(shí)線處),若兩者通道不同,需要用1x1卷積對(duì)特征進(jìn)行升維,將通道數(shù)變?yōu)橄嗤?圖虛線處):

3.2.4 實(shí)現(xiàn)一個(gè)Bottleneck模塊:
#?將Conv+BN封裝成一個(gè)基礎(chǔ)卷積類:
class?BasicConv2d(nn.Module):
????def?__init__(self,?in_channel,?out_channel,?kernel_size,?stride=1,?padding=0):
????????super(BasicConv2d,?self).__init__()
????????self.conv?=?nn.Sequential(
????????????nn.Conv2d(in_channel,?out_channel,?kernel_size,
??????????????????????stride=stride,?padding=padding,?bias=False),
????????????nn.BatchNorm2d(out_channel)
????????)
????def?forward(self,?x):
????????x?=?self.conv(x)
????????return?x
#?一個(gè)Bottleneck模塊:
class?Bottleneck(nn.Module):
????def?__init__(self,?in_channel,?mid_channel,?out_channel,?stride=1):
????????super(Bottleneck,?self).__init__()
????????self.judge?=?in_channel?==?out_channel
????????self.bottleneck?=?nn.Sequential(
????????????BasicConv2d(in_channel,?mid_channel,?1),
????????????nn.ReLU(True),
????????????BasicConv2d(mid_channel,?mid_channel,?3,?padding=1,?stride=stride),
????????????nn.ReLU(True),
????????????BasicConv2d(mid_channel,?out_channel,?1),
????????)
????????self.relu?=?nn.ReLU(True)
????????#?下采樣部分由一個(gè)包含BN層的1x1卷積構(gòu)成:
????????if?in_channel?!=?out_channel:
????????????self.downsample?=?BasicConv2d(
????????????????in_channel,?out_channel,?1,?stride=stride)
????def?forward(self,?x):
????????out?=?self.bottleneck(x)
????????#?若通道不一致需使用1x1卷積下采樣
????????if?not?self.judge:
????????????self.identity?=?self.downsample(x)
????????????#?殘差+恒等映射=輸出
????????????out?+=?self.identity
????????#?否則直接相加
????????else:
????????????out?+=?x
????????out?=?self.relu(out)
????????return?out
3.2.5 實(shí)現(xiàn)resnet-50
#?Resnet50:
class?ResNet_50(nn.Module):
????def?__init__(self,?class_num):
????????super(ResNet_50,?self).__init__()
????????self.conv?=?BasicConv2d(3,?64,?7,?stride=2,?padding=3)
????????self.maxpool?=?nn.MaxPool2d(3,?stride=2,?padding=1)
????????#?卷積組1
????????self.block1?=?nn.Sequential(
????????????Bottleneck(64,?64,?256),
????????????Bottleneck(256,?64,?256),
????????????Bottleneck(256,?64,?256),
????????)
????????#?卷積組2
????????self.block2?=?nn.Sequential(
????????????Bottleneck(256,?128,?512,?stride=2),
????????????Bottleneck(512,?128,?512),
????????????Bottleneck(512,?128,?512),
????????????Bottleneck(512,?128,?512),
????????)
????????#?卷積組3
????????self.block3?=?nn.Sequential(
????????????Bottleneck(512,?256,?1024,?stride=2),
????????????Bottleneck(1024,?256,?1024),
????????????Bottleneck(1024,?256,?1024),
????????????Bottleneck(1024,?256,?1024),
????????????Bottleneck(1024,?256,?1024),
????????????Bottleneck(1024,?256,?1024),
????????)
????????#?卷積組4
????????self.block4?=?nn.Sequential(
????????????Bottleneck(1024,?512,?2048,?stride=2),
????????????Bottleneck(2048,?512,?2048),
????????????Bottleneck(2048,?512,?2048),
????????)
????????self.avgpool?=?nn.AvgPool2d(4)
????????self.classifier?=?nn.Linear(2048,?class_num)
????def?forward(self,?x):
????????x?=?self.conv(x)
????????x?=?self.maxpool(x)
????????x?=?self.block1(x)
????????x?=?self.block2(x)
????????x?=?self.block3(x)
????????x?=?self.block4(x)
????????x?=?self.avgpool(x)
????????x?=?x.view(x.size(0),?-1)
????????out?=?self.classifier(x)
????????return?out
4.FPN(特征金字塔)
4.1 特征的語(yǔ)義信息
對(duì)于CNN網(wǎng)絡(luò),圖像通過(guò)網(wǎng)絡(luò)淺層的卷積層,輸出的特征圖往往只能表示一些簡(jiǎn)單的語(yǔ)義信息(比如一些簡(jiǎn)單的線條),越深層的網(wǎng)絡(luò),提取特征表示的語(yǔ)義信息也就越復(fù)雜(從一些紋理,到一些類別具有的相似輪廓):(圖中的特征有經(jīng)過(guò)反卷積上采樣)

因此,傳統(tǒng)的檢測(cè)網(wǎng)絡(luò)通常只在最后一個(gè)卷積輸出上的高層語(yǔ)義特征圖進(jìn)行后續(xù)的步驟,但這也不可避免的存在一些問(wèn)題。
4.2 改進(jìn)
我們知道,越是深層的網(wǎng)絡(luò),特征的下采樣率也就越高,即深層的特征圖一個(gè)像素就對(duì)應(yīng)淺層特征的一片區(qū)域,這對(duì)于大目標(biāo)的檢測(cè)不會(huì)造成太大的影響。但對(duì)于圖像上的小目標(biāo),在深層特征上的有效信息較少,導(dǎo)致網(wǎng)絡(luò)對(duì)于小物體的檢測(cè)性能急劇下降,這種現(xiàn)象也被稱作多尺度問(wèn)題。
基于多尺度問(wèn)題,一個(gè)直接的解決辦法便是利用圖像金字塔,將原始的輸入變換為多張不同尺寸的多尺度圖像,將這些圖像分別進(jìn)行特征提取,生成多尺度的特征后再進(jìn)行后續(xù)的處理,這樣一來(lái),在小尺度的特征上檢測(cè)到小目標(biāo)的幾率就大大增加。這種方法簡(jiǎn)單有效,曾大量在COCO目標(biāo)檢測(cè)競(jìng)賽上使用。但這種方法的缺點(diǎn)就在于計(jì)算量大,需要消耗大量的時(shí)間。
對(duì)此,FPN網(wǎng)絡(luò)(Feature Pyramid Networks)針對(duì)這一問(wèn)題改進(jìn)了提取多尺度特征的方法?;?.1的介紹我們知道,卷積網(wǎng)絡(luò)不同層提取的特征尺寸各不相同,本身就類似于一個(gè)金字塔的結(jié)構(gòu),同時(shí),每一層的語(yǔ)義信息也各不相同,越淺的特征語(yǔ)義信息越簡(jiǎn)單,顯示的細(xì)節(jié)也就越多,越深層的特征顯示的細(xì)節(jié)越少,語(yǔ)義信息越高級(jí)?;诖?,FPN網(wǎng)絡(luò)在特征提取的過(guò)程中融合了不同卷積層的特征,較好的改善了多尺度檢測(cè)問(wèn)題。
4.3 PyTorch 復(fù)現(xiàn) FPN
4.3.1 FPN網(wǎng)絡(luò)架構(gòu)

FPN網(wǎng)絡(luò)主要包含四個(gè)部分,自下而上網(wǎng)絡(luò),自上而下網(wǎng)絡(luò),橫向連接與卷積融合。

自下而上網(wǎng)絡(luò)(提供不同尺度的特征):
最左側(cè)為普通的特征提取卷積網(wǎng)絡(luò)(ResNet),C2-C4代表resnet中的四個(gè)大的卷積組,包含了多個(gè)Bottleneck結(jié)構(gòu),原始圖像的輸入就從該結(jié)構(gòu)開(kāi)始。
自上而下網(wǎng)絡(luò)(提供高層語(yǔ)義特征): 在這一結(jié)構(gòu)中,首先對(duì)C5進(jìn)行1x1卷積降低通道數(shù)得到M5,接著依次上采樣得到M4,M3,M2.目的是得到與C4,C3,C2相同尺寸但不同語(yǔ)義的特征。方便特征的融合(融合的方式為逐元素相加)。
值得注意的是,在網(wǎng)絡(luò)的上采樣過(guò)程中采用的不是反卷積或者非線性插值方法,而是普通的2倍最鄰近上采樣(可以最大程度保留特征圖的語(yǔ)義信息,得到既有良好的空間信息又有較強(qiáng)烈的語(yǔ)義信息的特征圖。):【論文筆記】FPN —— 特征金字塔:https://zhuanlan.zhihu.com/p/92005927

橫向連接:
將高層的語(yǔ)義特征與淺層的細(xì)節(jié)特征相融合(中途使用1x1卷積使得兩者的通道數(shù)相同)
卷積融合:
得到相加的特征后,再利用3x3卷積對(duì)M2-M4進(jìn)一步融合(論文表示這么做可以消除上采樣帶來(lái)的重疊效應(yīng))
4.3.2 復(fù)現(xiàn)FPN網(wǎng)絡(luò)
#?導(dǎo)入resnet50
resnet?=?models.resnet50(pretrained=True)
#?分塊,?以便提取不同深度網(wǎng)絡(luò)的特征
layer1?=?nn.Sequential(
????resnet.conv1,
????resnet.bn1,
????resnet.relu,
????resnet.maxpool,
)
layer2?=?resnet.layer1
layer3?=?resnet.layer2
layer4?=?resnet.layer3
layer5?=?resnet.layer4
class?FPN(nn.Module):
????def?__init__(self):
????????super(FPN,?self).__init__()
????????#?3x3?卷積融合特征
????????self.MtoP?=?nn.Conv2d(256,?256,?3,?1,?1)
????????#?橫向連接,?使用1x1卷積降維
????????self.C2toM2?=?nn.Conv2d(256,?256,?1,?1,?0)
????????self.C3toM3?=?nn.Conv2d(512,?256,?1,?1,?0)
????????self.C4toM4?=?nn.Conv2d(1024,?256,?1,?1,?0)
????????self.C5toM5?=?nn.Conv2d(2048,?256,?1,?1,?0)
????#?特征融合方法
????def?_upsample_add(self,?in_C,?in_M):
????????H?=?in_M.shape[2]
????????W?=?in_M.shape[3]
????????#?最鄰近上采樣方法
????????return?F.upsample_bilinear(in_C,?size=(H,?W))?+?in_M
????def?forward(self,?x):
????????#?自下而上
????????C1?=?layer1(x)
????????C2?=?layer2(C1)
????????C3?=?layer3(C2)
????????C4?=?layer4(C3)
????????C5?=?layer5(C4)
????????#?自上而下+橫向連接
????????M5?=?self.C5toM5(C5)
????????M4?=?self._upsample_add(M5,?self.C4toM4(C4))
????????M3?=?self._upsample_add(M4,?self.C3toM3(C3))
????????M2?=?self._upsample_add(M3,?self.C2toM2(C2))
????????#?卷積融合
????????P5?=?self.MtoP(M5)
????????P4?=?self.MtoP(M4)
????????P3?=?self.MtoP(M3)
????????P2?=?self.MtoP(M2)
????????#?返回的是多尺度特征
????????return?P2,?P3,?P4,?P5
如發(fā)現(xiàn)本文中存在任何錯(cuò)誤或是有不解的地方,歡迎在評(píng)論區(qū)留言~
往期精彩回顧
適合初學(xué)者入門(mén)人工智能的路線及資料下載 (圖文+視頻)機(jī)器學(xué)習(xí)入門(mén)系列下載 中國(guó)大學(xué)慕課《機(jī)器學(xué)習(xí)》(黃海廣主講) 機(jī)器學(xué)習(xí)及深度學(xué)習(xí)筆記等資料打印 《統(tǒng)計(jì)學(xué)習(xí)方法》的代碼復(fù)現(xiàn)專輯 機(jī)器學(xué)習(xí)交流qq群955171419,加入微信群請(qǐng)掃碼:
