ShuffleNet V2

ShuffleNet V2 是一种轻量级卷积神经网络架构,专为移动设备和资源受限的环境设计。它在 ShuffleNet V1 的基础上进行了优化,尤其在计算效率和实际设备推理速度方面。ShuffleNet V2 由 Megvii (Face++) 团队提出,目标是解决移动设备上的深度学习模型在高效性和准确性之间的平衡问题。


ShuffleNet V2 的核心思想

ShuffleNet V2 的设计核心在于以下几点:

1. 优化实际速度

与 ShuffleNet V1 不同,ShuffleNet V2 不仅关注理论上的 FLOPs(浮点运算次数),还特别关注 实际设备上的速度。它提出了一些影响实际性能的关键原则:

  • 均衡的通道分配:每一层的输入和输出通道数应该保持一致,避免分组卷积的不均匀计算。
  • 减少内存访问成本:减少数据在不同内存之间的频繁传递。
  • 避免过多的分组卷积:虽然分组卷积可以减少计算量,但可能会带来额外的开销。
  • 减少碎片操作:避免复杂的操作,如元素级的操作,保持网络结构简单。
2. 深度可分离卷积

类似于 MobileNet,ShuffleNet V2 也利用了深度可分离卷积,将标准卷积分解为深度卷积和逐点卷积,从而大幅减少计算量和参数量。

3. 通道混洗操作(Channel Shuffle)

通道混洗是 ShuffleNet V 系列的核心创新点。为了在分组卷积中让特征更好地交互,ShuffleNet V2 引入了通道混洗操作:

  • 操作流程
    1. 将输入特征分为多个组。
    2. 对分组的特征图重新排列(打乱顺序)。
  • 优势:通过通道混洗,使得分组卷积的输出特征在后续层可以更好地交互,从而提升表达能力。
4. 分支结构(Branch Structure)

ShuffleNet V2 的倒残差模块采用了分支结构,分为两个并行的路径:

  • 主路径:进行标准卷积、深度卷积和逐点卷积操作。
  • 跳跃路径:直接传递输入,避免额外的计算。

ShuffleNet V2 的结构

ShuffleNet V2 的整体架构是由 多个阶段 堆叠而成,每个阶段由若干个倒残差模块组成。下表列出了 ShuffleNet V2 的主要结构配置:

输出大小通道数模块数量步幅
112×1122412
56×564842
28×289682
14×1419242
7×7102411


ShuffleNet V2 的关键模块

  1. 基本单元(Inverted Residual with Channel Shuffle)

    • 通过通道分组和通道混洗实现高效特征交互。
    • 在轻量级网络中通过分支结构实现快速推理。
  2. 通道混洗(Channel Shuffle)

    • 将通道分组并重新排列。
    • 在多个通道之间提供信息交换,避免分组卷积带来的信息隔离。
  3. 轻量级倒残差块(Lightweight Inverted Residual Block)

    • 在每个分支中使用深度卷积和逐点卷积。
    • 一侧通过深度卷积处理特征,另一侧直接跳跃传递输入,减少计算。

ShuffleNet V2 的优势

  1. 更高效的计算: ShuffleNet V2 不仅关注理论上的计算量(FLOPs),还显著优化了实际设备上的推理速度。

  2. 轻量化设计: 它的模型参数量较小,适合资源受限的移动设备和嵌入式设备。

  3. 优秀的表现: 在 ImageNet 等主流数据集上,ShuffleNet V2 以较少的计算量和参数量取得了与更复杂网络相当的性能。

  4. 模块化与可扩展性: ShuffleNet V2 的设计模块简单,易于在其他模型中集成或扩展。


PyTorch 实现

以下是一个简单的 ShuffleNet V2 加载和推理的代码示例:

import torch
import torch.nn as nn
from torch.autograd import Function


