关于ResNet的原理和具体细节,可参见上篇解读:经典神经网络论文超详细解读(五)——ResNet(残差网络)学习笔记(翻译+精读+代码复现)
接下来我们就来复现一下代码。
源代码比较复杂,感兴趣的同学可以上官网学习:
https://github.com/pytorch/vision/tree/master/torchvision
本篇是简化版本
一、BasicBlock模块
BasicBlock结构图如图所示:
BasicBlock是基础版本,主要用来构建ResNet18和ResNet34网络,里面只包含两个卷积层,使用了两个 3*3 的卷积,通道数都是64,卷积后接着 BN 和 ReLU。
右边的曲线就是Shortcut Connections,将输入x加到输出。
代码:
'''-------------一、BasicBlock模块-----------------------------''' # 用于ResNet18和ResNet34基本残差结构块 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表示进行原地操作,一般默认为False,表示新建一个变量存储操作 nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, 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) #这是由于残差块需要保留原始输入 out += self.shortcut(x)#这是ResNet的核心,在输出上叠加了输入x out = F.relu(out) return out
二、Bottleneck 模块
Bottleneck结构图如图所示:
Bottleneck主要用在ResNet50及以上的网络结构,与BasicBlock不同的是这里有 3 个卷积,分别为 1*1,3*3,1*1大小的卷积核,分别用于压缩维度、卷积处理、恢复维度。
这里的通道数是变化的,1*1卷积层的作用就是用于改变特征图的通数,使得可以和恒等映射x相叠加,另外这里的1*1卷积层改变维度的很重要的一点是可以降低网络参数量,这也是为什么更深层的网络采用BottleNeck而不是BasicBlock的原因。
注意:这里outchannel / 4是因为Bottleneck层输出通道都是输入的4倍
代码:
'''-------------二、Bottleneck模块-----------------------------''' # 用于ResNet50及以上的残差结构块 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基础块和BotteNeck结构后,我们就可以搭建ResNet结构了。
5种不同层数的ResNet结构图如图所示:
代码:
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基本残差结构块 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表示进行原地操作,一般默认为False,表示新建一个变量存储操作 nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, 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) #这是由于残差块需要保留原始输入 out += self.shortcut(x)#这是ResNet的核心,在输出上叠加了输入x out = F.relu(out) return out '''-------------二、Bottleneck模块-----------------------------''' # 用于ResNet50及以上的残差结构块 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)
本篇到这里就结束啦,有什么问题,欢迎大家留言讨论~