4. TensorFlow 模型导出 ONNX:tf2onnx 工具实战
TensorFlow 转 ONNX,说白了就是给模型换个「通用格式」。我刚开始接触时也觉得挺玄乎,后来发现核心工具就一个——tf2onnx。今天咱们就把 Keras 模型和 SavedModel 的转换流程彻底捋清楚。
核心结论:tf2onnx 是目前最成熟的 TF→ONNX 转换工具,支持 TF 1.x 和 2.x,覆盖 Keras、SavedModel、Checkpoint 等多种格式。
4.1 环境准备与工具安装
先装好依赖。我个人习惯用 pip 一次性搞定:
pip install tensorflow==2.12.0
pip install tf2onnx==1.15.1
pip install onnx==1.14.0
pip install onnxruntime==1.15.1
版本对齐建议:tf2onnx 1.15.x 对应 TF 2.12~2.13,太新的 TF 版本可能遇到 opset 兼容问题。我踩过这个坑——TF 2.15 刚出时直接装最新版,结果转换报了一堆算子不支持。
4.2 Keras 模型转换:最常用的路径
Keras 模型转换有两种主流方式:命令行和 Python API。我推荐后者,调试起来更灵活。
4.2.1 方式一:Python API 转换
import tf2onnx
import tensorflow as tf
# 构建或加载 Keras 模型
model = tf.keras.Sequential([
tf.keras.layers.Dense(128, activation='relu', input_shape=(784,)),
tf.keras.layers.Dropout(0.2),
tf.keras.layers.Dense(10, activation='softmax')
])
# 指定输入签名(关键!)
spec = (tf.TensorSpec((None, 784), tf.float32, name="input"),)
# 转换
output_path = "./model.onnx"
model_proto, _ = tf2onnx.convert.from_keras(
model,
input_signature=spec,
opset=15,
output_path=output_path
)
print(f"ONNX 模型已保存至: {output_path}")
这里有个细节:input_signature 必须显式指定。为什么?因为 Keras 模型在转换时需要知道输入张量的形状和数据类型。我曾经漏掉这一步,结果导出的 ONNX 模型输入形状全是 unknown,推理时直接报错。
4.2.2 方式二:命令行转换
适合快速验证:
python -m tf2onnx.convert \
--saved-model ./saved_model \
--output ./model.onnx \
--opset 15 \
--inputs input:0 \
--outputs output:0
注意:命令行方式需要提前知道输入输出张量的名称。可以用 saved_model_cli show --dir ./saved_model --all 查看。
4.3 SavedModel 转换:生产环境首选
SavedModel 是 TF 官方推荐的部署格式。我参与的一个工业项目,模型就是用 SavedModel 格式分发的。转换时要注意 SignatureDef 的匹配。
import tf2onnx
import tensorflow as tf
# 加载 SavedModel
model = tf.saved_model.load("./saved_model")
# 获取 serving signature
signature = model.signatures["serving_default"]
# 转换
spec = (tf.TensorSpec((None, 224, 224, 3), tf.float32, name="input"),)
output_path = "./saved_model.onnx"
model_proto, _ = tf2onnx.convert.from_function(
signature,
input_signature=spec,
output_path=output_path
)
嗯,这里要注意:from_function 接收的是 ConcreteFunction,不是原始的 tf.function。我刚开始搞混过,转换出来的模型推理结果全是 NaN。
4.4 转换流程全景图
下面这张图总结了 TF→ONNX 的完整转换链路:
4.5 常见转换问题与避坑指南
转换过程中,我遇到过不少「坑」。下面这张表总结了最常见的三类问题:
| 问题类型 | 典型表现 | 解决方案 |
|---|---|---|
| 算子不支持 | 转换时报错:Unsupported Op: ... |
升级 opset 版本;或使用 custom_ops 注册自定义算子 |
| 输入形状不匹配 | ONNX 模型输入形状为 unknown |
显式指定 input_signature,包含 batch 维度 |
| 动态轴丢失 | 推理时 batch size 固定为 1 | 转换时设置 dynamic_axes 参数 |
我曾经踩过的坑:有一次转换一个包含 tf.while_loop 的模型,tf2onnx 直接崩溃。后来发现是循环体内的算子没有被正确追踪。解决办法是把循环体改写成 tf.map_fn 或手动展开。
4.6 精度验证:转换后的第一道防线
模型转换完,第一件事就是验证精度。我通常用这个脚本做快速比对:
import onnxruntime as ort
import numpy as np
import tensorflow as tf
# 生成测试数据
test_input = np.random.randn(1, 784).astype(np.float32)
# TF 推理
tf_output = model.predict(test_input)
# ONNX 推理
ort_session = ort.InferenceSession("./model.onnx")
ort_input = {ort_session.get_inputs()[0].name: test_input}
ort_output = ort_session.run(None, ort_input)[0]
# 计算误差
diff = np.abs(tf_output - ort_output).max()
print(f"最大误差: {diff:.6f}")
if diff < 1e-4:
print("✅ 精度验证通过")
else:
print("⚠️ 精度偏差较大,需要排查")
经验阈值:对于分类模型,最大误差 < 1e-4 通常可接受;对于回归模型,建议 < 1e-5。如果误差在 1e-2 级别,基本可以断定转换有问题。
4.7 动态轴配置:让模型更灵活
很多场景下,我们需要模型支持动态 batch size 或动态输入尺寸。tf2onnx 通过 dynamic_axes 参数实现:
import tf2onnx
import tensorflow as tf
model = tf.keras.Sequential([
tf.keras.layers.Conv2D(32, 3, input_shape=(None, None, 3)),
tf.keras.layers.GlobalAvgPool2D(),
tf.keras.layers.Dense(10)
])
spec = (tf.TensorSpec((None, None, None, 3), tf.float32, name="input"),)
# 配置动态轴
model_proto, _ = tf2onnx.convert.from_keras(
model,
input_signature=spec,
output_path="./dynamic_model.onnx",
custom_ops=None,
extra_args={
"dynamic_axes": {
"input": {0: "batch_size", 1: "height", 2: "width"},
"output": {0: "batch_size"}
}
}
)
配置动态轴后,ONNX 模型在推理时可以接受不同尺寸的输入。不过要注意:动态轴会增加推理框架的调度开销。如果生产环境输入尺寸固定,建议用静态形状以获得最佳性能。
4.8 转换后的模型检查
转换完成后,我习惯用 ONNX 自带的工具做一次完整性检查:
import onnx
# 加载模型
model = onnx.load("./model.onnx")
# 检查模型结构
onnx.checker.check_model(model)
print("✅ 模型结构检查通过")
# 打印输入输出信息
print("输入:", model.graph.input)
print("输出:", model.graph.output)
print("算子数:", len(model.graph.node))
这一步能发现很多隐藏问题,比如输入输出名称不匹配、形状信息丢失等。我建议把它加到 CI 流程里,每次模型更新后自动跑一遍。
总结一下:TF→ONNX 转换的核心就是三件事——选对工具版本、配好输入签名、验证精度。把这三点做好,基本不会出大问题。