经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用

news2024/11/14 13:42:28

经典轻量级神经网络(3)ShuffleNet V1及其在Fashion-MNIST数据集上的应用

1 ShuffleNet V1的简述

ShuffleNet 提出了 1x1分组卷积+通道混洗 的策略,在保证准确率的同时大幅降低计算成本。

ShuffleNet 专为计算能力有限的设备(如:10~150MFLOPs)设计。在基于ARM 的移动设备上,ShuffleNetAlexNet 相比,在保持相当的准确率的同时,大约 13 倍的加速

论文地址: https://arxiv.org/pdf/1707.01083.pdf

1.1 ShuffleNet的创新点

1.1.1 背景介绍

  • 建立更深、更大的卷积神经网络CNN是解决视觉识别任务的主要趋势。但通常具有数百个层和数千个通道,因此需要极大算力消耗。

  • 无人机,机器人和智能手机等常见的移动平台需要在非常有限的算力支撑下尽可能的提升准确率。

因此,旷视科技针对计算能力非常有限的移动设备,设计引入计算效率极高的CNN架构ShuffleNet。通过分组点卷积和通道重排两点创新,在保持精度的同时大大降低了计算成本。

1.1.2 分组点卷积(Pointwise group convolution)

XceptionResNeXt 中,有大量的1x1 卷积,所以整体而言1x1 卷积的计算开销较大。如ResNeXt 的每个残差块中,1x1 卷积占据了乘-加运算的 93.4% (基数为32时)。

在小型网络中,为了满足计算性能的约束(因为计算资源不够)需要控制计算量。虽然限制通道数量可以降低计算量,但这也可能会严重降低准确率。

解决办法是:对1x1 卷积应用分组卷积,将每个 1x1 卷积仅仅在相应的通道分组上操作,这样就可以降低每个1x1 卷积的计算代价。

在这里插入图片描述

论文的分组点卷积也就是说又点卷积又分组。也就是之前的分组卷积分析中,将卷积核的大小变为1×1的卷积。但是这里的点卷积就不是贯通输入全通道了,而是贯通分组后的每一组的全通道

1.1.3 通道重排(Channel shuffle)

1x1 卷积仅在相应的通道分组上操作会带来一个副作用:每个通道的输出仅仅与该通道所在分组的输入(一般占总输入的比例较小)有关,与其它分组的输入(一般占总输入的比例较大)无关。这会阻止通道之间的信息流动,降低网络的表达能力。

解决办法是:采用通道混洗,允许分组卷积从不同分组中获取输入。

  • 如下图所示:(a) 表示没有通道混洗的分组卷积;(b) 表示进行通道混洗的分组卷积;(c)(b) 的等效表示。
  • 由于通道混洗是可微的,因此它可以嵌入到网络中以进行端到端的训练。

在这里插入图片描述

1.1.4 ResNeXt 块和ShuffleNet

ShuffleNet 块的结构从ResNeXt 块改进而来:下图中(a) 是一个ResNeXt 块,(b) 是一个 ShuffleNet 块,(c) 是一个步长为2ShuffleNet 块。

ShuffleNet 块中:

  • 第一个1x1 卷积替换为1x1 分组卷积+通道随机混洗。

  • 第二个1x1 卷积替换为1x1 分组卷积,但是并没有附加通道随机混洗。这是为了简单起见,因为不附加通道随机混洗已经有了很好的结果。

  • 3x3 depthwise 卷积之后只有BN 而没有ReLU

  • 当步长为2时:

    • 恒等映射直连替换为一个尺寸为 3x3 、步长为2 的平均池化。

    • 3x3 depthwise 卷积的步长为2

    • 将残差部分与直连部分的feature map 拼接,而不是相加。

      因为当feature map 减半时,为了缓解信息丢失需要将输出通道数加倍从而保持模型的有效容量。

在这里插入图片描述

1.2 ShuffleNet网络性能

1.2.1 ShuffleNet网络结构

