0
# Text Processing
1
2
Convert between ONNX protocol buffer representations and human-readable text formats for debugging, serialization, and model inspection. This module enables working with ONNX models in textual form for better readability and debugging.
3
4
## Capabilities
5
6
### Text Parsing
7
8
Parse human-readable text representations into ONNX protocol buffer objects.
9
10
```python { .api }
11
def parse_model(model_text):
12
"""
13
Parse text representation to ModelProto.
14
15
Parameters:
16
- model_text: String containing text representation of ONNX model
17
18
Returns:
19
ModelProto: Parsed model object
20
21
Raises:
22
ParseError: If text cannot be parsed or contains syntax errors
23
"""
24
25
def parse_graph(graph_text):
26
"""
27
Parse text representation to GraphProto.
28
29
Parameters:
30
- graph_text: String containing text representation of ONNX graph
31
32
Returns:
33
GraphProto: Parsed graph object
34
35
Raises:
36
ParseError: If text cannot be parsed or contains syntax errors
37
"""
38
39
def parse_function(function_text):
40
"""
41
Parse text representation to FunctionProto.
42
43
Parameters:
44
- function_text: String containing text representation of ONNX function
45
46
Returns:
47
FunctionProto: Parsed function object
48
49
Raises:
50
ParseError: If text cannot be parsed or contains syntax errors
51
"""
52
53
def parse_node(node_text):
54
"""
55
Parse text representation to NodeProto.
56
57
Parameters:
58
- node_text: String containing text representation of ONNX node
59
60
Returns:
61
NodeProto: Parsed node object
62
63
Raises:
64
ParseError: If text cannot be parsed or contains syntax errors
65
"""
66
```
67
68
### Text Generation
69
70
Convert ONNX protocol buffer objects to human-readable text representations.
71
72
```python { .api }
73
def to_text(proto):
74
"""
75
Convert ONNX proto to text representation.
76
77
Parameters:
78
- proto: ONNX protocol buffer object (ModelProto, GraphProto, FunctionProto, or NodeProto)
79
80
Returns:
81
str: Human-readable text representation
82
83
Raises:
84
ValueError: If proto type is not supported for text conversion
85
"""
86
```
87
88
### Exception Classes
89
90
Exception types for text processing errors.
91
92
```python { .api }
93
class ParseError(Exception):
94
"""
95
Exception raised when text parsing fails.
96
97
Contains detailed information about parsing errors including
98
line numbers and specific syntax issues.
99
"""
100
```
101
102
## Usage Examples
103
104
### Model Text Conversion
105
106
```python
107
import onnx
108
from onnx import printer, parser
109
110
# Load a binary model
111
model = onnx.load_model("example_model.onnx")
112
113
# Convert to text representation
114
model_text = printer.to_text(model)
115
print("Model in text format:")
116
print(model_text[:500] + "..." if len(model_text) > 500 else model_text)
117
118
# Save text representation to file
119
with open("model.txt", "w") as f:
120
f.write(model_text)
121
122
# Parse text back to model
123
try:
124
parsed_model = parser.parse_model(model_text)
125
126
# Verify they are equivalent
127
onnx.checker.check_model(parsed_model)
128
print("Text parsing and conversion successful!")
129
130
except parser.ParseError as e:
131
print(f"Parse error: {e}")
132
```
133
134
### Graph Inspection and Debugging
135
136
```python
137
import onnx
138
from onnx import printer, helper, TensorProto
139
import numpy as np
140
141
# Create a simple graph for demonstration
142
def create_debug_graph():
143
X = helper.make_tensor_value_info('input', TensorProto.FLOAT, [1, 3, 224, 224])
144
Y = helper.make_tensor_value_info('output', TensorProto.FLOAT, [1, 1000])
145
146
# Create some weights
147
conv_weight = np.random.randn(64, 3, 7, 7).astype(np.float32)
148
conv_tensor = helper.make_tensor('conv_weight', TensorProto.FLOAT,
149
conv_weight.shape, conv_weight)
150
151
fc_weight = np.random.randn(64, 1000).astype(np.float32)
152
fc_tensor = helper.make_tensor('fc_weight', TensorProto.FLOAT,
153
fc_weight.shape, fc_weight)
154
155
# Create nodes
156
conv_node = helper.make_node(
157
'Conv', ['input', 'conv_weight'], ['conv_out'],
158
kernel_shape=[7, 7], strides=[2, 2], pads=[3, 3, 3, 3]
159
)
160
161
relu_node = helper.make_node('Relu', ['conv_out'], ['relu_out'])
162
163
pool_node = helper.make_node('GlobalAveragePool', ['relu_out'], ['pool_out'])
164
165
reshape_node = helper.make_node('Flatten', ['pool_out'], ['flat_out'])
166
167
fc_node = helper.make_node('MatMul', ['flat_out', 'fc_weight'], ['output'])
168
169
# Create graph
170
graph = helper.make_graph(
171
[conv_node, relu_node, pool_node, reshape_node, fc_node],
172
'debug_model',
173
[X], [Y],
174
[conv_tensor, fc_tensor]
175
)
176
177
return helper.make_model(graph)
178
179
# Create and inspect model
180
debug_model = create_debug_graph()
181
182
# Print different components
183
print("=== FULL MODEL ===")
184
model_text = printer.to_text(debug_model)
185
print(model_text)
186
187
print("\n=== GRAPH ONLY ===")
188
graph_text = printer.to_text(debug_model.graph)
189
print(graph_text)
190
191
print("\n=== INDIVIDUAL NODES ===")
192
for i, node in enumerate(debug_model.graph.node):
193
node_text = printer.to_text(node)
194
print(f"Node {i} ({node.op_type}):")
195
print(node_text)
196
print()
197
```
198
199
### Interactive Model Editing
200
201
```python
202
import onnx
203
from onnx import printer, parser
204
205
def interactive_node_editor(model_path):
206
"""Interactive tool for editing model nodes via text."""
207
208
model = onnx.load_model(model_path)
209
210
print(f"Model has {len(model.graph.node)} nodes:")
211
for i, node in enumerate(model.graph.node):
212
print(f" {i}: {node.op_type} ({node.name or 'unnamed'})")
213
214
while True:
215
try:
216
node_idx = input("\nEnter node index to edit (or 'q' to quit): ")
217
if node_idx.lower() == 'q':
218
break
219
220
node_idx = int(node_idx)
221
if node_idx < 0 or node_idx >= len(model.graph.node):
222
print("Invalid node index")
223
continue
224
225
# Show current node
226
current_node = model.graph.node[node_idx]
227
node_text = printer.to_text(current_node)
228
print(f"\nCurrent node {node_idx}:")
229
print(node_text)
230
231
# Get new text
232
print("\nEnter new node definition (or press Enter to skip):")
233
new_text = input()
234
if not new_text.strip():
235
continue
236
237
# Parse new node
238
try:
239
new_node = parser.parse_node(new_text)
240
model.graph.node[node_idx].CopyFrom(new_node)
241
print("Node updated successfully!")
242
243
# Validate model
244
onnx.checker.check_model(model)
245
print("Model validation passed!")
246
247
except parser.ParseError as e:
248
print(f"Parse error: {e}")
249
except onnx.checker.ValidationError as e:
250
print(f"Validation error: {e}")
251
252
except (ValueError, KeyboardInterrupt):
253
print("Invalid input or interrupted")
254
continue
255
256
# Save modified model
257
save_path = input("\nEnter path to save modified model (or press Enter to skip): ")
258
if save_path.strip():
259
onnx.save_model(model, save_path)
260
print(f"Model saved to {save_path}")
261
262
# Example usage (commented out)
263
# interactive_node_editor("model.onnx")
264
```
265
266
### Model Comparison via Text
267
268
```python
269
import onnx
270
from onnx import printer
271
import difflib
272
273
def compare_models_text(model1_path, model2_path):
274
"""Compare two models using text diff."""
275
276
model1 = onnx.load_model(model1_path)
277
model2 = onnx.load_model(model2_path)
278
279
# Convert to text
280
text1 = printer.to_text(model1).splitlines()
281
text2 = printer.to_text(model2).splitlines()
282
283
# Generate diff
284
diff = list(difflib.unified_diff(
285
text1, text2,
286
fromfile=model1_path,
287
tofile=model2_path,
288
n=3
289
))
290
291
if diff:
292
print(f"Differences between {model1_path} and {model2_path}:")
293
for line in diff:
294
print(line)
295
else:
296
print("Models are identical in structure")
297
298
# Example usage (commented out)
299
# compare_models_text("model_v1.onnx", "model_v2.onnx")
300
```
301
302
### Text-Based Model Templates
303
304
```python
305
import onnx
306
from onnx import parser
307
308
def create_model_from_template():
309
"""Create models using text templates."""
310
311
# Define a model template
312
model_template = '''
313
ir_version: 7
314
producer_name: "text-template"
315
graph {
316
name: "linear_model"
317
input {
318
name: "input"
319
type {
320
tensor_type {
321
elem_type: 1
322
shape {
323
dim { dim_value: 1 }
324
dim { dim_value: 784 }
325
}
326
}
327
}
328
}
329
output {
330
name: "output"
331
type {
332
tensor_type {
333
elem_type: 1
334
shape {
335
dim { dim_value: 1 }
336
dim { dim_value: 10 }
337
}
338
}
339
}
340
}
341
initializer {
342
name: "weight"
343
data_type: 1
344
dims: 784
345
dims: 10
346
raw_data: "\\000\\000\\000..."
347
}
348
node {
349
input: "input"
350
input: "weight"
351
output: "output"
352
op_type: "MatMul"
353
}
354
}
355
opset_import {
356
version: 14
357
}
358
'''
359
360
try:
361
# Parse the template (this is a simplified example)
362
# In practice, you'd need proper tensor data
363
print("Model template:")
364
print(model_template)
365
366
# For a real implementation, you'd parse actual protobuf text format
367
print("Note: This is a demonstration of the text format structure")
368
print("Real implementation would require proper protobuf text parsing")
369
370
except Exception as e:
371
print(f"Template parsing error: {e}")
372
373
# Demonstrate text template structure
374
create_model_from_template()
375
```
376
377
### Text Format Validation
378
379
```python
380
import onnx
381
from onnx import parser, printer
382
383
def validate_text_roundtrip(model_path):
384
"""Validate that text conversion preserves model integrity."""
385
386
# Load original model
387
original_model = onnx.load_model(model_path)
388
print("Original model loaded successfully")
389
390
# Convert to text
391
model_text = printer.to_text(original_model)
392
print(f"Model converted to text ({len(model_text)} characters)")
393
394
try:
395
# Parse back from text
396
parsed_model = parser.parse_model(model_text)
397
print("Text parsed back to model successfully")
398
399
# Validate both models
400
onnx.checker.check_model(original_model)
401
onnx.checker.check_model(parsed_model)
402
print("Both models pass validation")
403
404
# Compare key properties
405
print("\nComparison:")
406
print(f" Nodes: {len(original_model.graph.node)} vs {len(parsed_model.graph.node)}")
407
print(f" Inputs: {len(original_model.graph.input)} vs {len(parsed_model.graph.input)}")
408
print(f" Outputs: {len(original_model.graph.output)} vs {len(parsed_model.graph.output)}")
409
print(f" Initializers: {len(original_model.graph.initializer)} vs {len(parsed_model.graph.initializer)}")
410
411
# Check if functionally equivalent
412
if (len(original_model.graph.node) == len(parsed_model.graph.node) and
413
len(original_model.graph.input) == len(parsed_model.graph.input) and
414
len(original_model.graph.output) == len(parsed_model.graph.output)):
415
print("✓ Text roundtrip preserves model structure")
416
else:
417
print("✗ Text roundtrip changed model structure")
418
419
except parser.ParseError as e:
420
print(f"✗ Parse error during roundtrip: {e}")
421
except onnx.checker.ValidationError as e:
422
print(f"✗ Validation error during roundtrip: {e}")
423
424
# Example usage (commented out)
425
# validate_text_roundtrip("model.onnx")
426
```