【论文泛读】 ResNeXt:深度神经网络的聚合残差变换(ResNet的改进,提出了一种新的维度)

简介: 【论文泛读】 ResNeXt:深度神经网络的聚合残差变换(ResNet的改进,提出了一种新的维度)

主要思想


简单来说呢,随着很多SOTA模型的出现,从一开始的“特征工程”慢慢地转入了一些“网络工程”,引入了一些新的结构和方法。


在前面说过的网络中呢,VGG作为一种经典的模型,他提出了堆叠相同块(stacking block)的策略,在VGG中是3x3卷积核不断堆叠,并且也通过增加深度提高了准确率。


除此之外,Inception系列网络提出了split-transform-merge的策略,通过多分支卷积实现在低计算开销的前提下去接近大型密集层的表达能力。而这篇文章则是合了stacking block和split-transform-merge的策略,在ResNet的基础上提出了ResNeXt网络架构。


摘要


这里还是简单讲一下摘要,作者提出了一种简单、高度模块化的图像分类网络体系结构。我们的网络是通过重复一个构建块来构建的,该构建块聚合了一组具有相同拓扑的变换。我们的简单设计导致了一个同构的、多分支的体系结构,只需要设置几个超参数。这一策略体现了一个新的维度,我们称之为“Cardinality”(转换集的大小),作为深度和宽度维度之外的一个基本因素。在ImageNet-1K数据集上的实验表明,即使在保持复杂度的限制条件下,增加基数也能够提高分类精度。而且,当我们增加容量时,增加基数比深入或扩大更有效。我们的模型ResNeXt是我们进入ILSVRC2016分类任务的基础,我们获得了第二名。我们进一步研究了ImageNet-5K集合和COCO检测集合上的ResNeXt,也显示了比ResNet对应的更好的结果。


简单来说呢,就是ResNeXt采用 VGGs/ResNets 的网络的 depth 加深方式,同时利用 split-transform-merge 策略.


模型结构


Inception模块

首先我们可以讲一下Inception模块,Inception模块提出了split-transform-merge的策略,但是存在一个问题**网络的超参数设定的针对性比较强,当应用在别的数据集上时需要修改许多参数,因此可扩展性一般。**所以说,如果我们要用到自己的数据集,就要重新设置一下我们的众多的超参数

14c09c410ad1412db077cb242df08f33.png



split-transform-merge


作者提出了ResNeXt,同时采用 VGG 堆叠的思想和 Inception 的 split-transform-merge 思想,但是可扩展性比较强,论文中也给了一个架构图,X到xi实际上就是一个split的过程,wx就是一个transfrom的过程,最后相加就是一个merge的过程。


  1. Splitting,X分解为多个低维向量Xi;
  2. Transforming,每个Xi对应一个Wi以进行变换;
  3. Aggregating/Merge,每个Xi变换后进行累加



18ab9707abceeb44a6614967656aacf1.png



其中将Wi替换为更复杂的函数T(Xi)。在ResNeXt网络中,变换函数使用ResNet中的Bottleneck形式来替代。


分组卷积


分组卷积的思想呢,实际上在很早的AlexNet中就有出现了,在AlexNet的时候,由受限于当时硬件的限制,作者不得不将卷积操作拆分到两台GPU上运行,这两台GPU的参数是不共享的。而我们的分组卷积也是如此,如果我们组数与我们的通道数相同的话,也就是每一个通道利用一个卷积核,这就会变成我们的MobileNet的DW卷积结构了,也就是深度可分离卷积。


1c0e2e774cd612c2f5756157d2bfb9b3.png


简单来说:Grouped Convolutions:group conv 层中,输入和输出的 channels 被分为 CC 个 groups,分别对每个 group 进行 conv.

比如这个就是深度可分离卷积,每一个通道对应这一个卷积



6a63b423ec4645bc8344b2bada244aaf.png


ResNeXt模型结构

