当前位置: 代码迷 >> 综合 >> CNN基础论文 精读+复现---- ResNet(二)
  详细解决方案

CNN基础论文 精读+复现---- ResNet(二)

热度:16   发布时间:2023-12-26 11:00:57.0

文章目录

  • 准备工作
  • BasicBlock块
  • ResNet-18、34网络结构
  • 完整代码:
  • 小总结

准备工作

昨天把论文读完了,CNN基础论文 精读+复现---- ResNet(一)
,今天用pytorch复现一下。

之前论文中提到过ResNet有很多种,这里复现一下ResNet-18和ResNet34吧,这俩基本一样。

这两种残差块,左边是 18 和34层的,50,101,152用右边的残差快。

ResNet-18,只需要左边的残差块,这俩残差块都实现一下,整体网络实现ResNet-18。
在这里插入图片描述

BasicBlock块

按照上面左边的图, 结构很清晰: 卷积 -> BN -> Relu -> 卷积 -> BN。
这几层写出来先:

nn.Conv2d(inchannel, outchannel, kernel_size=3, stride=stride, padding=1, bias=False),
nn.BatchNorm2d(outchannel),
nn.ReLU(inplace=True),
nn.Conv2d(outchannel, outchannel, kernel_size=3, stride=1, padding=1, bias=False),
nn.BatchNorm2d(outchannel)

然后定义一下右路的恒等映射,这个其实就是个空就行了,里面什么层都不放。

self.shortcut = nn.Sequential()

之后还有个很重要的东西,就是之前说过的 残差F(X)与自身输入x维度必须一致。

这里直接通过1 * 1 卷积核进行卷积升降维就ok,也记得要加BN。

if stride != 1 or inchannel != outchannel:#shortcut,这里为了跟2个卷积层的维度结构一致。self.shortcut = nn.Sequential(nn.Conv2d(inchannel, outchannel, kernel_size=1, stride=stride, bias=False),nn.BatchNorm2d(outchannel))

最后在汇总的时候是 两路相加然后再Relu:

out = self.left(x) 
out = out + self.shortcut(x)
out = nn.relu(out)

放到一起就有了BasicBlock块:


import torch
import torch.nn as nn
#残差块ResBlock
class ResBlock(nn.Module):def __init__(self, inchannel, outchannel, stride=1):super(ResBlock, 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),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:#shortcut,这里为了跟2个卷积层的维度结构一致。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 = out + self.shortcut(x)out = nn.relu(out)return out

另外一个 BottleNeck块 就不写了,跟这个差不多 就是都了一层而已。

ResNet-18、34网络结构

根据论文中的这张图:
在这里插入图片描述
ResNet-18和34 一共6个部分: 开头的卷积层,然后中间4块残差块,最后一个全连接层。

先将各层堆叠起来:

	    # 一开始的卷积+池化层
self.pre = nn.Sequential(nn.Conv2d(3, 64, 7, 2, 3, bias=False),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2, 1))#各层的残差块
self.layer1 = self._make_layer(64, 64, blocks[0])
self.layer2 = self._make_layer(64, 128, blocks[1], stride=2)
self.layer3 = self._make_layer(128, 256, blocks[2], stride=2)
self.layer4 = self._make_layer(256, 512, blocks[3], stride=2)# 最后的全连接层
self.fc = nn.Linear(512, num_classes)

将重复的残差块放到一起去:

def _make_layer(self, inchannel, outchannel, block_num, stride=1):# 重复的残差块shortcut = nn.Sequential(nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),nn.BatchNorm2d(outchannel),nn.ReLU())layers = []layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))for i in range(1, block_num):layers.append(ResidualBlock(outchannel, outchannel))return nn.Sequential(*layers)

改写一下上面实现的BasicBlock残差块让他适应 18和34层的ResNet。

