第06章 移动端微量神经网络模型

2023-11-21 22:31

本文主要是介绍第06章 移动端微量神经网络模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

序言

1. 内容介绍

  本章介绍深度学习算法-卷积神经网络用于 图片分类 的应用,主要介绍主流微型卷积神经网络 (CNN) 模型,包括 SqueezeNet ShuffleNet 的算法模型数学推理模型实现 以及 PyTorch框架 的实现。并能够把它应用于现实世界的 数据集 实现分类效果。

2. 理论目标

  • 移动端微型卷积神经网络的通用设计准则
  • SqueezeNet 的基础模型架构、训练细节与数学推理
  • ShuffleNet 的基础模型架构、训练细节与数学推理

3. 实践目标

  • 掌握PyTorch框架下SqueezeNet ShuffleNetV2 的实现
  • 掌握迁移学习与特征提取
  • 熟悉各经典算法在图像分类应用上的优缺点

4. 实践数据集

  • Flower 数据集分类
  • Oxford-IIIT 数据集分类
  • CIFAR-10 数据集分类

5. 内容目录

  • 1.微型神经网络简介
  • 2.卷积神经网络模型详解 SqueezeNet
  • 3.卷积神经网络模型详解 ShuffleNet
  • 4.PyTorch 实践
  • 5.图像分类实例 Flower 数据集
  • 6.图像分类实例 Oxford-IIIT 数据集
  • 7.图像分类实例 CIFAR-10 数据集

第1节 微型神经网络简介

  目前神经网络领域的研究基本可以概括为两个方向:探索模型更好的 预测能力,关注模型在实际应用中的难点。事实上卷积神经网络在图像识别领域超越了人类的表现,但是这些先进的网络需要较高的计算资源。这些资源需求超出了很多移动设备和嵌入式设备的能力(如无人驾驶),导致实际应用中难以在这些设备上应用。小型网络就是为解决这个难点来设计的。

  小型网络的设计和优化目标并不是模型的准确率,而是在满足一定准确率的条件下,尽可能的使得模型小,从而降低对计算资源的需求,降低计算延迟。小型高效的神经网络的构建方法大致可以归为两类:对已经训练好的网络进行压缩,直接训练小网络模型。模型参数的数量决定了模型的大小,所谓的 微型网络 指的是网络的参数数量较少

  • 较小的模型具有更高效的 分布式训练效率: Worker 与 PS 以及 Worker 与 Worker 之间的通信是神经网络分布式训练的重要限制因素。在分布式数据并行训练中,通信开销与模型的参数数量成正比。较小的模型需要更少的通信,从而可以更快的训练

  • 较小的模型在 模型导出 时开销更低:当在tensorflow 等框架中训练好模型并准备部署时,需要将模型导出。如将训练好的模型导出到自动驾驶汽车上。模型越小,则数据导出需要传输的数据量越少,这样可以支持更频繁的模型更新

  • 较小的模型可以在 FPGA 和 嵌入式硬件 上部署:FPGA 通常只有小于10MB 的片上内存,并且没有外部存储。因此如果希望在FPGA 上部署模型,则要求模型足够小从而满足内存限制

1.1 微型网络通用设计准则

  目前衡量模型推断速度的一个通用指标是 FLOPs (即乘-加 计算量)。事实上这是一个间接指标,因为它不完全等同于推断速度。如 MobileNet V2 和 NASNET-A 的FLOPs 相差无几,但是 MobileNet V2 的推断速度要快得多。

  如下所示为几种模型在 GPU 和 ARM 上的准确率 (在 ImageNet 验证集上的测试结果)、模型推断速度 (通过 Batch/秒 来衡量)、计算复杂度 (通过 FLOPs 衡量) 的关系

  • 在 ARM 平台上 batchsize=1, 在 GPU 平台上 batchsize = 8

  • 准确率与模型容量成正比,而模型容量与模型计算复杂度成成比、计算复杂度与推断速度成反比

因此模型准确率越高,则推断速度越低;模型计算复杂度越高,则推断速度越低

  高效网络设计的几个通用准则

  • 准则一:输入通道数和输出通道数 相等 时,MAC 指标最小

假设输入 feature map 尺寸为 W\times HW×H、通道数为 C_ICI​ ,输出通道数为 C_OCO​, 假设为 1\times11×1 卷积,则 FLOPs 为 B = C_I \times 1 \times 1 \times W \times H \times C_O = WHC_IC_OB=CI​×1×1×W×H×CO​=WHCI​CO​

其内存访问量为输入 featuremap 内存访问量 + 输出 featuremap 内存访问量+ 卷积核内存访问量。因此有 MAC = WHC_I + WHC_O + C_I \times 1 \times 1 \times C_O = WH(C_I + C_O)MAC=WHCI​+WHCO​+CI​×1×1×CO​=WH(CI​+CO​) 。根据不等式 C_I + C_O \ge 2\sqrt{C_IC_O}CI​+CO​≥2CI​CO​​,以及 C_IC_O = \frac{B}{WH}CI​CO​=WHB​,则有 MAC \ge 2 \sqrt{WHB} + \frac{B}{WH}MAC≥2WHB​+WHB​ 。当 C_I = C_OCI​=CO​ 时等式成立

  • 准则二:大量使用 分组卷积 会增加 MAC

分组卷积可以降低 FLOPs, 可以在 FLOPs 固定的情况下,增大 featuremap 的通道数从而提高模型的容量。但是采用分组卷积可能增加MAC

对于 1\times11×1 卷积,设分组数为 gg ,则 FLOPs 数为 B = \frac{WHC_IC_O}{g}B=gWHCI​CO​​, 内存访问量为 MAC = WH(C_I + C_O) + \frac{C_IC_O}{g}MAC=WH(CI​+CO​)+gCI​CO​​

当 C_I = C_OCI​=CO​ 时,MAC = 2\sqrt{WHBg} + \frac{B}{WH}MAC=2WHBg​+WHB​ 最小。因此 MACMAC 随着 gg 的增加而增加

  • 准则三:网络分支 降低 并行度

虽然网络中采用分支(如Inception系列、ResNet系列)有利于提高模型的准确率,但是它对 GPU 等具有高并行计算能力的设备不友好,因此会降低计算效率。另外它还带来了卷积核的 lauching 以及计算的同步等问题,这也是推断时间的开销

  • 准则四:不能忽视 元素级 操作的影响

元素级操作包括 ReLU、AddTensor、AddBias 等,它们的 FLOPs 很小但是 MAC 很大。在 ResNet 中,实验发现如果去掉 ReLU 和旁路连接,则在 GPU 和 ARM 上大约有 20% 的推断速度的提升

第2节 卷积神经网络模型 SqueezeNet

2.1 SqueezeNet 简介

   Squeezenet 提出了 Fire 模块,并通过该模型构成一种小型 CNN 网络,在满足 AlexNet 级别准确率的条件下大幅度降低参数数量。CNN 结构设计三个主要策略

  • 部分的使用 1\times11×1 卷积替换 3\times33×3 卷积。因为 1\times11×1 卷积的参数数量比 3\times33×3 卷积的参数数量少了 9 倍

  • 减少 3\times33×3 卷积输入通道的数量。这会进一步降低网络的参数数量

  • 将网络中下采样的时机推迟到网络的后面。这会使得网络整体具有尺寸较大的 feature map, 基于在其它不变的情况下,尺寸大的feature map 具有更高的分类准确率。

