模型结构 #
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