轻量化网络:ShuffleNet v2解析

ShuffleNetV2提出高效CNN设计四大准则:平衡通道数减少内存访问成本;限制分组卷积使用;简化网络分支提升并行性;减少element-wise操作。通过channelsplit等操作,改进了ShuffleNetV1的不足,实现更优速度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文: ShuffleNet V2: Practical Guidelines for Efficient CNN Architecture Design

论文链接https://arxiv.org/abs/1807.11164

开源代码增加自定义layer(caffe版本)https://github.com/anlongstory/ShuffleNet_V2-caffe

开源代码(tensorflow版本)https://github.com/TropComplique/shufflenet-v2-tensorflow

原文链接https://blog.youkuaiyun.com/linmingan/article/details/81383912

        现在很多模型的速度评估都是用FLOPs这个指标,但这个指标用来评价速度是不直接的。因为影响模型速度的因素不仅仅是FLOPs,内存访问操作所消耗的计算(memory access cost,MAC),以及平台因素。

       对于MAC这种情况,有很多操作会有非常高的MAC,比如分组卷积,因此我们应该尽量少用分组卷积。另一个情况是,低并行性,如inception系列的多尺度卷积核。

       对于平台因素,以GPU的张量分解来说。在一些论文里,提出了FLOPs减少75%的张量分解,理论上应该是加速了很多的。但是在GPU中却慢了很多。论文认为,这是因为CUDNN是针对3*3的卷积进行优化的,我们不能想当然的认为3*3卷积会比1*1卷积慢9倍。我们可以进一步看看不同平台,在不同操作上消耗的计算: 


        在GPU上,不管是shuffleNet V1还是MobileNet V2卷积和其他的一些消耗MAC的操作所消耗的计算是差不多的。但是在ARM上卷积消耗的计算比MAC消耗的计算更多。

高效CNN网络设计的四个准则(Practical Guidelines for Efficient Network Design)
     论文提出了一些提高速度的网络结构设计原则,这些原则主要是针对GPU和ARM硬件。另外,论文中的FLOPs只考虑卷积操作,其他的一些操作如data I/O,data shuffle,element-wise 操作(add,relu等)也会用来评估计算速度。接下来介绍一下具体的指导原则:

G1、使用相同的通道数来最小化MAC

      现在有很多模型都会使用可分离卷积,在可分离卷积中,1*1卷积的计算量最大。我们研究了不同的输入通道和输出通道对速度的影响。

      对于输入通道为c1c1,输出通道为c2c2,特征图大小为wh,1*1卷积的FLOPs,B=hwc1c2B=hwc1c2。为了简单起见,我们假设cache能够容纳所有的特征图和卷积核参数。这样我们可以计算出MAC=hw(c1+c2)+c1c2MAC=hw(c1+c2)+c1c2(访问内存的次数,因为特征图大小为hw,在计算1*1卷积过程中我们需要将将所有的输入特征图和卷积核载入cache一次,这样需要hwc1+c1c2hwc1+c1c2次访存;然后需要将计算结果返回到内存,需要hwc2hwc2次访存。),根据高中学过的不等式原理,可以得到: 

         因此在给定FLOPs时MAC有一个下界,MAC等于这个下界的条件为输入通道和输出通道相等。

         当然上面的结论是理想情况下的。在现实中很少有cache的大小能够非常大。为了验证上面的结论,论文设计了一个基准网络结构作为实验:由10个block构成,每个block包含两个卷积层,第一个的输入通道为c1c1,输出通道为c2c2,第二个卷积固定。 
 


上面的表,给出了实验结果。每一行表示,总的FLOPs相同,但是输入通道数和输出通道数不一样的计算速度。可以发现通道数相等的时候,速度最快。

G2、过多的分组卷积会增加MAC

在shuffleNet V1中大量的使用了分组卷积来降低FLOPs,但事实上却增加了MAC。分组卷积的MAC为: 


 
其中B=hwc1c2/gB=hwc1c2/g,g为组数。在输入大小c1∗h∗wc1∗h∗w和B固定的情况下,随着g的增加MAC也会增加,见下表。

 G3、网络分支会降低并行性

       在inception系列中,大量使用了“多路”结构作为网络的基础block。很多小的操作集代替大的操作。比如在NASNET-A中的分支操作有13个,而ResNet只有2或者3个。 
      尽管这样的分支能够提升精度,但是对像GPU这种高并行设备的并行性却很不友好。因为这引入了其他的操作,比如kernel的启动和同步。 
     论文使用了1到4个1*1的卷积构造一个block,构造方法有串联和并行两种: 