2ac26d6a2c339006d7f6cf63dd53db69.png

ResNeXt对ResNet进行了改进,采用了多分支的策略,在论文中作者提出了三种等价的模型结构,最后的ResNeXt用了©的结构来构建我们的ResNeXt、这里面和我们的Inception是不同的,在Inception中,每一部分的拓扑结构是不同的,比如一部分是1x1卷积,3x3卷积还有5x5卷积,而我们ResNeXt是用相同的拓扑结构,并在保持参数量的情况下提高了准确率。


这三个结构实际上是等价的,对于选c的原因,其实是因为c结构比较简洁而且速度更快。


63b0e5ec03857f57464e3018f0723635.png


这个构建基于两个准则


  1. 同stage中的block使用相同的width和filter size;
  2. spatial size减小时,增加channel的数量。


除此之外,作者还提出了一个新的维度,也就是基数(Cardinality)也就是转换集的大小,原文解释是the size of the set of transformations,并且论文中说,这是比深度和宽度更有效的一个维度


除此之外,ResNeXt 只能在 block 的 depth>3时使用. 如果 block 的 depth=2,则会得到宽而密集的模块,所以这也是为什么不在ResNet18和34进行修改的原因


95f5cfe8dd076328eeec9a21eee7015b.png


ResNeXt模型评估及结论


基于上述准则,文章在ResNet-50模型的基础上,提出了ResNeXt-50模型。

我们可以看到,这两者模型的参数是差不多的,复杂度也是差不多的,但是最后在经典数据集ImageNet1k中得到的准确率,ResNeXt更胜一筹。



8f3e86cf91fb6dad6e8a8d58955eb7c9.png


探究Cardinality


这个表主要列举了一些参数,来探究我们的Cardiality,也用来说明模型的结构和参数也是差不多的,第二行的d表示每个path的中间channels数量,最后一行则表示整个block的宽度,是第一行C和第二行d的乘积。


0c688f9e2c55a7d3992441e15789f57f.png


以下就是我们的实验结果,可以看到ResNeXt-50是优于ResNet50的

153fc348144e4098e88bc367e546d737.png


主要说明增加Cardinality和增加深度或宽度的区别,增加宽度就是简单地增加filter channels。第一个是基准模型,增加深度和宽度的分别是第三和第四个,可以看到误差分别降低了0.3%和0.7%。但是第五个加倍了Cardinality,则降低了1.3%,第六个Cardinality加到64,则降低了1.6%。显然增加Cardianlity比增加深度或宽度更有效。结果表明,增加基数可以提高模型的性能,且要比增加宽度和深度更有效。


f378aa0cb5a154c008d60aec1bbdba89.png


此外,模型还对基数(分支数)进行了对比试验。


ca8099e81daaff72f0069fe79923552f.png


和当前最好的模型的对比


对我们会发现,虽然说ResNeXt与Inception差不多,但是有一个点很重要,计算效率和复杂性是大大高于我们的Inceptionv3的

a61afb9482cca2eb7780a1ba0e768fa0.png


论文还在简单的CIFAR10数据集作了研究,模型还对比验证了残差结构的有效性,实验表明,在深层网络中引入残差结构,可以明显提高模型的性能



79b8f29c3e32518e60c7703b28ad033d.png


总结和感想


在这篇论文中,我觉得比较好的就是,ResNeXt虽然并没有提出一种很奇特的结构,而是在ResNet的基础上增加了分组卷积的方式。并且在保持模型参数变化不大的基础下进行的,最后的ResNeXt50与ResNet50参数相似,但是性能却是更好的。最后也拿了当时2016的ILSVRC的图像分类的第二名。


最后总结来说,作者的核心创新点就在于提出了 aggregrated transformations,用一种平行堆叠相同拓扑结构的blocks代替原来 ResNet 的三层卷积的block,在不明显增加参数量级的情况下提升了模型的准确率,同时由于拓扑结构相同,超参数也减少了,便于模型移植。


