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 引入了通道混洗操作:
- 操作流程:
- 将输入特征分为多个组。
- 对分组的特征图重新排列(打乱顺序)。
- 优势:通过通道混洗,使得分组卷积的输出特征在后续层可以更好地交互,从而提升表达能力。
4. 分支结构(Branch Structure)
ShuffleNet V2 的倒残差模块采用了分支结构,分为两个并行的路径:
- 主路径:进行标准卷积、深度卷积和逐点卷积操作。
- 跳跃路径:直接传递输入,避免额外的计算。
ShuffleNet V2 的结构
ShuffleNet V2 的整体架构是由 多个阶段 堆叠而成,每个阶段由若干个倒残差模块组成。下表列出了 ShuffleNet V2 的主要结构配置:
输出大小 | 通道数 | 模块数量 | 步幅 |
---|---|---|---|
112×112 | 24 | 1 | 2 |
56×56 | 48 | 4 | 2 |
28×28 | 96 | 8 | 2 |
14×14 | 192 | 4 | 2 |
7×7 | 1024 | 1 | 1 |
ShuffleNet V2 的关键模块
-
基本单元(Inverted Residual with Channel Shuffle):
- 通过通道分组和通道混洗实现高效特征交互。
- 在轻量级网络中通过分支结构实现快速推理。
-
通道混洗(Channel Shuffle):
- 将通道分组并重新排列。
- 在多个通道之间提供信息交换,避免分组卷积带来的信息隔离。
-
轻量级倒残差块(Lightweight Inverted Residual Block):
- 在每个分支中使用深度卷积和逐点卷积。
- 一侧通过深度卷积处理特征,另一侧直接跳跃传递输入,减少计算。
ShuffleNet V2 的优势
-
更高效的计算: ShuffleNet V2 不仅关注理论上的计算量(FLOPs),还显著优化了实际设备上的推理速度。
-
轻量化设计: 它的模型参数量较小,适合资源受限的移动设备和嵌入式设备。
-
优秀的表现: 在 ImageNet 等主流数据集上,ShuffleNet V2 以较少的计算量和参数量取得了与更复杂网络相当的性能。
-
模块化与可扩展性: 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 的应用场景
-
移动设备图像分类: ShuffleNet V2 是专为移动设备设计的,可以快速完成图像分类任务。
-
实时目标检测: 结合 SSD、YOLO 等目标检测框架,ShuffleNet V2 能够以较低的计算成本完成实时目标检测任务。
-
嵌入式系统: 由于其计算和内存的高效性,ShuffleNet V2 被广泛应用于物联网和嵌入式设备。
-
视频流分析: 在实时视频分析任务中,ShuffleNet V2 由于速度快,非常适合部署在摄像头或边缘设备上。
总结
ShuffleNet V2 是一款为移动和嵌入式设备设计的高效轻量级网络,它在优化理论计算量的同时,更注重在实际设备上的速度表现。通过通道混洗和轻量化设计,ShuffleNet V2 提供了一个强大、快速且易于集成的深度学习模型,是现代深度学习轻量化的典范之一。
标签:self,ShuffleNet,channels,V2,out,size From: https://blog.csdn.net/qq_67654130/article/details/143963000