地瓜机器人 · 2022年04月07日

模型精度验证及调优建议

当您在板端验证.bin或在python端评测quantized.onnx发现精度不及预期时(精度损失超过4%),可参照本文第二章所述步骤排查问题。若精度损失较小,则可参考本文第三章尝试精度调优。

在开始定位模型精度问题之前,我们建议您可以先浏览一下模型转换的内部过程解读,这将有助于您理解并排查数据和yaml文件准备过程中的问题。

1.内部过程详解

模型转换完成浮点模型到地平线混合异构模型的转换。为了使得这个异构模型能快速高效地在嵌入式端运行,模型转换重点在解决 输入数据处理 和 模型优化编译 两个问题。

1.1 输入数据处理

输入数据处理方面我们为模型插入了预处理节点,帮助实现硬件通路数据和模型输入数据的转换对齐。因为地平线的边缘AI计算平台会为某些特定类型的输入通路提供硬件级的支撑方案, 但是这些方案的输出不一定符合模型输入的要求。 例如视频通路方面就有视频处理子系统,为采集提供图像裁剪、缩放和其他图像质量优化功能,这些子系统的输出往往是yuv420格式图像, 而我们的算法模型往往是基于bgr/rgb等一般常用图像格式训练得到的。为减少客户板端部署时的工作量,我们将几种常见的图像格式转换以及常用的图像标准化操作固化进了模型当中,其表现为模型input节点之后插入了预处理节点HzPreprocess(您可以使用开源工具 Netron 观察转换过程中的中间产物)。
转换过程中,工具会根据yaml文件中 input_type_rt 和 input_type_train 指定的数据格式自动向HzPreprocess节点中添加数据格式转换的操作。根据实际生产经验, 并不是任意type组合都是需要的,为避免误用,我们只开放了一些固定的type组合如下表所示。

1.png

表格中第一行是 input_type_rt 中支持的类型,第一列是 input_type_train 支持的类型, 其中的 Y/N 表示是否支持相应的 input_type_rt 到 input_type_train 的转换。 在.bin模型部署阶段,您只需要关注input_type_rt的数据格式。 以下是对 input_type_rt每种格式的说明:

(1) rgb、bgr和gray都是比较常见的图像数据,注意每个数值都采用UINT8表示。

(2) yuv444是一种常见的图像格式,注意每个数值都采用UINT8表示。

(3) nv12是常见的yuv420图像数据,每个数值都采用UINT8表示。

(4) nv12有个比较特别的情况是 input_space_and_range 设置 bt601_video (配置参数介绍可参考《horizon_ai_toolchain_user_guide》3.4. 转换模型 章节),较于常规nv12情况,它的数值范围由[0,255]变成了[16,235], 每个数值仍然采用UINT8表示。

(5) featuremap适用于以上列举格式不满足您需求的情况,此type只要求您的数据是四维的,每个数值采用float32表示。 例如雷达和语音等模型处理就常用这个格式。
图像数据标准化操作则是根据yaml文件中的norm_type、mean_value、scale_value参数,判断是否向HzPreprocess节点中添加mean/scale操作。

1.2 模型优化编译
模型优化编译方面则完成了模型解析、模型优化、模型校准与量化、模型编译等几个重要过程。其内部工作过程及输入数据准备示例如下图所示。
2.png

*最右边一列为各阶段图像输入类模型预处理示例,主要差异在于normalize操作以及图像格式的转换。若为featuremap输入,则预处理不存在上述差异。

模型解析阶段 对于Caffe浮点模型会完成到ONNX浮点模型的转换。 在原始浮点模型上会根据转换配置中的配置参数决定是否加入HzPreprocess节点,此阶段产出original_float_model.onnx。 这个ONNX模型计算精度仍然是float32,和原始浮点模型输出结果一致。
理想状态下,这个HzPreprocess节点应该完成 input_type_rt 到 input_type_train 的完整转换, 实际情况是整个type转换过程会配合地平线AI芯片硬件完成,ONNX模型里面并没有包含硬件转换的部分。 因此ONNX的真实输入类型会使用一种中间类型,这种中间类型就是硬件对 input_type_rt 的处理结果类型, 数据layout(NCHW/NHWC)会保持和原始浮点模型的输入layout一致。 每种 input_type_rt 都有特定的对应中间类型,如下表:

3.png
表格中第一行是 input_type_rt 指定的数据类型,第二行是特定 input_type_rt 对应的中间类型, 这个中间类型就是original_float_model.onnx的输入类型。每个类型解释如下:

(1) yuv444_128/RGB_128/BGR_128/GRAY_128为对应input_type_rt减去128的结果。

(2) featuremap 是一个四维张量数据,每个数值采用float32表示。

模型优化阶段 实现模型的一些适用于地平线平台的算子优化策略,例如BN融合到Conv等。 此阶段的产出是optimized_float_model.onnx,这个ONNX模型的计算精度仍然是float32,经过优化后不会影响模型的计算结果。 模型的输入数据要求还是与前面的original_float_model一致。

模型校准阶段 会使用您提供的校准数据来计算必要的量化阈值参数,这些参数会直接输入到量化阶段,不会产生新的模型状态。

模型量化阶段 使用校准得到的参数完成模型量化,此阶段的产出是quantized_model.onnx。 这个模型的输入计算精度已经是int8,使用这个模型可以评估到模型量化带来的精度损失情况。 这个模型要求输入的基本数据格式仍然与 original_float_model 一样,不过layout和数值表示已经发生了变化, 整体较于 original_float_model 输入的变化情况描述如下:
(1) 数据layout均使用NHWC。

(2) 当 input_type_rt 的取值为非 featuremap 时,则输入的数据类型均使用int8, 反之, 当 input_type_rt 取值为 featuremap 时,则输入的数据类型为float32。

模型编译阶段 会使用地平线模型编译器,将量化模型转换为地平线平台支持的计算指令和数据, 这个阶段的产出是*.bin模型,这个bin模型是后续将在地平线边缘嵌入式平台运行的模型,也就是模型转换的最终产出结果。

2 精度问题定位建议流程
精度问题定位流程主要包括如下三个部分:
4.png

1)验证Caffe/Onnx的有效性,确保其单张推理结果与原始浮点模型保持一致;
2)通过对比original_float_model.onnx与原始浮点模型的单张推理结果,确保PC端推理代码的正确性;
3)通过比对quantized_model.onnx与.bin的单张推理结果,确保板端代码与PC端代码的一致性,以及模型集成(将quantized_model.onnx编译为.bin)的过程没有引入误差。

2.1 验证原始Caffe/Onnx模型有效性
这一步为了排查拿错模型,或是导出onnx有误等误操作。onnx模型的正确性验证,可参考如下代码:

from horizon_nn import horizon_onnx
import horizon_nn.horizon_onnxruntime as rt
import numpy as np
import cv2

def preprocess(input_name):

BGR->RGB、Resize、CenterCrop···

HWC->CHW

normalization

return norm_data

def main():

加载模型文件

onnx_model = horizon_onnx.load(MODEL_PATH)

创建推理Session

sess = rt.InferenceSession(onnx_model.SerializeToString())

获取输入&输出节点名称

input_names = [input.name for input in sess.get_inputs()]
output_names = [output.name for output in sess.get_outputs()]

准备模型输入数据

feed_dict = dict()
for input_name in input_names:
feed_dict[input_name] = preprocess(input_name)

开始模型推理,推理的返回值是一个list,依次与output_names指定名称一一对应

result = sess.run(output_names, feed_dict)

后处理

postprocess(result)

if name == '__main__':
main()

2.2 验证PC端推理代码的正确性
转换完成后,将在model_output文件夹下生成四个模型,其中original_float_model.onnx以及optimized_float_model.onnx的精度是与原始浮点模型完全一致的。但是由于您通过配置yaml文件中的 input_type_rt 以及norm_type等参数,将图像格式转换以及normalize这两项常用的预处理操作固化进了模型中,因此预处理代码会与训练时有所差异,具体差异及注意事项可参考前文1.2节。若发现推理结果与浮点模型不一致,则需再次确认预处理代码的正确性。常见错误如下:

(1)已在yaml文件中配置 norm_type(scale/mean),前处理仍做了重复的normalize操作

(2)读图方式与浮点训练时不一致。skimage、OpenCV、PIL读图差异如下表所示
5.png

确保PC端代码的正确性之后,建议您可以测试一下*quantized_model.onnx的精度或单张推理结果,确认量化后精度满足您的预期,再至板端完成应用开发。若精度不满足预期,则可参照第三章内容尝试精度调优。