ShuffleNet 网络由ShuffleNet 块组成。

  • 网络主要由三个Stage 组成。

    • 每个Stage 的第一个块的步长为 2 ,stage 内的块在其它参数上相同。
    • 每个Stage 的输出通道数翻倍。
  • Stage2 的第一个1x1 卷积并不执行分组卷积,因此此时的输入通道数相对较小。

  • 每个ShuffleNet 块中的第一个1x1 分组卷积的输出通道数为:该块的输出通道数的 1/4

  • 使用较少的数据集增强,因为这一类小模型更多的是遇到欠拟合而不是过拟合。

  • 复杂度给出了计算量(乘-加运算),KSize 给出了卷积核的尺寸,Stride 给出了ShuffleNet block 的步长,Repeat 给出了 ShuffleNet block 重复的次数,g 控制了ShuffleNet block 分组的数量。

    g=1 时,1x1 的通道分组卷积退化回原始的1x1 卷积。

在这里插入图片描述

1.2.2 超参数g的影响

超参数 g 会影响模型的准确率和计算量。在 ImageNet 测试集上的表现如下:

  • ShuffleNet sx 表示对ShuffleNet 的通道数增加到 s 倍。这通过控制 Conv1 卷积的输出通道数来实现。

  • g 越大,则计算量越小,模型越准确。

    其背后的原理是:小模型的表达能力不足,通道数量越大,则小模型的表达能力越强。

    • g 越大,则准确率越高。这是因为对于ShuffleNet,分组越大则生成的feature map 通道数量越多,模型表达能力越强。
    • 网络的通道数越小(如ShuffleNet 0.25x ),则这种增益越大。
  • 随着分组越来越大,准确率饱和甚至下降。

    这是因为随着分组数量增大,每个组内的通道数量变少。虽然总体通道数增加,但是每个分组提取有效信息的能力变弱,降低了模型整体的表征能力。

  • 虽然较大gShuffleNet 通常具有更好的准确率。但是由于它的实现效率较低,会带来较大的推断时间。

在这里插入图片描述

1.2.3 混洗的效果

通道随机混洗的效果要比不混洗好。在 ImageNet 测试集上的表现如下:

  • 通道混洗使得分组卷积中,信息能够跨分组流动。
  • 分组数g 越大,这种混洗带来的好处越大。

在这里插入图片描述

1.2.4 在移动设备上的推断时间

在移动设备上的推断时间(Qualcomm Snapdragon 820 processor,单线程):

在骁龙820的处理器上,shuffleNet 0.5x 的错误率与 AlexNet 相当,但是推理速度为原来的十倍,通过对比能发现它有多么轻量级。suffleNet 2x 在推理速度和 MobileNet V1 相当的情况下,准确率要更好。

在这里插入图片描述

1.3 ShuffleNet代码实现

1.3.1 Shuffle的实现

shufflenet中代码比较有趣的点就在于shuffle的实现,实际上是利用了张量维度的变化来实现这一功能。

具体来说,对于下面这个图来讲,现在是channels分成了三组,每个组里有四个小球,于是就通过先reshape再转置再展开的方式来实现最后的目的。

在这里插入图片描述

对于通道数为c的输入,先将其拆分为g组,每组有n各通道。也就是说c=g×n。

  1. 进行拆分 reshape (c=g * n) -> (g, n)
  2. 进行转置 transpose (g, n) -> (n, g)
  3. 将其展开 flatten

具体的shuffle步骤的代码:

def channel_shuffle(x, groups):
    batchsize, num_channels, height, width = x.data.size()

    channels_per_group = num_channels // groups  # groups是分的组数

    # reshape
    x = x.view(batchsize, groups,channels_per_group, height, width)

    # transpose
    # - contiguous() required if transpose() is used before view().
    x = torch.transpose(x, 1, 2).contiguous()

    # flatten
    x = x.view(batchsize, -1, height, width)

    return x

1.3.2 ShuffleNet 块

在这里插入图片描述

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
from collections import OrderedDict
from torch.nn import init


