pytorch入门——边学边练06 Residual_Network

访问本站观看效果更佳

写在前面

今天我们探讨一下大名鼎鼎的ResNet。ResNet在2015年被提出,在ImageNet比赛classification任务上获得第一名,因为它“简单与实用”并存,之后很多方法都建立在ResNet50或者ResNet101的基础上完成的,检测,分割,识别等领域都纷纷使用ResNet,Alphazero也使用了ResNet,所以可见ResNet确实很好用。 源码地址Deep Residual Network。我们根据论文的内容来做一下,您会发现非常容易就能实现Resnet。

Resnet关键部分理解

图1
关于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个堆叠的权重层。下表总结了架构:screenshot from 2018-08-27 15-30-15

  • 当使用捷径连接时,它们连接到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。前面的实现里我们都自己写过了。图1
    我们再看第二个问题——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收敛更快。
图1

# 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的操作。应当说,内容还是比较简单的,我们后面再来点复杂的东西吧!

猜你喜欢

转载自blog.csdn.net/zcgyq/article/details/83087640
今日推荐