Pytorch代码实现


最后附一下他的ResNeXt的pytorch实现吧,这是基于ImageNet的,并且是在ResNet上进行修改。

对于CIFAR10的是网络分类的实现,可以到时候关注我们图像分类篇进行一个添加学习


'''
ResNeXt in PyTorch.
See the paper "Aggregated Residual Transformations for Deep Neural Networks" for more details.
这一部分借鉴官方的实现方式,改ResNet进行训练
'''
from numpy import pad
import torch
import torch.nn as nn
import torch.nn.functional as F
class BasicBlock(nn.Module):
    # 最基础的Block的压缩为1,在ResNet18,34有效
    expansion = 1
    # downsample代表:是否进行下采样,或者说我们是否需要一个shortcut,也就是我们的下采样
    def __init__(self, in_channel, out_channel, stride=1, downsample=None, **kwargs):
        super(BasicBlock, self).__init__()
        # 首先是一个3x3的卷积层,这里的stride是我们设置的,因为对于我们的第一个卷积层,stride为2
        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
    # 这里相对于简单的残差网络中多增加了两个参数,一个是groups和width_per_group,分别是组卷积的个数和每个组卷积的通道数
    # 默认值就是正常的ResNet
    def __init__(self, in_channel, out_channel, stride=1, downsample=None,
                 groups=1, width_per_group=64):
        super(Bottleneck, self).__init__()
        # 这里也可以自动计算中间的通道数,也就是3x3卷积后的通道数,如果不改变就是out_channels
        # 如果groups=32,with_per_group=4,out_channels就翻倍了
        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, # 表示block的类型
                 blocks_num, # 表示的是每一层block的个数
                 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)
# 论文中的ResNeXt50_32x4d
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)
def test():
    net = ResNeXt50_32x4d(num_classes=10)
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    net = net.to(device)
    # x = torch.randn(2,3,32,32).to(device)
    # y = net(x)
    # print(y.size())
    from torchinfo import summary
    print(summary(net,(1,3,224,224)))
test()


