基于PyTorch的卷積神經(jīng)網(wǎng)絡(luò)經(jīng)典BackBone(骨干網(wǎng)絡(luò))復(fù)現(xiàn)
共 30248字,需瀏覽 61分鐘
·
2024-07-29 10:05
點(diǎn)擊上方“小白學(xué)視覺(jué)”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時(shí)間送達(dá)
導(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)了提取多尺度特征的方法。基于4.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
下載1:OpenCV-Contrib擴(kuò)展模塊中文版教程
在「小白學(xué)視覺(jué)」公眾號(hào)后臺(tái)回復(fù):擴(kuò)展模塊中文教程,即可下載全網(wǎng)第一份OpenCV擴(kuò)展模塊教程中文版,涵蓋擴(kuò)展模塊安裝、SFM算法、立體視覺(jué)、目標(biāo)跟蹤、生物視覺(jué)、超分辨率處理等二十多章內(nèi)容。
下載2:Python視覺(jué)實(shí)戰(zhàn)項(xiàng)目52講 在「小白學(xué)視覺(jué)」公眾號(hào)后臺(tái)回復(fù):Python視覺(jué)實(shí)戰(zhàn)項(xiàng)目,即可下載包括圖像分割、口罩檢測(cè)、車道線檢測(cè)、車輛計(jì)數(shù)、添加眼線、車牌識(shí)別、字符識(shí)別、情緒檢測(cè)、文本內(nèi)容提取、面部識(shí)別等31個(gè)視覺(jué)實(shí)戰(zhàn)項(xiàng)目,助力快速學(xué)校計(jì)算機(jī)視覺(jué)。
下載3:OpenCV實(shí)戰(zhàn)項(xiàng)目20講 在「小白學(xué)視覺(jué)」公眾號(hào)后臺(tái)回復(fù):OpenCV實(shí)戰(zhàn)項(xiàng)目20講,即可下載含有20個(gè)基于OpenCV實(shí)現(xiàn)20個(gè)實(shí)戰(zhàn)項(xiàng)目,實(shí)現(xiàn)OpenCV學(xué)習(xí)進(jìn)階。
交流群
歡迎加入公眾號(hào)讀者群一起和同行交流,目前有SLAM、三維視覺(jué)、傳感器、自動(dòng)駕駛、計(jì)算攝影、檢測(cè)、分割、識(shí)別、醫(yī)學(xué)影像、GAN、算法競(jìng)賽等微信群(以后會(huì)逐漸細(xì)分),請(qǐng)掃描下面微信號(hào)加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺(jué)SLAM“。請(qǐng)按照格式備注,否則不予通過(guò)。添加成功后會(huì)根據(jù)研究方向邀請(qǐng)進(jìn)入相關(guān)微信群。請(qǐng)勿在群內(nèi)發(fā)送廣告,否則會(huì)請(qǐng)出群,謝謝理解~
