ResNet是视觉领域的一个重要模型,其残差连接的思想更是影响了后续很多模型的设计。
总算是把这玩意儿给折腾出来了,后续更新一个使用搭建的ResNet训练案例。
自己动手实现,强烈建议手动debug一遍,加深理解。
代码实现
# -*- coding: utf-8 -*-
"""
# @file name : model.py
# @author : Csy
# @date : 2023-06-03 13:30
# @brief : 搭建 resnet
"""
import torch
import torch.nn as nn
'''
写在前面
downsample 是一种维度变换操作,而不是字面意义上的下采样
构建不同resnet的参数
block block_num
resnet-18 BasicResidualBlock [2,2,2,2]
resnet-34 BasicResidualBlock [3,4,6,3]
resnet-50 BottleNeck [3,4,6,3]
resnet-101 BottleNeck [3,4,23,3]
resnet-152 BottleNeck [3,8,36,3]
'''
class BasicResidualBlock(nn.Module):
expansion = 1
def __init__(self, in_ch, out_ch, stride=1, downsample=None) -> None:
super().__init__()
self.downsample = downsample
self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_ch)
self.relu1 = nn.ReLU()
self.conv2 = nn.Conv2d(out_ch, out_ch * self.expansion, kernel_size=3, stride=1, padding=1)
self.bn2 = nn.BatchNorm2d(out_ch * self.expansion)
self.relu2 = nn.ReLU()
def forward(self, X):
identity = X
# 是否进行维度变换
if self.downsample is not None:
identity = self.downsample(X)
out = self.relu1(self.bn1(self.conv1(X)))
out = self.bn2(self.conv2(out))
out += identity
return self.relu2(out)
class BottleNeck(nn.Module):
expansion = 4
def __init__(self, in_ch, out_ch, stride=1, downsample=None):
super(BottleNeck, self).__init__()
self.downsample = downsample
self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=stride, bias=False)
self.bn1 = nn.BatchNorm2d(out_ch)
self.relu1 = nn.ReLU(inplace=True) # 少了 激活函数
self.conv2 = nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_ch)
self.relu2 = nn.ReLU()
self.conv3 = nn.Conv2d(out_ch, out_ch * self.expansion, kernel_size=1, stride=1, bias=False)
self.bn3 = nn.BatchNorm2d(out_ch * self.expansion)
self.relu3 = nn.ReLU(inplace=True)
def forward(self, X):
identity = X
# 是否进行维度变换
if self.downsample is not None:
identity = self.downsample(X)
out = self.relu1(self.bn1(self.conv1(X)))
out = self.relu2(self.bn2(self.conv2(out)))
out = self.bn3(self.conv3(out))
out += identity
return self.relu3(out)
class ResNet(nn.Module):
def __init__(self, in_ch=3, num_classes=1001, block=BottleNeck, block_num=[3, 4, 6, 3]):
'''
:param in_ch: 图片输入通道数 默认3
:param num_classes: 分类数 根据任务设定
:param block: block 类型
:param block_num: block的数量
'''
super(ResNet, self).__init__()
self.in_ch = in_ch
self.conv1 = nn.Conv2d(in_ch, 64, kernel_size=7, stride=2, padding=3, bias=False) # 图片通道 由 3 到 64
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) # 图片大小 /2
'''
第一种写法
'''
self.in_ch = 64 # 跟踪输入通道的变化
self.layer1 = self.make_layer(block, 64, block_num[0], stride=1)
self.layer2 = self.make_layer(block, 128, block_num[1], stride=2)
self.layer3 = self.make_layer(block, 256, block_num[2], stride=2)
self.layer4 = self.make_layer(block, 512, block_num[3], stride=2)
self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 之前未加池化 512*block.expansion*7*7
self.fc_layer = nn.Sequential(
nn.Linear(512 * block.expansion, num_classes),
nn.Softmax(dim=-1)
)
def forward(self, X):
out = self.maxpool1(self.relu(self.bn1(self.conv1(X)))) # 224,224 --> 56,56 图片高和宽减小4倍
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.layer4(out)
out = self.avgpool(out) # 512*expansion*7*7 -> X*X *1*1
# flatten 展开
out = out.reshape(out.shape[0], -1)
# 分类器
out = self.fc_layer(out)
return out
def make_layer(self, block, out_ch, block_num, stride=1):
'''
:param block: 使用的模块 类型 两种可选
:param out_ch: 输出通道数 也就一个 block 中 第一层的输出通道数
:param block_num: 构建的模块数量
:param stride: 步幅
:return:
'''
downsample = None
# downsample 只发生在 stride != 1 和 模块前后输出不一致的地方
if stride != 1 or self.in_ch != out_ch * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_ch, out_ch * block.expansion, kernel_size=1, stride=stride),
nn.BatchNorm2d(out_ch * block.expansion)
)
layers = [block(self.in_ch, out_ch, stride=stride, downsample=downsample)]
# 生成第一个 block 后 改变输入通道数 之后的 block 输入通道 和 输出通道 相等 不发生变化 因此也不需要添加 downsample 进行维度变换
self.in_ch = out_ch * block.expansion
for _ in range(1, block_num):
layers.append(block(self.in_ch,
out_ch)) # block(self.in_ch, out_ch, stride=1, downsample=None) 之前写法 stride downsample与默认保持一致 可以省略
return nn.Sequential(*layers)
if __name__ == '__main__':
X = torch.randn(1, 3, 224, 224)
resnet50 = ResNet(in_ch=3, num_classes=1001, block=BottleNeck, block_num=[3, 4, 6, 3])
y = resnet50(X)
print(y.shape)