def conv3x3(in_channels, out_channels, stride=1,padding=1, bias=True, groups=1):
    """3x3 convolution with padding
    """
    return nn.Conv2d(
        in_channels,
        out_channels,
        kernel_size=3,
        stride=stride,
        padding=padding,
        bias=bias,
        groups=groups)


def conv1x1(in_channels, out_channels, groups=1):
    """1x1 convolution with padding
    - Normal pointwise convolution When groups == 1
    - Grouped pointwise convolution when groups > 1
    """
    return nn.Conv2d(
        in_channels,
        out_channels,
        kernel_size=1,
        groups=groups,
        stride=1)


class ShuffleUnit(nn.Module):
    def __init__(self, in_channels, out_channels, groups=3,
                 grouped_conv=True, combine='add'):

        super(ShuffleUnit, self).__init__()

        self.in_channels = in_channels
        self.out_channels = out_channels
        self.grouped_conv = grouped_conv
        self.combine = combine
        self.groups = groups
        self.bottleneck_channels = self.out_channels // 4

        # define the type of ShuffleUnit
        if self.combine == 'add':
            # ShuffleUnit Figure 2b
            self.depthwise_stride = 1
            self._combine_func = self._add
        elif self.combine == 'concat':
            # ShuffleUnit Figure 2c
            self.depthwise_stride = 2
            self._combine_func = self._concat

            # ensure output of concat has the same channels as
            # original output channels.
            self.out_channels -= self.in_channels
        else:
            raise ValueError("Cannot combine tensors with \"{}\"" \
                             "Only \"add\" and \"concat\" are" \
                             "supported".format(self.combine))

        # Use a 1x1 grouped or non-grouped convolution to reduce input channels
        # to bottleneck channels, as in a ResNet bottleneck module.
        # NOTE: Do not use group convolution for the first conv1x1 in Stage 2.
        self.first_1x1_groups = self.groups if grouped_conv else 1

        self.g_conv_1x1_compress = self._make_grouped_conv1x1(
            self.in_channels,
            self.bottleneck_channels,
            self.first_1x1_groups,
            batch_norm=True,
            relu=True
        )

        # 3x3 depthwise convolution followed by batch normalization
        self.depthwise_conv3x3 = conv3x3(
            self.bottleneck_channels, self.bottleneck_channels,
            stride=self.depthwise_stride, groups=self.bottleneck_channels)
        self.bn_after_depthwise = nn.BatchNorm2d(self.bottleneck_channels)

        # Use 1x1 grouped convolution to expand from
        # bottleneck_channels to out_channels
        self.g_conv_1x1_expand = self._make_grouped_conv1x1(
            self.bottleneck_channels,
            self.out_channels,
            self.groups,
            batch_norm=True,
            relu=False
        )

    @staticmethod
    def _add(x, out):
        # residual connection
        return x + out

    @staticmethod
    def _concat(x, out):
        # concatenate along channel axis
        return torch.cat((x, out), 1)

    def _make_grouped_conv1x1(self, in_channels, out_channels, groups,
                              batch_norm=True, relu=False):

        modules = OrderedDict()

        conv = conv1x1(in_channels, out_channels, groups=groups)
        modules['conv1x1'] = conv

        if batch_norm:
            modules['batch_norm'] = nn.BatchNorm2d(out_channels)
        if relu:
            modules['relu'] = nn.ReLU()
        if len(modules) > 1:
            return nn.Sequential(modules)
        else:
            return conv

    def forward(self, x):
        # save for combining later with output
        residual = x

        if self.combine == 'concat':
            residual = F.avg_pool2d(residual, kernel_size=3,
                                    stride=2, padding=1)

        out = self.g_conv_1x1_compress(x)
        out = channel_shuffle(out, self.groups)
        out = self.depthwise_conv3x3(out)
        out = self.bn_after_depthwise(out)
        out = self.g_conv_1x1_expand(out)

        out = self._combine_func(residual, out)
        return F.relu(out)

1.3.3 ShuffleNet实现

