目录

1. 简介

1.1 ResNet18 模型介绍

1.2 迁移学习

2. 模型训练

2.1 选择云平台

2.2 模块导入

2.3 分析模型结构

2.4 修改模型结构

2.4.1 微调训练最后一层(fc)

2.4.2 微调训练所有层

2.5 执行训练

2.6 保存模型

3. 模型编译

3.1 配置 Vitis AI 量化器文件

3.2 执行编译

4. 总结

5. 参考资料

5.1 Featurize 使用技巧

5.2 迁移学习参考


1. 简介

本文分享完整使用 Vitis AI 开发实践过程,包括进行迁移学习,使用量化工具对浮点模型进行量化,并将 xmodel 部署在 DPU 照中。

使用迁移学习的方法,将预训练的 resnet18 模型从原来的 1000 类分类任务,改造为适应自定义的 30 类分类任务。本迁移学习使用同济自豪兄的分类模型。

1.1 ResNet18 模型介绍

ResNet18 是一种基于深度残差网络(ResNet)的卷积神经网络模型,由何凯明等人于2015年提出。ResNet 的核心思想是通过引入残差块(Residual Block),解决了深度网络中的梯度消失和退化问题,使得网络可以更深更有效地学习特征。

ResNet18 是 ResNet 系列中最简单的一个模型,共有18层。

  • 一个7×7的卷积层,输出通道数为64,步幅为2,后接批量归一化(Batch Normalization)和ReLU激活函数。
  • 一个3×3的最大池化层(Max Pooling),步幅为2。
  • 四个由残差块组成的模块,每个模块包含两个或三个残差块,每个残差块由两个3×3的卷积层、批量归一化和ReLU激活函数组成。每个模块的第一个残差块可能会改变输入输出的通道数和步幅,以适应下一个模块。这四个模块的输出通道数分别为64、128、256、512,步幅分别为1、2、2、2。
  • 一个全局平均池化层(Global Average Pooling),将最后一个模块的输出转换为一个一维向量。
  • 一个全连接层(Fully Connected),将一维向量映射到最终的类别数1000类上。

1.2 迁移学习

根据不同的任务和数据集,迁移学习有以下几种常见的方法:

1. 微调网络:这种方法是在预训练模型的基础上,修改最后一层或几层,并且对整个网络进行微调训练。这种方法适用于新数据集和原数据集相似度较高,且新数据集规模较大的情况。

2. 特征提取:这种方法是将预训练模型看作一个特征提取器,冻结除了最后一层以外的所有层,只修改和训练最后一层。这种方法适用于新数据集和原数据集相似度较高,但新数据集规模较小的情况。

3. 模型蒸馏:这种方法是将预训练模型看作一个教师模型,用它来指导一个更小的学生模型,使学生模型能够学习到教师模型的知识。这种方法适用于新数据集和原数据集相似度较低,或者需要减少模型大小和计算量的情况。

2. 模型训练

2.1 选择云平台

本文省去手动搭建 Pytorch 环境,直接通过云平台选择现成的环境:

Featurizeicon-default.png?t=N7T8https://featurize.cn/推荐如下配置:

RTX 3060

PyTorch 1(版本较老的环境,兼容性好)

Docker: v20.10.10

Python: v3.7

PyTorch: v1.10

TensorFlow: v2.7.0

PyTorch 2.0 相较于 PyTorch 1.x 有几个显著的改进和新特性:

  • 性能提升:PyTorch 2.0 引入了 torch.compile,一个可选的编译模式,可以显著提高模型在训练和推理时的性能。
  • 动态形状支持:PyTorch 2.0 支持动态形状,可以处理不同大小的张量而无需重新编译。
  • 分布式训练:改进了对分布式训练的支持,使得在多GPU和多节点环境下的训练更加高效。
  • 新技术栈:引入了如 TorchDynamo、AOTAutograd、PrimTorch 和 TorchInductor 等新技术,这些技术在底层优化了 PyTorch 的操作。

