写点什么

PyTorch 实现 MobileNetV1 用于图像分类

  • 2025-05-27
    上海
  • 本文字数:4798 字

    阅读完需:约 16 分钟

PyTorch 实现 MobileNetV1 用于图像分类

本实验主要介绍了如何在昇腾上,使用 pytorch 对经典的 MobileNetV1 模型在公开的 CIFAR10 数据集进行分类训练的实战讲解。内容包括 MobileNetV1 模型特点介绍MobileNetV1 网络架构剖析MobileNetV1 网络模型代码实战分析等等。


本实验的目录结构安排如下所示:


  • MobileNetV1 网络模型介绍

  • MobileNetV1 的网络架构剖析

  • 深度可分离卷积

  • MobileNetV1 网络模型代码实现分析

  • MobileNetV1 网络用于 cifar 数据集分类实战

MobileNetV1 网络模型介绍

卷积神经网络(CNN)被普遍应用在计算机视觉领域,并且已经取得了不错的效果。为了追求分类准确度,模型深度越来越深,模型复杂度也越来越高,如深度残差网络(ResNet)其层数已经多达 152 层。然而,在某些真实的应用场景如移动或者嵌入式设备,如此大而复杂的模型是难以被应用的。


首先是模型过于庞大,面临着内存不足的问题,其次这些场景要求低延迟,或者说响应速度要快,想象一下自动驾驶汽车的行人检测系统如果速度很慢会发生什么可怕的事情。所以,研究小而高效的 CNN 模型在这些场景至关重要。



MobileNetV1 网络专注于移动端或者嵌入式设备中的轻量级 CNN 网络,该论文最大的创新点是,使用深度可分离卷积(深度卷积+点卷积)代替标准卷积来显著降低参数量的同时小幅度降低精度。

MobileNetV1 的网络架构剖析

深度可分离卷积

MobileNet 的基本单元是深度可分离卷积(depthwise separable convolution),下图中右侧部分,深度可分离卷积其实是一种可分解卷积操作,其可以分解为两个更小的操作:depthwise convolution 和 pointwise convolution。



Depthwise convolution 和标准卷积不同,对于标准卷积其卷积核是作用在所有的输入通道上,而 depthwise convolution 针对每个输入通道采用不同的卷积核,就是说一个卷积核对应一个通道,所以说 depthwise convolution 是 depth 级别的操作。而 pointwise convolution 其实就是普通的卷积,只不过其采用 1x1 的卷积核。

深度可分离卷积为什么可以减少参数量


一个输入 shape 为 FxFXM 与卷积核大小为 KxKxN 执行一次卷积操作需要的乘法计算量:(KxK)xMxNxFxF。


常规卷积所需要的乘法计算量是:2x2x3x8x5x5 = 2400


深度可分离卷积所需要的乘法计算量:2x2x3x5x5 + 3x8x5x5 = 900


import torchimport torch.nn as nn#  深度可分离卷积 DSC, 深度卷积 Depthwise + 逐点卷积 Pointwiseclass DSCconv(nn.Module):    def __init__(self, in_ch, out_ch, stride=1):        super(DSCconv, self).__init__()        # 深度卷积, (DW+BN+ReLU)        self.depthConv = nn.Sequential(              nn.Conv2d(in_ch, in_ch, kernel_size=3, stride=stride,                      padding=1, groups=in_ch, bias=False),            nn.BatchNorm2d(in_ch),            nn.ReLU6(inplace=True))         # 逐点卷积, (PW+BN+ReLU)        self.pointConv = nn.Sequential(             nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=1, bias=False),            nn.BatchNorm2d(out_ch),            nn.ReLU6(inplace=True))
def forward(self, x): x = self.depthConv(x) x = self.pointConv(x) return x
复制代码

MobileNetV1 网络代码实现分析

MobileNetV1 简单的直筒型网络一条道走到黑,没有 shortcut,唯一的黑科技也只有深度分离卷积了,但是这个也不是啥首创,Xception 就用过这种技术了,之所以 MobileNetV1 能发论文,因为首次有人关注到 Mobile AI 领域,让 Mobile 能够流畅的运行一些简单的 AI 模型。