相关文章
|
2月前
|
安全 网络协议 网络安全
端口转发:解锁网络访问的新维度
端口转发技术,简化网络数据流,用于家庭至企业服务器场景。它隐藏内部网络服务,提供远程访问、个人网站公开、NAT穿透及安全的VPN连接。设置涉及路由器管理界面,添加转发规则,但需注意安全风险,仅开放必要端口并加强内部安全措施。了解和善用端口转发,提升网络服务可达性与安全性。
144 5
|
3月前
|
机器学习/深度学习 计算机视觉 知识图谱
【YOLOv8改进】MobileViT 更换主干网络: 轻量级、通用且适合移动设备的视觉变压器 (论文笔记+引入代码)
MobileViT是针对移动设备的轻量级视觉Transformer网络,结合CNN的局部特征、Transformer的全局注意力和ViT的表示学习。在ImageNet-1k上,它以600万参数实现78.4%的top-1准确率,超越MobileNetv3和DeiT。MobileViT不仅适用于图像分类,还在目标检测等任务中表现出色,且优化简单,代码已开源。YOLOv8引入了MobileViT块,整合卷积和Transformer结构,提升模型性能。更多详情可参考相关专栏和链接。
|
16天前
|
机器学习/深度学习 算法 网络架构
神经网络架构殊途同归?ICML 2024论文:模型不同,但学习内容相同
【8月更文挑战第3天】《神经语言模型的缩放定律》由OpenAI研究人员完成并在ICML 2024发表。研究揭示了模型性能与大小、数据集及计算资源间的幂律关系,表明增大任一资源均可预测地提升性能。此外,论文指出模型宽度与深度对性能影响较小,较大模型在更多数据上训练能更好泛化,且能高效利用计算资源。研究提供了训练策略建议,对于神经语言模型优化意义重大,但也存在局限性,需进一步探索。论文链接:[https://arxiv.org/abs/2001.08361]。
17 1
|
12天前
|
人工智能 算法 安全
【2023 年第十三届 MathorCup 高校数学建模挑战赛】C 题 电商物流网络包裹应急调运与结构优化问题 赛后总结之31页论文及代码
本文总结了2023年第十三届MathorCup高校数学建模挑战赛C题的解题过程,详细阐述了电商物流网络在面临突发事件时的包裹应急调运与结构优化问题,提出了基于时间序列预测、多目标优化、遗传算法和重要性评价模型的综合解决方案,并提供了相应的31页论文和代码实现。
22 0
|
3月前
|
机器学习/深度学习 自然语言处理 搜索推荐
【传知代码】图神经网络长对话理解-论文复现
在ACL2023会议上发表的论文《使用带有辅助跨模态交互的关系时态图神经网络进行对话理解》提出了一种新方法,名为correct,用于多模态情感识别。correct框架通过全局和局部上下文信息捕捉对话情感,同时有效处理跨模态交互和时间依赖。模型利用图神经网络结构,通过构建图来表示对话中的交互和时间关系,提高了情感预测的准确性。在IEMOCAP和CMU-MOSEI数据集上的实验结果证明了correct的有效性。源码和更多细节可在文章链接提供的附件中获取。
【传知代码】图神经网络长对话理解-论文复现
|
2月前
|
机器学习/深度学习 算法
基于BP神经网络和小波变换特征提取的烟草香型分类算法matlab仿真,分为浓香型,清香型和中间香型
```markdown 探索烟草香型分类:使用Matlab2022a中的BP神经网络结合小波变换。小波分析揭示香气成分的局部特征,降低维度,PCA等用于特征选择。BP网络随后处理这些特征,以区分浓香、清香和中间香型。 ```
|
2月前
|
Web App开发 自然语言处理 算法
一文搞懂:【论文笔记】BINE:二分网络嵌入
一文搞懂:【论文笔记】BINE:二分网络嵌入
21 0
|
3月前
|
计算机视觉
【YOLOv8改进】 AFPN :渐进特征金字塔网络 (论文笔记+引入代码).md
YOLO目标检测专栏介绍了YOLO的有效改进和实战案例,包括AFPN——一种解决特征金字塔网络信息丢失问题的新方法。AFPN通过非相邻层直接融合和自适应空间融合处理多尺度特征,提高检测性能。此外,还展示了YOLOv8中引入的MPDIoU和ASFF模块的代码实现。详情可参考提供的专栏链接。
|
3月前
|
机器学习/深度学习 存储 测试技术
【YOLOv8改进】 YOLOv8 更换骨干网络之 GhostNet :通过低成本操作获得更多特征 (论文笔记+引入代码).md
YOLO目标检测专栏探讨了卷积神经网络的创新改进,如Ghost模块,它通过低成本运算生成更多特征图,降低资源消耗,适用于嵌入式设备。GhostNet利用Ghost模块实现轻量级架构,性能超越MobileNetV3。此外,文章还介绍了SegNeXt,一个高效卷积注意力网络,提升语义分割性能,参数少但效果优于EfficientNet-L2。专栏提供YOLO相关基础解析、改进方法和实战案例。
|
3月前
|
机器学习/深度学习 监控 自动驾驶
【传知代码】从零开始搭建图像去雾神经网络-论文复现
本文介绍了基于集成学习的双分支非均匀去雾神经网络的复现,该网络由迁移学习子网和数据拟合子网组成,分别处理全局表示和数据拟合。网络使用Res2Net作为编码器,并结合通道和像素注意力模块。代码可在提供的链接下载。网络在交通监控、自动驾驶、航海和目标跟踪等领域有广泛应用,通过提升图像质量来提高系统性能。实验在O-Haze、I-Haze和NH-Haze数据集上进行,展示了网络在去除雾霾方面的效果,尽管存在细节模糊和色彩饱和度低的问题。

热门文章

最新文章