關(guān)于ResNet的原理和具體細(xì)節(jié),可參見上篇解讀:經(jīng)典神經(jīng)網(wǎng)絡(luò)論文超詳細(xì)解讀(五)——ResNet(殘差網(wǎng)絡(luò))學(xué)習(xí)筆記(翻譯+精讀+代碼復(fù)現(xiàn))
接下來我們就來復(fù)現(xiàn)一下代碼。
源代碼比較復(fù)雜,感興趣的同學(xué)可以上官網(wǎng)學(xué)習(xí):?
https://github.com/pytorch/vision/tree/master/torchvision
本篇是簡(jiǎn)化版本?
?一、BasicBlock模塊
BasicBlock結(jié)構(gòu)圖如圖所示:
?BasicBlock是基礎(chǔ)版本,主要用來構(gòu)建ResNet18和ResNet34網(wǎng)絡(luò),里面只包含兩個(gè)卷積層,使用了兩個(gè) 3*3 的卷積,通道數(shù)都是64,卷積后接著 BN 和 ReLU。
右邊的曲線就是Shortcut Connections,將輸入x加到輸出。
代碼:
'''-------------一、BasicBlock模塊-----------------------------'''
# 用于ResNet18和ResNet34基本殘差結(jié)構(gòu)塊
class BasicBlock(nn.Module):
def __init__(self, inchannel, outchannel, stride=1):
super(BasicBlock, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True), #inplace=True表示進(jìn)行原地操作,一般默認(rèn)為False,表示新建一個(gè)變量存儲(chǔ)操作
nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(outchannel)
)
self.shortcut = nn.Sequential()
#論文中模型架構(gòu)的虛線部分,需要下采樣
if stride != 1 or inchannel != outchannel:
self.shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(outchannel)
)
def forward(self, x):
out = self.left(x) #這是由于殘差塊需要保留原始輸入
out += self.shortcut(x)#這是ResNet的核心,在輸出上疊加了輸入x
out = F.relu(out)
return out
二、Bottleneck 模塊
Bottleneck結(jié)構(gòu)圖如圖所示:
Bottleneck主要用在ResNet50及以上的網(wǎng)絡(luò)結(jié)構(gòu),與BasicBlock不同的是這里有 3 個(gè)卷積,分別為 1*1,3*3,1*1大小的卷積核,分別用于壓縮維度、卷積處理、恢復(fù)維度。
這里的通道數(shù)是變化的,1*1卷積層的作用就是用于改變特征圖的通數(shù),使得可以和恒等映射x相疊加,另外這里的1*1卷積層改變維度的很重要的一點(diǎn)是可以降低網(wǎng)絡(luò)參數(shù)量,這也是為什么更深層的網(wǎng)絡(luò)采用BottleNeck而不是BasicBlock的原因。
注意:這里outchannel / 4是因?yàn)锽ottleneck層輸出通道都是輸入的4倍
代碼:
'''-------------二、Bottleneck模塊-----------------------------'''
# 用于ResNet50及以上的殘差結(jié)構(gòu)塊
class Bottleneck(nn.Module):
def __init__(self, inchannel, outchannel, stride=1):
super(Bottleneck, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(inchannel, int(outchannel / 4), kernel_size=1, stride=stride, padding=0, bias=False),
nn.BatchNorm2d(int(outchannel / 4)),
nn.ReLU(inplace=True),
nn.Conv2d(int(outchannel / 4), int(outchannel / 4), kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(int(outchannel / 4)),
nn.ReLU(inplace=True),
nn.Conv2d(int(outchannel / 4), outchannel, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(outchannel),
)
self.shortcut = nn.Sequential()
if stride != 1 or inchannel != outchannel:
self.shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(outchannel)
)
def forward(self, x):
out = self.left(x)
y = self.shortcut(x)
out += self.shortcut(x)
out = F.relu(out)
return out
三、ResNet主體
介紹了上述BasicBlock基礎(chǔ)塊和BotteNeck結(jié)構(gòu)后,我們就可以搭建ResNet結(jié)構(gòu)了。
5種不同層數(shù)的ResNet結(jié)構(gòu)圖如圖所示:
文章來源:http://www.zghlxwxcb.cn/news/detail-500118.html
代碼:?
ResNet18
'''----------ResNet18----------'''
class ResNet_18(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_18, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 64, 2, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)
self.fc = nn.Linear(512, num_classes)
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
ResNet34
'''----------ResNet34----------'''
class ResNet_34(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_34, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 64, 3, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 128, 4, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 256, 6, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 512, 3, stride=2)
self.fc = nn.Linear(512, num_classes)
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
ResNet50
'''---------ResNet50--------'''
class ResNet_50(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_50, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 256, 3, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 512, 4, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 1024, 6, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 2048, 3, stride=2)
self.fc = nn.Linear(512 * 4, num_classes)
# **************************
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
# print(out.size())
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
四、完整代碼
import torch
import torch.nn as nn
import torch.nn.functional as F
'''-------------一、BasicBlock模塊-----------------------------'''
# 用于ResNet18和ResNet34基本殘差結(jié)構(gòu)塊
class BasicBlock(nn.Module):
def __init__(self, inchannel, outchannel, stride=1):
super(BasicBlock, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True), #inplace=True表示進(jìn)行原地操作,一般默認(rèn)為False,表示新建一個(gè)變量存儲(chǔ)操作
nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(outchannel)
)
self.shortcut = nn.Sequential()
#論文中模型架構(gòu)的虛線部分,需要下采樣
if stride != 1 or inchannel != outchannel:
self.shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(outchannel)
)
def forward(self, x):
out = self.left(x) #這是由于殘差塊需要保留原始輸入
out += self.shortcut(x)#這是ResNet的核心,在輸出上疊加了輸入x
out = F.relu(out)
return out
'''-------------二、Bottleneck模塊-----------------------------'''
# 用于ResNet50及以上的殘差結(jié)構(gòu)塊
class Bottleneck(nn.Module):
def __init__(self, inchannel, outchannel, stride=1):
super(Bottleneck, self).__init__()
self.left = nn.Sequential(
nn.Conv2d(inchannel, int(outchannel / 4), kernel_size=1, stride=stride, padding=0, bias=False),
nn.BatchNorm2d(int(outchannel / 4)),
nn.ReLU(inplace=True),
nn.Conv2d(int(outchannel / 4), int(outchannel / 4), kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(int(outchannel / 4)),
nn.ReLU(inplace=True),
nn.Conv2d(int(outchannel / 4), outchannel, kernel_size=1, stride=1, padding=0, bias=False),
nn.BatchNorm2d(outchannel),
)
self.shortcut = nn.Sequential()
if stride != 1 or inchannel != outchannel:
self.shortcut = nn.Sequential(
nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(outchannel)
)
def forward(self, x):
out = self.left(x)
y = self.shortcut(x)
out += self.shortcut(x)
out = F.relu(out)
return out
'''-------------ResNet18---------------'''
class ResNet_18(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_18, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 64, 2, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 128, 2, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 256, 2, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 512, 2, stride=2)
self.fc = nn.Linear(512, num_classes)
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
'''-------------ResNet34---------------'''
class ResNet_34(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_34, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 64, 3, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 128, 4, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 256, 6, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 512, 3, stride=2)
self.fc = nn.Linear(512, num_classes)
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
'''-------------ResNet50---------------'''
class ResNet_50(nn.Module):
def __init__(self, ResidualBlock, num_classes=10):
super(ResNet_50, self).__init__()
self.inchannel = 64
self.conv1 = nn.Sequential(
nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(),
)
self.layer1 = self.make_layer(ResidualBlock, 256, 3, stride=1)
self.layer2 = self.make_layer(ResidualBlock, 512, 4, stride=2)
self.layer3 = self.make_layer(ResidualBlock, 1024, 6, stride=2)
self.layer4 = self.make_layer(ResidualBlock, 2048, 3, stride=2)
self.fc = nn.Linear(512 * 4, num_classes)
# **************************
def make_layer(self, block, channels, num_blocks, stride):
strides = [stride] + [1] * (num_blocks - 1) # strides=[1,1]
layers = []
for stride in strides:
layers.append(block(self.inchannel, channels, stride))
self.inchannel = channels
return nn.Sequential(*layers)
def forward(self, x): # 3*32*32
out = self.conv1(x) # 64*32*32
out = self.layer1(out) # 64*32*32
out = self.layer2(out) # 128*16*16
out = self.layer3(out) # 256*8*8
out = self.layer4(out) # 512*4*4
out = F.avg_pool2d(out, 4) # 512*1*1
# print(out.size())
out = out.view(out.size(0), -1) # 512
out = self.fc(out)
return out
def ResNet18():
return ResNet_18(BasicBlock)
def ResNet34():
return ResNet_34(BasicBlock)
def ResNet50():
return ResNet_50(Bottleneck)
本篇到這里就結(jié)束啦,有什么問題,歡迎大家留言討論~文章來源地址http://www.zghlxwxcb.cn/news/detail-500118.html
到了這里,關(guān)于ResNet代碼復(fù)現(xiàn)+超詳細(xì)注釋(PyTorch)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!