ResNet50 MNIST 量化示例

OE 包在 samples/onnx_ptq 路径下为您提供了一个 ONNX 模型的训练后量化 PTQ(Post-Training Quantization)转换示例,即位于 samples/onnx_ptq/resnet50_mnist 路径下的 ResNet50 MNIST 示例。

该示例以 MNIST 手写数字数据集上训练得到的 ResNet50 模型为对象,提供了从模型验证、校准数据准备、量化编译到单张推理和数据集精度评测的完整脚本。除第 5 步的数据集精度评测需要您自行准备 MNIST 测试集并在脚本中填写路径外,其余脚本无需改动即可直接运行。 如果您的模型输入是特征图、雷达点云特征、频谱数据这类非图像数据,可以直接参考本示例的配置方式。

注意

在进行以下操作前,请确认您已经参考 环境部署 章节完成了开发机环境安装。

示例内容说明

示例位于 OE 包的 samples/onnx_ptq/resnet50_mnist 路径下,浮点模型、校准图片和测试图片都随包提供,无需额外下载。目录内容如下:

resnet50_mnist/
├── 01_check.sh                     # 验证浮点模型
├── 02_preprocess.sh                # 准备校准数据
├── 03_build.sh                     # 量化编译
├── 04_inference.sh                 # 单张图片推理
├── 05_evaluate.sh                  # 数据集精度评测
├── resnet50_mnist_config.yaml      # 量化编译配置文件
├── preprocess.py                   # 前处理与测试集数据加载
├── postprocess.py                  # 后处理与精度统计
├── infer.py                        # 单张推理脚本
├── eval.py                         # 数据集评测脚本
├── resnet50_mnist.onnx             # 浮点 ONNX 模型,输入形状 1×1×28×28
├── calibration_data/               # 校准用 28×28 灰度 PNG 图片
├── test_data/                      # 单张推理验证用灰度 PNG 图片
└── README.md                       # 示例自带的简要说明

五个编号脚本对应完整的转换链路,按 01 到 05 的顺序依次执行即可。四个 Python 文件由编号脚本内部调用,各自的分工如下:

  • preprocess.py,负责校准数据预处理、单张推理前处理和测试集数据加载。

  • postprocess.py,负责分类后处理、精度统计和评测报告生成。

  • infer.py,单张推理脚本,由 04_inference.sh 调用。

  • eval.py,数据集评测脚本,由 05_evaluate.sh 调用。

注解

本示例中的 infer.py 和 eval.py 仅适配此示例,对其他模型不通用。

使用流程

环境准备

开始执行编号脚本前,请先参考 Docker 容器部署 一节完成 Docker 环境的安装和配置并进入容器,再进入示例目录:

cd samples/onnx_ptq/resnet50_mnist

示例本身自带浮点模型、校准图片和测试图片,无需额外准备。

准备评测数据集

唯一需要自行准备的是一份 MNIST 官方 IDX 格式测试集,供第 5 步的数据集精度评测使用。请准备好 t10k-images-idx3-ubyte 和 t10k-labels-idx1-ubyte 两个文件并放在同一目录下,具体配置方式见 第 5 步:评测数据集精度。

注解

如果您暂时只想跑通链路、确认前处理和推理是否正确,可以先跳过 IDX 测试集的准备,执行到第 4 步的单张推理为止。

准备完成后,即可按 01 到 05 的顺序依次执行五个脚本。

第 1 步:验证浮点模型

执行 01_check.sh 完成模型检查:

sh 01_check.sh

脚本内部调用 hb_compile 对 resnet50_mnist.onnx 做检查,示例默认按 J6E 平台验证,因此 --march 取值为 nash-e。如果您的目标平台不同,请改为对应取值,不同平台的量化策略差异见 平台差异说明 章节。

检查通过时,终端会逐层打印算子的部署情况。如果检查不通过,请根据终端打印或当前路径下生成的 hb_compile.log 确认报错信息,更多说明见 模型验证 章节。

第 2 步:准备校准数据

执行 02_preprocess.sh 生成校准数据:

sh 02_preprocess.sh

脚本内部调用 preprocess.py,从 calibration_data 目录读取校准图片,处理后存放到 calibration_data_float32 目录,默认取 100 张参与校准。这三项分别由 --src_dir、--dst_dir 和 --cal_img_num 参数控制,可以按需修改。

预处理步骤与训练前处理完全一致,即先读成单通道灰度图,再把像素值除以 255 缩放到 0~1,最后按 MNIST_MEAN 和 MNIST_STD 做标准化。这两个数值在 preprocess.py 中定义,均值取 0.1307,标准差取 0.3081,与模型训练时的取值一致,校准、单张推理和数据集评测三处共用同一份取值。

处理后每张图片保存为一个 .npy 文件,文件名形如 xxx.cali.npy,数据类型为 float32,形状为 (1, 28, 28),与配置文件中 input_shape 去掉 batch 维后的形状一致。脚本执行结束时会打印一份校验信息,包含首个样本的形状、数据类型和数值范围,可用来确认预处理结果是否符合预期。

注意

校准数据必须与训练时的前处理保持一致。本示例把归一化放在了 Python 侧,因此校准数据是已经归一化后的浮点数据,而不是 0~255 的原始像素值。如果这一步漏做归一化,校准得到的量化参数会明显偏离真实数据分布,进而影响量化模型精度。

校准数据准备的通用说明见 数据准备 章节。

第 3 步:量化编译

本步依据 resnet50_mnist_config.yaml 完成量化编译,执行前建议先确认配置文件内容与您的模型一致。

配置文件说明

resnet50_mnist_config.yaml 的主要参数如下:

model_parameters:
  onnx_model: './resnet50_mnist.onnx'
  march: "nash-e"
  working_dir: 'model_output'
  output_model_file_prefix: 'resnet50_mnist_28x28_gray'