class ShuffleNetV1(nn.Module):
    """ShuffleNet implementation.
    """

    def __init__(self, groups = 3, in_channels=3, num_classes=1000):
        """ShuffleNet constructor.

        Arguments:
            groups (int, optional): number of groups to be used in grouped
                1x1 convolutions in each ShuffleUnit. Default is 3 for best
                performance according to original paper.
            in_channels (int, optional): number of channels in the input tensor.
                Default is 3 for RGB image inputs.
            num_classes (int, optional): number of classes to predict. Default
                is 1000 for ImageNet.

        """
        super(ShuffleNetV1, self).__init__()

        self.groups = groups
        self.stage_repeats = [3, 7, 3]
        self.in_channels = in_channels
        self.num_classes = num_classes

        # index 0 is invalid and should never be called.
        # only used for indexing convenience.
        if groups == 1:
            self.stage_out_channels = [-1, 24, 144, 288, 567]
        elif groups == 2:
            self.stage_out_channels = [-1, 24, 200, 400, 800]
        elif groups == 3:
            self.stage_out_channels = [-1, 24, 240, 480, 960]
        elif groups == 4:
            self.stage_out_channels = [-1, 24, 272, 544, 1088]
        elif groups == 8:
            self.stage_out_channels = [-1, 24, 384, 768, 1536]
        else:
            raise ValueError(
                """{} groups is not supported for 1x1 Grouped Convolutions""".format(groups))

        # Stage 1 always has 24 output channels
        self.conv1 = conv3x3(self.in_channels,
                             self.stage_out_channels[1],  # stage 1
                             stride=2)
        self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)

        # Stage 2
        self.stage2 = self._make_stage(2)
        # Stage 3
        self.stage3 = self._make_stage(3)
        # Stage 4
        self.stage4 = self._make_stage(4)

        # Global pooling:
        # Undefined as PyTorch's functional API can be used for on-the-fly
        # shape inference if input size is not ImageNet's 224x224

        # Fully-connected classification layer
        num_inputs = self.stage_out_channels[-1]
        self.fc = nn.Linear(num_inputs, self.num_classes)
        self.init_params()

    def init_params(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                init.kaiming_normal(m.weight, mode='fan_out')
                if m.bias is not None:
                    init.constant(m.bias, 0)
            elif isinstance(m, nn.BatchNorm2d):
                init.constant(m.weight, 1)
                init.constant(m.bias, 0)
            elif isinstance(m, nn.Linear):
                init.normal(m.weight, std=0.001)
                if m.bias is not None:
                    init.constant(m.bias, 0)

    def _make_stage(self, stage):
        modules = OrderedDict()
        stage_name = "ShuffleUnit_Stage{}".format(stage)

        # First ShuffleUnit in the stage
        # 1. non-grouped 1x1 convolution (i.e. pointwise convolution)
        #   is used in Stage 2. Group convolutions used everywhere else.
        grouped_conv = stage > 2

        # 2. concatenation unit is always used.
        first_module = ShuffleUnit(
            self.stage_out_channels[stage - 1],
            self.stage_out_channels[stage],
            groups=self.groups,
            grouped_conv=grouped_conv,
            combine='concat'
        )
        modules[stage_name + "_0"] = first_module

        # add more ShuffleUnits depending on pre-defined number of repeats
        for i in range(self.stage_repeats[stage - 2]):
            name = stage_name + "_{}".format(i + 1)
            module = ShuffleUnit(
                self.stage_out_channels[stage],
                self.stage_out_channels[stage],
                groups=self.groups,
                grouped_conv=True,
                combine='add'
            )
            modules[name] = module

        return nn.Sequential(modules)

    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool(x)

        x = self.stage2(x)
        x = self.stage3(x)
        x = self.stage4(x)

        # global average pooling layer
        x = F.avg_pool2d(x, x.data.size()[-2:])

        # flatten for input to fully-connected layer
        x = x.view(x.size(0), -1)
        x = self.fc(x)

        return x
    

if __name__ == '__main__':
    net = ShuffleNetV1(in_channels=1, num_classes=10)
    X = torch.rand(size=(1, 1, 224, 224), dtype=torch.float32)
    print(net(X).shape)

2 ShuffleNet V1在Fashion-MNIST数据集上的应用示例

2.1 创建ShuffleNet V1网络模型

ShuffleNet V1网络,如1.3所示。

2.2 读取Fashion-MNIST数据集

其他所有的函数,与经典神经网络(1)LeNet及其在Fashion-MNIST数据集上的应用完全一致。

# 我们将图片大小设置224×224
# 训练机器内存有限,将批量大小设置为64
batch_size = 64

train_iter,test_iter = get_mnist_data(batch_size,resize=224)

2.3 在GPU上进行模型训练

from _10_ShuffleNetV1 import ShuffleNetV1

# 初始化模型,并设置为10分类
net = ShuffleNetV1(in_channels=1, num_classes=10)

lr, num_epochs = 0.1, 10
train_ch(net, train_iter, test_iter, num_epochs, lr, try_gpu())

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/736479.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

开发uniapp苹果app,苹果签名证书的创建方法

在uniapp云打包界面,打包苹果app,需要私钥证书p12文件,还需要证书profile文件和证书密码。 这两个文件到底是从什么地方获取的呢?答案是这两个证书需要在苹果开发者中心生成,下面我们这篇教程,将教会大家如…

Java小白的学习之路——day12

目录 一、final 什么是final? 二、接口概述 什么是接口? 与抽象类的区别 常量接口 接口传参多态 四、内部类 什么是内部类? 成员内部类 静态内部类 局部内部类 一、final 什么是final? final从字面意思来看时最终的&a…

你真的了解JS垃圾回收机制吗?

目录 前言 堆栈内存管理 JS垃圾回收机制 标记清除(Mark and Sweep) 标记阶段 清除阶段 标记清除的特点 优点 缺点 引用计数(Reference Counting) 引用计数器的维护 引用计数的跟踪 垃圾回收的触发 回收对象 引用计…

视频转音频MP3格式怎么做?教你几种转换小妙招

当我们需要编辑视频中的声音,例如去除噪音、调整音量、加入配乐等,此时需要先将视频中的音频提取出来进行编辑,再将编辑后的音频重新与视频合并,以便达到一个最佳效果。那么怎么将视频转换成MP3格式的音频文件呢?教大家…

SpringBoot项目多模块打包部署Docker实战

前言 我们好多程序员都只关注功能代码的编写,在一些运维工作上则显得略有不足。这篇文章通过介绍最常见的Maven管理的Spring Boot项目多模块打包部署Docker来介绍一下项目部署过程中操作流程和几个需要注意的点。文章假设读者有前面提到的技术点的前置知识&#xf…

C#(五十八)之C#List

前几天&#xff0c;看同事写的代码中有list相关的字眼&#xff0c;百度了一下&#xff0c;原来是C#中list泛型集合。 了解一下。 List&#xff1a;泛型集合&#xff0c;List<T>类是 ArrayList 类的泛型等效类。该类使用大小可按需动态增加的数组实现 IList<T> 泛型…

Maven工程分模块开发讲解及入门案例

1.分模块开发的意义 一个模块只做自己对应的功能&#xff0c;提升开发效率&#xff0c;将一个工程拆分成若干个子模块方便之间相互调用&#xff0c;接口共享&#xff0c;降低耦合度提高代码复用率。 2.分模块开发入门案例 下面将domain这个模块从当前模块当中给拆分出来。 …

开心档之CSS 测验

目录 CSS 测验 CSS 测验 CSS测验是一种衡量前端开发人员对CSS的熟练程度的测试。通过CSS测验&#xff0c;可以评估一个人对CSS语言的掌握程度和应用能力&#xff0c;帮助公司或招聘方挑选合适的人才。下面将介绍如何进行CSS测验以及一些常见的CSS考题。 一、CSS测验的类型 1…

OpenCV 入门教程:寻找和绘制轮廓

OpenCV 入门教程&#xff1a;寻找和绘制轮廓 导语一、寻找轮廓二、绘制轮廓三、示例应用3.1 目标检测和定位3.2 图像分割 总结 导语 寻找和绘制轮廓是图像处理中常用的技术之一&#xff0c;用于识别、定位和分析图像中的目标区域。在 OpenCV 中&#xff0c;寻找和绘制轮廓可以…

「2024」预备研究生mem-行程问题

一、行程问题 二、课后题 往返 上山下山

LeetCode[75]颜色分类

难度:Medium 题目&#xff1a; 给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums &#xff0c;原地对它们进行排序&#xff0c;使得相同颜色的元素相邻&#xff0c;并按照红色、白色、蓝色顺序排列。 我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。 必须在不使用库…

【前端面试专栏】用户输入网址到页面返回都发生了什么?

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

软件安全测试流程与方法分享(下)

安全测试是在IT软件产品的生命周期中&#xff0c;特别是产品开发基本完成到发布阶段&#xff0c;对产品进行检验以验证产品符合安全需求定义和产品质量标准的过程。安全是软件产品的一个重要特性&#xff0c;也是CNAS测试认证中非常重要的项目&#xff0c;本系列文章我们与大家…

linux 信号原理 信号处理设置signal, 信号发送kill,信号等待sigsuspend,信号阻塞sigprocmask,一网打尽信号使用

​专栏内容&#xff1a; postgresql内核源码分析 手写数据库toadb 并发编程 个人主页&#xff1a;我的主页 座右铭&#xff1a;天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物. 概述 信号是一种软中断的方式&#xff0c;让进程陷入中断处理调…

vector的resver和resize

#include <iostream> #include <vector> using namespace std; int main() {std::vector<std::vector<std::vector<int> > > a(2);//创建2个vector<vector<int> >类型的数组vector<int> vec;//vec.resize(10); //结果1vec.res…

【Linux之拿捏信号3】阻塞信号

文章目录 相关概念原理sigset_t信号集信号集操作函数sigprocmask系统调用sigpending 相关概念 实际执行信号的处理动作——信号递达Delivery&#xff08;例如自定义捕捉动作&#xff0c;core&#xff0c;Term终止进程的动作&#xff09;。信号从产生到递达之间的状态——信号未…

Verilog基础之十四、FIFO实现

目录 一、FIFO 1.1 定义 1.2 实现方式 1.3 实现原理 二、代码实现 三、仿真结果 3.1 复位阶段 3.2 写入阶段 3.3 读取阶段 3.4 同时读写或不读不写 四、参考资料 一、FIFO 1.1 定义 FIFO(First in First out)为先进先出队列&#xff0c;具有存储功能&#xff0c;…

一篇带你彻底搞懂线程池

目录 一、自定义线程池 1、产生背景 2、堵塞队列 3、线程池 4、拒绝策略 二、ThreadPoolExecuor 1、线程池状态 2、构造方法 3、newFixedThreadPool 4、newCachedThreadPool 5、newSingleThreadExecutor 6、提交任务 7、关闭线程池 三、异步模式之工作线程 1、定…

C-数据的储存(上)

文章目录 前言&#x1f31f;一、数据类型详细介绍&#x1f30f;1.内置类型&#x1f4ab;&#xff08;1&#xff09;.整形家族&#x1f4ab;&#xff08;2&#xff09;.浮点数家族&#x1f30f;2.构造类型&#xff08;也称自定义类型&#xff09;&#x1f30f;3.指针类型&#x…

OpenCV 入门教程:Haar特征分类器

OpenCV 入门教程&#xff1a; Haar 特征分类器 导语一、Haar特征分类器原理二、Haar特征分类器步骤三、示例应用总结 导语 Haar 特征分类器是图像处理中常用的目标检测算法&#xff0c;用于识别图像中的特定目标。该算法基于 Haar-like 特征模板&#xff0c;通过训练分类器来实…