pytorch實(shí)現(xiàn)ResNet結(jié)構(gòu)的實(shí)例代碼
1.ResNet的創(chuàng)新
現(xiàn)在重新稍微系統(tǒng)的介紹一下ResNet網(wǎng)絡(luò)結(jié)構(gòu)。 ResNet結(jié)構(gòu)首先通過一個(gè)卷積層然后有一個(gè)池化層,然后通過一系列的殘差結(jié)構(gòu),最后再通過一個(gè)平均池化下采樣操作,以及一個(gè)全連接層的得到了一個(gè)輸出。ResNet網(wǎng)絡(luò)可以達(dá)到很深的層數(shù)的原因就是不斷的堆疊殘差結(jié)構(gòu)而來(lái)的。
1)亮點(diǎn)
網(wǎng)絡(luò)中的亮點(diǎn) :
- 超深的網(wǎng)絡(luò)結(jié)構(gòu)( 突破1000 層)
- 提出residual 模塊
- 使用Batch Normalization 加速訓(xùn)練( 丟棄dropout)
但是,一般來(lái)說,并不是一直的加深神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)就會(huì)得到一個(gè)更好的結(jié)果,一般太深的網(wǎng)絡(luò)會(huì)出現(xiàn)過擬合的現(xiàn)象嚴(yán)重,可能還沒有一些淺層網(wǎng)絡(luò)要好。

2)原因
其中有兩個(gè)原因:
- 梯度消失或梯度爆炸
當(dāng)層數(shù)過多的時(shí)候,假設(shè)每一層的誤差梯度都是一個(gè)小于1的數(shù)值,當(dāng)進(jìn)行方向傳播的過程中,每向前傳播一層,都要乘以一個(gè)小于1的誤差梯度,當(dāng)網(wǎng)絡(luò)越來(lái)越深時(shí),所成的小于1的系數(shù)也就越來(lái)越多,此時(shí)梯度便越趨近于0,這樣梯度便會(huì)越來(lái)越小。這便會(huì)造成梯度消失的現(xiàn)象。
而當(dāng)所成的誤差梯度是一個(gè)大于1的系數(shù),而隨著網(wǎng)絡(luò)層數(shù)的加深,梯度便會(huì)越來(lái)越大,這便會(huì)造成梯度爆炸的現(xiàn)象。
- 退化問題(degradation problem)
當(dāng)解決了梯度消失或者梯度爆炸的問題之后,其實(shí)網(wǎng)絡(luò)的效果可能還是不盡如意,還可能有退化問題。為此,ResNet提出了殘差結(jié)構(gòu)來(lái)解決這個(gè)退化問題。 也正是因?yàn)橛羞@個(gè)殘差的結(jié)構(gòu),所以才可以搭建這么深的網(wǎng)絡(luò)。

2.ResNet的結(jié)構(gòu)
殘差結(jié)構(gòu)如圖所示

作圖是針對(duì)ResNet-18/34層淺層網(wǎng)絡(luò)的結(jié)構(gòu),右圖是ResNet-50/101/152層深層網(wǎng)絡(luò)的結(jié)構(gòu),其中注意:主分支與shortcut 的輸出特征矩陣shape。
一下表格為網(wǎng)絡(luò)的一些主要參數(shù)

可以看見,不同層數(shù)的網(wǎng)絡(luò)結(jié)構(gòu)其實(shí)框架是類似的,不同的至少堆疊的殘差結(jié)構(gòu)的數(shù)量。
1)淺層的殘差結(jié)構(gòu)

需要注意,有些殘差結(jié)構(gòu)的ShortCut是實(shí)線,而有的是虛線,這兩者是不同的。對(duì)于左圖來(lái)說,ShortCut是實(shí)線,這表明輸入與輸出的shape是一樣的,所以可以直接的進(jìn)行相加。而對(duì)于右圖來(lái)說,其輸入的shape與輸出的shape是不一樣的,這時(shí)候需要調(diào)整步長(zhǎng)stribe與kernel size來(lái)使得兩條路(主分支與捷徑分支)所處理好的shape是一模一樣的。
2)深層的殘差結(jié)構(gòu)

