赞
踩
由于随着卷积层数的增加,会导致梯度消失/爆炸的问题,虽然这两种方法可以通过归一化等方法解决,但是还存在退化问题,所以提出了ResNet。
(关于梯度消失和爆炸,退化可参照 链接: link.)
对于整个模型的搭建主要是Block类,接下来以BasicBlock为例来对代码进行解析。(代码来源:太阳花的小绿豆的CSDN 链接: link.))
** Block**的含义:
例如:ResNet34由以下几部分组成,conv1,conv2_x,conv3_x,conv4_x,conv5_x,以及最后的全连接层, BasicBlock指的是 conv2_x,conv3_x,conv4_x,conv5_x里的这些具有重复结构的块,如conv3_x层中具有四个([33,128]+[33,128])块, BasicBlock就指的是这些块,在ResNet中,通过重复调用BasicBlock方法就能方便的构建出这些块。简单来说,Block是为了方便调用代码,就是为了省力。
我们可以了解到ResNet中对于50层以下的构建块采用的是BasicBlock,而大于50的深层则采用的是Bottleneck
class BasicBlock(nn.Module): expansion = 1 def __init__(self, in_channel, out_channel, stride = 1, downsample = None, **kwargs): super(BasicBlock, self).__init__() self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channel) self.relu = nn.ReLU() self.conv2 = nn.Conv2d(in_channels=out_channel, out_channels=out_channel, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channel) self.downsample = downsample def forward(self, x): identity = x if self.downsample is not None: identity = self.downsample(x) out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out += identity out = self.relu(out) return out
**步骤1:**令BasicBlock继承nn.Module(对于这一模块,可查看Pytorch文档中的nn.Module或 参考
LoveMIss-Y的CSDN链接link.,简单来说,就是利用库中定义的一个模型来改写)
**步骤2:**重写__init__和forward
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然我也可以吧不具有参数的层也放在里面。
(2)一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面, 则在forward方法里面可以使用nn.functional来代替。
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
对__init__的理解,我们可以发现在18层和34层的网络中,卷积核大小均为3*3,且特征图数量没有增加,因此在BasicBlock中我们的conv的kernel_size=3,stride默认取1,这里需要注意BN层必须是在conv层和激活函数层中间,且conv中的bias(偏置参数)不需要设置,因为经过BN层后bias会被消掉。
(参照马佳的男人的CSDN博文 链接link.)
首先我们要明确,在resnet中的downsample有两种:
1、真正意义上让output.shape长宽变成1/2的我暂且称之为real_downsample
2、shortcut(是指经过结构中捷径的过程)前的x的为了适应shortcut后变化的shape而做的自适应调节,暂且称之为identity_downsample
接下来看ResNet这个类:
class ResNet(nn.Module): def __init__(self, block, block_num, num_classes=1000, include_top=True, groups=1, width_per_group=64): super(ResNet, self).__init__() self.include_top = include_top self.in_channel = 64 self.groups = groups self.width_per_group = width_per_group self.conv1 = nn.Conv2d(3, self.in_channel, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(self.in_channel) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, block_num[0]) 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) if self.include_top: self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(512 * block.expansion, num_classes) for m in self.modules(): if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') #权重初始化(正态分布) def _make_layer(self, block, channel, block_num, stride=1): downsample = None if stride != 1 or self.in_channel != channel * block.expansion: downsample = nn.Sequential( nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(channel * block.expansion)) layers = [] layers.append(block( self.in_channel, channel, downsample=downsample, stride=stride, groups=self.groups, width_per_group=self.width_per_group)) self.in_channel = channel * block.expansion for _ in range(1, block_num): layers.append(block( self.in_channel, channel, groups=self.groups, width_per_group=self.width_per_group)) return nn.Sequential(*layers) def forward(self, x): x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) if self.include_top: x = self.avgpool(x) x = torch.flatten(x, 1) x = self.fc(x) return x
** real_downsample:**
#这里的layer是指包含多个block的一个层,层>块
(1) 第一个大layer不做real_downsample,所以此时stride=1
(2)剩下的所有大layer都只在第一个block里的第一个3x3用stride=2做real_downsample 那么如何对所有的第一个block进行调整?
当然就是对于每个layer,先把第一个block拉出来用我们传进去的stride进行领导的特殊对待啦(stride默认为1)传进去stride = 1,那么第一个block就不用downsample;如果传进去stride = 2,那第一个block就要downsample。至于剩下的block, 就不用看领导脸色了,传进去stride = 1还是2都跟它们无关,因为它们反正都不downsample,所以可以看到在_make_layer的循环里没有指定接下来的stride。
** identity_downsample:**(identity:其实意思就是这个网络层的设计是用于占位的,即不干活,只是有这么一个层,放到残差网络里就是在跳过连接的地方用这个层,显得没有那么空虚!)
由于在残差结构里,参数通过主线和捷径后又一个相加操作,所以需要保证两者维度一致,所以需要做accommodation(适应)
什么时候需要做identity_downsample?
简单来说,做了real_downsample就要做identity_downsample
if stride != 1 or self.in_channel != channel * block.expansion:
downsample = nn.Sequential(
nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(channel * block.expansion))
现在,来看代码,我们对stride是否等于1进行了判断,因为我们可以知道除了第一个大layer没做real_downsample外,其余所有大layer的第一个block都做了real_downsample,所以后续对传入参数进行变换。
downsample总结:
real downsample是Resnet主动为了downsample而downsample的,而identity_downsample只是我们为了能让out += identity 的被动的适应性调整
通过对比BasicBlock和Bottleneck可以发现在前者中expansion=1,后者等于4。
所以这个参数的用途究竟是什么?
我们经过两个block的对比可以发现,实际上这个参数是用于区分这两个block,已经知道了对于50层以下的网络使用BasicBlock,而以上使用Bottleneck。两者的区别可以通过表格看出,在block中50层以下网络,卷积后没有改变深度(特征图的数目),而对于50层以上的网 络,最后一次卷积将深度扩大了4倍,所以这个参数就是这么来的。
这是我在学习过程中,对于ResNet代码阅读的一些心得和参考的博文。刚刚开始学习深度学习,这笔记算是对学习的证明吧!接下来将使用迁移学习的方法对宝可梦数据集进行训练。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。