整个 MobileNetV1 网络用一个 MobileNetV1 类实现,'self.stage1'、'self.stage2'与'self.stage3'分别对应图中红框内模块,'self.avgpool'、self.linear 与'self.softmax'与图中最后三个输出层相对应。


class MobileNetV1(nn.Module):    def __init__(self, input_channel=3, num_classes=100):        super(MobileNetV1, self).__init__()        self.num_classes = num_classes        self.entry = nn.Sequential(            nn.Conv2d(input_channel, 32, kernel_size=3, stride=1, padding=1, bias=False),            nn.BatchNorm2d(32),            nn.ReLU6(inplace=True))         # 1        self.stage1 = nn.Sequential(            DSCconv(32, 64, 1),            DSCconv(64, 128, 2),            DSCconv(128, 128, 1),            DSCconv(128, 256, 2),            DSCconv(256, 256, 1))         # 2        self.stage2 = nn.Sequential(            DSCconv(256, 512, 2),            DSCconv(512, 512, 1),            DSCconv(512, 512, 1),            DSCconv(512, 512, 1),            DSCconv(512, 512, 1),            DSCconv(512, 512, 1))         # 3        self.stage3 = nn.Sequential(            DSCconv(512, 1024, 2),            DSCconv(1024, 1024, 1))
self.avgpool = nn.AdaptiveAvgPool2d((1,1)) # torch.Size([batch, 1024, 1, 1])
self.linear = nn.Linear(in_features=1024, out_features=num_classes) self.softmax = nn.Softmax(dim=1)
def forward(self, x): x = self.entry(x) x = self.stage1(x) x = self.stage2(x) x = self.stage3(x) x = self.avgpool(x) x = x.view(x.size(0), -1) x = self.linear(x) out = self.softmax(x) return out
复制代码

MobileNetV1 网络用于 cifar 数据集分类实战

基于上述搭建好的网络模型,我们现在就可以正式来使用该模型开始训练 cifir 数据集。


导入昇腾 npu 相关库 transfer_to_npu、该模块可以使能模型自动迁移至昇腾上。


import torch_npufrom torch_npu.contrib import transfer_to_npu
复制代码


torchvision 模块中集成了一些当今比较流行的数据集、模型架构和用于计算机视觉的常见图像转换功能,torchvision 模块中含有本次实验所需要的 CIFAR 数据集,因此导入该模块用于数据集的下载。tqdm 是用于训练过程中训练进度条,便于我们能够清晰的看到整个训练过程。


import torchvisionimport torchvision.transforms as transformsfrom tqdm import tqdm
复制代码


数据集预处理功能定义: 对图像数据集进行不同程度的变化,包括裁剪、翻转等方式增加数据的多样性,防止过拟合现象的出现,以增强模型的泛化能力。


调用了 torchvision 中的 transform 库中的 compose 方法,使用裁剪(RandomCrop)、翻转(RandomHorizontalFlip)等组合成 tensor 形式后并对 tensor 进行正则化(Normalize)。


transform_train = transforms.Compose([    transforms.RandomCrop(32, padding=4),    transforms.RandomHorizontalFlip(),    transforms.ToTensor(),    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])transform_test = transforms.Compose([    transforms.ToTensor(),    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),])
复制代码


cifar 数据集共有 60000 张彩色图像,这些图像是 32*32,分为 10 个类,每类 6000 张图。有 50000 张用于训练,构成了 5 个训练批,每一批 10000 张图;另外 10000 用于测试,单独构成一批。测试批的数据里,取自 10 类中的每一类,每一类随机取 1000 张。抽剩下的就随机排列组成了训练批。注意一个训练批中的各类图像并不一定数量相同,总的来看训练批,每一类都有 5000 张图。



数据集加载: torchvision 中集成了一些通用的开源数据集,其中也包含 cifar,此处通过 torchvision 函数加载 cifar 数据集到工作目录上的指定路径,如果已经下载好了,会直接校验通过,不会二次进行下载。


