模型结构 #

ProtoBuf 格式 #

ONNX 模型使用 Protocol Buffers(ProtoBuf)格式存储,这是一种高效的二进制序列化格式。

ProtoBuf 定义 #

text
┌─────────────────────────────────────────────────────────────┐
│                    ONNX ProtoBuf 定义                        │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  message ModelProto {                                       │
│    optional int64 ir_version = 1;                          │
│    repeated OperatorSetIdProto opset_import = 8;           │
│    optional string producer_name = 2;                      │
│    optional string producer_version = 3;                   │
│    optional string domain = 4;                             │
│    optional ModelProto model_version = 5;                  │
│    optional string doc_string = 6;                         │
│    optional GraphProto graph = 7;                          │
│    repeated StringStringEntryProto metadata_props = 14;    │
│  }                                                          │
│                                                             │
│  message GraphProto {                                       │
│    repeated NodeProto node = 1;                            │
│    repeated ValueInfoProto input = 2;                      │
│    repeated ValueInfoProto output = 3;                     │
│    repeated ValueInfoProto value_info = 4;                 │
│    repeated TensorProto initializer = 5;                   │
│    optional string name = 6;                               │
│    optional string doc_string = 7;                         │
│  }                                                          │
│                                                             │
│  message NodeProto {                                        │
│    repeated string input = 1;                              │
│    repeated string output = 2;                             │
│    optional string name = 3;                               │
│    optional string op_type = 4;                            │
│    optional string domain = 6;                             │
│    repeated AttributeProto attribute = 5;                  │
│    optional string doc_string = 7;                         │
│  }                                                          │
│                                                             │
└─────────────────────────────────────────────────────────────┘

模型文件结构 #

text
model.onnx (二进制文件)
├── Header
│   └── ProtoBuf 消息头
├── ModelProto
│   ├── ir_version
│   ├── opset_import
│   ├── producer_info
│   └── GraphProto
│       ├── inputs
│       ├── outputs
│       ├── initializers
│       └── nodes
└── Footer
    └── 校验信息(可选)

模型元数据 #

IR 版本 #

IR(Intermediate Representation)版本定义了模型格式的版本。

python
import onnx

model = onnx.load("model.onnx")

print(f"IR 版本: {model.ir_version}")

if model.ir_version >= 7:
    print("支持动态形状")
if model.ir_version >= 8:
    print("支持异常处理")

IR 版本历史 #

IR 版本 ONNX 版本 重要特性
4 1.1 基础功能
5 1.3 算子集版本
6 1.5 动态形状基础
7 1.6 完整动态形状
8 1.10 异常处理

生产者信息 #

python
model = onnx.load("model.onnx")

print(f"生产者名称: {model.producer_name}")
print(f"生产者版本: {model.producer_version}")
print(f"模型版本: {model.model_version}")
print(f"域: {model.domain}")

for prop in model.metadata_props:
    print(f"  {prop.key}: {prop.value}")

修改元数据 #

python
import onnx

model = onnx.load("model.onnx")

model.producer_name = "my_tool"
model.producer_version = "1.0.0"
model.model_version = 1

model.metadata_props.append(
    onnx.StringStringEntryProto(key="author", value="developer")
)
model.metadata_props.append(
    onnx.StringStringEntryProto(key="description", value="My ONNX model")
)

onnx.save(model, "model_updated.onnx")

计算图详解 #

图的基本属性 #

python
import onnx

model = onnx.load("model.onnx")
graph = model.graph

print(f"图名称: {graph.name}")
print(f"输入数量: {len(graph.input)}")
print(f"输出数量: {len(graph.output)}")
print(f"节点数量: {len(graph.node)}")
print(f"初始化器数量: {len(graph.initializer)}")
print(f"值信息数量: {len(graph.value_info)}")

输入输出定义 #

python
import onnx
from onnx import helper, TensorProto

graph = model.graph

input_tensor = helper.make_tensor_value_info(
    "input",
    TensorProto.FLOAT,
    [1, 3, 224, 224]
)

output_tensor = helper.make_tensor_value_info(
    "output",
    TensorProto.FLOAT,
    [1, 1000]
)

graph.input.append(input_tensor)
graph.output.append(output_tensor)

动态形状定义 #

python
from onnx import helper, TensorProto

input_tensor = helper.make_tensor_value_info(
    "input",
    TensorProto.FLOAT,
    None
)

input_tensor.type.tensor_type.shape.dim.add().dim_param = "batch_size"
input_tensor.type.tensor_type.shape.dim.add().dim_value = 3
input_tensor.type.tensor_type.shape.dim.add().dim_value = 224
input_tensor.type.tensor_type.shape.dim.add().dim_value = 224

节点详解 #

节点属性 #

python
import onnx
from onnx import AttributeProto

model = onnx.load("model.onnx")