class ResidualBlock(nn.Module):def __init__(self, inchannel, outchannel, stride=1, shortcut=None):super(ResidualBlock, self).__init__()self.left = nn.Sequential(nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),nn.BatchNorm2d(outchannel),nn.ReLU(inplace=True),nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),nn.BatchNorm2d(outchannel))self.right = shortcutdef forward(self, x):out = self.left(x)residual = x if self.right is None else self.right(x)out += residualreturn F.relu(out)

上面这段代码没事好说的,就是稍微改动了一下 最开始实现的残差块的参数。

整体forward一下:

def forward(self, x):x = self.pre(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = F.avg_pool2d(x, 7)x = x.view(x.size(0), -1)return self.fc(x)

完整代码:

将上面的汇总到一起看一下完整的 ResNet-18、34代码。
这里没有加数据集。

torchsummary 也是pytorch里的可视化方法。

import torch
import torchvision
from torch import nn
from torch.nn import functional as F
from torchsummary import summaryclass ResidualBlock(nn.Module):"""实现子module: Residual Block"""def __init__(self, inchannel, outchannel, stride=1, shortcut=None):super(ResidualBlock, self).__init__()self.left = nn.Sequential(nn.Conv2d(inchannel, outchannel, 3, stride, 1, bias=False),nn.BatchNorm2d(outchannel),nn.ReLU(inplace=True),nn.Conv2d(outchannel, outchannel, 3, 1, 1, bias=False),nn.BatchNorm2d(outchannel))self.right = shortcutdef forward(self, x):out = self.left(x)residual = x if self.right is None else self.right(x)out += residualreturn F.relu(out)class ResNet(nn.Module):"""实现主module:ResNet34ResNet34包含多个layer,每个layer又包含多个Residual block用子module来实现Residual block,用_make_layer函数来实现layer"""def __init__(self, blocks, num_classes=1000):super(ResNet, self).__init__()self.model_name = 'resnet34'# 前几层: 图像转换self.pre = nn.Sequential(nn.Conv2d(3, 64, 7, 2, 3, bias=False),nn.BatchNorm2d(64),nn.ReLU(inplace=True),nn.MaxPool2d(3, 2, 1))# 重复的layer,分别有3,4,6,3个residual blockself.layer1 = self._make_layer(64, 64, blocks[0])self.layer2 = self._make_layer(64, 128, blocks[1], stride=2)self.layer3 = self._make_layer(128, 256, blocks[2], stride=2)self.layer4 = self._make_layer(256, 512, blocks[3], stride=2)# 分类用的全连接self.fc = nn.Linear(512, num_classes)def _make_layer(self, inchannel, outchannel, block_num, stride=1):"""构建layer,包含多个residual block"""shortcut = nn.Sequential(nn.Conv2d(inchannel, outchannel, 1, stride, bias=False),nn.BatchNorm2d(outchannel),nn.ReLU())layers = []layers.append(ResidualBlock(inchannel, outchannel, stride, shortcut))for i in range(1, block_num):layers.append(ResidualBlock(outchannel, outchannel))return nn.Sequential(*layers)def forward(self, x):x = self.pre(x)x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)x = F.avg_pool2d(x, 7)x = x.view(x.size(0), -1)return self.fc(x)def ResNet18():return ResNet([2, 2, 2, 2])def ResNet34():return ResNet([3, 4, 6, 3])if __name__ == '__main__':device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")model = ResNet34()model.to(device)summary(model, (3, 224, 224))

输出各层的信息:

在这里插入图片描述

小总结

其实使用的话完全没必要自己写,可以直接 用API调用ResNet的预训练模型,然后修改最后的全连接层做迁移学习就行了,像下面这样。

import torchvision
model = torchvision.models.resnet18(pretrained=True)

总的来说ResNet代码复现起来非常潦草,可以看到我都不像之前那样加入数据集和可视化结果了,代码写一半,感觉自己太菜了,差好多东西, 我得继续去学习了,后面论文先不看了。完整代码我放到Github上了一份:https://github.com/shitbro6/paper

  相关解决方案