class ChannelShuffleFunction(Function):
    @staticmethod
    def forward(ctx, x, groups):
        batch_size, num_channels, height, width = x.size()
        channels_per_group = num_channels // groups

        # 调整形状并转置
        x = x.view(batch_size, groups, channels_per_group, height, width)
        x = torch.transpose(x, 1, 2).contiguous()

        # 保存张量形状以备后向传播
        ctx.save_for_backward(x)
        ctx.groups = groups

        # 展平
        x = x.view(batch_size, -1, height, width)
        return x

    @staticmethod
    def backward(ctx, grad_output):
        x, = ctx.saved_tensors
        groups = ctx.groups
        batch_size, groups, channels_per_group, height, width = x.size()

        # 调整形状并转置回去
        grad_output = grad_output.view(batch_size, channels_per_group, groups, height, width)
        grad_output = torch.transpose(grad_output, 1, 2).contiguous()
        grad_output = grad_output.view(batch_size, -1, height, width)
        return grad_output, None

class ChannelShuffle(nn.Module):
    def __init__(self, groups):
        super(ChannelShuffle, self).__init__()
        self.groups = groups

    def forward(self, x):
        return ChannelShuffleFunction.apply(x, self.groups)

class Conv(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding=0, groups=1, stride=1):
        super(Conv, self).__init__()
        self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=groups)
        self.bn = nn.BatchNorm2d(num_features=out_channels)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x
class Conv_DW(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, padding, stride=1):
        super(Conv_DW, self).__init__()
        self.conv = nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=kernel_size, stride=stride, padding=padding, groups=in_channels)
        self.bn = nn.BatchNorm2d(num_features=out_channels)

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        return x


class ChannelSplit(nn.Module):
    def __init__(self, split_ratio=0.5):
        super(ChannelSplit, self).__init__()
        self.split_ratio = split_ratio

    def forward(self, x):
        # 确定分割点
        c = x.size(1)
        split_point = int(c * self.split_ratio)

        # 分割通道
        x1, x2 = torch.split(x, [split_point, c - split_point], dim=1)
        return x1, x2

class Shuffle1(nn.Module):
    def __init__(self, channels):
        super(Shuffle1, self).__init__()
        self.split = ChannelSplit(split_ratio=0.5)
        channels = channels // 2
        self.conv1 = Conv(in_channels=channels, out_channels=channels, kernel_size=1, padding=0)
        self.conv2 = Conv_DW(in_channels=channels, out_channels=channels, kernel_size=3, padding=1)
        self.conv3 = Conv(in_channels=channels, out_channels=channels, kernel_size=1, padding=0)
        self.shuffle = ChannelShuffle(groups=2)

    def forward(self, x):
        x1, x2 = self.split(x)
        x1 = self.conv1(x1)
        x1 = self.conv2(x1)
        x1 = self.conv3(x1)
        x = torch.cat([x1, x2], dim=1)
        x = self.shuffle(x)
        return x

class Shuffle2(nn.Module):
    def __init__(self, in_channels, out_channels):
        super(Shuffle2, self).__init__()
        out_channels = out_channels // 2
        self.conv1 = Conv(in_channels=in_channels, out_channels=out_channels, kernel_size=1, padding=0)
        self.conv2 = Conv_DW(in_channels=out_channels, out_channels=out_channels, kernel_size=3, stride=2, padding=1)
        self.conv3 = Conv(in_channels=out_channels, out_channels=out_channels, kernel_size=1, padding=0)
        self.conv4 = Conv_DW(in_channels=in_channels, out_channels=in_channels, kernel_size=3, stride=2, padding=1)
        self.conv5 = Conv(in_channels=in_channels, out_channels=out_channels, kernel_size=1, padding=0)
        self.shuffle = ChannelShuffle(groups=2)

    def forward(self, x):
        x1 = self.conv1(x)
        x1= self.conv2(x1)
        x1 = self.conv3(x1)
        x2 = self.conv4(x)
        x2 = self.conv5(x2)
        x = torch.cat([x1, x2], dim=1)
        x = self.shuffle(x)
        return x


