ResNet笔记
# 总体
整体结构如下:

这里面主要有左侧六个阶段组成,包括input,output在内,还有4个stage。下面分别描述一下各个部分的组成。
# input阶段
输入阶段包括四步,分别为:
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.relu = nn.ReLU(inplace=True)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
2
3
4

把图片从224*224降成了56*56的大小,简化了后续的计算。
# stage层
这里首先要注意的是,18层和34层使用的是BasicBlock而后面50层以上则使用的是BottleNeck结构,下面分别描述两种结构存在的不同之处。
# BasicBlock

def forward(self, x):
这里面forward( )函数规定了网络数据的流向。
# Bottleneck结构
# 1*1卷积
# 图片尺寸变化
# 如何让输入输出保持不变
设
填充$padding = P$
卷积核大小$kernel_size = K$
步长$stride = S$
则经过卷积的输入输出关系为:$output_size = \frac{input_size + 2 \times P - K}{S} + 1$(这里的相除,其实是求商,舍掉小数)
如果想让图片输入输出不发生变化,设S=1,则只需满足$K = 2P+1$即可。
例如$K = 3, P =1, S = 1$,这种设计在resnet中已经被广泛使用。
# 如何让输出尺寸变成输入尺寸的一半
继续利用上面的关系式,只要取$K=3, P=1, S=2$,就可以了,这样算出来的结果为:
$output_size = \frac{input_size + 1}{2}$,因为这个除法实际上是取商,所以得到的最终结果为:
$output_size = \frac{input_size}{2}$
# resnet代码中的操作
为了完成上述的操作,resnet在代码中用_make_layer函数完成,核心代码如下:
def _make_layer(self, block, planes, blocks, stride=1, dilate=False):
……
layers = []
layers.append(block(self.inplanes, planes, stride, downsample, self.groups,
self.base_width, previous_dilation, norm_layer))
self.inplanes = planes * block.expansion
for _ in range(1, blocks):
layers.append(block(self.inplanes, planes, groups=self.groups,
base_width=self.base_width, dilation=self.dilation,
norm_layer=norm_layer))
2
3
4
5
6
7
8
9
10
11
12
调用时:
self.layer1 = self._make_layer(block, 64, layers[0])
self.layer2 = self._make_layer(block, 128, layers[1], stride=2,
dilate=replace_stride_with_dilation[0])
self.layer3 = self._make_layer(block, 256, layers[2], stride=2,
dilate=replace_stride_with_dilation[1])
self.layer4 = self._make_layer(block, 512, layers[3], stride=2,
dilate=replace_stride_with_dilation[2])
2
3
4
5
6
7
可以发现实际上在layer2-4中传递了stride = 2的情况,而在make_layer函数中,只有第一层用了stride,后面统一使用了默认值1,进而保证了尺度保持不变。
参考
[1] https://zhuanlan.zhihu.com/p/54289848