Skip to content

Commit feb2dcc

Browse files
Tom/keras2onnx application tests (#1590)
* Add keras2onnx application tests Signed-off-by: Tom Wildenhain <[email protected]> * Pin Pillow version Signed-off-by: Tom Wildenhain <[email protected]> * Revmoe coverage reporting and old onnx version tests Signed-off-by: Tom Wildenhain <[email protected]> Co-authored-by: Guenther Schmuelling <[email protected]>
1 parent b1ffafe commit feb2dcc

File tree

86 files changed

+16858
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+16858
-3
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# Python package
2+
# Create and test a Python package on multiple Python versions.
3+
# Add steps that analyze code, save the dist with the build record, publish to a PyPI-compatible index, and more:
4+
# https://docs.microsoft.com/azure/devops/pipelines/languages/python
5+
6+
jobs:
7+
8+
- job: 'Test'
9+
timeoutInMinutes: 180
10+
pool:
11+
vmImage: 'Ubuntu-16.04'
12+
strategy:
13+
matrix:
14+
Python36-onnx1.5:
15+
python.version: '3.6'
16+
ONNX_PATH: onnx==1.5.0
17+
INSTALL_KERAS: pip install keras==2.2.4
18+
UNINSTALL_KERAS:
19+
INSTALL_TENSORFLOW: pip install tensorflow==1.15.0
20+
INSTALL_ORT: pip install onnxruntime==1.8.0
21+
INSTALL_KERAS_RESNET: pip install keras-resnet
22+
INSTALL_TRANSFORMERS:
23+
NIGHTLY_BUILD_TEST: python run_all.py --exclude "test_keras_applications_v2.py"
24+
25+
Python37-onnx1.6:
26+
python.version: '3.7.3'
27+
ONNX_PATH: onnx==1.6.0
28+
INSTALL_KERAS: pip install keras==2.3.1
29+
UNINSTALL_KERAS:
30+
INSTALL_TENSORFLOW: pip install tensorflow==1.15.0
31+
INSTALL_ORT: pip install onnxruntime==1.8.0
32+
INSTALL_KERAS_RESNET: pip install keras-resnet
33+
INSTALL_TRANSFORMERS:
34+
NIGHTLY_BUILD_TEST: python run_all.py --exclude "test_keras_applications_v2.py"
35+
36+
Python37-onnx1.9:
37+
python.version: '3.7.3'
38+
ONNX_PATH: onnx==1.9.0
39+
INSTALL_KERAS: pip install keras==2.3.1
40+
UNINSTALL_KERAS:
41+
INSTALL_TENSORFLOW: pip install tensorflow==1.15.0
42+
INSTALL_ORT: pip install onnxruntime==1.8.0
43+
INSTALL_KERAS_RESNET: pip install keras-resnet
44+
INSTALL_TRANSFORMERS:
45+
NIGHTLY_BUILD_TEST: python run_all.py --exclude "test_keras_applications_v2.py"
46+
47+
Python38-tf2:
48+
python.version: '3.8'
49+
ONNX_PATH: onnx==1.9.0
50+
INSTALL_KERAS:
51+
UNINSTALL_KERAS: pip uninstall keras -y
52+
INSTALL_TENSORFLOW: pip install tensorflow==2.2.0
53+
INSTALL_ORT: pip install onnxruntime==1.8.0
54+
INSTALL_KERAS_RESNET: pip install keras-resnet
55+
INSTALL_TRANSFORMERS: pip install transformers==3.4.0
56+
NIGHTLY_BUILD_TEST: python run_all_v2.py
57+
58+
steps:
59+
- script: sudo install -d -m 0777 /home/vsts/.conda/envs
60+
displayName: Fix Conda permissions
61+
62+
- task: CondaEnvironment@1
63+
inputs:
64+
createCustomEnvironment: true
65+
environmentName: 'py$(python.version)'
66+
packageSpecs: 'python=$(python.version)'
67+
68+
- script: |
69+
python -m pip install --upgrade pip
70+
conda config --set always_yes yes --set changeps1 no
71+
pip install $(ONNX_PATH)
72+
pip install h5py==2.9.0
73+
pip install numpy==1.19
74+
pip install parameterized
75+
$(INSTALL_TENSORFLOW)
76+
$(INSTALL_KERAS)
77+
pip install git+https://github.com/microsoft/onnxconverter-common
78+
$(INSTALL_ORT)
79+
pip install Pillow==8.2.0
80+
pip install opencv-python
81+
pip install tqdm
82+
pip install keras-segmentation==0.2.0
83+
git clone https://github.com/matterport/Mask_RCNN
84+
cd Mask_RCNN
85+
pip install -r requirements.txt
86+
python setup.py install
87+
cd ..
88+
pip install matplotlib
89+
git clone https://github.com/qqwweee/keras-yolo3
90+
$(INSTALL_KERAS_RESNET)
91+
pip install git+https://www.github.com/keras-team/keras-contrib.git
92+
pip install keras-tcn==2.8.3
93+
$(UNINSTALL_KERAS)
94+
pip install git+https://github.com/qubvel/efficientnet
95+
$(INSTALL_TRANSFORMERS)
96+
pip install keras-self-attention
97+
pip install pytest pytest-cov pytest-runner
98+
displayName: 'Install dependencies'
99+
100+
- script: |
101+
pip install -e .
102+
python -c "import onnxruntime"
103+
pytest tests/keras2onnx_unit_tests --doctest-modules --junitxml=junit/test-results.xml
104+
cd tests/keras2onnx_applications/nightly_build
105+
$(NIGHTLY_BUILD_TEST)
106+
displayName: 'pytest'
107+
108+
- task: PublishTestResults@2
109+
inputs:
110+
testResultsFiles: '**/test-results-*.xml'
111+
testRunTitle: 'Python $(python.version)'
112+
condition: succeededOrFailed()

ci_build/azure_pipelines/templates/unit_test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ steps:
1313
export TF2ONNX_SKIP_TFLITE_TESTS=$CI_SKIP_TFLITE_TESTS
1414
export TF2ONNX_SKIP_TF_TESTS=$CI_SKIP_TF_TESTS
1515
python -m pytest --cov=tf2onnx --cov-report=term --disable-pytest-warnings -r s tests --cov-append \
16-
--ignore tests/keras2onnx_unit_tests
16+
--ignore tests/keras2onnx_unit_tests --ignore tests/keras2onnx_applications
1717
timeoutInMinutes: 15
1818
displayName: ${{ format('Run UnitTest - Opset{0}', onnx_opset) }}
1919
condition: succeededOrFailed()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.h5
2+
*.onnx
66.8 KB
Loading
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!--- SPDX-License-Identifier: Apache-2.0 -->
2+
3+
# Introduction
4+
This tool converts the lpcnet model to onnx.
5+
To run this code, we need first install the original lpcnet model from <https://github.com/mozilla/LPCNet/>.
6+
Note that lpcnet is not a package, so please add its directory to the path.
7+
Then run
8+
```
9+
python convert_lpcnet_to_onnx.py [model_file]
10+
```
11+
model_file is the model with trained weights, it is a *.h5 file.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
import lpcnet
4+
import sys
5+
6+
model, enc, dec = lpcnet.new_lpcnet_model(use_gpu=False)
7+
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['sparse_categorical_accuracy'])
8+
model_file = sys.argv[1]
9+
model.load_weights(model_file)
10+
11+
import mock_keras2onnx
12+
oxml_enc = mock_keras2onnx.convert_keras(enc, 'lpcnet_enc')
13+
oxml_dec = mock_keras2onnx.convert_keras(dec, 'lpcnet_dec')
14+
15+
import onnx
16+
onnx.save(oxml_enc, "lpcnet_enc.onnx")
17+
onnx.save(oxml_dec, "lpcnet_dec.onnx")
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!--- SPDX-License-Identifier: Apache-2.0 -->
2+
3+
# Introduction
4+
The original Keras project of Masked RCNN is: <https://github.com/matterport/Mask_RCNN>. Follow the 'Installation' section in README.md to set up the model.
5+
There is also a good [tutorial](https://github.com/matterport/Mask_RCNN#step-by-step-detection) to learn about the object detection.
6+
7+
The conversion supports since opset 11, And the converted model need to be working with ONNXRuntime latest version which supports ONNX opset 11 and contrib ops needed by this model.
8+
9+
# Convert and Run the model.
10+
```
11+
cd <mask_rcnn directory>
12+
pip install -e .
13+
cd <mock_keras2onnx directory>/applications/mask_rcnn
14+
# convert the model to onnx and test it with an image.
15+
python mask_rcnn.py <image_file_path>
16+
```
17+
The unit test is added in our nightly build, see [here](https://github.com/onnx/keras-onnx/blob/master/applications/nightly_build/test_mask_rcnn.py)
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
import os
4+
import sys
5+
import numpy as np
6+
import skimage
7+
import onnx
8+
import mock_keras2onnx
9+
10+
from mrcnn.config import Config
11+
from mrcnn.model import BatchNorm, DetectionLayer
12+
from mrcnn import model as modellib
13+
from mrcnn import visualize
14+
15+
from mock_keras2onnx import set_converter
16+
from mock_keras2onnx.ke2onnx.batch_norm import convert_keras_batch_normalization
17+
from os.path import dirname, abspath
18+
sys.path.insert(0, os.path.join(dirname(abspath(__file__)), '../../keras2onnx_tests/'))
19+
from test_utils import convert_tf_crop_and_resize
20+
21+
22+
ROOT_DIR = os.path.abspath("./")
23+
24+
# Directory to save logs and trained model
25+
MODEL_DIR = os.path.join(ROOT_DIR, "logs")
26+
27+
# Path to trained weights file
28+
COCO_MODEL_PATH = os.path.join(ROOT_DIR, "mask_rcnn_coco.h5")
29+
30+
31+
class CocoConfig(Config):
32+
"""Configuration for training on MS COCO.
33+
Derives from the base Config class and overrides values specific
34+
to the COCO dataset.
35+
"""
36+
# Give the configuration a recognizable name
37+
NAME = "coco"
38+
39+
# We use a GPU with 12GB memory, which can fit two images.
40+
# Adjust down if you use a smaller GPU.
41+
IMAGES_PER_GPU = 2
42+
43+
# Uncomment to train on 8 GPUs (default is 1)
44+
# GPU_COUNT = 8
45+
46+
# Number of classes (including background)
47+
NUM_CLASSES = 1 + 80 # COCO has 80 classes
48+
49+
50+
class InferenceConfig(CocoConfig):
51+
# Set batch size to 1 since we'll be running inference on
52+
# one image at a time. Batch size = GPU_COUNT * IMAGES_PER_GPU
53+
GPU_COUNT = 1
54+
IMAGES_PER_GPU = 1
55+
56+
57+
config = InferenceConfig()
58+
config.display()
59+
60+
model = modellib.MaskRCNN(mode="inference", model_dir=MODEL_DIR, config=config)
61+
62+
# Load weights trained on MS-COCO
63+
model.load_weights(COCO_MODEL_PATH, by_name=True)
64+
65+
66+
def convert_BatchNorm(scope, operator, container):
67+
convert_keras_batch_normalization(scope, operator, container)
68+
69+
70+
def norm_boxes_graph(scope, operator, container, oopb, image_meta):
71+
image_shapes = oopb.add_node('Slice',
72+
[image_meta,
73+
('_start', oopb.int64, np.array([4], dtype='int64')),
74+
('_end', oopb.int64, np.array([7], dtype='int64')),
75+
('_axes', oopb.int64, np.array([1], dtype='int64'))
76+
],
77+
operator.inputs[0].full_name + '_image_shapes')
78+
image_shape = oopb.add_node('Slice',
79+
[image_shapes,
80+
('_start', oopb.int64, np.array([0], dtype='int64')),
81+
('_end', oopb.int64, np.array([1], dtype='int64')),
82+
('_axes', oopb.int64, np.array([0], dtype='int64'))
83+
],
84+
operator.inputs[0].full_name + '_image_shape')
85+
image_shape_squeeze = oopb.apply_squeeze(image_shape, name=operator.full_name + '_image_shape_squeeze', axes=[0])[0]
86+
87+
window = oopb.add_node('Slice',
88+
[image_meta,
89+
('_start', oopb.int64, np.array([7], dtype='int64')),
90+
('_end', oopb.int64, np.array([11], dtype='int64')),
91+
('_axes', oopb.int64, np.array([1], dtype='int64'))
92+
],
93+
operator.inputs[0].full_name + '_window')
94+
h_norm = oopb.add_node('Slice',
95+
[image_shape_squeeze,
96+
('_start', oopb.int64, np.array([0], dtype='int64')),
97+
('_end', oopb.int64, np.array([1], dtype='int64')),
98+
('_axes', oopb.int64, np.array([0], dtype='int64'))
99+
],
100+
operator.inputs[0].full_name + '_h_norm')
101+
w_norm = oopb.add_node('Slice',
102+
[image_shape_squeeze,
103+
('_start', oopb.int64, np.array([1], dtype='int64')),
104+
('_end', oopb.int64, np.array([2], dtype='int64')),
105+
('_axes', oopb.int64, np.array([0], dtype='int64'))
106+
],
107+
operator.inputs[0].full_name + '_w_norm')
108+
h_norm_float = scope.get_unique_variable_name('h_norm_float')
109+
attrs = {'to': 1}
110+
container.add_node('Cast', h_norm, h_norm_float, op_version=operator.target_opset,
111+
**attrs)
112+
w_norm_float = scope.get_unique_variable_name('w_norm_float')
113+
attrs = {'to': 1}
114+
container.add_node('Cast', w_norm, w_norm_float, op_version=operator.target_opset,
115+
**attrs)
116+
hw_concat = scope.get_unique_variable_name(operator.inputs[0].full_name + '_hw_concat')
117+
attrs = {'axis': -1}
118+
container.add_node("Concat",
119+
[h_norm_float, w_norm_float, h_norm_float, w_norm_float],
120+
hw_concat,
121+
op_version=operator.target_opset,
122+
name=operator.inputs[0].full_name + '_hw_concat', **attrs)
123+
scale = oopb.add_node('Sub',
124+
[hw_concat,
125+
('_sub', oopb.float, np.array([1.0], dtype='float32'))
126+
],
127+
operator.inputs[0].full_name + '_scale')
128+
boxes_shift = oopb.add_node('Sub',
129+
[window,
130+
('_sub', oopb.float, np.array([0.0, 0.0, 1.0, 1.0], dtype='float32'))
131+
],
132+
operator.inputs[0].full_name + '_boxes_shift')
133+
divide = oopb.add_node('Div',
134+
[boxes_shift, scale],
135+
operator.inputs[0].full_name + '_divide')
136+
# output shape: [batch, 4]
137+
return divide
138+
139+
140+
def convert_DetectionLayer(scope, operator, container):
141+
# type: (mock_keras2onnx.common.InterimContext, mock_keras2onnx.common.Operator, mock_keras2onnx.common.OnnxObjectContainer) -> None
142+
pass
143+
144+
145+
set_converter(DetectionLayer, convert_DetectionLayer)
146+
set_converter(BatchNorm, convert_BatchNorm)
147+
148+
149+
# Run detection
150+
class_names = ['BG', 'person', 'bicycle', 'car', 'motorcycle', 'airplane',
151+
'bus', 'train', 'truck', 'boat', 'traffic light',
152+
'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird',
153+
'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', 'bear',
154+
'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie',
155+
'suitcase', 'frisbee', 'skis', 'snowboard', 'sports ball',
156+
'kite', 'baseball bat', 'baseball glove', 'skateboard',
157+
'surfboard', 'tennis racket', 'bottle', 'wine glass', 'cup',
158+
'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple',
159+
'sandwich', 'orange', 'broccoli', 'carrot', 'hot dog', 'pizza',
160+
'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed',
161+
'dining table', 'toilet', 'tv', 'laptop', 'mouse', 'remote',
162+
'keyboard', 'cell phone', 'microwave', 'oven', 'toaster',
163+
'sink', 'refrigerator', 'book', 'clock', 'vase', 'scissors',
164+
'teddy bear', 'hair drier', 'toothbrush']
165+
166+
167+
def generate_image(images, molded_images, windows, results):
168+
results_final = []
169+
for i, image in enumerate(images):
170+
final_rois, final_class_ids, final_scores, final_masks = \
171+
model.unmold_detections(results[0][i], results[3][i], # detections[i], mrcnn_mask[i]
172+
image.shape, molded_images[i].shape,
173+
windows[i])
174+
results_final.append({
175+
"rois": final_rois,
176+
"class_ids": final_class_ids,
177+
"scores": final_scores,
178+
"masks": final_masks,
179+
})
180+
r = results_final[i]
181+
visualize.display_instances(image, r['rois'], r['masks'], r['class_ids'],
182+
class_names, r['scores'])
183+
return results_final
184+
185+
186+
if __name__ == '__main__':
187+
if len(sys.argv) < 2:
188+
print("Need an image file for object detection.")
189+
exit(-1)
190+
191+
model_file_name = './mrcnn.onnx'
192+
if not os.path.exists(model_file_name):
193+
# use opset 11 or later
194+
set_converter('CropAndResize', convert_tf_crop_and_resize)
195+
oml = mock_keras2onnx.convert_keras(model.keras_model, target_opset=11)
196+
onnx.save_model(oml, model_file_name)
197+
198+
# run with ONNXRuntime
199+
import onnxruntime
200+
filename = sys.argv[1]
201+
image = skimage.io.imread(filename)
202+
images = [image]
203+
204+
sess = onnxruntime.InferenceSession(model_file_name)
205+
206+
# preprocessing
207+
molded_images, image_metas, windows = model.mold_inputs(images)
208+
anchors = model.get_anchors(molded_images[0].shape)
209+
anchors = np.broadcast_to(anchors, (model.config.BATCH_SIZE,) + anchors.shape)
210+
211+
results = \
212+
sess.run(None, {"input_image": molded_images.astype(np.float32),
213+
"input_anchors": anchors,
214+
"input_image_meta": image_metas.astype(np.float32)})
215+
216+
# postprocessing
217+
results_final = generate_image(images, molded_images, windows, results)

0 commit comments

Comments
 (0)