为了保持兼容性,本文使用 PyTorch V1.10 版本。

2.2 模块导入

import time
import os

import numpy as np
from tqdm import tqdm

import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F

import matplotlib.pyplot as plt
%matplotlib inline

# 忽略红色提示
import warnings
warnings.filterwarnings("ignore")

2.3 分析模型结构

然后导入预训练模型,并查看网络结构:

model = models.resnet18(pretrained=True) # 载入预训练模型
summary(model, (3, 224, 224))

---以下为执行结果
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1         [-1, 64, 112, 112]           9,408
       BatchNorm2d-2         [-1, 64, 112, 112]             128
              ReLU-3         [-1, 64, 112, 112]               0
         MaxPool2d-4           [-1, 64, 56, 56]               0
            Conv2d-5           [-1, 64, 56, 56]          36,864
       BatchNorm2d-6           [-1, 64, 56, 56]             128
              ReLU-7           [-1, 64, 56, 56]               0
            Conv2d-8           [-1, 64, 56, 56]          36,864
       BatchNorm2d-9           [-1, 64, 56, 56]             128
             ReLU-10           [-1, 64, 56, 56]               0
       BasicBlock-11           [-1, 64, 56, 56]               0
           Conv2d-12           [-1, 64, 56, 56]          36,864
      BatchNorm2d-13           [-1, 64, 56, 56]             128
             ReLU-14           [-1, 64, 56, 56]               0
           Conv2d-15           [-1, 64, 56, 56]          36,864
      BatchNorm2d-16           [-1, 64, 56, 56]             128
             ReLU-17           [-1, 64, 56, 56]               0
       BasicBlock-18           [-1, 64, 56, 56]               0
           Conv2d-19          [-1, 128, 28, 28]          73,728
      BatchNorm2d-20          [-1, 128, 28, 28]             256
             ReLU-21          [-1, 128, 28, 28]               0
           Conv2d-22          [-1, 128, 28, 28]         147,456
      BatchNorm2d-23          [-1, 128, 28, 28]             256
           Conv2d-24          [-1, 128, 28, 28]           8,192
      BatchNorm2d-25          [-1, 128, 28, 28]             256
             ReLU-26          [-1, 128, 28, 28]               0
       BasicBlock-27          [-1, 128, 28, 28]               0
           Conv2d-28          [-1, 128, 28, 28]         147,456
      BatchNorm2d-29          [-1, 128, 28, 28]             256
             ReLU-30          [-1, 128, 28, 28]               0
           Conv2d-31          [-1, 128, 28, 28]         147,456
      BatchNorm2d-32          [-1, 128, 28, 28]             256
             ReLU-33          [-1, 128, 28, 28]               0
       BasicBlock-34          [-1, 128, 28, 28]               0
           Conv2d-35          [-1, 256, 14, 14]         294,912
      BatchNorm2d-36          [-1, 256, 14, 14]             512
             ReLU-37          [-1, 256, 14, 14]               0
           Conv2d-38          [-1, 256, 14, 14]         589,824
      BatchNorm2d-39          [-1, 256, 14, 14]             512
           Conv2d-40          [-1, 256, 14, 14]          32,768
      BatchNorm2d-41          [-1, 256, 14, 14]             512
             ReLU-42          [-1, 256, 14, 14]               0
       BasicBlock-43          [-1, 256, 14, 14]               0
           Conv2d-44          [-1, 256, 14, 14]         589,824
      BatchNorm2d-45          [-1, 256, 14, 14]             512
             ReLU-46          [-1, 256, 14, 14]               0
           Conv2d-47          [-1, 256, 14, 14]         589,824
      BatchNorm2d-48          [-1, 256, 14, 14]             512
             ReLU-49          [-1, 256, 14, 14]               0
       BasicBlock-50          [-1, 256, 14, 14]               0
           Conv2d-51            [-1, 512, 7, 7]       1,179,648
      BatchNorm2d-52            [-1, 512, 7, 7]           1,024
             ReLU-53            [-1, 512, 7, 7]               0
           Conv2d-54            [-1, 512, 7, 7]       2,359,296
      BatchNorm2d-55            [-1, 512, 7, 7]           1,024
           Conv2d-56            [-1, 512, 7, 7]         131,072
      BatchNorm2d-57            [-1, 512, 7, 7]           1,024
             ReLU-58            [-1, 512, 7, 7]               0
       BasicBlock-59            [-1, 512, 7, 7]               0
           Conv2d-60            [-1, 512, 7, 7]       2,359,296
      BatchNorm2d-61            [-1, 512, 7, 7]           1,024
             ReLU-62            [-1, 512, 7, 7]               0
           Conv2d-63            [-1, 512, 7, 7]       2,359,296
      BatchNorm2d-64            [-1, 512, 7, 7]           1,024
             ReLU-65            [-1, 512, 7, 7]               0
       BasicBlock-66            [-1, 512, 7, 7]               0
