模型格式基础: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
社区活跃度 极高 高 低 中
典型场景 模型中转 手机/树莓派 旧系统维护 国产芯片

格式转换的核心流程

不管从哪种格式转到哪种格式,核心步骤都一样:

  1. 导出中间表示:尽量用 ONNX 作为桥梁
  2. 算子映射检查:确保源框架的算子能在目标框架中找到对应
  3. 精度验证:转换前后跑同一批数据,对比输出差异
  4. 量化校准:如果需要量化,准备校准数据集

我曾经踩过的坑:有一次从 PyTorch 转 ONNX 再转 TFLite,模型跑出来全是 NaN。查了两天才发现是 BatchNorm 层的训练/推理模式没切换。导出 ONNX 前一定要调用 model.eval(),这个细节我至少忘了三次。

知识结构图:模型格式转换全景

模型格式转换全景图 PyTorch (.pth) TensorFlow (.pb) Caffe (.caffemodel) ONNX 中间表示 算子映射 + 精度验证 TensorFlow Lite Paddle Lite (.nb) TensorRT / OpenVINO 量化校准(INT8/FP16) 边缘设备部署 转换后务必做精度对比 差异 > 1% 需排查算子映射

这张图展示了我个人最推荐的转换路径:所有训练框架统一先转 ONNX,再从 ONNX 分发到各目标格式。这样做的好处是——你只需要维护一套算子映射规则,而不是 N 对 N 的映射关系。

一句话总结:ONNX 是桥梁,TFLite 是移动端首选,Caffe 是历史包袱,Paddle Lite 是国产芯片的好搭档。选哪个,取决于你的目标硬件和团队技术栈。