深度学习模型基础回顾:PyTorch/TensorFlow模型定义、训练与导出、模型结构可视化

说实话,很多同学一上来就急着搞ONNX转TensorRT,结果模型导出来各种报错。我见过太多次了——模型结构写错了都不知道,导出时才发现算子不支持。所以这一章,咱们老老实实把基础打牢。

你想想看,模型转换的本质是什么?说白了就是把一种框架的“计算图”翻译成另一种框架能理解的格式。如果原始模型本身就写得稀里糊涂,那后面再怎么优化也是白搭。

2.1 PyTorch模型定义与训练

我个人习惯用PyTorch做原型开发,灵活、调试方便。咱们先看一个标准的模型定义流程。

2.1.1 模型定义

import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=10):
        super(SimpleCNN, self).__init__()
        self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1)
        self.bn1 = nn.BatchNorm2d(16)
        self.relu = nn.ReLU(inplace=True)
        self.pool = nn.MaxPool2d(2)
        self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
        self.bn2 = nn.BatchNorm2d(32)
        self.fc = nn.Linear(32 * 8 * 8, num_classes)

    def forward(self, x):
        x = self.pool(self.relu(self.bn1(self.conv1(x))))
        x = self.pool(self.relu(self.bn2(self.conv2(x))))
        x = x.view(x.size(0), -1)
        x = self.fc(x)
        return x

model = SimpleCNN()
print(model)
我的经验:定义模型时尽量把 inplace=True 用对地方。它节省显存,但导出ONNX时某些算子会报错。我建议导出前先关掉 inplace,省得后面排查半天。

2.1.2 训练与检查点保存

训练过程我就不啰嗦了,重点看保存方式。这里有个坑——很多人只保存 model.state_dict(),但做转换时你还需要模型结构。

# 推荐方式:保存完整模型
torch.save(model, 'model_full.pth')

# 或者保存 state_dict + 结构信息
torch.save({
    'model_state_dict': model.state_dict(),
    'input_shape': (1, 3, 32, 32),  # 记下输入尺寸
    'num_classes': 10
}, 'model_checkpoint.pth')
注意:ONNX导出时需要知道输入输出的名称和形状。我建议训练完第一时间用 torch.onnx.export 试导出,别等到部署时才发现问题。

2.2 TensorFlow模型定义与训练

TensorFlow这边,我个人更推荐用Keras API,简洁直观。不过要注意版本差异——TF1.x和TF2.x的导出方式完全不同。

2.2.1 模型定义(Keras方式)

import tensorflow as tf

model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(16, 3, padding='same', activation='relu', input_shape=(32, 32, 3)),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D(2),
    tf.keras.layers.Conv2D(32, 3, padding='same', activation='relu'),
    tf.keras.layers.BatchNormalization(),
    tf.keras.layers.MaxPooling2D(2),
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(10, activation='softmax')
])

model.summary()

2.2.2 模型保存与导出

TF2.x推荐用SavedModel格式,它包含了模型结构和权重,后续转ONNX最方便。

# 训练后保存
model.save('tf_model_savedmodel', save_format='tf')

# 或者保存为H5
model.save('tf_model.h5')
关键点:TF转ONNX时,我遇到过很多自定义层不支持的情况。所以建议尽量用标准Keras层,少用Lambda层或自定义操作。如果非用不可,记得提前查一下ONNX算子支持列表。

2.3 模型导出为ONNX

嗯,这里才是重头戏。ONNX就像模型界的“通用语言”,但翻译过程中总会有信息丢失。

2.3.1 PyTorch导出ONNX

import torch.onnx

dummy_input = torch.randn(1, 3, 32, 32)
torch.onnx.export(
    model,
    dummy_input,
    'model.onnx',
    export_params=True,
    opset_version=11,  # 推荐11或更高
    do_constant_folding=True,
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={
        'input': {0: 'batch_size'},
        'output': {0: 'batch_size'}
    }
)
避坑指南:我曾经在 opset_version 上吃过亏。版本太低有些算子不支持,版本太高TensorRT又不认识。我一般用opset 11或13,兼容性最好。

2.3.2 TensorFlow导出ONNX

TF这边需要用 tf2onnx 工具。安装很简单:pip install tf2onnx。

import tf2onnx

# 加载SavedModel
imported = tf.saved_model.load('tf_model_savedmodel')
spec = imported.signatures['serving_default']

# 转换
onnx_model, _ = tf2onnx.convert.from_function(
    spec,
    input_signature=[tf.TensorSpec([None, 32, 32, 3], tf.float32, name='input')],
    output_path='model.onnx'
)

2.4 模型结构可视化(Netron工具)

拿到ONNX文件后,第一件事就是看结构对不对。Netron是我最常用的工具,没有之一。

2.4.1 Netron使用方式

  • 在线版:打开 netron.app,直接拖入模型文件
  • 离线版:pip install netron,然后 netron model.onnx
  • 支持格式:ONNX、PyTorch (.pth)、TensorFlow (.pb/.h5)、Keras、Caffe等

2.4.2 用Netron检查什么

检查项 说明 常见问题
输入输出节点 确认名称、形状、数据类型 输入名称不对,TensorRT绑定失败
算子类型 查看每个节点的op类型 出现不支持算子(如自定义OP)
数据流方向 跟踪张量流动路径 多余的分支或未使用的节点
常量折叠 检查是否有常量节点 常量过多影响TensorRT优化
我曾经踩过的坑:有一次模型导出后Netron显示一切正常,但TensorRT就是跑不起来。后来发现是某个Reshape节点的形状写死了,动态batch时直接崩了。所以一定要检查 dynamic_axes 是否生效。

2.5 本章知识体系

下面这张图总结了从模型定义到可视化的完整流程,你可以对照着检查自己的步骤。

模型定义到可视化全流程 模型定义 → 训练 → 导出 → 可视化 全流程 PyTorch 模型 TensorFlow 模型 训练 + 保存权重 训练 + 保存模型 torch.onnx.export tf2onnx 转换 ONNX 模型文件 Netron 可视化 关键 检查 输入 输出 算子 形状 动态 轴 常量 折叠 版本 兼容

2.6 本章小结

这一章我们走完了从模型定义到可视化的完整链路。你可能会觉得这些步骤很基础,但说实话,我见过太多人在导出ONNX这一步卡住——要么算子不支持,要么动态轴没配好,要么模型结构跟预期不一样。