AdaptiveAvgPool2d-67            [-1, 512, 1, 1]               0
           Linear-68                 [-1, 1000]         513,000
================================================================
Total params: 11,689,512
Trainable params: 11,689,512
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.57
Forward/backward pass size (MB): 62.79
Params size (MB): 44.59
Estimated Total Size (MB): 107.96
----------------------------------------------------------------

可以原始看到最后一层有1000个特征输出,对应1000分类。我们要做的,就是使用特征提取方法,修改最后一层(FC),实现一个30分类的特征输出。

2.4 修改模型结构

2.4.1 微调训练最后一层(fc)

查看当前 fc 层状态:

print(model.fc)
---
Linear(in_features=512, out_features=1000, bias=True)

修改全链接层,输入特征数(in_features)保持不变,输出特征数(out_features)设置为30:

model.fc = torch.nn.Linear(model.fc.in_features, 30)

查看修改后 fc 层状态: 

print(model.fc)
---
Linear(in_features=512, out_features=30, bias=True)

只微调训练最后一层全连接层的参数,其它层冻结 

optimizer = optim.Adam(model.fc.parameters())

2.4.2 微调训练所有层

optimizer = optim.Adam(model.parameters())

2.5 执行训练

配置优化器,只微调输出层(FC),然后执行训练:

# 遍历每个 EPOCH
for epoch in tqdm(range(EPOCHS)):
    model.train()
    for images, labels in train_loader:  # 获取训练集的一个 batch,包含数据和标注
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)           # 前向预测,获得当前 batch 的预测结果
        loss = criterion(outputs, labels) # 比较预测结果和标注,计算当前 batch 的交叉熵损失函数
        
        optimizer.zero_grad()
        loss.backward()                   # 损失函数对神经网络权重反向传播求梯度
        optimizer.step()                  # 优化更新神经网络权重

---以下为执行结果
100%|██████████| 20/20 [03:04<00:00,  9.24s/it]

在测试集的准确度为:85.900 %

注:本文不详解如何构建数据集加载器,以及测试部分相关代码,只保留关键代码以演示使用迁移学习过程,需要读者自行补齐机器学习相关知识。

2.6 保存模型

导出迁移学习后的结果模型:

torch.save(model, 'resnet18_out30.pth')

将在当前工作目录下,生成 resnet18_out30.pth 文件。

3. 模型编译

3.1 配置 Vitis AI 量化器文件

默认的量化配置如下:

"convert_relu6_to_relu": 关闭,
"include_cle": 启用,
"keep_first_last_layer_accuracy": 关闭,
"keep_add_layer_accuracy": 关闭,
"include_bias_corr": 开,
"target_device": "DPU",
"quantizable_data_type": ["input", "weights", "bias", "activation"],
"bit_width": 8,
"method": "diffs",
"round_mode": "std_round",
"symmetry": 启用,
"per_channel": 关闭,
"signed": 启用,
"narrow_range": 关闭,
"scale_type": "power_of_two",
"calib_statistic_method": "modal"