策略 1、2 是关于在尽可能保持模型准确率的条件下减少模型的参数数量,策略 3 是关于在有限的参数数量下最大化准确率

2.2 SqueezeNet 模型结构

2.2.1 Fire 模块

  Fire 模块由一个 Squeeze 层 和一个 Expand 层 组成

  • Squeeze 层是一个 1\times11×1 卷积层,输出通道数为超参数 s_{1\times1}s1×1​

    • 通常选择超参数 s_{1\times1}s1×1​ 满足 e_{1\times1} + e_{3\times3}e1×1​+e3×3​
    • 它用于减少expand 层的输入通道数,即应用策略 2
  • Expand 层是一个 1\times11×1 卷积层和一个 3\times33×3 卷积层,它们卷积的结果沿着深度进行拼接

    • 1\times11×1 卷积输出通道数为超参数 e_{1\times1}e1×1​,3\times33×3 卷积输出通道数为超参数 e_{3\times3}e3×3​
    • 选择 1\times11×1 卷积是应用策略 1

2.2.2 SqueezeNet 网络设计

  • SqueezeNet 从一个 独立 的卷积层(conv1)开始,后跟 8 个Fire 模块 (fire 2~9), 最后连接卷积层 conv10、全局平均池化层、softmax 输出层
  • 从网络开始到末尾,每个 Fire 模块的输出通道数逐渐增加
  • 在 conv1、fire4、fire8 之后执行最大池化,步长为 2。这种相对较晚的执行池化操作是采用了策略3
  • 在 expand 层中的 3\times33×3 执行的是 same 卷积,即在原始输入上下左右各添加一个像素,使得输出的 feature map 尺寸不变
  • 在 fire9 之后使用 Dropout,遗忘比例为 0.5

      

2.2.3 SqueezeNet 网络参数

  • s_{1\times1}s1×1​ (1 x 1 Squeeze) 列给出了超参数 s_{1\times1}s1×1​ ,e_{1\times1}e1×1​ (1 x 1 Expand) 列给出了超参数 e_{3\times3}e3×3​, (3 x 3 Expand) 列给出了超参数 e_{3\times3}e3×3​
  • s_{1\times1}s1×1​ sparsity, e_{1\times1}e1×1​ sparsity, e_{3\times3}e3×3​ sparsity, bitbit 这四列给出了模型裁剪的参数
  • Parameter before pruning 列给出了模型裁剪之前的参数数量
  • Parameter after pruning 列给出了模型裁剪之后的参数数量

2.2.4 SqueezeNet 模型性能

  • 网络 压缩方法 列给出了网络裁剪方法,包括 SVD (Denton et al. 2014)、Network pruning (Han et al. 2015b)、Deep compression (Han et al. 2015a)
  • 数据类型列给出了 计算精度
  • 模型压缩比例列给出了模型相对于原始 AlexNet 的模型大小压缩的比例
  • Top-1 Accuracy/Top-5 Accuracy 列给出了模型在 ImageNet 测试集上的评估结果

SqueezeNet 在满足同样准确率的情况下,模型大小比AlexNet 压缩了 50 倍。如果使用 Deep Compression 以及 6 bit 精度的情况下,模型大小比AlexNet 压缩了 510 倍

2.3 SqueezeNet PyTorch

 

# %load squeeze.py import torch import torch.nn as nn class Fire(nn.Module): def __init__(self,inplanes,outplane,ep1,ep3): super(Fire, self).__init__() self.squeeze = nn.Conv2d(inplanes, outplane, kernel_size=1) self.squeeze_relu = nn.ReLU(inplace=True) self.expand1x1 = nn.Conv2d(outplane,ep1,kernel_size=1) self.expand1x1_relu = nn.ReLU(inplace=True) self.expand3x3 = nn.Conv2d(outplane,ep3,kernel_size=3, padding=1) self.expand3x3_relu = nn.ReLU(inplace=True) def forward(self, x): x = self.squeeze_relu(self.squeeze(x)) output = [self.expand3x3_relu(self.expand1x1(x)), self.expand3x3_relu(self.expand3x3(x))] return torch.cat(output, 1) class SqueezeNet(nn.Module): def __init__(self,num_classes=1000): super(SqueezeNet, self).__init__() self.features = nn.Sequential( nn.Conv2d(3, 64, kernel_size=3, stride=2), nn.ReLU(inplace=True), nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True), Fire(64, 16, 64, 64), Fire(128, 16, 64, 64), nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True), Fire(128, 32, 128, 128), Fire(256, 32, 128, 128), nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True), Fire(256, 48, 192, 192), Fire(384, 48, 192, 192), Fire(384, 64, 256, 256), Fire(512, 64, 256, 256), ) self.classifier = nn.Sequential( nn.Dropout(p=0.5), nn.Conv2d(512, num_classes, kernel_size=1), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d((1, 1)) ) def forward(self, x): x = self.features(x) x = self.classifier(x) return torch.flatten(x, 1) def build_squeeze(phase,num_classes,pretrained): if phase != "test" and phase != "train": print("ERROR: Phase: " + phase + " not recognized") return if not pretrained: model = SqueezeNet(num_classes=num_classes) else: model = SqueezeNet() model_weights_path = 'weights/squeezenet1_1-b8a52dc0.pth' model.load_state_dict(torch.load(model_weights_path), strict=False) for parma in model.parameters(): parma.requires_grad = False model.classifier = nn.Sequential( nn.Dropout(p=0.5), nn.Conv2d(512, num_classes, kernel_size=1), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d((1, 1)) ) return model

 

from torchsummary import summary net = build_squeeze('train',10,False) net.cuda() summary(net,(3,224,224))