2.3 验证.bin模型的正确性
通常来说,将quantized_model.onnx编译生成.bin的过程不会引入误差,但事有万一,我们提供了 hb_model_verifier 工具帮助您验证定点模型和runtime模型的一致性。具体使用方式因OE版本不同而有所差异,您可以通过 hb_model_verifier --help 查看帮助信息,或查阅《hb_mapper_tools_guide》文档了解该工具的使用方式。验证通过,终端将打印 Onnx and Arm result Strict check PASSED 提示信息。若验证失败,请将模型及OE版本号提供给地平线技术支持人员分析。
但是目前该工具只支持单输入模型,若为多输入模型则可使用板端 hrt_model_exec infer工具获取模型原始输出。为保证输入数据的一致性,建议您将python端预处理好的数据通过 np.tofile() 函数保存为二进制文件,并通过 hrt_model_exec infer 工具的 --input_file 参数指定输入数据(多个输入文件请以“,”隔开),具体使用方式可通过在板端执行 hrt_model_exec,查看帮助信息。若使用该工具得到的输出结果与python端不一致,请将模型及OE版本号提供给地平线技术支持人员分析。
*目前 hrt_model_exec infer 工具不支持自动完成featuremap输入的 padding 操作(该操作与硬件对齐规则相关,具体介绍请参考后文2.4节),您需要在PC端预处理时完成该操作,参考代码如下:
pad_image = np.zeros((target_h, target_w, 3), dtype=np.int)
pad_image[:image_h, :image_w, :] = image

  • target_h, target_w可通过hrt_model_exec model_info工具查看输入节点的aligned shape属性获取

2.4 验证板端推理代码的正确性
确认前面所有环节都正常之后,最后我们就只需要排查板端推理代码是否有误了。常见问题有如下几项:

(1)PC端与板端计算环境的差异(例如opencv读图差异、浮点计算精度不同等);

(2)输入数据未对齐至转换配置的input_type_rt和input_layout_rt;

(3)输入数据不满足对齐规则,且未修改InputTensor的aligned_shape属性(仅针对图像输入)。(BPU对齐规则可参考下图解析)
6.png

其中,featuremap输入时较为特殊,由于预测库不会对featuremap数据做padding操作,因此当您的模型输入为featuremap时,需在预处理时完成数据对齐,参考代码如下:

if (input_w == out_w) {
memcpy(out, input, static_cast<size_t>(input_h input_w) data_size);
} else {
for (int i = 0; i < input_h; i++) {
memcpy(out, input, static_cast<size_t>(input_w) * data_size);
input += input_w;
out += out_w;
}
}

3 精度调优

3.1 后量化调优
对于后量化的精度误差,我们一般会通过以下 3 种方式进行优化,且均需要在 yaml 文件配置后重新转换模型:

1.调整校准方式

(1)calibration_type 优先尝试 default,除此之外还可以尝试 kl/max;

(2)将 calibration_type 配置为 max,并配置 max_percentile 为不同的分位数,我们推荐您优先尝试 0.99999、0.99995、0.9999、0.9995、0.999;

(3)尝试启用 per_channel,可与任意校准方式配合使用。

2.调准校准数据集

(1)可以尝试适当增加或减少数据量;

(2)尝试换一批校准数据。

3.将部分尾部算子回退到 CPU 高精度计算

参考依据为转换日志中模型每一层输出的余弦相似度,若您观察到有某一层余弦相似度异常,可尝试在yaml文件中通过 run_on_cpu 参数配置,将该层指定到cpu进行高精度计算。一般我们仅会尝试将模型输出层 1~2 个算子回退至 CPU,太多的CPU算子会较大程度影响模型最终性能。

3.2 Pytorch QAT训练
如果您的模型经过以上调优手段还是无法解决量化精度问题,那么该模型可能确实是 后量化(post training quantization,PTQ)方案中的 corner case,只能尝试 量化感知训练(quantization aware training,QAT)。
目前很多开源训练框架均已支持 QAT 训练能力,例如 Pytorch 的 eager-mode 和 fx-graph方案,tf-lite的量化方案等等。相比于后量化,QAT 训练在浮点模型训练收敛后进行 finetune,其精度损失由算法同学自行训练优化,会更加可控,且开源社区中也有非常多的帮助资料。但 QAT 方案因为训练成本和上手难度相对更高,所以我们更建议您在后量化实在无法解决精度问题时再选择此方案。
地平线目前仅支持编译 Pytorch 框架的 QAT 模型,具体示例请参考用户手册《horizon_ai_toolchain_user_guide》3.6.3.4 QAT模型量化编译。

本文转载自地平线开发者社区:https://developer.horizon.ai
原作者:颜值即正义
原文链接:https://developer.horizon.ai/...
推荐阅读
关注数
1254
内容数
69
我是地瓜机器人,为加速智能生长而来
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息