对应的实验结果: 

在GPU上,4分支会比1分支慢3倍,但是在ARM中则差不多。

G4、Element-wise操作不可忽视

        element-wise操作,比如ReLU,ADD,尽管他们的FLOPs都很小,但是MAC却很大。特别的,depthwise convolution也是一个高MAC/FLOPs率的element-wise操作(这就是为什么depthwise卷积很难达到理论上的加速)。 
        论文利用ResNet中的block进行实验,对比了有ReLU和shortcut操作,和没有ReLU和shortcut操作的速度。结果显示去掉这两个操作后GPU和ARM上都有20%的速度提升。具体看表: 


总结与讨论

基于上面的那些指导和经验,论文认为一个高效的网络结构应该包含一下几个要点: 
1、使用平衡的卷积,即通道数一样 
2、意识到分组卷积所带来的计算消耗 
3、减少网络分支 
4、减少element-wise操作

    在shuffleNet V1中使用了大量的分组卷积,这违反了G2;bottleneck-like的block,这违反了G1。在mobileNet V2中使用了inverted bottleneck,这违反了G1;在稠密的特征图上使用了depthwise卷积和ReLU,这违反了G4。而在自动生成的网络中,其分支数太高,这违反了G3。

 

ShuffleNet V2

论文为了达到上面的高效网络结构设计原则。使用了一个叫channel split操作,该操作很简单。如下图所示: 

这里写图片描述

         图中的(a)和(b)都是shuffleNet V1里面的block。(c)和(d)则为shuffleNet V2的block。

          在(c)中我们可以看到channel split操作,该操作将输入通道为c的特征图分为个分支,每个分支分别有c−c′c−c′和c′c′(在论文中为c/2)个通道的特征图。在(c)中,利用channel split分出两个分支后(满足G3),左边的分支不做任何操作,右边的分支包含了3个卷积操作,并且其通道数都一样(满足G1)。相比shuffleNet V1两个1*1卷积不再是分组卷积(channe split已经进行了通道分组了),这满足了G2。 
        两个分支用concat操作进行合并,这样使得block的输入通道数和输出通道数一样,这满足了G1。合并后,紧接这使用了channel shuffle操作。

       需要注意的是,这样的block,已经没有了shuffleNet V1中的Add和ReLU操作了。同时depthwise卷积只在一个分支里面。并且concat,channe shuffle以及channel split这三个element-wise操作,可以合并到一个单独的element-wise操作,这满足了G4。

由于(d)中没有了channel split,因此该block的输出通道是输入通道的两倍。

整个shuffleNet V2结构如下: 

实验结果

论文做了不少实验,并进行了讨论。我们只看看最终的实验比较。从下表,可以看出shuffleNet V2在ARM平台上都是最快的。在GPU上还是mobileNet V1快一些。 

这里写图片描述

