Skip to content

Commit e986e85

Browse files
authored
ONNX&YOLO Converter (#185)
* The parser has been tested on the resnest101e_Opset16.onnx, GoogLeNet.onnx, and densenet121_Opset16.onnx models. - The yolo11x-cls.pt model has been converted to the onnx format using ultralytics. - The parser for our first model has been fixed to use a single json file style and a single code for reading and converting to a tensor. * I removed the check for the equality of the bias to the last dimension of the weights from the tensor implementation, as in the onnx and yolo models, the bias is equal in size to the first array. - Additionally, the weights and bias data are now stored more explicitly in json, with each data item in its own fields "weights": and "bias":, which helps prevent potential errors during conversion. * I also think that we should provide a link to download each model, as we won't be able to store them all in the repository. We should also configure the "evaluate model" check in github to download the model from the link and enable caching.
1 parent adaa1da commit e986e85

22 files changed

Lines changed: 363 additions & 206 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,9 @@ jobs:
203203
sudo ldconfig
204204
- name: Generate model JSON
205205
run: |
206-
cd app/AlexNet
206+
cd docs && mkdir jsons
207+
cd ..
208+
cd app/Converters
207209
pip install -r requirements.txt
208210
python parser.py
209211
cd ../..

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ build
33
build*
44
3rdparty/tensorflow
55
app/AccuracyImgNet/imgs
6-
docs/model_data_alexnet_1.json
6+
docs/
77
docs/input
88
docs/mnist

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ A lightweight C++ library for performing high-performance inference on MNIST han
2020
### Neural network models
2121
You need to download [Alexnet-model.h5](https://github.com/moizahmed97/Convolutional-Neural-Net-Designer/blob/master/AlexNet-model.h5) to the folder *docs*
2222

23+
Other models:</br>
24+
[GoogLeNet.onnx](https://huggingface.co/qualcomm/GoogLeNet/resolve/main/GoogLeNet.onnx)</br>
25+
[yolo11x-cls.pt](https://github.com/ultralytics/assets/releases/download/v8.3.0/yolo11x-cls.pt)</br>
26+
[resnest101e_Opset16.onnx](https://github.com/onnx/models/raw/refs/heads/main/Computer_Vision/resnest101e_Opset16_timm/resnest101e_Opset16.onnx?download=)</br>
27+
[densenet121_Opset16.onnx](https://github.com/onnx/models/raw/refs/heads/main/Computer_Vision/densenet121_Opset16_timm/densenet121_Opset16.onnx?download=)
28+
29+
30+
2331
## **How do I launch the inference?**
2432
* Make sure you install the project dependencies by running: *pip install -r requirements.txt*
2533
* You need to run the script *parser.py* that is located in app/AlexNet to read weights from a model *Alexnet-model.h5* and the json file with the weights will be stored in the *docs* folder.

app/AlexNet/CMakeLists.txt

Lines changed: 0 additions & 6 deletions
This file was deleted.

app/AlexNet/parser.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

app/AlexNet/requirements.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

app/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
add_subdirectory(ReaderImage)
22
add_subdirectory(Accuracy)
3-
add_subdirectory(AlexNet)
3+
add_subdirectory(Converters)
44
add_subdirectory(AccuracyImgNet)
55
add_subdirectory(Graph)

app/Converters/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
add_executable(Reader_weights reader_weights_sample.cpp)
2+
add_executable(Reader_weights_onnx reader_weights_sample_onnx.cpp)
3+
target_link_libraries(Reader_weights PUBLIC perf_lib layers_lib reader_lib)
4+
target_link_libraries(Reader_weights_onnx PUBLIC perf_lib layers_lib reader_lib)
5+
add_definitions(-DMODEL_PATH_H5="${CMAKE_SOURCE_DIR}/docs/jsons/model_data_alexnet_1.json")
6+
add_definitions(-DMODEL_PATH_GOOGLENET_ONNX="${CMAKE_SOURCE_DIR}/docs/jsons/googlenet_onnx_model.json")
7+
add_definitions(-DMODEL_PATH_DENSENET_ONNX="${CMAKE_SOURCE_DIR}/docs/jsons/densenet121_Opset16_onnx_model.json")
8+
add_definitions(-DMODEL_PATH_RESNET_ONNX="${CMAKE_SOURCE_DIR}/docs/jsons/resnest101e_Opset16_onnx_model.json")
9+
add_definitions(-DMODEL_PATH_YOLO11NET_ONNX="${CMAKE_SOURCE_DIR}/docs/jsons/yolo11x-cls_onnx_model.json")

app/Converters/parser.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import json
2+
import os
3+
import tensorflow as tf
4+
from tensorflow.keras.initializers import GlorotUniform as OriginalGlorotUniform, Zeros as OriginalZeros
5+
from tensorflow.keras.models import load_model
6+
7+
8+
class CustomGlorotUniform(OriginalGlorotUniform):
9+
def __init__(self, seed=None, **kwargs):
10+
kwargs.pop('dtype', None)
11+
super().__init__(seed=seed, **kwargs)
12+
13+
14+
class CustomZeros(OriginalZeros):
15+
def __init__(self, **kwargs):
16+
kwargs.pop('dtype', None)
17+
super().__init__(**kwargs)
18+
19+
20+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21+
MODEL_PATH = os.path.join(BASE_DIR, 'docs/models', 'AlexNet-model.h5')
22+
MODEL_DATA_PATH = os.path.join(BASE_DIR, 'docs/jsons', 'model_data_alexnet_1.json')
23+
24+
model = load_model(MODEL_PATH, custom_objects={'GlorotUniform': CustomGlorotUniform, 'Zeros': CustomZeros})
25+
26+
layer_info = []
27+
for index, layer in enumerate(model.layers):
28+
layer_name = layer.name
29+
layer_type = type(layer).__name__
30+
layer_config = layer.get_config()
31+
weights = layer.get_weights()
32+
33+
layer_data = {
34+
'index': len(layer_info),
35+
'name': layer_name,
36+
'type': layer_type,
37+
'weights': [],
38+
'bias': []
39+
}
40+
41+
if isinstance(layer, tf.keras.layers.Conv2D):
42+
layer_data['padding'] = layer_config.get('padding', None)
43+
layer_activation = layer_config.get('activation', None)
44+
45+
if len(weights) > 0:
46+
layer_data['weights'] = weights[0].tolist()
47+
if len(weights) > 1:
48+
layer_data['bias'] = weights[1].tolist()
49+
50+
elif isinstance(layer, tf.keras.layers.Dense):
51+
if len(weights) > 0:
52+
layer_data['weights'] = weights[0].tolist()
53+
if len(weights) > 1:
54+
layer_data['bias'] = weights[1].tolist()
55+
56+
else:
57+
if weights:
58+
layer_data['weights'] = [w.tolist() for w in weights]
59+
60+
layer_info.append(layer_data)
61+
62+
if isinstance(layer, (tf.keras.layers.Conv2D)) and layer_activation:
63+
activation_layer = {
64+
'index': len(layer_info),
65+
'name': f"activation_{layer_name}",
66+
'type': layer_activation,
67+
'weights': [],
68+
'bias': []
69+
}
70+
layer_info.append(activation_layer)
71+
72+
with open(MODEL_DATA_PATH, 'w') as f:
73+
json.dump(layer_info, f, indent=2)
74+
75+
print(f"Model data saved to {MODEL_DATA_PATH}")

app/Converters/parser_onnx.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import json
2+
import onnx
3+
import os
4+
from onnx import TensorProto
5+
from onnx import helper, numpy_helper
6+
from ultralytics import YOLO
7+
8+
def convert_pt_to_onnx(pt_model_path, onnx_model_path=None):
9+
if onnx_model_path is None:
10+
onnx_model_path = pt_model_path.replace('.pt', '.onnx')
11+
12+
model = YOLO(pt_model_path)
13+
model.export(format="onnx", dynamic=False, simplify=True)
14+
15+
return onnx_model_path
16+
17+
def onnx_to_json(model_path, output_json_path):
18+
if model_path.endswith('.pt'):
19+
model_path = convert_pt_to_onnx(model_path)
20+
21+
model = onnx.load(model_path)
22+
onnx.checker.check_model(model)
23+
24+
initializers_dict = {
25+
init.name: {
26+
"data_type": init.data_type,
27+
"dims": list(init.dims),
28+
"values": numpy_helper.to_array(init).tolist()
29+
}
30+
for init in model.graph.initializer
31+
}
32+
33+
layer_info = []
34+
input_layer = {
35+
"index": 0,
36+
"name": "input_1",
37+
"type": "InputLayer",
38+
"weights": [],
39+
"attributes": {}
40+
}
41+
layer_info.append(input_layer)
42+
43+
for node in model.graph.node:
44+
layer_data = {
45+
"index": len(layer_info),
46+
"name": node.name.replace('/', '_'),
47+
"type": node.op_type,
48+
"attributes": {}
49+
}
50+
51+
for attr in node.attribute:
52+
attr_value = helper.get_attribute_value(attr)
53+
if isinstance(attr_value, TensorProto):
54+
attr_value = numpy_helper.to_array(attr_value).tolist()
55+
elif isinstance(attr_value, bytes):
56+
attr_value = attr_value.decode('utf-8', errors='ignore')
57+
elif hasattr(attr_value, 'tolist'):
58+
attr_value = attr_value.tolist()
59+
elif str(type(attr_value)).endswith("RepeatedScalarContainer'>"):
60+
attr_value = list(attr_value)
61+
layer_data["attributes"][attr.name] = attr_value
62+
63+
if attr.name == "pads":
64+
layer_data["padding"] = "same" if any(p > 0 for p in attr_value) else "valid"
65+
elif attr.name == "kernel_shape":
66+
layer_data["kernel_size"] = attr_value
67+
elif attr.name == "strides":
68+
layer_data["strides"] = attr_value
69+
70+
node_init = []
71+
for input_name in node.input:
72+
if input_name in initializers_dict:
73+
node_init.append(initializers_dict[input_name])
74+
75+
if len(node_init) == 1:
76+
init = node_init[0]
77+
if len(init["dims"]) == 0 or (len(init["dims"]) == 1 and init["dims"][0] == 1):
78+
layer_data["value"] = init["values"] if len(init["dims"]) == 0 else init["values"][0]
79+
else:
80+
layer_data["weights"] = init["values"]
81+
elif len(node_init) > 1:
82+
weights = []
83+
for init in node_init[:-1]:
84+
if len(init["dims"]) > 0:
85+
weights.extend(init["values"]) if isinstance(init["values"][0], list) else weights.append(
86+
init["values"])
87+
88+
if weights:
89+
layer_data["weights"] = weights
90+
91+
if len(node_init[-1]["dims"]) == 1:
92+
layer_data["bias"] = node_init[-1]["values"]
93+
94+
layer_info.append(layer_data)
95+
96+
class CustomEncoder(json.JSONEncoder):
97+
def default(self, obj):
98+
if isinstance(obj, TensorProto):
99+
return {
100+
"name": obj.name,
101+
"dims": list(obj.dims),
102+
"data_type": obj.data_type,
103+
"raw_data": obj.raw_data.hex() if obj.raw_data else None,
104+
}
105+
elif hasattr(obj, 'tolist'):
106+
return obj.tolist()
107+
elif str(type(obj)).endswith("RepeatedScalarContainer'>"):
108+
return list(obj)
109+
return super().default(obj)
110+
111+
with open(output_json_path, 'w') as f:
112+
json.dump(layer_info, f, indent=2, cls=CustomEncoder)
113+
114+
print(f"Модель успешно сохранена в {output_json_path}")
115+
116+
117+
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
118+
119+
MODEL_PATH = os.path.join(BASE_DIR, 'docs\\models', 'yolo11x-cls.pt')
120+
MODEL_DATA_PATH = os.path.join(BASE_DIR, 'docs\\jsons', 'yolo11x-cls_onnx_model.json')
121+
122+
onnx_to_json(MODEL_PATH, MODEL_DATA_PATH)

0 commit comments

Comments
 (0)