模型导出与验证:从PyTorch/TensorFlow导出ONNX、使用onnxruntime验证模型正确性、常见导出错误排查

这一章,我们来聊聊模型导出。说白了,就是把你在PyTorch或TensorFlow里训好的模型,转成ONNX这种中间格式。为什么要这么干?因为ONNX是跨平台的,能跑在各种各样的推理引擎上。我个人习惯是,模型训完第一件事就是导出ONNX,然后立刻验证。别等到部署时才发现问题,那可就晚了。

从PyTorch导出ONNX

PyTorch导出ONNX,主要靠 torch.onnx.export 这个函数。嗯,这里要注意,导出时模型得设成 eval 模式,不然BatchNorm和Dropout这些层会捣乱。

import torch
import torch.onnx

# 假设你有一个训练好的模型
model = MyModel()
model.eval()

# 创建一个 dummy input,形状要和实际输入一致
dummy_input = torch.randn(1, 3, 224, 224)

# 导出
torch.onnx.export(
    model,
    dummy_input,
    "model.onnx",
    export_params=True,
    opset_version=11,
    do_constant_folding=True,
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={
        'input': {0: 'batch_size'},
        'output': {0: 'batch_size'}
    }
)

这里有个关键点:dynamic_axes。如果你希望模型支持动态batch size,就得配这个参数。我在项目中遇到过,一开始没配,结果部署时发现只能跑固定batch size,又得重新导出,挺折腾的。

重要提醒:导出时一定要用 torch.randn 生成dummy input,别用全0或全1。因为ONNX导出时会做一次前向推理,用真实分布的数据能避免一些奇怪的数值问题。

从TensorFlow导出ONNX

TensorFlow这边,得用 tf2onnx 这个工具。流程稍微绕一点,但也不复杂。

import tensorflow as tf
import tf2onnx

# 加载你的TensorFlow模型
model = tf.keras.models.load_model('my_model.h5')

# 指定输入签名
spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name="input"),)

# 导出
output_path = "model.onnx"
model_proto, _ = tf2onnx.convert.from_keras(
    model,
    input_signature=spec,
    opset=13,
    output_path=output_path
)

你想想看,TensorFlow的图结构比PyTorch复杂,所以导出时更容易出问题。我建议导出后立刻用 onnx.checker 检查一下。

使用onnxruntime验证模型正确性

模型导出来了,怎么知道它对不对?跑一下推理,跟原始框架的结果对比一下。这就是验证的核心思路。

import onnxruntime as ort
import numpy as np

# 创建推理会话
ort_session = ort.InferenceSession("model.onnx")

# 准备输入数据
input_data = np.random.randn(1, 3, 224, 224).astype(np.float32)

# 运行推理
ort_inputs = {ort_session.get_inputs()[0].name: input_data}
ort_outputs = ort_session.run(None, ort_inputs)

# 对比结果
# 假设你在PyTorch里也跑了一次
torch_output = model(torch.from_numpy(input_data)).detach().numpy()

# 计算误差
diff = np.abs(ort_outputs[0] - torch_output).max()
print(f"最大误差: {diff}")

if diff < 1e-5:
    print("验证通过!")
else:
    print("验证失败,需要排查问题")

小技巧:验证时别只用一个样本。我习惯用3-5个不同分布的输入数据做测试,包括边界值。曾经有一次,我用全0数据测试通过了,结果上线后遇到真实数据就崩了。嗯,从那以后我再也不偷懒了。

常见导出错误排查

导出过程中,你大概率会遇到各种报错。我把常见的几种列出来,方便你对照排查。

错误类型 典型报错信息 原因与解决方案
算子不支持 Unsupported operator: aten::xxx PyTorch的某些算子ONNX不支持。可以换用等价算子,或者升级opset版本。我遇到过 aten::meshgrid 不支持,换成 torch.stack 加广播就解决了。
动态shape问题 RuntimeError: Only tensors with static shape are supported 模型里有动态shape的操作,比如 torch.wheretorch.nonzero。建议用 torch.ones_liketorch.zeros_like 替代。
输入输出命名冲突 ValueError: Duplicate name in graph 多个输入或输出用了相同的名字。检查 input_namesoutput_names,确保唯一。
TensorFlow自定义层 AttributeError: 'CustomLayer' object has no attribute 'get_config' 自定义层没有实现 get_config 方法。继承 tf.keras.layers.Layer 并实现序列化接口即可。

避坑指南:我曾经在导出时遇到一个诡异的错误,报错信息指向某个不相关的算子。排查了半天,最后发现是dummy input的dtype不对——我用的是float64,但模型期望的是float32。所以,导出前务必检查数据类型是否匹配。

验证通过后的检查清单

验证通过不代表万事大吉。我建议你对照这个清单再检查一遍:

  • 数值精度:最大误差是否在可接受范围内?通常1e-5以内算正常。
  • 动态batch:用不同batch size跑一下,看结果是否一致。
  • 多输入多输出:如果你的模型有多个输入输出,每个都要单独验证。
  • 量化模型:如果是量化后的模型,误差会大一些,但趋势应该一致。
  • 性能基线:记录一下ONNX的推理耗时,跟原始框架对比,确保没有性能退化。

嗯,这一章的内容就这些。导出和验证是模型部署的第一步,也是最容易踩坑的一步。你想想看,如果模型导出来就是错的,后面所有的优化工作都白费了。所以,花点时间把这一步做扎实,绝对值得。