【论文泛读】 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()


相关文章
|
6天前
|
机器学习/深度学习 人工智能
类人神经网络再进一步!DeepMind最新50页论文提出AligNet框架:用层次化视觉概念对齐人类
【10月更文挑战第18天】这篇论文提出了一种名为AligNet的框架,旨在通过将人类知识注入神经网络来解决其与人类认知的不匹配问题。AligNet通过训练教师模型模仿人类判断,并将人类化的结构和知识转移至预训练的视觉模型中,从而提高模型在多种任务上的泛化能力和稳健性。实验结果表明,人类对齐的模型在相似性任务和出分布情况下表现更佳。
19 3
|
18天前
|
机器学习/深度学习 Web App开发 人工智能
轻量级网络论文精度笔(一):《Micro-YOLO: Exploring Efficient Methods to Compress CNN based Object Detection Model》
《Micro-YOLO: Exploring Efficient Methods to Compress CNN based Object Detection Model》这篇论文提出了一种基于YOLOv3-Tiny的轻量级目标检测模型Micro-YOLO,通过渐进式通道剪枝和轻量级卷积层,显著减少了参数数量和计算成本,同时保持了较高的检测性能。
27 2
轻量级网络论文精度笔(一):《Micro-YOLO: Exploring Efficient Methods to Compress CNN based Object Detection Model》
|
18天前
|
机器学习/深度学习 编解码 算法
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
MobileNetV3是谷歌为移动设备优化的神经网络模型,通过神经架构搜索和新设计计算块提升效率和精度。它引入了h-swish激活函数和高效的分割解码器LR-ASPP,实现了移动端分类、检测和分割的最新SOTA成果。大模型在ImageNet分类上比MobileNetV2更准确,延迟降低20%;小模型准确度提升,延迟相当。
42 1
轻量级网络论文精度笔记(三):《Searching for MobileNetV3》
|
5月前
|
安全 网络协议 网络安全
端口转发:解锁网络访问的新维度
端口转发技术,简化网络数据流,用于家庭至企业服务器场景。它隐藏内部网络服务,提供远程访问、个人网站公开、NAT穿透及安全的VPN连接。设置涉及路由器管理界面,添加转发规则,但需注意安全风险,仅开放必要端口并加强内部安全措施。了解和善用端口转发,提升网络服务可达性与安全性。
263 5
|
2月前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
92 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
5月前
|
机器学习/深度学习 PyTorch 算法框架/工具
【从零开始学习深度学习】31. 卷积神经网络之残差网络(ResNet)介绍及其Pytorch实现
【从零开始学习深度学习】31. 卷积神经网络之残差网络(ResNet)介绍及其Pytorch实现
|
18天前
|
编解码 人工智能 文件存储
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
YOLOv7是一种新的实时目标检测器,通过引入可训练的免费技术包和优化的网络架构,显著提高了检测精度,同时减少了参数和计算量。该研究还提出了新的模型重参数化和标签分配策略,有效提升了模型性能。实验结果显示,YOLOv7在速度和准确性上超越了其他目标检测器。
35 0
轻量级网络论文精度笔记(二):《YOLOv7: Trainable bag-of-freebies sets new state-of-the-art for real-time object ..》
|
6月前
|
机器学习/深度学习 计算机视觉 知识图谱
【YOLOv8改进】MobileViT 更换主干网络: 轻量级、通用且适合移动设备的视觉变压器 (论文笔记+引入代码)
MobileViT是针对移动设备的轻量级视觉Transformer网络,结合CNN的局部特征、Transformer的全局注意力和ViT的表示学习。在ImageNet-1k上,它以600万参数实现78.4%的top-1准确率,超越MobileNetv3和DeiT。MobileViT不仅适用于图像分类,还在目标检测等任务中表现出色,且优化简单,代码已开源。YOLOv8引入了MobileViT块,整合卷积和Transformer结构,提升模型性能。更多详情可参考相关专栏和链接。
|
2月前
|
机器学习/深度学习 算法 数据安全/隐私保护
基于NSCT非采样轮廓波变换和CNN网络人脸识别matlab仿真
本项目展示了一种结合非采样轮廓波变换(NSCT)与卷积神经网络(CNN)的人脸识别系统。通过NSCT提取多尺度、多方向特征,并利用CNN的强大分类能力实现高效识别。项目包括ORL人脸库的训练结果对比,提供Matlab 2022a版本下的完整代码及详细中文注释,另有操作步骤视频指导。
|
3月前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
49 0