DL_残差网络(ResNet)

为了解决两个问题:
1)退化问题(degradation problem)
2)梯度消失/爆炸

什么是resnet?

将网络学习目标改为学习残差函数,也就是目标值与预测值的差,通过一个跳跃连接可以解决梯度消失问题,这样就可以搭建更深的网络结构,得到更好的训练结果
image.png

卷积输入和输出尺寸关系:
image.png

模型退化

神经网络层数越深,网络就能进行更加复杂的特征提取,理论上可以取得更好的结果,但是实验发现网络深度增加时,训练误差和测试误差增大,这种现象称为模型退化问题。

image.png

梯度消失和爆炸

神经网络的经典问题,解决方法:对输入数据和中间层数据进行归一化操作,这种方法可以保证网络在反向传播中采用随机梯度下降(SGD),从而让网络达到收敛。但是这种方法只对几十层的网络有用,当网络深度增加时,效果就不理想了。

残差学习

假设建立深层网络,当我们不断堆积新的层,但是新增加的层什么也不学习,只是复制浅层网络的特征,即恒等映射(Identity mapping)。在这种情况下,深层网络应该至少和浅层网络性能一样,也不应该出现退化现象。

对于一个堆叠的网络结构,当输入为x时其学到的特征为H(x),现在希望其可以学到残差F(x)=H(x)-x,这样原始的学习特征为F(x)+x。之所以这样是因为残差学习相比原始特征直接学习更容易。当残差为0时, 此时堆积层仅仅做了恒等映射,至少网络性能不会下降,实际残差不会为0,这也会使得堆积层在输入特征基础上学习到新的特征,从而拥有更好的性能。

image.png

ResNet网络结构

ResNet参考了VGG19网络,在其基础上进行了修改,并通过短路机制加入了残差单元。变化主要体现在ResNet直接使用stride=2的卷积做下采样,并且用global average pool层替换了全连接层。
ResNet的一个重要设计原则是:当feature map大小降低一半时,feature map的数量增加一倍,这样保持了网络的复杂度。
image.png

image.png

注意,这里网络结构中padding部分没给,但是可以根据维度减半推出padding的大小。

残差单元

ResNet使用了两种残差单元,左侧对应的是浅层网络,右侧对应的是深层网络,称为Bottleneck Architectures。
image.png
三层残差单元结构的卷积核分别是1x1,3x3和1x1,通过1x1卷积来巧妙的缩减或扩张feature map维度,从而使得我们的3x3卷积的filter数目不受上一层输入的影响,输出也不会影响到下一层。中间3x3的卷积层首先在一个降维1x1卷积层下减少了计算,然后在另一个1x1的卷积层下做了还原。既保持了模型精度又减少了网络参数和计算量,节省了计算时间。

对于短路连接(shortcut),当输入和输出的维度一致时,可以直接将输入加到输出上。当输入和输出维度不一致时,就不能直接相加(对应的虚线连接),有两种策略:
(1)使用zero-padding增加维度,一般是先做一个downsampling,可以采用stride=2的pooling,这样不会增加参数。
(2)采用新的映射(projection shortcut),一般是采用1x1的卷积,这样会增加参数,也会增加计算量。当然恒等映射也可以使用projection shortcut,只是参数是方阵。

实验结果

ResNet使用了更深的网络,在ImageNet上采用了152层,是VGG的8倍深度,但仍然拥有较低的复杂度,由于网络层数更深,准确率更好,取得了2015年ImageNet分类任务和目标检测任务的冠军。
同样,在COCO 数据集中目标检测和图像分割任务上都取得了第一的成绩。

作者还尝试将网络增加到1000层,但是出现了退化问题,作者分析认为是产生了过拟合,数据集太小。

总结

ResNet是何恺明何博士的辉煌战绩之一,是CNN图像处理史上的一件里程碑事件,主要有如下两点:
(1)提出了残差网络结构,将网络的拟合对象转变为拟合残差。在一定程度上解决了模型退化问题以及网络的梯度消失/爆炸问题,突破了1000层的网络深度,使得大规模深度网络成为可能。
(2)使用Batch Normalization加速训练(舍弃dropout)

另外,论文还是有很多trick的,比如1x1的卷积增加/缩放维度,使用stride=2替代pooling等等,非常精彩的著作。

代码实现

本地代码存放路径为:~/PycharmProjects/DL_tutorials/CNNs/ResNet