<think>我们使用Lua语言调用深度学习模型通常需要借助深度学习框架。ShuffleNet V2是一个轻量级的神经网络模型,常用于移动端或嵌入式设备。在Lua生态中,Torch是一个常用的深度学习框架,但请注意,目前Torch已经不再活跃,主流的框架如PyTorch(使用Python)已经取代了它。然而,如果你必须在Lua环境中使用,我们仍然可以通过Torch来加载预训练的ShuffleNet V2模型。 步骤: 1. 安装Torch:确保你的环境中已经安装了Torch(http://torch.ch/)。 2. 下载预训练的ShuffleNet V2模型:通常模型文件是以.t7格式保存的(Torch的序列化文件)。 3. 使用Lua代码加载模型并进行推理。 但是,由于ShuffleNet V2相对较新,可能没有官方的Torch实现。你可能需要寻找一个Torch版本的ShuffleNet V2实现,或者自己转换模型。 这里假设你已经有了一个ShuffleNet V2(1x)的Torch模型文件(例如:shufflenet_v2_1x.t7)。 以下是一个示例代码,展示如何加载模型并进行图像分类预测: 注意:在运行之前,请确保已经安装了必要的Torch包(如torch, nn, image等)以及用于图像处理的包。 示例代码: ```lua -- 导入必要的包 require 'torch' require 'nn' require 'image' -- 设置默认的Tensor类型(根据是否有cuda决定) if pcall(require, 'cutorch') then require 'cunn' torch.setdefaulttensortype('torch.CudaTensor') else torch.setdefaulttensortype('torch.FloatTensor') end -- 加载预训练模型 local modelPath = 'shufflenet_v2_1x.t7' local model = torch.load(modelPath) model:evaluate() -- 设置为评估模式(不计算梯度) -- 定义图像预处理函数 -- ShuffleNet V2的输入通常为224x224,并且需要进行归一化 function preprocess(imgPath) local img = image.load(imgPath) -- 调整大小 img = image.scale(img, 224, 224) -- 转换为float并归一化(这里使用ImageNet的均值和标准差) img = img:float() local mean = {0.485, 0.456, 0.406} local std = {0.229, 0.224, 0.225} for i=1,3 do img[i]:add(-mean[i]) img[i]:div(std[i]) end -- 调整维度顺序为:batch, channels, height, width img = img:view(1, 3, 224, 224) return img end -- 加载并预处理图像 local img = preprocess('path/to/your/image.jpg') -- 进行推理 local output = model:forward(img) -- 将输出转换为概率(softmax) local softmax = nn.SoftMax() local probs = softmax:forward(output) -- 获取前5个类别及其概率 local _, indices = torch.sort(probs, true) -- 降序排序 print("Top 5 predictions:") for i=1,5 do local idx = indices[i] -- 这里假设你有一个将类别索引映射到类别名称的表,例如imagenet_classes -- 你需要自己定义这个表,或者从文件中加载 print(string.format("Class %d: %f", idx, probs[idx])) end ``` 注意: 1. 上述代码中的预处理步骤使用了ImageNet的均值和标准差进行归一化,这是常见的做法。 2. 你需要一个包含类别名称的文件(例如imagenet_classes.lua)来将预测的类别索引转换为可读的类别名称。这里没有提供,你需要自行准备。 3. 模型文件(shufflenet_v2_1x.t7)需要是Torch支持的序列化模型。 如果你没有现成的Torch模型,你可能需要从其他框架(如PyTorch)转换模型到Torch。但请注意,由于框架差异,转换可能比较复杂。 另外,由于Torch的生态已经逐渐被PyTorch取代,建议如果可能的话,考虑使用PyTorch(Python)进行开发,这样你可以获得更多的社区支持和更丰富的预训练模型。 如果你坚持使用Lua和Torch,你可能需要自己实现ShuffleNet V2的结构,然后加载在其他框架(如PyTorch)中预训练的权重(需要转换权重格式)。 由于ShuffleNet V2的结构相对复杂,自己实现并转换权重是一个繁琐的过程。这里不展开。 因此,更实际的做法是,如果你没有Torch格式的ShuffleNet V2模型,可以尝试以下步骤: 1. 在PyTorch中加载预训练的ShuffleNet V2模型(PyTorch官方模型库中提供了预训练模型)。 2. 使用导出工具(如https://github.com/clcarwin/convert_torch_to_pytorch 的逆向转换)将PyTorch模型转换为Torch模型。但请注意,这个工具是将Torch转PyTorch,逆向转换可能需要自己写脚本。 3. 或者,你可以使用ONNX作为中间格式进行转换。 但是,由于转换过程可能比较麻烦,且Lua Torch环境已经不再主流,所以这里我们假设你已经有了一个Torch格式的模型。 最后,请注意,上述代码只是一个基本的示例,实际应用中可能需要根据具体情况进行调整。 §§相关问题§§: 1. 如何在PyTorch中加载ShuffleNet V2模型? 2. 如何将PyTorch模型转换为Torch(.t7)模型? 3. ShuffleNet V2网络结构有什么特点? 4. 除了Torch,还有哪些框架支持Lua语言进行深度学习?</think>### LUA调用ShuffleNet V2(1x)的解决方案 在Lua中调用ShuffleNet V2(1x)需要借助Torch框架(Lua的深度学习库)。以下是完整实现步骤: --- #### 1. **环境准备** 安装依赖: ```bash # 安装Torch curl -s https://raw.githubusercontent.com/torch/ezinstall/master/install-deps | bash git clone https://github.com/torch/distro.git ~/torch --recursive cd ~/torch; ./install.sh # 安装额外依赖 luarocks install image luarocks install torchx ``` --- #### 2. **加载预训练模型** ```lua require 'torch' require 'nn' require 'image' -- 下载预训练模型 (约8.7MB) os.execute('wget https://github.com/ericsun99/Shufflenet-v2-Pytorch/raw/master/shufflenetv2_x1.pth') -- 转换PyTorch模型为Torch格式 (需要Python环境) os.execute([[ python -c "import torch; model = torch.load('shufflenetv2_x1.pth', map_location='cpu'); torch.save('shufflenetv2_x1.t7', model.state_dict())" ]]) ``` --- #### 3. **定义ShuffleNet V2结构** 创建`shufflenet.lua`: ```lua -- shufflenet.lua local nn = require 'nn' function channel_shuffle(x, groups) local batch, channels, height, width = x:size(1), x:size(2), x:size(3), x:size(4) local channels_per_group = channels // groups x = x:view(batch, groups, channels_per_group, height, width) x = torch.transpose(x, 2, 3):contiguous() return x:view(batch, channels, height, width) end function create_model() local model = nn.Sequential() -- 初始卷积层 model:add(nn.SpatialConvolution(3, 24, 3, 3, 2, 2, 1, 1)) model:add(nn.SpatialBatchNormalization(24)) model:add(nn.ReLU(true)) model:add(nn.SpatialMaxPooling(3, 3, 2, 2, 1, 1)) -- ShuffleNet块定义(此处简化为1个块,实际需完整实现) local function shufflenet_block(inp, oup, stride) local branch = nn.Sequential() -- 深度可分离卷积 branch:add(nn.SpatialConvolution(inp, oup, 3, 3, stride, stride, 1, 1, 1)) branch:add(nn.SpatialBatchNormalization(oup)) branch:add(nn.ReLU(true)) return branch end model:add(shufflenet_block(24, 116, 2)) -- 全局池化和分类层 model:add(nn.SpatialAveragePooling(7, 7)) model:add(nn.View(-1):setNumInputDims(3)) model:add(nn.Linear(116, 1000)) -- ImageNet分类 return model end return create_model ``` --- #### 4. **加载模型进行推理** ```lua -- main.lua require 'torch' require 'image' require 'shufflenet' -- 1. 加载模型 local model = create_model() local weights = torch.load('shufflenetv2_x1.t7') model:loadStateDict(weights) -- 2. 预处理图像 local img = image.load('test.jpg', 3, 'float') -- 加载RGB图像 img = image.scale(img, 224, 224) -- 调整大小 img = img * 255 -- 归一化到0-255 local mean = {0.485, 0.456, 0.406} -- ImageNet均值 local std = {0.229, 0.224, 0.225} -- ImageNet标准差 for i=1,3 do img[i]:add(-mean[i]):div(std[i]) end img = img:view(1, 3, 224, 224) -- 添加batch维度 -- 3. 执行推理 model:evaluate() -- 设置为评估模式 local output = model:forward(img) -- 4. 解析结果 local _, cls = torch.max(output, 2) print("Predicted class ID: " .. cls[1]) ``` --- #### 关键解释: 1. **模型转换** PyTorch模型需转换为Torch7格式(`.t7`),通过Python脚本转换权重。 2. **通道重排** `channel_shuffle`函数实现特征图通道重排,这是ShuffleNet的核心操作。 3. **预处理要求** 输入图像需: - 尺寸缩放至22224 - 按ImageNet均值/标准差归一化 - 转换为FloatTensor 4. **计算资源** 模型默认在CPU运行,如需GPU支持: ```lua require 'cunn' model:cuda() img = img:cuda() ``` --- #### 常见问题排查: 1. **模型加载失败** ```lua -- 检查权重文件路径 print(torch.exists('shufflenetv2_x1.t7')) ``` 2. **输出尺寸不匹配** 调整全连接层维度: ```lua model:get(9):reset(116, YOUR_CLASS_NUM) ``` 3. **通道重排错误** 确保分组卷积后执行: ```lua x = nn.Identity()({x, channel_shuffle(x, groups)}) ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值