trainset = torchvision.datasets.CIFAR10(    root='/home/ma-user/work/resnet50Experiments/dataset/cifar-10-batches-py', train=True, download=True, transform=transform_train)trainloader = torch.utils.data.DataLoader(    trainset, batch_size=128, shuffle=True)testset = torchvision.datasets.CIFAR10(    root='/home/ma-user/work/resnet50Experiments/dataset/cifar-10-batches-py', train=False, download=True, transform=transform_test)testloader = torch.utils.data.DataLoader(    testset, batch_size=100, shuffle=False)classes = ('plane', 'car', 'bird', 'cat', 'deer',           'dog', 'frog', 'horse', 'ship', 'truck')
复制代码


训练模块: 根据传入的迭代次数'epoch'开始训练网络模型,这里需要在 model 开始前加入'net.train()',使用随机梯度下降算法是将梯度值初始化为 0('zero_grad()'),计算梯度、通过梯度下降算法更新模型参数的值以及统计每次训练后的 loss 值(每隔 100 次打印一次)。


def train(epoch):    net.train()    train_loss = 0.0    epoch_loss = 0.0    for batch_idx, (inputs, targets) in enumerate(tqdm(trainloader, 0)):        inputs, targets = inputs.to(device), targets.to(device)        optimizer.zero_grad()        outputs = net(inputs)        loss = criterion(outputs, targets)        loss.backward()        optimizer.step()        lr_scheduler.step()
train_loss += loss.item() epoch_loss += loss.item()
if batch_idx % 100 == 99: # 每100次迭代打印一次损失 print(f'[Epoch {epoch + 1}, Iteration {batch_idx + 1}] loss: {train_loss / 100:.3f}') train_loss = 0.0 return epoch_loss / len(trainloader)
复制代码


测试模块: 每训练一轮将会对最新得到的训练模型效果进行测试,使用的是数据集准备时期划分得到的测试集,每类约为 1000 张。


def test():    net.eval()    test_loss = 0    correct = 0    total = 0    with torch.no_grad():        for batch_idx, (inputs, targets) in enumerate(tqdm(testloader)):            inputs, targets = inputs.to(device), targets.to(device)            outputs = net(inputs)            loss = criterion(outputs, targets)
test_loss += loss.item() _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item() return 100 * correct / total
复制代码


主功能调用模块: 该模块用于开启模型在指定数据集(cifar)上训练,其中定义了硬件设备为昇腾 npu(device = 'npu'),定义了损失函数为交叉熵损失'CrossEntropyLoss()',梯度下降优化算法为 SGD 并同时指定了学习率等参数。


import torch.optim as optimdevice = 'npu'net = MobileNetV1(num_classes=10)net = net.to(device)criterion = nn.CrossEntropyLoss()optimizer = optim.SGD(net.parameters(), lr=1.0, weight_decay=5e-4)lr_scheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer,0.1,steps_per_epoch=len(trainloader),                                                   epochs=150,div_factor=25,final_div_factor=10000,pct_start=0.3)
复制代码


训练与测试的次数为 60 次,这里用户可以根据需要自行选择设置更高或更低,每个 epoch 的测试准确率都会被打印出来,如果不需要将代码注释掉即可。


for epoch in range(60):    epoch_loss = train(epoch)    test_accuray = test()    print(f'\nTest accuracy for AlexNet at epoch {epoch + 1}: {test_accuray:.2f}%')    print(f'Epoch loss for AlexNet at epoch {epoch + 1}: {epoch_loss:.3f}')
复制代码

Reference

[1] Howard A G. Mobilenets: Efficient convolutional neural networks for mobile vision applications[J]. arXiv preprint arXiv:1704.04861, 2017.


用户头像

还未添加个人签名 2024-12-19 加入

还未添加个人简介

评论

发布
暂无评论
PyTorch 实现MobileNetV1用于图像分类_永荣带你玩转昇腾_InfoQ写作社区