----------------------------------------------------------------Layer (type)               Output Shape         Param #
================================================================Conv2d-1         [-1, 64, 111, 111]           1,792ReLU-2         [-1, 64, 111, 111]               0MaxPool2d-3           [-1, 64, 55, 55]               0Conv2d-4           [-1, 16, 55, 55]           1,040ReLU-5           [-1, 16, 55, 55]               0Conv2d-6           [-1, 64, 55, 55]           1,088ReLU-7           [-1, 64, 55, 55]               0Conv2d-8           [-1, 64, 55, 55]           9,280ReLU-9           [-1, 64, 55, 55]               0Fire-10          [-1, 128, 55, 55]               0Conv2d-11           [-1, 16, 55, 55]           2,064ReLU-12           [-1, 16, 55, 55]               0Conv2d-13           [-1, 64, 55, 55]           1,088ReLU-14           [-1, 64, 55, 55]               0Conv2d-15           [-1, 64, 55, 55]           9,280ReLU-16           [-1, 64, 55, 55]               0Fire-17          [-1, 128, 55, 55]               0MaxPool2d-18          [-1, 128, 27, 27]               0Conv2d-19           [-1, 32, 27, 27]           4,128ReLU-20           [-1, 32, 27, 27]               0Conv2d-21          [-1, 128, 27, 27]           4,224ReLU-22          [-1, 128, 27, 27]               0Conv2d-23          [-1, 128, 27, 27]          36,992ReLU-24          [-1, 128, 27, 27]               0Fire-25          [-1, 256, 27, 27]               0Conv2d-26           [-1, 32, 27, 27]           8,224ReLU-27           [-1, 32, 27, 27]               0Conv2d-28          [-1, 128, 27, 27]           4,224ReLU-29          [-1, 128, 27, 27]               0Conv2d-30          [-1, 128, 27, 27]          36,992ReLU-31          [-1, 128, 27, 27]               0Fire-32          [-1, 256, 27, 27]               0MaxPool2d-33          [-1, 256, 13, 13]               0Conv2d-34           [-1, 48, 13, 13]          12,336ReLU-35           [-1, 48, 13, 13]               0Conv2d-36          [-1, 192, 13, 13]           9,408ReLU-37          [-1, 192, 13, 13]               0Conv2d-38          [-1, 192, 13, 13]          83,136ReLU-39          [-1, 192, 13, 13]               0Fire-40          [-1, 384, 13, 13]               0Conv2d-41           [-1, 48, 13, 13]          18,480ReLU-42           [-1, 48, 13, 13]               0Conv2d-43          [-1, 192, 13, 13]           9,408ReLU-44          [-1, 192, 13, 13]               0Conv2d-45          [-1, 192, 13, 13]          83,136ReLU-46          [-1, 192, 13, 13]               0Fire-47          [-1, 384, 13, 13]               0Conv2d-48           [-1, 64, 13, 13]          24,640ReLU-49           [-1, 64, 13, 13]               0Conv2d-50          [-1, 256, 13, 13]          16,640ReLU-51          [-1, 256, 13, 13]               0Conv2d-52          [-1, 256, 13, 13]         147,712ReLU-53          [-1, 256, 13, 13]               0Fire-54          [-1, 512, 13, 13]               0Conv2d-55           [-1, 64, 13, 13]          32,832ReLU-56           [-1, 64, 13, 13]               0Conv2d-57          [-1, 256, 13, 13]          16,640ReLU-58          [-1, 256, 13, 13]               0Conv2d-59          [-1, 256, 13, 13]         147,712ReLU-60          [-1, 256, 13, 13]               0Fire-61          [-1, 512, 13, 13]               0Dropout-62          [-1, 512, 13, 13]               0Conv2d-63           [-1, 10, 13, 13]           5,130ReLU-64           [-1, 10, 13, 13]               0
AdaptiveAvgPool2d-65             [-1, 10, 1, 1]               0
================================================================
Total params: 727,626
Trainable params: 727,626
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 51.20
Params size (MB): 2.78
Estimated Total Size (MB): 54.55
----------------------------------------------------------------

2.4 SqueezeNet 设计技巧

2.4.1 超参数设计

  定义 base_ebasee​ 为一个 Fire 模块的输出通道数,假设每 freqfreq 个 Fire 模块将 Fire 模块的输出通道数增加 incr_e \to freq = 2incre​→freq=2, 则网络中所有 Fire 模块的输出通道数依次为

    base_e,base_e,base_e + incr_e,base_e + incr_e,base_e + 2 * incr_e,base_e + 2 * incr_e,\dotsbasee​,basee​,basee​+incre​,basee​+incre​,basee​+2∗incre​,basee​+2∗incre​,…

  因此第 ii 个 Fire 模块的输出通道数为

    e_i = base_e + (incr_e \times [\frac{i-1}{freq}]), i = 1,2,\dots,9ei​=basee​+(incre​×[freqi−1​]),i=1,2,…,9

  由于 Fire 模块的输出就是 Fire 模块中 Expand 层的输出,因此 e_iei​ 也就是第 ii 个 Fire 模块中 Expand 层的输出通道数

  • 定义 pct_{3\times3}pct3×3​ 为所有 Fire 模块的 Expand 层中,3\times33×3 卷积数量的比例。这意味着不同 Fire 模块的 Expand 层中 3\times33×3 卷积数量的占比都是同一个比例
    • 第 ii 个 Fire 模块的 Expand 层中的 1\times11×1 卷积数量
    • 第 ii 个 Fire 模块的 Expand 层中的 3\times33×3 卷积数量
  • 定义 squeeze ratiosqueezeratio 为所有 Fire 模块中,Squeeze 层的输出通道数与 Expand 层的输出通道数的比例称作压缩比, 这意味着不同 Fire 模块的压缩比都相同
    • 第 ii 个 Fire 模块的 Squeeze 层的输出通道数为 s_{i,1\times1} = e_i \times SRsi,1×1​=ei​×SR

  对于前面给到的 SqueezeNet 有 base_e = 128, freq = 2, incr_e = 128, pct_{3\times3} = 0.5, SR = 0.125basee​=128,freq=2,incre​=128,pct3×3​=0.5,SR=0.125

  • 评估超参数 SR: base_e = 128, freq = 2, incr_e = 128, pct_{3\times3} = 0.5, SR \in \textbf{(0.15, 1.0)}SR:basee​=128,freq=2,incre​=128,pct3×3​=0.5,SR∈(0.15, 1.0)

在 SR = 0.75SR=0.75 时, ImageNet top-5 准确率达到峰值 86.0% , 此时模型大小为 19MB, 此后进一步增加 SR 只会增加模型大小而不会提高模型准确率

  • 评估超参数 SR: base_e = 128, freq = 2, incr_e = 128, pct_{3\times3} = 0.5, SR \in \textbf{(1, 99)}SR:basee​=128,freq=2,incre​=128,pct3×3​=0.5,SR∈(1, 99)

3\times33×3 卷积占比相对于 Expand 层的卷积数量的占比为 50% 时,ImageNet top-5 准确率达到 85.3% 。此后进一步增加 3\times33×3 卷积的占比只会增加模型大小而几乎不会提高模型准确率

2.4.2 旁路连接

  采用 ResNet 的思想,可以在 SqueezeNet 中添加旁路连接。下图中,左图为标准的 SqueezeNet ,中图为引入简单旁路连接的 SqueezeNet,右图为引入复杂旁路连接的SqueezeNet

  • 简单旁路 连接:等同于恒等映射。此时要求输入 feature map 和残差 feature map 的通道数相等
  • 复杂旁路 连接:针对输入 feature map 和残差 feature map 的通道数不等的情况,旁路采用一个 1\times11×1 卷积来调整旁路的输出通道数

  

  在 ImageNet 测试集上的表现证明添加简单旁路连接能够 提升模型的准确率,还能保持模型的 大小不变

模型结构top-1准确率top-5准确率模型大小
SqueezeNet57.5%80.3%4.8MB
SqueezeNet + 简单旁路连接60.4%82.5%4.8MB
SqueezeNet + 复杂旁路连接58.8%82.0%7.7MB

第3节 卷积神经网络模型 ShuffleNet

3.1 ShuffleNet 简介

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

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

3.2 ShuffleNetV1 Block

  在 Xception 和ResNeXt 中,有大量的 1\times11×1 卷积,所以整体而言 1\times11×1 卷积的计算开销较大。ResNeXt 的每个残差块中,1\times11×1 卷积占据了乘加运算的 93.4% (基数为32时), 在小型网络中,为了满足计算性能的约束 (因为计算资源不够) 需要控制计算量。虽然限制通道数量可以降低计算量,但这也可能会严重降低准确率

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

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