"""  
ResNet模型搭建  
"""  
import torch.nn as nn  
import torch  
  
  
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  
  
  
class Bottleneck(nn.Module):  
    """  
    注意:原论文中,在虚线残差结构的主分支上,第一个1x1卷积层的步距是2,第二个3x3卷积层步距是1。  
    但在pytorch官方实现过程中是第一个1x1卷积层的步距是1,第二个3x3卷积层步距是2,  
    这么做的好处是能够在top1上提升大概0.5%的准确率。  
    可参考Resnet v1.5 https://ngc.nvidia.com/catalog/model-scripts/nvidia:resnet_50_v1_5_for_pytorch  
    """    expansion = 4  
  
    def __init__(self, in_channel, out_channel, stride=1, downsample=None,  
                 groups=1, width_per_group=64):  
        super(Bottleneck, self).__init__()  
  
        width = int(out_channel * (width_per_group / 64.)) * groups  
  
        self.conv1 = nn.Conv2d(in_channels=in_channel, out_channels=width,  
                               kernel_size=1, stride=1, bias=False)  # squeeze channels  
        self.bn1 = nn.BatchNorm2d(width)  
        # -----------------------------------------  
        self.conv2 = nn.Conv2d(in_channels=width, out_channels=width, groups=groups,  
                               kernel_size=3, stride=stride, bias=False, padding=1)  
        self.bn2 = nn.BatchNorm2d(width)  
        # -----------------------------------------  
        self.conv3 = nn.Conv2d(in_channels=width, out_channels=out_channel*self.expansion,  
                               kernel_size=1, stride=1, bias=False)  # unsqueeze channels  
        self.bn3 = nn.BatchNorm2d(out_channel*self.expansion)  
        self.relu = nn.ReLU(inplace=True)  
        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 = self.relu(out)  
  
        out = self.conv3(out)  
        out = self.bn3(out)  
  
        out += identity  
        out = self.relu(out)  
  
        return out  
  
  
class ResNet(nn.Module):  
  
    def __init__(self,  
                 block,  
                 blocks_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, blocks_num[0])  
        self.layer2 = self._make_layer(block, 128, blocks_num[1], stride=2)  
        self.layer3 = self._make_layer(block, 256, blocks_num[2], stride=2)  
        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')  
  
    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  
  
  
def resnet34(num_classes=1000, include_top=True):  
    # https://download.pytorch.org/models/resnet34-333f7ec4.pth  
    return ResNet(BasicBlock, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)  
  
  
def resnet50(num_classes=1000, include_top=True):  
    # https://download.pytorch.org/models/resnet50-19c8e357.pth  
    return ResNet(Bottleneck, [3, 4, 6, 3], num_classes=num_classes, include_top=include_top)  
  
  
def resnet101(num_classes=1000, include_top=True):  
    # https://download.pytorch.org/models/resnet101-5d3b4d8f.pth  
    return ResNet(Bottleneck, [3, 4, 23, 3], num_classes=num_classes, include_top=include_top)  
  
  
def resnext50_32x4d(num_classes=1000, include_top=True):  
    # https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth  
    groups = 32  
    width_per_group = 4  
    return ResNet(Bottleneck, [3, 4, 6, 3],  
                  num_classes=num_classes,  
                  include_top=include_top,  
                  groups=groups,  
                  width_per_group=width_per_group)  
  
  
def resnext101_32x8d(num_classes=1000, include_top=True):  
    # https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth  
    groups = 32  
    width_per_group = 8  
    return ResNet(Bottleneck, [3, 4, 23, 3],  
                  num_classes=num_classes,  
                  include_top=include_top,  
                  groups=groups,  
                  width_per_group=width_per_group)

参考

https://zhuanlan.zhihu.com/p/31852747
https://blog.csdn.net/m0_54487331/article/details/112758795
https://blog.csdn.net/qq_37541097/article/details/104710784
膜拜大神
原论文

Batch Normalization

https://blog.csdn.net/qq_37541097/article/details/104434557

Batch Normalization是google团队在2015年论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》提出的。通过该方法能够加速网络的收敛并提升准确率。之前的办法是减少学习率(不稳定的收敛),通过这种方法可以使用较大的学习率。

我们在进行图像预处理过程中通常进行标准化处理,进而作为网络的输入。而Batch Normalization的目的就是使我们的feature map也满足均值为0,方差为1的分布规律。
image.png
对于一个d维的输入,我们将对其每一个维度进行标准化处理。
image.png

举个例子,batch_size=2的RGB三通道的图像BN过程如下:
image.png

原文公式中的 γ \gamma γ β \beta β分别用来调整数值分布的方差大小和数值均值的位置,这两个参数是在反向传播过程中学习得到的, γ \gamma γ的默认值是1, β \beta β的默认值是0

使用BN的注意事项:

(1)训练时要将traning参数设置为True,在验证时将trainning参数设置为False。在pytorch中可通过创建模型的model.train()和model.eval()方法控制。

(2)batch size尽可能设置大点,设置小后表现可能很糟糕,设置的越大求的均值和方差越接近整个训练集的均值和方差。

(3)建议将bn层放在卷积层(Conv)和激活层(例如Relu)之间,且卷积层不要使用偏置bias,因为没有用,参考下图推理,即使使用了偏置bias求出的结果也是一样的
image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值