第3周学习笔记:ResNet+ResNeXt

本文介绍了ResNet的创新设计,包括残差结构解决深度网络难题,BatchNorm加速训练并保持特征分布稳定。LeNet与ResNet对比,展示ResNet在图像分类中的优势。此外,探讨了ResNetX的改进策略和分组卷积在提升精度中的作用。
摘要由CSDN通过智能技术生成

目录

一 理论学习

1. ResNet

2.resnetx

 二 代码学习

1.LeNet

2.ResNet

三 问题


一 理论学习

1. ResNet

ResNet在2015年由微软实验室提出,斩获当年ImageNet竞赛中分类任务第一名,目标检测第一名。获得COCO数据集中目标检测第一名,图像分割第一名。

论文亮点

  • 超深的网络结构(突破1000层):随着网络层度的不断加深,会出现梯度消失或者梯度爆炸和退化问题,可以用残差解决
  • 提出残差模块
  • 使用Batch Normalization加速训练(丢弃dropout)

背景

论文作者认为,网络的深度对于网络而言至关重要,然而,随着网络层度的不断加深,会出现梯度消失或者梯度爆炸和退化问题

随着网络深度的增加,准确率达到饱和然后迅速退化。即网络达到一定层数后继续加深模型会导致模型表现下降。然而,这种退化并不是由过拟合造成的,也不是由梯度消失和爆炸造成的,在一个合理的深度模型中增加更多的层却导致了更高的错误率。作者提出这样的问题可以用残差解决。

在适当深度的模型上添加更多的层会导致更高的训练误差

残差结构

通过跳跃连接来实现了一个简单的恒等映射。作者通过优化F(x),使得F(x)→0,从而使得F(x)+x→x ,最终达到一个恒等映射的关系。

论文提出的残差结构:

左边结构针对于Resnet34,右边针对于ResNet50/101/152,不同之处在于多了两个1*1的卷积层,其作用在于降维和升维。

网络结构

网络由一系列残差结构组成

Plain network:卷积层主要为3*3的filter,设计遵循:(i) 输出特征尺寸相同的层含有相同数量的filter;(ii) 如果特征尺寸减半,则filter的数量增加一倍来保证每层的时间复杂度相同。论文直接通过stride为2的卷积层来进行下采样。

Residual network在以上plain网络的基础上,插入shortcut连接,将网络变成了对应的残差版本。如果输入和输出的维度相同时,可以直接使用恒等shortcuts(实线),当维度增加时(虚线),考虑两个选项:(A) shortcut仍然使用恒等映射,在增加的维度上使用0来填充,这样做不会增加额外的参数;(B) 通过通过1*1的卷积改变x的维度,来使维度保持一致。

 34层的网络结构:7*7卷积核-3*3最大池化下采样-3层Conv2残差层-4层Conv3残差层-6层Conv4残差层-3层Conv5残差层-平均池化下采样-全连接

实验表明,引入残差学习后,深层的网络更容易优化并且不会产生更高的训练错误率,甚至还能降低错误率。

注意:实线和虚线

实线输入的shape和输出的shape一样,所以可以直接相加;虚线的残差结构输出和输入shape不一样,右图先利用为2的步长,把长和宽缩减为原来的一半,然后通过128的卷积核,改变输入的深度(卷积结构交接的第一层)

注意原论文中,右侧虚线残差结构的主分支 上,第一个1x1卷积层的步距是2,第二个3x3卷积层步距是1。但在pytorch官方实现过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,这样能够在imagenet的top1.上提升大概0.5%的准确率

Batch Normalization
Batch Normalization的目的是使一批( Batch )feature map满足均值为0,方差为1的分布规律。

我们在图像预处理过程中通常会对图像进行标准化处理,这样能够加速网络的收敛,如下图所示对于Conv1来说,它的输入就足满足某一分布的特征矩阵,但对Conv2而言输入的feature map就不一定满足某一分布规律了(注意这里所说满足某一分布规律并不是指某一个feature map的数据要满足分布规律,理论上是指整个训练样本集所对应feature map的数据要满足分布规律) 。而Batch Normalization的目的就是去调整feature map,使fature map满足均值为0,方差为1的分布规律。

 下面看一个例子,对下面两个图进行bn处理,首先要计算均值和方差,通过公式进行计算

 使用BN需要注意:

  • 训练时要将traning参数设置为True,在验证时将trainning参数设置为False。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。
  • batch size尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。
  • 建议将bn层放在卷积层(Conv) 和激活层(例如Relu) 之间,且卷积层不要使用偏置bias,因为没有用,参考下图推理,即使使用了偏置bias求出的结果也是一样的

迁移学习

使用迁移学习的优势:

  • 能够快速的训练出一个理想的结果
  • 当数据集较小时也能训练出理想的效果

网络前几层学到的是比较通用的信息

常见的迁移学习方式:

  1. 载入权重后训练所有参数
  2. 载入权重后只训练最后几层参数
  3. 载入权重后在原网络基础上再添加一层全连接层,仅训练最后-一个全连接层.

2.ResNetX

论文亮点

采用 VGGs/ResNets 的网络的 depth 加深方式,同时利用 split-transform-merge 策略

组卷积(Group Convolution)

假设输入特征矩阵channel等于4,分为两个组,对每个组分别进行卷积操作,假设对每个Group使用n/2个卷积核,通过第每个Group的卷积可以得到对应的channel是n/2的特征矩阵,再对两组进行concat拼接,那么最终特征矩阵得到的channel是n。

再假设输入矩阵的channel等于cin,对输入特征矩阵分为g个组,那么对于每个group而言,每个group采用卷积核的参数是(k×k×cin/g×n/g)。