for node in model.graph.node:
    print(f"节点名称: {node.name}")
    print(f"算子类型: {node.op_type}")
    print(f"域: {node.domain}")
    print(f"输入: {list(node.input)}")
    print(f"输出: {list(node.output)}")
    
    print("属性:")
    for attr in node.attribute:
        print(f"  {attr.name}: ", end="")
        
        if attr.type == AttributeProto.INT:
            print(attr.i)
        elif attr.type == AttributeProto.INTS:
            print(list(attr.ints))
        elif attr.type == AttributeProto.FLOAT:
            print(attr.f)
        elif attr.type == AttributeProto.FLOATS:
            print(list(attr.floats))
        elif attr.type == AttributeProto.STRING:
            print(attr.s.decode())
        elif attr.type == AttributeProto.STRINGS:
            print([s.decode() for s in attr.strings])
        elif attr.type == AttributeProto.TENSOR:
            print(f"Tensor shape: {list(attr.t.dims)}")

创建节点 #

python
from onnx import helper, TensorProto

conv_node = helper.make_node(
    "Conv",
    inputs=["input", "weight", "bias"],
    outputs=["output"],
    name="conv1",
    kernel_shape=[3, 3],
    strides=[1, 1],
    pads=[1, 1, 1, 1],
    dilations=[1, 1],
    group=1
)

relu_node = helper.make_node(
    "Relu",
    inputs=["conv_output"],
    outputs=["relu_output"],
    name="relu1"
)

add_node = helper.make_node(
    "Add",
    inputs=["a", "b"],
    outputs=["c"],
    name="add1"
)

节点属性类型 #

text
┌─────────────────────────────────────────────────────────────┐
│                    属性类型对照表                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  AttributeProto.INT         - 整数                         │
│  AttributeProto.INTS        - 整数列表                     │
│  AttributeProto.FLOAT       - 浮点数                       │
│  AttributeProto.FLOATS      - 浮点数列表                   │
│  AttributeProto.STRING      - 字符串                       │
│  AttributeProto.STRINGS     - 字符串列表                   │
│  AttributeProto.TENSOR      - 张量                         │
│  AttributeProto.GRAPH       - 子图                         │
│  AttributeProto.GRAPHS      - 子图列表                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

初始化器详解 #

访问权重 #

python
import onnx
from onnx import numpy_helper
import numpy as np

model = onnx.load("model.onnx")

weights_dict = {}
for initializer in model.graph.initializer:
    weights_dict[initializer.name] = numpy_helper.to_array(initializer)

for name, weights in weights_dict.items():
    print(f"{name}: shape={weights.shape}, dtype={weights.dtype}")
    print(f"  min={weights.min():.4f}, max={weights.max():.4f}")
    print(f"  mean={weights.mean():.4f}, std={weights.std():.4f}")

创建初始化器 #

python
import numpy as np
from onnx import numpy_helper, helper

weights = np.random.randn(64, 3, 7, 7).astype(np.float32)
bias = np.zeros(64, dtype=np.float32)

weight_initializer = numpy_helper.from_array(weights, name="conv1.weight")
bias_initializer = numpy_helper.from_array(bias, name="conv1.bias")

model.graph.initializer.extend([weight_initializer, bias_initializer])

修改权重 #

python
import onnx
from onnx import numpy_helper
import numpy as np

model = onnx.load("model.onnx")

for initializer in model.graph.initializer:
    if initializer.name == "conv1.weight":
        weights = numpy_helper.to_array(initializer)
        
        weights = weights * 0.5
        
        new_initializer = numpy_helper.from_array(weights, name=initializer.name)
        
        for i, init in enumerate(model.graph.initializer):
            if init.name == "conv1.weight":
                model.graph.initializer[i].CopyFrom(new_initializer)
                break

onnx.save(model, "model_modified.onnx")

从零创建模型 #

创建简单模型 #

python
import onnx
from onnx import helper, TensorProto
import numpy as np

X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [1, 3, 224, 224])
Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [1, 64, 112, 112])

conv_node = helper.make_node(
    "Conv",
    inputs=["X", "conv_weight", "conv_bias"],
    outputs=["conv_out"],
    kernel_shape=[7, 7],
    strides=[2, 2],
    pads=[3, 3, 3, 3]
)

relu_node = helper.make_node(
    "Relu",
    inputs=["conv_out"],
    outputs=["Y"]
)

conv_weight = np.random.randn(64, 3, 7, 7).astype(np.float32)
conv_bias = np.zeros(64, dtype=np.float32)

weight_init = numpy_helper.from_array(conv_weight, name="conv_weight")
bias_init = numpy_helper.from_array(conv_bias, name="conv_bias")

graph = helper.make_graph(
    [conv_node, relu_node],
    "simple_conv",
    [X],
    [Y],
    [weight_init, bias_init]
)

model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 17)])

model.producer_name = "my_tool"
model.producer_version = "1.0.0"

onnx.checker.check_model(model)

onnx.save(model, "simple_model.onnx")

创建带动态形状的模型 #