解决办法是采用通道混洗,允许分组卷积从不同分组中获取输入。由于通道混洗是可微的,因此它可以嵌入到网络中以进行端到端的训练

  ShuffleNet 块的结构从 ResNeXt 块改进而来

  • 第一个 1\times11×1 卷积替换为 1\times11×1 分组卷积+通道随机混洗

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

  • 在 3\times33×3 depthwise 卷积之后只有 BN 而没有 ReLU

  • 当步长为2时

    • 恒等映射直连替换为一个尺寸为 3x3 、步长为 2 的平均池化
    • 3x3 depthwise 卷积的步长为 2
    • 将残差部分与直连部分的 feature map 拼接,而不是相加, 因为当 feature map 减半时,为了缓解信息丢失需要将输出通道数加倍从而保持模型的有效容量

(a) 表示没有通道混洗的分组卷积;(b) 表示进行通道混洗的分组卷积;© 为(b) 的等效表示

3.3 ShuffleNetV2 Block

  ShuffleNet V1 block 的分组卷积 违反准则二, 1\times11×1 卷积 违反准则一,旁路连接的元素级加法 违反准则四。而ShuffleNet V2 block 修正了这些违背的地方, 在 ShuffleNet V1 block 的基础上修改

  • 步长为1 时,ShuffleNet V2 block 首先将输入 feature map 沿着通道进行拆分。设输入通道数为 C_ICI​ ,则拆分为 C''_ICI′′​ 和 C_I - C''_ICI​−CI′′​

    • 根据准则一,右路的三个卷积操作都保持通道数不变

    • 根据准则二,右路的两个 1\times11×1 卷积不再是分组卷积,而是标准的卷积操作。因为分组已经由通道拆分操作执行了

    • 根据准则三,左路分支保持不变,右路分支执行各种卷积操作

    • 根据准则四,左右两路的featuremap 不再执行相加,而是执行特征拼接

  • 步长为2时,ShuffleNet V2 block 不再拆分通道,因为通道数量需要翻倍从而保证模型的有效容量

(a),(b) 表示ShuffleNet V1 block (步长分别为1、2),©,(d) 表示ShuffleNet V2 block (步长分别为1、2)。其中GConv 表示分组卷积,DWConv 表示depthwise 卷积

3.4 ShuffleNetV2 模型性能

  ShuffleNet V2 的网络结构类似 ShuffleNet V1,主要有两个不同

  • 用 ShuffleNet v2 block 代替 ShuffleNet v1 block
  • 在 Global Pool 之前加入一个 1\times11×1 卷积

  

  ShuffleNet V2 可以结合 SENet 的思想,也可以增加层数从而由小网络变身为大网络, 下表为几个模型在 ImageNet 验证集上的表现 (single-crop)

  

3.5 ShuffleNetV2 PyTorch

 

# %load shuffle.py import math import torch import torch.nn as nn def channel_shuffle(x, groups): N,C,H,W = x.size() return x.view(N,groups,int(C/groups),H,W).transpose(1,2).contiguous().view(N,-1,H,W) class InvertedResidual(nn.Module): def __init__(self,input_features,output_features,stride): super(InvertedResidual, self).__init__() branch_features = output_features//2 self.stride = stride self.branch1 = nn.Sequential() if stride > 1: self.branch1 = nn.Sequential( nn.Conv2d(input_features, input_features, kernel_size=3, stride=stride, padding=1, bias=False, groups=input_features), nn.BatchNorm2d(input_features), nn.Conv2d(input_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False), nn.BatchNorm2d(branch_features), nn.ReLU(inplace=True), ) self.branch2 = nn.Sequential( nn.Conv2d(input_features if (stride > 1) else branch_features,branch_features, kernel_size=1, stride=1, padding=0, bias=False), nn.BatchNorm2d(branch_features), nn.ReLU(inplace=True), nn.Conv2d(branch_features, branch_features, kernel_size=3, stride=stride, padding=1, bias=False, groups=branch_features), nn.BatchNorm2d(branch_features), nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False), nn.BatchNorm2d(branch_features), nn.ReLU(inplace=True), ) def forward(self, x): if self.stride == 1: x1, x2 = x.chunk(2, 1) out = torch.cat((x1, self.branch2(x2)), 1) else: out = torch.cat((self.branch1(x), self.branch2(x)), 1) out = channel_shuffle(out, 2) return out class ShuffleNetV2(nn.Module): def __init__(self,num_classes=1000): super(ShuffleNetV2, self).__init__() self.input_channels = 24 self.conv1 = nn.Sequential( nn.Conv2d(3, self.input_channels, 3, 2, 1, bias=False), nn.BatchNorm2d(self.input_channels), nn.ReLU(inplace=True), ) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.stage2 = self._make_layer(116,4) self.stage3 = self._make_layer(232,8) self.stage4 = self._make_layer(464,4) self.conv5 = nn.Sequential( nn.Conv2d(self.input_channels, 1024, kernel_size=1, stride=1, padding=0, bias=False), nn.BatchNorm2d(1024), nn.ReLU(inplace=True), ) self.fc = nn.Linear(1024, num_classes) def _make_layer(self, output_channels,num_blocks): layers = [] layers.append(InvertedResidual(self.input_channels,output_channels,2)) for i in range(num_blocks-1): layers.append(InvertedResidual(output_channels,output_channels,1)) self.input_channels = output_channels return nn.Sequential(*layers) def forward(self,x): x = self.conv1(x) x = self.maxpool(x) x = self.stage2(x) x = self.stage3(x) x = self.stage4(x) x = self.conv5(x) x = x.mean([2, 3]) x = self.fc(x) return x def build_shufflev2(phase,num_classes,pretrained): if phase != "test" and phase != "train": print("ERROR: Phase: " + phase + " not recognized") return if not pretrained: model = ShuffleNetV2(num_classes=num_classes) else: model = ShuffleNetV2() model_weights_path = 'weights/shufflenetv2_x1-5666bf0f80.pth.pth' model.load_state_dict(torch.load(model_weights_path), strict=False) for parma in model.parameters(): parma.requires_grad = False ratio = int(math.sqrt(1024/num_classes)) floor = math.floor(math.log2(ratio)) hidden_size = int(math.pow(2,10-floor)) model.fc = nn.Sequential(nn.Linear(1024, hidden_size), nn.ReLU(inplace=True), nn.Dropout(p=0.5), nn.Linear(hidden_size, num_classes) ) return model

 

net = build_shufflev2('train',10,False) net.cuda() summary(net,(3,224,224))