class MyNetwork(nn.Module):
    '''一般在init中来构建网络算子层的初始属性'''

    def __init__(self, num_class):
        # 继承父类中的初始构造函数
        super(MyNetwork, self).__init__()
        self.conv = nn.Sequential(
            Conv(in_channels=3, out_channels=24, kernel_size=3, padding=1, stride=2),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
            Shuffle2(in_channels=24, out_channels=116),
            Shuffle1(channels=116),
            Shuffle1(channels=116),
            Shuffle1(channels=116),
            Shuffle2(in_channels=116, out_channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle1(channels=232),
            Shuffle2(in_channels=232, out_channels=464),
            Shuffle1(channels=464),
            Shuffle1(channels=464),
            Shuffle1(channels=464),
            Conv(in_channels=464, out_channels=1024, kernel_size=1, padding=0),
            nn.AdaptiveAvgPool2d((1, 1)),
            Conv(in_channels=1024, out_channels=num_class, kernel_size=1, padding=0)
        )
        self.flat = nn.Flatten()
        self.softmax = nn.Softmax(dim=1)  # 分类层

    ''' forward,必须叫这个名称。但是输入的参数,可以有多少。至少有1个input_X'''

    def forward(self, input_X):
        x = self.conv(input_X)
        x = self.flat(x)
        out = self.softmax(x)
        return out

ShuffleNet V2 的应用场景

  1. 移动设备图像分类: ShuffleNet V2 是专为移动设备设计的,可以快速完成图像分类任务。

  2. 实时目标检测: 结合 SSD、YOLO 等目标检测框架,ShuffleNet V2 能够以较低的计算成本完成实时目标检测任务。

  3. 嵌入式系统: 由于其计算和内存的高效性,ShuffleNet V2 被广泛应用于物联网和嵌入式设备。

  4. 视频流分析: 在实时视频分析任务中,ShuffleNet V2 由于速度快,非常适合部署在摄像头或边缘设备上。


总结

ShuffleNet V2 是一款为移动和嵌入式设备设计的高效轻量级网络,它在优化理论计算量的同时,更注重在实际设备上的速度表现。通过通道混洗和轻量化设计,ShuffleNet V2 提供了一个强大、快速且易于集成的深度学习模型,是现代深度学习轻量化的典范之一。

### ShuffleNet V2 架构详解 #### 设计原则 ShuffleNet V2 的设计基于四个黄金准则,这些准则是通过大量实验得出的最佳实践[^1]: - **平衡计算量和内存访问成本 (MAC)**:实际运行时间不仅取决于 FLOPs 计算开销,还受到 MAC 影响。 - **最小化逐点卷积的数量**:减少 pointwise 卷积的比例有助于提升效率。 - **避免过多的网络碎片化**:增加分支数量会降低计算效率。 - **元素级操作不可忽视**:element-wise 操作虽然简单,但在深层网络中累积影响显著。 #### 主要改进点 相比 ShuffleNet V1,V2 版本做了如下重要改动: - 取消残差连接中的 shortcut path 上的 1×1 卷积层; - 将 channel shuffle 放置在每个基本模块结束处而非开始位置; - 去掉了 group convolution 中不必要的 ReLU 层; ```python class BasicUnit(nn.Module): def __init__(self, inp, oup, stride): super(BasicUnit, self).__init__() branch_main = [ nn.Conv2d(inp//2, inp//2, kernel_size=1), nn.BatchNorm2d(inp//2), nn.ReLU(inplace=True), nn.Conv2d(inp//2, inp//2, kernel_size=3, padding=1, groups=inp//2, stride=stride), nn.BatchNorm2d(inp//2), nn.Conv2d(inp//2, oup-inp//2, kernel_size=1), nn.BatchNorm2d(oup-inp//2), nn.ReLU(inplace=True) ] ``` #### 应用场景 由于其高效的特性,ShuffleNet V2 广泛应用于移动端设备上的实时图像处理任务,如: - 实时物体检测 - 图像分类 - 移动端视觉识别服务 该模型特别适合资源受限环境下的高性能需求应用开发[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值