python
import onnx
from onnx import helper, TensorProto, shape_inference
import numpy as np

batch_size = helper.make_tensor_value_info(
    "batch_size",
    TensorProto.INT64,
    [1]
)

X = helper.make_tensor_value_info("X", TensorProto.FLOAT, None)
X.type.tensor_type.shape.dim.add().dim_param = "N"
X.type.tensor_type.shape.dim.add().dim_value = 3
X.type.tensor_type.shape.dim.add().dim_value = 224
X.type.tensor_type.shape.dim.add().dim_value = 224

Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, None)
Y.type.tensor_type.shape.dim.add().dim_param = "N"
Y.type.tensor_type.shape.dim.add().dim_value = 64
Y.type.tensor_type.shape.dim.add().dim_value = 112
Y.type.tensor_type.shape.dim.add().dim_value = 112

conv_node = helper.make_node(
    "Conv",
    inputs=["X", "weight"],
    outputs=["conv_out"],
    kernel_shape=[7, 7],
    strides=[2, 2],
    pads=[3, 3, 3, 3]
)

relu_node = helper.make_node(
    "Relu",
    inputs=["conv_out"],
    outputs=["Y"]
)

weight = np.random.randn(64, 3, 7, 7).astype(np.float32)
weight_init = numpy_helper.from_array(weight, name="weight")

graph = helper.make_graph(
    [conv_node, relu_node],
    "dynamic_model",
    [X],
    [Y],
    [weight_init]
)

model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 17)])

model = shape_inference.infer_shapes(model)

onnx.save(model, "dynamic_model.onnx")

模型操作 #

提取子图 #

python
import onnx
from onnx import helper

model = onnx.load("model.onnx")
graph = model.graph

new_nodes = []
for node in graph.node:
    if node.name in ["conv1", "relu1", "pool1"]:
        new_nodes.append(node)

new_graph = helper.make_graph(
    new_nodes,
    "subgraph",
    [graph.input[0]],
    [graph.output[0]],
    graph.initializer
)

new_model = helper.make_model(new_graph)

onnx.save(new_model, "subgraph.onnx")

合并模型 #

python
import onnx
from onnx import helper

model1 = onnx.load("model1.onnx")
model2 = onnx.load("model2.onnx")

all_nodes = list(model1.graph.node) + list(model2.graph.node)
all_initializers = list(model1.graph.initializer) + list(model2.graph.initializer)

merged_graph = helper.make_graph(
    all_nodes,
    "merged_model",
    model1.graph.input,
    model2.graph.output,
    all_initializers
)

merged_model = helper.make_model(merged_graph)

onnx.save(merged_model, "merged_model.onnx")

模型简化 #

python
import onnx
from onnx import helper, optimizer

model = onnx.load("model.onnx")

passes = [
    "eliminate_identity",
    "eliminate_nop_transpose",
    "fuse_bn_into_conv",
    "fuse_consecutive_transposes"
]

optimized_model = optimizer.optimize(model, passes)

onnx.save(optimized_model, "simplified_model.onnx")

模型检查与调试 #

模型验证 #

python
import onnx

model = onnx.load("model.onnx")

try:
    onnx.checker.check_model(model)
    print("✅ 模型验证通过")
except onnx.checker.ValidationError as e:
    print(f"❌ 模型验证失败: {e}")

try:
    onnx.checker.check_model(model, full_check=True)
    print("✅ 完整验证通过")
except Exception as e:
    print(f"❌ 完整验证失败: {e}")

打印模型信息 #

python
import onnx

def print_model_info(model_path):
    model = onnx.load(model_path)
    
    print("=" * 60)
    print(f"模型: {model_path}")
    print("=" * 60)
    
    print(f"\nIR 版本: {model.ir_version}")
    print(f"生产者: {model.producer_name} {model.producer_version}")
    print(f"Opset 版本: {model.opset_import[0].version}")
    
    graph = model.graph
    print(f"\n图名称: {graph.name}")
    print(f"节点数量: {len(graph.node)}")
    
    print("\n输入:")
    for inp in graph.input:
        shape = [d.dim_value or d.dim_param or "?" for d in inp.type.tensor_type.shape.dim]
        print(f"  {inp.name}: {shape}")
    
    print("\n输出:")
    for out in graph.output:
        shape = [d.dim_value or d.dim_param or "?" for d in out.type.tensor_type.shape.dim]
        print(f"  {out.name}: {shape}")
    
    print("\n节点:")
    for node in graph.node:
        print(f"  {node.op_type}: {node.name}")
        print(f"    输入: {list(node.input)}")
        print(f"    输出: {list(node.output)}")
    
    print("\n初始化器:")
    for init in graph.initializer:
        print(f"  {init.name}: {list(init.dims)}")

print_model_info("model.onnx")

下一步 #

现在你已经了解了 ONNX 模型结构,接下来学习 算子详解,深入了解 ONNX 标准算子!

最后更新:2026-04-04