访问本站观看效果更佳
写在前面
今天我们探讨一下大名鼎鼎的ResNet。ResNet在2015年被提出,在ImageNet比赛classification任务上获得第一名,因为它“简单与实用”并存,之后很多方法都建立在ResNet50或者ResNet101的基础上完成的,检测,分割,识别等领域都纷纷使用ResNet,Alphazero也使用了ResNet,所以可见ResNet确实很好用。 源码地址Deep Residual Network。我们根据论文的内容来做一下,您会发现非常容易就能实现Resnet。
Resnet关键部分理解
关于Resnet的原理部分,本文不做详细介绍,我们主要精力放在实现上。上图就是Resnet的主要结构,甚至我们对比着上图就能把Resnet实现出来了。这里我们撇开为什么有效
的问题,来看看如何做,此处我们主要看论文的4.2节。以下是论文翻译:
-
我们在CIFAR-10数据集上进行了更多的研究[20],该数据集有10个类别,50k个训练图像和10k个测试图像。我们在训练集上训练并在测试集上评估的实验。我们关注的是极深网络的行为,而不是推动最先进的结果,所以我们故意使用如下简单的体系结构。
-
普通/残差体系结构遵循图3(中/右)的形式。网络输入是32×32图像,它的每个像素减去平均值。第一层是3×3卷积。然后,我们分别在尺寸为{32,16,8}的特征图上使用3×3卷积的6n个堆栈层,每个特征图尺寸为2n层。过滤器的数量分别为{16,32,64}。下采样通过以2的步长卷积来执行。网络以全局平均池化,10路全连接层和softmax结束。总共有6n + 2个堆叠的权重层。下表总结了架构:
-
当使用捷径连接时,它们连接到3×3的层对(总共3n个捷径连接)。在这个数据集中,我们在所有情况下都使用恒等捷径(即选项A),因此我们的残差模型具有与相应的普通模型完全相同的深度,宽度和参数数量。
我们使用0.0001的权重衰减和0.9的动量,并采用[12]和BN中的权重初始化,但没有使用dropout。这些模型在两个GPU上以128个小批量进行训练。我们以0.1的学习速率开始,在32k和48k迭代时将其除以10,并于64k迭代后终止训练。网络是在45k / 5k的训练/ 验证集上训练的。我们使用[24]中的简单数据增强策略进行训练:每边填充4个像素,从填充图像或其水平翻转中随机采样32×32裁剪。对于测试,我们只评估原本的32×32的图像。 -
我们比较了n={3,5,7,9},分别对应20,32,44,56层的网络。图6(左)显示了普通网络的表现。深度普通网络经历了深度的增加,并且随着深度的增加表现出更高的训练误差。这种现象与ImageNet(图4左侧)和MNIST(见[41])类似,表明这样的优化难度是一个根本的问题。
** 好了,如果嫌麻烦可以直接跳过上面的话,跟着我一起来看看。首先我们要明确一点,上文提到的Resnet是由3个Resnet Block
构成的。这里就有两个问题。**
ResNet Block
内部结构是什么?
block
如何构建网络?
我们先看第一个问题——ResNet Block
内部结构。其实下图已经告诉我们怎么去做了。主要的想法就是在输出的位置加上一个x
再送入relu
,剩下的部分就是一个CNN
。前面的实现里我们都自己写过了。
我们再看第二个问题——block
如何构建网络。我们再重新读一读这句话,这句话就是答案:
第一层是3×3卷积。然后,我们分别在尺寸为{32,16,8}的特征图上使用3×3卷积的6n个堆栈层,每个特征图尺寸为2n层。过滤器的数量分别为{16,32,64}。下采样通过以2的步长卷积来执行。网络以全局平均池化,10路全连接层和softmax结束。总共有6n + 2个堆叠的权重层。
逐句逐句的分析。
第一层是3×3卷积。
首先放一个conv3x3
。
然后,我们分别在尺寸为{32,16,8}的特征图上使用3×3卷积的6n个堆栈层,每个特征图尺寸为2n层。
我们操作的对象是不同尺寸的特征图。什么是特征图?输入经过conv3x3
是特征图吗?还不是哦~得再加一个激活层relu
。我们拿到了out=relu(conv3×3)
。接着要干什么呢?使用3×3卷积,但参数怎么定呢?所谓的尺寸就是指图片的大小,{32,16,8}就是告诉我们stride=2
。
再接着看6n个堆栈层,每个特征图尺寸为2n层
注意之前的图,说白了就是把Block
叠放两层。
过滤器的数量分别为{16,32,64}
这也就是说卷积层的out_channels
分别为{16,32,64}。
下采样通过以2的步长卷积来执行
就是告诉我们做一个卷积,步长为2,但是要注意下采样放的位置。
网络以全局平均池化
告诉我们加一个池化层。
10路全连接层和softmax结束
这个操作不用多说了吧?
具体实现
下面我们来敲敲代码,完成上述网络结构的实现。
首先我们来完成最简单的一部分,做一个3×3的卷积,因为后面反复要用到:
# 3x3 convolution
def conv3x3(in_channels, out_channels, stride=1):
return nn.Conv2d(in_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
再做一个ResNet Block
。这个也是非常简单,CNN
加上x
。再看一眼图。先放一个conv3x3
,然后加入relu
再放一个conv3x3
。注意中间加上batchnorm
收敛更快。
# Residual block
class ResidualBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, downsample=None):
super(ResidualBlock, self).__init__()
self.conv1 = conv3x3(in_channels, out_channels, stride)
self.bn1 = nn.BatchNorm2d(out_channels)
self.relu = nn.ReLU(inplace=True)
self.conv2 = conv3x3(out_channels, out_channels)
self.bn2 = nn.BatchNorm2d(out_channels)
self.downsample = downsample
def forward(self, x):
residual = x
out = self.conv1(x)
out = self.bn1(out)
out = self.relu(out)
out = self.conv2(out)
out = self.bn2(out)
if self.downsample:
residual = self.downsample(x)
out += residual
out = self.relu(out)
return out
out += residual
简简单单一句话就搞定了。一个部分解决了,再看另一个部分。比对上文的分析,我们可以看到这里从概念上讲没有太多新的东西,主要是搞明白论文里的各种名词对应的是何种操作。
# ResNet
class ResNet(nn.Module):
def __init__(self, block, layers, num_classes=10):
super(ResNet, self).__init__()
self.in_channels = 16
self.conv = conv3x3(3, 16)
self.bn = nn.BatchNorm2d(16)
self.relu = nn.ReLU(inplace=True)
self.layer1 = self.make_layer(block, 16, layers[0])
self.layer2 = self.make_layer(block, 32, layers[0], 2)
self.layer3 = self.make_layer(block, 64, layers[1], 2)
self.avg_pool = nn.AvgPool2d(8)
self.fc = nn.Linear(64, num_classes)
def make_layer(self, block, out_channels, blocks, stride=1):
downsample = None
if (stride != 1) or (self.in_channels != out_channels):
downsample = nn.Sequential(
conv3x3(self.in_channels, out_channels, stride=stride),
nn.BatchNorm2d(out_channels))
layers = []
layers.append(block(self.in_channels, out_channels, stride, downsample))
self.in_channels = out_channels
for i in range(1, blocks):
layers.append(block(out_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
out = self.conv(x)
out = self.bn(out)
out = self.relu(out)
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
小结
今天我们研究了一下经典的resnet
结构,熟悉了pytorch
的操作。应当说,内容还是比较简单的,我们后面再来点复杂的东西吧!