当g=cin,n=cin,这就相当于对输入特征矩阵的每一个channel分配了一个channel为1的卷积核进行卷积

block

将原ResNet中的block替换成新的block

 这三个block在数学计算上完全等价,

c:先通过1*1卷积层进行降维处理(channel 256-128),再通过group对它进行处理(group数32,大小3*3,输出channel128),最后通过一个1*1的卷积升维

b:第一层有32个分支(32*4=128,与c第一层等价),第二层和c的group卷积一样,对于每个path理解为一个Group

a:第三层对每个path,先通过1*1的卷积,在进行相加(和b的第三层先通过concat拼接,在进行1*1卷积等价)

abc第三层的等价示例如下图所示,(a)假设path=2,对于每个path采用1*1卷积,再把feature进行相加。(b)chennel为4的特征图进行1*1卷积

(a)
(b)

网络结构

32是Group数,4对应每个组卷积核的个数

 论文作者还研究了不同的Group个数和Group内卷积核个数的搭配效果

 只有block层数大于等于3的时候,才能构建出一个比较有意义的block,所以对之前的浅层block而言,还是使用下图的方式

 二 代码学习

1.LeNet

网络结构如下:

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(16, 32, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32*5*5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 2)

    def forward(self, x):
        x = F.relu(self.conv1(x))    # input(3, 32, 32) output(16, 28, 28)
        x = self.pool1(x)            # output(16, 14, 14)
        x = F.relu(self.conv2(x))    # output(32, 10, 10)
        x = self.pool2(x)            # output(32, 5, 5)
        x = x.view(-1, 32*5*5)       # output(32*5*5)
        x = F.relu(self.fc1(x))      # output(120)
        x = F.relu(self.fc2(x))      # output(84)
        x = self.fc3(x)              # output(10)
        return x

训练结果:

 生成csv文件后提交平台,评审结果如下:

2.ResNet

ResNet18和ResNet34的网络结构:

#18、34层的残差网络
class BasicBlock(nn.Module):
    expansion = 1

    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
        super(BasicBlock, self).__init__()
        # 不使用偏执参数(使用BN时不需要)
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=out_channel,
                               kernel_size=3, stride=stride, padding=1, bias=False)
        # output - (input3 +2*1)/ 2 +1= input /2 +0.5= input/ 2 (向下取整)
        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
class ResNet(nn.Module):

    def __init__(self,
                 block,   #定义的残差结构
                 blocks_num, #列表,对应残差结构的数目(34:3 4 6 3)
                 num_classes=1000,  #训练集的分类个数
                 include_top=True,
                 groups=1,  #X
                 width_per_group=64):
        super(ResNet, self).__init__()
        self.include_top = include_top
        self.in_channel = 64  #max pooling之后得到的特征矩阵的深度

        self.groups = groups
        self.width_per_group = width_per_group
        # 1.对应表格中7*7的卷积层
        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)
        # 2.最大池化下采样操作
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        # 3.conv2
        self.layer1 = self._make_layer(block, 64, blocks_num[0])
        # 4.conv3
        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)
        # 5.conv4
        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)
        # 6.conv4
        self.layer4 = self._make_layer(block, 512, blocks_num[3], stride=2)
        if self.include_top:
            #平均池化下采样
            self.avgpool = nn.AdaptiveAvgPool2d((1, 1))  # output size = (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')

    #channel对应第一个卷积核的个数
    #block_num该层一共包含了多少残差结构
    def _make_layer(self, block, channel, block_num, stride=1):
        downsample = None
        # 34的conv2: 64 == 64*1  18和34会跳过这个if;50、101、152的64 !=64*4
        # 从conv3开始,由于stride变成2,所以都会生成下采样函数
        if stride != 1 or self.in_channel != channel * block.expansion:
            # stride=1时高和宽不变,从conv3开始会调整高宽
            downsample = nn.Sequential(
                #(64,64*4,)
                nn.Conv2d(self.in_channel, channel * block.expansion, kernel_size=1, stride=stride, bias=False),
                nn.BatchNorm2d(channel * block.expansion))

        layers = []
        #先把第一层残差结构添加进来。这个部分实现了1834和50们实线虚线的区别
        layers.append(block(self.in_channel,
                            channel,
                            downsample=downsample,
                            stride=stride,
                            groups=self.groups,  #X
                            width_per_group=self.width_per_group))
        # 18和34不变,其他的要*4
        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

采用ResNet34,训练效果如下:

提交网站后得分如下,可以看出ResNet相比LeNet可以得到更好的分类效果

三 问题

1、Residual learning

 残差学习包括两种映射,分别是

  • identity mapping,指的是上图右边那条弯的曲线。identity mapping指的就是本身的映射,也就是x自身;
  • residual mapping,指的是另一条分支,也就是F(x)部分,这部分称为残差映射,也就是 y-x。

2、Batch Normailization 的原理

通过归一化手段,使一批( Batch )feature map满足均值为0,方差为1的分布规律,这样使得激活输入值分布在非线性函数梯度敏感区域,从而避免梯度消失问题,大大加快训练速度。

3、为什么分组卷积可以提升准确率?即然分组卷积可以提升准确率,同时还能降低计算量,分数数量尽量多不行吗?

分组卷积可以减少参数量,它用少量的参数量和运算量就可以生成大量的特征图,由此可以提取到更充分的模型特征,从而提升准确率。

当分组数目多到与输入层通道数相同(一个卷积核负责一个通道),卷积完成后的特征图数量与输入层的通道数也就相同,无法扩展特征图,而且这种运算对输入层的每个通道独立进行卷积运算,没法效的利用不同通道在相同空间位置上的特征信息。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值