配置完毕后,需要按照上一讲的内容,进行校准和量化:

config_file= "./int8_config.json"
quantizer= torch_quantizer(quant_mode=quant_mode,
module=model,
input_args=(input),
device=device,
quant_config_file=config_file)

唯一的不同,是以上代码部分需要引用新配置的 json 文件。

3.2 执行编译

导入模块

#!pip install -i <https://pypi.tuna.tsinghua.edu.cn/simple> torchsummary
# torchsummary是一个用于查看网络结构,非必须

from torchsummary import summary
import torch, torchvision, random
from pytorch_nndct.apis import Inspector, torch_quantizer
import torchvision.transforms as transforms
from torchvision import models
from tqdm import tqdm
vai_c_xir -x /PATH/TO/quantized.xmodel \\\\
          -a /PATH/TO/arch.json        \\\\
          -o /OUTPUTPATH               \\\\
          -n netname

编译模型,只需这一条指令。

附:查看支持的型号

!ls -l /opt/vitis_ai/compiler/arch/

---
total 24
drwxr-xr-x 4 root root 4096 Jun 12  2022 DPUCADF8H
drwxr-xr-x 8 root root 4096 Jun 12  2022 DPUCAHX8H
drwxr-xr-x 5 root root 4096 Jun 12  2022 DPUCAHX8L
drwxr-xr-x 3 root root 4096 Jun 12  2022 DPUCVDX8G
drwxr-xr-x 6 root root 4096 Jun 12  2022 DPUCVDX8H
drwxr-xr-x 5 root root 4096 Jun 12  2022 DPUCZDX8G

附:查看DPUCZDX8G支持的板卡

!ls -l /opt/vitis_ai/compiler/arch/DPUCZDX8G

---
total 12
drwxr-xr-x 2 root root 4096 Jun 12  2022 KV260
drwxr-xr-x 2 root root 4096 Jun 12  2022 ZCU102
drwxr-xr-x 2 root root 4096 Jun 12  2022 ZCU104

执行完毕编译过程,就可以在对应目录中得到*.xmodel文件了~

4. 总结

本文主要介绍使用Vitis AI工具创建自定义的Xmodol,难点并不在工具本身,而是需要了解很多机器学习的知识。一个很好的出发点是使用Vitis Model Zoo库,是一个包含了大量预训练模型的资源库,这些模型涵盖了多种AI应用领域,如图像分类、目标检测、语义分割、人脸识别、自然语言处理等。我们可以利用这些模型作为一个起点,快速入门并开发自己的应用。这些是由Xilinx官方提供的经过优化和验证的模型,可以直接在Xilinx硬件平台上部署和运行。在熟悉自己的业务的需求,并建立数据集后,我们可以定制的模型。

5. 参考资料

5.1 Featurize 使用技巧

1. 同步盘内容复制。

同步盘,即 work 目录,绝对路径为 /home/featurize/work,是 Featurize 为所有用户提供的一种文件持久化的方案。用户可以将自己的代码、模型文件保存至同步盘中,这样即使在归还实例后同步盘中的文件也不会丢失。

将同步盘文件拷贝至顶层目录,并解压:

!cp ./work/Train_Custom_Dataset-main.zip ./
!unzip Train_Custom_Dataset-main.zip

5.2 迁移学习参考

https://github.com/TommyZihao/Train_Custom_Dataset/tree/mainhttps://github.com/TommyZihao/Train_Custom_Dataset/tree/main/%E5%9B%BE%E5%83%8F%E5%88%86%E7%B1%BBicon-default.png?t=N7T8https://github.com/TommyZihao/Train_Custom_Dataset/tree/main/%E5%9B%BE%E5%83%8F%E5%88%86%E7%B1%BB

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