神经网络----Pytorch实现残差网络ResNet18
文章目录
前言
ResNet18是参照👉https://www.bilibili.com/video/BV1154y1S7WC?share_source=copy_web中up主搭建ResNet的思路来实现的。如有错误或其他问题,欢迎指出!!!
此外,本文只介绍实现的思路,不对ResNet做具体解读。
ResNet18结构介绍
网络的整体结构如下图
从图中可以看到红色框内八个比较特别的块,这些块就是该网络的特色:残差结构
残差结构
如图,可以看出最终的输出包括两个部分:经过处理后的F(x)和原始输入x
ResNet18
从刚刚的网络整体结构图我们可以看到红色框内的块不完全相同,有两类:Identity Block和Conv Block。
|
|
两者的结构如下图,左边的主干部分两者是相同的:两次 [卷积,标准化,激活函数] 和一次 [卷积,标准化]。
不同的是在右侧的残差边,Identity Block直接将 原始输入 与左侧结果相连,而Conv Block则 对原始输入进行了一次 [卷积,标准化] 后再与左侧结果相连。
|
|
ResNet18与ResNet50网络对比
开始之前先对后面会用到的结构代称做个介绍
一个Stage中含有多个Bottleneck,Bottleneck是Conv Block和Identity Block的统称
相似点
C1:结构分割
在参考的视频中,有这样一张图
作者把输出通道数相同的Bottleneck(包括Identity Block和Conv Block)整合成一个Stage,于是得到了4个输出通道数不同的Stage。
而对比以下ResNet18和ResNet50,我们也可以把ResNet18分成4个Stage。
C2:对残差边的处理
将残差边由两部分决定,若此时的Bottleneck是Conv Block,则对x进行处理,将结果赋值给side;若是Identity Block则side = None。
后续定义残差边residual=初始输入x,然后读取side,若side不为None,则说明残差边进行了处理,把其值赋给residual;若side为None,则说明残差边没有进行处理,residual仍然为x。
不同点
D1:ResNet18中每个 Stage中的 Bottleneck个数相同
- 在ResNet50中,因为每个Stage的Bottleneck的个数不同,且都是第一个为Conv Block,剩下的都是Identity Block。
所以作者将Stage分成两部分:1)单独处理第一层的Conv Block,2)后续的Identity Block通过传入一个list:[3, 4, 6, 3],读取为block_num放入循环中实现剩余的Identity Block。
# Conv Block
conv_block = block(self.inplane, midplane, stride=stride, downsample=downsample)
# Identity Block
for i in range (1, block_num):
block_list.append(block(self.inplane, midplane, stride=1))
- 在ResNet18中每个Stage中的Bottleneck个数相同,所以我们不用传入list,不用循环,直接实现
block1 = block(in_ch, self.out_ch, stride=stride, side=side)
block2 = block(self.out_ch, self.out_ch, stride=1, side=None)
D2:ResNet18中 Stage1的第一个 Bottleneck是 Identity Block而不是 Conv Block
- 在ResNet50中,Identity Block有两个特点:
1)stride=1
2)每个Identity Block的最终输出结果通道数 = 中间处理层 [卷积,标准化,激活函数] 结果通道数 * 4
所以不满足这两个特点的就是Conv Block,我们就需要对其残差边输入的x进行处理
if stride != 1 or self.inplane != midplane*block.extention:
downsample = nn.Sequential(
nn.Conv2d(self.inplane, midplane*block.extention, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(midplane*block.extention)
)
- 在ResNet18中,Identity Block和Conv Block用stride就可以区别
if stride != 1:
side = nn.Sequential(
nn.Conv2d(in_ch, self.out_ch, kernel_size=1, stride=2, bias=False),
nn.BatchNorm2d(self.out_ch)
)