同樣的,需要注意,主分支與shortcut 的輸出特征矩陣shape必須相同,同樣的通過步長(zhǎng)來(lái)調(diào)整。
但是注意原論文中:
右側(cè)虛線殘差結(jié)構(gòu)的主分支上、第一個(gè)1x1卷積層的步距是2,第二個(gè)3x3卷積層的步距是1.
而在pytorch官方實(shí)現(xiàn)的過程中是第一個(gè)1x1卷積層的步距是1,第二個(gè)3x3卷積層步距是2,這樣能夠在ImageNet的top1上提升大概0.5%的準(zhǔn)確率。
所以在conv3_x,conv4_x,conv5_x中所對(duì)應(yīng)的殘差結(jié)構(gòu)的第一層,都是指虛線的殘差結(jié)構(gòu),其他的殘差結(jié)構(gòu)是實(shí)線的殘差結(jié)構(gòu)。
3)總結(jié)
對(duì)于每個(gè)大模塊中的第一個(gè)殘差結(jié)構(gòu),需要通過虛線分支來(lái)調(diào)整殘差結(jié)構(gòu)的輸入與輸出是同一個(gè)shape。此時(shí)使用了下采樣的操作函數(shù)。
對(duì)于每個(gè)大模塊中的其他剩余的殘差結(jié)構(gòu),只需要通過實(shí)線分支來(lái)調(diào)整殘差網(wǎng)絡(luò)結(jié)構(gòu),因?yàn)槠漭敵龊洼斎氡旧砭褪峭粋€(gè)shape的。
對(duì)于第一個(gè)大模塊的第一個(gè)殘差結(jié)構(gòu),其第二個(gè)3x3的卷積中,步長(zhǎng)是1的,而其他的三個(gè)大模塊的步長(zhǎng)均為2.
在每一個(gè)大模塊的維度變換中,主要是第一個(gè)殘差結(jié)構(gòu)使得shape減半,而模塊中其他的殘差結(jié)構(gòu)都是沒有改變shape的。也真因?yàn)闆]有改變shape,所以這些殘差結(jié)構(gòu)才可以直接的通過實(shí)線進(jìn)行相加。
3.Batch Normalization
Batch Normalization的目的是使我們的一批(Batch)特征矩陣feature map滿足均值為0,方差為1的分布規(guī)律。