----------------------------------------------------------------Layer (type)               Output Shape         Param #
================================================================Conv2d-1         [-1, 24, 112, 112]             648BatchNorm2d-2         [-1, 24, 112, 112]              48ReLU-3         [-1, 24, 112, 112]               0MaxPool2d-4           [-1, 24, 56, 56]               0Conv2d-5           [-1, 24, 28, 28]             216BatchNorm2d-6           [-1, 24, 28, 28]              48Conv2d-7           [-1, 58, 28, 28]           1,392BatchNorm2d-8           [-1, 58, 28, 28]             116ReLU-9           [-1, 58, 28, 28]               0Conv2d-10           [-1, 58, 56, 56]           1,392BatchNorm2d-11           [-1, 58, 56, 56]             116ReLU-12           [-1, 58, 56, 56]               0Conv2d-13           [-1, 58, 28, 28]             522BatchNorm2d-14           [-1, 58, 28, 28]             116Conv2d-15           [-1, 58, 28, 28]           3,364BatchNorm2d-16           [-1, 58, 28, 28]             116ReLU-17           [-1, 58, 28, 28]               0InvertedResidual-18          [-1, 116, 28, 28]               0Conv2d-19           [-1, 58, 28, 28]           3,364BatchNorm2d-20           [-1, 58, 28, 28]             116ReLU-21           [-1, 58, 28, 28]               0Conv2d-22           [-1, 58, 28, 28]             522BatchNorm2d-23           [-1, 58, 28, 28]             116Conv2d-24           [-1, 58, 28, 28]           3,364BatchNorm2d-25           [-1, 58, 28, 28]             116ReLU-26           [-1, 58, 28, 28]               0InvertedResidual-27          [-1, 116, 28, 28]               0Conv2d-28           [-1, 58, 28, 28]           3,364BatchNorm2d-29           [-1, 58, 28, 28]             116ReLU-30           [-1, 58, 28, 28]               0Conv2d-31           [-1, 58, 28, 28]             522BatchNorm2d-32           [-1, 58, 28, 28]             116Conv2d-33           [-1, 58, 28, 28]           3,364BatchNorm2d-34           [-1, 58, 28, 28]             116ReLU-35           [-1, 58, 28, 28]               0InvertedResidual-36          [-1, 116, 28, 28]               0Conv2d-37           [-1, 58, 28, 28]           3,364BatchNorm2d-38           [-1, 58, 28, 28]             116ReLU-39           [-1, 58, 28, 28]               0Conv2d-40           [-1, 58, 28, 28]             522BatchNorm2d-41           [-1, 58, 28, 28]             116Conv2d-42           [-1, 58, 28, 28]           3,364BatchNorm2d-43           [-1, 58, 28, 28]             116ReLU-44           [-1, 58, 28, 28]               0InvertedResidual-45          [-1, 116, 28, 28]               0Conv2d-46          [-1, 116, 14, 14]           1,044BatchNorm2d-47          [-1, 116, 14, 14]             232Conv2d-48          [-1, 116, 14, 14]          13,456BatchNorm2d-49          [-1, 116, 14, 14]             232ReLU-50          [-1, 116, 14, 14]               0Conv2d-51          [-1, 116, 28, 28]          13,456BatchNorm2d-52          [-1, 116, 28, 28]             232ReLU-53          [-1, 116, 28, 28]               0Conv2d-54          [-1, 116, 14, 14]           1,044BatchNorm2d-55          [-1, 116, 14, 14]             232Conv2d-56          [-1, 116, 14, 14]          13,456BatchNorm2d-57          [-1, 116, 14, 14]             232ReLU-58          [-1, 116, 14, 14]               0InvertedResidual-59          [-1, 232, 14, 14]               0Conv2d-60          [-1, 116, 14, 14]          13,456BatchNorm2d-61          [-1, 116, 14, 14]             232ReLU-62          [-1, 116, 14, 14]               0Conv2d-63          [-1, 116, 14, 14]           1,044BatchNorm2d-64          [-1, 116, 14, 14]             232Conv2d-65          [-1, 116, 14, 14]          13,456BatchNorm2d-66          [-1, 116, 14, 14]             232ReLU-67          [-1, 116, 14, 14]               0InvertedResidual-68          [-1, 232, 14, 14]               0Conv2d-69          [-1, 116, 14, 14]          13,456BatchNorm2d-70          [-1, 116, 14, 14]             232ReLU-71          [-1, 116, 14, 14]               0Conv2d-72          [-1, 116, 14, 14]           1,044BatchNorm2d-73          [-1, 116, 14, 14]             232Conv2d-74          [-1, 116, 14, 14]          13,456BatchNorm2d-75          [-1, 116, 14, 14]             232ReLU-76          [-1, 116, 14, 14]               0InvertedResidual-77          [-1, 232, 14, 14]               0Conv2d-78          [-1, 116, 14, 14]          13,456BatchNorm2d-79          [-1, 116, 14, 14]             232ReLU-80          [-1, 116, 14, 14]               0Conv2d-81          [-1, 116, 14, 14]           1,044BatchNorm2d-82          [-1, 116, 14, 14]             232Conv2d-83          [-1, 116, 14, 14]          13,456BatchNorm2d-84          [-1, 116, 14, 14]             232ReLU-85          [-1, 116, 14, 14]               0InvertedResidual-86          [-1, 232, 14, 14]               0Conv2d-87          [-1, 116, 14, 14]          13,456BatchNorm2d-88          [-1, 116, 14, 14]             232ReLU-89          [-1, 116, 14, 14]               0Conv2d-90          [-1, 116, 14, 14]           1,044BatchNorm2d-91          [-1, 116, 14, 14]             232Conv2d-92          [-1, 116, 14, 14]          13,456BatchNorm2d-93          [-1, 116, 14, 14]             232ReLU-94          [-1, 116, 14, 14]               0InvertedResidual-95          [-1, 232, 14, 14]               0Conv2d-96          [-1, 116, 14, 14]          13,456BatchNorm2d-97          [-1, 116, 14, 14]             232ReLU-98          [-1, 116, 14, 14]               0Conv2d-99          [-1, 116, 14, 14]           1,044BatchNorm2d-100          [-1, 116, 14, 14]             232Conv2d-101          [-1, 116, 14, 14]          13,456BatchNorm2d-102          [-1, 116, 14, 14]             232ReLU-103          [-1, 116, 14, 14]               0
InvertedResidual-104          [-1, 232, 14, 14]               0Conv2d-105          [-1, 116, 14, 14]          13,456BatchNorm2d-106          [-1, 116, 14, 14]             232ReLU-107          [-1, 116, 14, 14]               0Conv2d-108          [-1, 116, 14, 14]           1,044BatchNorm2d-109          [-1, 116, 14, 14]             232Conv2d-110          [-1, 116, 14, 14]          13,456BatchNorm2d-111          [-1, 116, 14, 14]             232ReLU-112          [-1, 116, 14, 14]               0
InvertedResidual-113          [-1, 232, 14, 14]               0Conv2d-114          [-1, 116, 14, 14]          13,456BatchNorm2d-115          [-1, 116, 14, 14]             232ReLU-116          [-1, 116, 14, 14]               0Conv2d-117          [-1, 116, 14, 14]           1,044BatchNorm2d-118          [-1, 116, 14, 14]             232Conv2d-119          [-1, 116, 14, 14]          13,456BatchNorm2d-120          [-1, 116, 14, 14]             232ReLU-121          [-1, 116, 14, 14]               0
InvertedResidual-122          [-1, 232, 14, 14]               0Conv2d-123            [-1, 232, 7, 7]           2,088BatchNorm2d-124            [-1, 232, 7, 7]             464Conv2d-125            [-1, 232, 7, 7]          53,824BatchNorm2d-126            [-1, 232, 7, 7]             464ReLU-127            [-1, 232, 7, 7]               0Conv2d-128          [-1, 232, 14, 14]          53,824BatchNorm2d-129          [-1, 232, 14, 14]             464ReLU-130          [-1, 232, 14, 14]               0Conv2d-131            [-1, 232, 7, 7]           2,088BatchNorm2d-132            [-1, 232, 7, 7]             464Conv2d-133            [-1, 232, 7, 7]          53,824BatchNorm2d-134            [-1, 232, 7, 7]             464ReLU-135            [-1, 232, 7, 7]               0
InvertedResidual-136            [-1, 464, 7, 7]               0Conv2d-137            [-1, 232, 7, 7]          53,824BatchNorm2d-138            [-1, 232, 7, 7]             464ReLU-139            [-1, 232, 7, 7]               0Conv2d-140            [-1, 232, 7, 7]           2,088BatchNorm2d-141            [-1, 232, 7, 7]             464Conv2d-142            [-1, 232, 7, 7]          53,824BatchNorm2d-143            [-1, 232, 7, 7]             464ReLU-144            [-1, 232, 7, 7]               0
InvertedResidual-145            [-1, 464, 7, 7]               0Conv2d-146            [-1, 232, 7, 7]          53,824BatchNorm2d-147            [-1, 232, 7, 7]             464ReLU-148            [-1, 232, 7, 7]               0Conv2d-149            [-1, 232, 7, 7]           2,088BatchNorm2d-150            [-1, 232, 7, 7]             464Conv2d-151            [-1, 232, 7, 7]          53,824BatchNorm2d-152            [-1, 232, 7, 7]             464ReLU-153            [-1, 232, 7, 7]               0
InvertedResidual-154            [-1, 464, 7, 7]               0Conv2d-155            [-1, 232, 7, 7]          53,824BatchNorm2d-156            [-1, 232, 7, 7]             464ReLU-157            [-1, 232, 7, 7]               0Conv2d-158            [-1, 232, 7, 7]           2,088BatchNorm2d-159            [-1, 232, 7, 7]             464Conv2d-160            [-1, 232, 7, 7]          53,824BatchNorm2d-161            [-1, 232, 7, 7]             464ReLU-162            [-1, 232, 7, 7]               0
InvertedResidual-163            [-1, 464, 7, 7]               0Conv2d-164           [-1, 1024, 7, 7]         475,136BatchNorm2d-165           [-1, 1024, 7, 7]           2,048ReLU-166           [-1, 1024, 7, 7]               0Linear-167                   [-1, 10]          10,250
================================================================
Total params: 1,263,854
Trainable params: 1,263,854
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 47.93
Params size (MB): 4.82
Estimated Total Size (MB): 53.32
----------------------------------------------------------------