input_parameters:
  input_name: ""
  input_type_rt: 'featuremap'
  input_type_train: 'featuremap'
  input_layout_train: 'NCHW'
  input_shape: '1x1x28x28'
  norm_type: 'no_preprocess'

calibration_parameters:
  cal_data_dir: './calibration_data_float32'
  calibration_type: 'default'

compiler_parameters:
  optimize_level: 'O2'

其中需要重点关注的是输入信息参数组,这也是本示例与图像输入模型配置差异最大的地方。input_type_rt 与 input_type_train 均配置为 featuremap,表示上板模型接收的数据格式和原始浮点模型的数据格式都按 float32 特征图处理,转换工具不会在模型前端插入颜色空间转换节点。norm_type 配置为 no_preprocess,表示不由转换工具执行归一化,归一化改由 preprocess.py 在 Python 侧完成。

注解
  • input_type_train 为 featuremap 时,input_type_rt 只能同样配置为 featuremap,不支持与 nv12、rgb、bgr、yuv444、gray 组合。完整的组合支持关系见 配置文件具体参数信息 章节。

  • 模型输入类型为 featuremap 时,input_layout_train 配置不生效。示例中仍保留 NCHW 取值,是为了与浮点模型的实际数据排布保持一致,便于对照阅读。

calibration_parameters 中 cal_data_dir 指向第 2 步生成的 calibration_data_float32 目录,calibration_type 取 default。如果量化后精度不满足要求,可以按 mix、kl、max 的顺序依次尝试。

其余参数的取值范围和填写方式见 配置文件具体参数信息 章节,完整模板见 配置文件模板 章节。

执行编译

确认配置无误后,执行 03_build.sh:

sh 03_build.sh

转换产物存放在配置文件 working_dir 指定的 model_output 目录下,文件名前缀由 output_model_file_prefix 指定,即 resnet50_mnist_28x28_gray。后续两步会用到其中两个产物:

  • model_output/resnet50_mnist_28x28_gray_original_float_model.onnx,用于评测浮点模型基线精度。

  • model_output/resnet50_mnist_28x28_gray_quantized_model.bc,用于评测量化模型精度。

完整的产物清单、各产物的用途以及一致性对比时应当选用哪一份,见 转换产出物 章节。

转换过程中打印的 Calibrated Cosine 和 Quantized Cosine 相似度可以作为量化后数值稳定性的参考,但与模型精度不存在绝对的直接关联。是否可以接受量化结果,仍应以第 5 步的数据集精度为准。

第 4 步:验证单张推理

执行 04_inference.sh 对单张测试图片做推理:

# 测试量化模型的单张推理结果
sh 04_inference.sh

# 测试浮点模型的单张推理结果
sh 04_inference.sh origin

不带参数时脚本使用量化模型 resnet50_mnist_28x28_gray_quantized_model.bc,参数中包含 origin 时使用浮点模型 resnet50_mnist_28x28_gray_original_float_model.onnx。两种模型的输入排布都是 NCHW,推理图片默认取 ./test_data/test_0000.png。

注解

因为 input_type_rt 配置为 featuremap,模型内部不包含预处理节点,所以 infer.py 前处理得到的归一化浮点数据可以直接送入模型,无需再做色彩空间转换或数据拆分。这一点与 nv12 输入的分类示例不同,nv12 输入需要先把数据拆分为 y 和 uv 两个输入。

推理成功时,终端会打印 top-1 的类别编号、概率和类别名称。类别名称由 postprocess.py 中的 MNIST_CLASSES 定义,取值为 digit 0 到 digit 9。如果打印结果与图片实际数字一致,说明前处理、推理和后处理链路已经打通,可以进入数据集精度评测。

第 5 步:评测数据集精度

编辑 05_evaluate.sh,把 MNIST_DATA_ROOT 修改为 IDX 测试集所在目录的实际路径:

MNIST_DATA_ROOT="<YOUR_MNIST_DATA_PATH>"
mnist_test_images="${MNIST_DATA_ROOT}/t10k-images-idx3-ubyte"
mnist_test_labels="${MNIST_DATA_ROOT}/t10k-labels-idx1-ubyte"
注意

如果您在 Docker 容器中执行本示例,请先确认测试集目录已经正确挂载进容器,并且 MNIST_DATA_ROOT 填写的是容器内路径。

修改完成后执行评测:

# 评测量化模型精度
sh 05_evaluate.sh

# 评测浮点模型精度
sh 05_evaluate.sh origin

与单张推理一致,不带参数时评测量化模型,参数中包含 origin 时评测浮点模型。脚本调用 eval.py,由 preprocess.py 中的 MNISTIdxDataLoader 直接读取 IDX 文件并逐样本归一化,无需提前把测试集转成图片。

评测过程中,终端每处理 1000 个样本会打印一次累计准确率,评测结束时打印样本总数、正确样本数和最终准确率。同时会在当前目录生成 resnet50_mnist_eval.log,其中逐条记录了每个样本的序号、预测类别和是否正确,可以用来定位具体的错分样本。

eval.py 还提供了 --parallel_num 参数用于指定并行进程数,默认取 1。如果评测耗时较长,可以在 05_evaluate.sh 的 python3 -u ./eval.py 命令中追加该参数适当提高并行度。

示例中的浮点模型在 MNIST 全部 10000 个测试样本上的准确率为 0.9968,可以作为量化前的基线。如果量化模型精度明显低于该基线,请先确认校准数据的前处理是否与训练一致,再参考 精度调优指导 章节调整量化配置。

跑通之后下一步看什么

完成上述五步后,您已经得到了可用于部署的量化模型。接下来可以按需要选择方向: