模型格式基础:ONNX、TensorFlow Lite、Caffe Model、Paddle Lite 格式介绍与转换
做边缘端模型移植,第一关就是跟各种模型格式打交道。说实话,我刚入行那会儿也被这些格式搞晕过——明明都是神经网络,怎么存法还不一样?后来踩的坑多了,才慢慢摸清楚每种格式的脾气。
这一章,我就把四种主流格式掰开揉碎讲清楚。你不需要背下所有细节,但得知道它们各自擅长什么、短板在哪、怎么互相倒腾。
为什么需要这么多模型格式?
说白了,不同框架的"母语"不一样。PyTorch 习惯用 .pth,TensorFlow 偏爱 .pb,Caffe 守着 .caffemodel。但边缘端设备资源有限,不能直接跑训练框架,所以需要一种"轻量化通用语言"。
我个人习惯把模型格式分成两类:
- 训练格式:保留完整计算图、优化器状态、训练参数。体积大,适合调参。
- 推理格式:只保留前向计算图,去掉反向传播部分。体积小,适合部署。
边缘端要的,就是后者。
核心原则:训练用框架原生格式,部署用中间表示格式(IR)。ONNX 就是目前最通用的 IR。
ONNX:模型界的"普通话"
ONNX(Open Neural Network Exchange)是微软和Meta牵头搞的开放标准。它的目标很明确——让模型能在不同框架间自由流动。
我在项目中遇到过最典型的场景:算法团队用 PyTorch 训练,部署团队要用 TensorRT 加速。中间全靠 ONNX 搭桥。
ONNX 的核心结构
一个 ONNX 模型包含三部分:
- 计算图(Graph):节点是算子(Conv、Relu、Add...),边是张量流动
- 元数据(Metadata):模型版本、作者、opset 版本号
- 权重(Initializer):所有可学习参数,以张量形式存储
嗯,这里要注意:ONNX 的算子集是有限的。如果你用了某些框架特有的算子(比如 PyTorch 的 torch.einsum),导出时就会报错。我刚开始做 ONNX 导出时,至少有一半时间花在替换算子上。
我的经验:导出 ONNX 前,先用 torch.onnx.export 的 dynamic_axes 参数设置动态 batch。否则换一个 batch size 就得重新导出,很麻烦。
PyTorch 转 ONNX 示例
import torch
import torchvision.models as models
# 加载预训练模型
model = models.resnet18(pretrained=True)
model.eval()
# 构造 dummy 输入
dummy_input = torch.randn(1, 3, 224, 224)
# 导出 ONNX
torch.onnx.export(
model,
dummy_input,
"resnet18.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch_size"},
"output": {0: "batch_size"}},
opset_version=11
)
print("ONNX 导出完成")
TensorFlow Lite:Google 的移动端利器
TFLite 是 TensorFlow 针对移动端和嵌入式设备推出的推理格式。它的核心优势在于:
- 体积小(通过量化压缩)
- 延迟低(针对 ARM CPU 和 GPU 优化)
- 支持硬件加速(NNAPI、GPU Delegates)
我记得第一次在树莓派上跑 TFLite 模型时,最惊讶的是它的加载速度——比原版 TensorFlow 快了将近 5 倍。
TFLite 的量化方式
| 量化类型 | 权重精度 | 激活精度 | 典型精度损失 |
|---|---|---|---|
| 动态范围量化 | INT8 | FP32 | < 1% |
| 全整数量化 | INT8 | INT8 | 1-3% |
| FP16 量化 | FP16 | FP32 | < 0.5% |
避坑指南:我曾经在某个分类模型上用了全整数量化,结果精度掉了 8%。后来发现是某个激活层的分布太宽,量化时信息丢失严重。解决办法是在量化校准阶段提供足够多的代表性数据。
TensorFlow 转 TFLite 示例
import tensorflow as tf
# 加载 SavedModel 或 Keras 模型
model = tf.saved_model.load("mobilenet_v2")
# 转换器配置
converter = tf.lite.TFLiteConverter.from_saved_model("mobilenet_v2")
# 启用动态范围量化
converter.optimizations = [tf.lite.Optimize.DEFAULT]
# 转换
tflite_model = converter.convert()
# 保存
with open("mobilenet_v2.tflite", "wb") as f:
f.write(tflite_model)
Caffe Model:老牌框架的遗产
Caffe 虽然现在用得少了,但很多工业场景的旧模型还是 .caffemodel 格式。你想想看,一些安防、工业检测项目跑了五六年,模型都是 Caffe 时代的产物。
Caffe 模型由两部分组成:
- .prototxt:网络结构定义(文本格式)
- .caffemodel:权重参数(二进制格式)
说实话,Caffe 的 prototxt 可读性很好,比 ONNX 的 protobuf 直观多了。但它的算子扩展性差,想加一个新层得写 C++ 代码编译,很折腾。
Caffe 转 ONNX 的常见路径
我建议用 caffe2onnx 工具,或者通过 MMdnn 中转。但要注意:
- Caffe 的某些层(如 LRN、Deconvolution)在 ONNX 中没有直接对应
- 需要手动处理输入数据的通道顺序(Caffe 是 CHW,ONNX 是 NCHW)
我的习惯:遇到 Caffe 旧模型,先用 Netron 可视化看看结构。如果层数不多,我甚至手动重写一个 ONNX 图——虽然笨,但最可控。
Paddle Lite:百度飞桨的轻量方案
Paddle Lite 是百度针对 ARM、NPU、FPGA 等边缘设备推出的推理引擎。它的模型格式是 .nb(Naive Buffer),特点是:
- 加载速度极快(内存映射加载)
- 支持 200+ 算子
- 对华为麒麟 NPU 有原生支持
我在一个智慧零售项目中用过 Paddle Lite,当时需要在海思 3559A 上跑目标检测模型。从 Paddle 训练到 .nb 格式,整个流程很顺畅,唯一要注意的是算子兼容性检查。
Paddle 转 Paddle Lite 示例
# 安装工具
pip install paddlelite
# 命令行转换
paddle_lite_opt \
--model_dir=./mobilenet_v3 \
--valid_targets=arm \
--optimize_out=mobilenet_v3_opt \
--optimize_out_type=naive_buffer
# 输出文件:mobilenet_v3_opt.nb
四种格式对比一览
| 特性 | ONNX | TFLite | Caffe Model | Paddle Lite |
|---|---|---|---|---|
| 主要框架 | PyTorch/TF | TensorFlow | Caffe | PaddlePaddle |
| 量化支持 | 需第三方工具 | 原生支持 | 无 | 原生支持 |
| 硬件加速 | 依赖转换 | NNAPI/GPU | 有限 | NPU/FPGA |
| 社区活跃度 | 极高 | 高 | 低 | 中 |
| 典型场景 | 模型中转 | 手机/树莓派 | 旧系统维护 | 国产芯片 |
格式转换的核心流程
不管从哪种格式转到哪种格式,核心步骤都一样:
- 导出中间表示:尽量用 ONNX 作为桥梁
- 算子映射检查:确保源框架的算子能在目标框架中找到对应
- 精度验证:转换前后跑同一批数据,对比输出差异
- 量化校准:如果需要量化,准备校准数据集
我曾经踩过的坑:有一次从 PyTorch 转 ONNX 再转 TFLite,模型跑出来全是 NaN。查了两天才发现是 BatchNorm 层的训练/推理模式没切换。导出 ONNX 前一定要调用 model.eval(),这个细节我至少忘了三次。
知识结构图:模型格式转换全景
这张图展示了我个人最推荐的转换路径:所有训练框架统一先转 ONNX,再从 ONNX 分发到各目标格式。这样做的好处是——你只需要维护一套算子映射规则,而不是 N 对 N 的映射关系。
一句话总结:ONNX 是桥梁,TFLite 是移动端首选,Caffe 是历史包袱,Paddle Lite 是国产芯片的好搭档。选哪个,取决于你的目标硬件和团队技术栈。