第4节 PyTorch 实践

4.1 模型训练代码

  加载同级目录下 train.py 程序代码

 

# %load train.py import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import time import argparse import sys import torch import torch.nn as nn import torch.optim as optim import torch.backends.cudnn as cudnn from torchvision import datasets, transforms from torch.autograd import Variable import matplotlib as mpl import matplotlib.pyplot as plt mpl.rc('axes', labelsize = 14) mpl.rc('xtick', labelsize = 12) mpl.rc('ytick', labelsize = 12) sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from shuffle import build_shufflev2 from squeeze import build_squeeze from datasets.config import * from datasets.cifar import CIFAR10 from datasets.flower import shuffle_flower from datasets.oxford_iiit import shuffle_oxford def str2bool(v): return v.lower() in ("yes", "true", "t", "1") parser = argparse.ArgumentParser( description='Image Classification Training With Pytorch') train_set = parser.add_mutually_exclusive_group() parser.add_argument('--dataset', default='Flower', choices=['Flower', 'Oxford-IIIT', 'CIFAR-10'], type=str, help='Flower, Oxford-IIIT, CIFAR-10') parser.add_argument('--dataset_root', default=FLOWER_ROOT, help='Dataset root directory path') parser.add_argument('--model', default='SqueezeNet', choices=['SqueezeNet', 'ShuffleNet'], type=str, help='SqueezeNet or ShuffleNet') parser.add_argument('--pretrained', default=True, type=str2bool, help='Using pretrained model weights') parser.add_argument('--crop_size', default=224, type=int, help='Resized crop value') parser.add_argument('--batch_size', default=32, type=int, help='Batch size for training') parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') parser.add_argument('--epoch_size', default=20, type=int, help='Number of Epoches for training') parser.add_argument('--cuda', default=True, type=str2bool, help='Use CUDA to train model') parser.add_argument('--shuffle', default=False, type=str2bool, help='Shuffle new train and test folders') parser.add_argument('--lr', '--learning-rate', default=2e-4, type=float, help='initial learning rate') parser.add_argument('--save_folder', default='weights/', help='Directory for saving checkpoint models') parser.add_argument('--photo_folder', default='results/', help='Directory for saving photos') args = parser.parse_args() if not os.path.exists(args.save_folder): os.mkdir(args.save_folder) if not os.path.exists(args.photo_folder): os.mkdir(args.photo_folder) data_transform = transforms.Compose([transforms.RandomResizedCrop(args.crop_size), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) def train(): if args.dataset == 'Flower': if not os.path.exists(FLOWER_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = FLOWER_ROOT train_path = os.path.join(FLOWER_ROOT, 'train') if not os.path.exists(train_path) or args.shuffle: shuffle_flower() dataset = datasets.ImageFolder(root=train_path,transform=data_transform) if args.dataset == 'Oxford-IIIT': if not os.path.exists(OXFORD_IIIT_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = OXFORD_IIIT_ROOT train_path = os.path.join(OXFORD_IIIT_ROOT, 'train') if not os.path.exists(train_path) or args.shuffle: shuffle_oxford() dataset = datasets.ImageFolder(root=train_path,transform=data_transform) if args.dataset == 'CIFAR-10': if not os.path.exists(CIFAR_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = CIFAR_ROOT dataset = CIFAR10(train=True,transform=data_transform,target_transform=None) classes = dataset.classes if args.model == 'SqueezeNet': net = build_squeeze(phase='train', num_classes=len(classes), pretrained=args.pretrained) if args.model == 'ShuffleNet': net = build_shufflev2(phase='train', num_classes=len(classes), pretrained=args.pretrained) if args.cuda and torch.cuda.is_available(): net = torch.nn.DataParallel(net) cudnn.benchmark = True net.cuda() optimizer = optim.Adam(net.parameters(), lr=args.lr) criterion = nn.CrossEntropyLoss() epoch_size = args.epoch_size print('Loading the dataset...') data_loader = torch.utils.data.DataLoader(dataset, args.batch_size, num_workers=args.num_workers, shuffle=True, pin_memory=True) print('Training on:', args.dataset) print('Using model:', args.model) print('Using the specified args:') print(args) loss_list = [] acc_list = [] for epoch in range(epoch_size): net.train() train_loss = 0.0 correct = 0 total = len(dataset) t0 = time.perf_counter() for step, data in enumerate(data_loader, start=0): images, labels = data if args.cuda: images = Variable(images.cuda()) labels = Variable(labels.cuda()) else: images = Variable(images) labels = Variable(labels) # forward outputs = net(images) # backprop optimizer.zero_grad() loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics train_loss += loss.item() _, predicted = outputs.max(1) correct += predicted.eq(labels).sum().item() # print train process rate = (step + 1) / len(data_loader) a = "*" * int(rate * 50) b = "." * int((1 - rate) * 50) print("\rEpoch {}: {:^3.0f}%[{}->{}]{:.3f}".format(epoch+1, int(rate * 100), a, b, loss), end="") print(' Running time: %.3f' % (time.perf_counter() - t0)) acc = 100.*correct/ total loss = train_loss / step print('train loss: %.6f, acc: %.3f%% (%d/%d)' % (loss, acc, correct, total)) loss_list.append(loss) acc_list.append(acc/100) torch.save(net.state_dict(),args.save_folder + args.dataset + "_" + args.model + '.pth') plt.plot(range(epoch_size), loss_list, range(epoch_size), acc_list) plt.xlabel('Epoches') plt.ylabel('Sparse CrossEntropy Loss | Accuracy') plt.savefig(os.path.join( os.path.dirname( os.path.abspath(__file__)), args.photo_folder, args.dataset + "_" + args.model + "_train_details.png")) if __name__ == '__main__': train()

程序输入参数说明

  • dataset

  训练采用的数据集,目前提供 Flower, Oxford-IIIT, CIFAR-10 供选择。 点击查看数据集加载Demo

  • dataset_root:

  数据集读取地址, default已设置为数据集相对路径,部署在云端可能需要修改

  • model:

  训练使用的算法模型,目前提供 SqueezeNet, ShuffleNet 等卷积神经网络

  • pretrained

  是否使用 PyTorch 预训练权重

  • crop_size:

  数据图像预处理剪裁大小,default为224,只有 LeNet 默认使用 32\times3232×32 尺寸大小

  • shuffle

  是否重新生成新的train-test数据集样本

  • batch_size:

  单次训练所抓取的数据样本数量,default为32

  • num_workers:

  加载数据所使用线程个数,default为0,n\in (2,4,8,12\dots)n∈(2,4,8,12…)

  • epoch_size:

  训练次数, default为20

  • cuda:

  是否调用GPU训练

  • lr:

  超参数学习率,采用Adam优化函数,default为 0.0020.002

  • save_folder:

  模型权重保存地址

程序输出文件说明

  • 训练细节

  print 于 python console, 包括单个epoch训练时间、训练集损失值、准确率

  • 模型权重

  模型保存路径为 ./weight/{dataset}_{model}.pth

  • 损失函数与正确率

  图片保存路径为 ./result/{dataset}_{model}_train_details.png

4.2 模型测试代码

  加载同级目录下 test.py 程序代码

 

# %load test.py import sys import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import argparse import torch import torch.nn as nn import torch.backends.cudnn as cudnn from torchvision import transforms, datasets from torch.autograd import Variable import itertools import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt mpl.rc('axes', labelsize = 14) mpl.rc('xtick', labelsize = 12) mpl.rc('ytick', labelsize = 12) sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from shuffle import build_shufflev2 from squeeze import build_squeeze from datasets.config import * from datasets.cifar import CIFAR10 parser = argparse.ArgumentParser( description='Convolutional Neural Network Testing With Pytorch') parser.add_argument('--dataset', default='Flower', choices=['Flower', 'Oxford-IIIT', 'CIFAR-10'], type=str, help='Flower, Oxford-IIIT, or CIFAR-10') parser.add_argument('--dataset_root', default=FLOWER_ROOT, help='Dataset root directory path') parser.add_argument('--model', default='SqueezeNet', choices=['SqueezeNet', 'ShuffleNet'], type=str, help='SqueezeNet or ShuffleNet') parser.add_argument('--crop_size', default=224, type=int, help='Resized crop value') parser.add_argument('--batch_size', default=32, type=int, help='Batch size for training') parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') parser.add_argument('--weight', default='weights/{}_{}.pth', type=str, help='Trained state_dict file path to open') parser.add_argument('--cuda', default=True, type=bool, help='Use cuda to train model') parser.add_argument('--pretrained', default=True, type=bool, help='Using pretrained model weights') parser.add_argument('-f', default=None, type=str, help="Dummy arg so we can load in Jupyter Notebooks") args = parser.parse_args() args.weight = args.weight.format(args.dataset,args.model) data_transform = transforms.Compose([transforms.RandomResizedCrop(args.crop_size), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) def confusion_matrix(preds, labels, conf_matrix): for p, t in zip(preds, labels): conf_matrix[p, t] += 1 return conf_matrix def save_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues): plt.imshow(cm, interpolation='nearest', cmap=cmap) plt.title(title) plt.colorbar() tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=90) plt.yticks(tick_marks, classes) plt.axis("equal") ax = plt.gca() left, right = plt.xlim() ax.spines['left'].set_position(('data', left)) ax.spines['right'].set_position(('data', right)) for edge_i in ['top', 'bottom', 'right', 'left']: ax.spines[edge_i].set_edgecolor("white") thresh = cm.max() / 2. for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): num = '{:.2f}'.format(cm[i, j]) if normalize else int(cm[i, j]) plt.text(j, i, num, verticalalignment='center', horizontalalignment="center", color="white" if num > thresh else "black") plt.ylabel('True label') plt.xlabel('Predicted label') plt.savefig(os.path.join( os.path.dirname( os.path.abspath(__file__)), "results", args.dataset + '_confusion_matrix.png')) def test(): # load data if args.dataset == 'Flower': if not os.path.exists(FLOWER_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = FLOWER_ROOT test_path = os.path.join(FLOWER_ROOT, 'val') if not os.path.exists(test_path): parser.error('Must train models before evaluating') dataset = datasets.ImageFolder(root=test_path,transform=data_transform) if args.dataset == 'Oxford-IIIT': if not os.path.exists(OXFORD_IIIT_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = OXFORD_IIIT_ROOT test_path = os.path.join(OXFORD_IIIT_ROOT, 'val') if not os.path.exists(test_path): parser.error('Must train models before evaluating') dataset = datasets.ImageFolder(root=test_path,transform=data_transform) if args.dataset == 'CIFAR-10': if not os.path.exists(CIFAR_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = CIFAR_ROOT dataset = CIFAR10(train=False,transform=data_transform,target_transform=None) classes = dataset.classes num_classes = len(classes) data_loader = torch.utils.data.DataLoader(dataset, args.batch_size, num_workers=args.num_workers, shuffle=True, pin_memory=True) # load net if args.model == 'SqueezeNet': net = build_squeeze(phase='train', num_classes=len(classes), pretrained=args.pretrained) if args.model == 'ShuffleNet': net = build_shufflev2(phase='train', num_classes=len(classes), pretrained=args.pretrained) if args.cuda and torch.cuda.is_available(): net = torch.nn.DataParallel(net) cudnn.benchmark = True net.cuda() net.load_state_dict(torch.load(args.weight)) print('Finish loading model: ', args.weight) net.eval() print('Training on:', args.dataset) print('Using model:', args.model) print('Using the specified args:') print(args) # evaluation criterion = nn.CrossEntropyLoss() test_loss = 0 correct = 0 total = 0 conf_matrix = torch.zeros(num_classes, num_classes) class_correct = list(0 for i in range(num_classes)) class_total = list(0 for i in range(num_classes)) with torch.no_grad(): for step, data in enumerate(data_loader): images, labels = data if args.cuda: images = Variable(images.cuda()) labels = Variable(labels.cuda()) else: images = Variable(images) labels = Variable(labels) # forward outputs = net(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = outputs.max(1) conf_matrix = confusion_matrix(predicted, labels=labels, conf_matrix=conf_matrix) total += labels.size(0) correct += predicted.eq(labels).sum().item() c = (predicted.eq(labels)).squeeze() for i in range(c.size(0)): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 acc = 100.* correct / total loss = test_loss / step print('test loss: %.6f, acc: %.3f%% (%d/%d)' % (loss, acc, correct, total)) for i in range(num_classes): print('accuracy of %s : %.3f%% (%d/%d)' % ( str(classes[i]), 100 * class_correct[i] / class_total[i], class_correct[i], class_total[i])) save_confusion_matrix(conf_matrix.numpy(), classes=classes, normalize=False, title = 'Normalized confusion matrix') if __name__ == '__main__': test()