其中:
μ,σ_2在正向傳播過程中統(tǒng)計(jì)得到
γ,β在反向傳播過程中訓(xùn)練得到
Batch Normalization是google團(tuán)隊(duì)在2015年論文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。通過該方法能夠加速網(wǎng)絡(luò)的收斂并提升準(zhǔn)確率。
具體的相關(guān)原理見:Batch Normalization詳解以及pytorch實(shí)驗(yàn)
4.參考代碼
import torch
import torch.nn as nn
# 分類數(shù)目
num_class = 5
# 各層數(shù)目
resnet18_params = [2, 2, 2, 2]
resnet34_params = [3, 4, 6, 3]
resnet50_params = [3, 4, 6, 3]
resnet101_params = [3, 4, 23, 3]
resnet152_params = [3, 8, 36, 3]
# 定義Conv1層
def Conv1(in_planes, places, stride=2):
return nn.Sequential(
nn.Conv2d(in_channels=in_planes,out_channels=places,kernel_size=7,stride=stride,padding=3, bias=False),
nn.BatchNorm2d(places),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
)
# 淺層的殘差結(jié)構(gòu)
class BasicBlock(nn.Module):
def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 1):
super(BasicBlock,self).__init__()
self.expansion = expansion
self.downsampling = downsampling
# torch.Size([1, 64, 56, 56]), stride = 1
# torch.Size([1, 128, 28, 28]), stride = 2
# torch.Size([1, 256, 14, 14]), stride = 2
# torch.Size([1, 512, 7, 7]), stride = 2
self.basicblock = nn.Sequential(
nn.Conv2d(in_channels=in_places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(places),
nn.ReLU(inplace=True),
nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(places * self.expansion),
)
# torch.Size([1, 64, 56, 56])
# torch.Size([1, 128, 28, 28])
# torch.Size([1, 256, 14, 14])
# torch.Size([1, 512, 7, 7])
# 每個(gè)大模塊的第一個(gè)殘差結(jié)構(gòu)需要改變步長(zhǎng)
if self.downsampling:
self.downsample = nn.Sequential(
nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(places*self.expansion)
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
# 實(shí)線分支
residual = x
out = self.basicblock(x)
# 虛線分支
if self.downsampling:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
# 深層的殘差結(jié)構(gòu)
class Bottleneck(nn.Module):
# 注意:默認(rèn) downsampling=False
def __init__(self,in_places,places, stride=1,downsampling=False, expansion = 4):
super(Bottleneck,self).__init__()
self.expansion = expansion
self.downsampling = downsampling
self.bottleneck = nn.Sequential(
# torch.Size([1, 64, 56, 56]),stride=1
# torch.Size([1, 128, 56, 56]),stride=1
# torch.Size([1, 256, 28, 28]), stride=1
# torch.Size([1, 512, 14, 14]), stride=1
nn.Conv2d(in_channels=in_places,out_channels=places,kernel_size=1,stride=1, bias=False),
nn.BatchNorm2d(places),
nn.ReLU(inplace=True),
# torch.Size([1, 64, 56, 56]),stride=1
# torch.Size([1, 128, 28, 28]), stride=2
# torch.Size([1, 256, 14, 14]), stride=2
# torch.Size([1, 512, 7, 7]), stride=2
nn.Conv2d(in_channels=places, out_channels=places, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(places),
nn.ReLU(inplace=True),
# torch.Size([1, 256, 56, 56]),stride=1
# torch.Size([1, 512, 28, 28]), stride=1
# torch.Size([1, 1024, 14, 14]), stride=1
# torch.Size([1, 2048, 7, 7]), stride=1
nn.Conv2d(in_channels=places, out_channels=places * self.expansion, kernel_size=1, stride=1, bias=False),
nn.BatchNorm2d(places * self.expansion),
)
# torch.Size([1, 256, 56, 56])
# torch.Size([1, 512, 28, 28])
# torch.Size([1, 1024, 14, 14])
# torch.Size([1, 2048, 7, 7])
if self.downsampling:
self.downsample = nn.Sequential(
nn.Conv2d(in_channels=in_places, out_channels=places*self.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(places*self.expansion)
)
self.relu = nn.ReLU(inplace=True)
def forward(self, x):
# 實(shí)線分支
residual = x
out = self.bottleneck(x)
# 虛線分支
if self.downsampling:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
class ResNet(nn.Module):
def __init__(self,blocks, blockkinds, num_classes=num_class):
super(ResNet,self).__init__()
self.blockkinds = blockkinds
self.conv1 = Conv1(in_planes = 3, places= 64)
# 對(duì)應(yīng)淺層網(wǎng)絡(luò)結(jié)構(gòu)
if self.blockkinds == BasicBlock:
self.expansion = 1
# 64 -> 64
self.layer1 = self.make_layer(in_places=64, places=64, block=blocks[0], stride=1)
# 64 -> 128
self.layer2 = self.make_layer(in_places=64, places=128, block=blocks[1], stride=2)
# 128 -> 256
self.layer3 = self.make_layer(in_places=128, places=256, block=blocks[2], stride=2)
# 256 -> 512
self.layer4 = self.make_layer(in_places=256, places=512, block=blocks[3], stride=2)
self.fc = nn.Linear(512, num_classes)
# 對(duì)應(yīng)深層網(wǎng)絡(luò)結(jié)構(gòu)
if self.blockkinds == Bottleneck:
self.expansion = 4
# 64 -> 64
self.layer1 = self.make_layer(in_places = 64, places= 64, block=blocks[0], stride=1)
# 256 -> 128
self.layer2 = self.make_layer(in_places = 256,places=128, block=blocks[1], stride=2)
# 512 -> 256
self.layer3 = self.make_layer(in_places=512,places=256, block=blocks[2], stride=2)
# 1024 -> 512
self.layer4 = self.make_layer(in_places=1024,places=512, block=blocks[3], stride=2)
self.fc = nn.Linear(2048, num_classes)
self.avgpool = nn.AvgPool2d(7, stride=1)
# 初始化網(wǎng)絡(luò)結(jié)構(gòu)
for m in self.modules():
if isinstance(m, nn.Conv2d):
# 采用了何凱明的初始化方法
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
def make_layer(self, in_places, places, block, stride):
layers = []
# torch.Size([1, 64, 56, 56]) -> torch.Size([1, 256, 56, 56]), stride=1 故w,h不變
# torch.Size([1, 256, 56, 56]) -> torch.Size([1, 512, 28, 28]), stride=2 故w,h變
# torch.Size([1, 512, 28, 28]) -> torch.Size([1, 1024, 14, 14]),stride=2 故w,h變
# torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 2048, 7, 7]), stride=2 故w,h變
# 此步需要通過虛線分支,downsampling=True
layers.append(self.blockkinds(in_places, places, stride, downsampling =True))
# torch.Size([1, 256, 56, 56]) -> torch.Size([1, 256, 56, 56])
# torch.Size([1, 512, 28, 28]) -> torch.Size([1, 512, 28, 28])
# torch.Size([1, 1024, 14, 14]) -> torch.Size([1, 1024, 14, 14])
# torch.Size([1, 2048, 7, 7]) -> torch.Size([1, 2048, 7, 7])
# print("places*self.expansion:", places*self.expansion)
# print("block:", block)
# 此步需要通過實(shí)線分支,downsampling=False, 每個(gè)大模塊的第一個(gè)殘差結(jié)構(gòu)需要改變步長(zhǎng)
for i in range(1, block):
layers.append(self.blockkinds(places*self.expansion, places))
return nn.Sequential(*layers)
def forward(self, x):
# conv1層
x = self.conv1(x) # torch.Size([1, 64, 56, 56])
# conv2_x層
x = self.layer1(x) # torch.Size([1, 256, 56, 56])
# conv3_x層
x = self.layer2(x) # torch.Size([1, 512, 28, 28])
# conv4_x層
x = self.layer3(x) # torch.Size([1, 1024, 14, 14])
# conv5_x層
x = self.layer4(x) # torch.Size([1, 2048, 7, 7])
x = self.avgpool(x) # torch.Size([1, 2048, 1, 1]) / torch.Size([1, 512])
x = x.view(x.size(0), -1) # torch.Size([1, 2048]) / torch.Size([1, 512])
x = self.fc(x) # torch.Size([1, 5])
return x
def ResNet18():
return ResNet(resnet18_params, BasicBlock)
def ResNet34():
return ResNet(resnet34_params, BasicBlock)
def ResNet50():
return ResNet(resnet50_params, Bottleneck)
def ResNet101():
return ResNet(resnet101_params, Bottleneck)
def ResNet152():
return ResNet(resnet152_params, Bottleneck)
if __name__=='__main__':
# model = torchvision.models.resnet50()
# 模型測(cè)試
# model = ResNet18()
# model = ResNet34()
# model = ResNet50()
# model = ResNet101()
model = ResNet152()
# print(model)
input = torch.randn(1, 3, 224, 224)
out = model(input)
print(out.shape)
以上就是pytorch實(shí)現(xiàn)ResNet結(jié)構(gòu)的實(shí)例代碼的詳細(xì)內(nèi)容,更多關(guān)于pytorch ResNet結(jié)構(gòu)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python celery分布式任務(wù)隊(duì)列的使用詳解
這篇文章主要介紹了python celery分布式任務(wù)隊(duì)列的使用詳解,Celery 是一個(gè) 基于python開發(fā)的分布式異步消息任務(wù)隊(duì)列,通過它可以輕松的實(shí)現(xiàn)任務(wù)的異步處理, 如果你的業(yè)務(wù)場(chǎng)景中需要用到異步任務(wù),就可以考慮使用celery,需要的朋友可以參考下2019-07-07
解決谷歌搜索技術(shù)文章時(shí)打不開網(wǎng)頁(yè)問題的python腳本
在用谷歌在搜索技術(shù)文章時(shí),總是時(shí)不時(shí)的打不開網(wǎng)頁(yè),于是寫了一個(gè)python腳本,感覺用著還行,分享給大家2013-02-02
Python selenium鍵盤鼠標(biāo)事件實(shí)現(xiàn)過程詳解
這篇文章主要介紹了Python selenium鍵盤鼠標(biāo)事件實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Python中import導(dǎo)入上一級(jí)目錄模塊及循環(huán)import問題的解決
Python中的import語(yǔ)句導(dǎo)入模塊相信大家再熟悉不過了,這里我們會(huì)來(lái)講解Python中import導(dǎo)入上一級(jí)目錄模塊及循環(huán)import問題的解決,需要的朋友可以參考下2016-06-06