程序输入参数说明

  训练采用的数据集,目前提供 Flower, Oxford-IIIT, CIFAR-10 供选择。 点击查看数据集加载Demo

  • dataset_root:

  数据集读取地址, default已设置为数据集相对路径,部署在云端可能需要修改

  • model:

  训练使用的算法模型,目前提供 SqueezeNet, ShuffleNet 等卷积神经网络

  • pretrained

  是否使用 PyTorch 预训练权重

  • crop_size:

  数据图像预处理剪裁大小,default为224,只有 LeNet 默认使用 32\times3232×32 尺寸大小

  • shuffle

  单次训练所抓取的数据样本数量,default为32

  • num_workers:

  加载数据所使用线程个数,default为0,n\in (2,4,8,12\dots)n∈(2,4,8,12…)

  • trained_model:

  模型权重保存路径,default为 train.py 生成的ptb文件路径

  • cuda:

  是否调用GPU训练

程序输出文件说明

  • 测试集损失值与准确率

  print 于 python console 第一行

  • 各类别准确率

  print 于 python console 后续列表

  • 混淆矩阵

  图片保存路径为 ./photos/%_confusion_matrix.png

第5节 Flower 数据集

  • Flower 数据集 来自 Tensorflow 团队,创建于 2019 年 1 月,作为 入门级轻量数据集 包含5个花卉类别 [‘daisy’, ‘dandelion’, ‘roses’, ‘sunflowers’, ‘tulips’]

  • Flower 数据集 是深度学习图像分类中经典的一个数据集,各个类别有 [633, 898, 641, 699, 799] 个样本,每个样本都是一张 320\times232320×232 像素的RGB图片

  • Dataset 库中的 flower.py 按照 0.1 的比例实现训练集与测试集的 样本分离

5.1 SqueezeNet

 

%run train.py --dataset Flower --model SqueezeNet --pretrained True

 

%run test.py --dataset Flower --model SqueezeNet --pretrained True

5.2 ShuffleNet

 

%run train.py --dataset Flower --model ShuffleNet --pretrained True

 

%run test.py --dataset Flower --model ShuffleNet --pretrained True

第6节 Oxford-IIIT 数据集

  • Oxford-IIIT 数据集覆盖 30 个种类的猫狗品种,每个类别收集了约 200 张图像样本
  • Oxford-IIIT 中每张图像在尺寸、姿势、光暗程度上有很大的浮动,但所有的图像都匹配了相关联的品种、头部框架定位、三维像素点语义分割的标注信息
  • Dataset 库中的 oxford_iiit.py 按照 0.1 的比例实现训练集与测试集的 样本分离

6.1 SqueezeNet

 

%run train.py --dataset Oxford-IIIT --model SqueezeNet --pretrained True

 

%run test.py --dataset Oxford-IIIT --model SqueezeNet --pretrained True

6.2 ShuffleNet

 

%run train.py --dataset Oxford-IIIT --model ShuffleNet --pretrained True

 

%run test.py --dataset Oxford-IIIT --model ShuffleNet --pretrained True

第7节 CIFAR-10 数据集

  • CIFAR-10 数据集是 Visual Dictionary (Teaching computers to recognize objects) 的子集,由三个多伦多大学教授收集,主要来自Google和各类搜索引擎的图片
  • CIFAR-10 数据集包含 60000 张 32\times3232×32 的RBG彩色图像,共计 10 个 包含 6000 张样本图像的不同类别,训练集包含 50000 张图像样本,测试集包含 10000 张图像样本
  • CIFAR-10 数据集在深度学习初期 (ImageNet 问世前) 一直是衡量各种算法模型的 benchmark,但其 32\times3232×32 的图像尺寸逐渐无法满足日渐飞速迭代的神经网络结构

7.1 SqueezeNet

 

%run train.py --dataset CIFAR-10 --model SqueezeNet --pretrained True

 

%run test.py --dataset CIFAR-10 --model SqueezeNet --pretrained True

7.2 ShuffleNet

 

%run train.py --dataset CIFAR-10 --model ShuffleNet --pretrained True

 

%run test.py --dataset CIFAR-10 --model ShuffleNet --pretrained True

开始实验

这篇关于第06章 移动端微量神经网络模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/405548

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

Retrieval-based-Voice-Conversion-WebUI模型构建指南

一、模型介绍 Retrieval-based-Voice-Conversion-WebUI(简称 RVC)模型是一个基于 VITS(Variational Inference with adversarial learning for end-to-end Text-to-Speech)的简单易用的语音转换框架。 具有以下特点 简单易用:RVC 模型通过简单易用的网页界面,使得用户无需深入了

透彻!驯服大型语言模型(LLMs)的五种方法,及具体方法选择思路

引言 随着时间的发展,大型语言模型不再停留在演示阶段而是逐步面向生产系统的应用,随着人们期望的不断增加,目标也发生了巨大的变化。在短短的几个月的时间里,人们对大模型的认识已经从对其zero-shot能力感到惊讶,转变为考虑改进模型质量、提高模型可用性。 「大语言模型(LLMs)其实就是利用高容量的模型架构(例如Transformer)对海量的、多种多样的数据分布进行建模得到,它包含了大量的先验

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}

秋招最新大模型算法面试,熬夜都要肝完它

💥大家在面试大模型LLM这个板块的时候,不知道面试完会不会复盘、总结,做笔记的习惯,这份大模型算法岗面试八股笔记也帮助不少人拿到过offer ✨对于面试大模型算法工程师会有一定的帮助,都附有完整答案,熬夜也要看完,祝大家一臂之力 这份《大模型算法工程师面试题》已经上传CSDN,还有完整版的大模型 AI 学习资料,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

AI Toolkit + H100 GPU,一小时内微调最新热门文生图模型 FLUX

上个月,FLUX 席卷了互联网,这并非没有原因。他们声称优于 DALLE 3、Ideogram 和 Stable Diffusion 3 等模型,而这一点已被证明是有依据的。随着越来越多的流行图像生成工具(如 Stable Diffusion Web UI Forge 和 ComyUI)开始支持这些模型,FLUX 在 Stable Diffusion 领域的扩展将会持续下去。 自 FLU

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者