diff --git a/pt2tf/README.md b/pt2tf/README.md
new file mode 100644
index 0000000..9462537
--- /dev/null
+++ b/pt2tf/README.md
@@ -0,0 +1,15 @@
+#1 建立虚拟环境
+$ virtualenv .venv
+
+#2 激活虚拟环境
+$ source .venv/bin/activate
+
+#3 安装依赖包
+$ pip install -r requirements.txt
+$ pip install -e onnx-tensorflow
+
+#4 生成onnx模型
+$ python pt2onnx.py
+
+#5 生成pb模型
+$ onnx-tf convert -i efficientnet-b3.onnx -o efficientnet-b3.pb
diff --git a/pt2tf/onnx-tensorflow/.travis.yml b/pt2tf/onnx-tensorflow/.travis.yml
new file mode 100644
index 0000000..b647b39
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/.travis.yml
@@ -0,0 +1,30 @@
+os: linux
+arch: amd64
+dist: bionic
+language: python
+python: 3.6
+cache: pip
+
+# Required
+# - ONNX 1.6 (Opset 11), TensorFlow 1.15.0, Sep 2019
+# - ONNX 1.6 (Opset 11), TensorFlow 1.15.3, May 2020
+# Optional
+# - ONNX 1.7 (Opset 12), TensorFlow 1.15.3, May 2020
+
+env:
+ - ONNX_PIP=onnx==1.6.0 TF_PIP=tensorflow==1.15.0
+ - ONNX_PIP=onnx==1.6.0 TF_PIP=tensorflow==1.15.3
+ - ONNX_PIP=onnx==1.7.0 TF_PIP=tensorflow==1.15.3
+
+jobs:
+ fast_finish: true
+ # Be aware when updating the dependency versions.
+ # Envs below must match *exactly* an env from above,
+ # otherwise an env failure will fail the overall build.
+ allow_failures:
+ - env: ONNX_PIP=onnx==1.7.0 TF_PIP=tensorflow==1.15.3
+
+before_install: pip install -U setuptools
+install: pip install $ONNX_PIP $TF_PIP
+before_script: pip install -e .
+script: python -m unittest discover test -v
diff --git a/pt2tf/onnx-tensorflow/LICENSE b/pt2tf/onnx-tensorflow/LICENSE
new file mode 100644
index 0000000..6598f66
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/LICENSE
@@ -0,0 +1,213 @@
+ONNX-Tensorflow Converter
+
+Copyright (c) International Business Machines Corporation 2017, 2018
+Copyright (c) LeapMind Inc. 2018
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction, and
+ distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by the
+ copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all other
+ entities that control, are controlled by, or are under common control with
+ that entity. For the purposes of this definition, "control" means (i) the
+ power, direct or indirect, to cause the direction or management of such
+ entity, whether by contract or otherwise, or (ii) ownership of
+ fifty percent (50%) or more of the outstanding shares, or (iii) beneficial
+ ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity exercising
+ permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation source,
+ and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical transformation
+ or translation of a Source form, including but not limited to compiled
+ object code, generated documentation, and conversions to
+ other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or Object
+ form, made available under the License, as indicated by a copyright notice
+ that is included in or attached to the work (an example is provided in the
+ Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object form,
+ that is based on (or derived from) the Work and for which the editorial
+ revisions, annotations, elaborations, or other modifications represent,
+ as a whole, an original work of authorship. For the purposes of this
+ License, Derivative Works shall not include works that remain separable
+ from, or merely link (or bind by name) to the interfaces of, the Work and
+ Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including the original
+ version of the Work and any modifications or additions to that Work or
+ Derivative Works thereof, that is intentionally submitted to Licensor for
+ inclusion in the Work by the copyright owner or by an individual or
+ Legal Entity authorized to submit on behalf of the copyright owner.
+ For the purposes of this definition, "submitted" means any form of
+ electronic, verbal, or written communication sent to the Licensor or its
+ representatives, including but not limited to communication on electronic
+ mailing lists, source code control systems, and issue tracking systems
+ that are managed by, or on behalf of, the Licensor for the purpose of
+ discussing and improving the Work, but excluding communication that is
+ conspicuously marked or otherwise designated in writing by the copyright
+ owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity on
+ behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License.
+
+ Subject to the terms and conditions of this License, each Contributor
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
+ royalty-free, irrevocable copyright license to reproduce, prepare
+ Derivative Works of, publicly display, publicly perform, sublicense,
+ and distribute the Work and such Derivative Works in
+ Source or Object form.
+
+ 3. Grant of Patent License.
+
+ Subject to the terms and conditions of this License, each Contributor
+ hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
+ royalty-free, irrevocable (except as stated in this section) patent
+ license to make, have made, use, offer to sell, sell, import, and
+ otherwise transfer the Work, where such license applies only to those
+ patent claims licensable by such Contributor that are necessarily
+ infringed by their Contribution(s) alone or by combination of their
+ Contribution(s) with the Work to which such Contribution(s) was submitted.
+ If You institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+ Contribution incorporated within the Work constitutes direct or
+ contributory patent infringement, then any patent licenses granted to
+ You under this License for that Work shall terminate as of the date such
+ litigation is filed.
+
+ 4. Redistribution.
+
+ You may reproduce and distribute copies of the Work or Derivative Works
+ thereof in any medium, with or without modifications, and in Source or
+ Object form, provided that You meet the following conditions:
+
+ 1. You must give any other recipients of the Work or Derivative Works a
+ copy of this License; and
+
+ 2. You must cause any modified files to carry prominent notices stating
+ that You changed the files; and
+
+ 3. You must retain, in the Source form of any Derivative Works that You
+ distribute, all copyright, patent, trademark, and attribution notices from
+ the Source form of the Work, excluding those notices that do not pertain
+ to any part of the Derivative Works; and
+
+ 4. If the Work includes a "NOTICE" text file as part of its distribution,
+ then any Derivative Works that You distribute must include a readable copy
+ of the attribution notices contained within such NOTICE file, excluding
+ those notices that do not pertain to any part of the Derivative Works,
+ in at least one of the following places: within a NOTICE text file
+ distributed as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or, within a
+ display generated by the Derivative Works, if and wherever such
+ third-party notices normally appear. The contents of the NOTICE file are
+ for informational purposes only and do not modify the License.
+ You may add Your own attribution notices within Derivative Works that You
+ distribute, alongside or as an addendum to the NOTICE text from the Work,
+ provided that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and may
+ provide additional or different license terms and conditions for use,
+ reproduction, or distribution of Your modifications, or for any such
+ Derivative Works as a whole, provided Your use, reproduction, and
+ distribution of the Work otherwise complies with the conditions
+ stated in this License.
+
+ 5. Submission of Contributions.
+
+ Unless You explicitly state otherwise, any Contribution intentionally
+ submitted for inclusion in the Work by You to the Licensor shall be under
+ the terms and conditions of this License, without any additional
+ terms or conditions. Notwithstanding the above, nothing herein shall
+ supersede or modify the terms of any separate license agreement you may
+ have executed with Licensor regarding such Contributions.
+
+ 6. Trademarks.
+
+ This License does not grant permission to use the trade names, trademarks,
+ service marks, or product names of the Licensor, except as required for
+ reasonable and customary use in describing the origin of the Work and
+ reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty.
+
+ Unless required by applicable law or agreed to in writing, Licensor
+ provides the Work (and each Contributor provides its Contributions)
+ on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
+ either express or implied, including, without limitation, any warranties
+ or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS
+ FOR A PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any risks
+ associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability.
+
+ In no event and under no legal theory, whether in tort
+ (including negligence), contract, or otherwise, unless required by
+ applicable law (such as deliberate and grossly negligent acts) or agreed
+ to in writing, shall any Contributor be liable to You for damages,
+ including any direct, indirect, special, incidental, or consequential
+ damages of any character arising as a result of this License or out of
+ the use or inability to use the Work (including but not limited to damages
+ for loss of goodwill, work stoppage, computer failure or malfunction,
+ or any and all other commercial damages or losses), even if such
+ Contributor has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability.
+
+ While redistributing the Work or Derivative Works thereof, You may choose
+ to offer, and charge a fee for, acceptance of support, warranty,
+ indemnity, or other liability obligations and/or rights consistent with
+ this License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf of any
+ other Contributor, and only if You agree to indemnify, defend, and hold
+ each Contributor harmless for any liability incurred by, or claims
+ asserted against, such Contributor by reason of your accepting any such
+ warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work
+
+ To apply the Apache License to your work, attach the following boilerplate
+ notice, with the fields enclosed by brackets "[]" replaced with your own
+ identifying information. (Don't include the brackets!) The text should be
+ enclosed in the appropriate comment syntax for the file format. We also
+ recommend that a file or class name and description of purpose be included
+ on the same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 ONNX-TF Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ or implied. See the License for the specific language governing
+ permissions and limitations under the License.
+
diff --git a/pt2tf/onnx-tensorflow/MANIFEST.in b/pt2tf/onnx-tensorflow/MANIFEST.in
new file mode 100644
index 0000000..ea4e78d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/MANIFEST.in
@@ -0,0 +1,3 @@
+include LICENSE
+include VERSION_NUMBER
+include ONNX_VERSION_NUMBER
diff --git a/pt2tf/onnx-tensorflow/ONNX_VERSION_NUMBER b/pt2tf/onnx-tensorflow/ONNX_VERSION_NUMBER
new file mode 100644
index 0000000..dc1e644
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/ONNX_VERSION_NUMBER
@@ -0,0 +1 @@
+1.6.0
diff --git a/pt2tf/onnx-tensorflow/README.md b/pt2tf/onnx-tensorflow/README.md
new file mode 100644
index 0000000..c909dd0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/README.md
@@ -0,0 +1,101 @@
+# Tensorflow Backend for ONNX
+[](https://travis-ci.org/onnx/onnx-tensorflow)
+
+## To convert models from ONNX to Tensorflow:
+
+### Use CLI:
+
+[Command Line Interface Documentation](https://github.com/onnx/onnx-tensorflow/blob/master/doc/CLI.md)
+
+From ONNX to Tensorflow: `onnx-tf convert -i /path/to/input.onnx -o /path/to/output.pb`
+
+### Convert programmatically:
+
+[From ONNX to Tensorflow](https://github.com/onnx/onnx-tensorflow/blob/master/example/onnx_to_tf.py)
+
+### Migrating from `onnx-tf` to `tf-onnx`:
+We have joined force with Microsoft to co-develop ONNX Tensorflow frontend.
+For current onnx-tf frontend users, please migrate to use tf-onnx (https://github.com/onnx/tensorflow-onnx) where our code had been merged into.
+
+## ONNX model inference with Tensorflow backend:
+```
+import onnx
+from onnx_tf.backend import prepare
+
+onnx_model = onnx.load("input_path") # load onnx model
+output = prepare(onnx_model).run(input) # run the loaded model
+```
+
+## More tutorials:
+[Running an ONNX model using Tensorflow](https://github.com/onnx/tutorials/blob/master/tutorials/OnnxTensorflowImport.ipynb)
+
+## Production Installation:
+ONNX-TF requires ONNX (Open Neural Network Exchange) as an external dependency, for any issues related to ONNX installation, we refer our users to [ONNX project repository](https://github.com/onnx/onnx) for documentation and help. Notably, please ensure that protoc is available if you plan to install ONNX via pip.
+
+The specific ONNX release version that we support in the master branch of ONNX-TF can be found [here](https://github.com/onnx/onnx-tensorflow/blob/master/ONNX_VERSION_NUMBER). This information about ONNX version requirement is automatically encoded in `setup.py`, therefore users needn't worry about ONNX version requirement when installing ONNX-TF.
+
+Because users often have their own preferences for which variant of Tensorflow to install (i.e., a GPU version instead of a CPU version), we do not explicitly require tensorflow in the installation script. It is therefore users' responsibility to ensure that the proper variant of Tensorflow is available to ONNX-TF. Moreover, we require Tensorflow version == 1.15.0.
+
+To install the latest version of ONNX-TF v1.6.0
+- Run `git clone https://github.com/onnx/onnx-tensorflow.git && cd onnx-tensorflow`.
+- Run `git checkout v1.6.0-tf-1.15`.
+- Run `pip install -e .`.
+
+## Development:
+
+### Coverage Status:
+[ONNX-Tensorflow Op Coverage Status](https://github.com/onnx/onnx-tensorflow/blob/tf-1.x/doc/support_status.md)
+
+### API:
+[ONNX-Tensorflow API](https://github.com/onnx/onnx-tensorflow/blob/tf-1.x/doc/API.md)
+
+### Installation:
+- Install ONNX master branch from source.
+- Install Tensorflow 1.15.0. (For Tensorflow 2.x support please refer [here](https://github.com/onnx/onnx-tensorflow/blob/master/README.md/).)
+- Run `git clone https://github.com/onnx/onnx-tensorflow.git && cd onnx-tensorflow`.
+- Run `git checkout tf-1.x`.
+- Run `pip install -e .`.
+
+### Folder Structure:
+- __onnx_tf__ main source code file.
+- __test__ test files.
+
+### Code Standard:
+- Format code:
+```
+pip install yapf
+yapf -rip --style="{based_on_style: google, indent_width: 2}" $FilePath$
+```
+- Install pylint:
+```
+pip install pylint
+wget -O /tmp/pylintrc https://raw.githubusercontent.com/tensorflow/tensorflow/master/tensorflow/tools/ci_build/pylintrc
+```
+- Check format:
+```
+pylint --rcfile=/tmp/pylintrc myfile.py
+```
+
+### Documentation Standard:
+http://sphinxcontrib-napoleon.readthedocs.io/en/latest/example_google.html
+
+### To test:
+To perfom unit tests, run `python -m unittest discover test`.
+Testing requires significant hardware resources, but nonetheless, we highly recommend that users run through the complete test suite before deploying onnx-tf. The complete test suite typically takes between 15 and 45 minutes to complete, depending on hardware configurations.
+
+PS. Please ensure your code is backward compatible with older version of ONNX. You can easily test it by running the following [docker container](https://hub.docker.com/r/winnietsang/onnx-tensorflow) with your code. If you don't have Docker installed yet, please follow this link to install [Docker](https://docs.docker.com/install/) on your environment.
+```
+sudo docker pull winnietsang/onnx-tensorflow:onnx1.7.0-tf1.15
+sudo docker run -it --name=YOUR-CONTAINER-NAME winnietsang/onnx-tensorflow:onnx1.7.0-tf1.15 /bin/bash
+git clone https://github.com/YOUR-USERNAME/onnx-tensorflow.git
+cd onnx-tensorflow
+git checkout -b YOUR-BRANCH --track remotes/origin/YOUR-BRANCH
+pip3 install -e .
+python3 -m unittest discover test
+```
+
+#### Test Help:
+https://docs.python.org/2/library/unittest.html
+
+#### Note:
+Branch tf-1.x is for users who cannot upgrade to Tensorflow 2.x yet. This branch will only support up to ONNX OpSet 12 operators. If any user needs to use operators in OpSet 13 or above, please upgrade Tensoflow to 2.x and use the master branch of this repo. By January 1st, 2021 this branch will switch to maintenance mode only, no new development will be added into this branch from then on.
diff --git a/pt2tf/onnx-tensorflow/VERSION_NUMBER b/pt2tf/onnx-tensorflow/VERSION_NUMBER
new file mode 100644
index 0000000..dc1e644
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/VERSION_NUMBER
@@ -0,0 +1 @@
+1.6.0
diff --git a/pt2tf/onnx-tensorflow/Versioning.md b/pt2tf/onnx-tensorflow/Versioning.md
new file mode 100644
index 0000000..b60e8d5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/Versioning.md
@@ -0,0 +1,10 @@
+## Released Versions
+
+These are the supported ONNX versions and TensorFlow versions for each release.
+
+ONNX-TensorFlow version|ONNX version|TensorFlow version
+-----------------------|------------|------------------
+1.2.1|1.1.2 (opset 4)|1.5
+1.3.0|1.3.0 (opset 8)|1.13.1
+1.5.0|1.5.0 (opset 10)|1.15.0
+1.6.0|1.6.0 (opset 11)|1.15.0
diff --git a/pt2tf/onnx-tensorflow/doc/API.md b/pt2tf/onnx-tensorflow/doc/API.md
new file mode 100644
index 0000000..7063643
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/API.md
@@ -0,0 +1,62 @@
+ONNX-Tensorflow API
+======
+
+#### `onnx_tf.backend.prepare`
+
+
+ Prepare an ONNX model for Tensorflow Backend.
+
+
+This function converts an ONNX model to an internel representation
+of the computational graph called TensorflowRep and returns
+the converted representation.
+
+
+
+
+
+_params_:
+
+`model` : The ONNX model to be converted.
+
+
+`device` : The device to execute this model on.
+
+
+`strict` : Whether to enforce semantic equivalence between the original model
+and the converted tensorflow model, defaults to True (yes, enforce semantic equivalence).
+Changing to False is strongly discouraged.
+Currently, the strict flag only affects the behavior of MaxPool and AveragePool ops.
+
+
+`logging_level` : The logging level, default is INFO. Change it to DEBUG
+to see more conversion details or to WARNING to see less
+
+
+_returns_:
+
+A TensorflowRep class object representing the ONNX model
+
+#### `onnx_tf.backend_rep.TensorflowRep.export_graph`
+
+
+ Export backend representation to a Tensorflow proto file.
+
+
+This function obtains the graph proto corresponding to the ONNX
+model associated with the backend representation and serializes
+to a protobuf file.
+
+
+
+
+
+_params_:
+
+`path` : The path to the output TF protobuf file.
+
+
+_returns_:
+
+none.
+
diff --git a/pt2tf/onnx-tensorflow/doc/CLI.md b/pt2tf/onnx-tensorflow/doc/CLI.md
new file mode 100644
index 0000000..1a7c57a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/CLI.md
@@ -0,0 +1,54 @@
+ONNX-Tensorflow Command Line Interface
+======
+
+## Available commands:
+- convert
+
+More information: `onnx-tf -h`
+```
+usage: onnx-tf [-h] {convert}
+
+ONNX-Tensorflow Command Line Interface
+
+positional arguments:
+ {convert} Available commands.
+
+optional arguments:
+ -h, --help show this help message and exit
+```
+
+## Usage:
+
+### Convert:
+
+#### From ONNX to Tensorflow:
+`onnx-tf convert -i /path/to/input.onnx -o /path/to/output.pb`
+
+More information: `onnx-tf convert -h`
+```
+usage: onnx-tf [-h] --infile INFILE --outfile OUTFILE [--device DEVICE]
+ [--strict STRICT] [--logging_level LOGGING_LEVEL]
+
+This is the converter for converting protocol buffer between tf and onnx.
+
+optional arguments:
+ -h, --help show this help message and exit
+ --infile INFILE, -i INFILE
+ Input file path.
+ --outfile OUTFILE, -o OUTFILE
+ Output file path.
+
+backend arguments (onnx -> tf):
+ --device DEVICE The device to execute this model on. (from
+ onnx_tf.backend.prepare)
+ --strict STRICT Whether to enforce semantic equivalence between the
+ original model and the converted tensorflow model,
+ defaults to True (yes, enforce semantic equivalence).
+ Changing to False is strongly discouraged. Currently,
+ the strict flag only affects the behavior of MaxPool
+ and AveragePool ops. (from onnx_tf.backend.prepare)
+ --logging_level LOGGING_LEVEL
+ The logging level, default is INFO. Change it to DEBUG
+ to see more conversion details or to WARNING to see
+ less (from onnx_tf.backend.prepare)
+```
diff --git a/pt2tf/onnx-tensorflow/doc/CLI_template.md b/pt2tf/onnx-tensorflow/doc/CLI_template.md
new file mode 100644
index 0000000..dd5e54f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/CLI_template.md
@@ -0,0 +1,22 @@
+ONNX-Tensorflow Command Line Interface
+======
+
+## Available commands:
+- convert
+
+More information: `onnx-tf -h`
+```
+{onnx-tf -h}
+```
+
+## Usage:
+
+### Convert:
+
+#### From ONNX to Tensorflow:
+`onnx-tf convert -i /path/to/input.onnx -o /path/to/output.pb`
+
+More information: `onnx-tf convert -h`
+```
+{onnx-tf convert -h}
+```
diff --git a/pt2tf/onnx-tensorflow/doc/IMPLEMENTING_NEW_OP.md b/pt2tf/onnx-tensorflow/doc/IMPLEMENTING_NEW_OP.md
new file mode 100644
index 0000000..6491727
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/IMPLEMENTING_NEW_OP.md
@@ -0,0 +1,22 @@
+How to implement new op
+======
+
+When you get `{} op is not implemented`, you can follow next steps to implement.
+Customize op can also be implemented in similar way.
+
+### Backend
+
+1. Verify the latest master version of ONNX is installed on your environment
+2. Find specification from [onnx/Operators](https://github.com/onnx/onnx/blob/master/docs/Operators.md).
+3. Implement the handler. All inputs and attrs could get from step 2.
+ ```
+ - add handler to /onnx_tf/handlers/backend/
+ - in the new handler define a classmethod called version_{version}
+
+ * version is the number of since version, which can get from operator's specification
+ ```
+4. From within the `onnx_tf` directory, run `gen_opset.py`.
+5. From within the `onnx_tf` directory, run `gen_status.py`.
+6. From within the `onnx_tf` directory, run `gen_doc.py` if there is any update to CLI or API.
+7. Verify the operator's test cases in `test/backend/test_onnx_backend.py` all pass.
+8. Add any additional test cases to `test/backend/test_node.py`.
diff --git a/pt2tf/onnx-tensorflow/doc/support_status.md b/pt2tf/onnx-tensorflow/doc/support_status.md
new file mode 100644
index 0000000..e3024c9
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/support_status.md
@@ -0,0 +1,209 @@
+# ONNX-Tensorflow Support Status
+|||
+|-:|:-|
+|ONNX-Tensorflow Version|Master ( commit id: 8fea59a976e2d65eab2ab021864e2cab038bb7d5 )|
+|ONNX Version|v1.7.0|
+|Tensorflow Version|v1.15.0|
+
+Notes:
+* Values that are new or updated from a previous opset version are in bold.
+* -: not defined in corresponding ONNX opset version
+* \*: the operator is deprecated
+* :small_red_triangle:: not supported yet
+* :small_orange_diamond:: partially supported
+* the rest are all supported
+
+|||||||||||||||
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+|**ONNX Operator**|**Opset 1**|**Opset 2**|**Opset 3**|**Opset 4**|**Opset 5**|**Opset 6**|**Opset 7**|**Opset 8**|**Opset 9**|**Opset 10**|**Opset 11**|**Opset 12**|**ONNX Operator**|
+|Abs|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Abs|
+|Acos|-|-|-|-|-|-|**7**|7|7|7|7|7|Acos|
+|Acosh|-|-|-|-|-|-|-|-|**9**|9|9|9|Acosh|
+|Add|**1**|1|1|1|1|**6**|**7**|7|7|7|7|7|Add|
+|And|**1**|1|1|1|1|1|**7**|7|7|7|7|7|And|
+|ArgMax|**1**|1|1|1|1|1|1|1|1|1|**11**|**12**|ArgMax|
+|ArgMin|**1**|1|1|1|1|1|1|1|1|1|**11**|**12**|ArgMin|
+|Asin|-|-|-|-|-|-|**7**|7|7|7|7|7|Asin|
+|Asinh|-|-|-|-|-|-|-|-|**9**|9|9|9|Asinh|
+|Atan|-|-|-|-|-|-|**7**|7|7|7|7|7|Atan|
+|Atanh|-|-|-|-|-|-|-|-|**9**|9|9|9|Atanh|
+|AveragePool|**1**|1|1|1|1|1|**7**|7|7|**10**|**11**|11|AveragePool|
+|BatchNormalization|**1**|1|1|1|1|**6**|**7**|7|**9**|9|9|9|BatchNormalization|
+|BitShift|-|-|-|-|-|-|-|-|-|-|**11**|11|BitShift|
+|Cast|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**6**:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|**9**:small_orange_diamond:|9:small_orange_diamond:|9:small_orange_diamond:|9:small_orange_diamond:|Cast|
+|Ceil|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Ceil|
+|Celu|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|Celu|
+|Clip|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**6**:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|**11**:small_orange_diamond:|**12**:small_orange_diamond:|Clip|
+|Compress|-|-|-|-|-|-|-|-|**9**|9|**11**|11|Compress|
+|Concat|**1**|1|1|**4**|4|4|4|4|4|4|**11**|11|Concat|
+|ConcatFromSequence|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|11:small_red_triangle:|ConcatFromSequence|
+|Constant|**1**|1|1|1|1|1|1|1|**9**|9|**11**|**12**|Constant|
+|ConstantOfShape|-|-|-|-|-|-|-|-|**9**|9|9|9|ConstantOfShape|
+|Conv|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Conv|
+|ConvInteger|-|-|-|-|-|-|-|-|-|**10**|10|10|ConvInteger|
+|ConvTranspose|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**11**:small_orange_diamond:|11:small_orange_diamond:|ConvTranspose|
+|Cos|-|-|-|-|-|-|**7**|7|7|7|7|7|Cos|
+|Cosh|-|-|-|-|-|-|-|-|**9**|9|9|9|Cosh|
+|CumSum|-|-|-|-|-|-|-|-|-|-|**11**:small_orange_diamond:|11:small_orange_diamond:|CumSum|
+|DepthToSpace|**1**|1|1|1|1|1|1|1|1|1|**11**|11|DepthToSpace|
+|DequantizeLinear|-|-|-|-|-|-|-|-|-|**10**|10|10|DequantizeLinear|
+|Det|-|-|-|-|-|-|-|-|-|-|**11**|11|Det|
+|Div|**1**|1|1|1|1|**6**|**7**|7|7|7|7|7|Div|
+|Dropout|**1**|1|1|1|1|**6**|**7**|7|7|**10**|10|**12**:small_red_triangle:|Dropout|
+|DynamicQuantizeLinear|-|-|-|-|-|-|-|-|-|-|**11**|11|DynamicQuantizeLinear|
+|Einsum|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|Einsum|
+|Elu|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Elu|
+|Equal|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|**11**:small_orange_diamond:|11:small_orange_diamond:|Equal|
+|Erf|-|-|-|-|-|-|-|-|**9**|9|9|9|Erf|
+|Exp|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Exp|
+|Expand|-|-|-|-|-|-|-|**8**|8|8|8|8|Expand|
+|EyeLike|-|-|-|-|-|-|-|-|**9**|9|9|9|EyeLike|
+|Flatten|**1**|1|1|1|1|1|1|1|**9**|9|**11**|11|Flatten|
+|Floor|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Floor|
+|GRU|**1**:small_orange_diamond:|1:small_orange_diamond:|**3**:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|GRU|
+|Gather|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Gather|
+|GatherElements|-|-|-|-|-|-|-|-|-|-|**11**|11|GatherElements|
+|GatherND|-|-|-|-|-|-|-|-|-|-|**11**|**12**:small_red_triangle:|GatherND|
+|Gemm|**1**|1|1|1|1|**6**|**7**|7|**9**|9|**11**|11|Gemm|
+|GlobalAveragePool|**1**|1|1|1|1|1|1|1|1|1|1|1|GlobalAveragePool|
+|GlobalLpPool|**1**|**2**|2|2|2|2|2|2|2|2|2|2|GlobalLpPool|
+|GlobalMaxPool|**1**|1|1|1|1|1|1|1|1|1|1|1|GlobalMaxPool|
+|Greater|**1**|1|1|1|1|1|**7**|7|**9**|9|9|9|Greater|
+|GreaterOrEqual|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|GreaterOrEqual|
+|HardSigmoid|**1**|1|1|1|1|**6**|6|6|6|6|6|6|HardSigmoid|
+|Hardmax|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Hardmax|
+|Identity|**1**|1|1|1|1|1|1|1|1|1|1|1|Identity|
+|If|**1**|1|1|1|1|1|1|1|1|1|**11**|11|If|
+|InstanceNormalization|**1**|1|1|1|1|**6**|6|6|6|6|6|6|InstanceNormalization|
+|IsInf|-|-|-|-|-|-|-|-|-|**10**|10|10|IsInf|
+|IsNaN|-|-|-|-|-|-|-|-|**9**|9|9|9|IsNaN|
+|LRN|**1**|1|1|1|1|1|1|1|1|1|1|1|LRN|
+|LSTM|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|LSTM|
+|LeakyRelu|**1**|1|1|1|1|**6**|6|6|6|6|6|6|LeakyRelu|
+|Less|**1**|1|1|1|1|1|**7**|7|**9**|9|9|9|Less|
+|LessOrEqual|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|LessOrEqual|
+|Log|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Log|
+|LogSoftmax|**1**|1|1|1|1|1|1|1|1|1|**11**|11|LogSoftmax|
+|Loop|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Loop|
+|LpNormalization|**1**|1|1|1|1|1|1|1|1|1|1|1|LpNormalization|
+|LpPool|**1**|**2**|2|2|2|2|2|2|2|2|**11**|11|LpPool|
+|MatMul|**1**|1|1|1|1|1|1|1|**9**|9|9|9|MatMul|
+|MatMulInteger|-|-|-|-|-|-|-|-|-|**10**|10|10|MatMulInteger|
+|Max|**1**|1|1|1|1|**6**|6|**8**|8|8|8|**12**:small_red_triangle:|Max|
+|MaxPool|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**8**:small_orange_diamond:|8:small_orange_diamond:|**10**:small_orange_diamond:|**11**:small_orange_diamond:|**12**:small_orange_diamond:|MaxPool|
+|MaxRoiPool|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|MaxRoiPool|
+|MaxUnpool|-|-|-|-|-|-|-|-|**9**|9|**11**|11|MaxUnpool|
+|Mean|**1**|1|1|1|1|**6**|6|**8**|8|8|8|8|Mean|
+|MeanVarianceNormalization|-|-|-|-|-|-|-|-|**9**|9|9|9|MeanVarianceNormalization|
+|Min|**1**|1|1|1|1|**6**|6|**8**|8|8|8|**12**:small_red_triangle:|Min|
+|Mod|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|10:small_orange_diamond:|10:small_orange_diamond:|Mod|
+|Mul|**1**|1|1|1|1|**6**|**7**|7|7|7|7|7|Mul|
+|Multinomial|-|-|-|-|-|-|**7**:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|Multinomial|
+|Neg|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Neg|
+|NegativeLogLikelihoodLoss|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|NegativeLogLikelihoodLoss|
+|NonMaxSuppression|-|-|-|-|-|-|-|-|-|**10**|**11**|11|NonMaxSuppression|
+|NonZero|-|-|-|-|-|-|-|-|**9**|9|9|9|NonZero|
+|Not|**1**|1|1|1|1|1|1|1|1|1|1|1|Not|
+|OneHot|-|-|-|-|-|-|-|-|**9**:small_orange_diamond:|9:small_orange_diamond:|**11**:small_orange_diamond:|11:small_orange_diamond:|OneHot|
+|Or|**1**|1|1|1|1|1|**7**|7|7|7|7|7|Or|
+|PRelu|**1**|1|1|1|1|**6**|**7**|7|**9**|9|9|9|PRelu|
+|Pad|**1**|**2**|2|2|2|2|2|2|2|2|**11**|11|Pad|
+|Pow|**1**|1|1|1|1|1|**7**|7|7|7|7|**12**:small_red_triangle:|Pow|
+|QLinearConv|-|-|-|-|-|-|-|-|-|**10**|10|10|QLinearConv|
+|QLinearMatMul|-|-|-|-|-|-|-|-|-|**10**|10|10|QLinearMatMul|
+|QuantizeLinear|-|-|-|-|-|-|-|-|-|**10**|10|10|QuantizeLinear|
+|RNN|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|RNN|
+|RandomNormal|**1**|1|1|1|1|1|1|1|1|1|1|1|RandomNormal|
+|RandomNormalLike|**1**|1|1|1|1|1|1|1|1|1|1|1|RandomNormalLike|
+|RandomUniform|**1**|1|1|1|1|1|1|1|1|1|1|1|RandomUniform|
+|RandomUniformLike|**1**|1|1|1|1|1|1|1|1|1|1|1|RandomUniformLike|
+|Range|-|-|-|-|-|-|-|-|-|-|**11**|11|Range|
+|Reciprocal|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Reciprocal|
+|ReduceL1|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceL1|
+|ReduceL2|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceL2|
+|ReduceLogSum|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceLogSum|
+|ReduceLogSumExp|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceLogSumExp|
+|ReduceMax|**1**|1|1|1|1|1|1|1|1|1|**11**|**12**|ReduceMax|
+|ReduceMean|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceMean|
+|ReduceMin|**1**|1|1|1|1|1|1|1|1|1|**11**|**12**|ReduceMin|
+|ReduceProd|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceProd|
+|ReduceSum|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceSum|
+|ReduceSumSquare|**1**|1|1|1|1|1|1|1|1|1|**11**|11|ReduceSumSquare|
+|Relu|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Relu|
+|Reshape|**1**|1|1|1|**5**|5|5|5|5|5|5|5|Reshape|
+|Resize|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|**11**:small_orange_diamond:|11:small_orange_diamond:|Resize|
+|ReverseSequence|-|-|-|-|-|-|-|-|-|**10**|10|10|ReverseSequence|
+|RoiAlign|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|10:small_red_triangle:|10:small_red_triangle:|RoiAlign|
+|Round|-|-|-|-|-|-|-|-|-|-|**11**|11|Round|
+|Scan|-|-|-|-|-|-|-|**8**|**9**|9|**11**|11|Scan|
+|Scatter|-|-|-|-|-|-|-|-|**9**|9|**11**\*|11\*|Scatter|
+|ScatterElements|-|-|-|-|-|-|-|-|-|-|**11**|11|ScatterElements|
+|ScatterND|-|-|-|-|-|-|-|-|-|-|**11**|11|ScatterND|
+|Selu|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Selu|
+|SequenceAt|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceAt|
+|SequenceConstruct|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceConstruct|
+|SequenceEmpty|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceEmpty|
+|SequenceErase|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceErase|
+|SequenceInsert|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceInsert|
+|SequenceLength|-|-|-|-|-|-|-|-|-|-|**11**|11|SequenceLength|
+|Shape|**1**|1|1|1|1|1|1|1|1|1|1|1|Shape|
+|Shrink|-|-|-|-|-|-|-|-|**9**|9|9|9|Shrink|
+|Sigmoid|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Sigmoid|
+|Sign|-|-|-|-|-|-|-|-|**9**|9|9|9|Sign|
+|Sin|-|-|-|-|-|-|**7**|7|7|7|7|7|Sin|
+|Sinh|-|-|-|-|-|-|-|-|**9**|9|9|9|Sinh|
+|Size|**1**|1|1|1|1|1|1|1|1|1|1|1|Size|
+|Slice|**1**|1|1|1|1|1|1|1|1|**10**|**11**|11|Slice|
+|Softmax|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Softmax|
+|SoftmaxCrossEntropyLoss|-|-|-|-|-|-|-|-|-|-|-|**12**:small_red_triangle:|SoftmaxCrossEntropyLoss|
+|Softplus|**1**|1|1|1|1|1|1|1|1|1|1|1|Softplus|
+|Softsign|**1**|1|1|1|1|1|1|1|1|1|1|1|Softsign|
+|SpaceToDepth|**1**|1|1|1|1|1|1|1|1|1|1|1|SpaceToDepth|
+|Split|**1**|**2**|2|2|2|2|2|2|2|2|**11**|11|Split|
+|SplitToSequence|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|11:small_red_triangle:|SplitToSequence|
+|Sqrt|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Sqrt|
+|Squeeze|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Squeeze|
+|StringNormalizer|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|10:small_red_triangle:|10:small_red_triangle:|StringNormalizer|
+|Sub|**1**|1|1|1|1|**6**|**7**|7|7|7|7|7|Sub|
+|Sum|**1**|1|1|1|1|**6**|6|**8**|8|8|8|8|Sum|
+|Tan|-|-|-|-|-|-|**7**|7|7|7|7|7|Tan|
+|Tanh|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Tanh|
+|TfIdfVectorizer|-|-|-|-|-|-|-|-|**9**|9|9|9|TfIdfVectorizer|
+|ThresholdedRelu|-|-|-|-|-|-|-|-|-|**10**|10|10|ThresholdedRelu|
+|Tile|**1**|1|1|1|1|**6**|6|6|6|6|6|6|Tile|
+|TopK|**1**|1|1|1|1|1|1|1|1|**10**|**11**|11|TopK|
+|Transpose|**1**|1|1|1|1|1|1|1|1|1|1|1|Transpose|
+|Unique|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|11:small_red_triangle:|Unique|
+|Unsqueeze|**1**|1|1|1|1|1|1|1|1|1|**11**|11|Unsqueeze|
+|Upsample|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|**7**:small_orange_diamond:|7:small_orange_diamond:|**9**:small_orange_diamond:|**10**\*|10\*|10\*|Upsample|
+|Where|-|-|-|-|-|-|-|-|**9**|9|9|9|Where|
+|Xor|**1**|1|1|1|1|1|**7**|7|7|7|7|7|Xor|
+
+ONNX-TF Supported Operators / ONNX Operators: 144 / 162
+
+Notes:
+1. Cast: Cast string to float32/float64/int32/int64 are not supported in Tensorflow.
+2. Clip: Clip input in uint64 is not supported in Tensorflow.
+3. ConvTranspose: ConvTranspose with dilations != 1, or transposed convolution for 4D or higher are not supported in Tensorflow.
+4. CumSum: CumSum inputs in uint32/uint64 are not supported in Tensorflow.
+5. Equal: Equal inputs in uint16/uint32/uint64 are not supported in Tensorflow.
+6. GRU: GRU with clip or GRU with linear_before_reset, or GRU not using sigmoid for z and r, or GRU using Elu as the activation function with alpha != 1, or GRU using HardSigmoid as the activation function with alpha != 0.2 or beta != 0.5 are not supported in TensorFlow.
+7. LSTM: LSTM not using sigmoid for `f`, or LSTM not using the same activation for `g` and `h` are not supported in Tensorflow.
+8. MaxPool: MaxPoolWithArgmax with pad is None or incompatible mode, or MaxPoolWithArgmax with 4D or higher input, orMaxPoolWithArgmax with column major are not supported in Tensorflow.
+9. Mod: Mod Dividend or Divisor in int8/int16/uint8/uint16/uint32/uint64 are not supported in Tensorflow.
+10. OneHot: OneHot indices in uint16/uint32/uint64/int8/int16/float16/float/double, or OneHot depth in uint8/uint16/uint32/uint64/int8/int16/int64/float16/float/double are not supported in Tensorflow.
+11. RNN: RNN with clip is not supported in Tensorflow.
+12. Resize: Resize required 4D input in Tensorflow. For opset 11, only the following attributes and inputs conbination are supported in Tensorflow:
+ 1. mode=nearest, coordinate_transformation_mode=align_corners, nearest_mode=round_prefer_ceil, can use scales(*) or sizes.
+ 2. mode=nearest, coordinate_transformation_mode=asymmetric, nearest_mode=floor, can use scales(*) or sizes.
+ 3. mode=nearest, coordinate_transformation_mode=tf_half_pixel_for_nn, nearest_mode=floor, can use scales(*) or sizes.
+ 4. mode=linear, coordinate_transformation_mode=align_corners, can use scales(*) or sizes.
+ 5. mode=linear, coordinate_transformation_mode=asymmetric, can use scales(*) or sizes.
+ 6. mode=linear, coordinate_transformation_mode=half_pixel, can use scales(*) or sizes.
+ 7. mode=cubic, coordinate_transformation_mode=align_corners, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 8. mode=cubic, coordinate_transformation_mode=asymmetric, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 9. mode=cubic, coordinate_transformation_mode=half_pixel, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 10. mode=nearest, coordinate_transformation_mode=tf_crop_and_resize, extrapolation_value=any_float_value, nearest_mode=round_prefer_ceil, can use scales or sizes.
+ 11. mode=linear, coordinate_transformation_mode=tf_crop_and_resize, extrapolation_value=any_float_value, can use scales or sizes.
+ - Note (*): The accuracy of your model will go down, if the height and the width of the new sizes(scales * origial sizes) are not in whole numbers.
+13. Upsample: Upsample required 4D input in Tensorflow.
diff --git a/pt2tf/onnx-tensorflow/doc/support_status_v1_5_0.md b/pt2tf/onnx-tensorflow/doc/support_status_v1_5_0.md
new file mode 100644
index 0000000..d3b2bd6
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/support_status_v1_5_0.md
@@ -0,0 +1,170 @@
+# ONNX-Tensorflow Support Status
+|||
+|-:|:-|
+|ONNX-Tensorflow Version|v1.5.0|
+|ONNX Version|v1.5.0|
+|Tensorflow Version|v1.15.0|
+
+Notes:
+* Values that are new or updated from a previous opset version are in bold.
+* -: not defined in corresponding ONNX opset version
+* \*: the operator is deprecated
+* :small_red_triangle:: not supported yet
+* :small_orange_diamond:: partially supported
+* the rest are all supported
+
+||||||||||||
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+|**ONNX Operator**|**Opset 1**|**Opset 2**|**Opset 3**|**Opset 4**|**Opset 5**|**Opset 6**|**Opset 7**|**Opset 8**|**Opset 9**|**Opset 10**|
+|Abs|**1**|1|1|1|1|**6**|6|6|6|6|
+|Acos|-|-|-|-|-|-|**7**|7|7|7|
+|Acosh|-|-|-|-|-|-|-|-|**9**|9|
+|Add|**1**|1|1|1|1|**6**|**7**|7|7|7|
+|And|**1**|1|1|1|1|1|**7**|7|7|7|
+|ArgMax|**1**|1|1|1|1|1|1|1|1|1|
+|ArgMin|**1**|1|1|1|1|1|1|1|1|1|
+|Asin|-|-|-|-|-|-|**7**|7|7|7|
+|Asinh|-|-|-|-|-|-|-|-|**9**|9|
+|Atan|-|-|-|-|-|-|**7**|7|7|7|
+|Atanh|-|-|-|-|-|-|-|-|**9**|9|
+|AveragePool|**1**|1|1|1|1|1|**7**|7|7|**10**|
+|BatchNormalization|**1**|1|1|1|1|**6**|**7**|7|**9**|9|
+|Cast|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**6**:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|**9**:small_orange_diamond:|9:small_orange_diamond:|
+|Ceil|**1**|1|1|1|1|**6**|6|6|6|6|
+|Clip|**1**|1|1|1|1|**6**|6|6|6|6|
+|Compress|-|-|-|-|-|-|-|-|**9**|9|
+|Concat|**1**|1|1|**4**|4|4|4|4|4|4|
+|Constant|**1**|1|1|1|1|1|1|1|**9**|9|
+|ConstantOfShape|-|-|-|-|-|-|-|-|**9**|9|
+|Conv|**1**|1|1|1|1|1|1|1|1|1|
+|ConvInteger|-|-|-|-|-|-|-|-|-|**10**|
+|ConvTranspose|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|
+|Cos|-|-|-|-|-|-|**7**|7|7|7|
+|Cosh|-|-|-|-|-|-|-|-|**9**|9|
+|DepthToSpace|**1**|1|1|1|1|1|1|1|1|1|
+|DequantizeLinear|-|-|-|-|-|-|-|-|-|**10**|
+|Div|**1**|1|1|1|1|**6**|**7**|7|7|7|
+|Dropout|**1**|1|1|1|1|**6**|**7**|7|7|**10**|
+|Elu|**1**|1|1|1|1|**6**|6|6|6|6|
+|Equal|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|
+|Erf|-|-|-|-|-|-|-|-|**9**|9|
+|Exp|**1**|1|1|1|1|**6**|6|6|6|6|
+|Expand|-|-|-|-|-|-|-|**8**|8|8|
+|EyeLike|-|-|-|-|-|-|-|-|**9**|9|
+|Flatten|**1**|1|1|1|1|1|1|1|**9**|9|
+|Floor|**1**|1|1|1|1|**6**|6|6|6|6|
+|GRU|**1**:small_orange_diamond:|1:small_orange_diamond:|**3**:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|
+|Gather|**1**|1|1|1|1|1|1|1|1|1|
+|Gemm|**1**|1|1|1|1|**6**|**7**|7|**9**|9|
+|GlobalAveragePool|**1**|1|1|1|1|1|1|1|1|1|
+|GlobalLpPool|**1**|**2**|2|2|2|2|2|2|2|2|
+|GlobalMaxPool|**1**|1|1|1|1|1|1|1|1|1|
+|Greater|**1**|1|1|1|1|1|**7**|7|**9**|9|
+|HardSigmoid|**1**|1|1|1|1|**6**|6|6|6|6|
+|Hardmax|**1**|1|1|1|1|1|1|1|1|1|
+|Identity|**1**|1|1|1|1|1|1|1|1|1|
+|If|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|
+|InstanceNormalization|**1**|1|1|1|1|**6**|6|6|6|6|
+|IsInf|-|-|-|-|-|-|-|-|-|**10**|
+|IsNaN|-|-|-|-|-|-|-|-|**9**|9|
+|LRN|**1**|1|1|1|1|1|1|1|1|1|
+|LSTM|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|
+|LeakyRelu|**1**|1|1|1|1|**6**|6|6|6|6|
+|Less|**1**|1|1|1|1|1|**7**|7|**9**|9|
+|Log|**1**|1|1|1|1|**6**|6|6|6|6|
+|LogSoftmax|**1**|1|1|1|1|1|1|1|1|1|
+|Loop|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|
+|LpNormalization|**1**|1|1|1|1|1|1|1|1|1|
+|LpPool|**1**:small_red_triangle:|**2**:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|2:small_red_triangle:|
+|MatMul|**1**|1|1|1|1|1|1|1|**9**|9|
+|MatMulInteger|-|-|-|-|-|-|-|-|-|**10**|
+|Max|**1**|1|1|1|1|**6**|6|**8**|8|8|
+|MaxPool|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**8**:small_orange_diamond:|8:small_orange_diamond:|**10**:small_orange_diamond:|
+|MaxRoiPool|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|
+|MaxUnpool|-|-|-|-|-|-|-|-|**9**|9|
+|Mean|**1**|1|1|1|1|**6**|6|**8**|8|8|
+|MeanVarianceNormalization|-|-|-|-|-|-|-|-|**9**|9|
+|Min|**1**|1|1|1|1|**6**|6|**8**|8|8|
+|Mod|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|
+|Mul|**1**|1|1|1|1|**6**|**7**|7|7|7|
+|Multinomial|-|-|-|-|-|-|**7**:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|
+|Neg|**1**|1|1|1|1|**6**|6|6|6|6|
+|NonMaxSuppression|-|-|-|-|-|-|-|-|-|**10**|
+|NonZero|-|-|-|-|-|-|-|-|**9**|9|
+|Not|**1**|1|1|1|1|1|1|1|1|1|
+|OneHot|-|-|-|-|-|-|-|-|**9**:small_orange_diamond:|9:small_orange_diamond:|
+|Or|**1**|1|1|1|1|1|**7**|7|7|7|
+|PRelu|**1**|1|1|1|1|**6**|**7**|7|**9**|9|
+|Pad|**1**|**2**|2|2|2|2|2|2|2|2|
+|Pow|**1**|1|1|1|1|1|**7**|7|7|7|
+|QLinearConv|-|-|-|-|-|-|-|-|-|**10**|
+|QLinearMatMul|-|-|-|-|-|-|-|-|-|**10**|
+|QuantizeLinear|-|-|-|-|-|-|-|-|-|**10**|
+|RNN|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|
+|RandomNormal|**1**|1|1|1|1|1|1|1|1|1|
+|RandomNormalLike|**1**|1|1|1|1|1|1|1|1|1|
+|RandomUniform|**1**|1|1|1|1|1|1|1|1|1|
+|RandomUniformLike|**1**|1|1|1|1|1|1|1|1|1|
+|Reciprocal|**1**|1|1|1|1|**6**|6|6|6|6|
+|ReduceL1|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceL2|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceLogSum|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceLogSumExp|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceMax|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceMean|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceMin|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceProd|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceSum|**1**|1|1|1|1|1|1|1|1|1|
+|ReduceSumSquare|**1**|1|1|1|1|1|1|1|1|1|
+|Relu|**1**|1|1|1|1|**6**|6|6|6|6|
+|Reshape|**1**|1|1|1|**5**|5|5|5|5|5|
+|Resize|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|
+|ReverseSequence|-|-|-|-|-|-|-|-|-|**10**|
+|RoiAlign|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|
+|Scan|-|-|-|-|-|-|-|**8**|**9**|9|
+|Scatter|-|-|-|-|-|-|-|-|**9**|9|
+|Selu|**1**|1|1|1|1|**6**|6|6|6|6|
+|Shape|**1**|1|1|1|1|1|1|1|1|1|
+|Shrink|-|-|-|-|-|-|-|-|**9**|9|
+|Sigmoid|**1**|1|1|1|1|**6**|6|6|6|6|
+|Sign|-|-|-|-|-|-|-|-|**9**|9|
+|Sin|-|-|-|-|-|-|**7**|7|7|7|
+|Sinh|-|-|-|-|-|-|-|-|**9**|9|
+|Size|**1**|1|1|1|1|1|1|1|1|1|
+|Slice|**1**|1|1|1|1|1|1|1|1|**10**|
+|Softmax|**1**|1|1|1|1|1|1|1|1|1|
+|Softplus|**1**|1|1|1|1|1|1|1|1|1|
+|Softsign|**1**|1|1|1|1|1|1|1|1|1|
+|SpaceToDepth|**1**|1|1|1|1|1|1|1|1|1|
+|Split|**1**|**2**|2|2|2|2|2|2|2|2|
+|Sqrt|**1**|1|1|1|1|**6**|6|6|6|6|
+|Squeeze|**1**|1|1|1|1|1|1|1|1|1|
+|StringNormalizer|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|
+|Sub|**1**|1|1|1|1|**6**|**7**|7|7|7|
+|Sum|**1**|1|1|1|1|**6**|6|**8**|8|8|
+|Tan|-|-|-|-|-|-|**7**|7|7|7|
+|Tanh|**1**|1|1|1|1|**6**|6|6|6|6|
+|TfIdfVectorizer|-|-|-|-|-|-|-|-|**9**|9|
+|ThresholdedRelu|-|-|-|-|-|-|-|-|-|**10**|
+|Tile|**1**|1|1|1|1|**6**|6|6|6|6|
+|TopK|**1**|1|1|1|1|1|1|1|1|**10**|
+|Transpose|**1**|1|1|1|1|1|1|1|1|1|
+|Unsqueeze|**1**|1|1|1|1|1|1|1|1|1|
+|Upsample|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|**7**:small_orange_diamond:|7:small_orange_diamond:|**9**:small_orange_diamond:|**10**\*|
+|Where|-|-|-|-|-|-|-|-|**9**|9|
+|Xor|**1**|1|1|1|1|1|**7**|7|7|7|
+
+ONNX-TF Supported Operators / ONNX Operators: 130 / 137
+
+Notes:
+1. Cast: Cast string to float32/float64/int32/int64 are not supported in Tensorflow.
+2. ConvTranspose: ConvTranspose with dilations != 1, or transposed convolution for 4D or higher are not supported in Tensorflow.
+3. Equal: Equal inputs in uint16/uint32/uint64 are not supported in Tensorflow.
+4. GRU: GRU with clip or GRU with linear_before_reset, or GRU not using sigmoid for z and r, or GRU using Elu as the activation function with alpha != 1, or GRU using HardSigmoid as the activation function with alpha != 0.2 or beta != 0.5 are not supported in TensorFlow.
+5. LSTM: LSTM not using sigmoid for `f`, or LSTM not using the same activation for `g` and `h` are not supported in Tensorflow.
+6. MaxPool: MaxPoolWithArgmax with pad is None or incompatible mode, or MaxPoolWithArgmax with 4D or higher input, orMaxPoolWithArgmax with column major are not supported in Tensorflow.
+7. Mod: Mod Dividend or Divisor in int8/int16/uint8/uint16/uint32/uint64 are not supported in Tensorflow.
+8. OneHot: OneHot indices in uint16/uint32/uint64/int8/int16/float16/float/double, or OneHot depth in uint8/uint16/uint32/uint64/int8/int16/int64/float16/float/double are not supported in Tensorflow.
+9. RNN: RNN with clip is not supported in Tensorflow.
+10. Resize: Resize required 4D input in Tensorflow.
+11. Upsample: Upsample required 4D input in Tensorflow.
diff --git a/pt2tf/onnx-tensorflow/doc/support_status_v1_6_0.md b/pt2tf/onnx-tensorflow/doc/support_status_v1_6_0.md
new file mode 100644
index 0000000..ffb4dfb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/doc/support_status_v1_6_0.md
@@ -0,0 +1,203 @@
+# ONNX-Tensorflow Support Status
+|||
+|-:|:-|
+|ONNX-Tensorflow Version|v1.6.0|
+|ONNX Version|v1.6.0|
+|Tensorflow Version|v1.15.0|
+
+Notes:
+* Values that are new or updated from a previous opset version are in bold.
+* -: not defined in corresponding ONNX opset version
+* \*: the operator is deprecated
+* :small_red_triangle:: not supported yet
+* :small_orange_diamond:: partially supported
+* the rest are all supported
+
+||||||||||||||
+|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
+|**ONNX Operator**|**Opset 1**|**Opset 2**|**Opset 3**|**Opset 4**|**Opset 5**|**Opset 6**|**Opset 7**|**Opset 8**|**Opset 9**|**Opset 10**|**Opset 11**|**ONNX Operator**|
+|Abs|**1**|1|1|1|1|**6**|6|6|6|6|6|Abs|
+|Acos|-|-|-|-|-|-|**7**|7|7|7|7|Acos|
+|Acosh|-|-|-|-|-|-|-|-|**9**|9|9|Acosh|
+|Add|**1**|1|1|1|1|**6**|**7**|7|7|7|7|Add|
+|And|**1**|1|1|1|1|1|**7**|7|7|7|7|And|
+|ArgMax|**1**|1|1|1|1|1|1|1|1|1|**11**|ArgMax|
+|ArgMin|**1**|1|1|1|1|1|1|1|1|1|**11**|ArgMin|
+|Asin|-|-|-|-|-|-|**7**|7|7|7|7|Asin|
+|Asinh|-|-|-|-|-|-|-|-|**9**|9|9|Asinh|
+|Atan|-|-|-|-|-|-|**7**|7|7|7|7|Atan|
+|Atanh|-|-|-|-|-|-|-|-|**9**|9|9|Atanh|
+|AveragePool|**1**|1|1|1|1|1|**7**|7|7|**10**|**11**|AveragePool|
+|BatchNormalization|**1**|1|1|1|1|**6**|**7**|7|**9**|9|9|BatchNormalization|
+|BitShift|-|-|-|-|-|-|-|-|-|-|**11**|BitShift|
+|Cast|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**6**:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|**9**:small_orange_diamond:|9:small_orange_diamond:|9:small_orange_diamond:|Cast|
+|Ceil|**1**|1|1|1|1|**6**|6|6|6|6|6|Ceil|
+|Clip|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**6**:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|6:small_orange_diamond:|**11**:small_orange_diamond:|Clip|
+|Compress|-|-|-|-|-|-|-|-|**9**|9|**11**|Compress|
+|Concat|**1**|1|1|**4**|4|4|4|4|4|4|**11**|Concat|
+|ConcatFromSequence|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|ConcatFromSequence|
+|Constant|**1**|1|1|1|1|1|1|1|**9**|9|**11**|Constant|
+|ConstantOfShape|-|-|-|-|-|-|-|-|**9**|9|9|ConstantOfShape|
+|Conv|**1**|1|1|1|1|1|1|1|1|1|**11**|Conv|
+|ConvInteger|-|-|-|-|-|-|-|-|-|**10**|10|ConvInteger|
+|ConvTranspose|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**11**:small_orange_diamond:|ConvTranspose|
+|Cos|-|-|-|-|-|-|**7**|7|7|7|7|Cos|
+|Cosh|-|-|-|-|-|-|-|-|**9**|9|9|Cosh|
+|CumSum|-|-|-|-|-|-|-|-|-|-|**11**:small_orange_diamond:|CumSum|
+|DepthToSpace|**1**|1|1|1|1|1|1|1|1|1|**11**|DepthToSpace|
+|DequantizeLinear|-|-|-|-|-|-|-|-|-|**10**|10|DequantizeLinear|
+|Det|-|-|-|-|-|-|-|-|-|-|**11**|Det|
+|Div|**1**|1|1|1|1|**6**|**7**|7|7|7|7|Div|
+|Dropout|**1**|1|1|1|1|**6**|**7**|7|7|**10**|10|Dropout|
+|DynamicQuantizeLinear|-|-|-|-|-|-|-|-|-|-|**11**|DynamicQuantizeLinear|
+|Elu|**1**|1|1|1|1|**6**|6|6|6|6|6|Elu|
+|Equal|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|**11**:small_orange_diamond:|Equal|
+|Erf|-|-|-|-|-|-|-|-|**9**|9|9|Erf|
+|Exp|**1**|1|1|1|1|**6**|6|6|6|6|6|Exp|
+|Expand|-|-|-|-|-|-|-|**8**|8|8|8|Expand|
+|EyeLike|-|-|-|-|-|-|-|-|**9**|9|9|EyeLike|
+|Flatten|**1**|1|1|1|1|1|1|1|**9**|9|**11**|Flatten|
+|Floor|**1**|1|1|1|1|**6**|6|6|6|6|6|Floor|
+|GRU|**1**:small_orange_diamond:|1:small_orange_diamond:|**3**:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|3:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|GRU|
+|Gather|**1**|1|1|1|1|1|1|1|1|1|**11**|Gather|
+|GatherElements|-|-|-|-|-|-|-|-|-|-|**11**|GatherElements|
+|GatherND|-|-|-|-|-|-|-|-|-|-|**11**|GatherND|
+|Gemm|**1**|1|1|1|1|**6**|**7**|7|**9**|9|**11**|Gemm|
+|GlobalAveragePool|**1**|1|1|1|1|1|1|1|1|1|1|GlobalAveragePool|
+|GlobalLpPool|**1**|**2**|2|2|2|2|2|2|2|2|2|GlobalLpPool|
+|GlobalMaxPool|**1**|1|1|1|1|1|1|1|1|1|1|GlobalMaxPool|
+|Greater|**1**|1|1|1|1|1|**7**|7|**9**|9|9|Greater|
+|HardSigmoid|**1**|1|1|1|1|**6**|6|6|6|6|6|HardSigmoid|
+|Hardmax|**1**|1|1|1|1|1|1|1|1|1|**11**|Hardmax|
+|Identity|**1**|1|1|1|1|1|1|1|1|1|1|Identity|
+|If|**1**|1|1|1|1|1|1|1|1|1|**11**|If|
+|InstanceNormalization|**1**|1|1|1|1|**6**|6|6|6|6|6|InstanceNormalization|
+|IsInf|-|-|-|-|-|-|-|-|-|**10**|10|IsInf|
+|IsNaN|-|-|-|-|-|-|-|-|**9**|9|9|IsNaN|
+|LRN|**1**|1|1|1|1|1|1|1|1|1|1|LRN|
+|LSTM|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|LSTM|
+|LeakyRelu|**1**|1|1|1|1|**6**|6|6|6|6|6|LeakyRelu|
+|Less|**1**|1|1|1|1|1|**7**|7|**9**|9|9|Less|
+|Log|**1**|1|1|1|1|**6**|6|6|6|6|6|Log|
+|LogSoftmax|**1**|1|1|1|1|1|1|1|1|1|**11**|LogSoftmax|
+|Loop|**1**|1|1|1|1|1|1|1|1|1|**11**|Loop|
+|LpNormalization|**1**|1|1|1|1|1|1|1|1|1|1|LpNormalization|
+|LpPool|**1**|**2**|2|2|2|2|2|2|2|2|**11**|LpPool|
+|MatMul|**1**|1|1|1|1|1|1|1|**9**|9|9|MatMul|
+|MatMulInteger|-|-|-|-|-|-|-|-|-|**10**|10|MatMulInteger|
+|Max|**1**|1|1|1|1|**6**|6|**8**|8|8|8|Max|
+|MaxPool|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**8**:small_orange_diamond:|8:small_orange_diamond:|**10**:small_orange_diamond:|**11**:small_orange_diamond:|MaxPool|
+|MaxRoiPool|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|MaxRoiPool|
+|MaxUnpool|-|-|-|-|-|-|-|-|**9**|9|**11**|MaxUnpool|
+|Mean|**1**|1|1|1|1|**6**|6|**8**|8|8|8|Mean|
+|MeanVarianceNormalization|-|-|-|-|-|-|-|-|**9**|9|9|MeanVarianceNormalization|
+|Min|**1**|1|1|1|1|**6**|6|**8**|8|8|8|Min|
+|Mod|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|10:small_orange_diamond:|Mod|
+|Mul|**1**|1|1|1|1|**6**|**7**|7|7|7|7|Mul|
+|Multinomial|-|-|-|-|-|-|**7**:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|7:small_red_triangle:|Multinomial|
+|Neg|**1**|1|1|1|1|**6**|6|6|6|6|6|Neg|
+|NonMaxSuppression|-|-|-|-|-|-|-|-|-|**10**|**11**|NonMaxSuppression|
+|NonZero|-|-|-|-|-|-|-|-|**9**|9|9|NonZero|
+|Not|**1**|1|1|1|1|1|1|1|1|1|1|Not|
+|OneHot|-|-|-|-|-|-|-|-|**9**:small_orange_diamond:|9:small_orange_diamond:|**11**:small_orange_diamond:|OneHot|
+|Or|**1**|1|1|1|1|1|**7**|7|7|7|7|Or|
+|PRelu|**1**|1|1|1|1|**6**|**7**|7|**9**|9|9|PRelu|
+|Pad|**1**|**2**|2|2|2|2|2|2|2|2|**11**|Pad|
+|Pow|**1**|1|1|1|1|1|**7**|7|7|7|7|Pow|
+|QLinearConv|-|-|-|-|-|-|-|-|-|**10**|10|QLinearConv|
+|QLinearMatMul|-|-|-|-|-|-|-|-|-|**10**|10|QLinearMatMul|
+|QuantizeLinear|-|-|-|-|-|-|-|-|-|**10**|10|QuantizeLinear|
+|RNN|**1**:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|1:small_orange_diamond:|**7**:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|7:small_orange_diamond:|RNN|
+|RandomNormal|**1**|1|1|1|1|1|1|1|1|1|1|RandomNormal|
+|RandomNormalLike|**1**|1|1|1|1|1|1|1|1|1|1|RandomNormalLike|
+|RandomUniform|**1**|1|1|1|1|1|1|1|1|1|1|RandomUniform|
+|RandomUniformLike|**1**|1|1|1|1|1|1|1|1|1|1|RandomUniformLike|
+|Range|-|-|-|-|-|-|-|-|-|-|**11**|Range|
+|Reciprocal|**1**|1|1|1|1|**6**|6|6|6|6|6|Reciprocal|
+|ReduceL1|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceL1|
+|ReduceL2|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceL2|
+|ReduceLogSum|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceLogSum|
+|ReduceLogSumExp|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceLogSumExp|
+|ReduceMax|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceMax|
+|ReduceMean|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceMean|
+|ReduceMin|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceMin|
+|ReduceProd|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceProd|
+|ReduceSum|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceSum|
+|ReduceSumSquare|**1**|1|1|1|1|1|1|1|1|1|**11**|ReduceSumSquare|
+|Relu|**1**|1|1|1|1|**6**|6|6|6|6|6|Relu|
+|Reshape|**1**|1|1|1|**5**|5|5|5|5|5|5|Reshape|
+|Resize|-|-|-|-|-|-|-|-|-|**10**:small_orange_diamond:|**11**:small_orange_diamond:|Resize|
+|ReverseSequence|-|-|-|-|-|-|-|-|-|**10**|10|ReverseSequence|
+|RoiAlign|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|10:small_red_triangle:|RoiAlign|
+|Round|-|-|-|-|-|-|-|-|-|-|**11**|Round|
+|Scan|-|-|-|-|-|-|-|**8**|**9**|9|**11**|Scan|
+|Scatter|-|-|-|-|-|-|-|-|**9**|9|**11**\*|Scatter|
+|ScatterElements|-|-|-|-|-|-|-|-|-|-|**11**|ScatterElements|
+|ScatterND|-|-|-|-|-|-|-|-|-|-|**11**|ScatterND|
+|Selu|**1**|1|1|1|1|**6**|6|6|6|6|6|Selu|
+|SequenceAt|-|-|-|-|-|-|-|-|-|-|**11**|SequenceAt|
+|SequenceConstruct|-|-|-|-|-|-|-|-|-|-|**11**|SequenceConstruct|
+|SequenceEmpty|-|-|-|-|-|-|-|-|-|-|**11**|SequenceEmpty|
+|SequenceErase|-|-|-|-|-|-|-|-|-|-|**11**|SequenceErase|
+|SequenceInsert|-|-|-|-|-|-|-|-|-|-|**11**|SequenceInsert|
+|SequenceLength|-|-|-|-|-|-|-|-|-|-|**11**|SequenceLength|
+|Shape|**1**|1|1|1|1|1|1|1|1|1|1|Shape|
+|Shrink|-|-|-|-|-|-|-|-|**9**|9|9|Shrink|
+|Sigmoid|**1**|1|1|1|1|**6**|6|6|6|6|6|Sigmoid|
+|Sign|-|-|-|-|-|-|-|-|**9**|9|9|Sign|
+|Sin|-|-|-|-|-|-|**7**|7|7|7|7|Sin|
+|Sinh|-|-|-|-|-|-|-|-|**9**|9|9|Sinh|
+|Size|**1**|1|1|1|1|1|1|1|1|1|1|Size|
+|Slice|**1**|1|1|1|1|1|1|1|1|**10**|**11**|Slice|
+|Softmax|**1**|1|1|1|1|1|1|1|1|1|**11**|Softmax|
+|Softplus|**1**|1|1|1|1|1|1|1|1|1|1|Softplus|
+|Softsign|**1**|1|1|1|1|1|1|1|1|1|1|Softsign|
+|SpaceToDepth|**1**|1|1|1|1|1|1|1|1|1|1|SpaceToDepth|
+|Split|**1**|**2**|2|2|2|2|2|2|2|2|**11**|Split|
+|SplitToSequence|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|SplitToSequence|
+|Sqrt|**1**|1|1|1|1|**6**|6|6|6|6|6|Sqrt|
+|Squeeze|**1**|1|1|1|1|1|1|1|1|1|**11**|Squeeze|
+|StringNormalizer|-|-|-|-|-|-|-|-|-|**10**:small_red_triangle:|10:small_red_triangle:|StringNormalizer|
+|Sub|**1**|1|1|1|1|**6**|**7**|7|7|7|7|Sub|
+|Sum|**1**|1|1|1|1|**6**|6|**8**|8|8|8|Sum|
+|Tan|-|-|-|-|-|-|**7**|7|7|7|7|Tan|
+|Tanh|**1**|1|1|1|1|**6**|6|6|6|6|6|Tanh|
+|TfIdfVectorizer|-|-|-|-|-|-|-|-|**9**|9|9|TfIdfVectorizer|
+|ThresholdedRelu|-|-|-|-|-|-|-|-|-|**10**|10|ThresholdedRelu|
+|Tile|**1**|1|1|1|1|**6**|6|6|6|6|6|Tile|
+|TopK|**1**|1|1|1|1|1|1|1|1|**10**|**11**|TopK|
+|Transpose|**1**|1|1|1|1|1|1|1|1|1|1|Transpose|
+|Unique|-|-|-|-|-|-|-|-|-|-|**11**:small_red_triangle:|Unique|
+|Unsqueeze|**1**|1|1|1|1|1|1|1|1|1|**11**|Unsqueeze|
+|Upsample|**1**:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|1:small_red_triangle:|**7**:small_orange_diamond:|7:small_orange_diamond:|**9**:small_orange_diamond:|**10**\*|10\*|Upsample|
+|Where|-|-|-|-|-|-|-|-|**9**|9|9|Where|
+|Xor|**1**|1|1|1|1|1|**7**|7|7|7|7|Xor|
+
+ONNX-TF Supported Operators / ONNX Operators: 149 / 156
+
+Notes:
+1. Cast: Cast string to float32/float64/int32/int64 are not supported in Tensorflow.
+2. Clip: Clip input in uint64 is not supported in Tensorflow.
+3. ConvTranspose: ConvTranspose with dilations != 1, or transposed convolution for 4D or higher are not supported in Tensorflow.
+4. CumSum: CumSum inputs in uint32/uint64 are not supported in Tensorflow.
+5. Equal: Equal inputs in uint16/uint32/uint64 are not supported in Tensorflow.
+6. GRU: GRU with clip or GRU with linear_before_reset, or GRU not using sigmoid for z and r, or GRU using Elu as the activation function with alpha != 1, or GRU using HardSigmoid as the activation function with alpha != 0.2 or beta != 0.5 are not supported in TensorFlow.
+7. LSTM: LSTM not using sigmoid for `f`, or LSTM not using the same activation for `g` and `h` are not supported in Tensorflow.
+8. MaxPool: MaxPoolWithArgmax with pad is None or incompatible mode, or MaxPoolWithArgmax with 4D or higher input, orMaxPoolWithArgmax with column major are not supported in Tensorflow.
+9. Mod: Mod Dividend or Divisor in int8/int16/uint8/uint16/uint32/uint64 are not supported in Tensorflow.
+10. OneHot: OneHot indices in uint16/uint32/uint64/int8/int16/float16/float/double, or OneHot depth in uint8/uint16/uint32/uint64/int8/int16/int64/float16/float/double are not supported in Tensorflow.
+11. RNN: RNN with clip is not supported in Tensorflow.
+12. Resize: Resize required 4D input in Tensorflow. For opset 11, only the following attributes and inputs conbination are supported in Tensorflow:
+ 1. mode=nearest, coordinate_transformation_mode=align_corners, nearest_mode=round_prefer_ceil, can use scales(*) or sizes.
+ 2. mode=nearest, coordinate_transformation_mode=asymmetric, nearest_mode=floor, can use scales(*) or sizes.
+ 3. mode=nearest, coordinate_transformation_mode=tf_half_pixel_for_nn, nearest_mode=floor, can use scales(*) or sizes.
+ 4. mode=linear, coordinate_transformation_mode=align_corners, can use scales(*) or sizes.
+ 5. mode=linear, coordinate_transformation_mode=asymmetric, can use scales(*) or sizes.
+ 6. mode=linear, coordinate_transformation_mode=half_pixel, can use scales(*) or sizes.
+ 7. mode=cubic, coordinate_transformation_mode=align_corners, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 8. mode=cubic, coordinate_transformation_mode=asymmetric, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 9. mode=cubic, coordinate_transformation_mode=half_pixel, cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or sizes.
+ 10. mode=nearest, coordinate_transformation_mode=tf_crop_and_resize, extrapolation_value=any_float_value, nearest_mode=round_prefer_ceil, can use scales or sizes.
+ 11. mode=linear, coordinate_transformation_mode=tf_crop_and_resize, extrapolation_value=any_float_value, can use scales or sizes.
+ - Note (*): The accuracy of your model will go down, if the height and the width of the new sizes(scales * origial sizes) are not in whole numbers.
+13. Upsample: Upsample required 4D input in Tensorflow.
diff --git a/pt2tf/onnx-tensorflow/example/onnx_to_tf.py b/pt2tf/onnx-tensorflow/example/onnx_to_tf.py
new file mode 100644
index 0000000..f47f04d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/example/onnx_to_tf.py
@@ -0,0 +1,7 @@
+import onnx
+
+from onnx_tf.backend import prepare
+
+onnx_model = onnx.load("input_path") # load onnx model
+tf_rep = prepare(onnx_model) # prepare tf representation
+tf_rep.export_graph("output_path") # export the model
diff --git a/pt2tf/onnx-tensorflow/example/relu.py b/pt2tf/onnx-tensorflow/example/relu.py
new file mode 100644
index 0000000..9831175
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/example/relu.py
@@ -0,0 +1,11 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+from onnx_tf.backend import run_node, prepare
+from onnx import helper
+
+node_def = helper.make_node("Relu", ["X"], ["Y"])
+output = run_node(node_def, [[-0.1, 0.1]])
+print(output["Y"])
diff --git a/pt2tf/onnx-tensorflow/example/test_model_large_stepping.py b/pt2tf/onnx-tensorflow/example/test_model_large_stepping.py
new file mode 100644
index 0000000..dfc3c64
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/example/test_model_large_stepping.py
@@ -0,0 +1,68 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import unittest
+import numpy as np
+
+import caffe2.python.onnx.backend as c2
+import onnx
+import onnx_tf.backend as tf
+from onnx import helper
+from onnx import TensorProto
+
+
+def find_between(s, first, last):
+ try:
+ start = s.index(first) + len(first)
+ end = s.index(last, start)
+ return s[start:end]
+ except ValueError:
+ return ""
+
+
+class TestLargeModel(unittest.TestCase):
+ MODEL_PATH = "../../onnx_models/"
+
+ def test(self):
+ _model = onnx.load(self.MODEL_PATH + "shufflenet/model.onnx")
+ node_count = len(_model.graph.node)
+ more_outputs = []
+ output_to_check = []
+ for node in _model.graph.node:
+ more_outputs.append(
+ helper.make_tensor_value_info(node.output[0], TensorProto.FLOAT,
+ (100, 100)))
+ output_to_check.append(node.output[0])
+ _model.graph.output.extend(more_outputs)
+
+ tf_rep = tf.prepare(_model)
+ cf_rep = c2.prepare(_model)
+
+ sample = np.load(
+ self.MODEL_PATH + "shufflenet/test_data_{}.npz".format(str(1)),
+ encoding='bytes')
+ inputs = list(sample['inputs'])
+ outputs = list(sample['outputs'])
+
+ my_out = tf_rep.run(inputs)
+ cf_out = cf_rep.run(inputs)
+
+ for op in output_to_check:
+ try:
+ np.savetxt(
+ op.replace("/", "__") + ".cf", cf_out[op].flatten(), delimiter='\t')
+ np.savetxt(
+ op.replace("/", "__") + ".tf", my_out[op].flatten(), delimiter='\t')
+ np.testing.assert_allclose(my_out[op], cf_out[op], rtol=1e-2)
+ print(op, "results of this layer are correct within tolerence.")
+ except Exception as e:
+ np.set_printoptions(threshold=np.inf)
+ mismatch_percent = (find_between(str(e), "(mismatch", "%)"))
+ print(op, "mismatch with percentage {} %".format(mismatch_percent))
+
+
+if __name__ == '__main__':
+ unittest.main()
+ pass
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/PKG-INFO b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/PKG-INFO
new file mode 100644
index 0000000..13ff0eb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/PKG-INFO
@@ -0,0 +1,12 @@
+Metadata-Version: 1.1
+Name: onnx-tf
+Version: 1.6.0
+Summary: Tensorflow backend for ONNX (Open Neural Network Exchange).
+Home-page: https://github.com/onnx/onnx-tensorflow/
+Author: Arpith Jacob, Tian Jin, Gheorghe-Teodor Bercea, Wenhao Hu
+Author-email: tian.jin1@ibm.com
+License: Apache License 2.0
+Description: UNKNOWN
+Platform: UNKNOWN
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 3
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/SOURCES.txt b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/SOURCES.txt
new file mode 100644
index 0000000..2e8e69a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/SOURCES.txt
@@ -0,0 +1,209 @@
+LICENSE
+MANIFEST.in
+ONNX_VERSION_NUMBER
+README.md
+VERSION_NUMBER
+setup.cfg
+setup.py
+onnx_tf/__init__.py
+onnx_tf/backend.py
+onnx_tf/backend_rep.py
+onnx_tf/cli.py
+onnx_tf/converter.py
+onnx_tf/gen_doc.py
+onnx_tf/gen_opset.py
+onnx_tf/gen_status.py
+onnx_tf/opset_version.py
+onnx_tf/pb_wrapper.py
+onnx_tf/version.py
+onnx_tf.egg-info/PKG-INFO
+onnx_tf.egg-info/SOURCES.txt
+onnx_tf.egg-info/dependency_links.txt
+onnx_tf.egg-info/entry_points.txt
+onnx_tf.egg-info/not-zip-safe
+onnx_tf.egg-info/requires.txt
+onnx_tf.egg-info/top_level.txt
+onnx_tf/common/__init__.py
+onnx_tf/common/attr_converter.py
+onnx_tf/common/attr_translator.py
+onnx_tf/common/data_type.py
+onnx_tf/common/exception.py
+onnx_tf/common/handler_helper.py
+onnx_tf/common/legacy.py
+onnx_tf/common/pooling_helper.py
+onnx_tf/common/tf_helper.py
+onnx_tf/handlers/__init__.py
+onnx_tf/handlers/backend_handler.py
+onnx_tf/handlers/handler.py
+onnx_tf/handlers/backend/__init__.py
+onnx_tf/handlers/backend/abs.py
+onnx_tf/handlers/backend/acos.py
+onnx_tf/handlers/backend/acosh.py
+onnx_tf/handlers/backend/add.py
+onnx_tf/handlers/backend/and.py
+onnx_tf/handlers/backend/arg_max.py
+onnx_tf/handlers/backend/arg_min.py
+onnx_tf/handlers/backend/asin.py
+onnx_tf/handlers/backend/asinh.py
+onnx_tf/handlers/backend/atan.py
+onnx_tf/handlers/backend/atanh.py
+onnx_tf/handlers/backend/average_pool.py
+onnx_tf/handlers/backend/batch_normalization.py
+onnx_tf/handlers/backend/bitshift.py
+onnx_tf/handlers/backend/broadcast_mixin.py
+onnx_tf/handlers/backend/cast.py
+onnx_tf/handlers/backend/ceil.py
+onnx_tf/handlers/backend/clip.py
+onnx_tf/handlers/backend/compress.py
+onnx_tf/handlers/backend/concat.py
+onnx_tf/handlers/backend/constant.py
+onnx_tf/handlers/backend/constant_fill.py
+onnx_tf/handlers/backend/constant_of_shape.py
+onnx_tf/handlers/backend/control_flow_mixin.py
+onnx_tf/handlers/backend/conv.py
+onnx_tf/handlers/backend/conv_integer.py
+onnx_tf/handlers/backend/conv_mixin.py
+onnx_tf/handlers/backend/conv_transpose.py
+onnx_tf/handlers/backend/cos.py
+onnx_tf/handlers/backend/cosh.py
+onnx_tf/handlers/backend/cumsum.py
+onnx_tf/handlers/backend/depth_to_space.py
+onnx_tf/handlers/backend/dequantize_linear.py
+onnx_tf/handlers/backend/det.py
+onnx_tf/handlers/backend/dilated_pooling.py
+onnx_tf/handlers/backend/div.py
+onnx_tf/handlers/backend/dropout.py
+onnx_tf/handlers/backend/dynamic_quantize_linear.py
+onnx_tf/handlers/backend/elu.py
+onnx_tf/handlers/backend/equal.py
+onnx_tf/handlers/backend/erf.py
+onnx_tf/handlers/backend/exp.py
+onnx_tf/handlers/backend/expand.py
+onnx_tf/handlers/backend/eye_like.py
+onnx_tf/handlers/backend/flatten.py
+onnx_tf/handlers/backend/floor.py
+onnx_tf/handlers/backend/gather.py
+onnx_tf/handlers/backend/gather_and_scatter_mixin.py
+onnx_tf/handlers/backend/gather_elements.py
+onnx_tf/handlers/backend/gather_nd.py
+onnx_tf/handlers/backend/gemm.py
+onnx_tf/handlers/backend/global_average_pool.py
+onnx_tf/handlers/backend/global_lp_pool.py
+onnx_tf/handlers/backend/global_max_pool.py
+onnx_tf/handlers/backend/greater.py
+onnx_tf/handlers/backend/gru.py
+onnx_tf/handlers/backend/hard_sigmoid.py
+onnx_tf/handlers/backend/hardmax.py
+onnx_tf/handlers/backend/identity.py
+onnx_tf/handlers/backend/if.py
+onnx_tf/handlers/backend/image_scaler.py
+onnx_tf/handlers/backend/instance_normalization.py
+onnx_tf/handlers/backend/is_inf.py
+onnx_tf/handlers/backend/is_nan.py
+onnx_tf/handlers/backend/leaky_relu.py
+onnx_tf/handlers/backend/less.py
+onnx_tf/handlers/backend/log.py
+onnx_tf/handlers/backend/log_softmax.py
+onnx_tf/handlers/backend/loop.py
+onnx_tf/handlers/backend/lp_normalization.py
+onnx_tf/handlers/backend/lp_pool.py
+onnx_tf/handlers/backend/lrn.py
+onnx_tf/handlers/backend/lstm.py
+onnx_tf/handlers/backend/mat_mul.py
+onnx_tf/handlers/backend/mat_mul_integer.py
+onnx_tf/handlers/backend/math_mixin.py
+onnx_tf/handlers/backend/max.py
+onnx_tf/handlers/backend/max_pool.py
+onnx_tf/handlers/backend/max_unpool.py
+onnx_tf/handlers/backend/mean.py
+onnx_tf/handlers/backend/mean_variance_normalization.py
+onnx_tf/handlers/backend/min.py
+onnx_tf/handlers/backend/mod.py
+onnx_tf/handlers/backend/mul.py
+onnx_tf/handlers/backend/neg.py
+onnx_tf/handlers/backend/non_max_suppression.py
+onnx_tf/handlers/backend/non_zero.py
+onnx_tf/handlers/backend/not.py
+onnx_tf/handlers/backend/onehot.py
+onnx_tf/handlers/backend/or.py
+onnx_tf/handlers/backend/p_relu.py
+onnx_tf/handlers/backend/pad.py
+onnx_tf/handlers/backend/pad_mixin.py
+onnx_tf/handlers/backend/pool_mixin.py
+onnx_tf/handlers/backend/pow.py
+onnx_tf/handlers/backend/q_linear_conv.py
+onnx_tf/handlers/backend/q_linear_mat_mul.py
+onnx_tf/handlers/backend/quantize_linear.py
+onnx_tf/handlers/backend/random_normal.py
+onnx_tf/handlers/backend/random_normal_like.py
+onnx_tf/handlers/backend/random_uniform.py
+onnx_tf/handlers/backend/random_uniform_like.py
+onnx_tf/handlers/backend/range.py
+onnx_tf/handlers/backend/reciprocal.py
+onnx_tf/handlers/backend/reduce_l1.py
+onnx_tf/handlers/backend/reduce_l2.py
+onnx_tf/handlers/backend/reduce_log_sum.py
+onnx_tf/handlers/backend/reduce_log_sum_exp.py
+onnx_tf/handlers/backend/reduce_max.py
+onnx_tf/handlers/backend/reduce_mean.py
+onnx_tf/handlers/backend/reduce_min.py
+onnx_tf/handlers/backend/reduce_prod.py
+onnx_tf/handlers/backend/reduce_sum.py
+onnx_tf/handlers/backend/reduce_sum_square.py
+onnx_tf/handlers/backend/relu.py
+onnx_tf/handlers/backend/reshape.py
+onnx_tf/handlers/backend/resize.py
+onnx_tf/handlers/backend/reverse_sequence.py
+onnx_tf/handlers/backend/rnn.py
+onnx_tf/handlers/backend/rnn_mixin.py
+onnx_tf/handlers/backend/round.py
+onnx_tf/handlers/backend/scan.py
+onnx_tf/handlers/backend/scan_mixin.py
+onnx_tf/handlers/backend/scatter.py
+onnx_tf/handlers/backend/scatter_elements.py
+onnx_tf/handlers/backend/scatter_nd.py
+onnx_tf/handlers/backend/selu.py
+onnx_tf/handlers/backend/sequence_at.py
+onnx_tf/handlers/backend/sequence_construct.py
+onnx_tf/handlers/backend/sequence_empty.py
+onnx_tf/handlers/backend/sequence_erase.py
+onnx_tf/handlers/backend/sequence_insert.py
+onnx_tf/handlers/backend/sequence_length.py
+onnx_tf/handlers/backend/shape.py
+onnx_tf/handlers/backend/shrink.py
+onnx_tf/handlers/backend/sigmoid.py
+onnx_tf/handlers/backend/sign.py
+onnx_tf/handlers/backend/sin.py
+onnx_tf/handlers/backend/sinh.py
+onnx_tf/handlers/backend/size.py
+onnx_tf/handlers/backend/slice.py
+onnx_tf/handlers/backend/softmax.py
+onnx_tf/handlers/backend/softplus.py
+onnx_tf/handlers/backend/softsign.py
+onnx_tf/handlers/backend/space_to_depth.py
+onnx_tf/handlers/backend/split.py
+onnx_tf/handlers/backend/sqrt.py
+onnx_tf/handlers/backend/squeeze.py
+onnx_tf/handlers/backend/sub.py
+onnx_tf/handlers/backend/sum.py
+onnx_tf/handlers/backend/tan.py
+onnx_tf/handlers/backend/tanh.py
+onnx_tf/handlers/backend/tfidf_vectorizer.py
+onnx_tf/handlers/backend/thresholded_relu.py
+onnx_tf/handlers/backend/tile.py
+onnx_tf/handlers/backend/top_k.py
+onnx_tf/handlers/backend/transpose.py
+onnx_tf/handlers/backend/unpool_mixin.py
+onnx_tf/handlers/backend/unsqueeze.py
+onnx_tf/handlers/backend/upsample.py
+onnx_tf/handlers/backend/where.py
+onnx_tf/handlers/backend/xor.py
+test/__init__.py
+test/test_cli.py
+test/backend/__init__.py
+test/backend/test_dynamic_shape.py
+test/backend/test_model.py
+test/backend/test_node.py
+test/backend/test_onnx_backend.py
+third_party/__init__.py
+third_party/get_info.py
\ No newline at end of file
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/dependency_links.txt b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/entry_points.txt b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/entry_points.txt
new file mode 100644
index 0000000..f05be4b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/entry_points.txt
@@ -0,0 +1,3 @@
+[console_scripts]
+onnx-tf = onnx_tf.cli:main
+
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/not-zip-safe b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/not-zip-safe
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/not-zip-safe
@@ -0,0 +1 @@
+
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/requires.txt b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/requires.txt
new file mode 100644
index 0000000..3517455
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/requires.txt
@@ -0,0 +1,2 @@
+onnx>=1.6.0
+PyYAML
diff --git a/pt2tf/onnx-tensorflow/onnx_tf.egg-info/top_level.txt b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/top_level.txt
new file mode 100644
index 0000000..70c9584
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf.egg-info/top_level.txt
@@ -0,0 +1,3 @@
+onnx_tf
+test
+third_party
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__init__.py b/pt2tf/onnx-tensorflow/onnx_tf/__init__.py
new file mode 100644
index 0000000..9e6aa3d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/__init__.py
@@ -0,0 +1,2 @@
+from . import backend
+from .version import version as __version__
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/__init__.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..2f17064
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/__init__.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend.cpython-36.pyc
new file mode 100644
index 0000000..d9d2c0b
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend_rep.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend_rep.cpython-36.pyc
new file mode 100644
index 0000000..1134088
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/backend_rep.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/cli.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/cli.cpython-36.pyc
new file mode 100644
index 0000000..071ebb4
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/cli.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/converter.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/converter.cpython-36.pyc
new file mode 100644
index 0000000..9446bc0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/converter.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/pb_wrapper.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/pb_wrapper.cpython-36.pyc
new file mode 100644
index 0000000..80b63b2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/pb_wrapper.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/version.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/version.cpython-36.pyc
new file mode 100644
index 0000000..f1044ad
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/__pycache__/version.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/backend.py b/pt2tf/onnx-tensorflow/onnx_tf/backend.py
new file mode 100644
index 0000000..8b7d7f2
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/backend.py
@@ -0,0 +1,352 @@
+"""Backend for running ONNX on Tensorflow
+
+To run this, you will need to have Tensorflow installed as well.
+"""
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+try:
+ from itertools import izip as zip
+except ImportError: # will be 3.x series
+ pass
+
+from onnx import defs
+from onnx import numpy_helper
+from onnx.backend.base import Backend
+from onnx.backend.base import Device
+from onnx.backend.base import namedtupledict
+from onnx.backend.test.runner import BackendIsNotSupposedToImplementIt
+from onnx.helper import make_opsetid
+import tensorflow as tf
+
+from onnx_tf.backend_rep import TensorflowRep
+from onnx_tf.common import data_type
+from onnx_tf.common import get_device_option
+from onnx_tf.common import get_unique_suffix
+from onnx_tf.common import supports_device as common_supports_device
+from onnx_tf.common.handler_helper import get_all_backend_handlers
+from onnx_tf.pb_wrapper import OnnxNode
+import onnx_tf.common as common
+
+
+class TensorflowBackend(Backend):
+ """ Tensorflow Backend for ONNX
+ """
+
+ @classmethod
+ def prepare(cls,
+ model,
+ device='CPU',
+ strict=True,
+ logging_level='INFO',
+ **kwargs):
+ """Prepare an ONNX model for Tensorflow Backend.
+
+ This function converts an ONNX model to an internel representation
+ of the computational graph called TensorflowRep and returns
+ the converted representation.
+
+ :param model: The ONNX model to be converted.
+ :param device: The device to execute this model on.
+ :param strict: Whether to enforce semantic equivalence between the original model
+ and the converted tensorflow model, defaults to True (yes, enforce semantic equivalence).
+ Changing to False is strongly discouraged.
+ Currently, the strict flag only affects the behavior of MaxPool and AveragePool ops.
+ :param logging_level: The logging level, default is INFO. Change it to DEBUG
+ to see more conversion details or to WARNING to see less
+
+ :returns: A TensorflowRep class object representing the ONNX model
+ """
+ super(TensorflowBackend, cls).prepare(model, device, **kwargs)
+ common.logger.setLevel(logging_level)
+ common.logger.handlers[0].setLevel(logging_level)
+
+ return cls.onnx_model_to_tensorflow_rep(model, strict)
+
+ @classmethod
+ def onnx_model_to_tensorflow_rep(cls, model, strict):
+ """ Convert ONNX model to TensorflowRep.
+
+ :param model: ONNX ModelProto object.
+ :param strict: whether to enforce semantic equivalence between the original model
+ and the converted tensorflow model.
+ :return: TensorflowRep object.
+ """
+
+ # Models with IR_VERSION less than 3 does not have opset_import set.
+ # We default to minimum opset, this behavior is consistent with
+ # onnx checker.
+ # c.f. https://github.com/onnx/onnx/blob/427ac0c1b792363d373e3d7e4eef97fa46458420/onnx/checker.cc#L478
+ if model.ir_version < 3:
+ opset_import = [make_opsetid(defs.ONNX_DOMAIN, 1)]
+ else:
+ opset_import = model.opset_import
+ return cls._onnx_graph_to_tensorflow_rep(model.graph, opset_import, strict)
+
+ @classmethod
+ def _onnx_graph_to_tensorflow_rep(cls, graph_def, opset, strict):
+ """ Convert ONNX graph to TensorflowRep.
+
+ :param graph_def: ONNX GraphProto object.
+ :param opset: ONNX OperatorSetIdProto list.
+ :param strict: whether to enforce semantic equivalence between the original model
+ and the converted tensorflow model.
+ :return: TensorflowRep object.
+ """
+ handlers = cls._get_handlers(opset)
+
+ tf_rep_graph = tf.Graph()
+ with tf_rep_graph.as_default():
+ # initializer: TensorProtos representing the values to initialize
+ # a given tensor.
+ # initialized: A list of names of the initialized tensors.
+ if graph_def.initializer:
+ input_dict_items = cls._onnx_initializer_to_input_dict_items(
+ graph_def.initializer)
+ initialized = {init.name for init in graph_def.initializer}
+ else:
+ input_dict_items = []
+ initialized = set()
+
+ # creating placeholders for currently unknown inputs
+ for value_info in graph_def.input:
+ if value_info.name in initialized:
+ continue
+ shape = list(
+ d.dim_value if (d.dim_value > 0 and d.dim_param == "") else None
+ for d in value_info.type.tensor_type.shape.dim)
+ value_info_name = value_info.name.replace(
+ ":", "_tf_") + "_" + get_unique_suffix(
+ ) if ":" in value_info.name else value_info.name
+
+ x = tf.placeholder(data_type.onnx2tf(
+ value_info.type.tensor_type.elem_type),
+ name=value_info_name,
+ shape=shape)
+ input_dict_items.append((value_info.name, x))
+
+ # tensor dict: this dictionary is a map from variable names
+ # to the latest produced TF tensors of the given name.
+ # This dictionary will get updated as we build the graph to
+ # record the names of newly produced tensors.
+ tensor_dict = dict(input_dict_items)
+ # Since tensor dict may be updated, we need to keep a copy
+ # of the original input dict where we track the earliest
+ # defined tensors so we can have access to the placeholders
+ # to feed in input tensors when we run the graph.
+ input_dict = dict(input_dict_items)
+
+ for node in graph_def.node:
+ onnx_node = OnnxNode(node)
+ output_ops = cls._onnx_node_to_tensorflow_op(onnx_node,
+ tensor_dict,
+ handlers,
+ opset=opset,
+ strict=strict)
+ curr_node_output_map = dict(zip(onnx_node.outputs, output_ops))
+ tensor_dict.update(curr_node_output_map)
+
+ tf_rep = TensorflowRep()
+ tf_rep.graph = tf_rep_graph
+ tf_rep.inputs = [
+ value_info.name
+ for value_info in graph_def.input
+ if value_info.name not in initialized
+ ]
+ tf_rep.outputs = [value_info.name for value_info in graph_def.output]
+ tf_rep.tensor_dict = tensor_dict
+ return tf_rep
+
+ @classmethod
+ def run_node(cls, node, inputs, device='CPU', outputs_info=None, **kwargs):
+ """ Run ONNX node.
+
+ :param node: ONNX NodeProto object.
+ :param inputs: Inputs.
+ :param device: Device run on.
+ :param outputs_info: None.
+ :param kwargs: Other args.
+ :return: Outputs.
+ """
+ super(TensorflowBackend, cls).run_node(node, inputs, device)
+ node_graph = tf.Graph()
+ with node_graph.as_default():
+ node = OnnxNode(node)
+ device_option = get_device_option(Device(device))
+ input_tensors = []
+ for i in inputs:
+ input_tensors.append(tf.constant(i))
+
+ if isinstance(inputs, dict):
+ feed_dict_raw = inputs
+ else:
+ assert len(node.inputs) == len(inputs)
+ feed_dict_raw = dict(zip(node.inputs, inputs))
+
+ # TODO: is constant the best way for feeding inputs?
+ input_dict = dict([
+ (x[0], tf.constant(x[1])) for x in feed_dict_raw.items()
+ ])
+ ops = cls._onnx_node_to_tensorflow_op(node, input_dict)
+
+ with tf.Session() as sess:
+ with tf.device(device_option):
+ sess.run(tf.global_variables_initializer())
+ output_vals = sess.run(ops)
+
+ return namedtupledict('Outputs', node.outputs)(*output_vals)
+
+ @classmethod
+ def _onnx_initializer_to_input_dict_items(cls, initializer):
+ """ Convert ONNX graph initializer to input dict items.
+
+ :param initializer: ONNX graph initializer, list of TensorProto.
+ :return: List of input dict items.
+ """
+
+ def tensor2list(onnx_tensor):
+ # Use the onnx.numpy_helper because the data may be raw
+ return numpy_helper.to_array(onnx_tensor).flatten().tolist()
+
+ def validate_initializer_name(name):
+ # Prepend a unique suffix if leading charater is "_"
+ name = get_unique_suffix() + name if name[0] is "_" else name
+
+ # Replace ":" with "_tf_" and append a unique suffix for
+ # traceability
+ return name.replace(
+ ":", "_tf_") + "_" + get_unique_suffix() if ":" in name else name
+
+ return [(init.name,
+ tf.constant(tensor2list(init),
+ shape=init.dims,
+ dtype=data_type.onnx2tf(init.data_type),
+ name=validate_initializer_name(init.name)))
+ for init in initializer]
+
+ @classmethod
+ def _onnx_node_to_tensorflow_op(cls,
+ node,
+ tensor_dict,
+ handlers=None,
+ opset=None,
+ strict=True):
+ """
+ Convert onnx node to tensorflow op.
+
+ Args:
+ node: Onnx node object.
+ tensor_dict: Tensor dict of graph.
+ opset: Opset version of the operator set. Default 0 means using latest version.
+ strict: whether to enforce semantic equivalence between the original model
+ and the converted tensorflow model, defaults to True (yes, enforce semantic equivalence).
+ Changing to False is strongly discouraged.
+ Returns:
+ Tensorflow op
+ """
+ handlers = handlers or cls._get_handlers(opset)
+ if handlers:
+ handler = handlers[node.domain].get(node.op_type, None) if node.domain in handlers else None
+ if handler:
+ return handler.handle(node, tensor_dict=tensor_dict, strict=strict)
+
+ raise BackendIsNotSupposedToImplementIt("{} is not implemented.".format(node.op_type))
+
+ @classmethod
+ def _get_handlers(cls, opset):
+ """ Get all backend handlers with opset.
+
+ :param opset: ONNX OperatorSetIdProto list.
+ :return: All backend handlers.
+ """
+ opset = opset or [make_opsetid(defs.ONNX_DOMAIN, defs.onnx_opset_version())]
+ opset_dict = dict([(o.domain, o.version) for o in opset])
+ return get_all_backend_handlers(opset_dict)
+
+ @classmethod
+ def supports_device(cls, device):
+ return common_supports_device(device)
+
+ @classmethod
+ def onnx_graph_to_tensorflow_ops(cls,
+ subgraph,
+ input_values,
+ tensor_dict,
+ opset=None,
+ strict=True):
+ """
+ Converts ONNX graph to Tensorflow operations
+ Args:
+ subgraph: the ONNX graph to be converted
+ input_values: dictionary with values/tensors to initialize
+ the subgraph inputs. if the subgraph.input
+ are send in as parameters then it is required,
+ otherwise this can be empty dictionary.
+ tensor_dict: the dictionary that contain values for all the
+ node.inputs in the subgraph that are not defined
+ in the subgraph or input_values.
+ opset: opset version of the operator set.
+ strict: whether to enforce semantic equivalence between the
+ original model and the converted tensorflow model,
+ defaults to True (yes, enforce semantic equivalence).
+ Returns:
+ array of Tensorflow Tensors
+ """
+ # get the subgraph.input from input_values
+ subgraph_tensor_dict = input_values.copy()
+ # get the rest of the subgraph input from tensor_dict
+ for i in subgraph.input:
+ if i.name not in subgraph_tensor_dict.keys():
+ subgraph_tensor_dict[i.name] = tensor_dict[i.name]
+ # get the required initializer constant node(s) for the subgraph
+ # Need to get the initializer constant nodes from tensor_dict here
+ # because input from initializer will not be send in as inputs
+ # to the subgraph and those nodes are not in the subgraph
+ nodes_outputs = []
+ for node in subgraph.node:
+ for o_name in node.output:
+ nodes_outputs.append(o_name)
+ for node in subgraph.node:
+ for i_name in node.input:
+ if i_name not in nodes_outputs and i_name not in subgraph_tensor_dict.keys(
+ ):
+ subgraph_tensor_dict[i_name] = tensor_dict[i_name]
+ onnx_node = OnnxNode(node)
+ output_ops = cls._onnx_node_to_tensorflow_op(onnx_node,
+ subgraph_tensor_dict,
+ opset=opset,
+ strict=strict)
+ curr_node_output_map = dict(zip(onnx_node.outputs, output_ops))
+ subgraph_tensor_dict.update(curr_node_output_map)
+ return subgraph_tensor_dict
+
+ @classmethod
+ def onnx_graph_to_tensorflow_rep(cls, graph_def, strict=True):
+ """
+ Converts ONNX graph to TensorflowRep
+ Args:
+ graph_def: the ONNX graph to be converted
+ strict: whether to enforce semantic equivalence between the
+ original model and the converted tensorflow model,
+ defaults to True (yes, enforce semantic equivalence).
+ Returns:
+ TensorflowRep object.
+ """
+ # get the opset of the installed ONNX
+ opset = [make_opsetid(defs.ONNX_DOMAIN, defs.onnx_opset_version())]
+ return cls._onnx_graph_to_tensorflow_rep(graph_def, opset, strict)
+
+
+prepare = TensorflowBackend.prepare
+
+run_node = TensorflowBackend.run_node
+
+run_model = TensorflowBackend.run_model
+
+supports_device = TensorflowBackend.supports_device
+
+onnx_graph_to_tensorflow_ops = TensorflowBackend.onnx_graph_to_tensorflow_ops
+
+onnx_graph_to_tensorflow_rep = TensorflowBackend.onnx_graph_to_tensorflow_rep
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/backend_rep.py b/pt2tf/onnx-tensorflow/onnx_tf/backend_rep.py
new file mode 100644
index 0000000..8b0c152
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/backend_rep.py
@@ -0,0 +1,109 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import tensorflow as tf
+
+from onnx.backend.base import BackendRep, namedtupledict
+
+
+class TensorflowRep(BackendRep):
+
+ def __init__(self, graph=None, inputs=None, outputs=None, tensor_dict=None):
+ super(TensorflowRep, self).__init__()
+ self._graph = graph
+ self._inputs = inputs or []
+ self._outputs = outputs or []
+ self._tensor_dict = tensor_dict or {}
+
+ @property
+ def graph(self):
+ return self._graph
+
+ @graph.setter
+ def graph(self, graph):
+ self._graph = graph
+
+ @property
+ def inputs(self):
+ return self._inputs
+
+ @inputs.setter
+ def inputs(self, inputs):
+ self._inputs = inputs
+
+ @property
+ def outputs(self):
+ return self._outputs
+
+ @outputs.setter
+ def outputs(self, outputs):
+ self._outputs = outputs
+
+ @property
+ def tensor_dict(self):
+ return self._tensor_dict
+
+ @tensor_dict.setter
+ def tensor_dict(self, tensor_dict):
+ self._tensor_dict = tensor_dict
+
+ def run(self, inputs, **kwargs):
+ """ Run TensorflowRep.
+
+ :param inputs: Given inputs.
+ :param kwargs: Other args.
+ :return: Outputs.
+ """
+ super(TensorflowRep, self).run(inputs, **kwargs)
+
+ # TODO: handle name scope if necessary
+ with self.graph.as_default():
+ with tf.Session() as sess:
+ if isinstance(inputs, dict):
+ feed_dict = inputs
+ elif isinstance(inputs, list) or isinstance(inputs, tuple):
+ if len(self.inputs) != len(inputs):
+ raise RuntimeError('Expected {} values for uninitialized '
+ 'graph inputs ({}), but got {}.'.format(
+ len(self.inputs), ', '.join(self.inputs),
+ len(inputs)))
+ feed_dict = dict(zip(self.inputs, inputs))
+ else:
+ # single input
+ feed_dict = dict([(self.inputs[0], inputs)])
+
+ feed_dict = {
+ self.tensor_dict[key]: feed_dict[key] for key in self.inputs
+ }
+
+ sess.run(tf.global_variables_initializer())
+ outputs = [self.tensor_dict[output] for output in self.outputs]
+
+ output_values = sess.run(outputs, feed_dict=feed_dict)
+ return namedtupledict('Outputs', self.outputs)(*output_values)
+
+ def export_graph(self, path):
+ """Export backend representation to a Tensorflow proto file.
+
+ This function obtains the graph proto corresponding to the ONNX
+ model associated with the backend representation and serializes
+ to a protobuf file.
+
+ :param path: The path to the output TF protobuf file.
+
+ :returns: none.
+ """
+ graph_proto = self.graph.as_graph_def()
+ # rename the output nodes
+ meaningful_names = {}
+ for output_name in self.outputs:
+ meaningful_names[self.tensor_dict[output_name].name.replace(':0', '')] = output_name
+ for node in graph_proto.node:
+ if node.name in meaningful_names.keys():
+ node.name = meaningful_names[node.name]
+
+ file = open(path, "wb")
+ file.write(graph_proto.SerializeToString())
+ file.close()
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/cli.py b/pt2tf/onnx-tensorflow/onnx_tf/cli.py
new file mode 100644
index 0000000..8364022
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/cli.py
@@ -0,0 +1,24 @@
+import argparse
+import sys
+
+import onnx_tf.converter
+
+
+def main():
+ args = sys.argv[1:]
+ parser = argparse.ArgumentParser(
+ description="ONNX-Tensorflow Command Line Interface")
+ parser.add_argument(
+ "command",
+ choices=["convert"],
+ help="Available commands.")
+
+ if len(args) == 0:
+ parser.parse_args(["-h"])
+ cli_tool = parser.parse_args([args[0]])
+ if cli_tool.command == "convert":
+ return onnx_tf.converter.main(args[1:])
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__init__.py b/pt2tf/onnx-tensorflow/onnx_tf/common/__init__.py
new file mode 100644
index 0000000..794ef1a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/__init__.py
@@ -0,0 +1,196 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import inspect
+import re
+import sys
+import uuid
+import warnings
+import logging
+
+from onnx.backend.base import DeviceType
+from tensorflow.python.client import device_lib
+
+IS_PYTHON3 = sys.version_info > (3,)
+logger = logging.getLogger('onnx-tf')
+
+# create console handler and formatter for logger
+console = logging.StreamHandler()
+formatter = logging.Formatter(
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+console.setFormatter(formatter)
+logger.addHandler(console)
+
+
+class Deprecated:
+ """Add deprecated message when function is called.
+
+ Usage:
+ from onnx_tf.common import deprecated
+
+ @deprecated
+ def func():
+ pass
+
+ UserWarning: func is deprecated. It will be removed in future release.
+
+ @deprecated("Message")
+ def func():
+ pass
+
+ UserWarning: Message
+
+ @deprecated({"arg": "Message",
+ "arg_1": deprecated.MSG_WILL_REMOVE,
+ "arg_2": "",})
+ def func(arg, arg_1, arg_2):
+ pass
+
+ UserWarning: Message
+ UserWarning: arg_1 of func is deprecated. It will be removed in future release.
+ UserWarning: arg_2 of func is deprecated.
+ """
+
+ MSG_WILL_REMOVE = " It will be removed in future release."
+
+ def __call__(self, *args, **kwargs):
+ return self.deprecated_decorator(*args, **kwargs)
+
+ @staticmethod
+ def messages():
+ return {v for k, v in inspect.getmembers(Deprecated) if k.startswith("MSG")}
+
+ @staticmethod
+ def deprecated_decorator(arg=None):
+ # deprecate function with default message MSG_WILL_REMOVE
+ # @deprecated
+ if inspect.isfunction(arg):
+
+ def wrapper(*args, **kwargs):
+ warnings.warn("{} is deprecated.{}".format(
+ arg.__module__ + "." + arg.__name__, Deprecated.MSG_WILL_REMOVE))
+ return arg(*args, **kwargs)
+
+ return wrapper
+
+ deprecated_arg = arg if arg is not None else Deprecated.MSG_WILL_REMOVE
+
+ def deco(func):
+ # deprecate arg
+ # @deprecated({...})
+ if isinstance(deprecated_arg, dict):
+ for name, message in deprecated_arg.items():
+ if message in Deprecated.messages():
+ message = "{} of {} is deprecated.{}".format(
+ name, func.__module__ + "." + func.__name__, message or "")
+ warnings.warn(message)
+ # deprecate function with message
+ # @deprecated("message")
+ elif isinstance(deprecated_arg, str):
+ message = deprecated_arg
+ if message in Deprecated.messages():
+ message = "{} is deprecated.{}".format(
+ func.__module__ + "." + func.__name__, message)
+ warnings.warn(message)
+ return func
+
+ return deco
+
+
+deprecated = Deprecated()
+
+
+# This function inserts an underscore before every upper
+# case letter and lowers that upper case letter except for
+# the first letter.
+def op_name_to_lower(name):
+ return re.sub('(? 0
+ elif device == "CPU":
+ return True
+ return False
+
+
+@deprecated("onnx_tf.common.get_outputs_names is deprecated.{} {}".format(
+ deprecated.MSG_WILL_REMOVE,
+ "Use TensorflowGraph.get_outputs_names instead."))
+def get_output_node_names(graph_def):
+ """Get output node names from GraphDef.
+ Args:
+ graph_def: GraphDef object.
+ Returns:
+ List of output node names.
+ """
+ nodes, input_names = dict(), set()
+ for node in graph_def.node:
+ nodes[node.name] = node
+ input_names.update(set(node.input))
+ return list(set(nodes) - input_names)
+
+
+CONST_MINUS_ONE_INT32 = "_onnx_tf_internal_minus_one_int32"
+CONST_ZERO_INT32 = "_onnx_tf_internal_zero_int32"
+CONST_ONE_INT32 = "_onnx_tf_internal_one_int32"
+CONST_ONE_FP32 = "_onnx_tf_internal_one_fp32"
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/__init__.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..056c78e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/__init__.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_converter.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_converter.cpython-36.pyc
new file mode 100644
index 0000000..df8bd71
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_converter.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_translator.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_translator.cpython-36.pyc
new file mode 100644
index 0000000..2e689f5
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/attr_translator.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/data_type.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/data_type.cpython-36.pyc
new file mode 100644
index 0000000..fb9d932
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/data_type.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/exception.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/exception.cpython-36.pyc
new file mode 100644
index 0000000..05d97e1
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/exception.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/handler_helper.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/handler_helper.cpython-36.pyc
new file mode 100644
index 0000000..2382165
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/handler_helper.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/pooling_helper.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/pooling_helper.cpython-36.pyc
new file mode 100644
index 0000000..4d44345
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/pooling_helper.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/tf_helper.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/tf_helper.cpython-36.pyc
new file mode 100644
index 0000000..bb1bb70
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/common/__pycache__/tf_helper.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/attr_converter.py b/pt2tf/onnx-tensorflow/onnx_tf/common/attr_converter.py
new file mode 100644
index 0000000..dc89397
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/attr_converter.py
@@ -0,0 +1,86 @@
+from onnx_tf.common import IS_PYTHON3
+
+
+def convert_tf(attr):
+ return __convert_tf_attr_value(attr)
+
+
+def convert_onnx(attr):
+ return __convert_onnx_attribute_proto(attr)
+
+
+def __convert_tf_attr_value(attr):
+ """ convert Tensorflow AttrValue object to Python object
+ """
+ if attr.HasField('list'):
+ return __convert_tf_list_value(attr.list)
+ if attr.HasField('s'):
+ return attr.s
+ elif attr.HasField('i'):
+ return attr.i
+ elif attr.HasField('f'):
+ return attr.f
+ elif attr.HasField('b'):
+ return attr.b
+ elif attr.HasField('type'):
+ return attr.type
+ elif attr.HasField('shape'):
+ return attr.type
+ elif attr.HasField('tensor'):
+ return attr.tensor
+ else:
+ raise ValueError("Unsupported Tensorflow attribute: {}".format(attr))
+
+
+def __convert_tf_list_value(list_value):
+ """ convert Tensorflow ListValue object to Python object
+ """
+ if list_value.s:
+ return list_value.s
+ elif list_value.i:
+ return list_value.i
+ elif list_value.f:
+ return list_value.f
+ elif list_value.b:
+ return list_value.b
+ elif list_value.tensor:
+ return list_value.tensor
+ elif list_value.type:
+ return list_value.type
+ elif list_value.shape:
+ return list_value.shape
+ elif list_value.func:
+ return list_value.func
+ else:
+ raise ValueError("Unsupported Tensorflow attribute: {}".format(list_value))
+
+
+def __convert_onnx_attribute_proto(attr_proto):
+ """
+ Convert an ONNX AttributeProto into an appropriate Python object
+ for the type.
+ NB: Tensor attribute gets returned as the straight proto.
+ """
+ if attr_proto.HasField('f'):
+ return attr_proto.f
+ elif attr_proto.HasField('i'):
+ return attr_proto.i
+ elif attr_proto.HasField('s'):
+ return str(attr_proto.s, 'utf-8') if IS_PYTHON3 else attr_proto.s
+ elif attr_proto.HasField('t'):
+ return attr_proto.t # this is a proto!
+ elif attr_proto.HasField('g'):
+ return attr_proto.g
+ elif attr_proto.floats:
+ return list(attr_proto.floats)
+ elif attr_proto.ints:
+ return list(attr_proto.ints)
+ elif attr_proto.strings:
+ str_list = list(attr_proto.strings)
+ if IS_PYTHON3:
+ str_list = list(map(lambda x: str(x, 'utf-8'), str_list))
+ return str_list
+ elif attr_proto.HasField('sparse_tensor'):
+ return attr_proto.sparse_tensor
+ else:
+ raise ValueError("Unsupported ONNX attribute: {}".format(attr_proto))
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/attr_translator.py b/pt2tf/onnx-tensorflow/onnx_tf/common/attr_translator.py
new file mode 100644
index 0000000..2842c30
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/attr_translator.py
@@ -0,0 +1,37 @@
+from tensorflow.python.framework.tensor_util import MakeNdarray
+
+from onnx_tf.common import data_type
+
+# Keyed by old attribute names.
+__tf_attr_translator = {
+ "_output_shapes": lambda x: list(map(lambda shape: get_tf_shape_as_list(shape.dim), x.list.shape)),
+ "shape": lambda x: get_tf_shape_as_list(x.shape.dim),
+ "T": lambda x: data_type.tf2onnx(list(x.list.type) or x.type),
+ "dtype": lambda x: data_type.tf2onnx(list(x.list.type) or x.type),
+ "component_types": lambda x: data_type.tf2onnx(list(x.list.type) or x.type),
+ "value": lambda x: MakeNdarray(x.tensor),
+ "seed2": lambda x: float(x.i),
+ "seed": lambda x: float(x.i),
+ "keep_dims": lambda x: int(x.b),
+ "squeeze_dims": lambda x: list(x.list.i),
+}
+
+__onnx_attr_translator = {
+ "axis": lambda x: int(x),
+ "axes": lambda x: [int(a) for a in x],
+ "dtype": lambda x: data_type.onnx2tf(x),
+ "keepdims": lambda x: bool(x),
+ "to": lambda x: data_type.onnx2tf(x),
+}
+
+
+def translate_tf(key, val):
+ return __tf_attr_translator.get(key, lambda x: x)(val)
+
+
+def translate_onnx(key, val):
+ return __onnx_attr_translator.get(key, lambda x: x)(val)
+
+
+def get_tf_shape_as_list(tf_shape_dim):
+ return list(map(lambda x: x.size, list(tf_shape_dim)))
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/data_type.py b/pt2tf/onnx-tensorflow/onnx_tf/common/data_type.py
new file mode 100644
index 0000000..483ab61
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/data_type.py
@@ -0,0 +1,71 @@
+from numbers import Number
+
+import numpy as np
+from onnx import mapping
+from onnx import TensorProto
+import tensorflow as tf
+
+
+def tf2onnx(dtype):
+ if isinstance(dtype, Number):
+ tf_dype = tf.as_dtype(dtype)
+ elif isinstance(dtype, tf.DType):
+ tf_dype = dtype
+ elif isinstance(dtype, list):
+ return [tf2onnx(t) for t in dtype]
+ else:
+ raise RuntimeError("dtype should be number or tf.DType.")
+
+ # Usually, tf2onnx is done via tf_type->numpy_type->onnx_type
+ # to leverage existing type conversion infrastructure;
+ # However, we need to intercept the string type early because
+ # lowering tf.string type to numpy dtype results in loss of
+ # information. is returned instead of the
+ # numpy string type desired.
+ if tf_dype is tf.string:
+ return TensorProto.STRING
+
+ onnx_dtype = None
+ try:
+ onnx_dtype = mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype(
+ tf_dype.as_numpy_dtype)]
+ finally:
+ if onnx_dtype is None:
+ common.logger.warning(
+ "Can't convert tf dtype {} to ONNX dtype. Return 0 (TensorProto.UNDEFINED)."
+ .format(tf_dype))
+ onnx_dtype = TensorProto.UNDEFINED
+ return onnx_dtype
+
+
+def onnx2tf(dtype):
+ return tf.as_dtype(mapping.TENSOR_TYPE_TO_NP_TYPE[_onnx_dtype(dtype)])
+
+
+def onnx2field(dtype):
+ return mapping.STORAGE_TENSOR_TYPE_TO_FIELD[_onnx_dtype(dtype)]
+
+
+def _onnx_dtype(dtype):
+ if isinstance(dtype, Number):
+ onnx_dype = dtype
+ elif isinstance(dtype, str):
+ onnx_dype = TensorProto.DataType.Value(dtype)
+ else:
+ raise RuntimeError("dtype should be number or str.")
+ return onnx_dype
+
+
+# TODO (tjingrant) unify _onnx_dtype into any_dtype_to_onnx_dtype
+def any_dtype_to_onnx_dtype(np_dtype=None, tf_dtype=None, onnx_dtype=None):
+ dtype_mask = [1 if val else 0 for val in [np_dtype, tf_dtype, onnx_dtype]]
+ num_type_set = sum(dtype_mask)
+ assert num_type_set == 1, "One and only one type must be set. However, {} set.".format(
+ sum(num_type_set))
+
+ if np_dtype:
+ onnx_dtype = mapping.NP_TYPE_TO_TENSOR_TYPE[np_dtype]
+ if tf_dtype:
+ onnx_dtype = tf2onnx(tf_dtype)
+
+ return onnx_dtype
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/exception.py b/pt2tf/onnx-tensorflow/onnx_tf/common/exception.py
new file mode 100644
index 0000000..d2c73ca
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/exception.py
@@ -0,0 +1,73 @@
+import inspect
+import onnx_tf.common as common
+
+
+class CustomException(object):
+
+ def __init__(self):
+ self._func = RuntimeError
+ self._message = ""
+
+ def __call__(self, *args, **kwargs):
+ if inspect.isclass(self._func) and issubclass(self._func, Exception):
+ raise self._func(self.get_message(*args, **kwargs))
+ elif callable(self._func):
+ self._func(self.get_message(*args, **kwargs))
+
+ def get_message(self, *args, **kwargs):
+ return self._message
+
+
+class OpUnimplementedException(CustomException):
+
+ def __init__(self):
+ super(OpUnimplementedException, self).__init__()
+ self._func = NotImplementedError
+ self._message = "{} is not implemented."
+
+ def __call__(self, op, version=None, domain=None):
+ if IGNORE_UNIMPLEMENTED:
+ self._func = common.logger.warning
+ super(OpUnimplementedException, self).__call__(op, version, domain)
+
+ def get_message(self, op, version=None, domain=None):
+ insert_message = op
+ if version is not None:
+ insert_message += " version {}".format(version)
+ if domain is not None:
+ insert_message += " in domain `{}`".format(domain)
+ return self._message.format(insert_message)
+
+
+class OpUnsupportedException(object):
+
+ def __init__(self):
+ super(OpUnsupportedException, self).__init__()
+ self._func = RuntimeError
+ self._message = "{} is not supported in {}."
+
+ def __call__(self, op, framework):
+ raise self._func(self.get_message(op, framework))
+
+ def get_message(self, op, framework):
+ return self._message.format(op, framework)
+
+
+class ConstNotFoundException(CustomException):
+
+ def __init__(self):
+ super(ConstNotFoundException, self).__init__()
+ self._func = RuntimeError
+ self._message = "{} of {} is not found in graph consts."
+
+ def __call__(self, name, op):
+ super(ConstNotFoundException, self).__call__(name, op)
+
+ def get_message(self, name, op):
+ return self._message.format(name, op)
+
+
+IGNORE_UNIMPLEMENTED = False
+OP_UNIMPLEMENTED_EXCEPT = OpUnimplementedException()
+OP_UNSUPPORTED_EXCEPT = OpUnsupportedException()
+CONST_NOT_FOUND_EXCEPT = ConstNotFoundException()
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/handler_helper.py b/pt2tf/onnx-tensorflow/onnx_tf/common/handler_helper.py
new file mode 100644
index 0000000..8d0c3ca
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/handler_helper.py
@@ -0,0 +1,76 @@
+from onnx import defs
+
+import onnx_tf.common as common
+from onnx_tf.handlers.backend import * # noqa
+from onnx_tf.handlers.backend_handler import BackendHandler
+
+import onnx_tf.common as common
+
+def get_all_backend_handlers(opset_dict):
+ """ Get a dict of all backend handler classes.
+ e.g. {'domain': {'Abs': Abs handler class}, ...}, }.
+
+ :param opset_dict: A dict of opset. e.g. {'domain': version, ...}
+ :return: Dict.
+ """
+ handlers = {}
+ for handler in BackendHandler.__subclasses__():
+ handler.check_cls()
+
+ domain = handler.DOMAIN
+ version = opset_dict[domain] if domain in opset_dict else 1
+ handler.VERSION = version
+
+ since_version = 1
+ if defs.has(handler.ONNX_OP, domain=handler.DOMAIN):
+ try:
+ since_version = defs.get_schema(
+ handler.ONNX_OP,
+ domain=handler.DOMAIN,
+ max_inclusive_version=version).since_version
+ except RuntimeError:
+ common.logger.debug("Fail to get since_version of {} in domain `{}` "
+ "with max_inclusive_version={}. Set to 1.".format(
+ handler.ONNX_OP, handler.DOMAIN, version))
+ else:
+ common.logger.debug("Unknown op {} in domain `{}`.".format(
+ handler.ONNX_OP, handler.DOMAIN or "ai.onnx"))
+ handler.SINCE_VERSION = since_version
+ handlers.setdefault(domain, {})[handler.ONNX_OP] = handler
+ return handlers
+
+
+def get_backend_coverage():
+ """ Get backend coverage for document.
+
+ :return: onnx_coverage: e.g. {'domain': {'ONNX_OP': [versions], ...}, ...}
+ """
+
+ onnx_coverage = {}
+ experimental_op = set()
+ for handler in BackendHandler.__subclasses__():
+ handler.check_cls()
+
+ versions = handler.get_versions()
+ domain = handler.DOMAIN
+ if getattr(handler, "EXPERIMENTAL", False):
+ experimental_op.add(handler.ONNX_OP)
+ _update_coverage(onnx_coverage, domain, handler.ONNX_OP, versions)
+ return onnx_coverage, experimental_op
+
+
+def _update_coverage(coverage, domain, key, versions):
+ domain_coverage = coverage.setdefault(domain, {})
+ vers = domain_coverage.get(key, [])
+ vers.extend(versions)
+ domain_coverage[key] = sorted(list(set(vers)))
+
+
+def get_backend_partial_support_detail():
+ ps_dict = {}
+ opset_dict = dict([(defs.ONNX_DOMAIN, defs.onnx_opset_version())])
+ handlers = get_all_backend_handlers(opset_dict)[defs.ONNX_DOMAIN]
+ for op_name in handlers:
+ if handlers[op_name].PARTIAL_SUPPORT:
+ ps_dict[op_name] = handlers[op_name].PS_DESCRIPTION
+ return ps_dict
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/legacy.py b/pt2tf/onnx-tensorflow/onnx_tf/common/legacy.py
new file mode 100644
index 0000000..93e6ef3
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/legacy.py
@@ -0,0 +1,21 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import onnx
+
+
+def get_onnx_version():
+ return tuple(map(int, onnx.version.version.split(".")))
+
+
+# Returns whether onnx version is prior to major.minor.patch
+def legacy_onnx_pre_ver(major=0, minor=0, patch=0):
+ return get_onnx_version() < (major, minor, patch)
+
+
+# Returns whether the opset version accompanying the
+# onnx installation is prior to version passed.
+def legacy_opset_pre_ver(version):
+ return onnx.defs.onnx_opset_version() < version
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/pooling_helper.py b/pt2tf/onnx-tensorflow/onnx_tf/common/pooling_helper.py
new file mode 100644
index 0000000..e5f6f64
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/pooling_helper.py
@@ -0,0 +1,263 @@
+from __future__ import division
+
+from collections import namedtuple
+import numpy as np
+import tensorflow as tf
+
+import itertools
+
+
+pad_ops = namedtuple("pad_ops",
+ ["max_op", "ceil_op", "floor_op", "cast_int_op"])
+
+pad_numpy_ops = pad_ops(np.maximum, np.ceil, np.floor,
+ lambda arr: arr.astype(np.int64))
+pad_tf_ops = pad_ops(tf.maximum, tf.math.ceil, tf.math.floor,
+ lambda tensor: tf.cast(tensor, tf.int64))
+
+
+def calc_pads_same(in_spatial_shape, kernel_shape, strides,
+ dilations, padding, padding_ops=pad_numpy_ops,
+ pads_order=1):
+ """
+ Calculates the SAME paddings that need to be added to the input
+
+ Args:
+ in_spatial_shape: input spatial shape
+ kernel_shape: the size of the kernel along each axis
+ strides: stride along each spatial axis
+ dilations: dilations value along each spatial axis
+ padding: padding to calculate: SAME_UPPER or
+ SAME_LOWER
+ padding_ops: namedtuple with ops to be used during
+ calculations. there are two sets of ops
+ defined pad_numpy_ops and pad_tf_ops with
+ numpy and tensorflow ops
+ pads_order: order of returned pads. possible options are:
+ 1 - b1, b2, ..., bn, e1, e2, ..., en
+ 2 - b1, e1, b2, e2, ..., bn, en
+ where n = len(kernel_shape) * 2,
+ b1, b2, ..., bn define pads at the begging of
+ axis
+ e1, e2, ..., en define pads at the end of
+ axis
+ Return:
+ pads: array with calculated pads. the order of the
+ values is determined by `pads_order`
+
+ """
+ spatial_size = len(kernel_shape)
+ pads = [0] * (spatial_size * 2)
+ for i in range(spatial_size):
+ in_size = in_spatial_shape[i]
+ filter_size = (kernel_shape[i] - 1) * dilations[i] + 1
+
+ out_size = padding_ops.ceil_op(in_size / strides[i])
+ out_size = padding_ops.cast_int_op(out_size)
+ pad_along_axis = \
+ padding_ops.max_op((out_size - 1) * strides[i] +
+ filter_size - in_size, 0)
+ if padding.lower() == "same_lower":
+ pad_op = padding_ops.ceil_op
+ else:
+ pad_op = padding_ops.floor_op
+ pad_begin = pad_op(pad_along_axis / 2)
+
+ pad_begin = padding_ops.cast_int_op(pad_begin)
+ pad_along_axis = padding_ops.cast_int_op(pad_along_axis)
+
+ pad_end = pad_along_axis - pad_begin
+
+ pads[i * pads_order] = pad_begin
+ pads[i * pads_order +
+ (spatial_size if pads_order == 1 else 1)] = pad_end
+
+ return pads
+
+
+def calc_output_shape(input_spatial_shape, kernel_shape, strides, dilations,
+ padding, ceil_mode=False):
+ """
+ Calculate output shape
+
+ Args:
+ input_spatial_shape: input spatial shape
+ kernel_shape: the size of the kernel along each axis
+ strides: stride along each spatial axis
+ dilations: dilations value along each spatial axis
+ padding: can be explicit paddings, "SAME_UPPER" or
+ "SAME_LOWER"
+ Return:
+ output_shape: calculated output shape
+ """
+ spatial_size = len(input_spatial_shape)
+
+ if type(padding) is not list and type(padding) is not np.ndarray:
+ if padding.lower().startswith("same"):
+ padding = calc_pads_same(input_spatial_shape, kernel_shape,
+ strides, dilations, padding)
+ else:
+ padding = [0] * spatial_size * 2
+
+ output_shape = []
+ for dim in range(spatial_size):
+ output_shape.append(_pooling_output_shape(input_spatial_shape[dim],
+ kernel_shape[dim], strides[dim], dilations[dim],
+ padding[dim] + padding[dim + spatial_size],
+ ceil_mode))
+
+ return output_shape
+
+
+def _pooling_output_shape(input_size, ksize, stride, dilation, pad, ceil_mode):
+ output_size = (input_size + pad - ((ksize - 1) * dilation + 1) +
+ ((stride-1) if ceil_mode else 0)) // stride + 1
+ if (pad):
+ if ((output_size - 1) * stride >= input_size + pad):
+ output_size -= 1
+ return output_size
+
+
+def py_pool(input, kernel_shape, strides=None, dilations=None,
+ padding=None, ceil_mode=False, pooling_type="MAX",
+ include_indices=True, p=2):
+ """
+ Implementation of Max and Average pool operations in Python
+ Args:
+ input: input N-D data array in NC* format
+ kernel_shape: the size of the kernel along each axis
+ strides: stride along each spatial axis
+ dilations: dilations value along each spatial axis of filter
+ padding: padding for the beginning and ending along each
+ spatial axis. `padding` format should be as follow
+ [x1_begin, x2_begin...x1_end, x2_end,...]
+ ceil_mode: whether to use ceil or floor (default) to compute
+ the output shape.
+ pooling_type: specifies pooling type. Values can be "MAX", "AVG" or
+ "LP"
+ include_indices: should indices be included in the output
+ p: specifies the p parameter for LpPooling
+ Return:
+ pooled: output data from max pooling across the input
+ ind: indices of the selected max values from the input
+ """
+
+ if type(pooling_type) is not str:
+ pooling_type = pooling_type.decode("UTF-8")
+
+ input_shape = np.shape(input)
+ inp_sp_shape = input_shape[2:]
+ input_dtype = input.dtype
+ if np.issubdtype(input_dtype, np.integer):
+ input_dtype_min = np.iinfo(input_dtype).min
+ else:
+ input_dtype_min = np.finfo(input_dtype).min
+
+ if pooling_type == "LP":
+ rootN = (1.0 / p)
+
+ def _loop_over_output(batch, channel):
+ dims = [range(output_sp_shape[d]) for d in range(spatial_size)]
+ for counters in itertools.product(*dims):
+ input_ranges = []
+ for dim in range(spatial_size):
+ dim_start = \
+ counters[dim] * strides[dim] - pads[dim * 2]
+ dim_end = \
+ min(dim_start + (kernel_shape[dim] - 1) * dilations[dim]
+ + 1, inp_sp_shape[dim])
+ while dim_start < 0:
+ dim_start += dilations[dim]
+
+ cur_range = [i for i in range(dim_start,
+ dim_end, dilations[dim])]
+ input_ranges.append(cur_range)
+ if pooling_type in ["AVG", "LP"]:
+ val_sum = 0
+ val_count = 0
+ else:
+ maxval = input_dtype_min
+ maxind = -1
+ for input_ind in itertools.product(*input_ranges):
+ ind = (batch, channel) + input_ind
+ val = input[ind]
+ if pooling_type == "AVG":
+ val_sum += val
+ val_count += 1
+ elif pooling_type == "LP":
+ val_sum += abs(val ** p)
+ else:
+ if val > maxval:
+ maxval = val
+ ind = 0
+ for i in range(spatial_size):
+ coef = 1
+ for j in range(i+1, spatial_size):
+ coef *= inp_sp_shape[j]
+ ind += input_ind[i] * coef
+ maxind = ind
+ ind = (batch, channel) + counters
+ if pooling_type == "AVG":
+ out_pool[ind] = val_sum / val_count
+ elif pooling_type == "LP":
+ out_pool[ind] = val_sum ** rootN
+ else:
+ out_pool[ind] = maxval
+ out_ind[ind] = maxind
+
+ spatial_size = len(kernel_shape)
+
+ batch_size = input_shape[0]
+ channels_num = input_shape[1]
+
+ if strides is None:
+ strides = kernel_shape
+
+ if dilations is None:
+ dilations = [1] * spatial_size
+
+ if padding is None:
+ padding = [0] * spatial_size * 2
+
+ if type(padding) is bytes:
+ padding = padding.decode()
+
+ if type(padding) is not list and type(padding) is not np.ndarray:
+ if type(padding) is not str:
+ padding = padding.decode("UTF-8")
+
+ if padding.lower().startswith("same"):
+ padding = calc_pads_same(inp_sp_shape, kernel_shape, strides,
+ dilations, padding)
+ else:
+ padding = [0] * spatial_size * 2
+
+ pads = []
+ pad_along_axis = []
+ output_sp_shape = []
+
+ for dim in range(spatial_size):
+ pads.append(padding[dim])
+ pads.append(padding[dim + spatial_size])
+ pad_along_axis.append(padding[dim] + padding[dim + spatial_size])
+
+ input_size = input_shape[dim + 2]
+ output_size = \
+ _pooling_output_shape(input_size, kernel_shape[dim],
+ strides[dim], dilations[dim],
+ pad_along_axis[dim], ceil_mode)
+ output_sp_shape.append(output_size)
+
+ out_pool = np.zeros([input_shape[0], input_shape[1]] +
+ output_sp_shape, input_dtype)
+ out_ind = np.zeros([input_shape[0], input_shape[1]] +
+ output_sp_shape, np.int64)
+
+ for batch in range(batch_size):
+ for channel in range(channels_num):
+ _loop_over_output(batch, channel)
+
+ if not include_indices:
+ return out_pool
+ else:
+ return out_pool, out_ind
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/common/tf_helper.py b/pt2tf/onnx-tensorflow/onnx_tf/common/tf_helper.py
new file mode 100644
index 0000000..d2a90b4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/common/tf_helper.py
@@ -0,0 +1,45 @@
+import tensorflow as tf
+import numpy as np
+
+
+def tf_shape(tensor):
+ """
+ Helper function returning the shape of a Tensor.
+ The function will check for fully defined shape and will return
+ numpy array or if the shape is not fully defined will use tf.shape()
+ to return the shape as a Tensor.
+ """
+ if tensor.shape.is_fully_defined():
+ return np.array(tensor.shape.as_list(), dtype=np.int64)
+ else:
+ return tf.shape(tensor, out_type=tf.int64)
+
+
+def tf_product(a, b):
+ """
+ Calculates the cartesian product of two column vectors a and b
+
+ Example:
+
+ a = [[1]
+ [2]
+ [3]]
+
+ b = [[0]
+ [1]]
+
+ result = [[1 0]
+ [1 1]
+ [2 0]
+ [2 1]
+ [3 0]
+ [3 1]]
+ """
+ tile_a = tf.tile(a, [1, tf.shape(b)[0]])
+ tile_a = tf.expand_dims(tile_a, 2)
+ tile_a = tf.reshape(tile_a, [-1, 1])
+
+ b = tf.tile(b, [tf.shape(a)[0], 1])
+ b = tf.concat([tile_a, b], axis=1)
+
+ return b
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/converter.py b/pt2tf/onnx-tensorflow/onnx_tf/converter.py
new file mode 100644
index 0000000..6cefd8c
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/converter.py
@@ -0,0 +1,138 @@
+import argparse
+import inspect
+import logging
+import os
+import shutil
+
+import onnx
+import tensorflow as tf
+from tensorflow.core.framework import graph_pb2
+from tensorflow.python.tools import freeze_graph
+
+import onnx_tf.backend as backend
+import onnx_tf.common as common
+from onnx_tf.common import get_unique_suffix
+from onnx_tf.pb_wrapper import TensorflowGraph
+
+
+def main(args):
+ args = parse_args(args)
+ convert(**{k: v for k, v in vars(args).items() if v is not None})
+
+
+def parse_args(args):
+
+ class ListAction(argparse.Action):
+ """ Define how to convert command line list strings to Python objects.
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ values = values if values[0] not in ("(", "[") or values[-1] not in (
+ ")", "]") else values[1:-1]
+ res = []
+ for value in values.split(","):
+ if value.isdigit():
+ res.append(int(value))
+ else:
+ res.append(value)
+ setattr(namespace, self.dest, res)
+
+ class OpsetAction(argparse.Action):
+ """ Define how to convert command line opset strings to Python objects.
+ """
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ if values.isdigit():
+ setattr(namespace, "opset", int(values))
+ else:
+ res = []
+ while values and values[0] in ("(", "["):
+ values = values[1:]
+ while values and values[-1] in (")", "]"):
+ values = values[:-1]
+ for value in values.split("),("):
+ l, r = value.split(",")
+ res.append((l, int(r)))
+ setattr(namespace, "opset", res)
+
+ def get_param_doc_dict(funcs):
+ """Get doc of funcs params.
+
+ Args:
+ funcs: Target funcs.
+
+ Returns:
+ Dict of params doc.
+ """
+
+ # TODO(fumihwh): support google doc format
+ def helper(doc, func):
+ first_idx = doc.find(":param")
+ last_idx = doc.find(":return")
+ last_idx = last_idx if last_idx != -1 else len(doc)
+ param_doc = doc[first_idx:last_idx]
+ params_doc = param_doc.split(":param ")[1:]
+ return {
+ p[:p.find(": ")]: p[p.find(": ") + len(": "):] +
+ " (from {})".format(func.__module__ + "." + func.__name__)
+ for p in params_doc
+ }
+
+ param_doc_dict = {}
+ for func, persists in funcs:
+ doc = inspect.getdoc(func)
+ doc_dict = helper(doc, func)
+ for k, v in doc_dict.items():
+ if k not in persists:
+ continue
+ param_doc_dict[k] = {"doc": v, "params": persists[k]}
+ return param_doc_dict
+
+ parser = argparse.ArgumentParser(
+ description=
+ "This is the converter for converting protocol buffer between tf and onnx."
+ )
+
+ # required two args, source and destination path
+ parser.add_argument("--infile", "-i", help="Input file path.", required=True)
+ parser.add_argument(
+ "--outfile", "-o", help="Output file path.", required=True)
+
+ def add_argument_group(parser, group_name, funcs):
+ group = parser.add_argument_group(group_name)
+ param_doc_dict = get_param_doc_dict(funcs)
+ for k, v in param_doc_dict.items():
+ group.add_argument("--{}".format(k), help=v["doc"], **v["params"])
+
+ # backend args
+ # Args must be named consistently with respect to backend.prepare.
+ add_argument_group(parser, "backend arguments (onnx -> tf)",
+ [(backend.prepare, {
+ "device": {},
+ "strict": {},
+ "logging_level": {}
+ })])
+
+ return parser.parse_args(args)
+
+
+def convert(infile, outfile, **kwargs):
+ """Convert pb.
+
+ Args:
+ infile: Input path.
+ outfile: Output path.
+ **kwargs: Other args for converting.
+
+ Returns:
+ None.
+ """
+ logging_level = kwargs.get("logging_level", "INFO")
+ common.logger.setLevel(logging_level)
+ common.logger.handlers[0].setLevel(logging_level)
+
+ common.logger.info("Start converting onnx pb to tf pb:")
+ onnx_model = onnx.load(infile)
+ tf_rep = backend.prepare(onnx_model, **kwargs)
+ tf_rep.export_graph(outfile)
+ common.logger.info("Converting completes successfully.")
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/gen_doc.py b/pt2tf/onnx-tensorflow/onnx_tf/gen_doc.py
new file mode 100644
index 0000000..74f0e2e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/gen_doc.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import os
+import re
+import subprocess
+
+import onnx_tf.backend
+import onnx_tf.backend_rep
+from third_party import get_info
+
+
+def main(docs_dir):
+ gen_api(docs_dir)
+ gen_cli(docs_dir)
+
+
+def gen_api(docs_dir):
+ gen_doc_for = {
+ 'onnx_tf.backend': [
+ onnx_tf.backend.prepare,
+ ],
+ 'onnx_tf.backend_rep.TensorflowRep': [
+ onnx_tf.backend_rep.TensorflowRep.export_graph,
+ ]
+ }
+ with open(os.path.join(docs_dir, 'API.md'), 'w') as doc_file:
+ doc_file.write('ONNX-Tensorflow API\n')
+ doc_file.write('======\n\n')
+
+ for scope, funcs in sorted(gen_doc_for.items()):
+ for func in funcs:
+ doc_parsed = get_info.parse_docstring(func.__doc__)
+ doc_file.write('#### `' + scope + '.' + func.__name__ + '`\n\n')
+ doc_file.write('\n')
+ doc_file.write(' ')
+ doc_file.write(doc_parsed['short_description'] + '\n\n')
+ doc_file.write('
\n')
+ doc_file.write(doc_parsed['long_description'] + '\n\n')
+ doc_file.write(' \n\n\n\n')
+
+ doc_file.write('_params_:\n\n')
+ for param in doc_parsed['params']:
+ doc_file.write('`' + param['name'] + '` : ' + param['doc'] + '\n\n')
+
+ doc_file.write('_returns_:\n\n')
+ doc_file.write(doc_parsed['returns'] + '\n\n')
+
+
+def gen_cli(docs_dir):
+ with open(os.path.join(docs_dir, 'CLI_template.md'), 'r') as cli_temp_file:
+ temp_lines = cli_temp_file.readlines()
+
+ lines = []
+ for line in temp_lines:
+ matched = re.match(r"{onnx-tf.*}", line)
+ if matched:
+ command = matched.string.strip()[1:-1]
+ output = subprocess.check_output(command.split(" ")).decode("UTF-8")
+ lines.append(output)
+ else:
+ lines.append(line)
+
+ with open(os.path.join(docs_dir, 'CLI.md'), 'w') as cli_file:
+ cli_file.writelines(lines)
+
+
+if __name__ == '__main__':
+ base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+ docs_dir = os.path.join(base_dir, 'doc')
+ main(docs_dir)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/gen_opset.py b/pt2tf/onnx-tensorflow/onnx_tf/gen_opset.py
new file mode 100644
index 0000000..8358b1c
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/gen_opset.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import pprint
+
+from onnx import defs
+
+from onnx_tf.common.handler_helper import get_backend_coverage
+from onnx_tf.common.handler_helper import get_backend_partial_support_detail
+
+
+def main():
+ backend_opset_dict = {}
+
+ for schema in defs.get_all_schemas():
+ op_name = schema.name
+ backend_opset_dict[op_name] = []
+
+ backend_onnx_coverage, backend_experimental_op = get_backend_coverage()
+ backend_opset_dict.update(backend_onnx_coverage.get(defs.ONNX_DOMAIN, {}))
+ backend_ps_dict = get_backend_partial_support_detail()
+
+ with open('opset_version.py', 'w') as version_file:
+ pp = pprint.PrettyPrinter(indent=4)
+ version_file.write("backend_opset_version = {\n " +
+ pp.pformat(backend_opset_dict)[1:-1] + "\n}\n\n")
+ version_file.write("backend_partial_support = {\n " +
+ pp.pformat(backend_ps_dict)[1:-1] + "\n}\n")
+
+
+if __name__ == '__main__':
+ main()
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/gen_status.py b/pt2tf/onnx-tensorflow/onnx_tf/gen_status.py
new file mode 100644
index 0000000..62b007b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/gen_status.py
@@ -0,0 +1,231 @@
+#!/usr/bin/env python
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import getopt
+import os
+import subprocess
+import sys
+
+import onnx
+
+import tensorflow as tf
+
+from onnx_tf import opset_version, __version__
+
+
+def main(docs_dir):
+ base_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+ docs_dir = os.path.join(base_dir, 'doc')
+ onnx_version = onnx.__version__
+ onnx_tf_release_build = False
+
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], 'h:mr',
+ ['onnx_master', 'onnx_tf_release_build'])
+ except getopt.GetoptError:
+ print('Usage:')
+ print(' gen_status.py [-m -r]')
+ print(' gen_status.py -h')
+ print('Description:')
+ print(' -m, --onnx_master installed ONNX is the latest master code')
+ print(' if omitted, ONNX version is onnx.__version__')
+ print(' -r, --onnx_tf_release_build create report for ONNX-TF release with version')
+ print(' stated in the VERSION_NUMBER file')
+ print(' if omitted, the report is for ONNX-TF master')
+ print(' -h show this help message and exit')
+ print('eg. 1. generate support_status.md for ONNX-TF master and ONNX onnx.__version__')
+ print(' gen_status.py')
+ print(' 2. generate support_status.md for ONNX-TF master and ONNX master')
+ print(' gen_status.py -m')
+ print(' 3. generate support_status_.md for ONNX-TF version')
+ print(' stated in the VERSION_NUMBER file and ONNX onnx.__version__ ')
+ print(' gen_status.py -r')
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print('Usage:')
+ print(' gen_status.py [-m -r]')
+ print(' gen_status.py -h')
+ print('Description:')
+ print(' -m, --onnx_master installed ONNX is the latest master code')
+ print(' if omitted, ONNX version is onnx.__version__')
+ print(' -r, --onnx_tf_release_build create report for ONNX-TF release with version')
+ print(' stated in the VERSION_NUMBER file')
+ print(' if omitted, the report is for ONNX-TF master')
+ print(' -h show this help message and exit')
+ print('eg. 1. generate support_status.md for ONNX-TF master and ONNX onnx.__version__')
+ print(' gen_status.py')
+ print(' 2. generate support_status.md for ONNX-TF master and ONNX master')
+ print(' gen_status.py -m')
+ print(' 3. generate support_status_.md for ONNX-TF version')
+ print(' stated in the VERSION_NUMBER file and ONNX onnx.__version__ ')
+ print(' gen_status.py -r')
+ sys.exit()
+ elif opt in ('-m', '--onnx_master'):
+ onnx_version = 'master'
+ elif opt in ('-r', '--onnx_tf_release_build'):
+ onnx_tf_release_build = True
+
+ gen_support_status(docs_dir, onnx_version, onnx_tf_release_build)
+
+
+def gen_support_status(docs_dir, onnx_version, onnx_tf_release_build):
+
+ # set filename
+ if onnx_tf_release_build:
+ onnx_tf_version = 'v' + __version__
+ filename = 'support_status_' + onnx_tf_version.replace('.', '_') + '.md'
+ else: # onnx-tf = master
+ # get onnx-tf commit id
+ onnx_tf_commit_id = subprocess.check_output('git rev-parse HEAD',
+ shell=True)
+ onnx_tf_commit_id = onnx_tf_commit_id.decode().strip('\n')
+ onnx_tf_version = 'Master ( commit id: {} )'.format(onnx_tf_commit_id)
+ filename = 'support_status.md'
+
+ with open(os.path.join(docs_dir, filename), 'w') as status_file:
+ status_file.write('# ONNX-Tensorflow Support Status\n')
+ status_file.write('|||\n')
+ status_file.write('|-:|:-|\n')
+ status_file.write('|ONNX-Tensorflow Version|{}|\n'.format(onnx_tf_version))
+
+ # get onnx commit id
+ if onnx_version == 'master':
+ onnx_commit_id = onnx.version.git_version
+ status_file.write(
+ '|ONNX Version|Master ( commit id: {} )|\n'.format(onnx_commit_id))
+ else:
+ status_file.write('|ONNX Version|v{}|\n'.format(onnx_version))
+
+ # get tf_version
+ status_file.write('|Tensorflow Version|v{}|\n\n'.format(tf.__version__))
+
+ # display the table legend
+ status_file.write('Notes:\n')
+ status_file.write('* Values that are new or updated from a ')
+ status_file.write('previous opset version are in bold.\n')
+ status_file.write('* -: not defined in corresponding ONNX ')
+ status_file.write('opset version\n')
+ status_file.write('* \*: the operator is deprecated\n')
+ status_file.write('* :small_red_triangle:: not supported yet\n')
+ status_file.write('* :small_orange_diamond:: partially supported\n')
+ status_file.write('* the rest are all supported\n\n')
+
+ # get oll onnx ops
+ onnx_ops = {}
+ for schema in onnx.defs.get_all_schemas():
+ if schema.domain == '': # only get onnx ops
+ onnx_ops[schema.name] = {
+ 'versions': [],
+ 'deprecated': schema.since_version if schema.deprecated else -1
+ }
+ for schema in onnx.defs.get_all_schemas_with_history():
+ if schema.domain == '': # only get onnx ops
+ op = onnx_ops[schema.name]
+ if schema.deprecated:
+ if schema.since_version <= op['deprecated']:
+ op['versions'].append(schema.since_version)
+ op['deprecated'] = schema.since_version
+ else:
+ op['versions'].append(schema.since_version)
+
+ # get all onnx-tf supported ops
+ onnx_tf_ops = opset_version.backend_opset_version
+ onnx_tf_ops_ps = opset_version.backend_partial_support
+
+ # get the cureent opset version
+ current_opset = onnx.defs.onnx_opset_version()
+
+ # setup table header
+ status_file.write('|||')
+ for i in range(current_opset):
+ status_file.write('|')
+ status_file.write('\n|:-:|:-:|')
+ for i in range(current_opset):
+ status_file.write(':-:|')
+ status_file.write('\n|**ONNX Operator**|')
+ for opset in range(1, current_opset + 1):
+ status_file.write('**Opset {}**|'.format(opset))
+ status_file.write('**ONNX Operator**|')
+
+ ops_count = len(onnx_ops)
+ # fill in data for the table
+ for key, val in sorted(onnx_ops.items()):
+ try:
+ status_file.write('\n|{}|'.format(key))
+ i = 0
+ vers = val['versions']
+ deprecated = val['deprecated']
+ for opset in range(1, current_opset + 1):
+ if i <= len(vers) - 1:
+ lb = vers[i]
+ ub = vers[i + 1] if i < len(vers) - 1 else vers[i]
+ if opset < lb:
+ if i == 0:
+ status_file.write('-')
+ elif opset == lb:
+ status_file.write('**{}**'.format(lb))
+ if lb >= deprecated and deprecated > 0:
+ status_file.write('\*')
+ elif lb not in onnx_tf_ops[key]:
+ status_file.write(':small_red_triangle:')
+ if opset == current_opset:
+ ops_count -= 1
+ elif key in onnx_tf_ops_ps:
+ status_file.write(':small_orange_diamond:')
+ else: # opset > lb
+ if opset < ub:
+ status_file.write('{}'.format(lb))
+ if lb >= deprecated and deprecated > 0:
+ status_file.write('\*')
+ elif lb not in onnx_tf_ops[key]:
+ status_file.write(':small_red_triangle:')
+ if opset == current_opset:
+ ops_count -= 1
+ elif key in onnx_tf_ops_ps:
+ status_file.write(':small_orange_diamond:')
+ elif opset == ub:
+ status_file.write('**{}**'.format(ub))
+ if ub >= deprecated and deprecated > 0:
+ status_file.write('\*')
+ elif ub not in onnx_tf_ops[key]:
+ status_file.write(':small_red_triangle:')
+ if opset == current_opset:
+ ops_count -= 1
+ elif key in onnx_tf_ops_ps:
+ status_file.write(':small_orange_diamond:')
+ i += 1
+ else: #opset > ub
+ status_file.write('{}'.format(ub))
+ if ub >= deprecated and deprecated > 0:
+ status_file.write('\*')
+ elif ub not in onnx_tf_ops[key]:
+ status_file.write(':small_red_triangle:')
+ if opset == current_opset:
+ ops_count -= 1
+ elif key in onnx_tf_ops_ps:
+ status_file.write(':small_orange_diamond:')
+ status_file.write('|')
+ status_file.write('{}|'.format(key))
+ except:
+ # ops defined in onnx but not in opset_version.backend_opset_versionn
+ status_file.write(':small_red_triangle:|')
+
+ status_file.write(
+ '\n\nONNX-TF Supported Operators / ONNX Operators: {} / {}'.format(
+ ops_count, len(onnx_ops)))
+
+ # display partial support footnote
+ status_file.write('\n\nNotes:\n')
+ index = 1
+ for key in onnx_tf_ops_ps:
+ status_file.write(
+ str(index) + '. ' + key + ': ' + onnx_tf_ops_ps[key] + '\n')
+ index += 1
+
+
+if __name__ == '__main__':
+ main(sys.argv[1:])
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/__init__.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/__init__.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..489c58e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/__init__.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/backend_handler.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/backend_handler.cpython-36.pyc
new file mode 100644
index 0000000..5e5947e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/backend_handler.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/handler.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/handler.cpython-36.pyc
new file mode 100644
index 0000000..440ea88
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/__pycache__/handler.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__init__.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__init__.py
new file mode 100644
index 0000000..5b372dc
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__init__.py
@@ -0,0 +1,7 @@
+import os
+import pkgutil
+
+__all__ = [
+ modname for _, modname, _ in pkgutil.walk_packages(
+ path=[os.path.split(__file__)[0]])
+]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/__init__.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/__init__.cpython-36.pyc
new file mode 100644
index 0000000..5318500
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/__init__.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/abs.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/abs.cpython-36.pyc
new file mode 100644
index 0000000..2c84a89
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/abs.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acos.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acos.cpython-36.pyc
new file mode 100644
index 0000000..dba76df
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acos.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acosh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acosh.cpython-36.pyc
new file mode 100644
index 0000000..e2ae849
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/acosh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/add.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/add.cpython-36.pyc
new file mode 100644
index 0000000..14da2e1
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/add.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/and.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/and.cpython-36.pyc
new file mode 100644
index 0000000..4c2e152
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/and.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_max.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_max.cpython-36.pyc
new file mode 100644
index 0000000..4bde5c3
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_max.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_min.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_min.cpython-36.pyc
new file mode 100644
index 0000000..13dd9a5
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/arg_min.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asin.cpython-36.pyc
new file mode 100644
index 0000000..92c91bf
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asinh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asinh.cpython-36.pyc
new file mode 100644
index 0000000..88b0284
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/asinh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atan.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atan.cpython-36.pyc
new file mode 100644
index 0000000..72cac64
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atan.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atanh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atanh.cpython-36.pyc
new file mode 100644
index 0000000..5447a8d
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/atanh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/average_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/average_pool.cpython-36.pyc
new file mode 100644
index 0000000..a0cffce
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/average_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/batch_normalization.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/batch_normalization.cpython-36.pyc
new file mode 100644
index 0000000..b05abcb
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/batch_normalization.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/bitshift.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/bitshift.cpython-36.pyc
new file mode 100644
index 0000000..3b5a49f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/bitshift.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/broadcast_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/broadcast_mixin.cpython-36.pyc
new file mode 100644
index 0000000..7582a39
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/broadcast_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cast.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cast.cpython-36.pyc
new file mode 100644
index 0000000..c72bac2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cast.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/ceil.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/ceil.cpython-36.pyc
new file mode 100644
index 0000000..d2f5d19
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/ceil.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/clip.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/clip.cpython-36.pyc
new file mode 100644
index 0000000..5ec0203
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/clip.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/compress.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/compress.cpython-36.pyc
new file mode 100644
index 0000000..e4ceb30
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/compress.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/concat.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/concat.cpython-36.pyc
new file mode 100644
index 0000000..72383b2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/concat.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant.cpython-36.pyc
new file mode 100644
index 0000000..ee4fc90
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_fill.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_fill.cpython-36.pyc
new file mode 100644
index 0000000..1acb557
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_fill.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_of_shape.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_of_shape.cpython-36.pyc
new file mode 100644
index 0000000..93b2e3e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/constant_of_shape.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/control_flow_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/control_flow_mixin.cpython-36.pyc
new file mode 100644
index 0000000..ff4ffe0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/control_flow_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv.cpython-36.pyc
new file mode 100644
index 0000000..33b9ca5
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_integer.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_integer.cpython-36.pyc
new file mode 100644
index 0000000..f7b1fba
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_integer.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_mixin.cpython-36.pyc
new file mode 100644
index 0000000..c8022b9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_transpose.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_transpose.cpython-36.pyc
new file mode 100644
index 0000000..bded52e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/conv_transpose.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cos.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cos.cpython-36.pyc
new file mode 100644
index 0000000..43cd732
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cos.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cosh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cosh.cpython-36.pyc
new file mode 100644
index 0000000..5d6dd6c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cosh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cumsum.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cumsum.cpython-36.pyc
new file mode 100644
index 0000000..b38b1ae
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/cumsum.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/depth_to_space.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/depth_to_space.cpython-36.pyc
new file mode 100644
index 0000000..05f7afc
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/depth_to_space.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dequantize_linear.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dequantize_linear.cpython-36.pyc
new file mode 100644
index 0000000..ff8fd92
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dequantize_linear.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/det.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/det.cpython-36.pyc
new file mode 100644
index 0000000..c056ede
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/det.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dilated_pooling.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dilated_pooling.cpython-36.pyc
new file mode 100644
index 0000000..1492b1c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dilated_pooling.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/div.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/div.cpython-36.pyc
new file mode 100644
index 0000000..078af91
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/div.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dropout.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dropout.cpython-36.pyc
new file mode 100644
index 0000000..8bd512f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dropout.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dynamic_quantize_linear.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dynamic_quantize_linear.cpython-36.pyc
new file mode 100644
index 0000000..8e66bb3
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/dynamic_quantize_linear.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/elu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/elu.cpython-36.pyc
new file mode 100644
index 0000000..1af147e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/elu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/equal.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/equal.cpython-36.pyc
new file mode 100644
index 0000000..b0699a9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/equal.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/erf.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/erf.cpython-36.pyc
new file mode 100644
index 0000000..9f70e7a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/erf.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/exp.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/exp.cpython-36.pyc
new file mode 100644
index 0000000..5c3586d
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/exp.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/expand.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/expand.cpython-36.pyc
new file mode 100644
index 0000000..a569bff
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/expand.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/eye_like.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/eye_like.cpython-36.pyc
new file mode 100644
index 0000000..3d719e8
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/eye_like.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/flatten.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/flatten.cpython-36.pyc
new file mode 100644
index 0000000..69ef543
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/flatten.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/floor.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/floor.cpython-36.pyc
new file mode 100644
index 0000000..f8a97ef
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/floor.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather.cpython-36.pyc
new file mode 100644
index 0000000..ae17c1e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_and_scatter_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_and_scatter_mixin.cpython-36.pyc
new file mode 100644
index 0000000..4387d0f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_and_scatter_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_elements.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_elements.cpython-36.pyc
new file mode 100644
index 0000000..8bf5004
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_elements.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_nd.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_nd.cpython-36.pyc
new file mode 100644
index 0000000..6da8da3
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gather_nd.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gemm.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gemm.cpython-36.pyc
new file mode 100644
index 0000000..a6bbedd
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gemm.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_average_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_average_pool.cpython-36.pyc
new file mode 100644
index 0000000..05fc028
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_average_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_lp_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_lp_pool.cpython-36.pyc
new file mode 100644
index 0000000..6942522
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_lp_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_max_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_max_pool.cpython-36.pyc
new file mode 100644
index 0000000..5f26408
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/global_max_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/greater.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/greater.cpython-36.pyc
new file mode 100644
index 0000000..f1ad833
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/greater.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gru.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gru.cpython-36.pyc
new file mode 100644
index 0000000..e537da7
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/gru.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hard_sigmoid.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hard_sigmoid.cpython-36.pyc
new file mode 100644
index 0000000..2d3da18
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hard_sigmoid.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hardmax.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hardmax.cpython-36.pyc
new file mode 100644
index 0000000..85ef71c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/hardmax.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/identity.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/identity.cpython-36.pyc
new file mode 100644
index 0000000..98a75fc
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/identity.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/if.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/if.cpython-36.pyc
new file mode 100644
index 0000000..2970622
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/if.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/image_scaler.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/image_scaler.cpython-36.pyc
new file mode 100644
index 0000000..91758e0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/image_scaler.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/instance_normalization.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/instance_normalization.cpython-36.pyc
new file mode 100644
index 0000000..3ff707a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/instance_normalization.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_inf.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_inf.cpython-36.pyc
new file mode 100644
index 0000000..1174bc9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_inf.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_nan.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_nan.cpython-36.pyc
new file mode 100644
index 0000000..6a9a0c2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/is_nan.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/leaky_relu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/leaky_relu.cpython-36.pyc
new file mode 100644
index 0000000..f0c09cf
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/leaky_relu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/less.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/less.cpython-36.pyc
new file mode 100644
index 0000000..4fb84c2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/less.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log.cpython-36.pyc
new file mode 100644
index 0000000..db3aff9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log_softmax.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log_softmax.cpython-36.pyc
new file mode 100644
index 0000000..998477c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/log_softmax.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/loop.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/loop.cpython-36.pyc
new file mode 100644
index 0000000..8345e40
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/loop.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_normalization.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_normalization.cpython-36.pyc
new file mode 100644
index 0000000..99b62f1
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_normalization.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_pool.cpython-36.pyc
new file mode 100644
index 0000000..a6b2366
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lp_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lrn.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lrn.cpython-36.pyc
new file mode 100644
index 0000000..a1fff88
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lrn.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lstm.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lstm.cpython-36.pyc
new file mode 100644
index 0000000..729bead
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/lstm.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul.cpython-36.pyc
new file mode 100644
index 0000000..3d1d228
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul_integer.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul_integer.cpython-36.pyc
new file mode 100644
index 0000000..07592d8
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mat_mul_integer.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/math_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/math_mixin.cpython-36.pyc
new file mode 100644
index 0000000..54e889f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/math_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max.cpython-36.pyc
new file mode 100644
index 0000000..06b7a0b
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_pool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_pool.cpython-36.pyc
new file mode 100644
index 0000000..018e437
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_pool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_unpool.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_unpool.cpython-36.pyc
new file mode 100644
index 0000000..a86e110
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/max_unpool.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean.cpython-36.pyc
new file mode 100644
index 0000000..882205b
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean_variance_normalization.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean_variance_normalization.cpython-36.pyc
new file mode 100644
index 0000000..ab76326
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mean_variance_normalization.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/min.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/min.cpython-36.pyc
new file mode 100644
index 0000000..ab01e88
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/min.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mod.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mod.cpython-36.pyc
new file mode 100644
index 0000000..d3984ab
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mod.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mul.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mul.cpython-36.pyc
new file mode 100644
index 0000000..15155fc
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/mul.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/neg.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/neg.cpython-36.pyc
new file mode 100644
index 0000000..76282ef
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/neg.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_max_suppression.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_max_suppression.cpython-36.pyc
new file mode 100644
index 0000000..33a1cae
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_max_suppression.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_zero.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_zero.cpython-36.pyc
new file mode 100644
index 0000000..0675da9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/non_zero.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/not.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/not.cpython-36.pyc
new file mode 100644
index 0000000..0f4be3b
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/not.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/onehot.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/onehot.cpython-36.pyc
new file mode 100644
index 0000000..a37d203
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/onehot.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/or.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/or.cpython-36.pyc
new file mode 100644
index 0000000..f10b43c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/or.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/p_relu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/p_relu.cpython-36.pyc
new file mode 100644
index 0000000..873630e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/p_relu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad.cpython-36.pyc
new file mode 100644
index 0000000..58c2590
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad_mixin.cpython-36.pyc
new file mode 100644
index 0000000..6d3ec33
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pad_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pool_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pool_mixin.cpython-36.pyc
new file mode 100644
index 0000000..cc98068
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pool_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pow.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pow.cpython-36.pyc
new file mode 100644
index 0000000..363d766
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/pow.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_conv.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_conv.cpython-36.pyc
new file mode 100644
index 0000000..25f8347
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_conv.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_mat_mul.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_mat_mul.cpython-36.pyc
new file mode 100644
index 0000000..462c749
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/q_linear_mat_mul.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/quantize_linear.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/quantize_linear.cpython-36.pyc
new file mode 100644
index 0000000..3d2bd16
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/quantize_linear.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal.cpython-36.pyc
new file mode 100644
index 0000000..caca6ff
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal_like.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal_like.cpython-36.pyc
new file mode 100644
index 0000000..4e5093a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_normal_like.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform.cpython-36.pyc
new file mode 100644
index 0000000..05e7fc7
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform_like.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform_like.cpython-36.pyc
new file mode 100644
index 0000000..0450ae9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/random_uniform_like.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/range.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/range.cpython-36.pyc
new file mode 100644
index 0000000..8002096
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/range.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reciprocal.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reciprocal.cpython-36.pyc
new file mode 100644
index 0000000..22ac765
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reciprocal.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l1.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l1.cpython-36.pyc
new file mode 100644
index 0000000..b6d1c60
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l1.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l2.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l2.cpython-36.pyc
new file mode 100644
index 0000000..72e628f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_l2.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum.cpython-36.pyc
new file mode 100644
index 0000000..f0c4b9e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum_exp.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum_exp.cpython-36.pyc
new file mode 100644
index 0000000..1af0147
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_log_sum_exp.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_max.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_max.cpython-36.pyc
new file mode 100644
index 0000000..cffa7bd
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_max.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_mean.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_mean.cpython-36.pyc
new file mode 100644
index 0000000..3a9524f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_mean.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_min.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_min.cpython-36.pyc
new file mode 100644
index 0000000..529420e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_min.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_prod.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_prod.cpython-36.pyc
new file mode 100644
index 0000000..13437f8
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_prod.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum.cpython-36.pyc
new file mode 100644
index 0000000..0965c11
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum_square.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum_square.cpython-36.pyc
new file mode 100644
index 0000000..1cf00d3
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reduce_sum_square.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/relu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/relu.cpython-36.pyc
new file mode 100644
index 0000000..b3074f3
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/relu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reshape.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reshape.cpython-36.pyc
new file mode 100644
index 0000000..e516800
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reshape.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/resize.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/resize.cpython-36.pyc
new file mode 100644
index 0000000..c8550f5
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/resize.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reverse_sequence.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reverse_sequence.cpython-36.pyc
new file mode 100644
index 0000000..0c19dbb
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/reverse_sequence.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn.cpython-36.pyc
new file mode 100644
index 0000000..1f13337
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn_mixin.cpython-36.pyc
new file mode 100644
index 0000000..bc1bdb8
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/rnn_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/round.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/round.cpython-36.pyc
new file mode 100644
index 0000000..f287a8a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/round.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan.cpython-36.pyc
new file mode 100644
index 0000000..04d4e9f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan_mixin.cpython-36.pyc
new file mode 100644
index 0000000..792ce41
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scan_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter.cpython-36.pyc
new file mode 100644
index 0000000..c91950f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_elements.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_elements.cpython-36.pyc
new file mode 100644
index 0000000..0273408
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_elements.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_nd.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_nd.cpython-36.pyc
new file mode 100644
index 0000000..c4b084a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/scatter_nd.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/selu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/selu.cpython-36.pyc
new file mode 100644
index 0000000..19049fb
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/selu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_at.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_at.cpython-36.pyc
new file mode 100644
index 0000000..dc8455f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_at.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_construct.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_construct.cpython-36.pyc
new file mode 100644
index 0000000..1f2afb8
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_construct.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_empty.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_empty.cpython-36.pyc
new file mode 100644
index 0000000..2b6e9f6
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_empty.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_erase.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_erase.cpython-36.pyc
new file mode 100644
index 0000000..b614716
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_erase.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_insert.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_insert.cpython-36.pyc
new file mode 100644
index 0000000..8727a12
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_insert.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_length.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_length.cpython-36.pyc
new file mode 100644
index 0000000..21b032d
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sequence_length.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shape.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shape.cpython-36.pyc
new file mode 100644
index 0000000..fdb8c1c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shape.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shrink.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shrink.cpython-36.pyc
new file mode 100644
index 0000000..c13fc44
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/shrink.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sigmoid.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sigmoid.cpython-36.pyc
new file mode 100644
index 0000000..fb45eb0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sigmoid.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sign.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sign.cpython-36.pyc
new file mode 100644
index 0000000..cfb6a1e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sign.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sin.cpython-36.pyc
new file mode 100644
index 0000000..f451c3c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sinh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sinh.cpython-36.pyc
new file mode 100644
index 0000000..ba51811
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sinh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/size.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/size.cpython-36.pyc
new file mode 100644
index 0000000..fa8bc0e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/size.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/slice.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/slice.cpython-36.pyc
new file mode 100644
index 0000000..2c7d862
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/slice.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softmax.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softmax.cpython-36.pyc
new file mode 100644
index 0000000..15e5fec
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softmax.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softplus.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softplus.cpython-36.pyc
new file mode 100644
index 0000000..606846c
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softplus.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softsign.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softsign.cpython-36.pyc
new file mode 100644
index 0000000..0c18f85
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/softsign.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/space_to_depth.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/space_to_depth.cpython-36.pyc
new file mode 100644
index 0000000..22e905a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/space_to_depth.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/split.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/split.cpython-36.pyc
new file mode 100644
index 0000000..6175ee5
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/split.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sqrt.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sqrt.cpython-36.pyc
new file mode 100644
index 0000000..5d37845
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sqrt.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/squeeze.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/squeeze.cpython-36.pyc
new file mode 100644
index 0000000..325b6b2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/squeeze.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sub.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sub.cpython-36.pyc
new file mode 100644
index 0000000..33d3f24
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sub.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sum.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sum.cpython-36.pyc
new file mode 100644
index 0000000..80b3cfe
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/sum.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tan.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tan.cpython-36.pyc
new file mode 100644
index 0000000..cfb704f
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tan.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tanh.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tanh.cpython-36.pyc
new file mode 100644
index 0000000..6a5bfe2
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tanh.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tfidf_vectorizer.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tfidf_vectorizer.cpython-36.pyc
new file mode 100644
index 0000000..617555a
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tfidf_vectorizer.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/thresholded_relu.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/thresholded_relu.cpython-36.pyc
new file mode 100644
index 0000000..0a25fdb
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/thresholded_relu.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tile.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tile.cpython-36.pyc
new file mode 100644
index 0000000..c770c7e
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/tile.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/top_k.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/top_k.cpython-36.pyc
new file mode 100644
index 0000000..7866e72
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/top_k.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/transpose.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/transpose.cpython-36.pyc
new file mode 100644
index 0000000..74655c0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/transpose.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unpool_mixin.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unpool_mixin.cpython-36.pyc
new file mode 100644
index 0000000..99c03aa
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unpool_mixin.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unsqueeze.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unsqueeze.cpython-36.pyc
new file mode 100644
index 0000000..3f7eac0
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/unsqueeze.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/upsample.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/upsample.cpython-36.pyc
new file mode 100644
index 0000000..4e15a6d
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/upsample.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/where.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/where.cpython-36.pyc
new file mode 100644
index 0000000..dab580d
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/where.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/xor.cpython-36.pyc b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/xor.cpython-36.pyc
new file mode 100644
index 0000000..bcb27a9
Binary files /dev/null and b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/__pycache__/xor.cpython-36.pyc differ
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/abs.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/abs.py
new file mode 100644
index 0000000..39e99a3
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/abs.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Abs")
+@tf_func(tf.abs)
+class Abs(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acos.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acos.py
new file mode 100644
index 0000000..7d1392e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acos.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Acos")
+@tf_func(tf.acos)
+class Acos(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acosh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acosh.py
new file mode 100644
index 0000000..d4ccd20
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/acosh.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Acosh")
+@tf_func(tf.acosh)
+class Acosh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/add.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/add.py
new file mode 100644
index 0000000..e306e6c
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/add.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Add")
+@tf_func(tf.add)
+class Add(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/and.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/and.py
new file mode 100644
index 0000000..8f0c6ae
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/and.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import LogicalMixin
+
+
+@onnx_op("And")
+@tf_func(tf.logical_and)
+class Add(LogicalMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_max.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_max.py
new file mode 100644
index 0000000..06ca43f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_max.py
@@ -0,0 +1,45 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.common.tf_helper import tf_shape
+
+
+@onnx_op("ArgMax")
+@tf_func(tf.argmax)
+class ArgMax(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ axis = node.attrs.get("axis", 0)
+ keepdims = node.attrs.get("keepdims", 1)
+ select_last_index = node.attrs.get("select_last_index", 0)
+ if select_last_index == 0:
+ arg_max = cls.make_tensor_from_onnx_node(node, **kwargs)
+ else:
+ # reverse the input and apply argmax on that to get last occurrence of max
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x = tf.reverse(x, axis=[axis])
+ arg_max = cls.make_tensor_from_onnx_node(node, inputs=[x], **kwargs)
+ # adjust indices to account for the reverse
+ arg_max = tf_shape(x)[axis] - arg_max - 1
+ if keepdims == 1:
+ return [tf.expand_dims(arg_max, axis=axis)]
+ return [arg_max]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_min.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_min.py
new file mode 100644
index 0000000..3ecb26f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/arg_min.py
@@ -0,0 +1,45 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.common.tf_helper import tf_shape
+
+
+@onnx_op("ArgMin")
+@tf_func(tf.argmin)
+class ArgMin(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ axis = node.attrs.get("axis", 0)
+ keepdims = node.attrs.get("keepdims", 1)
+ select_last_index = node.attrs.get("select_last_index", 0)
+ if select_last_index == 0:
+ arg_min = cls.make_tensor_from_onnx_node(node, **kwargs)
+ else:
+ # reverse the input and apply argmax on that to get last occurrence of max
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x = tf.reverse(x, axis=[axis])
+ arg_min = cls.make_tensor_from_onnx_node(node, inputs=[x], **kwargs)
+ # adjust indices to account for the reverse
+ arg_min = tf_shape(x)[axis] - arg_min - 1
+ if keepdims == 1:
+ return [tf.expand_dims(arg_min, axis=axis)]
+ return [arg_min]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asin.py
new file mode 100644
index 0000000..4a020c9
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asin.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Asin")
+@tf_func(tf.asin)
+class Asin(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asinh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asinh.py
new file mode 100644
index 0000000..beade1a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/asinh.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Asinh")
+@tf_func(tf.asinh)
+class Asinh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atan.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atan.py
new file mode 100644
index 0000000..a3b6517
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atan.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Atan")
+@tf_func(tf.atan)
+class Atan(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atanh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atanh.py
new file mode 100644
index 0000000..ba3407a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/atanh.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Atanh")
+@tf_func(tf.atanh)
+class Atanh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/average_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/average_pool.py
new file mode 100644
index 0000000..360727d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/average_pool.py
@@ -0,0 +1,28 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .pool_mixin import PoolMixin
+
+
+@onnx_op("AveragePool")
+class AveragePool(PoolMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ return cls.pool(node, kwargs["tensor_dict"], "AVG",
+ kwargs.get("strict", True))
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/batch_normalization.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/batch_normalization.py
new file mode 100644
index 0000000..92c6a24
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/batch_normalization.py
@@ -0,0 +1,73 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("BatchNormalization")
+@tf_func(tf.nn.batch_normalization)
+class BatchNormalization(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {
+ "default": {
+ "epsilon": 1e-5
+ },
+ "rename": {
+ "epsilon": "variance_epsilon"
+ }
+ }
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x_shape = x.get_shape().as_list()
+ x_rank = len(x_shape)
+
+ params_shape_broadcast = list([1, x_shape[1]] +
+ [1 for _ in range(2, x_rank)])
+ # process unknown channel shape
+ if params_shape_broadcast[1] is None:
+ params_shape_broadcast[1] = tf.shape(x)[1]
+ params_shape_broadcast = tf.stack(params_shape_broadcast)
+
+ total_num_dim = len(x.get_shape())
+ scale = tf.reshape(tensor_dict[node.inputs[1]], params_shape_broadcast)
+ bias = tf.reshape(tensor_dict[node.inputs[2]], params_shape_broadcast)
+ running_mean = tf.reshape(tensor_dict[node.inputs[3]],
+ params_shape_broadcast)
+ running_variance = tf.reshape(tensor_dict[node.inputs[4]],
+ params_shape_broadcast)
+
+ # from version 7, force to use test mode
+ if cls.SINCE_VERSION >= 7 or node.attrs.get("is_test", 0):
+ inputs = [x, running_mean, running_variance, bias, scale]
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs)]
+ spatial = node.attrs.get("spatial", 1) == 1
+ momentum = node.attrs.get("momentum", 0.9)
+ axis = [0] if spatial else [0] + list(range(2, total_num_dim))
+ mean, variance = tf.nn.moments(x, axis)
+ running_mean = running_mean * momentum + mean * (1 - momentum)
+ running_variance = running_variance * momentum + variance * (1 - momentum)
+ # TODO: need to conform to the documentation here
+ inputs = [x, running_mean, running_variance, bias, scale]
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/bitshift.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/bitshift.py
new file mode 100644
index 0000000..252f74d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/bitshift.py
@@ -0,0 +1,20 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("BitShift")
+class BitShift(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tf_func = tf.bitwise.left_shift if node.attrs.get(
+ "direction") == "LEFT" else tf.bitwise.right_shift
+ return [
+ cls.make_tensor_from_onnx_node(node, tf_func=tf_func, **kwargs)
+ ]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/broadcast_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/broadcast_mixin.py
new file mode 100644
index 0000000..4af769a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/broadcast_mixin.py
@@ -0,0 +1,45 @@
+import numpy as np
+import tensorflow as tf
+
+
+class BroadcastMixin(object):
+
+ @classmethod
+ def explicit_broadcast(cls, inputs, axis=None, tensor_dict=None):
+ x = inputs[0] if isinstance(inputs[0],
+ tf.Tensor) else tensor_dict[inputs[0]]
+ y = inputs[1] if isinstance(inputs[1],
+ tf.Tensor) else tensor_dict[inputs[1]]
+
+ if np.prod(y.shape) == 1:
+ return y
+
+ if not isinstance(x, tf.Tensor) or not isinstance(y, tf.Tensor):
+ raise ValueError("Targets for explicit broadcasting need to be Tensor.")
+
+ if axis is None:
+ return y
+
+ total_num_dim = len(x.get_shape())
+ if axis < 0:
+ axis += total_num_dim
+
+ if axis + len(y.get_shape()) == total_num_dim:
+ return y
+
+ dims = [axis + i for i in range(len(y.get_shape()))]
+ new_y = y
+ for i in range(total_num_dim):
+ if i not in dims:
+ new_y = tf.expand_dims(new_y, i)
+ return new_y
+
+ @classmethod
+ def limited_broadcast(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ y = tensor_dict[node.inputs[1]]
+ if node.attrs.get("broadcast") == 1:
+ y = cls.explicit_broadcast([x, y], node.attrs.get("axis", None))
+ return [cls.make_tensor_from_onnx_node(node, inputs=[x, y], **kwargs)]
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cast.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cast.py
new file mode 100644
index 0000000..b08a87b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cast.py
@@ -0,0 +1,44 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+
+
+@onnx_op("Cast")
+@tf_func(tf.cast)
+@partial_support(True)
+@ps_description("Cast string to float32/float64/int32/int64 " +
+ "are not supported in Tensorflow.")
+class Cast(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"rename": {"to": "dtype"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ inp = kwargs["tensor_dict"][node.inputs[0]]
+ to_type = node.attrs.get("to")
+
+ if to_type == tf.string:
+ return [tf.as_string(inp)]
+
+ if inp.dtype == tf.string:
+ if to_type not in [tf.float32, tf.float64, tf.int32, tf.int64]:
+ raise RuntimeError(
+ "Cast string to type {} is not supported in Tensorflow.".format(
+ to_type))
+ return [tf.strings.to_number(inp, to_type)]
+
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/ceil.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/ceil.py
new file mode 100644
index 0000000..45ea7f5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/ceil.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Ceil")
+@tf_func(tf.ceil)
+class Ceil(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/clip.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/clip.py
new file mode 100644
index 0000000..a3956f5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/clip.py
@@ -0,0 +1,70 @@
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+
+
+@onnx_op("Clip")
+@partial_support(True)
+@ps_description("Clip input in uint64 is not supported in Tensorflow.")
+class Clip(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ # uint64 cannot upcast to any tensorflow supported datatype
+ # for tf.clip_by_value that didn't lose precision
+ if x.dtype == tf.uint64:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Clip input, min and max in " + str(x.dtype) + " datatype",
+ "Tensorflow")
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x_dtype = x.dtype
+
+ if cls.SINCE_VERSION < 11:
+ # min/max were required and passed as attributes
+ clip_value_min = node.attrs.get("min", tf.reduce_min(x))
+ clip_value_max = node.attrs.get("max", tf.reduce_max(x))
+ else:
+ # min/max are optional and passed as inputs
+ clip_value_min = tensor_dict[node.inputs[1]] if len(
+ node.inputs) > 1 and node.inputs[1] != "" else x_dtype.min
+ clip_value_max = tensor_dict[node.inputs[2]] if len(
+ node.inputs) > 2 and node.inputs[2] != "" else x_dtype.max
+
+ # tf.clip_by_value doesn't support uint8, uint16, uint32, int8 and int16
+ # dtype for x, therefore need to upcast it to tf.int32 or tf.int64
+ if x_dtype in [tf.uint8, tf.uint16, tf.uint32, tf.int8, tf.int16]:
+ cast_to = tf.int64 if x_dtype == tf.uint32 else tf.int32
+ x = tf.cast(x, cast_to)
+ clip_value_min = tf.cast(clip_value_min, cast_to)
+ clip_value_max = tf.cast(clip_value_max, cast_to)
+ y = tf.clip_by_value(x, clip_value_min, clip_value_max)
+ y = tf.cast(y, x_dtype)
+ else:
+ y = tf.clip_by_value(x, clip_value_min, clip_value_max)
+
+ return [y]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/compress.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/compress.py
new file mode 100644
index 0000000..123f06c
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/compress.py
@@ -0,0 +1,36 @@
+import copy
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Compress")
+@tf_func(tf.gather)
+class Compress(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ condition = tensor_dict[node.inputs[1]]
+
+ x = tf.reshape(x, [-1]) if node.attrs.get("axis") is None else x
+
+ indices = tf.constant(list(range(condition.shape[0])), dtype=tf.int64)
+ not_zero = tf.not_equal(condition, tf.zeros_like(condition))
+ attrs['indices'] = tf.boolean_mask(indices, not_zero)
+
+ return [
+ cls.make_tensor_from_onnx_node(node, inputs=[x], attrs=attrs, **kwargs)
+ ]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/concat.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/concat.py
new file mode 100644
index 0000000..85a1ee7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/concat.py
@@ -0,0 +1,27 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Concat")
+@tf_func(tf.concat)
+class Concat(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ inputs = [kwargs["tensor_dict"][inp] for inp in node.inputs]
+ return [cls.make_tensor_from_onnx_node(node, inputs=[inputs])]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_4(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant.py
new file mode 100644
index 0000000..d009882
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant.py
@@ -0,0 +1,74 @@
+import numpy as np
+
+from onnx import numpy_helper
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.common import data_type
+
+
+@onnx_op("Constant")
+@tf_func(tf.constant)
+class Constant(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ attr_value = node.attrs["value"]
+ dtype = data_type.onnx2tf(attr_value.data_type)
+ value = numpy_helper.to_array(attr_value)
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[value], attrs={
+ "dtype": dtype
+ })
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ # either value or sparse_value
+ if "value" in node.attrs:
+ return cls._common(node, **kwargs)
+ else:
+ sparse_value = node.attrs["sparse_value"]
+ indices = numpy_helper.to_array(sparse_value.indices)
+ values = numpy_helper.to_array(sparse_value.values)
+ shape = np.array(sparse_value.dims)
+ return [tf.SparseTensor(indices, values, shape)]
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ if "value" in node.attrs or "sparse_value" in node.attrs:
+ return cls.version_11(node, **kwargs)
+ elif "value_float" in node.attrs:
+ value = node.attrs["value_float"]
+ dtype = tf.float32
+ elif "value_floats" in node.attrs:
+ value = node.attrs["value_floats"]
+ dtype = tf.float32
+ elif "value_int" in node.attrs:
+ value = node.attrs["value_int"]
+ dtype = tf.int64
+ elif "value_ints" in node.attrs:
+ value = node.attrs["value_ints"]
+ dtype = tf.int64
+ elif "value_string" in node.attrs:
+ value = node.attrs["value_string"]
+ dtype = tf.string
+ elif "value_strings" in node.attrs:
+ value = node.attrs["value_strings"]
+ dtype = tf.string
+ return [
+ cls.make_tensor_from_onnx_node(node,
+ inputs=[value],
+ attrs={"dtype": dtype})
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_fill.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_fill.py
new file mode 100644
index 0000000..e718fdb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_fill.py
@@ -0,0 +1,44 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("ConstantFill")
+@tf_func(tf.fill)
+class ConstantFill(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ if node.inputs and "shape" in node.attrs:
+ raise ValueError(
+ "Cannot set the shape argument and pass in an input at the same time."
+ )
+ if not node.inputs and "extra_shape" in node.attrs:
+ raise ValueError("Cannot set extra_shape when there is no input.")
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"value": 0.}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+
+ if "shape" in node.attrs:
+ shape = node.attrs["shape"]
+ else:
+ shape = tensor_dict[
+ node.inputs[0]].get_shape().as_list() if node.attrs.get(
+ "input_as_shape", 0) == 0 else tensor_dict[node.inputs[0]]
+
+ if "extra_shape" in node.attrs:
+ shape = tf.concat([shape, node.attrs["extra_shape"]], 0)
+
+ value = node.attrs.get("value", 0.)
+
+ if "dtype" in node.attrs:
+ return [tf.cast(tf.fill(shape, value), dtype=node.attrs["dtype"])]
+
+ return [cls.make_tensor_from_onnx_node(node, inputs=[shape], **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_of_shape.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_of_shape.py
new file mode 100644
index 0000000..27c228f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/constant_of_shape.py
@@ -0,0 +1,37 @@
+import copy
+
+import tensorflow as tf
+
+from onnx import numpy_helper
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("ConstantOfShape")
+@tf_func(tf.fill)
+class ConstantOfShape(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+
+ shape = kwargs["tensor_dict"][node.inputs[0]]
+
+ # make sure the shape dtype is either int32 or int64
+ if shape.dtype not in [tf.int64, tf.int32]:
+ shape = tf.cast(shape, tf.int64)
+
+ # the default value is 0, float32
+ if "value" in node.attrs:
+ attr_value = node.attrs["value"]
+ value = numpy_helper.to_array(attr_value)
+ attrs["value"] = value[0]
+ else:
+ attrs["value"] = 0.
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[shape], attrs=attrs, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/control_flow_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/control_flow_mixin.py
new file mode 100644
index 0000000..e1f0301
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/control_flow_mixin.py
@@ -0,0 +1,9 @@
+from .broadcast_mixin import BroadcastMixin
+
+
+class LogicalMixin(BroadcastMixin):
+ pass
+
+
+class ComparisonMixin(BroadcastMixin):
+ pass
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv.py
new file mode 100644
index 0000000..656b3d1
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv.py
@@ -0,0 +1,15 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .conv_mixin import ConvMixin
+
+
+@onnx_op("Conv")
+class Conv(ConvMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.conv(node, kwargs["tensor_dict"])
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls.conv(node, kwargs["tensor_dict"])
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_integer.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_integer.py
new file mode 100644
index 0000000..8018335
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_integer.py
@@ -0,0 +1,63 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .conv_mixin import ConvMixin
+
+
+@onnx_op("ConvInteger")
+class ConvInteger(ConvMixin, BackendHandler):
+
+ @classmethod
+ def _apply_zero_point(cls, base, zero_point):
+ base = tf.cast(base, tf.float32)
+ zero_point = tf.cast(zero_point, tf.float32)
+ return base - zero_point
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ w = tensor_dict[node.inputs[1]]
+
+ def process_conv(new_x, new_w):
+ # Remove zero-points from inputs
+ if len(node.inputs) == 4:
+ node.inputs.remove(node.inputs[3])
+ if len(node.inputs) == 3:
+ node.inputs.remove(node.inputs[2])
+
+ new_dict = { node.inputs[0]:new_x, node.inputs[1]:new_w }
+
+ # Use common conv handling
+ conv_node = cls.conv(node, new_dict)
+
+ return conv_node
+
+ # Apply x_zero_point first
+ x = cls._apply_zero_point(
+ x, tensor_dict[node.inputs[2]]) if len(node.inputs) > 2 else tf.cast(
+ x, tf.float32)
+
+ # Apply w_zero_point next
+ if len(node.inputs) == 4:
+ w_zero_point = tensor_dict[node.inputs[3]]
+ if w_zero_point.shape.rank == 0:
+ # Simply apply w_zero_point for scalar
+ w = cls._apply_zero_point(w, w_zero_point)
+ elif w_zero_point.shape.rank == 1:
+ # Need additional processing for 1d w_zero_point
+ tensor_list = []
+ process_shape = [1] + [w.shape[i] for i in range(1, len(w.shape))]
+ for i in range(w.shape.as_list()[0]):
+ # Apply w_zero_point for each element in 1d tensor
+ out_tensor = cls._apply_zero_point(w[i], w_zero_point[i])
+ tensor_list.append(tf.reshape(out_tensor, process_shape))
+ w = tf.concat(tensor_list, 0)
+ else:
+ raise ValueError("Unsupported w zero point: {}".format(w_zero_point))
+ else:
+ # Just cast without processing w
+ w = tf.cast(w, tf.float32)
+
+ return [tf.cast(process_conv(x, w)[0], tf.int32)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_mixin.py
new file mode 100644
index 0000000..9720c8a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_mixin.py
@@ -0,0 +1,274 @@
+import tensorflow as tf
+
+from onnx_tf.common import get_data_format
+from onnx_tf.common import get_perm_from_formats
+from onnx_tf.common import supports_device
+from onnx_tf.common import exception
+from .broadcast_mixin import BroadcastMixin
+from .pad_mixin import PadMixin
+
+# Constant string used to indicate that requested padding
+# is not natively supported in Tensorflow.
+PAD_TF_INCOMPATIBLE = "PAD_TF_INCOMPATIBLE"
+
+
+class ConvMixin(BroadcastMixin):
+
+ @classmethod
+ def conv(cls, node, input_dict, transpose=False):
+ """ Convolution method for both conv and transposed conv
+ For transposed conv,
+ Attr pads is not used for input, but declares how much output is padded.
+ Here, output means output from transposed conv which already pad output_padding if set.
+ So the pseudo explanation for output should be:
+ output = conv_transpose_output + output_padding - pads
+ And conv_transpose_output shape should be:
+ conv_transpose_output_shape[i] = strides[i] * (input_shape[i] - 1) + kernel_shape[i]
+ """
+ x = input_dict[node.inputs[0]]
+ x_rank = len(x.get_shape())
+ x_shape = x.get_shape().as_list()
+ spatial_size = x_rank - 2
+
+ support_cuda = supports_device("CUDA")
+ storage_format, compute_format = get_data_format(x_rank)
+ compute_c_idx = compute_format.find("C")
+ spatial_format = "".join([d for d in compute_format if d not in ["N", "C"]])
+
+ in_weights = input_dict[node.inputs[1]]
+ weights_rank = len(in_weights.get_shape())
+ if transpose:
+ # Translate weights from (C x M x KH x KW) to (KH x KW X M X C)
+ perm = list(range(2, weights_rank)) + [1, 0]
+ else:
+ # Translate weights from (M x C x KH x KW) to (KH x KW X C X M)
+ perm = list(range(2, weights_rank)) + [1, 0]
+
+ if "kernel_shape" in node.attrs.keys():
+ kernel_shape = node.attrs["kernel_shape"]
+ assert in_weights.get_shape().as_list()[2:] == kernel_shape, (
+ "kernel_shape "
+ "attr of convolution does not match the actual weight "
+ "passed to this operation, attr {}, actual {}").format(
+ kernel_shape,
+ in_weights.get_shape().as_list())
+ else:
+ kernel_shape = in_weights.get_shape().as_list()[2:]
+
+ weights = tf.transpose(in_weights, perm)
+ dilations = node.attrs.get("dilations", [1] * spatial_size)
+ strides = node.attrs.get("strides", [1] * spatial_size)
+
+ pads = node.attrs.get("pads", [0, 0] * spatial_size)
+
+ # Check auto_pad nonexistent or NOTSET first
+ if "auto_pad" not in node.attrs or node.attrs["auto_pad"] == "NOTSET":
+ if not transpose:
+ if pads != [0, 0] * spatial_size:
+ x = PadMixin.get_padding_as_op(x, pads)
+ pad_mode = "VALID"
+ else:
+ pad_mode = "NOTSET"
+ # Then we use auto_pad to setup pad_mode
+ elif node.attrs["auto_pad"] == "SAME_UPPER":
+ pad_mode = "SAME"
+ elif node.attrs["auto_pad"] == "VALID":
+ pad_mode = "VALID"
+ elif node.attrs["auto_pad"] == "SAME_LOWER":
+ pad_mode = PAD_TF_INCOMPATIBLE
+ else:
+ raise ValueError("Invalid auto_pad attribute: {}".format(
+ node.attrs["auto_pad"]))
+
+ # Currently auto_pad = SAME_LOWER is not supported
+ if pad_mode is PAD_TF_INCOMPATIBLE:
+ if transpose:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "ConvTranspose with auto_pad `SAME_LOWER`", "Tensorflow")
+ else:
+ exception.OP_UNSUPPORTED_EXCEPT("Conv with auto_pad `SAME_LOWER`",
+ "Tensorflow")
+
+ group = node.attrs.get("group", 1)
+
+ weight_groups = tf.split(weights, num_or_size_splits=group, axis=-1)
+
+ if support_cuda:
+ xs = tf.split(x, num_or_size_splits=group, axis=1)
+ else:
+ x = tf.transpose(
+ x, perm=get_perm_from_formats(storage_format, compute_format))
+ xs = tf.split(x, num_or_size_splits=group, axis=-1)
+
+ if transpose:
+ if dilations != [1] * spatial_size:
+ raise RuntimeError("Cannot set non-1 dilation for conv transpose.")
+ convolved = []
+ for (x, weight) in zip(xs, weight_groups):
+ x_spatial_shape = [
+ x_shape[storage_format.find(d)] for d in spatial_format
+ ]
+ weights_shape = weights.get_shape().as_list()
+ output_shape = node.attrs.get("output_shape", None)
+ conv_output_shape = [x_shape[storage_format.find("N")]]
+
+ # calculate output shape
+ if pad_mode == "NOTSET":
+ if output_shape is None:
+ conv_output_shape += [
+ strides[i] * x_spatial_shape[i] - strides[i] +
+ (kernel_shape[i] - 1) * dilations[i] + 1
+ for i in list(range(spatial_size))
+ ]
+ else:
+ conv_output_shape += [
+ s + pads[i] + pads[spatial_size + i]
+ for i, s in enumerate(output_shape[-2:])
+ ]
+ conv_output_shape.insert(compute_c_idx, weights_shape[-2])
+
+ def handle_dynamic_batch_size(output_shape, batch_idx):
+ output_shape[batch_idx] = tf.shape(x)[batch_idx]
+ return tf.stack(output_shape)
+
+ # process dynamic batch size
+ if conv_output_shape[storage_format.find("N")] is None:
+ batch_idx = storage_format.find("N")
+ conv_output_shape = handle_dynamic_batch_size(conv_output_shape,
+ batch_idx)
+
+ # make strides to match input rank
+ strides_full = [1] + strides
+ strides_full.insert(compute_c_idx, 1)
+
+ # get corresponding function in tf
+ if spatial_size == 1:
+ conv_func = tf.contrib.nn.conv1d_transpose
+ strides_full = strides[0]
+ elif spatial_size == 2:
+ conv_func = tf.nn.conv2d_transpose
+ elif spatial_size == 3:
+ conv_func = tf.nn.conv3d_transpose
+ else:
+ raise NotImplementedError(
+ "Transposed convolution for {}d is not implemented in Tensorflow".
+ format(spatial_size))
+
+ # use raw input x to do transposed conv
+ conv_rs = conv_func(
+ x,
+ weight,
+ conv_output_shape,
+ strides_full,
+ padding="VALID",
+ data_format=compute_format)
+
+ # pad output first by output_padding attr
+ if "output_padding" in node.attrs and output_shape is None:
+ output_padding = [[0, 0]
+ ] + [[0, p] for p in node.attrs["output_padding"]]
+ output_padding.insert(compute_c_idx, [0, 0])
+ conv_rs = tf.pad(conv_rs, output_padding)
+
+ # remove pads set in pads attr
+ conv_rs_shape = conv_rs.get_shape().as_list()
+ begin = [0] + pads[:spatial_size]
+ begin.insert(compute_c_idx, 0)
+ size = [
+ s if d in ["N", "C"] else s - pads[spatial_format.find(d)] -
+ pads[spatial_format.find(d) + spatial_size]
+ for d, s in zip(compute_format, conv_rs_shape)
+ ]
+
+ # process dynamic batch size
+ if size[compute_format.find("N")] is None:
+ batch_idx = compute_format.find("N")
+ size = handle_dynamic_batch_size(size, batch_idx)
+
+ conv_rs = tf.slice(conv_rs, begin=begin, size=size)
+
+ convolved.append(conv_rs)
+ else:
+ # No need to check pads if auto_pad is specifically provided.
+ # The assumption is that once auto_pad is provided as either VALID
+ # or SAME_UPPER (SAME_LOWER is currently not supported in TF) the
+ # output_shape will always be inferred. That is, the output_shape
+ # and output_padding will not be used in this case.
+ if pad_mode == "VALID":
+ conv_output_shape += [
+ strides[i] * (x_spatial_shape[i] - 1) + weights_shape[i]
+ for i in list(range(spatial_size))
+ ]
+ else:
+ conv_output_shape += [
+ strides[i] * x_spatial_shape[i]
+ for i in list(range(spatial_size))
+ ]
+ conv_output_shape.insert(compute_c_idx, weights_shape[-2])
+
+ # process dynamic batch size
+ if conv_output_shape[storage_format.find("N")] is None:
+ batch_idx = storage_format.find("N")
+ conv_output_shape = handle_dynamic_batch_size(conv_output_shape,
+ batch_idx)
+
+ # make strides to match input rank
+ strides_full = [1] + strides
+ strides_full.insert(compute_c_idx, 1)
+
+ # get corresponding function in tf
+ if spatial_size == 1:
+ conv_func = tf.contrib.nn.conv1d_transpose
+ strides_full = strides[0]
+ elif spatial_size == 2:
+ conv_func = tf.nn.conv2d_transpose
+ elif spatial_size == 3:
+ conv_func = tf.nn.conv3d_transpose
+ else:
+ raise NotImplementedError(
+ "Transposed convolution for {}d is not implemented in Tensorflow".
+ format(spatial_size))
+
+ # use raw input x to do transposed conv
+ conv_rs = conv_func(
+ x,
+ weight,
+ conv_output_shape,
+ strides_full,
+ padding=pad_mode,
+ data_format=compute_format)
+ convolved.append(conv_rs)
+
+ else:
+ convolved = [
+ tf.nn.convolution(
+ x,
+ weight,
+ pad_mode,
+ strides=strides,
+ dilation_rate=dilations,
+ data_format=compute_format)
+ for (x, weight) in zip(xs, weight_groups)
+ ]
+
+ if len(node.inputs) == 2:
+ if support_cuda:
+ output = tf.concat(convolved, axis=1)
+ else:
+ output = tf.concat(convolved, axis=-1)
+ output = tf.transpose(
+ output, perm=get_perm_from_formats(compute_format, storage_format))
+ else:
+ bias = input_dict[node.inputs[2]]
+ bias = cls.explicit_broadcast([x, bias], compute_c_idx)
+
+ if support_cuda:
+ output = tf.concat(convolved, axis=1)
+ output = tf.add(output, bias)
+ else:
+ output = tf.concat(convolved, axis=-1)
+ output = tf.add(output, bias)
+ output = tf.transpose(
+ output, perm=get_perm_from_formats(compute_format, storage_format))
+
+ return [output]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_transpose.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_transpose.py
new file mode 100644
index 0000000..6449a67
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/conv_transpose.py
@@ -0,0 +1,21 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .conv_mixin import ConvMixin
+
+
+@onnx_op("ConvTranspose")
+@partial_support(True)
+@ps_description("ConvTranspose with dilations != 1, or " +
+ "transposed convolution for 4D or higher " +
+ "are not supported in Tensorflow.")
+class ConvTranspose(ConvMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.conv(node, kwargs["tensor_dict"], transpose=True)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls.conv(node, kwargs["tensor_dict"], transpose=True)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cos.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cos.py
new file mode 100644
index 0000000..d996a73
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cos.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Cos")
+@tf_func(tf.cos)
+class Cos(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cosh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cosh.py
new file mode 100644
index 0000000..10ca889
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cosh.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Cosh")
+@tf_func(tf.cosh)
+class Cosh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cumsum.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cumsum.py
new file mode 100644
index 0000000..1b2d391
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/cumsum.py
@@ -0,0 +1,46 @@
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("CumSum")
+@tf_func(tf.math.cumsum)
+@partial_support(True)
+@ps_description(
+ "CumSum inputs in uint32/uint64 " + "are not supported in Tensorflow."
+)
+class CumSum(BackendHandler):
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ supported_dtype = [
+ tf.bfloat16, tf.half, tf.float32, tf.float64, tf.uint8, tf.uint16,
+ tf.int8, tf.int16, tf.int32, tf.int64, tf.complex64, tf.complex128
+ ]
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ if x.dtype not in supported_dtype:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "CumSum input in " + str(x.dtype) + " which", "Tensorflow")
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ inputs = [x]
+
+ if len(node.inputs) > 1:
+ # optional 0-D tensor, range [-rank(x), rank(x)-1]
+ axis = tensor_dict[node.inputs[1]]
+ inputs.append(axis)
+
+ attrs = {
+ "exclusive": bool(node.attrs.get("exclusive", 0)),
+ "reverse": bool(node.attrs.get("reverse", 0))
+ }
+
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs, attrs=attrs)]
+
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/depth_to_space.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/depth_to_space.py
new file mode 100644
index 0000000..9f743f7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/depth_to_space.py
@@ -0,0 +1,57 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.common import get_data_format
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("DepthToSpace")
+@tf_func(tf.depth_to_space)
+class DepthToSpace(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"rename": {"blocksize": "block_size"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ storage_format, compute_format = get_data_format(x_rank)
+ attrs = copy.deepcopy(node.attrs)
+ attrs["data_format"] = storage_format
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, attrs=attrs, c_first_cuda_only=True, **kwargs)
+ ]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ storage_format, compute_format = get_data_format(x_rank)
+ attrs = copy.deepcopy(node.attrs)
+ attrs["data_format"] = storage_format
+ mode = attrs.get("mode", "DCR")
+
+ if mode == "CRD":
+ # need native computation
+ bsize = attrs.get("blocksize")
+ batch, channel, height, width = x.shape
+ csize = channel // (bsize**2)
+
+ reshape_node = tf.reshape(x, [batch, csize, bsize, bsize, height, width])
+ transpose_node = tf.transpose(reshape_node, perm=[0, 1, 4, 2, 5, 3])
+ return [
+ tf.reshape(transpose_node,
+ [batch, csize, height * bsize, width * bsize])
+ ]
+
+ else:
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, attrs=attrs, c_first_cuda_only=True, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dequantize_linear.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dequantize_linear.py
new file mode 100644
index 0000000..4bf759f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dequantize_linear.py
@@ -0,0 +1,40 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("DequantizeLinear")
+class DequantizeLinear(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ if len(node.inputs) == 3:
+ x = tensor_dict[node.inputs[0]]
+ x_scale = tensor_dict[node.inputs[1]]
+ x_zero_point = tensor_dict[node.inputs[2]]
+ if x_scale.shape != x_zero_point.shape:
+ raise ValueError("DequantizeLinear x_scale(shape=" +
+ str(x_scale.shape) + ") and x_zero_point(shape=" +
+ str(x_zero_point.shape) +
+ ") must be in the same shape")
+ if x_zero_point.dtype != x.dtype:
+ raise ValueError("DequantizeLinear x_zero_point(" +
+ str(x_zero_point.dtype) + ") and x(" + str(x.dtype) +
+ ") must be in the same dtype")
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x = tf.cast(x, tf.float32)
+ x_scale = tensor_dict[node.inputs[1]]
+ if len(node.inputs) == 3 and x.dtype != tf.int32:
+ x_zero_point = tensor_dict[node.inputs[2]]
+ x_zero_point = tf.cast(x_zero_point, tf.float32)
+ x = tf.subtract(x, x_zero_point)
+
+ y = tf.multiply(x, x_scale)
+
+ return [y]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/det.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/det.py
new file mode 100644
index 0000000..863d8fb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/det.py
@@ -0,0 +1,16 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Det")
+@tf_func(tf.linalg.det)
+class Det(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return [
+ cls.make_tensor_from_onnx_node(node, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dilated_pooling.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dilated_pooling.py
new file mode 100644
index 0000000..14b3deb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dilated_pooling.py
@@ -0,0 +1,702 @@
+from __future__ import division
+
+import tensorflow as tf
+import numpy as np
+
+from onnx_tf.common import pooling_helper
+from onnx_tf.common.tf_helper import tf_shape
+from onnx_tf.common.tf_helper import tf_product
+
+
+class DilatedPooling(object):
+ """
+ This class implements two main methods:
+ dilated_pool:
+ calculates a max or average pool over the input
+
+ dilated_maxpool_with_argmax:
+ calculates a maxpool over the input and returns the
+ indices/argmax of the selected values
+
+ In addition to the standard features of pooling operations in
+ Tensorflow, these methods support dilations, ceil mode, SAME_LOWER and
+ explicit padding.
+
+ Dilations are partly supported in Tensorflow in `tf.nn.pool` and
+ `tf.nn.dilation2d`. The code will try to use the Tensoflow build-in
+ functions as much as poosible.
+
+ In cases, not supported by Tensorflow there is a custom algorith of
+ dilated pooling `_remove_dilations`.
+
+ The idea behind `_remove_dilations` is to transform the input N-D data
+ into a supported input for the standard tf.nn.pool operation.
+ This is achieved by calculating N-D indicies for the values which will
+ be selected from the input when applying the dilations and
+ then extracting the values using tf.gather_nd. Next step is to execute
+ `tf.nn.pool` on this new input data with **strides=kernel_shape** and
+ no dilations. The resulting pool will be the result we are looking for.
+
+ In case of `deilated_maxpool_with_argmax` an additional step is needed
+ to recalculated the resulting indices back into the original
+ data indices. It is done with `_calc_orig_argmax`
+
+ Here is a simple example of how the algorithm works:
+
+ kernel_shape = [3]
+ strides = [2]
+ dilations = [3]
+
+ Input 1D data:
+
+ x-----x-----x-----x-----x-----x-----x-----x-----x-----x-----x
+ | * | | ** | * | | ** | * | | ** | |
+ | 10 | 9 | 30 | 7 | 6 | 15 | 16 | 17 | 18 | 19 |
+ x-----x-----x-----x-----x-----x-----x-----x-----x-----x-----x
+ (0) (1) (2) (3) (4) (5) (6) (7) (8) (9)
+
+ where * represents the values selected during the first sliding window
+ step and ** during the second sliding window step
+
+ the resulting indices will be:
+
+ [0, 3, 6, 2, 5, 8]
+ | | | |
+ First Second
+ step step
+
+ after tf.gather_nd operation we get a new input data with
+ removed dilations:
+
+ [10, 7, 16, 30, 15, 18]
+
+ and apllying tf.nn.maxpool (or avgpool) with strides = kernel_shape = 3
+ will result into:
+
+ [16, 30]
+
+ which is the result of the dilated maxpooling.
+
+ Here is pseudo code of the algorithm with comments:
+
+ FUNCTION _remove_dilations:
+ /* Calculate N-D index of the values to be selected by the
+ dilations and strides */
+
+ /* Do a loop over the input spatial dimensions starting from the
+ last (most internal) going up to the first dimension
+
+ On every step of the loop calculate the input indices and
+ "combine" them with the already calculated indices from the
+ previous dimensions using cartesian product.
+ */
+ LOOP with **dimension** from **dimensions_count** to **0**:
+
+ // Initialize empty gather_nd index
+ gather_ind = []
+
+ // Calculate the output size for the current dimension
+ dim_filter_size = (dim_kernel_size - 1) * dim_dilations
+ dim_output_size = (((dim_input_size - dim_filter_size) //
+ dim_strides) + 1) * dim_kernel_size)
+
+ /* For every output index, calculate the corresponding index
+ into the input data */
+ dim_input_indices = range(0, dim_output_size)
+ dim_input_indices = calculate_input_indicies(dim_input_indices)
+
+ /* combine the calculated indices with the previous dimensions
+ */
+ gather_ind = cartesian_product(dim_input_indices, gather_ind)
+ END LOOP
+
+ /* For example for 2D input the resulting gather_ind will
+ look like this:
+
+ [[y1, x1], [y2, x2], ..., [yn, xm]]
+
+ where:
+ n is the height
+ m is the width and
+ [xi, yi] are the 2D indices in the input data
+ */
+
+ new_data = tf.gather_nd(input, gather_ind)
+
+ reshape new_data to the correct output shape
+
+ RETURN new_data
+
+
+ Before executing _remove_dilations the code will apply paddings to the
+ input data if needed. Padding is done using tf.pad with -inf values.
+ Check `_remove_dilations` code for more details explanation of the
+ implementation
+
+ In case of dilated_maxpool_with_argmax the returned indices from
+ tf.nn.max_pool_with_argmax will point into our "no dilations" data.
+ That is why they need to be mapped back to the original input data.
+ It is done with `_calc_orig_argmax` function which will apply the same
+ calculations, that are used in _remove_dilations when calculating the
+ input data indices from output indices (check `_calc_orig_argmax` for
+ detailed inline comments explaining the calculations)
+
+ """
+
+ def __init__(self,
+ input,
+ kernel_shape,
+ strides,
+ dilations,
+ padding="VALID",
+ ceil_mode=False,
+ count_include_pad=False,
+ pooling_type="MAX",
+ p=2):
+ self.input = tf.convert_to_tensor(input)
+
+ self.kernel_shape = kernel_shape
+ self.strides = strides
+ self.dilations = dilations
+ self.padding = padding
+ self.is_explicit_padding = type(padding) is list
+ self.ceil_mode = ceil_mode
+ self.count_include_pad = count_include_pad
+ self.pooling_type = pooling_type.upper()
+ self.p = p
+
+ self.is_known_shape = self.input.shape.is_fully_defined()
+ self.spatial_size = len(kernel_shape)
+ self.input_rank = self.spatial_size + 2
+
+ # if the rank is not defined, set it to the calculated input_rank
+ # rank should be known for ops like tf.gather_nd
+ if not input.shape.rank:
+ input.set_shape([None] * self.input_rank)
+ self.orig_input_shape = tf_shape(input)
+ self.input_shape = self.orig_input_shape
+
+ if pooling_type.startswith("MAX"):
+ self.padding_constant = input.dtype.min
+ else:
+ self.padding_constant = 0
+
+ def _calc_input_ind(self, output_ind, kernel, dilation, stride):
+ """
+ This function maps index from the output of _remove_dilations
+ to index from the original input along single axis. It calculates
+ the index inside the input data from the index of the output.
+ It is used to generate the correct indexes of the values to be
+ extracted by gather_nd.
+
+ Args:
+ output_ind: vector with indices from the output to be mapped
+ kernel: kernel size along the axis
+ dilation: dilations along the axis
+ stride: strides along the axis
+ Return:
+ input_ind: calculated indices
+
+ The formula is:
+ input_ind = (output_ind // kernel) * stride +
+ (output_ind % kernel) * dilation
+
+ Example:
+ If we have following 2D input to _remove_dilations:
+ [[ 0, 1, 2, 3],
+ [ 4, 5, 6, 7],
+ [ 8, 9, 10, 11],
+ [ 12, 13, 14, 15]]
+ and Kernel = [2, 2], Dilations: [2, 2], Strides: [1, 1]
+
+ the output of _remove_dilations will have shape [4, 4] and
+ _calc_input_ind will be called twice for the two axis 0 (along
+ height) and axis 1 (along width) with
+
+ output_ind = [0, 1, 2, 3]
+
+ which will result in:
+
+ input_ind = [0, 2, 1, 3]
+ """
+ return (output_ind // kernel) * (stride - kernel * dilation) + \
+ output_ind * dilation
+
+ def _calc_orig_argmax(self, ind):
+ """
+ Map result argxmax to the original input indices
+
+ Maps indices generated by maxpool_with_argmax on top of the
+ dilation reduced input to the orignal input indices
+ """
+
+ in_width = self.orig_input_shape[2]
+ num_channels = self.orig_input_shape[3]
+ output_width = self.output_shape[2]
+
+ # mod_floor op is not implemented on GPU
+ # implement it using: a % b = a - (a // b) * b
+
+ # inRow = (ind // num_channels) // output_width
+ # inCol = (ind // num_channels) % output_width
+ # ind_channel = ind % num_channels
+
+ ind_nhw = ind // num_channels
+
+ inRow = ind_nhw // output_width
+ inCol = ind_nhw - (ind_nhw // output_width) * output_width
+
+ ind_channel = ind - ind_nhw * num_channels
+
+ row = self._calc_input_ind(inRow, self.kernel_shape[0], self.dilations[0],
+ self.strides[0]) - self.pads[0]
+ col = self._calc_input_ind(inCol, self.kernel_shape[1], self.dilations[1],
+ self.strides[1]) - self.pads[2]
+
+ new_ind = num_channels * (row * in_width + col) + ind_channel
+ return new_ind
+
+ def _remove_dilations(self):
+ """
+ This method removes the dilations by extracting the values from
+ the input for every sliding window according to the dilations,
+ strides and kernel size and generates output that can be used by
+ pooling operations with strides = kernel_shape to accomplish
+ dilated pooling
+
+ Example:
+ Input: [[ 0, 1, 2, 3],
+ [ 4, 5, 6, 7],
+ [ 8, 9, 10, 11],
+ [ 12, 13, 14, 15]]
+
+ Kernel: [2, 2]
+ Dilations: [2, 2]
+ Strides: [1, 1]
+
+ Will return:
+ [[ 0, 2, 1, 3],
+ [ 8, 10, 9, 11],
+ [ 4, 6, 5, 7],
+ [ 12, 14, 13, 15]]
+
+ After max_pool2d with kernel_shape = strides = [2, 2]
+ the result is:
+ [[ 10, 11],
+ [ 14, 15]]
+ """
+
+ input_shape = tf_shape(self.input)
+ in_spatial_shape = input_shape[1:self.spatial_size + 1]
+
+ channels_count = input_shape[self.spatial_size + 1]
+ # Initialize gather_ind with the range of channels
+ # e.g. [0 1]
+ gather_ind = tf.range(channels_count, dtype=tf.int64)
+ # convert the vector to column vector
+ # in the following logic we use column vectors
+ gather_ind = tf.expand_dims(gather_ind, 1)
+
+ # initilize the output_shape with zeros
+ # self.output_shape will contain the shape of the
+ # output tensor after the loop below is executed
+ self.output_shape = [0] * (self.spatial_size + 2)
+ self.output_shape[0] = input_shape[0]
+ """
+ Loop over the input spatial dimensions starting from the
+ last (most internal) going up to the first dimension
+
+ On every step of the loop calculate the output indices and
+ map them to the input indices using `_calc_input_ind`,
+ then "combine" with the already calculated indices from the
+ previous dimensions using cartesian product.
+
+ For the following example input:
+
+ Input: [[ 0, 1, 2, 3],
+ [ 4, 5, 6, 7],
+ [ 8, 9, 10, 11],
+ [ 12, 13, 14, 15]]
+
+ Kernel: [2, 2]
+ Dilations: [2, 2]
+ Strides: [1, 1]
+
+ these are the steps that will be executed:
+
+ 1. Initilize gather_ind = [[0]] # we have only 1 channel
+
+ 2. Loop step 0 (axis 1):
+ filter_size = 3
+ output_size = 4
+ dim_ind = [[0]
+ [2]
+ [1]
+ [3]]
+
+ gather_ind = [[0 0]
+ [2 0]
+ [1 0]
+ [3 0]]
+
+ 3. Loop step 1 (axis 0):
+ filter_size = 3
+ output_size = 4
+ dim_ind = [[0]
+ [2]
+ [1]
+ [3]]
+
+ gather_ind = [[0 0 0]
+ [0 2 0]
+ [0 1 0]
+ [0 3 0]
+ [2 0 0]
+ [2 2 0]
+ [2 1 0]
+ [2 3 0]
+ [1 0 0]
+ [1 2 0]
+ [1 1 0]
+ [1 3 0]
+ [3 0 0]
+ [3 2 0]
+ [3 1 0]
+ [3 3 0]]
+
+ These are the indices used for gather_nd operation to collect
+ the values from the input data.
+ """
+
+ for dim in range(self.spatial_size - 1, -1, -1):
+ filter_size = (self.kernel_shape[dim] - 1) * \
+ self.dilations[dim] + 1
+ output_size = ((
+ (in_spatial_shape[dim] - filter_size) // self.strides[dim]) + 1
+ ) * self.kernel_shape[dim]
+ self.output_shape[dim + 1] = output_size
+
+ # initialize the output dimension index with the range of the
+ # dimension output size (e.g. 4): [0, 1, 2, 3]
+ dim_ind = tf.range(output_size)
+
+ # calculate the matching indices in the input data
+ # [0, 1, 2, 3] will calculate to [0, 2, 1, 3]
+ # from the above example
+ dim_ind = self._calc_input_ind(dim_ind, self.kernel_shape[dim],
+ self.dilations[dim], self.strides[dim])
+ # convert to column vector
+ dim_ind = tf.expand_dims(dim_ind, 1)
+
+ # "combine" current dimension indices with the previous dimensions
+ # using cartesian product
+ gather_ind = tf_product(dim_ind, gather_ind)
+
+ # The result from the above loop for 2D data will be:
+ # [[y1, x1, c], [y2, x2, c], ..., [yn, xm, c]] where n is the height,
+ # m is the width and c is the channel number.
+
+ # set the channels count in the output_shape
+ self.output_shape[self.spatial_size + 1] = channels_count
+
+ # expand the dimensions to match the input dimensions + 1
+ for x in range(self.spatial_size):
+ gather_ind = tf.expand_dims(gather_ind, 0)
+ # dublicate the indices for every batch
+ gather_ind = tf.tile(gather_ind,
+ [input_shape[0]] + [1] * (self.spatial_size + 1))
+
+ # extract the selected values from the input
+ output = tf.gather_nd(self.input, gather_ind, batch_dims=1)
+ # reshape the output to the correct shape calculated earlier
+ output = tf.reshape(output, self.output_shape)
+
+ return output
+
+ def _calc_pads_same(self, in_spatial_shape):
+ """
+ Calculate SAME_* paddings.
+ """
+
+ pad_ops = pooling_helper.pad_numpy_ops if self.is_known_shape else \
+ pooling_helper.pad_tf_ops
+
+ return pooling_helper.calc_pads_same(in_spatial_shape, self.kernel_shape,
+ self.strides, self.dilations,
+ self.padding, pad_ops, 2)
+
+ def _calc_pads_explicit(self):
+ """
+ Calculate explicit padding
+ """
+ assert type(self.padding) is list
+
+ pads = []
+ for i in range(self.spatial_size):
+ pads += [self.padding[i], self.padding[i + self.spatial_size]]
+ return pads
+
+ def _calc_pads_ceil_mode(self, in_spatial_shape):
+ """
+ Calculate padding in ceil_mode
+ """
+
+ pads = []
+ for i in range(self.spatial_size):
+ dim_size = in_spatial_shape[i]
+ filter_size = (self.kernel_shape[i] - 1) * self.dilations[i] + 1
+ out_size = (dim_size - filter_size) / self.strides[i]
+ if self.is_known_shape:
+ pad_size = (np.ceil(out_size) - np.floor(out_size)).astype(np.int64)
+ else:
+ pad_size = tf.cast(
+ tf.math.ceil(out_size) - tf.math.floor(out_size), tf.int64)
+
+ pads += [0, pad_size * self.strides[i]]
+ return pads
+
+ def _calc_pads(self, in_spatial_shape):
+ if self.is_known_shape:
+ pads = np.zeros([self.spatial_size * 2], np.int64)
+ else:
+ pads = tf.zeros([self.spatial_size * 2], tf.int64)
+
+ # check for explicit padding
+ if type(self.padding) is list:
+ pads += self._calc_pads_explicit()
+ elif self.padding.lower().startswith("same"):
+ pads += self._calc_pads_same(in_spatial_shape)
+
+ # when padding is set to SAME, ceil_mode will not do anything
+ # because output sizes will be multiple of the strides
+ if self.ceil_mode and (type(self.padding) is list or
+ not self.padding.lower().startswith("same")):
+ new_spatial_shape = [
+ in_spatial_shape[i] + pads[i * 2] + pads[i * 2 + 1]
+ for i in range(self.spatial_size)
+ ]
+ pads += self._calc_pads_ceil_mode(new_spatial_shape)
+ return pads
+
+ def _pad_input(self):
+ """
+ Pad the input according to the parameters
+ """
+ # check if we need to do any padding at all
+ if not self.ceil_mode and ((type(self.padding) is list and
+ self.padding == [0] * self.spatial_size * 2) or
+ self.padding == "VALID"):
+ self.pads = np.array([0] * self.spatial_size * 2)
+ return (self.input, self.pads)
+
+ in_spatial_shape = self.input_shape[1:self.spatial_size + 1]
+ pads = self._calc_pads(in_spatial_shape)
+
+ if self.is_known_shape and np.count_nonzero(pads) == 0:
+ self.pads = pads
+ return (self.input, pads)
+
+ tf_paddings = [[0, 0]]
+ for i in range(self.spatial_size):
+ tf_paddings += [[pads[i * 2], pads[i * 2 + 1]]]
+ tf_paddings += [[0, 0]]
+
+ self.input = tf.pad(
+ self.input,
+ tf_paddings,
+ mode='CONSTANT',
+ constant_values=self.padding_constant)
+ # update input shape and pads values
+ self.input_shape = tf_shape(self.input)
+ self.pads = pads
+
+ def _calc_argmax_without_padding(self, ind):
+ """
+ Calculate the original indices as they would be without padding
+ """
+ in_width = self.orig_input_shape[2]
+ padded_width = self.input_shape[2]
+ num_channels = self.input_shape[3]
+
+ # mod_floor op is not implemented on GPU
+ # implement it using: a % b = a - (a // b) * b
+
+ # ind_nhw = ind // num_channels
+ # ind_channel = ind % num_channels
+
+ ind_nhw = ind // num_channels
+ ind_channel = ind - ind_nhw * num_channels
+
+ new_ind = (ind_nhw // padded_width) * (self.pads[2] + self.pads[3])
+ new_ind = ind_nhw - new_ind - self.pads[0] * in_width - self.pads[2]
+ new_ind = num_channels * new_ind + ind_channel
+ return new_ind
+
+ def dilated_maxpool_with_argmax(self, force_custom_impl=False):
+ """
+ Do a dilated maxpool and return indices/argmax
+ """
+ # Tensorflow does not support maxpool_with_argmax on
+ # spatial_size != 2
+ assert self.spatial_size == 2
+
+ if list(self.dilations) != [1] * self.spatial_size or \
+ force_custom_impl:
+ # pad the input
+ self._pad_input()
+
+ new_input = self._remove_dilations()
+ kernel_shape = [1] + list(self.kernel_shape) + [1]
+ pooled, new_ind = tf.nn.max_pool_with_argmax(
+ new_input, ksize=kernel_shape, strides=kernel_shape, padding="VALID")
+ new_ind = self._calc_orig_argmax(new_ind)
+ else:
+ self.pads = np.array([0] * self.spatial_size * 2)
+ if type(self.padding) is list or \
+ self.padding.lower() == "same_lower":
+ # pad the input
+ self._pad_input()
+
+ padding_ = "VALID"
+ elif self.padding.lower() == "same_upper":
+ padding_ = "SAME"
+ else:
+ padding_ = self.padding
+
+ strides = [1] + list(self.strides) + [1]
+ kernel_shape = [1] + list(self.kernel_shape) + [1]
+ pooled, new_ind = tf.nn.max_pool_with_argmax(
+ self.input, ksize=kernel_shape, strides=strides, padding=padding_)
+ # if there was padding, recalculate the returned index
+ # to exclude the padding
+ if np.count_nonzero(self.pads) != 0:
+ new_ind = self._calc_argmax_without_padding(new_ind)
+
+ return (pooled, new_ind)
+
+ def _lp_pool(self, input, ksize, strides, padding):
+ window_size = np.prod(ksize)
+
+ input = tf.math.pow(tf.math.abs(input), self.p) * window_size
+ pooled = tf.nn.avg_pool_v2(input, ksize=ksize, strides=strides,
+ padding=padding)
+ pooled = tf.math.pow(pooled, 1.0 / self.p)
+
+ return pooled
+
+ def dilated_pool(self, force_custom_impl=False):
+ """
+ Does N-D dilated max/avg pooling. Pads the input if explicit or
+ SAME_* padding is provided or ceil_mode is True
+ """
+
+ assert self.is_supported()
+
+ if self.is_explicit_padding or self.padding.lower() == "same_lower" \
+ or (self.padding.lower() == "same_upper" and
+ self.count_include_pad) or self.pooling_type.upper() == "LP":
+ # pad the input
+ self._pad_input()
+
+ padding_ = "VALID"
+ elif self.padding.lower() == "same_upper":
+ padding_ = "SAME"
+ else:
+ padding_ = self.padding
+
+ # if maxpool op with dialtions != 1 and spatial_size == 2
+ # we can use tf.nn.dilation2d directly
+ if self.spatial_size == 2 and self.pooling_type.startswith("MAX") \
+ and self.dilations != [1] * self.spatial_size and \
+ not force_custom_impl:
+ strides = [1] + list(self.strides) + [1]
+ dilations = [1] + list(self.dilations) + [1]
+
+ filter = tf.zeros(
+ [self.kernel_shape[0], self.kernel_shape[1], self.input_shape[3]],
+ self.input.dtype)
+ pooled = tf.nn.dilation2d(
+ input=self.input,
+ filters=filter,
+ strides=strides,
+ dilations=dilations,
+ padding=padding_)
+ # if spatial_size < 4 and strides == 1 or dilation == 1 use tf.nn.pool
+ elif self.spatial_size < 4 and (self.strides == [1] * self.spatial_size or
+ self.dilations == [1] * self.spatial_size) and \
+ not force_custom_impl:
+ # if strides == 1 and not LpPool use tf.nn.pool directly
+ if self.strides == [1] * self.spatial_size and self.pooling_type != "LP":
+ pooled = tf.nn.pool(
+ self.input,
+ window_shape=self.kernel_shape,
+ dilations=self.dilations,
+ strides=self.strides,
+ padding=padding_,
+ pooling_type=self.pooling_type)
+ else:
+ # othwerwise check the pooling_type and use the correct op
+ if self.pooling_type.startswith("MAX"):
+ op = tf.nn.max_pool_v2
+ elif self.pooling_type == "AVG":
+ op = tf.nn.avg_pool_v2
+ elif self.pooling_type == "LP":
+ op = self._lp_pool
+ else:
+ raise ValueError("%d-D %s pooling is not supported." %
+ (self.spatial_size, self.pooling_type))
+ pooled = op(self.input, ksize=self.kernel_shape, strides=self.strides,
+ padding=padding_)
+ # in any other case we use custom implementation _remove_dilations
+ # to reduce atrous/dilated pooling into regular pooling and selecting
+ # only the values of the input that should have been selected by
+ # applying the strides and dilations. Then use tf.nn.pool with
+ # strides = kernel_shape and no dilations
+ else:
+ if padding_ == "SAME":
+ # pad the input
+ self._pad_input()
+ input_ = self._remove_dilations()
+ if self.pooling_type=="LP":
+ pooled = self._lp_pool(
+ input_,
+ ksize=self.kernel_shape,
+ strides=self.kernel_shape,
+ padding="VALID")
+
+ else:
+ pooled = tf.nn.pool(
+ input_,
+ window_shape=self.kernel_shape,
+ strides=self.kernel_shape,
+ padding="VALID",
+ pooling_type=self.pooling_type)
+ return pooled
+
+ def is_supported(self):
+ """
+ Function to check if the current set of arguments are
+ supported for average pool
+ """
+ # check for maxpool
+ if self.pooling_type.startswith("MAX") or \
+ self.pooling_type=="LP":
+ return True
+ else:
+ # if count_include_pad is true it is fully supported
+ if self.count_include_pad:
+ return True
+ # ceil mode is not supported
+ elif self.ceil_mode:
+ return False
+ # explicit padding with padding values set to 0 is supported
+ elif (self.is_explicit_padding and
+ self.padding == [0] * self.spatial_size * 2):
+ return True
+ # "valid" and "same_upper" auto padding is supported
+ elif (not self.is_explicit_padding and
+ self.padding.lower() in ["valid", "same_upper"]):
+ return True
+ # any other case is not supported
+ else:
+ return False
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/div.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/div.py
new file mode 100644
index 0000000..f4228a7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/div.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Div")
+@tf_func(tf.div)
+class Div(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dropout.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dropout.py
new file mode 100644
index 0000000..80184bb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dropout.py
@@ -0,0 +1,37 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Dropout")
+@tf_func(tf.nn.dropout)
+class Dropout(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ attrs = copy.deepcopy(node.attrs)
+ if cls.SINCE_VERSION >= 7 or attrs.pop("is_test", 0) == 1:
+ return [x]
+ attrs["keep_prob"] = 1 - attrs.pop("ratio", 0.5)
+ return [cls.make_tensor_from_onnx_node(node, attrs=attrs, **kwargs)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dynamic_quantize_linear.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dynamic_quantize_linear.py
new file mode 100644
index 0000000..d0b799a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/dynamic_quantize_linear.py
@@ -0,0 +1,39 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("DynamicQuantizeLinear")
+class DynamicQuantizeLinear(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ # A Function to fuse calculation for Scale, Zero Point and FP32->8Bit convertion of FP32 Input data.
+
+ # Scale is calculated as:
+ # y_scale = (max(x) - min(x))/(qmax - qmin)
+ # Zero point is calculated as:
+ # intermediate_zero_point = qmin - min(x)/y_scale
+ # y_zero_point = cast(round(saturate(intermediate_zero_point)))
+ # Data quantization formula is:
+ # y = saturate(round(x / y_scale) + y_zero_point)
+ # Only uint8 is supported, so saturation range is [0, 255]
+
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ dtype = tf.uint8
+ qmin = dtype.min
+ qmax = dtype.max
+ min_x = tf.math.minimum(0., tf.math.reduce_min(x))
+ max_x = tf.math.maximum(0., tf.math.reduce_max(x))
+ y_scale = (max_x - min_x) / (qmax - qmin)
+ intermediate_zero_point = qmin - (min_x / y_scale)
+ y_zero_point = tf.clip_by_value(tf.round(intermediate_zero_point), qmin,
+ qmax)
+ y = tf.cast(
+ tf.clip_by_value((tf.round(x / y_scale) + y_zero_point), qmin, qmax),
+ dtype)
+
+ return [y, y_scale, tf.cast(y_zero_point, dtype)]
+
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/elu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/elu.py
new file mode 100644
index 0000000..b375e12
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/elu.py
@@ -0,0 +1,30 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Elu")
+@tf_func(tf.nn.elu)
+class Elu(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ alpha = node.attrs.get("alpha", 1.0)
+ if alpha != 1.0:
+ return [
+ tf.cast(x < 0.0, tf.float32) * alpha * (tf.exp(x) - 1.0) +
+ tf.cast(x >= 0.0, tf.float32) * x
+ ]
+ else:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/equal.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/equal.py
new file mode 100644
index 0000000..f30a978
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/equal.py
@@ -0,0 +1,41 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .control_flow_mixin import ComparisonMixin
+
+
+@onnx_op("Equal")
+@tf_func(tf.equal)
+@partial_support(True)
+@ps_description("Equal inputs in uint16/uint32/uint64 " +
+ "are not supported in Tensorflow.")
+class Equal(ComparisonMixin, BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ supported_dtype = [
+ tf.bfloat16, tf.half, tf.float32, tf.float64, tf.uint8,
+ tf.int8, tf.int16, tf.int32, tf.int64, tf.complex64,
+ tf.quint8, tf.qint8, tf.qint32, tf.string, tf.bool,
+ tf.complex128
+ ]
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ if x.dtype not in supported_dtype:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Equal inputs in " + str(x.dtype) + " which", "Tensorflow")
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/erf.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/erf.py
new file mode 100644
index 0000000..150ed35
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/erf.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Erf")
+@tf_func(tf.erf)
+class Erf(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/exp.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/exp.py
new file mode 100644
index 0000000..50ca20d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/exp.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Exp")
+@tf_func(tf.exp)
+class Exp(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/expand.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/expand.py
new file mode 100644
index 0000000..ddfcd5a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/expand.py
@@ -0,0 +1,22 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("Expand")
+class Expand(BackendHandler):
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x, shape = tensor_dict[node.inputs[0]], tensor_dict[node.inputs[1]]
+
+ # tf.math.multiply does not support bool therefore use int8
+ if x.dtype is tf.bool:
+ ones = tf.ones(shape, dtype=tf.int8)
+ r = tf.cast(x, tf.int8) * ones
+ return [tf.cast(r, tf.bool)]
+ else:
+ ones = tf.ones(shape, dtype=x.dtype)
+ return [x * ones]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/eye_like.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/eye_like.py
new file mode 100644
index 0000000..d49274b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/eye_like.py
@@ -0,0 +1,41 @@
+import copy
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("EyeLike")
+class EyeLike(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+
+ attrs = copy.deepcopy(node.attrs)
+ inp = kwargs["tensor_dict"][node.inputs[0]]
+ dtype = attrs.pop("dtype", inp.dtype)
+ offset = attrs.pop("k", 0)
+ shape = inp.shape.as_list()
+
+ # calculate upper and lower bound of max eye shape
+ max_eye_shape_ub = shape[1] if offset > 0 else shape[0]
+ max_eye_shape_lb = shape[0] if offset > 0 else shape[1]
+ offset = max_eye_shape_ub * np.sign(offset) if abs(
+ offset) > max_eye_shape_ub else offset
+ abs_offset = abs(offset)
+ eye_shape = min(max_eye_shape_ub - abs_offset, max_eye_shape_lb)
+ tensor = tf.eye(eye_shape, num_columns=eye_shape, dtype=dtype)
+ if offset > 0:
+ tb_paddings = [0, shape[0] - eye_shape]
+ lr_paddings = [offset, shape[1] - offset - eye_shape]
+ else:
+ tb_paddings = [abs_offset, shape[0] - abs_offset - eye_shape]
+ lr_paddings = [0, shape[1] - eye_shape]
+ paddings = tf.constant([tb_paddings, lr_paddings], dtype=tf.int32)
+ attrs["paddings"] = paddings
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, tf_func=tf.pad, inputs=[tensor], attrs=attrs, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/flatten.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/flatten.py
new file mode 100644
index 0000000..70f1669
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/flatten.py
@@ -0,0 +1,39 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Flatten")
+@tf_func(tf.layers.flatten)
+class Flatten(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ shape = tf.shape(x)
+ x_rank = len(x.shape)
+ axis = node.attrs.get("axis", 1)
+
+ if axis == 1 and x_rank > 1:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ if axis == 0:
+ cal_shape = (1, -1)
+ else:
+ cal_shape = (tf.reduce_prod(shape[0:axis]),
+ tf.reduce_prod(shape[axis:tf.size(shape)]))
+ return [tf.reshape(x, cal_shape)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/floor.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/floor.py
new file mode 100644
index 0000000..8aa7dff
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/floor.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Floor")
+@tf_func(tf.floor)
+class Floor(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather.py
new file mode 100644
index 0000000..c049d8e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather.py
@@ -0,0 +1,29 @@
+import copy
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .gather_and_scatter_mixin import GatherAndScatterMixin
+
+
+@onnx_op("Gather")
+@tf_func(tf.gather)
+class Gather(GatherAndScatterMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ indices = kwargs["tensor_dict"][node.inputs[1]]
+ attrs = copy.deepcopy(node.attrs)
+ axis = attrs.get("axis", 0)
+ result = cls.chk_idx_out_of_bounds_along_axis(x, axis, indices)
+ msg = 'Gather indices are out of bounds, please double check the indices and retry.'
+ with tf.control_dependencies([tf.compat.v1.assert_equal(result, True, message=msg)]):
+ indices = cls.process_neg_idx_along_axis(x, axis, indices)
+ attrs['axis'] = axis
+ return [cls.make_tensor_from_onnx_node(node, attrs=attrs, inputs=[x, indices], **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_and_scatter_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_and_scatter_mixin.py
new file mode 100644
index 0000000..92bbaf1
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_and_scatter_mixin.py
@@ -0,0 +1,79 @@
+import tensorflow as tf
+try:
+ from tensorflow.math import floormod as tf_floormod
+except ImportError:
+ from tensorflow import floormod as tf_floormod
+
+from onnx_tf.common.tf_helper import tf_shape
+
+class GatherAndScatterMixin(object):
+
+ @classmethod
+ def chk_idx_out_of_bounds(cls, data, indices):
+ """ Check indices out of bounds for ScatterND and GatherND
+ In Tensorflow GPU version, if an out of bound index is found,
+ a 0 is stored in the corresponding output value for GatherND;
+ and the index is ignored for ScatterND/TensorScatterNDUpdate.
+ But ONNX spec state that it is an error if any index values
+ are out of bounds. Therefore the converter need to run this
+ function to verify all the indices are in bounds before send
+ it to Tensoflow. If out of bound is detected then the caller
+ of this function need to throw InvalidArgumentError exception.
+ """
+ data_shape = tf_shape(data)
+ indices_shape = tf_shape(indices)
+
+ def _chk_idx_out_of_bounds(i, result):
+ indices_i = tf.transpose(indices)[i]
+ limit_i = tf.cast(data_shape, indices.dtype)[i]
+ cond1 = tf.greater_equal(indices_i, tf.negative(limit_i))
+ cond2 = tf.less(indices_i, limit_i)
+ result = tf.reduce_all(tf.logical_and(cond1, cond2))
+ return i + 1, result
+
+ _, result = tf.while_loop(
+ lambda i, result: tf.logical_and(tf.less(i, indices_shape[-1]), result),
+ _chk_idx_out_of_bounds, [0, True])
+ return result
+
+ @classmethod
+ def chk_idx_out_of_bounds_along_axis(cls, data, axis, indices):
+ """ Check indices out of bounds for ScatterElement
+ In Tensorflow GPU version, if an out of bound index is found,
+ the index is ignored for ScatterND/TensorScatterNDUpdate.
+ But ONNX spec state that it is an error if any index values
+ are out of bounds. Therefore the converter need to run this
+ function to verify all the indices are in bounds along the
+ axis before send it to Tensoflow. If out of bound is detected
+ then the caller of this function need to throw
+ InvalidArgumentError exception.
+ """
+ data_shape = tf.cast(tf_shape(data), indices.dtype)
+ limit = data_shape[axis]
+ cond1 = tf.greater_equal(indices, tf.negative(limit))
+ cond2 = tf.less(indices, limit)
+ return tf.logical_and(cond1, cond2)
+
+ @classmethod
+ def process_neg_idx(cls, data, indices):
+ """ Convert all the negative indices to positive
+ GatherND and ScatterND/TensorScatterNDUpdate in Tensorflow
+ doesn't support negative indices. Therefore need to run this
+ function to convert all the negative indices to positive before
+ send it to Tensorflow.
+ """
+ data_shape = tf_shape(data)
+ indices_shape = tf_shape(indices)
+ max_i = tf.cast(data_shape[:indices_shape[-1]], indices.dtype)
+ return tf_floormod(tf.add(indices, max_i), max_i)
+
+ @classmethod
+ def process_neg_idx_along_axis(cls, data, axis, indices):
+ """ Convert all the negative indices to positive
+ ScatterND/TensorScatterNDUpdate in Tensorflow doesn't support
+ negative indices. Therefore need to run this function to convert
+ all the negative indices to positive before send it to Tensorflow.
+ """
+ data_shape = tf_shape(data)
+ max_i = tf.cast(data_shape[axis], indices.dtype)
+ return tf_floormod(tf.add(indices, max_i), max_i)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_elements.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_elements.py
new file mode 100644
index 0000000..d030858
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_elements.py
@@ -0,0 +1,58 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .gather_and_scatter_mixin import GatherAndScatterMixin
+
+
+@onnx_op("GatherElements")
+class GatherElements(GatherAndScatterMixin, BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ # GatherElements takes two inputs data and indices of the same rank r >= 1 and an optional attribute axis that identifies
+ # an axis of data (by default, the outer-most axis, that is axis 0). It is an indexing operation that produces its output by
+ # indexing into the input data tensor at index positions determined by elements of the indices tensor. Its output shape is the
+ # same as the shape of indices and consists of one value (gathered from the data) for each element in indices.
+
+ axis = node.attrs.get("axis", 0)
+ data = kwargs["tensor_dict"][node.inputs[0]]
+ indices = kwargs["tensor_dict"][node.inputs[1]]
+
+ # poocess negative axis
+ axis = axis if axis >= 0 else tf.add(tf.rank(data), axis)
+
+ # check are there any indices are out of bounds
+ result = cls.chk_idx_out_of_bounds_along_axis(data, axis, indices)
+ msg = 'GatherElements indices are out of bounds,'\
+ ' please double check the indices and retry.'
+ with tf.control_dependencies(
+ [tf.compat.v1.assert_equal(result, True, message=msg)]):
+ # process negative indices
+ indices = cls.process_neg_idx_along_axis(data, axis, indices)
+
+ # adapted from reference implementation in onnx/onnx/backend/test/case/node/gatherelements.py
+ if axis == 0:
+ axis_perm = tf.range(tf.rank(data))
+ data_swaped = data
+ index_swaped = indices
+ else:
+ axis_perm = tf.tensor_scatter_nd_update(tf.range(tf.rank(data)),
+ tf.constant([[0], [axis]]),
+ tf.constant([axis, 0]))
+ data_swaped = tf.transpose(data, perm=axis_perm)
+ index_swaped = tf.transpose(indices, perm=axis_perm)
+
+ idx_tensors_per_axis = tf.meshgrid(*list(
+ map(lambda x: tf.range(x, dtype=index_swaped.dtype),
+ index_swaped.shape.as_list())),
+ indexing='ij')
+ idx_tensors_per_axis[0] = index_swaped
+ dim_expanded_idx_tensors_per_axis = list(
+ map(lambda x: tf.expand_dims(x, axis=-1), idx_tensors_per_axis))
+ index_expanded = tf.concat(dim_expanded_idx_tensors_per_axis, axis=-1)
+
+ gathered = tf.gather_nd(data_swaped, index_expanded)
+ y = tf.transpose(gathered, perm=axis_perm)
+
+ return [y]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_nd.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_nd.py
new file mode 100644
index 0000000..3a9d86e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gather_nd.py
@@ -0,0 +1,20 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .gather_and_scatter_mixin import GatherAndScatterMixin
+
+
+@onnx_op("GatherND")
+class GatherND(GatherAndScatterMixin, BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ data = kwargs["tensor_dict"][node.inputs[0]]
+ indices = kwargs["tensor_dict"][node.inputs[1]]
+
+ result = cls.chk_idx_out_of_bounds(data, indices)
+ msg = 'GatherND indices are out of bounds, please double check the indices and retry.'
+ with tf.control_dependencies([tf.compat.v1.assert_equal(result, True, message=msg)]):
+ indices = cls.process_neg_idx(data, indices)
+ return [tf.gather_nd(data, indices)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gemm.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gemm.py
new file mode 100644
index 0000000..72983c4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gemm.py
@@ -0,0 +1,48 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("Gemm")
+class Gemm(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x = tf.layers.flatten(x)
+ y = tensor_dict[node.inputs[1]]
+
+ if len(node.inputs) > 2:
+ z = tensor_dict[node.inputs[2]]
+ else:
+ z = 0
+
+ if node.attrs.get("transA", 0):
+ x = tf.transpose(x)
+ if node.attrs.get("transB", 0):
+ y = tf.transpose(y)
+ alpha = node.attrs.get("alpha", 1.0)
+ beta = node.attrs.get("beta", 1.0)
+ return [alpha * tf.matmul(x, y) + beta * z]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_average_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_average_pool.py
new file mode 100644
index 0000000..5812bb2
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_average_pool.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("GlobalAveragePool")
+class GlobalAveragePool(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ dims = tf.range(tf.rank(x))
+ _, dim_window = tf.split(dims, [2, tf.size(dims) - 2])
+ return [tf.reduce_mean(x, axis=dim_window, keep_dims=True)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_lp_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_lp_pool.py
new file mode 100644
index 0000000..2b2b374
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_lp_pool.py
@@ -0,0 +1,26 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("GlobalLpPool")
+class GlobalLpPool(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ p = node.attrs.get("p", 2.)
+ dims = list(range(len(x.shape)))
+ dim_window = dims[2:]
+ if len(dim_window) > 1 and p == 2:
+ p = "euclidean"
+ return [tf.norm(x, ord=p, axis=dim_window, keepdims=True)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_2(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_max_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_max_pool.py
new file mode 100644
index 0000000..3a3dfb3
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/global_max_pool.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("GlobalMaxPool")
+class GlobalMaxPool(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ dims = tf.range(tf.rank(x))
+ _, dim_window = tf.split(dims, [2, tf.size(dims) - 2])
+ return [tf.reduce_max(x, axis=dim_window, keep_dims=True)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/greater.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/greater.py
new file mode 100644
index 0000000..32527f8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/greater.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import ComparisonMixin
+
+
+@onnx_op("Greater")
+@tf_func(tf.greater)
+class Greater(ComparisonMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gru.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gru.py
new file mode 100644
index 0000000..c0e6cf5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/gru.py
@@ -0,0 +1,202 @@
+from functools import partial
+
+import tensorflow as tf
+
+from onnx_tf.common import get_unique_suffix
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .rnn_mixin import RNNMixin
+
+
+@onnx_op("GRU")
+@partial_support(True)
+@ps_description("GRU with clip or GRU with linear_before_reset, or " +
+ "GRU not using sigmoid for z and r, or " +
+ "GRU using Elu as the activation function " +
+ "with alpha != 1, or " +
+ "GRU using HardSigmoid as the activation function " +
+ "with alpha != 0.2 or beta != 0.5 " +
+ "are not supported in TensorFlow.")
+class GRU(RNNMixin, BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ direction = node.attrs.get("direction", "forward")
+ num_directions = 2 if direction == "bidirectional" else 1
+ if "clip" in node.attrs:
+ exception.OP_UNSUPPORTED_EXCEPT("GRU with clip", "Tensorflow")
+ if node.attrs.get("linear_before_reset", 0):
+ exception.OP_UNSUPPORTED_EXCEPT("GRU with linear_before_reset",
+ "Tensorflow")
+ if "activations" in node.attrs:
+ activations = list(map(lambda x: x.lower(), node.attrs["activations"]))
+ if activations[0] != "sigmoid":
+ exception.OP_UNSUPPORTED_EXCEPT("GRU without sigmoid for `z` and `r`",
+ "Tensorflow")
+ if num_directions == 2:
+ if activations[2] != "sigmoid":
+ exception.OP_UNSUPPORTED_EXCEPT("GRU without sigmoid for `z` and `r`",
+ "Tensorflow")
+
+ @classmethod
+ def _custom_getter(cls,
+ getter,
+ name,
+ node=None,
+ tensor_dict=None,
+ is_bidirectional=None,
+ *args,
+ **kwargs):
+ names = name.split("/")
+ if is_bidirectional:
+ if "fw" in names:
+ index = 0
+ elif "bw" in names:
+ index = 1
+ else:
+ raise RuntimeError("Can not get {} for bidirectional. "
+ "Either fw and bw is not in name scope.".format(
+ names[-1]))
+ if names[-1] == "kernel":
+ # onnx W[zrh], R[zrh]
+ if is_bidirectional:
+ w = tf.split(tensor_dict[node.inputs[1]], 2)[index]
+ r = tf.split(tensor_dict[node.inputs[2]], 2)[index]
+ else:
+ w = tensor_dict[node.inputs[1]]
+ r = tensor_dict[node.inputs[2]]
+ w_z, w_r, w_h = tf.split(tf.squeeze(w), 3)
+ r_z, r_r, r_h = tf.split(tf.squeeze(r), 3)
+ if names[-2] == "gates":
+ new_w = tf.transpose(tf.concat([w_r, w_z], 0))
+ new_r = tf.transpose(tf.concat([r_r, r_z], 0))
+ elif names[-2] == "candidate":
+ new_w = tf.transpose(w_h)
+ new_r = tf.transpose(r_h)
+ kernel = tf.concat([new_w, new_r], 0)
+ return kernel
+ if names[-1] == "bias":
+ if len(node.inputs) >= 4:
+ # onnx Wb[zrh], Rb[zrh]
+ if is_bidirectional:
+ b = tf.split(tensor_dict[node.inputs[3]], 2)[index]
+ else:
+ b = tensor_dict[node.inputs[3]]
+ w_b, r_b = tf.split(tf.squeeze(b), 2)
+ w_b_z, w_b_r, w_b_h = tf.split(w_b, 3)
+ r_b_z, r_b_r, r_b_h = tf.split(r_b, 3)
+ if names[-2] == "gates":
+ w_b = tf.transpose(tf.concat([w_b_r, w_b_z], 0))
+ r_b = tf.transpose(tf.concat([r_b_r, r_b_z], 0))
+ elif names[-2] == "candidate":
+ w_b = tf.transpose(w_b_h)
+ r_b = tf.transpose(r_b_h)
+ return tf.add(w_b, r_b)
+ return getter(name, *args, **kwargs)
+ return getter(name, *args, **kwargs)
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ input_shape = x.get_shape().as_list()
+ input_size = len(node.inputs)
+ hidden_size = node.attrs["hidden_size"]
+ direction = node.attrs.get("direction", "forward")
+ num_directions = 2 if direction == "bidirectional" else 1
+
+ # removed from version 7, default is 0
+ output_sequence = node.attrs.get("output_sequence", 0)
+
+ # TODO(fumihwh): check if prev node is one of RNN
+ # process input if it comes from other previous cell
+ # which has shape [seq_length, num_directions, batch_size, hidden_size]
+ if len(input_shape) == 4 and input_shape[1] == 1:
+ x = tf.squeeze(x)
+
+ sequence_length = None
+ if input_size >= 5 and node.inputs[4] in tensor_dict:
+ sequence_length = tensor_dict[node.inputs[4]]
+
+ cell_kwargs = {}
+
+ tf_activations = [tf.nn.tanh]
+ if "activations" in node.attrs:
+ activations = list(map(lambda x: x.lower(), node.attrs["activations"]))
+ activation_alpha = node.attrs.get("activation_alpha", [None] * 4)
+ activation_beta = node.attrs.get("activation_beta", [None] * 4)
+ tf_activations = [
+ cls.rnn_get_activation(activations[1], activation_alpha[1],
+ activation_beta[1])
+ ]
+ if num_directions == 2:
+ tf_activations.append(
+ cls.rnn_get_activation(activations[3], activation_alpha[3],
+ activation_beta[3]))
+
+ # TODO(fumihwh): check if reverse and bidirectional works
+ with tf.variable_scope(
+ "GRU_" + get_unique_suffix(),
+ custom_getter=partial(
+ cls._custom_getter,
+ node=node,
+ tensor_dict=tensor_dict,
+ is_bidirectional=num_directions == 2)):
+
+ cell_kwargs["num_units"] = hidden_size
+ if input_size < 4 or node.inputs[3] not in tensor_dict:
+ cell_kwargs["bias_initializer"] = tf.zeros_initializer
+ initial_state = None
+ initial_state_bw = None
+ if input_size == 6:
+ initial_h = tensor_dict.get(node.inputs[5], None)
+ if initial_h is not None:
+ initial_state = (initial_h[0],)
+ if num_directions == 2:
+ initial_state_bw = (initial_h[1],)
+
+ rnn_kwargs = {}
+ if num_directions == 1:
+ rnn_kwargs["initial_state"] = initial_state
+ elif num_directions == 2:
+ rnn_kwargs["initial_state_fw"] = initial_state
+ rnn_kwargs["initial_state_bw"] = initial_state_bw
+ rnn_kwargs["sequence_length"] = sequence_length
+ rnn_kwargs["time_major"] = True
+ rnn_kwargs["dtype"] = tf.float32
+
+ outputs, states = cls.rnn(x, tf.nn.rnn_cell.GRUCell, cell_kwargs,
+ rnn_kwargs, tf_activations, direction)
+
+ if num_directions == 1:
+ state = states[0]
+ h = tf.expand_dims(state, 0)
+ output = tf.expand_dims(outputs, 1)
+ else:
+ state_fw = states[0][0]
+ state_bw = states[1][0]
+ output_fw = outputs[0]
+ output_bw = outputs[1]
+ h_fw = tf.expand_dims(state_fw, 0)
+ h_bw = tf.expand_dims(state_bw, 0)
+ h = tf.concat((h_fw, h_bw), axis=0)
+ output_fw = tf.expand_dims(output_fw, 1)
+ output_bw = tf.expand_dims(output_bw, 1)
+ output = tf.concat((output_fw, output_bw), axis=1)
+
+ return [output, h] if output_sequence == 0 else [h]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_3(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hard_sigmoid.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hard_sigmoid.py
new file mode 100644
index 0000000..5f1e8d5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hard_sigmoid.py
@@ -0,0 +1,26 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("HardSigmoid")
+class HardSigmoid(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ if "alpha" not in node.attrs and "beta" not in node.attrs:
+ return [tf.keras.backend.hard_sigmoid(x)]
+
+ alpha = node.attrs.get("alpha", 0.2)
+ beta = node.attrs.get("beta", 0.5)
+ return [tf.clip_by_value(x * alpha + beta, 0, 1)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hardmax.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hardmax.py
new file mode 100644
index 0000000..42f0ba9
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/hardmax.py
@@ -0,0 +1,35 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Hardmax")
+@tf_func(tf.contrib.seq2seq.hardmax)
+class Hardmax(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ axis = node.attrs.get("axis", 1)
+ axis = axis if axis >= 0 else len(np.shape(x)) + axis
+
+ if axis == len(np.shape(x)) - 1:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ shape = tf.shape(x)
+ cal_shape = (tf.reduce_prod(shape[0:axis]),
+ tf.reduce_prod(shape[axis:tf.size(shape)]))
+ x = tf.reshape(x, cal_shape)
+
+ return [tf.reshape(tf.contrib.seq2seq.hardmax(x), shape)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/identity.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/identity.py
new file mode 100644
index 0000000..b974b2f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/identity.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Identity")
+@tf_func(tf.identity)
+class Identity(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/if.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/if.py
new file mode 100644
index 0000000..1eac5ee
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/if.py
@@ -0,0 +1,51 @@
+import tensorflow as tf
+
+import onnx_tf
+from onnx.helper import make_opsetid
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("If")
+@tf_func(tf.cond)
+class If(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ cond = kwargs["tensor_dict"][node.inputs[0]]
+ then_branch = node.attrs["then_branch"]
+ else_branch = node.attrs["else_branch"]
+ current_opset = [make_opsetid(cls.DOMAIN, cls.VERSION)]
+
+ def true_fn():
+ subgraph_tensor_dict = onnx_tf.backend.onnx_graph_to_tensorflow_ops(
+ subgraph=then_branch,
+ input_values={}, # all inputs of then_branch are in tensor_dict
+ tensor_dict=kwargs["tensor_dict"],
+ opset=current_opset)
+ return [subgraph_tensor_dict[o.name] for o in then_branch.output]
+
+ def false_fn():
+ subgraph_tensor_dict = onnx_tf.backend.onnx_graph_to_tensorflow_ops(
+ subgraph=else_branch,
+ input_values={}, # all inputs of else_branch are in tensor_dict
+ tensor_dict=kwargs["tensor_dict"],
+ opset=current_opset)
+ return [subgraph_tensor_dict[o.name] for o in else_branch.output]
+
+ # Set strict=True to make sure singleton lists and tuples return from
+ # true_fn and false_fn will not be implicitly unpacked to single values
+ strict = True
+ return [
+ cls.make_tensor_from_onnx_node(node,
+ inputs=[cond, true_fn, false_fn, strict])
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/image_scaler.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/image_scaler.py
new file mode 100644
index 0000000..4ae2599
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/image_scaler.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("ImageScaler")
+class ImageScaler(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ input_dict = kwargs["tensor_dict"]
+ x = input_dict[node.inputs[0]]
+ scale = node.attrs.get("scale", 1.0)
+ output = tf.multiply(x, scale)
+ if "bias" in node.attrs:
+ bias = node.attrs["bias"]
+ output = tf.nn.bias_add(output, bias, data_format="NCHW")
+ return [output]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/instance_normalization.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/instance_normalization.py
new file mode 100644
index 0000000..525626e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/instance_normalization.py
@@ -0,0 +1,60 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("InstanceNormalization")
+@tf_func(tf.nn.batch_normalization)
+class InstanceNormalization(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {
+ "default": {
+ "epsilon": 1e-5
+ },
+ "rename": {
+ "epsilon": "variance_epsilon"
+ }
+ }
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ # this file is adapted from :
+ # https://github.com/tensorflow/tensorflow/blob/r1.8/tensorflow/contrib/layers/python/layers/normalization.py.
+ # We do not use the tf layer instance_norm because there is no way
+ # to pass in tensor as beta or gamma.
+ gamma = tensor_dict[node.inputs[1]]
+ beta = tensor_dict[node.inputs[2]]
+
+ inputs = tensor_dict[node.inputs[0]]
+ inputs_shape = inputs.shape
+ inputs_rank = inputs.shape.ndims
+
+ moments_axes = list(range(inputs_rank))[2:]
+ params_shape_broadcast = list([1, inputs_shape[1].value] +
+ [1 for _ in range(2, inputs_rank)])
+
+ beta = tf.reshape(beta, params_shape_broadcast)
+ gamma = tf.reshape(gamma, params_shape_broadcast)
+
+ # Calculate the moments (instance activations).
+ mean, variance = tf.nn.moments(inputs, moments_axes, keep_dims=True)
+
+ # Compute instance normalization.
+ inputs = [inputs, mean, variance, beta, gamma]
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=inputs, name="instancenorm", **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_inf.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_inf.py
new file mode 100644
index 0000000..09fb75b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_inf.py
@@ -0,0 +1,27 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+@onnx_op("IsInf")
+@tf_func(tf.math.is_inf)
+class IsInf(BackendHandler):
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ inp = kwargs["tensor_dict"][node.inputs[0]]
+ dtype = inp.dtype
+ shape = inp.shape
+ zero = tf.zeros(shape, dtype)
+ dn = node.attrs.get("detect_negative", 1)
+ dp = node.attrs.get("detect_positive", 1)
+ # detecting only positive infinity, zero out elements < 0
+ if dn == 0:
+ inp = tf.maximum(zero, inp)
+ # detecting only negative infinity, zero out elements > 0
+ if dp == 0:
+ inp = tf.minimum(zero, inp)
+ return [
+ cls.make_tensor_from_onnx_node(node, inputs=[inp], **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_nan.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_nan.py
new file mode 100644
index 0000000..b7874e8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/is_nan.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("IsNaN")
+@tf_func(tf.is_nan)
+class IsNaN(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/leaky_relu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/leaky_relu.py
new file mode 100644
index 0000000..e0efe19
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/leaky_relu.py
@@ -0,0 +1,22 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("LeakyRelu")
+@tf_func(tf.nn.leaky_relu)
+class Identity(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"alpha": 0.01}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/less.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/less.py
new file mode 100644
index 0000000..c2f6701
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/less.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import ComparisonMixin
+
+
+@onnx_op("Less")
+@tf_func(tf.less)
+class Less(ComparisonMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log.py
new file mode 100644
index 0000000..19370f7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Log")
+@tf_func(tf.log)
+class Log(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log_softmax.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log_softmax.py
new file mode 100644
index 0000000..506c643
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/log_softmax.py
@@ -0,0 +1,35 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("LogSoftmax")
+@tf_func(tf.nn.log_softmax)
+class LogSoftmax(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ axis = node.attrs.get("axis", 1)
+ axis = axis if axis >= 0 else len(np.shape(x)) + axis
+
+ if axis == len(np.shape(x)) - 1:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ shape = tf.shape(x)
+ cal_shape = (tf.reduce_prod(shape[0:axis]),
+ tf.reduce_prod(shape[axis:tf.size(shape)]))
+ x = tf.reshape(x, cal_shape)
+
+ return [tf.reshape(tf.nn.log_softmax(x - tf.reduce_max(x)), shape)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/loop.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/loop.py
new file mode 100644
index 0000000..b29ce8a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/loop.py
@@ -0,0 +1,110 @@
+import tensorflow as tf
+
+import onnx_tf
+from onnx.helper import make_opsetid
+from onnx_tf.common import data_type
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("Loop")
+class Loop(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ body = node.attrs["body"]
+ tensor_dict = kwargs["tensor_dict"]
+ M = tensor_dict[node.inputs[0]] if node.inputs[0] != "" else None
+ cond = tf.cast(tensor_dict[node.inputs[1]],
+ tf.bool) if node.inputs[1] != "" else None
+ v_initial = [tensor_dict[graph_input] for graph_input in node.inputs[2:]]
+ v_shapes = [v.get_shape() for v in v_initial]
+ current_opset = [make_opsetid(cls.DOMAIN, cls.VERSION)]
+ # outputs of the body will be in this format:
+ # (condition, loop carried dependencies..., scan_outputs...)
+ scan_outputs_start_index = 1 + len(v_initial)
+ scan_outputs = [
+ tf.TensorArray(dtype=data_type.onnx2tf(
+ body.output[i].type.tensor_type.elem_type),
+ size=0,
+ dynamic_size=True)
+ for i in range(scan_outputs_start_index, len(body.output))
+ ]
+ scan_outputs_shapes = [tf.TensorShape(None) for o in scan_outputs]
+
+ def run_subgraph(cond, v, scan_outputs):
+ input_values = {}
+ input_values[body.input[0].name] = M
+ input_values[body.input[1].name] = cond
+ for i in range(2, len(body.input)):
+ input_values[body.input[i].name] = v[i - 2]
+ subgraph_tensor_dict = onnx_tf.backend.onnx_graph_to_tensorflow_ops(
+ subgraph=body,
+ input_values=input_values,
+ tensor_dict=tensor_dict,
+ opset=current_opset)
+ outputs = [subgraph_tensor_dict[output.name] for output in body.output]
+ for i in range(scan_outputs_start_index, len(outputs)):
+ s_index = i - scan_outputs_start_index
+ insert_index = scan_outputs[s_index].size()
+ scan_outputs[s_index] = scan_outputs[s_index].write(
+ insert_index, outputs[i])
+ return [outputs[0], outputs[1:scan_outputs_start_index], scan_outputs]
+
+ # for loop
+ if M is not None and cond is None:
+ M = tf.cast(M, tf.int32)
+ condition = lambda cond, v, scan_outputs: True
+ _, v_final, scan_outputs = tf.while_loop(
+ cond=condition,
+ body=run_subgraph,
+ loop_vars=["", v_initial, scan_outputs],
+ shape_invariants=[
+ tf.TensorShape(None), v_shapes, scan_outputs_shapes
+ ],
+ maximum_iterations=M)
+ # while and do-while loop
+ elif M is None and cond is not None:
+ condition = lambda cond, v, scan_outputs: tf.reduce_all(
+ tf.equal(cond, True))
+ cond, v_final, scan_outputs = tf.while_loop(
+ cond=condition,
+ body=run_subgraph,
+ loop_vars=[cond, v_initial, scan_outputs],
+ shape_invariants=[
+ tf.TensorShape(None), v_shapes, scan_outputs_shapes
+ ])
+ # combine for loop and while loop together
+ elif M is not None and cond is not None:
+ M = tf.cast(M, tf.int32)
+ condition = lambda cond, v, scan_outputs: tf.reduce_all(
+ tf.equal(cond, True))
+ cond, v_final, scan_outputs = tf.while_loop(
+ cond=condition,
+ body=run_subgraph,
+ loop_vars=[cond, v_initial, scan_outputs],
+ shape_invariants=[
+ tf.TensorShape(None), v_shapes, scan_outputs_shapes
+ ],
+ maximum_iterations=M)
+ else: # M is None and cond is None
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Both M and cond in Loop are not set at the same time",
+ "Tensorflow.(PS. if you want to create a do-while loop " +
+ "then please set cond to True or 1)")
+
+ scan_outputs_tensors = [o.stack() for o in scan_outputs]
+ if scan_outputs_start_index == len(body.output):
+ # there is no scan_output in the body graph
+ return [v_final]
+ else:
+ return [v_final, scan_outputs_tensors]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_normalization.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_normalization.py
new file mode 100644
index 0000000..a22d844
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_normalization.py
@@ -0,0 +1,33 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("LpNormalization")
+@tf_func(tf.norm)
+class LpNormalization(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {
+ "default": {
+ "axis": -1,
+ "p": 2,
+ "keepdims": True
+ },
+ "rename": {
+ "p": "ord"
+ }
+ }
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ input_tensor = kwargs["tensor_dict"][node.inputs[0]]
+ tf_norm = cls.make_tensor_from_onnx_node(node, **kwargs)
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, tf_func=tf.div, inputs=[input_tensor, tf_norm], **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_pool.py
new file mode 100644
index 0000000..25732e0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lp_pool.py
@@ -0,0 +1,24 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .pool_mixin import PoolMixin
+
+
+@onnx_op("LpPool")
+class LpPool(PoolMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ return cls.pool(node, kwargs["tensor_dict"], "LP",
+ kwargs.get("strict", True))
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_2(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lrn.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lrn.py
new file mode 100644
index 0000000..cff8796
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lrn.py
@@ -0,0 +1,29 @@
+import copy
+
+import tensorflow as tf
+import numpy as np
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("LRN")
+@tf_func(tf.nn.lrn)
+class LRN(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+ alpha = attrs.get("alpha", 1e-4)
+ attrs.setdefault("beta", 0.75)
+ size = attrs["size"]
+ attrs["alpha"] = alpha / size
+ attrs["depth_radius"] = np.floor([(size - 1) / 2.])[0]
+ # TODO: LRN in tf accepts radius
+ # but in ONNX/Caffe accepts diameter.
+ # This could be a problem.
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, attrs=attrs, c_last_only=True, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lstm.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lstm.py
new file mode 100644
index 0000000..74369e0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/lstm.py
@@ -0,0 +1,218 @@
+from functools import partial
+
+import tensorflow as tf
+
+from onnx_tf.common import get_unique_suffix
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .rnn_mixin import RNNMixin
+
+
+@onnx_op("LSTM")
+@partial_support(True)
+@ps_description("LSTM not using sigmoid for `f`, or " +
+ "LSTM not using the same activation for `g` and `h` " +
+ "are not supported in Tensorflow.")
+class LSTM(RNNMixin, BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ direction = node.attrs.get("direction", "forward")
+ num_directions = 2 if direction == "bidirectional" else 1
+ if node.attrs.get("input_forget", 0):
+ # TODO(fumihwh): warning
+ pass
+ if "activations" in node.attrs:
+ activations = list(map(lambda x: x.lower(), node.attrs["activations"]))
+ if activations[0] != "sigmoid":
+ exception.OP_UNSUPPORTED_EXCEPT("LSTM without sigmoid for `f`",
+ "Tensorflow")
+ if activations[1] != activations[2]:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "LSTM without same activation for `g` and `h`", "Tensorflow")
+ if num_directions == 2:
+ if activations[3] != "sigmoid":
+ exception.OP_UNSUPPORTED_EXCEPT("LSTM without sigmoid for `f`",
+ "Tensorflow")
+ if activations[4] != activations[5]:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "LSTM without same activation for `g` and `h`", "Tensorflow")
+
+ @classmethod
+ def _custom_getter(cls,
+ getter,
+ name,
+ node=None,
+ tensor_dict=None,
+ is_bidirectional=None,
+ *args,
+ **kwargs):
+ names = name.split("/")
+ if is_bidirectional:
+ if "fw" in names:
+ index = 0
+ elif "bw" in names:
+ index = 1
+ else:
+ raise RuntimeError("Can not get {} for bidirectional. "
+ "Either fw and bw is not in name scope.".format(
+ names[-1]))
+
+ if names[-1] == "kernel":
+ # onnx W[iofc], R[iofc]
+ if is_bidirectional:
+ w = tf.split(tensor_dict[node.inputs[1]], 2)[index]
+ r = tf.split(tensor_dict[node.inputs[2]], 2)[index]
+ else:
+ w = tensor_dict[node.inputs[1]]
+ r = tensor_dict[node.inputs[2]]
+ w_i, w_o, w_f, w_c = tf.split(tf.squeeze(w), 4)
+ r_i, r_o, r_f, r_c = tf.split(tf.squeeze(r), 4)
+ new_w = tf.transpose(tf.concat([w_i, w_c, w_f, w_o], 0))
+ new_r = tf.transpose(tf.concat([r_i, r_c, r_f, r_o], 0))
+ kernel = tf.concat([new_w, new_r], 0)
+ return kernel
+ if names[-1] == "bias":
+ if len(node.inputs) >= 4:
+ # onnx Wb[iofc], Rb[iofc]
+ if is_bidirectional:
+ b = tf.split(tensor_dict[node.inputs[3]], 2)[index]
+ else:
+ b = tensor_dict[node.inputs[3]]
+ w_b, r_b = tf.split(tf.squeeze(b), 2)
+ w_b_i, w_b_o, w_b_f, w_b_c = tf.split(w_b, 4)
+ r_b_i, r_b_o, r_b_f, r_b_c = tf.split(r_b, 4)
+ w_b = tf.transpose(tf.concat([w_b_i, w_b_c, w_b_f, w_b_o], 0))
+ r_b = tf.transpose(tf.concat([r_b_i, r_b_c, r_b_f, r_b_o], 0))
+ return tf.add(w_b, r_b)
+ return getter(name, *args, **kwargs)
+ # Only use_peepholes is True,
+ # will try to get w_f_diag, w_i_diag, w_o_diag
+ # onnx P[iof]
+ if names[-1] in ["w_f_diag", "w_i_diag", "w_o_diag"]:
+ if is_bidirectional:
+ p = tf.split(tensor_dict[node.inputs[7]], 2)[index]
+ else:
+ p = tensor_dict[node.inputs[7]]
+ if names[-1] == "w_f_diag":
+ return tf.split(p, 3, axis=1)[2]
+ if names[-1] == "w_i_diag":
+ return tf.split(p, 3, axis=1)[0]
+ if names[-1] == "w_o_diag":
+ return tf.split(p, 3, axis=1)[1]
+ return getter(name, *args, **kwargs)
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ input_shape = x.get_shape().as_list()
+ input_size = len(node.inputs)
+ hidden_size = node.attrs["hidden_size"]
+ direction = node.attrs.get("direction", "forward")
+ num_directions = 2 if direction == "bidirectional" else 1
+
+ # removed from version 7, default is 0
+ output_sequence = node.attrs.get("output_sequence", 0)
+
+ # TODO(fumihwh): check if prev node is one of RNN
+ # process input if it comes from other previous cell
+ # which has shape [seq_length, num_directions, batch_size, hidden_size]
+ if len(input_shape) == 4 and input_shape[1] == 1:
+ x = tf.squeeze(x)
+
+ sequence_length = None
+ if input_size >= 5 and node.inputs[4] in tensor_dict:
+ sequence_length = tensor_dict[node.inputs[4]]
+
+ cell_kwargs = {}
+
+ if "clip" in node.attrs:
+ cell_kwargs["cell_clip"] = node.attrs["clip"]
+
+ tf_activations = [tf.nn.tanh] * num_directions
+ if "activations" in node.attrs:
+ activations = list(map(lambda x: x.lower(), node.attrs["activations"]))
+ activation_alpha = node.attrs.get("activation_alpha", [None] * 6)
+ activation_beta = node.attrs.get("activation_beta", [None] * 6)
+
+ # tf only supports cutomizing hidden states activation function,
+ # which correspond to activation functions specified at position 1
+ # and 4 in onnx's activations attribute.
+ activation_idxs = [1, 4] if num_directions == 2 else [1]
+ tf_activations = [
+ cls.rnn_get_activation(activations[i], activation_alpha[i],
+ activation_beta[i]) for i in activation_idxs
+ ]
+
+ # TODO(fumihwh): check if reverse and bidirectional works
+ with tf.variable_scope("LSTM_" + get_unique_suffix(),
+ custom_getter=partial(
+ cls._custom_getter,
+ node=node,
+ tensor_dict=tensor_dict,
+ is_bidirectional=num_directions == 2)):
+
+ cell_kwargs[
+ "use_peepholes"] = input_size == 8 and node.inputs[7] in tensor_dict
+ cell_kwargs["forget_bias"] = 0.
+ cell_kwargs["num_units"] = hidden_size
+ initial_state = None
+ initial_state_bw = None
+ if input_size >= 6:
+ initial_h = tensor_dict.get(node.inputs[5], None)
+ initial_c = tensor_dict.get(
+ node.inputs[6],
+ None) if input_size >= 7 else tf.zeros_like(initial_h)
+ if initial_h is not None and initial_c is not None:
+ initial_state = (tf.nn.rnn_cell.LSTMStateTuple(
+ initial_c[0], initial_h[0]),)
+ if num_directions == 2:
+ initial_state_bw = (tf.nn.rnn_cell.LSTMStateTuple(
+ initial_c[1], initial_h[1]),)
+
+ rnn_kwargs = {}
+ if num_directions == 1:
+ rnn_kwargs["initial_state"] = initial_state
+ elif num_directions == 2:
+ rnn_kwargs["initial_state_fw"] = initial_state
+ rnn_kwargs["initial_state_bw"] = initial_state_bw
+ rnn_kwargs["sequence_length"] = sequence_length
+ rnn_kwargs["time_major"] = True
+ rnn_kwargs["dtype"] = tf.float32
+
+ outputs, states = cls.rnn(x, tf.nn.rnn_cell.LSTMCell, cell_kwargs,
+ rnn_kwargs, tf_activations, direction)
+
+ if num_directions == 1:
+ state = states[0]
+ c = tf.expand_dims(state[0], 0)
+ h = tf.expand_dims(state[1], 0)
+ output = tf.expand_dims(outputs, 1)
+ else:
+ state_fw = states[0][0]
+ state_bw = states[1][0]
+ output_fw = outputs[0]
+ output_bw = outputs[1]
+ c_fw = tf.expand_dims(state_fw[0], 0)
+ c_bw = tf.expand_dims(state_bw[0], 0)
+ c = tf.concat((c_fw, c_bw), axis=0)
+ h_fw = tf.expand_dims(state_fw[1], 0)
+ h_bw = tf.expand_dims(state_bw[1], 0)
+ h = tf.concat((h_fw, h_bw), axis=0)
+ output_fw = tf.expand_dims(output_fw, 1)
+ output_bw = tf.expand_dims(output_bw, 1)
+ output = tf.concat((output_fw, output_bw), axis=1)
+
+ return [output, h, c] if output_sequence == 0 else [h, c]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul.py
new file mode 100644
index 0000000..c45c049
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("MatMul")
+@tf_func(tf.matmul)
+class MatMul(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul_integer.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul_integer.py
new file mode 100644
index 0000000..fcc26be
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mat_mul_integer.py
@@ -0,0 +1,38 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("MatMulInteger")
+@tf_func(tf.matmul)
+class MatMulInteger(BackendHandler):
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ A = tensor_dict[node.inputs[0]]
+ B = tensor_dict[node.inputs[1]]
+ # tf.matmul doesn't support int8 and uint8 for A and B,
+ # therefore need to cast them to int32
+ A = tf.cast(A, tf.int32)
+ B = tf.cast(B, tf.int32)
+
+ # apply a_zero_point to A
+ if len(node.inputs) > 2:
+ a_zero_point = tensor_dict[node.inputs[2]]
+ shape = a_zero_point.get_shape().as_list()
+ if len(shape) > 0 and shape[0] > 1:
+ # reshape a_zero_point before subtract it from A
+ a_zero_point = tf.reshape(a_zero_point, [shape[0], 1])
+ a_zero_point = tf.cast(a_zero_point, tf.int32)
+ A = tf.subtract(A, a_zero_point)
+
+ # apply b_zero_point to B
+ if len(node.inputs) == 4:
+ b_zero_point = tensor_dict[node.inputs[3]]
+ b_zero_point = tf.cast(b_zero_point, tf.int32)
+ B = tf.subtract(B, b_zero_point)
+
+ return [cls.make_tensor_from_onnx_node(node, inputs=[A, B], **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/math_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/math_mixin.py
new file mode 100644
index 0000000..dfe5919
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/math_mixin.py
@@ -0,0 +1,25 @@
+import copy
+
+from .broadcast_mixin import BroadcastMixin
+
+
+class BasicMathMixin(BroadcastMixin):
+ pass
+
+
+class ArithmeticMixin(BroadcastMixin):
+ pass
+
+
+class ReductionMixin(BroadcastMixin):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+ axis = attrs.pop("axes", None)
+ if isinstance(axis, (list, tuple)) and len(axis) == 1:
+ axis = axis[0]
+ attrs["axis"] = axis
+ # https://github.com/onnx/onnx/issues/585
+ attrs["keepdims"] = attrs.pop("keepdims", 1) == 1
+ return [cls.make_tensor_from_onnx_node(node, attrs=attrs, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max.py
new file mode 100644
index 0000000..8bffa69
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max.py
@@ -0,0 +1,34 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Max")
+@tf_func(tf.reduce_max)
+class Max(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ values = [kwargs["tensor_dict"][inp] for inp in node.inputs]
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[tf.stack(values)], **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_pool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_pool.py
new file mode 100644
index 0000000..acc14c4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_pool.py
@@ -0,0 +1,40 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .pool_mixin import PoolMixin
+
+
+@onnx_op("MaxPool")
+@partial_support(True)
+@ps_description(
+ "MaxPoolWithArgmax with pad is None or incompatible mode, or " +
+ "MaxPoolWithArgmax with 4D or higher input, or" +
+ "MaxPoolWithArgmax with column major " + "are not supported in Tensorflow.")
+class MaxPool(PoolMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ pool_type = "MAX" if len(node.outputs) == 1 else "MAX_WITH_ARGMAX"
+ return cls.pool(node, kwargs["tensor_dict"], pool_type,
+ kwargs.get("strict", True))
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_unpool.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_unpool.py
new file mode 100644
index 0000000..8fdfa6f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/max_unpool.py
@@ -0,0 +1,15 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .unpool_mixin import UnpoolMixin
+
+
+@onnx_op("MaxUnpool")
+class MaxUnpool(UnpoolMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls.max_unpool(node, kwargs["tensor_dict"])
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls.max_unpool(node, kwargs["tensor_dict"])
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean.py
new file mode 100644
index 0000000..6afdea5
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean.py
@@ -0,0 +1,34 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Mean")
+@tf_func(tf.reduce_mean)
+class Mean(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ values = [kwargs["tensor_dict"][inp] for inp in node.inputs]
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[tf.stack(values)], **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean_variance_normalization.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean_variance_normalization.py
new file mode 100644
index 0000000..be8b783
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mean_variance_normalization.py
@@ -0,0 +1,47 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("MeanVarianceNormalization")
+class MeanVarianceNormalization(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ inputs = tensor_dict[node.inputs[0]]
+ inputs_rank = inputs.shape.ndims
+
+ across_channels = node.attrs.get("across_channels", 0)
+ normalize_variance = node.attrs.get("normalize_variance", 1)
+
+ moments_axes = [0] if not across_channels else [0, 1]
+ moments_axes += list(range(inputs_rank))[2:]
+
+ mean, variance = tf.nn.moments(inputs, moments_axes, keep_dims=True)
+
+ if not normalize_variance:
+ return [inputs - mean]
+ return [(inputs - mean) / tf.sqrt(variance)]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ inputs = tensor_dict[node.inputs[0]]
+ inputs_rank = inputs.shape.ndims
+ # To satisfy default axes=[0,2,3], also assume the
+ # following default when rank is not 4
+ # rank1 -> axes=[0]
+ # rank2 -> axes=[0]
+ # rank3 -> axes=[0,2]
+ # rank4 -> axes=[0,2,3]
+ # rankN -> axes=[0,2,3,..,N-1]
+ # TODO(tedhtchang): Since input tensor is no longer limited
+ # to shape [N,C,H,W], consider using "[0]" or "[]" as default axes.
+ # See issue https://github.com/onnx/onnx/issues/2047
+ default_axes = [0] if inputs_rank < 3 else [0, 2]
+ default_axes += list(range(inputs_rank))[3:]
+ moments_axes = node.attrs.get("axes", default_axes)
+ mean, variance = tf.nn.moments(inputs, moments_axes, keep_dims=True)
+ return [(inputs - mean) / tf.sqrt(variance)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/min.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/min.py
new file mode 100644
index 0000000..f478b0d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/min.py
@@ -0,0 +1,34 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Min")
+@tf_func(tf.reduce_min)
+class Min(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ values = [kwargs["tensor_dict"][inp] for inp in node.inputs]
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[tf.stack(values)], **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mod.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mod.py
new file mode 100644
index 0000000..ad5567f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mod.py
@@ -0,0 +1,38 @@
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Mod")
+@partial_support(True)
+@ps_description("Mod Dividend or Divisor in " +
+ "int8/int16/uint8/uint16/uint32/uint64 " +
+ "are not supported in Tensorflow.")
+class Mod(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ unsupported_dtype = [
+ tf.int8, tf.int16, tf.uint8, tf.uint16, tf.uint32, tf.uint64
+ ]
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ y = kwargs["tensor_dict"][node.inputs[1]]
+ if x.dtype in unsupported_dtype:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Mod Dividend in " + str(x.dtype), "Tensorflow")
+ if y.dtype in unsupported_dtype:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Mod Divisor in " + str(y.dtype), "Tensorflow")
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ fmod = node.attrs.get("fmod", 0)
+ tf_func = tf.floormod
+ if fmod == 1:
+ tf_func = tf.truncatemod
+ return [cls.make_tensor_from_onnx_node(node, tf_func=tf_func, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mul.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mul.py
new file mode 100644
index 0000000..55056b0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/mul.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Mul")
+@tf_func(tf.multiply)
+class Mul(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/neg.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/neg.py
new file mode 100644
index 0000000..09b33c1
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/neg.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Neg")
+@tf_func(tf.negative)
+class Neg(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_max_suppression.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_max_suppression.py
new file mode 100644
index 0000000..d9745a6
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_max_suppression.py
@@ -0,0 +1,84 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("NonMaxSuppression")
+class NonMaxSuppression(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ boxes = tensor_dict[node.inputs[0]]
+ scores = tensor_dict[node.inputs[1]]
+ # in ONNX spec max_output_boxes_per_class need to be in int64 but
+ # max_output_boxes for tf.image.non_max_suppression must be in tf.int32
+ # therefore need to cast this input to tf.int32
+ max_output_boxes_per_class = tf.cast(
+ tensor_dict[node.inputs[2]],
+ tf.int32) if (len(node.inputs) > 2 and
+ node.inputs[2] != "") else tf.constant(0, tf.int32)
+ # make sure max_output_boxes_per_class is a scalar not a 1-D 1 element tensor
+ max_output_boxes_per_class = tf.squeeze(max_output_boxes_per_class) if len(
+ max_output_boxes_per_class.shape) == 1 else max_output_boxes_per_class
+ iou_threshold = tensor_dict[node.inputs[3]] if (
+ len(node.inputs) > 3 and node.inputs[3] != "") else tf.constant(
+ 0, tf.float32)
+ # make sure iou_threshold is a scalar not a 1-D 1 element tensor
+ iou_threshold = tf.squeeze(iou_threshold) if len(
+ iou_threshold.shape) == 1 else iou_threshold
+ score_threshold = tensor_dict[node.inputs[4]] if (
+ len(node.inputs) > 4 and node.inputs[4] != "") else tf.constant(
+ float('-inf'))
+ # make sure score_threshold is a scalar not a 1-D 1 element tensor
+ score_threshold = tf.squeeze(score_threshold) if len(
+ score_threshold.shape) == 1 else score_threshold
+ center_point_box = node.attrs.get("center_point_box", 0)
+
+ if center_point_box == 1:
+ boxes_t = tf.transpose(boxes, perm=[0, 2, 1])
+ x_centers = tf.slice(boxes_t, [0, 0, 0], [-1, 1, -1])
+ y_centers = tf.slice(boxes_t, [0, 1, 0], [-1, 1, -1])
+ widths = tf.slice(boxes_t, [0, 2, 0], [-1, 1, -1])
+ heights = tf.slice(boxes_t, [0, 3, 0], [-1, 1, -1])
+ y1 = tf.subtract(y_centers, tf.divide(heights, 2))
+ x1 = tf.subtract(x_centers, tf.divide(widths, 2))
+ y2 = tf.add(y_centers, tf.divide(heights, 2))
+ x2 = tf.add(x_centers, tf.divide(widths, 2))
+ boxes_t = tf.concat([y1, x1, y2, x2], 1)
+ boxes = tf.transpose(boxes_t, perm=[0, 2, 1])
+
+ # get number of batches in boxes
+ num_batches = boxes.shape[0]
+ for batch_i in range(num_batches):
+ # get boxes in batch_i only
+ tf_boxes = tf.squeeze(tf.gather(boxes, [batch_i]), axis=0)
+ # get scores of all classes in batch_i only
+ batch_i_scores = tf.squeeze(tf.gather(scores, [batch_i]), axis=0)
+ # get number of classess in batch_i only
+ num_classes = batch_i_scores.shape[0]
+ for class_j in range(num_classes):
+ # get scores in class_j for batch_i only
+ tf_scores = tf.squeeze(tf.gather(batch_i_scores, [class_j]), axis=0)
+ # get the selected boxes indices
+ selected_indices = tf.image.non_max_suppression(
+ tf_boxes, tf_scores, max_output_boxes_per_class, iou_threshold,
+ score_threshold)
+ # add batch and class information into the indices
+ output = tf.transpose([tf.cast(selected_indices, dtype=tf.int64)])
+ paddings = tf.constant([[0, 0], [1, 0]])
+ output = tf.pad(output, paddings, constant_values=class_j)
+ output = tf.pad(output, paddings, constant_values=batch_i)
+ result = tf.concat([result, output],
+ 0) if 'result' in locals() else output
+
+ return [result]
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_zero.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_zero.py
new file mode 100644
index 0000000..b3bdd43
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/non_zero.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("NonZero")
+class NonZero(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ input_tensor = kwargs["tensor_dict"][node.inputs[0]]
+ condition = tf.not_equal(input_tensor, tf.zeros_like(input_tensor))
+ nonzero_indices = tf.where(condition)
+ return [tf.transpose(nonzero_indices)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/not.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/not.py
new file mode 100644
index 0000000..f65815c
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/not.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import LogicalMixin
+
+
+@onnx_op("Not")
+@tf_func(tf.logical_not)
+class Not(LogicalMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/onehot.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/onehot.py
new file mode 100644
index 0000000..2d32cbc
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/onehot.py
@@ -0,0 +1,60 @@
+import copy
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("OneHot")
+@tf_func(tf.one_hot)
+@partial_support(True)
+@ps_description("OneHot indices in uint16/uint32/uint64/int8/int16/"+
+ "float16/float/double, or " +
+ "OneHot depth in uint8/uint16/uint32/uint64/int8/" +
+ "int16/int64/float16/float/double " +
+ "are not supported in Tensorflow.")
+class OneHot(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ indices = tensor_dict[node.inputs[0]]
+ depth = tensor_dict[node.inputs[1]]
+ if indices.dtype not in [tf.uint8, tf.int32, tf.int64]:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "OneHot indices must be in uint8 or int32 or int64 " +
+ "but it is currently in " + str(indices.dtype) + " which",
+ "Tensorflow")
+ if depth.dtype not in [tf.int32]:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "OneHot depth must be in int32 but it is currently in " + str(
+ depth.dtype) + " which", "Tensorflow")
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+ tensor_dict = kwargs["tensor_dict"]
+ indices = tensor_dict[node.inputs[0]]
+ depth = tensor_dict[node.inputs[1]]
+ off_value = tensor_dict[node.inputs[2]][0]
+ on_value = tensor_dict[node.inputs[2]][1]
+ attrs["dtype"] = on_value.dtype
+ return [
+ cls.make_tensor_from_onnx_node(
+ node,
+ inputs=[indices, depth, on_value, off_value],
+ attrs=attrs,
+ **kwargs)
+ ]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/or.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/or.py
new file mode 100644
index 0000000..7db3686
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/or.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import LogicalMixin
+
+
+@onnx_op("Or")
+@tf_func(tf.logical_or)
+class Or(LogicalMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/p_relu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/p_relu.py
new file mode 100644
index 0000000..5a24ffb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/p_relu.py
@@ -0,0 +1,38 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .broadcast_mixin import BroadcastMixin
+
+
+@onnx_op("PRelu")
+class PRelu(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ """
+ Reference implementation at
+ https://github.com/tflearn/tflearn/blob/4ba8c8d78bf1bbdfc595bf547bad30580cb4c20b/tflearn/activations.py#L191
+ """
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ slope = BroadcastMixin.explicit_broadcast([x, tensor_dict[node.inputs[1]]])
+ pos = tf.nn.relu(x)
+ neg = slope * (x - abs(x)) * 0.5
+ return [pos + neg]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad.py
new file mode 100644
index 0000000..85ed2f4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad.py
@@ -0,0 +1,65 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Pad")
+@tf_func(tf.pad)
+class Pad(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ num_dim = len(tensor_dict[node.inputs[0]].get_shape())
+ mode = node.attrs.pop("mode", "constant")
+
+ if cls.SINCE_VERSION < 11: # for opset 1 and opset 2
+ paddings = node.attrs.pop("pads", None)
+ # tf requires int32 paddings
+ paddings = tf.constant(
+ np.transpose(
+ np.array(paddings).reshape([2, num_dim]).astype(np.int32)))
+ constant_values = node.attrs.pop("value", 0.)
+
+ else: # for opset 11
+ paddings = tensor_dict[node.inputs[1]]
+ # tf requires int32 paddings
+ paddings = tf.cast(tf.transpose(tf.reshape(paddings, [2, num_dim])),
+ dtype=tf.int32)
+ constant_values = tensor_dict[node.inputs[2]] if len(
+ node.inputs) == 3 else 0
+
+ def _symmetric_pad(i, x):
+ paddings_i = tf.map_fn(lambda e: tf.where(i < e, 1, 0), paddings)
+ paddings_i = tf.reshape(paddings_i, [num_dim, 2])
+ x = tf.pad(x, paddings_i, 'SYMMETRIC')
+ return i + 1, x
+
+ if mode.lower() == "edge":
+ paddings = tf.reshape(paddings, [-1])
+ max_i = tf.reduce_max(paddings)
+ _, x = tf.while_loop(
+ lambda i, x: tf.less(i, max_i), _symmetric_pad, [0, x],
+ [tf.TensorShape([]), tf.TensorShape(None)])
+ return [x]
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[x, paddings, mode, None, constant_values], **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_2(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad_mixin.py
new file mode 100644
index 0000000..271f4f9
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pad_mixin.py
@@ -0,0 +1,17 @@
+import numpy as np
+import tensorflow as tf
+
+
+class PadMixin(object):
+
+ @classmethod
+ def get_padding_as_op(cls, x, pads):
+ num_dim = int(len(pads) / 2)
+
+ tf_pads = np.transpose(np.array(pads).reshape([2, num_dim]))
+ tf_pads = [0, 0, 0, 0] + tf_pads.flatten().tolist()
+
+ padding = tf.constant(
+ np.array(tf_pads).reshape([num_dim + 2, 2])
+ .astype(np.int32)) # tf requires int32 paddings
+ return tf.pad(x, padding)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pool_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pool_mixin.py
new file mode 100644
index 0000000..aa26f0d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pool_mixin.py
@@ -0,0 +1,125 @@
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.common import get_data_format
+from onnx_tf.common import get_perm_from_formats
+
+from onnx_tf.common import logger
+from .dilated_pooling import DilatedPooling
+from onnx_tf.common.pooling_helper import py_pool
+from onnx_tf.common.pooling_helper import calc_pads_same
+from onnx_tf.common.pooling_helper import calc_output_shape
+
+class PoolMixin(object):
+
+ @classmethod
+ def pool(cls, node, input_dict, pooling_type, strict=True):
+ x = input_dict[node.inputs[0]]
+ orig_x = x
+
+ kernel_shape = node.attrs["kernel_shape"]
+
+ spatial_size = len(kernel_shape)
+ x_rank = spatial_size + 2
+
+ kernel_shape = node.attrs["kernel_shape"]
+ strides = node.attrs.get("strides", [1] * spatial_size)
+ dilations = node.attrs.get("dilations", [1] * spatial_size)
+ ceil_mode = bool(node.attrs.get("ceil_mode", 0))
+ pads = node.attrs.get("auto_pad", "NOTSET")
+ p = node.attrs.get("p", 2)
+
+ if pads == "NOTSET":
+ pads = node.attrs.get("pads", [0] * spatial_size * 2)
+ # In case shape is fully defined, check if pads match
+ # SAME padding in Tensorflow
+ if x.shape.is_fully_defined() and pads != [0] * spatial_size * 2:
+ in_shape = x.get_shape().as_list()
+ same_paddings = calc_pads_same(in_shape[1:x_rank-1], kernel_shape,
+ strides, dilations, "SAME_UPPER")
+ if pads == same_paddings:
+ pads = "SAME_UPPER"
+
+ count_include_pad = bool(node.attrs.get("count_include_pad", 0))
+ if pooling_type == "AVG":
+ pooling_name = "AveragePool"
+ elif pooling_type == "MAX":
+ pooling_name = "MaxPool"
+ elif pooling_type == "MAX_WITH_ARGMAX":
+ pooling_name = "MaxPoolWithArgmax"
+ elif pooling_type == "LP":
+ pooling_name = "LpPool"
+
+ if spatial_size > 3:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ pooling_name + " with {}D input".format(x_rank), "Tensorflow")
+ if pooling_type == "MAX_WITH_ARGMAX" and x_rank != 4:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ pooling_name + " with {}D input".format(x_rank), "Tensorflow")
+ if node.attrs.get("storage_order", 0) != 0:
+ exception.OP_UNSUPPORTED_EXCEPT(pooling_name + " with column major",
+ "Tensorflow")
+
+ storage_format, _ = get_data_format(x_rank)
+
+ need_trans = storage_format.startswith("NC")
+ if need_trans:
+ compute_format = "N" + storage_format[2:] + "C"
+ x = tf.transpose(
+ x, perm=get_perm_from_formats(storage_format, compute_format))
+
+ dp = DilatedPooling(
+ input=x,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ padding=pads,
+ ceil_mode=ceil_mode,
+ pooling_type=pooling_type,
+ count_include_pad=count_include_pad,
+ p=p)
+ if not dp.is_supported():
+ if strict:
+ logger.warning(
+ "Using the pooling op in compatibility mode. "
+ "This means your graph cannot be serialized.", UserWarning)
+
+ result = tf.py_func(py_pool, [
+ orig_x, kernel_shape, strides, dilations, pads, ceil_mode,
+ pooling_type, False
+ ], orig_x.dtype)
+
+ if orig_x.shape.is_fully_defined():
+ shape = orig_x.get_shape().as_list()
+ output_shape = shape[0:2] + calc_output_shape(shape[2:x_rank],
+ kernel_shape, strides, dilations, pads, ceil_mode)
+ else:
+ output_shape = [None] * x_rank
+ result.set_shape(output_shape)
+ return [result]
+ else:
+ exception.OP_UNSUPPORTED_EXCEPT("strict == 0 and " + pooling_name +
+ " arguments not compatible",
+ "Tensorflow")
+
+ def dilated_pool():
+ return (dp.dilated_pool(), None)
+
+ # select correct op depending on the pooling type
+ pooling_op = dilated_pool if pooling_type in ["MAX", "AVG", "LP"] else \
+ dp.dilated_maxpool_with_argmax
+
+ # select the correct transpose ops depending on the input storage format
+ perm = get_perm_from_formats(compute_format, storage_format)
+
+ def postprocess(pooled, argmax):
+ return (tf.transpose(pooled, perm=perm) if need_trans else pooled,
+ tf.transpose(argmax, perm=perm)
+ if need_trans and argmax is not None else argmax)
+
+ pooled, argmax = pooling_op()
+ pooled, argmax = postprocess(pooled, argmax)
+
+ result = [pooled] if argmax is None else [pooled, argmax]
+
+ return result
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pow.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pow.py
new file mode 100644
index 0000000..1fddc2a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/pow.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Pow")
+@tf_func(tf.pow)
+class Pow(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_conv.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_conv.py
new file mode 100644
index 0000000..e3e2dda
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_conv.py
@@ -0,0 +1,86 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .conv_mixin import ConvMixin
+
+
+@onnx_op("QLinearConv")
+class QLinearConv(ConvMixin, BackendHandler):
+
+ @classmethod
+ def _dequantize_tensor(cls, base, zero_point, scale):
+ # Do computation in float32
+ base = tf.cast(base, tf.float32)
+ zero_point = tf.cast(zero_point, tf.float32)
+ return (base - zero_point) * scale
+
+ @classmethod
+ def _dequantize_w(cls, base, zero_point, scale):
+ tensor_list = [
+ cls._dequantize_tensor(base[i][j], zero_point[j], scale[j])
+ for i in range(base.shape.as_list()[0])
+ for j in range(zero_point.shape.as_list()[0])
+ ]
+
+ out_tensor = tf.concat(tensor_list, 0)
+ return tf.reshape(out_tensor, base.shape)
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x_scale = tensor_dict[node.inputs[1]]
+ x_zero_point = tensor_dict[node.inputs[2]]
+ w = tensor_dict[node.inputs[3]]
+ w_scale = tensor_dict[node.inputs[4]]
+ w_zero_point = tensor_dict[node.inputs[5]]
+ y_scale = tensor_dict[node.inputs[6]]
+ y_zero_point = tensor_dict[node.inputs[7]]
+
+ output_dtype = x.dtype
+
+ # Convert w_zero_point and w_scale to 1-D if scalar
+ if len(w_zero_point.shape) == 0:
+ w_zero_point = tf.fill([x.shape[1]], w_zero_point)
+ elif len(w_zero_point.shape) > 1:
+ raise ValueError("Unsupported zero point: {}".format(w_zero_point))
+
+ if len(w_scale.shape) == 0:
+ w_scale = tf.fill([x.shape[1]], w_scale)
+ elif len(w_scale.shape) > 1:
+ raise ValueError("Unsupported scale: {}".format(w_scale))
+
+ # Dequantize variables to float32
+ x = cls._dequantize_tensor(x, x_zero_point, x_scale)
+ w = cls._dequantize_w(w, w_zero_point, w_scale)
+ y_zero_point = tf.cast(y_zero_point, tf.float32)
+
+ new_dict = tensor_dict.copy()
+ new_dict[node.inputs[0]] = x
+ new_dict[node.inputs[3]] = w
+
+ # if bias is defined save it here
+ B = tensor_dict[node.inputs[8]] if len(node.inputs) == 9 else tf.constant(
+ [0], tf.float32)
+ if len(node.inputs) == 9:
+ B = tf.cast(B, tf.float32)
+ B_scale = x_scale * w_scale
+ B = tf.round(B / B_scale)
+ # Remove bias from inputs
+ node.inputs.remove(node.inputs[8])
+
+ # Remove scales and zero-points from inputs
+ for i in [7, 6, 5, 4, 2, 1]:
+ node.inputs.remove(node.inputs[i])
+
+ # Use common conv handling
+ conv_node = cls.conv(node, new_dict)[0]
+
+ # Process output
+ y = tf.round(conv_node / y_scale) + y_zero_point
+
+ # Add bias to the convolution
+ y = y + B
+
+ return [tf.cast(y, output_dtype)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_mat_mul.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_mat_mul.py
new file mode 100644
index 0000000..9462881
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/q_linear_mat_mul.py
@@ -0,0 +1,57 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("QLinearMatMul")
+class QLinearMatMul(BackendHandler):
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ a = tensor_dict[node.inputs[0]]
+ a_scale = tensor_dict[node.inputs[1]]
+ a_zero_point = tensor_dict[node.inputs[2]]
+ b = tensor_dict[node.inputs[3]]
+ b_scale = tensor_dict[node.inputs[4]]
+ b_zero_point = tensor_dict[node.inputs[5]]
+ y_scale = tensor_dict[node.inputs[6]]
+ y_zero_point = tensor_dict[node.inputs[7]]
+ y_dtype = y_zero_point.dtype
+
+ # reshape 1-D a_scale, a_zero_point, y_scale and
+ # y_zero_point so it can broadcast in arithmetic
+ # operations later
+ a_scale_shape = a_scale.get_shape().as_list()
+ if a_scale_shape and a_scale_shape[0] > 1:
+ a_scale = tf.reshape(a_scale, [a_scale_shape[0], 1])
+ a_zero_point = tf.reshape(a_zero_point, [a_scale_shape[0], 1])
+ y_scale_shape = y_scale.get_shape().as_list()
+ if y_scale_shape and y_scale_shape[0] > 1:
+ y_scale = tf.reshape(y_scale, [y_scale_shape[0], 1])
+ y_zero_point = tf.reshape(y_zero_point, [y_scale_shape[0], 1])
+
+ # cast all inputs to float32
+ a = tf.cast(a, tf.float32)
+ a_zero_point = tf.cast(a_zero_point, tf.float32)
+ b = tf.cast(b, tf.float32)
+ b_zero_point = tf.cast(b_zero_point, tf.float32)
+ y_zero_point = tf.cast(y_zero_point, tf.float32)
+
+ # dequantize a and b
+ dequantized_a = tf.subtract(a, a_zero_point)
+ dequantized_a = tf.multiply(dequantized_a, a_scale)
+ dequantized_b = tf.subtract(b, b_zero_point)
+ dequantized_b = tf.multiply(dequantized_b, b_scale)
+
+ # matmul
+ x = tf.matmul(dequantized_a, dequantized_b)
+
+ # quantize x
+ y = tf.divide(x, y_scale)
+ y = tf.round(y)
+ y = tf.add(y, y_zero_point)
+ y = tf.saturate_cast(y, y_dtype)
+
+ return [y]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/quantize_linear.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/quantize_linear.py
new file mode 100644
index 0000000..f9241f2
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/quantize_linear.py
@@ -0,0 +1,29 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("QuantizeLinear")
+class QuantizeLinear(BackendHandler):
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ y_scale = tensor_dict[node.inputs[1]]
+
+ x = tf.cast(x, tf.float32)
+ y = tf.divide(x, y_scale)
+ y = tf.round(y)
+ if len(node.inputs) == 3:
+ y_zero_point = tensor_dict[node.inputs[2]]
+ y_dtype = y_zero_point.dtype
+ y_zero_point = tf.cast(y_zero_point, tf.float32)
+ y = tf.add(y, y_zero_point)
+ else: # y_zero_point default dtype = uint8
+ y_dtype = tf.uint8
+
+ y = tf.saturate_cast(y, y_dtype)
+
+ return [y]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal.py
new file mode 100644
index 0000000..8ae0768
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("RandomNormal")
+@tf_func(tf.random_normal)
+class RandomNormal(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"mean": 0., "scale": 1.}, "rename": {"scale": "stddev"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal_like.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal_like.py
new file mode 100644
index 0000000..464d6c8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_normal_like.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("RandomNormalLike")
+@tf_func(tf.random_normal)
+class RandomNormalLike(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"mean": 0., "scale": 1.}, "rename": {"scale": "stddev"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ inputs = [kwargs["tensor_dict"][node.inputs[0]].get_shape()]
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform.py
new file mode 100644
index 0000000..9aa8ca9
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform.py
@@ -0,0 +1,27 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("RandomUniform")
+@tf_func(tf.random_uniform)
+class RandomUniform(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {
+ "default": {
+ "low": 0.,
+ "high": 1.
+ },
+ "rename": {
+ "low": "minval",
+ "high": "maxval"
+ }
+ }
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform_like.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform_like.py
new file mode 100644
index 0000000..f17c247
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/random_uniform_like.py
@@ -0,0 +1,28 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("RandomUniformLike")
+@tf_func(tf.random_uniform)
+class RandomUniformLike(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {
+ "default": {
+ "low": 0.,
+ "high": 1.
+ },
+ "rename": {
+ "low": "minval",
+ "high": "maxval"
+ }
+ }
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ inputs = [kwargs["tensor_dict"][node.inputs[0]].get_shape()]
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/range.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/range.py
new file mode 100644
index 0000000..3ede22e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/range.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Range")
+@tf_func(tf.range)
+class Round(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reciprocal.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reciprocal.py
new file mode 100644
index 0000000..ec6ca73
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reciprocal.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Reciprocal")
+@tf_func(tf.reciprocal)
+class Reciprocal(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l1.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l1.py
new file mode 100644
index 0000000..a429848
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l1.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceL1")
+@tf_func(tf.norm)
+class ReduceL1(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"ord": 1}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l2.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l2.py
new file mode 100644
index 0000000..6913b9f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_l2.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceL2")
+@tf_func(tf.norm)
+class ReduceL2(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"ord": 2}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum.py
new file mode 100644
index 0000000..3fad6dd
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum.py
@@ -0,0 +1,24 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceLogSum")
+class ReduceLogSum(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ axis = node.attrs.get("axes", list(range(len(x.get_shape().as_list()))))
+ keepdims = node.attrs.get("keepdims", 1) == 1
+ return [tf.log(tf.reduce_sum(x, axis=axis, keepdims=keepdims))]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum_exp.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum_exp.py
new file mode 100644
index 0000000..d9fdc9b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_log_sum_exp.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceLogSumExp")
+@tf_func(tf.reduce_logsumexp)
+class ReduceLogSumExp(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_max.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_max.py
new file mode 100644
index 0000000..768de25
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_max.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceMax")
+@tf_func(tf.reduce_max)
+class ReduceMax(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_mean.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_mean.py
new file mode 100644
index 0000000..cf3bf2a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_mean.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceMean")
+@tf_func(tf.reduce_mean)
+class ReduceMean(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_min.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_min.py
new file mode 100644
index 0000000..839cc3d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_min.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceMin")
+@tf_func(tf.reduce_min)
+class ReduceMin(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_12(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_prod.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_prod.py
new file mode 100644
index 0000000..5d3ea7e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_prod.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceProd")
+@tf_func(tf.reduce_prod)
+class ReduceProd(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum.py
new file mode 100644
index 0000000..fabc91e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceSum")
+@tf_func(tf.reduce_sum)
+class ReduceSum(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum_square.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum_square.py
new file mode 100644
index 0000000..cef9a7b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reduce_sum_square.py
@@ -0,0 +1,24 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .math_mixin import ReductionMixin
+
+
+@onnx_op("ReduceSumSquare")
+class ReduceSumSquare(ReductionMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ axis = node.attrs.get("axes", list(range(len(x.get_shape().as_list()))))
+ keepdims = node.attrs.get("keepdims", 1) == 1
+ return [tf.reduce_sum(tf.square(x), axis=axis, keepdims=keepdims)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/relu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/relu.py
new file mode 100644
index 0000000..c160f18
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/relu.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Relu")
+@tf_func(tf.nn.relu)
+class Relu(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reshape.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reshape.py
new file mode 100644
index 0000000..f493564
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reshape.py
@@ -0,0 +1,48 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Reshape")
+@tf_func(tf.reshape)
+class Reshape(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor = kwargs["tensor_dict"][node.inputs[0]]
+ if cls.SINCE_VERSION == 1:
+ shape = tf.constant(node.attrs["shape"], dtype=tf.int64)
+ else: # since_version >= 5
+ shape = tf.cast(kwargs["tensor_dict"][node.inputs[1]], tf.int64)
+ input_shape = tf.shape(tensor, out_type=tf.int64)
+
+ # Extract indicies of the shape parameter where
+ # a copy from the original dimension size is needed.
+ copy_indices = tf.squeeze(
+ tf.where(tf.equal(shape, tf.constant(0, dtype=tf.int64))), -1)
+
+ indices_gathered = tf.gather(input_shape, copy_indices)
+ indices_scattered = tf.sparse_to_dense(copy_indices,
+ tf.cast(tf.shape(shape), tf.int64),
+ indices_gathered)
+
+ # Perform the copy wherever requested (wherever dim_size == 0)
+ copied_shape = shape + indices_scattered
+ attrs = copy.deepcopy(node.attrs)
+ attrs.pop("shape", None)
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[tensor, copied_shape], attrs=attrs, **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_5(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/resize.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/resize.py
new file mode 100644
index 0000000..5e5c6b7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/resize.py
@@ -0,0 +1,274 @@
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.common.tf_helper import tf_shape
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+
+
+@onnx_op("Resize")
+@partial_support(True)
+@ps_description(
+ "Resize required 4D input in Tensorflow. " +
+ "For opset 11, only the following attributes and inputs " +
+ "conbination are supported in Tensorflow:\n\t1. mode=nearest, " +
+ "coordinate_transformation_mode=align_corners, nearest_mode=" +
+ "round_prefer_ceil, can use scales(*) or sizes.\n" +
+ "\t2. mode=nearest, coordinate_transformation_mode=asymmetric, " +
+ "nearest_mode=floor, can use scales(*) or sizes.\n" +
+ "\t3. mode=nearest, coordinate_transformation_mode=" +
+ "tf_half_pixel_for_nn, nearest_mode=floor, can use scales(*) " +
+ "or sizes.\n\t4. mode=linear, coordinate_transformation_mode=" +
+ "align_corners, can use scales(*) or sizes.\n\t5. mode=linear, " +
+ "coordinate_transformation_mode=asymmetric, can use scales(*) " +
+ "or sizes.\n\t6. mode=linear, coordinate_transformation_mode=" +
+ "half_pixel, can use scales(*) or sizes.\n\t7. mode=cubic, " +
+ "coordinate_transformation_mode=align_corners, " +
+ "cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) " +
+ "or sizes.\n\t8. mode=cubic, coordinate_transformation_mode=" +
+ "asymmetric, cubic_coeff_a=-0.5, exclude_outside=1, can use " +
+ "scales(*) or sizes.\n\t9. mode=cubic, coordinate_transformation_mode=" +
+ "half_pixel, cubic_coeff_a=-0.5, exclude_outside=1, can use " +
+ "scales(*) or sizes.\n\t10. mode=nearest, " +
+ "coordinate_transformation_mode=tf_crop_and_resize, " +
+ "extrapolation_value=any_float_value, nearest_mode=round_prefer_ceil, " +
+ "can use scales or sizes.\n\t11. mode=linear, " +
+ "coordinate_transformation_mode=tf_crop_and_resize, " +
+ "extrapolation_value=any_float_value, can use scales or sizes." +
+ "\n\t- Note (*): The accuracy of your model will go down, if the height and " +
+ "the width of the new sizes(scales * origial sizes) are not in whole numbers."
+)
+class Resize(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_shape = x.get_shape().as_list()
+ if len(x_shape) != 4:
+ exception.OP_UNSUPPORTED_EXCEPT("Resize required 4D input", "Tensorflow")
+ if cls.SINCE_VERSION >= 11:
+ # supported attributes combination
+ # ____________________________________________________________________________________________________________________________________________________
+ # | mode | coordinate_transformation_mode | cubic_coeff_a | exclude_outside | extrapolation_value | nearest_mode | scales | sizes |
+ # |_________|________________________________|_______________|_________________|_____________________|___________________|_______________|___________|
+ # | nearest | align_corners | not apply | 0 | not apply | round_prefer_ceil | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | nearest | asymmetric | not apply | 0 | not apply | floor | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | nearest | tf_half_pixel_for_nn | not apply | 0 | not apply | floor | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | linear | align_corners | not apply | 0 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | linear | asymmetric | not apply | 0 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | linear | half_pixel | not apply | 0 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | cubic | align_corners | -0.5 | 1 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | cubic | asymmetric | -0.5 | 1 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | cubic | half_pixel | -0.5 | 1 | not apply | not apply | supported (1) | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | nearest | tf_crop_and_resize | not apply | 0 | any float value | round_prefer_ceil | supported | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # | linear | tf_crop_and_resize | not apply | 0 | any float value | not apply | supported | supported |
+ # |---------|--------------------------------|---------------|-----------------|---------------------|-------------------|---------------|-----------|
+ # Note:
+ # 1. The accuracy of your model will go down, if the height and the width of the new sizes(scales * origial sizes) are not in whole numbers.
+ coordinate_transformation_mode = node.attrs.get(
+ "coordinate_transformation_mode", "half_pixel")
+ cubic_coeff_a = node.attrs.get("cubic_coeff_a", -0.75)
+ exclude_outside = node.attrs.get("exclude_outside", 0)
+ mode = node.attrs.get("mode", "nearest")
+ nearest_mode = node.attrs.get("nearest_mode", "round_prefer_floor")
+ if coordinate_transformation_mode == "pytorch_half_pixel":
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Resize coordinate_transformation_mode=pytorch_half_pixel",
+ "Tensorflow")
+ if (coordinate_transformation_mode == "half_pixel" and mode == "nearest"
+ ) or (coordinate_transformation_mode == "tf_half_pixel_for_nn" and
+ mode in ["linear", "cubic"]) or (
+ coordinate_transformation_mode == "tf_crop_and_resize" and
+ mode == "cubic"):
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Resize coordinate_transformation_mode=" +
+ coordinate_transformation_mode + " and mode=" + mode, "Tensorflow")
+ if (exclude_outside == 1 and
+ mode in ["nearest", "linear"]) or (exclude_outside == 0 and
+ mode == "cubic"):
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Resize mode=" + mode + " and exclude_outside=" +
+ str(exclude_outside), "Tensorflow")
+ if cubic_coeff_a != -0.5 and mode == "cubic":
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Resize mode=cubic and cubic_coeff_a=" + cubic_coeff_a,
+ "Tensorflow")
+ if mode == "nearest":
+ if (nearest_mode in [
+ "round_prefer_floor", "ceil"
+ ]) or (coordinate_transformation_mode in [
+ "align_corners", "tf_crop_and_resize"
+ ] and nearest_mode == "floor") or (coordinate_transformation_mode in [
+ "asymmetric", "tf_half_pixel_for_nn"
+ ] and nearest_mode == "round_prefer_ceil"):
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Resize coordinate_transformation_mode=" +
+ coordinate_transformation_mode +
+ ", mode=nearest and nearest_mode=" + nearest_mode, "Tensorflow")
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_shape = x.get_shape().as_list()
+ scales = kwargs["tensor_dict"][node.inputs[1]]
+
+ n_in_scales_is_one = tf.equal(scales[0], 1)
+ c_in_scales_is_one = tf.logical_or(tf.equal(scales[1], 1),
+ tf.equal(scales[3], 1))
+ assert_n_c_in_scales_are_ones = tf.Assert(
+ tf.logical_and(n_in_scales_is_one, c_in_scales_is_one), [scales])
+
+ with tf.control_dependencies([assert_n_c_in_scales_are_ones]):
+ x_in_NCHW_format = tf.equal(scales[1], 1)
+ h_w_scale = tf.where(x_in_NCHW_format, scales[2:], scales[1:3])
+ h_w_shape = tf.where(x_in_NCHW_format, x_shape[2:], x_shape[1:3])
+ new_h_w_shape = tf.cast(h_w_scale * tf.cast(h_w_shape, scales.dtype),
+ tf.int32)
+
+ mode = node.attrs.get("mode", "nearest")
+ if mode.lower() == "linear":
+ mode = tf.image.ResizeMethod.BILINEAR
+ else:
+ mode = tf.image.ResizeMethod.NEAREST_NEIGHBOR
+
+ def process_NCHW_format(x):
+ x_t = tf.transpose(x, perm=[0, 2, 3, 1])
+ y = tf.image.resize_images(x_t, size=new_h_w_shape, method=mode)
+ y_t = tf.transpose(y, perm=[0, 3, 1, 2])
+ return y_t
+
+ def process_NHWC_format(x):
+ y = tf.image.resize_images(x, size=new_h_w_shape, method=mode)
+ return y
+
+ output = tf.cond(x_in_NCHW_format, lambda: process_NCHW_format(x),
+ lambda: process_NHWC_format(x))
+
+ return [output]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ x_shape = tf_shape(x)
+ roi = tensor_dict[node.inputs[1]]
+ scales = tensor_dict[node.inputs[2]]
+ sizes = tensor_dict[node.inputs[3]] if len(
+ node.inputs) == 4 else tf.constant([], dtype=tf.int64)
+ coordinate_transformation_mode = node.attrs.get(
+ "coordinate_transformation_mode", "half_pixel")
+ extrapolation_value = node.attrs.get("extrapolation_value", 0.0)
+ mode = node.attrs.get("mode", "nearest")
+
+ param = scales if len(node.inputs) == 3 else sizes
+ n_in_param_is_one = tf.equal(param[0], 1)
+ c_in_param_is_one = tf.logical_or(tf.equal(param[1], 1),
+ tf.equal(param[3], 1))
+ assert_n_c_in_param_are_ones = tf.Assert(
+ tf.logical_and(n_in_param_is_one, c_in_param_is_one), [param])
+
+ with tf.control_dependencies([assert_n_c_in_param_are_ones]):
+ if mode.lower() == "linear":
+ tf_resize = tf.image.resize_bilinear
+ mode = "bilinear"
+ elif mode.lower() == "cubic":
+ tf_resize = tf.image.resize_bicubic
+ else:
+ tf_resize = tf.image.resize_nearest_neighbor
+
+ x_in_NCHW_format = tf.equal(param[1], 1)
+
+ if len(node.inputs) == 3: # only scales is defined
+ h_w_scale = tf.where(x_in_NCHW_format, scales[2:], scales[1:3])
+ h_w_shape = tf.where(x_in_NCHW_format, x_shape[2:], x_shape[1:3])
+ new_size = tf.cast(h_w_scale * tf.cast(h_w_shape, scales.dtype),
+ tf.int32)
+ else: # sizes is defined
+ # The number of elements of 'sizes' should be the same as the rank of input 'X'
+ sizes.set_shape(x_shape.shape)
+ new_size = tf.cast(tf.where(x_in_NCHW_format, sizes[2:], sizes[1:3]),
+ tf.int32)
+ # Tensorflow require the shape of "size" in the "tf.image.resize" must be known at
+ # graph creation time. However in the dynamic shape situation, the shape of "new_size"
+ # will be "None", the actual shape can only be determine at runtime. But we know
+ # "new_size" should always contain [h, w], therefore the shape must be 2.
+ new_size.set_shape([2])
+
+ def get_NCHW_boxes():
+ indices = []
+ x_rank = len(x.get_shape())
+ for i in range(2, x_rank):
+ indices.insert(i - 2, i)
+ indices.insert(i, i + x_rank)
+ return tf.expand_dims(tf.gather(roi, indices, axis=0), 0)
+
+ def get_NHWC_boxes():
+ indices = []
+ x_rank = len(x.get_shape())
+ for i in range(1, x_rank - 1):
+ indices.insert(i - 1, i)
+ indices.insert(i + 1, i + x_rank)
+ return tf.expand_dims(tf.gather(roi, indices, axis=0), 0)
+
+ box_indices = tf.cast(tf.range(0, x_shape[0]), dtype=tf.int32)
+
+ def process_NCHW_format():
+ x_t = tf.transpose(x, perm=[0, 2, 3, 1])
+ if coordinate_transformation_mode == "tf_crop_and_resize":
+ boxes = get_NCHW_boxes()
+ y = tf.image.crop_and_resize(x_t, boxes, box_indices, new_size, mode,
+ extrapolation_value)
+ elif coordinate_transformation_mode == "align_corners":
+ y = tf_resize(x_t,
+ size=new_size,
+ align_corners=True,
+ half_pixel_centers=False)
+ elif coordinate_transformation_mode == "asymmetric":
+ y = tf_resize(x_t,
+ size=new_size,
+ align_corners=False,
+ half_pixel_centers=False)
+ else: # half_pixel or tf_half_pixel_for_nn
+ y = tf_resize(x_t,
+ size=new_size,
+ align_corners=False,
+ half_pixel_centers=True)
+ return tf.transpose(y, perm=[0, 3, 1, 2])
+
+ def process_NHWC_format():
+ if coordinate_transformation_mode == "tf_crop_and_resize":
+ boxes = get_NHWC_boxes()
+ return tf.image.crop_and_resize(x, boxes, box_indices, new_size, mode,
+ extrapolation_value)
+ elif coordinate_transformation_mode == "align_corners":
+ return tf_resize(x,
+ size=new_size,
+ align_corners=True,
+ half_pixel_centers=False)
+ elif coordinate_transformation_mode == "asymmetric":
+ return tf_resize(x,
+ size=new_size,
+ align_corners=False,
+ half_pixel_centers=False)
+ else: # half_pixel or tf_half_pixel_for_nn
+ return tf_resize(x,
+ size=new_size,
+ align_corners=False,
+ half_pixel_centers=True)
+
+ output = tf.cond(x_in_NCHW_format, process_NCHW_format,
+ process_NHWC_format)
+
+ return [output]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reverse_sequence.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reverse_sequence.py
new file mode 100644
index 0000000..07e069e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/reverse_sequence.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("ReverseSequence")
+@tf_func(tf.reverse_sequence)
+class ReverseSequence(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"time_axis": 0, "batch_axis": 1},
+ "rename": {"time_axis": "seq_axis"}}
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn.py
new file mode 100644
index 0000000..02b4ca8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn.py
@@ -0,0 +1,161 @@
+from functools import partial
+
+import tensorflow as tf
+
+from onnx_tf.common import get_unique_suffix
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from .rnn_mixin import RNNMixin
+
+
+@onnx_op("RNN")
+@partial_support(True)
+@ps_description("RNN with clip is not supported in Tensorflow.")
+class RNN(RNNMixin, BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ if "clip" in node.attrs:
+ exception.OP_UNSUPPORTED_EXCEPT("RNN with clip", "Tensorflow")
+
+ @classmethod
+ def _custom_getter(cls,
+ getter,
+ name,
+ node=None,
+ tensor_dict=None,
+ is_bidirectional=None,
+ *args,
+ **kwargs):
+ names = name.split("/")
+ if is_bidirectional:
+ if "fw" in names:
+ index = 0
+ elif "bw" in names:
+ index = 1
+ else:
+ raise RuntimeError("Can not get {} for bidirectional. "
+ "Either fw and bw is not in name scope.".format(
+ names[-1]))
+ if names[-1] == "kernel":
+ if is_bidirectional:
+ w = tf.split(tensor_dict[node.inputs[1]], 2)[index]
+ r = tf.split(tensor_dict[node.inputs[2]], 2)[index]
+ else:
+ w = tensor_dict[node.inputs[1]]
+ r = tensor_dict[node.inputs[2]]
+ new_w = tf.transpose(tf.squeeze(w))
+ new_r = tf.transpose(tf.squeeze(r))
+ kernel = tf.concat([new_w, new_r], 0)
+ return kernel
+ if names[-1] == "bias":
+ if len(node.inputs) >= 4:
+ if is_bidirectional:
+ b = tf.split(tensor_dict[node.inputs[3]], 2)[index]
+ else:
+ b = tensor_dict[node.inputs[3]]
+ w_b, r_b = tf.split(tf.squeeze(b), 2)
+ w_b = tf.transpose(w_b)
+ r_b = tf.transpose(r_b)
+ return tf.add(w_b, r_b)
+ return getter(name, *args, **kwargs)
+ return getter(name, *args, **kwargs)
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+ input_shape = x.get_shape().as_list()
+ input_size = len(node.inputs)
+ hidden_size = node.attrs["hidden_size"]
+ direction = node.attrs.get("direction", "forward")
+ num_directions = 2 if direction == "bidirectional" else 1
+
+ output_sequence = node.attrs.get("output_sequence", 0)
+
+ # TODO(fumihwh): check if prev node is one of RNN
+ # process input if it comes from other previous cell
+ # which has shape [seq_length, num_directions, batch_size, hidden_size]
+ if len(input_shape) == 4 and input_shape[1] == 1:
+ x = tf.squeeze(x)
+
+ sequence_length = None
+ if input_size >= 5 and node.inputs[4] in tensor_dict:
+ sequence_length = tensor_dict[node.inputs[4]]
+
+ cell_kwargs = {}
+
+ tf_activations = [tf.nn.tanh]
+ if "activations" in node.attrs:
+ activations = list(map(lambda x: x.lower(), node.attrs["activations"]))
+ activation_alpha = node.attrs.get("activation_alpha", [None] * 2)
+ activation_beta = node.attrs.get("activation_beta", [None] * 2)
+ tf_activations = [
+ cls.rnn_get_activation(activations[0], activation_alpha[0],
+ activation_beta[0])
+ ]
+ if num_directions == 2:
+ tf_activations.append(
+ cls.rnn_get_activation(activations[1], activation_alpha[1],
+ activation_beta[1]))
+
+ # TODO(fumihwh): check if reverse and bidirectional works
+ with tf.variable_scope(
+ "RNN_" + get_unique_suffix(),
+ custom_getter=partial(
+ cls._custom_getter,
+ node=node,
+ tensor_dict=tensor_dict,
+ is_bidirectional=num_directions == 2)):
+
+ cell_kwargs["num_units"] = hidden_size
+ initial_state = None
+ initial_state_bw = None
+ if input_size == 6:
+ initial_h = tensor_dict.get(node.inputs[5], None)
+ if initial_h is not None:
+ initial_state = (initial_h[0],)
+ if num_directions == 2:
+ initial_state_bw = (initial_h[1],)
+
+ rnn_kwargs = {}
+ if num_directions == 1:
+ rnn_kwargs["initial_state"] = initial_state
+ elif num_directions == 2:
+ rnn_kwargs["initial_state_fw"] = initial_state
+ rnn_kwargs["initial_state_bw"] = initial_state_bw
+ rnn_kwargs["sequence_length"] = sequence_length
+ rnn_kwargs["time_major"] = True
+ rnn_kwargs["dtype"] = tf.float32
+
+ outputs, states = cls.rnn(x, tf.nn.rnn_cell.BasicRNNCell, cell_kwargs,
+ rnn_kwargs, tf_activations, direction)
+
+ if num_directions == 1:
+ state = states[0]
+ h = tf.expand_dims(state, 0)
+ output = tf.expand_dims(outputs, 1)
+ else:
+ state_fw = states[0][0]
+ state_bw = states[1][0]
+ output_fw = outputs[0]
+ output_bw = outputs[1]
+ h_fw = tf.expand_dims(state_fw, 0)
+ h_bw = tf.expand_dims(state_bw, 0)
+ h = tf.concat((h_fw, h_bw), axis=0)
+ output_fw = tf.expand_dims(output_fw, 1)
+ output_bw = tf.expand_dims(output_bw, 1)
+ output = tf.concat((output_fw, output_bw), axis=1)
+
+ return [output, h] if output_sequence == 0 else [h]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn_mixin.py
new file mode 100644
index 0000000..3cd5852
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/rnn_mixin.py
@@ -0,0 +1,86 @@
+from functools import partial
+
+import tensorflow as tf
+# import tensorflow_probability as tfp
+from tensorflow.python.ops import array_ops
+
+from onnx_tf.common import exception
+
+
+class RNNMixin(object):
+
+ ONNX_ACTIVATION_MAPPING = {
+ # Added from tf 1.8
+ # "affine": tf.contrib.distributions.bijectors.AffineScalar,
+ # tf.contrib was removed since tf 2.0,
+ # Class Affine had been move to the following module
+ # "affine": tfp.bijectors.Affine,
+ "elu": tf.nn.elu,
+ "hard_sigmoid": tf.keras.backend.hard_sigmoid,
+ "leaky_relu": tf.nn.leaky_relu,
+ "relu": tf.nn.relu,
+ "sigmoid": tf.sigmoid,
+ "softsign": tf.nn.softsign,
+ "softplus": tf.nn.softplus,
+ "tanh": tf.tanh,
+ "thresholded_relu": tf.keras.layers.ThresholdedReLU,
+ }
+
+ @classmethod
+ def rnn(cls, x, cell_class, cell_kwargs, rnn_kwargs, activations, direction):
+ cell_kwargs["activation"] = activations[0]
+
+ rnn_cell = [cell_class(**cell_kwargs)]
+ cell_fw = tf.nn.rnn_cell.MultiRNNCell(rnn_cell)
+
+ if direction == "bidirectional":
+ cell_kwargs["activation"] = activations[1]
+ rnn_cell_bw = [cell_class(**cell_kwargs)]
+ cell_bw = tf.nn.rnn_cell.MultiRNNCell(rnn_cell_bw)
+
+ if direction == "forward":
+ outputs, states = tf.nn.dynamic_rnn(cell_fw, x, **rnn_kwargs)
+ elif direction == "bidirectional":
+ outputs, states = tf.nn.bidirectional_dynamic_rnn(cell_fw, cell_bw, x,
+ **rnn_kwargs)
+ elif direction == "reverse":
+
+ def _reverse(input_, seq_dim):
+ return array_ops.reverse(input_, axis=[seq_dim])
+
+ time_dim = 0
+ inputs_reverse = _reverse(x, time_dim)
+ outputs, states = tf.nn.dynamic_rnn(cell_fw, inputs_reverse, **rnn_kwargs)
+ outputs = _reverse(outputs, time_dim)
+
+ return outputs, states
+
+ @classmethod
+ def rnn_get_activation(cls, name, alpha, beta):
+ if name not in cls.ONNX_ACTIVATION_MAPPING:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Activation function {} for {}".format(name, cls.__name__),
+ "Tensorflow")
+ activation = cls.ONNX_ACTIVATION_MAPPING[name]
+ kwargs = {}
+ if name == "affine":
+ kwargs["scale"] = alpha
+ kwargs["shift"] = beta
+ activation = activation(**kwargs)
+ elif name == "elu":
+ if alpha != 1:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Activation function {} with alpha={} for {}".format(
+ name, alpha, cls.__name__), "Tensorflow")
+ elif name == "hard_sigmoid":
+ if alpha != 0.2 or beta != 0.5:
+ exception.OP_UNSUPPORTED_EXCEPT(
+ "Activation function {} with alpha={}, beta={} for {}".format(
+ name, alpha, beta, cls.__name__), "Tensorflow")
+ elif name == "leaky_relu":
+ kwargs["alpha"] = alpha or 0.01
+ activation = partial(activation, **kwargs)
+ elif name == "thresholded_relu":
+ kwargs["theta"] = alpha
+ activation = activation(**kwargs)
+ return activation
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/round.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/round.py
new file mode 100644
index 0000000..28dba15
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/round.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Round")
+@tf_func(tf.round)
+class Round(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan.py
new file mode 100644
index 0000000..4bc7644
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan.py
@@ -0,0 +1,24 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .scan_mixin import ScanMixin
+
+
+@onnx_op("Scan")
+class Scan(ScanMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ return cls.scan(node, kwargs["tensor_dict"],
+ kwargs.get("strict", True))
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan_mixin.py
new file mode 100644
index 0000000..3fd8dab
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scan_mixin.py
@@ -0,0 +1,188 @@
+import tensorflow as tf
+from onnx.helper import make_opsetid
+import onnx_tf
+from onnx_tf.common import data_type
+
+
+class ScanMixin(object):
+
+ @classmethod
+ def scan(cls, node, input_dict, strict):
+ current_opset = [make_opsetid(cls.DOMAIN, cls.VERSION)]
+
+ body = node.attrs["body"]
+
+ # in version 8, node.inputs[0] is the sequence_lens
+ node_inputs = node.inputs if cls.SINCE_VERSION != 8 else \
+ node.inputs[1:]
+ # M
+ num_scan_inputs = int(node.attrs["num_scan_inputs"])
+ # N = num_inputs - M
+ num_state_vars = len(node_inputs) - num_scan_inputs
+ # K = num_outputs - N
+ num_scan_outputs = len(node.outputs) - num_state_vars
+
+ """
+ Function to run subgraph used with tf.scan
+ """
+
+ def run_subgraph(a, b):
+ input_values = {}
+ # set the input values for the subgraph
+ # set the values for the state variables
+ for i in range(num_state_vars):
+ input_values[body.input[i].name] = a[i]
+ # set the values for the scan inputs
+ for i in range(num_scan_inputs):
+ input_values[body.input[i + num_state_vars].name] = b[i]
+
+ # get the tensor operations for the onnx graph
+ subgraph_tensor_dict = onnx_tf.backend.onnx_graph_to_tensorflow_ops(
+ subgraph=body,
+ input_values=input_values,
+ tensor_dict=input_dict,
+ opset=current_opset,
+ strict=strict)
+ # return sequence of tensors for every subgraph output
+ outputs = [subgraph_tensor_dict[output.name] for output in body.output]
+ return outputs
+
+ scan_input_axes = node.attrs.get("scan_input_axes", [0] * num_scan_inputs)
+ scan_input_directions = node.attrs.get(
+ "directions" if cls.SINCE_VERSION == 8 else "scan_input_directions",
+ [0] * num_scan_inputs)
+ scan_output_axes = node.attrs.get("scan_output_axes",
+ [0] * num_scan_outputs)
+ scan_output_directions = node.attrs.get("scan_output_directions",
+ [0] * num_scan_outputs)
+
+ # if version 8 read the sequnce_lens from the first input
+ if cls.SINCE_VERSION == 8:
+ sequence_lens = input_dict[node.inputs[0]] \
+ if node.inputs[0] != '' else None
+
+ inputs = [input_dict[node_input] for node_input in node_inputs]
+
+ scan_inputs = inputs[num_state_vars:]
+ # loop over all the scan inputs and apply transpose depending
+ # on input axes provided and also reverse the scan inputs if
+ # reverse direction for scan is provided
+ for i in range(num_scan_inputs):
+ # if input axes are different than 0, use transpose to scan over
+ # the provided axes
+ if scan_input_axes[i] != 0:
+ transpose_perm = cls._calc_transpose_perm_input(tf.rank(scan_inputs[i]),
+ scan_input_axes[i])
+ scan_inputs[i] = tf.transpose(scan_inputs[i], transpose_perm)
+
+ # check for reverse direction scans
+ if scan_input_directions[i] == 1:
+ # version 8 has a batch dimension
+ axis = 0 if cls.SINCE_VERSION != 8 else 1
+ scan_inputs[i] = tf.reverse(scan_inputs[i], [axis])
+
+ state_vars_init = inputs[:num_state_vars]
+
+ scan_outputs_init = []
+ # generate sequence of zero tensors for all scan outputs
+ # with the correct shape and dtype
+ for scan_output in body.output[num_state_vars:]:
+ tensor_type = scan_output.type.tensor_type
+ shape = [
+ d.dim_value if (d.dim_value > 0 and d.dim_param == "") else None
+ for d in tensor_type.shape.dim
+ ]
+ dtype = data_type.onnx2tf(tensor_type.elem_type)
+ scan_outputs_init.append(tf.zeros(shape, dtype=dtype))
+
+ # tf.scan initilizer is state_variables_init + scan_outputs_init
+ initializer = state_vars_init + scan_outputs_init
+
+ if cls.SINCE_VERSION == 8:
+ # version == 8
+ # function to process the batches. it is used with tf.map_fn
+ def run_batches(x):
+ # state vars initial values per batch
+ initial = x[0]
+ # scan inputs per batch
+ scan_inputs = x[1]
+ # sequence length for the batch
+ seq_len = x[2]
+
+ # slice the input to the current sequence len
+ scan_inputs = [scan_input[:seq_len, ...] for scan_input in scan_inputs]
+
+ # run scan on the current batch
+ out = tf.scan(run_subgraph,
+ scan_inputs,
+ initializer=initial + scan_outputs_init)
+
+ # pad to the original shape with zeros
+ paddings = [[0, tf.shape(x[1][0], out_type=seq_len.dtype)[0] - seq_len]]
+ for i in range(len(out)):
+ pads = tf.concat(
+ [paddings,
+ tf.zeros([(tf.rank(out[i]) - 1), 2], dtype=tf.int32)],
+ axis=0)
+ out[i] = tf.pad(out[i], pads)
+ return out
+
+ if sequence_lens is None:
+ # if sequence_lens is None, fill it with the shape of
+ # the input axis 1
+ sequence_lens = tf.fill([tf.shape(scan_inputs[0])[0]],
+ tf.shape(scan_inputs[0], out_type=tf.int32)[1])
+
+ output_types = [
+ data_type.onnx2tf(output.type.tensor_type.elem_type)
+ for output in body.output
+ ]
+ # run scan for every batch
+ out = tf.map_fn(run_batches,
+ (state_vars_init, scan_inputs, sequence_lens),
+ dtype=output_types)
+
+ state_vars_outputs = []
+ # extract the final values of the state variables
+ for state_var in out[:num_state_vars]:
+ state_vars_outputs.append(
+ tf.map_fn(lambda x: x[0][x[1] - 1], (state_var, sequence_lens),
+ state_var.dtype))
+ else:
+ # version > 8
+ # run the scan
+ out = tf.scan(run_subgraph, scan_inputs, initializer=initializer)
+
+ # extract the final values of the state variables
+ state_vars_outputs = [
+ state_var[tf.shape(state_var)[0] - 1]
+ for state_var in out[:num_state_vars]
+ ]
+
+ scan_outputs = out[num_state_vars:]
+
+ # post process the scan outputs depending on the directions and
+ # axes provided.
+ for i in range(num_scan_outputs):
+ # check for reverse direction scan outputs
+ if scan_output_directions[i] == 1:
+ scan_outputs[i] = tf.reverse(scan_outputs[i], [0])
+
+ if scan_output_axes[i] != 0:
+ transpose_perm = cls._calc_transpose_perm_output(
+ tf.rank(scan_outputs[i]), scan_output_axes[i])
+ scan_outputs[i] = tf.transpose(scan_outputs[i], transpose_perm)
+
+ return state_vars_outputs + scan_outputs
+
+ @classmethod
+ def _calc_transpose_perm_input(cls, rank, axis):
+ if axis < 0:
+ axis = rank + axis
+ return tf.concat([[axis], tf.range(axis), tf.range(axis + 1, rank)], 0)
+
+ @classmethod
+ def _calc_transpose_perm_output(cls, rank, axis):
+ if axis < 0:
+ axis = rank + axis
+ return tf.concat([tf.range(1, axis + 1), [0], tf.range(axis + 1, rank)], 0)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter.py
new file mode 100644
index 0000000..67c8596
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter.py
@@ -0,0 +1,12 @@
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+from onnx_tf.handlers.backend.scatter_elements import ScatterElements
+
+
+@onnx_op("Scatter")
+class Scatter(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return ScatterElements.version_11(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_elements.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_elements.py
new file mode 100644
index 0000000..03ea802
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_elements.py
@@ -0,0 +1,64 @@
+import tensorflow as tf
+
+from onnx_tf.common.tf_helper import tf_shape
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .gather_and_scatter_mixin import GatherAndScatterMixin
+
+
+@onnx_op("ScatterElements")
+class ScatterElements(GatherAndScatterMixin, BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ axis = node.attrs.get("axis", 0)
+ data = kwargs["tensor_dict"][node.inputs[0]]
+ indices = kwargs["tensor_dict"][node.inputs[1]]
+ updates = kwargs["tensor_dict"][node.inputs[2]]
+
+ # poocess negative axis
+ axis = axis if axis >= 0 else tf.add(tf.rank(data), axis)
+
+ # check are there any indices are out of bounds
+ result = cls.chk_idx_out_of_bounds_along_axis(data, axis, indices)
+ msg = 'ScatterElements indices are out of bounds, please double check the indices and retry.'
+ with tf.control_dependencies([tf.compat.v1.assert_equal(result, True, message=msg)]):
+ # process negative indices
+ indices = cls.process_neg_idx_along_axis(data, axis, indices)
+
+ # Calculate shape of the tensorflow version of indices tensor.
+ sparsified_dense_idx_shape = tf_shape(updates)
+
+ # Move on to convert ONNX indices to tensorflow indices in 2 steps:
+ #
+ # Step 1:
+ # What would the index tensors look like if updates are all
+ # dense? In other words, produce a coordinate tensor for updates:
+ #
+ # coordinate[i, j, k ...] = [i, j, k ...]
+ # where the shape of "coordinate" tensor is same as that of updates.
+ #
+ # Step 2:
+ # But the coordinate tensor needs some correction because coord
+ # vector at position axis is wrong (since we assumed update is dense,
+ # but it is not at the axis specified).
+ # So we update coordinate vector tensor elements at psotion=axis with
+ # the sparse coordinate indices.
+
+ idx_tensors_per_axis = tf.meshgrid(*list(
+ map(lambda x: tf.range(x, dtype=tf.dtypes.int64),
+ sparsified_dense_idx_shape)),
+ indexing='ij')
+ idx_tensors_per_axis[axis] = indices
+ dim_expanded_idx_tensors_per_axis = list(
+ map(lambda x: tf.expand_dims(x, axis=-1), idx_tensors_per_axis))
+ coordinate = tf.concat(dim_expanded_idx_tensors_per_axis, axis=-1)
+
+ # Now the coordinate tensor is in the shape
+ # [updates.shape, updates.rank]
+ # we need it to flattened into the shape:
+ # [product(updates.shape), updates.rank]
+ indices = tf.reshape(coordinate, [-1, tf.rank(data)])
+ updates = tf.reshape(updates, [-1])
+
+ return [tf.tensor_scatter_nd_update(data, indices, updates)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_nd.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_nd.py
new file mode 100644
index 0000000..51cc40d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/scatter_nd.py
@@ -0,0 +1,20 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from .gather_and_scatter_mixin import GatherAndScatterMixin
+
+
+@onnx_op("ScatterND")
+class ScatterND(GatherAndScatterMixin, BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ data = kwargs["tensor_dict"][node.inputs[0]]
+ indices = kwargs["tensor_dict"][node.inputs[1]]
+ updates = kwargs["tensor_dict"][node.inputs[2]]
+
+ result = cls.chk_idx_out_of_bounds(data, indices)
+ msg = 'ScatterND indices are out of bounds, please double check the indices and retry.'
+ with tf.control_dependencies([tf.compat.v1.assert_equal(result, True, message=msg)]):
+ return [tf.tensor_scatter_nd_update(data, cls.process_neg_idx(data, indices), updates)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/selu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/selu.py
new file mode 100644
index 0000000..1bf31d2
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/selu.py
@@ -0,0 +1,33 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Selu")
+@tf_func(tf.nn.selu)
+class Selu(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ if "alpha" not in node.attrs and "gamma" not in node.attrs:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ x = tensor_dict[node.inputs[0]]
+ alpha = node.attrs.get("alpha", 1.67326319217681884765625)
+ gamma = node.attrs.get("gamma", 1.05070102214813232421875)
+
+ return [
+ tf.clip_by_value(x, 0, tf.reduce_max(x)) * gamma +
+ (tf.exp(tf.clip_by_value(x, tf.reduce_min(x), 0)) - 1) * alpha * gamma
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_at.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_at.py
new file mode 100644
index 0000000..79a25f6
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_at.py
@@ -0,0 +1,45 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("SequenceAt")
+class SequenceAt(BackendHandler):
+
+ @classmethod
+ def chk_pos_in_bounds(cls, input_seq, pos):
+ """
+ Check the position is in-bounds with respect to the sequence.
+ Accepted range for 'position' is in [-n, n - 1], where n is the
+ number of tensors in 'input_sequence'.
+
+ :param input_seq: input sequence
+ :param pos: position of the output tensor
+
+ :return: True if position is in-bounds or input length is dynamic.
+ """
+ seq_length = input_seq.shape[0].value
+ if seq_length is None:
+ return True
+
+ seq_length = tf.cast(seq_length, pos.dtype)
+
+ cond1 = tf.greater_equal(pos, tf.negative(seq_length))
+ cond2 = tf.less_equal(pos, seq_length - 1)
+
+ # pos >= -n and pos < n
+ return tf.reduce_all(tf.logical_and(cond1, cond2))
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_sequence = tensor_dict[node.inputs[0]]
+ position = tensor_dict[node.inputs[1]]
+
+ # check whether position is in-bounds and assert if not
+ result = cls.chk_pos_in_bounds(input_sequence, position)
+ assert_pos = tf.Assert(tf.equal(result, True), [result])
+
+ with tf.control_dependencies([assert_pos]):
+ return [input_sequence[position]]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_construct.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_construct.py
new file mode 100644
index 0000000..32aa33b
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_construct.py
@@ -0,0 +1,26 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("SequenceConstruct")
+class SequenceConstruct(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ # create an empty sequence first
+ tensor_dict = kwargs["tensor_dict"]
+ dtype = tensor_dict[node.inputs[0]].dtype
+ input_sequence = tf.ragged.constant([], dtype=dtype)
+
+ # insert tensors at the end of sequence
+ for i in range(len(node.inputs)):
+ input_tensor = tf.expand_dims(tensor_dict[node.inputs[i]], 0)
+ if input_sequence.shape[0] == 0:
+ output_seq = tf.RaggedTensor.from_tensor(input_tensor)
+ else:
+ output_seq = tf.concat([input_sequence, input_tensor], axis=0)
+ input_sequence = output_seq
+
+ return [output_seq]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_empty.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_empty.py
new file mode 100644
index 0000000..5609830
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_empty.py
@@ -0,0 +1,20 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.common import data_type
+from onnx import mapping
+
+
+@onnx_op("SequenceEmpty")
+class SequenceEmpty(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ default_dtype = mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')]
+ dtype = data_type.onnx2tf(node.attrs.get("dtype", default_dtype))
+
+ ragged = tf.RaggedTensor.from_row_lengths(values=[], row_lengths=[])
+ sparse = tf.cast(ragged.to_sparse(), dtype)
+ return [tf.RaggedTensor.from_sparse(sparse)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_erase.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_erase.py
new file mode 100644
index 0000000..f773547
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_erase.py
@@ -0,0 +1,45 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("SequenceErase")
+class SequenceErase(BackendHandler):
+
+ @classmethod
+ def chk_pos_in_bounds(cls, input_seq, pos):
+ """
+ Check the position is in-bounds with respect to the sequence.
+ Accepted range for 'position' is in [-n, n - 1], where n is the
+ number of tensors in 'input_sequence'.
+
+ :param input_seq: input sequence
+ :param pos: position of the output tensor
+
+ :return: True if position is in-bounds
+ """
+ seq_length = tf.shape(input_seq.to_sparse(), out_type=pos.dtype)[0]
+
+ cond1 = tf.greater_equal(pos, tf.negative(seq_length))
+ cond2 = tf.less_equal(pos, seq_length - 1)
+
+ # pos >= -n and pos < n
+ return tf.reduce_all(tf.logical_and(cond1, cond2))
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_sequence = tensor_dict[node.inputs[0]]
+ seq_length = tf.shape(input_sequence.to_sparse())[0]
+ position = tensor_dict[node.inputs[1]] if len(
+ node.inputs) == 2 else seq_length - 1
+
+ # check whether position is in-bounds and assert if not
+ result = cls.chk_pos_in_bounds(input_sequence, position)
+ assert_pos = tf.Assert(tf.equal(result, True), [result])
+
+ with tf.control_dependencies([assert_pos]):
+ s1 = input_sequence[:position]
+ s2 = input_sequence[position + 1:]
+ return [tf.concat([s1, s2], axis=0)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_insert.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_insert.py
new file mode 100644
index 0000000..45b5337
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_insert.py
@@ -0,0 +1,52 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("SequenceInsert")
+class SequenceInsert(BackendHandler):
+
+ @classmethod
+ def chk_pos_in_bounds(cls, input_seq, pos):
+ """
+ Check the position is in-bounds with respect to the sequence.
+ Accepted range for 'position' is in [-n, n], where n is the
+ number of tensors in 'input_sequence'.
+
+ :param input_seq: input sequence
+ :param pos: position to insert the tensor
+
+ :return: True if position is in-bounds.
+ """
+ seq_length = tf.shape(input_seq.to_sparse(), out_type=pos.dtype)[0]
+
+ cond1 = tf.greater_equal(pos, tf.negative(seq_length))
+ cond2 = tf.less_equal(pos, seq_length)
+
+ # pos >= -n and pos <= n
+ return tf.reduce_all(tf.logical_and(cond1, cond2))
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_sequence = tensor_dict[node.inputs[0]]
+ input_tensor = tensor_dict[node.inputs[1]]
+
+ position = tensor_dict[node.inputs[2]] if len(
+ node.inputs) > 2 else tf.shape(input_sequence.to_sparse())[0]
+
+ # check whether position is in-bounds and assert if not
+ result = cls.chk_pos_in_bounds(input_sequence, position)
+ assert_pos = tf.Assert(tf.equal(result, True), [result])
+
+ with tf.control_dependencies([assert_pos]):
+ input_tensor = tf.expand_dims(input_tensor, 0)
+ if input_sequence.shape[0] == 0:
+ output_seq = tf.RaggedTensor.from_tensor(input_tensor)
+ else:
+ s1 = input_sequence[:position]
+ s2 = input_sequence[position:]
+ output_seq = tf.concat([s1, input_tensor, s2], axis=0)
+
+ return [output_seq]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_length.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_length.py
new file mode 100644
index 0000000..0cfc2b3
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sequence_length.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("SequenceLength")
+class SequenceLength(BackendHandler):
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_sequence = tensor_dict[node.inputs[0]]
+
+ return [tf.shape(input_sequence.to_sparse(), out_type=tf.int64)[0]]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shape.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shape.py
new file mode 100644
index 0000000..a730f4e
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shape.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Shape")
+@tf_func(tf.shape)
+class Shape(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"out_type": tf.int64}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shrink.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shrink.py
new file mode 100644
index 0000000..d670217
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/shrink.py
@@ -0,0 +1,36 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("Shrink")
+class Shrink(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_tensor = tensor_dict[node.inputs[0]]
+ input_shape = tf.shape(input_tensor, out_type=tf.int64)
+
+ # handle defaults for attributes
+ lambd = node.attrs["lambd"] if "lambd" in node.attrs else 0.5
+ bias = node.attrs["bias"] if "bias" in node.attrs else 0.0
+
+ # make tensors in the right shape
+ lambd_tensor = tf.fill(input_shape, tf.constant(lambd, input_tensor.dtype))
+ lambd_neg_tensor = tf.fill(input_shape,
+ tf.constant(lambd * -1, input_tensor.dtype))
+ bias_tensor = tf.fill(input_shape, tf.constant(bias, input_tensor.dtype))
+ zeros_tensor = tf.zeros(input_shape, input_tensor.dtype)
+
+ # prepare return values and conditions
+ input_plus = tf.add(input_tensor, bias_tensor)
+ input_minus = tf.subtract(input_tensor, bias_tensor)
+ greater_cond = tf.greater(input_tensor, lambd_tensor)
+ less_cond = tf.less(input_tensor, lambd_neg_tensor)
+
+ return [
+ tf.where(less_cond, input_plus,
+ tf.where(greater_cond, input_minus, zeros_tensor))
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sigmoid.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sigmoid.py
new file mode 100644
index 0000000..36cc641
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sigmoid.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Sigmoid")
+@tf_func(tf.nn.sigmoid)
+class Sigmoid(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sign.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sign.py
new file mode 100644
index 0000000..429b965
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sign.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Sign")
+@tf_func(tf.sign)
+class Sign(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sin.py
new file mode 100644
index 0000000..2847524
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sin.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Sin")
+@tf_func(tf.sin)
+class Sin(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sinh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sinh.py
new file mode 100644
index 0000000..c22f75a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sinh.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Sinh")
+@tf_func(tf.sinh)
+class Sinh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/size.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/size.py
new file mode 100644
index 0000000..185245f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/size.py
@@ -0,0 +1,18 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Size")
+@tf_func(tf.size)
+class Size(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"out_type": tf.int64}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/slice.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/slice.py
new file mode 100644
index 0000000..cc1bb13
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/slice.py
@@ -0,0 +1,129 @@
+import numpy as np
+import tensorflow as tf
+try:
+ from tensorflow.math import floormod as tf_floormod
+except ImportError: # for older tf-1.x versions
+ from tensorflow import floormod as tf_floormod
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Slice")
+@tf_func(tf.strided_slice)
+class Slice(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x = tensor_dict[node.inputs[0]]
+
+ full_sizes = x.get_shape().as_list()
+ full_begin = [0] * len(full_sizes)
+
+ starts = node.attrs.get("starts")
+ ends = node.attrs.get("ends")
+ slice_len = len(starts)
+ axes = node.attrs.get("axes", list(range(slice_len)))
+
+ for i in range(slice_len):
+ starts[i] = full_sizes[
+ axes[i]] + starts[i] if starts[i] < 0 else starts[i]
+ ends[i] = full_sizes[axes[i]] + ends[i] if ends[i] < 0 else ends[i]
+ if full_sizes[axes[i]] is not None:
+ ends[i] = np.min([full_sizes[axes[i]], ends[i]])
+ starts[i] = np.min([full_sizes[axes[i]], starts[i]])
+ full_begin[axes[i]] = starts[i]
+ full_sizes[axes[i]] = ends[i] - starts[i]
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node,
+ tf_func=tf.slice,
+ inputs=[
+ tensor_dict[node.inputs[0]],
+ tf.constant(full_begin),
+ tf.constant(full_sizes)
+ ],
+ **kwargs)
+ ]
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ input_tensor = tensor_dict[node.inputs[0]]
+ starts = tensor_dict[node.inputs[1]]
+ ends = tensor_dict[node.inputs[2]]
+
+ # first of all, get the input tensor shape
+ input_tensor_shape = tf.shape(input_tensor, out_type=ends.dtype)
+
+ axes = tensor_dict[node.inputs[3]] if len(
+ node.inputs) >= 4 else tf.range(tf.shape(starts, out_type=ends.dtype)[0])
+
+ # process negative axes
+ input_rank = tf.cast(tf.rank(input_tensor), axes.dtype)
+ axes = tf_floormod(tf.add(axes, input_rank), input_rank)
+
+ # expand a dimension of 1 at the end
+ sparse_indices = tf.expand_dims(axes, -1)
+
+ # build the indexed dimension sizes as sparse_shape
+ sparse_shape = tf.gather_nd(
+ params=input_tensor_shape, indices=sparse_indices)
+ sparse_shape = tf.cast(sparse_shape, ends.dtype)
+
+ # take care of starts, ends that are larger than the dim size.
+ starts_min = tf.minimum(starts, sparse_shape)
+ ends_min = tf.minimum(ends, sparse_shape)
+
+ # take care of starts, ends that are negative
+ is_starts_negative = tf.less(starts_min, tf.zeros_like(starts_min))
+ starts_final = tf.where(is_starts_negative, starts_min + sparse_shape,
+ starts_min)
+ is_ends_negative = tf.less(ends_min, tf.zeros_like(ends_min))
+ ends_final = tf.where(is_ends_negative, ends_min + sparse_shape, ends_min)
+
+ # need to densify everything for the inputs to slice
+ # the output shape is the input_tensor rank
+ output_shape = tf.reshape(tf.rank(input_tensor), [1])
+ output_shape = tf.cast(output_shape, ends.dtype)
+
+ # create dense tensor, pad 0 as default begins
+ dense_begins = tf.sparse_to_dense(sparse_indices, output_shape,
+ starts_final)
+ # create dense tensor, pad -1 for next step
+ dense_ends = tf.sparse_to_dense(
+ sparse_indices,
+ output_shape,
+ ends_final,
+ default_value=tf.constant(-1, dtype=dense_begins.dtype))
+ # replace -1 with respective dimension sizes
+ dense_ends = tf.where(
+ tf.equal(dense_ends, tf.constant(-1, dtype=dense_begins.dtype)),
+ input_tensor_shape, dense_ends)
+
+ # create dense tensor for steps if not already so
+ if len(node.inputs) >= 5:
+ dense_steps = tf.sparse_to_dense(
+ sparse_indices,
+ output_shape,
+ tensor_dict[node.inputs[4]],
+ default_value=tf.constant(1, dtype=tensor_dict[node.inputs[4]].dtype))
+ else:
+ dense_steps = tf.ones(input_tensor_shape.shape, ends.dtype)
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node,
+ inputs=[
+ tensor_dict[node.inputs[0]], dense_begins, dense_ends,
+ dense_steps
+ ],
+ **kwargs)
+ ]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls.version_10(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softmax.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softmax.py
new file mode 100644
index 0000000..e729187
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softmax.py
@@ -0,0 +1,35 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Softmax")
+@tf_func(tf.nn.softmax)
+class Softmax(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ axis = node.attrs.get("axis", 1)
+ axis = axis if axis >= 0 else len(np.shape(x)) + axis
+
+ if axis == len(np.shape(x)) - 1:
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ shape = tf.shape(x)
+ cal_shape = (tf.reduce_prod(shape[0:axis]),
+ tf.reduce_prod(shape[axis:tf.size(shape)]))
+ x = tf.reshape(x, cal_shape)
+
+ return [tf.reshape(tf.nn.softmax(x), shape)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softplus.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softplus.py
new file mode 100644
index 0000000..ba2e532
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softplus.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Softplus")
+@tf_func(tf.nn.softplus)
+class Softplus(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softsign.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softsign.py
new file mode 100644
index 0000000..4a148cf
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/softsign.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Softsign")
+@tf_func(tf.nn.softsign)
+class Softsign(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/space_to_depth.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/space_to_depth.py
new file mode 100644
index 0000000..fe079d0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/space_to_depth.py
@@ -0,0 +1,29 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.common import get_data_format
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("SpaceToDepth")
+@tf_func(tf.space_to_depth)
+class SpaceToDepth(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"rename": {"blocksize": "block_size"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ storage_format, compute_format = get_data_format(x_rank)
+ attrs = copy.deepcopy(node.attrs)
+ attrs["data_format"] = storage_format
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, attrs=attrs, c_first_cuda_only=True, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/split.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/split.py
new file mode 100644
index 0000000..680d38f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/split.py
@@ -0,0 +1,56 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Split")
+@tf_func(tf.split)
+class Split(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ axis = node.attrs.get("axis", 0)
+ x_rank = len(kwargs["tensor_dict"][node.inputs[0]].get_shape().as_list())
+ if axis > x_rank - 1 or axis < -x_rank:
+ raise ValueError("Axis is out of bound")
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"default": {"axis": 0}}
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ x_shape = tensor_dict[node.inputs[0]].get_shape().as_list()
+ attrs = copy.deepcopy(node.attrs)
+ axis = attrs.get("axis", 0)
+ axis = axis if axis >= 0 else len(x_shape) + axis
+ if "split" in node.attrs:
+ split = attrs["split"]
+ elif len(node.inputs) == 2: # since version 1
+ split = tensor_dict[node.inputs[1]]
+ else:
+ per_part = x_shape[axis] / len(node.outputs)
+ if int(per_part) != per_part:
+ raise ValueError("Split can not be evenly divided.")
+ split = [int(per_part)] * len(node.outputs)
+ attrs["num_or_size_splits"] = split
+ return list(
+ cls.make_tensor_from_onnx_node(
+ node, inputs=[tensor_dict[node.inputs[0]]], attrs=attrs, **kwargs))
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_2(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sqrt.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sqrt.py
new file mode 100644
index 0000000..010c370
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sqrt.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Sqrt")
+@tf_func(tf.sqrt)
+class Sqrt(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/squeeze.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/squeeze.py
new file mode 100644
index 0000000..ac53de7
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/squeeze.py
@@ -0,0 +1,22 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Squeeze")
+@tf_func(tf.squeeze)
+class Squeeze(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"rename": {"axes": "axis"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sub.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sub.py
new file mode 100644
index 0000000..663e8ef
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sub.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Sub")
+@tf_func(tf.subtract)
+class Sub(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sum.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sum.py
new file mode 100644
index 0000000..c7f749d
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/sum.py
@@ -0,0 +1,33 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import ArithmeticMixin
+
+
+@onnx_op("Sum")
+@tf_func(tf.add_n)
+class Sum(ArithmeticMixin, BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ tensor_dict = kwargs["tensor_dict"]
+ return [
+ cls.make_tensor_from_onnx_node(
+ node,
+ inputs=[[tensor_dict.get(inp, None) for inp in node.inputs]],
+ **kwargs)
+ ]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_8(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tan.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tan.py
new file mode 100644
index 0000000..b2eb463
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tan.py
@@ -0,0 +1,15 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Tan")
+@tf_func(tf.tan)
+class Tan(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tanh.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tanh.py
new file mode 100644
index 0000000..a123e5a
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tanh.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .math_mixin import BasicMathMixin
+
+
+@onnx_op("Tanh")
+@tf_func(tf.tanh)
+class Tanh(BasicMathMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tfidf_vectorizer.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tfidf_vectorizer.py
new file mode 100644
index 0000000..051f336
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tfidf_vectorizer.py
@@ -0,0 +1,136 @@
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("TfIdfVectorizer")
+class TfIdfVectorizer(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ if "pool_int64s" in node.attrs and "pool_strings" in node.attrs:
+ raise ValueError(
+ "Cannot set the pool_int64s and pool_strings in an input at the same time."
+ )
+
+ @classmethod
+ def _prepare_ngrams(cls, x, n, skip):
+ # This method transform input into n-grams for a specific skip
+ # input x is a 1D tensor
+ # ex1: x=[1,2,3,4] n=1 skip=0 output=[[1],[2],[3],[4]]
+ # ex2: x=[1,2,3,4] n=2 skip=0 output=[[1,2],[2,3],[3,4]]
+ # ex3: x=[1,2,3,4] n=2 skip=1 output=[[1,3],[2,4]]
+ count = x.shape[0] - n + 1 - skip
+ multiplier = skip + 1
+ ngrams = [x[i * multiplier:i * multiplier + count] for i in range(n)]
+ ngrams = tf.stack(ngrams)
+ ngrams = tf.transpose(ngrams, [1, 0])
+ return ngrams
+
+ @classmethod
+ def _calc_ngram_skip(cls, x, pool, n, skip=0):
+ # This method calculates ngram counts for specific n and skip
+
+ # Make pool into an array of ngrams
+ pool = np.reshape(pool, (int(len(pool) / n), n))
+
+ # Make input as an array of ngrams
+ new_x = cls._prepare_ngrams(x, n, skip)
+
+ # Loop through the ngram targets in the pool
+ tensor_list = []
+ for i in range(len(pool)):
+ # There is a pending issue in running tf.map_fn with strings
+ # on GPU, https://github.com/tensorflow/tensorflow/issues/28007
+ # So this is a temporary solution to ensure tf.map_fn
+ # runs on CPU. Later can be removed once Tensorflow has the
+ # issue resolved.
+ with tf.device("/cpu:0"):
+ ngram_count = tf.map_fn(lambda in_x: tf.where(
+ tf.reduce_all(
+ tf.equal(in_x, tf.constant(pool[i], dtype=new_x.dtype))),
+ tf.constant([1]), tf.constant([0])),
+ new_x,
+ dtype=tf.int32)
+ ngram_count = tf.math.count_nonzero(ngram_count, dtype=tf.int32)
+ ngram_count = tf.reshape(ngram_count, [1])
+ tensor_list.append(ngram_count)
+
+ return tf.concat(tensor_list, 0)
+
+ @classmethod
+ def _calc_ngram(cls, x, pool, n, max_skip):
+ # This method calculates ngram counts for a specific n and
+ # all allowable skips
+
+ # For 1gram, skip is not in use. Not clearly described in ONNX
+ # spec, this code logic is based on observation of ONNX examples,
+ # tf_batch_uniandbigrams_skip5 and tf_uniandbigrams_skip5,
+ # where the 1-gram results [0, 3, 0, 0] and [0, 3, 1, 0]
+ # are not the accumulated counts from multiple skips.
+ if n == 1:
+ return cls._calc_ngram_skip(x, pool, n)
+
+ # Loop through maximum allowable skip count and sum up the results
+ result = tf.zeros([int(len(pool) / n)], dtype=tf.int32)
+ max_allowable_skip = np.minimum(max_skip,
+ int((int(x.shape[0]) - 1) / (n - 1) - 1))
+
+ for skip in range(max_allowable_skip + 1):
+ # For each skip calculate the ngram counts
+ result += cls._calc_ngram_skip(x, pool, n, skip)
+
+ return result
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ input_tensor = kwargs["tensor_dict"][node.inputs[0]]
+ mode = node.attrs.get("mode")
+ max_skip_count = node.attrs.get("max_skip_count")
+ min_gram_len = node.attrs.get("min_gram_length")
+ max_gram_len = node.attrs.get("max_gram_length")
+ ngram_counts = node.attrs.get("ngram_counts")
+ ngram_indexes = node.attrs.get("ngram_indexes")
+ pool_int64s = node.attrs.get("pool_int64s")
+ pool_strings = node.attrs.get("pool_strings")
+ weights = node.attrs.get("weights", np.ones(len(ngram_indexes)))
+
+ def process_ngram(input_t):
+ # This is the main method that processes and produces ngram counts
+ # for one row of inputs regardless of the operator input dimension.
+ size = len(ngram_indexes)
+ new_ngram_counts = np.append(ngram_counts, size)
+ result_ngram = np.zeros(size)
+ for i in range(len(new_ngram_counts) - 1):
+ gram_len = i + 1
+ count = new_ngram_counts[i + 1] - new_ngram_counts[i]
+ total_len = count * gram_len
+ if gram_len >= min_gram_len and gram_len <= max_gram_len:
+ idx = ngram_indexes[new_ngram_counts[i]:new_ngram_counts[i + 1]]
+ process_pool = pool_int64s[
+ new_ngram_counts[i]:new_ngram_counts[i] +
+ total_len] if pool_int64s is not None else pool_strings[
+ new_ngram_counts[i]:new_ngram_counts[i] + total_len]
+ result = cls._calc_ngram(input_t, process_pool, gram_len,
+ max_skip_count)
+ idx = tf.constant(idx, shape=[len(idx), 1])
+ result_ngram = result_ngram + tf.scatter_nd(idx, result, [size])
+ return result_ngram
+
+ # The input can be either 1d or 2d. Need to loop through
+ # each element for 2d inputs
+ n = len(input_tensor.shape)
+ final_out = [
+ process_ngram(input_tensor[i]) for i in range(input_tensor.shape[0])
+ ] if n > 1 else process_ngram(input_tensor)
+ tf_out = tf.cast(final_out, tf.float32)
+
+ # Apply the mode based of the TF output
+ if mode == 'IDF':
+ return [tf.minimum(tf_out, 1) * weights]
+ elif mode == 'TFIDF':
+ return [tf_out * weights]
+ else:
+ return [tf_out]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/thresholded_relu.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/thresholded_relu.py
new file mode 100644
index 0000000..90a75ec
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/thresholded_relu.py
@@ -0,0 +1,23 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+
+
+@onnx_op("ThresholdedRelu")
+class ThresholdedRelu(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ alpha = node.attrs.get("alpha", 1.0)
+ epsilon = 1e-5
+ return [tf.nn.relu(x) - tf.nn.relu(tf.sign(alpha - x + epsilon) * x)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tile.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tile.py
new file mode 100644
index 0000000..a170150
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/tile.py
@@ -0,0 +1,29 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Tile")
+@tf_func(tf.tile)
+class Tile(BackendHandler):
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ return {"rename": {"axes": "axis"}}
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ multiples = [1] * x_rank
+ axis = node.attrs["axis"]
+ tiles = node.attrs["tiles"]
+ multiples[axis] = tiles
+ inputs = [x, multiples]
+ return [cls.make_tensor_from_onnx_node(node, inputs=inputs, **kwargs)]
+
+ @classmethod
+ def version_6(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/top_k.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/top_k.py
new file mode 100644
index 0000000..665a767
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/top_k.py
@@ -0,0 +1,80 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("TopK")
+@tf_func(tf.nn.top_k)
+class TopK(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ axes = list(range(x_rank))
+ axis = node.attrs.get("axis", -1)
+ axis = axis if axis >= 0 else axis + x_rank
+
+ if axis != x_rank - 1:
+ pre_perm = [a for a in axes if a != axis] + [axis]
+ post_perm = axes[:axis] + [x_rank - 1] + axes[axis:x_rank - 1]
+ x = tf.transpose(x, perm=pre_perm)
+ values, indices = tf.nn.top_k(x, k=node.attrs["k"])
+ values = tf.transpose(values, perm=post_perm)
+ return [values, tf.cast(indices, dtype=tf.int64)]
+
+ values, indices = tf.nn.top_k(x, k=node.attrs["k"])
+ return [values, tf.cast(indices, dtype=tf.int64)]
+
+ @classmethod
+ def version_10(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ axes = list(range(x_rank))
+ axis = node.attrs.get("axis", -1)
+ axis = axis if axis >= 0 else axis + x_rank
+ k = kwargs["tensor_dict"][node.inputs[1]][0]
+ k = tf.cast(k, dtype=tf.int32)
+
+ if axis != x_rank - 1:
+ pre_perm = [a for a in axes if a != axis] + [axis]
+ post_perm = axes[:axis] + [x_rank - 1] + axes[axis:x_rank - 1]
+ x = tf.transpose(x, perm=pre_perm)
+ values, indices = tf.nn.top_k(x, k)
+ values = tf.transpose(values, perm=post_perm)
+ return [values, tf.cast(indices, dtype=tf.int64)]
+
+ values, indices = tf.nn.top_k(x, k)
+ return [values, tf.cast(indices, dtype=tf.int64)]
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_rank = len(x.get_shape())
+ axes = list(range(x_rank))
+ axis = node.attrs.get("axis", -1)
+ axis = axis if axis >= 0 else axis + x_rank
+ largest = node.attrs.get("largest", 1)
+ sort = node.attrs.get("sorted", 1)
+ sort = False if sort == 0 else True
+ k = kwargs["tensor_dict"][node.inputs[1]][0]
+ k = tf.cast(k, dtype=tf.int32)
+
+ if largest == 0:
+ x = tf.negative(x)
+
+ if axis != x_rank - 1:
+ pre_perm = [a for a in axes if a != axis] + [axis]
+ post_perm = axes[:axis] + [x_rank - 1] + axes[axis:x_rank - 1]
+ x = tf.transpose(x, perm=pre_perm)
+ values, indices = tf.nn.top_k(x, k, sort)
+ values = tf.transpose(values, perm=post_perm)
+ else :
+ values, indices = tf.nn.top_k(x, k, sort)
+
+ if largest == 0:
+ values = tf.negative(values)
+
+ return [values, tf.cast(indices, dtype=tf.int64)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/transpose.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/transpose.py
new file mode 100644
index 0000000..9910fe4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/transpose.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Transpose")
+@tf_func(tf.transpose)
+class Transpose(BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unpool_mixin.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unpool_mixin.py
new file mode 100644
index 0000000..c071ed0
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unpool_mixin.py
@@ -0,0 +1,155 @@
+import tensorflow as tf
+
+from onnx_tf.common import get_data_format
+from onnx_tf.common import get_perm_from_formats
+
+
+class UnpoolMixin(object):
+
+ @classmethod
+ def max_unpool(cls, node, input_dict):
+ """
+ MaxUnpooling operation
+ """
+ x = input_dict[node.inputs[0]]
+ ind = input_dict[node.inputs[1]]
+ if len(node.inputs) > 2:
+ output_shape = input_dict.get(node.inputs[2], None)
+ else:
+ output_shape = None
+
+ input_shape = x.get_shape()
+ x_rank = len(x.get_shape())
+ spatial_size = x_rank - 2
+ storage_format, _ = get_data_format(x_rank)
+
+ kernel_shape = node.attrs["kernel_shape"]
+ # if strides are not provided default is 1 along each spatial axis
+ strides = node.attrs.get("strides", [1] * spatial_size)
+ pads = node.attrs.get("pads", None)
+
+ default_shape = cls._get_default_shape(input_shape, kernel_shape,
+ strides)
+
+ need_trans = storage_format != "NHWC"
+ if need_trans:
+ x = tf.transpose(x, perm=get_perm_from_formats(storage_format,
+ "NHWC"))
+ ind = tf.transpose(ind, perm=get_perm_from_formats(storage_format,
+ "NHWC"))
+
+ # default_shape to NHWC storage format
+ default_shape = [int(input_shape[0])] + default_shape + \
+ [int(input_shape[1])]
+
+ unpooled = cls._unpool(x, ind, default_shape)
+
+ if need_trans:
+ unpooled = tf.transpose(
+ unpooled, perm=get_perm_from_formats("NHWC", storage_format))
+
+ if output_shape is not None:
+ pads = cls._get_pads_from_output_shape(unpooled, output_shape)
+ if pads is not None:
+ unpooled = cls._pad_output(unpooled, pads, 0)
+
+ return [unpooled]
+
+ @classmethod
+ def _get_default_shape(cls, input_shape, kernel_shape, strides):
+ """
+ Calculates default shape from kernel_shape and strides
+ Args:
+ input_shape: shape of the input to unpool op
+ kernel_shape: the size of the kernel along each axis
+ output_shape: stride along each spatial axis
+ Return:
+ default_shape: calculated default_shape
+ """
+ default_shape = []
+ for d in range(len(kernel_shape)):
+ default_shape.append((int(input_shape[d + 2]) - 1) *
+ int(strides[d]) + int(kernel_shape[d]))
+ return default_shape
+
+ @classmethod
+ def _get_pads_from_output_shape(cls, unpool, output_shape):
+ """
+ Calculates the paddings from specified output_shape
+ Args:
+ unpool: result from unpool operation
+ output_shape: expected shape of the output
+ Return:
+ pads: calculated paddings in format
+ [x1_begin, x2_begin,.., x1_end, x2_end]
+ where xi_... represent pads added to begin
+ or end of axis i
+ """
+ unpool_shape = tf.cast(tf.shape(unpool), dtype=tf.int32)
+ new_shape = tf.cast(output_shape, dtype=tf.int32)
+
+ pads_begin = []
+ pads_end = []
+
+ for d in range(len(unpool.get_shape())):
+ pad_total = new_shape[d] - unpool_shape[d]
+ pad_begin = tf.cast(pad_total / 2, tf.int32)
+ pad_end = pad_total - pad_begin
+ pads_begin = pads_begin + [pad_begin]
+ pads_end = pads_end + [pad_end]
+
+ pads = pads_begin + pads_end
+ return pads
+
+ @classmethod
+ def _pad_output(cls, unpool, pads, constant_values):
+ """
+ Pad the output from unpool op
+ Args:
+ unpool: result from unpool op
+ pads: paddings in format
+ [x1_begin, x2_begin,..., x1_end, x2_end]
+ constant_values: constant value to fill up the padded spaces
+ Return:
+ padded: padded tensor
+ """
+ unpool_shape = unpool.get_shape()
+ paddings = []
+ for d in range(len(unpool_shape)):
+ paddings = paddings + [[pads[d], pads[d + len(unpool_shape)]]]
+ padded = tf.pad(unpool, paddings, 'CONSTANT',
+ constant_values=constant_values)
+ return padded
+
+ @classmethod
+ def _unpool(cls, pool, ind, output_shape, scope='unpool'):
+ """
+ Unpooling layer after max_pool_with_argmax.
+
+ Args:
+ pool: max pooled output tensor
+ ind: argmax indices
+ output_shape: the shape of the output
+ Return:
+ unpool: unpooling tensor
+ """
+ with tf.variable_scope(scope):
+ input_shape = tf.shape(pool)
+
+ flat_input_size = tf.reduce_prod(input_shape)
+ flat_output_shape = [output_shape[0], output_shape[1] *
+ output_shape[2] * output_shape[3]]
+
+ pool_ = tf.reshape(pool, [flat_input_size])
+ batch_range = tf.reshape(
+ tf.range(tf.cast(output_shape[0], tf.int64),
+ dtype=ind.dtype), shape=[input_shape[0], 1, 1, 1])
+ b = tf.ones_like(ind) * batch_range
+ b1 = tf.reshape(b, [flat_input_size, 1])
+ ind_ = tf.reshape(ind, [flat_input_size, 1])
+ ind_ = tf.concat([b1, ind_], 1)
+
+ ret = tf.scatter_nd(ind_, pool_, shape=tf.cast(flat_output_shape,
+ tf.int64))
+ ret = tf.reshape(ret, output_shape)
+ return ret
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unsqueeze.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unsqueeze.py
new file mode 100644
index 0000000..061ef96
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/unsqueeze.py
@@ -0,0 +1,32 @@
+import copy
+
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Unsqueeze")
+@tf_func(tf.expand_dims)
+class Unsqueeze(BackendHandler):
+
+ @classmethod
+ def _common(cls, node, **kwargs):
+ attrs = copy.deepcopy(node.attrs)
+ axes = attrs.pop("axes")
+ if len(axes) != 1:
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ for axis in sorted(axes):
+ x = tf.expand_dims(x, axis=axis)
+ return [x]
+ attrs["axis"] = axes[0]
+ return [cls.make_tensor_from_onnx_node(node, attrs=attrs, **kwargs)]
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
+
+ @classmethod
+ def version_11(cls, node, **kwargs):
+ return cls._common(node, **kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/upsample.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/upsample.py
new file mode 100644
index 0000000..6df8ec8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/upsample.py
@@ -0,0 +1,87 @@
+import copy
+
+import numpy as np
+import tensorflow as tf
+
+from onnx_tf.common import exception
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import partial_support
+from onnx_tf.handlers.handler import ps_description
+from onnx_tf.handlers.handler import tf_func
+from onnx_tf.common.tf_helper import tf_shape
+
+
+@onnx_op("Upsample")
+@tf_func(tf.image.resize_images)
+@partial_support(True)
+@ps_description("Upsample required 4D input in Tensorflow.")
+class Upsample(BackendHandler):
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_shape = x.get_shape().as_list()
+ if len(x_shape) != 4:
+ exception.OP_UNSUPPORTED_EXCEPT("Upsample without 4D input", "Tensorflow")
+
+ if node.attrs.get("mode", "nearest").lower() not in ["nearest", "bilinear", "linear"]:
+ exception.OP_UNSUPPORTED_EXCEPT("Upsample without nearest or bilinear",
+ "Tensorflow")
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_shape = x.get_shape().as_list()
+ attrs = copy.deepcopy(node.attrs)
+ scales = attrs["scales"]
+ new_height = np.floor(x_shape[2] * scales[2])
+ new_weight = np.floor(x_shape[3] * scales[3])
+
+ mode = attrs.get("mode", "nearest")
+ if mode.lower() == "bilinear" or mode.lower() == "linear":
+ mode = tf.image.ResizeMethod.BILINEAR
+ else:
+ mode = tf.image.ResizeMethod.NEAREST_NEIGHBOR
+
+ attrs["size"] = np.array((new_height, new_weight), dtype=np.int32)
+ attrs["method"] = mode
+
+ return [
+ cls.make_tensor_from_onnx_node(
+ node, attrs=attrs, c_last_only=True, **kwargs)
+ ]
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ x = kwargs["tensor_dict"][node.inputs[0]]
+ x_shape = tf_shape(x)
+ attrs = copy.deepcopy(node.attrs)
+ scales = kwargs["tensor_dict"][node.inputs[1]]
+
+ assert_n_c_scale_is_one = tf.Assert(
+ tf.logical_and(tf.equal(scales[0], 1), tf.equal(scales[1], 1)),
+ [scales])
+
+ with tf.control_dependencies([assert_n_c_scale_is_one]):
+ h_w_scale = scales[2:]
+ h_w_shape = x_shape[2:]
+ new_h_w_shape = tf.cast(h_w_scale * tf.cast(h_w_shape, scales.dtype),
+ tf.int32)
+
+ mode = attrs.get("mode", "nearest")
+ if mode.lower() == "bilinear" or mode.lower() == "linear":
+ mode = tf.image.ResizeMethod.BILINEAR
+ else:
+ mode = tf.image.ResizeMethod.NEAREST_NEIGHBOR
+
+ attrs["size"] = new_h_w_shape
+ attrs["method"] = mode
+
+ # Remove scale.
+ upsample_node = copy.deepcopy(node)
+ del upsample_node.inputs[1]
+ return [
+ cls.make_tensor_from_onnx_node(
+ upsample_node, attrs=attrs, c_last_only=True, **kwargs)
+ ]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/where.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/where.py
new file mode 100644
index 0000000..0f8b04f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/where.py
@@ -0,0 +1,14 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+
+
+@onnx_op("Where")
+@tf_func(tf.where)
+class Where(BackendHandler):
+
+ @classmethod
+ def version_9(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/xor.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/xor.py
new file mode 100644
index 0000000..9339a8f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend/xor.py
@@ -0,0 +1,19 @@
+import tensorflow as tf
+
+from onnx_tf.handlers.backend_handler import BackendHandler
+from onnx_tf.handlers.handler import onnx_op
+from onnx_tf.handlers.handler import tf_func
+from .control_flow_mixin import LogicalMixin
+
+
+@onnx_op("Xor")
+@tf_func(tf.logical_xor)
+class Xor(LogicalMixin, BackendHandler):
+
+ @classmethod
+ def version_1(cls, node, **kwargs):
+ return cls.limited_broadcast(node, **kwargs)
+
+ @classmethod
+ def version_7(cls, node, **kwargs):
+ return [cls.make_tensor_from_onnx_node(node, **kwargs)]
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend_handler.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend_handler.py
new file mode 100644
index 0000000..279c873
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/backend_handler.py
@@ -0,0 +1,188 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import copy
+import inspect
+
+import tensorflow as tf
+
+from onnx_tf.common import IS_PYTHON3
+from onnx_tf.common import get_data_format
+from onnx_tf.common import get_perm_from_formats
+from onnx_tf.common import supports_device
+from .handler import Handler
+
+
+class BackendHandler(Handler):
+ """ This class is base backend handler class.
+ All backend operator handler class MUST inherit this class.
+ In backend, operator handler class's name should be pascal case of file name
+ which should be snake case.
+ Use ONNX operator name as class name.
+ """
+
+ TF_FUNC = None
+
+ @classmethod
+ def get_attrs_processor_param(cls):
+ """ Get param for attrs processor.
+
+ :return: Dict.
+ """
+ return {}
+
+ @classmethod
+ def _process_attrs(cls, attrs):
+ """ Private method for processing attrs.
+ Param for this processor got from `get_attrs_processor_param`.
+ Param is dict contains two key: `default` and `raname`.
+ First add default value to attrs if key does not exist.
+ Second rename key to new key.
+
+ For example:
+ attrs = {"keep_dims": True}
+ param = {"default": {"axis": 1},
+ "rename": {"keep_dims": "keepdims"}}
+
+ processed_attrs = {"axis": "1", "keepdims": True}
+
+ :param attrs: Process target attrs.
+ :return: Processed attrs.
+ """
+ param = {"rename": {}, "default": {}}
+ param.update(cls.get_attrs_processor_param())
+
+ for k, v in param["default"].items():
+ attrs.setdefault(k, v)
+
+ for k, new_k in param["rename"].items():
+ if k in attrs:
+ attrs[new_k] = attrs.pop(k)
+
+ return attrs
+
+ @classmethod
+ def make_tensor_from_onnx_node(cls,
+ node,
+ tf_func=None,
+ inputs=None,
+ attrs=None,
+ name="",
+ c_first_cuda_only=False,
+ c_last_only=False,
+ **kwargs):
+ """ Helper method to make tensor.
+
+ :param node: OnnxNode object.
+ :param tf_func: Callable Tf function. Default is cls.TF_FUNC.
+ :param inputs: Inputs tensor. Default is got from node.inputs.
+ :param attrs: Attributes. Default is node.attrs.
+ :param name: Node name.
+ :param c_first_cuda_only: If channel first is only supported by cuda.
+ If true and not cuda, do pre and post transpose.
+ :param c_last_only: If only channel last is support,
+ do pre and post transpose.
+ :param kwargs: Other args.
+ :return: Tensor.
+ """
+ tensor_dict = kwargs.get("tensor_dict", {})
+ tf_func = tf_func or cls.TF_FUNC
+ if tf_func is None:
+ raise RuntimeError("No Tensorflow function is given.")
+ if inputs is None:
+ inputs = [tensor_dict.get(inp, None) for inp in node.inputs]
+ if attrs is None:
+ attrs = copy.deepcopy(node.attrs)
+ name = name or node.name
+ if name != "":
+ attrs["name"] = name
+
+ if c_first_cuda_only and c_last_only:
+ raise ValueError(
+ "c_first_cuda_only and c_last_only can not both be True.")
+
+ if c_first_cuda_only:
+ return cls.c_first_cuda_only(tf_func, inputs, attrs)
+ elif c_last_only:
+ return cls.c_last_only(tf_func, inputs, attrs)
+
+ return cls._run_tf_func(tf_func, inputs, attrs)
+
+ @classmethod
+ def c_first_cuda_only(cls, tf_func, inputs, attrs):
+ """ Handle operator that channel first is only supported by CUDA.
+ When using CPU, two transposes should be added.
+
+ :param tf_func: Callable Tf function.
+ :param inputs: Inputs tensor.
+ :param attrs: Attributes.
+ :return: Tensor.
+ """
+ support_cuda = supports_device("CUDA")
+ if not support_cuda:
+ return cls._tuck_transpose(tf_func, inputs, attrs)
+ return cls._run_tf_func(tf_func, inputs, attrs)
+
+ @classmethod
+ def c_last_only(cls, tf_func, inputs, attrs):
+ """ Handle operator that channel last only is supported.
+ Add two transposes anyway.
+
+ :param tf_func: Callable Tf function.
+ :param inputs: Inputs tensor.
+ :param attrs: Attributes.
+ :return: Tensor.
+ """
+ storage_format, compute_format = get_data_format(len(inputs[0].get_shape()))
+ compute_format = compute_format.replace("C", "") + "C"
+ return cls._tuck_transpose(tf_func, inputs, attrs,
+ (storage_format, compute_format))
+
+ @classmethod
+ def _tuck_transpose(cls, tf_func, inputs, attrs, data_format=None):
+ x = inputs[0]
+ x_rank = len(x.get_shape())
+ if not data_format:
+ data_format = get_data_format(x_rank)
+ pre_perm = get_perm_from_formats(data_format[0], data_format[1])
+ post_perm = get_perm_from_formats(data_format[1], data_format[0])
+ attrs["data_format"] = data_format[1]
+ if pre_perm != list(range(x_rank)):
+ x_t = tf.transpose(x, perm=pre_perm)
+ y = cls._run_tf_func(tf_func, [x_t] + inputs[1:], attrs)
+ y_t = tf.transpose(y, perm=post_perm)
+ return y_t
+ return cls._run_tf_func(tf_func, inputs, attrs)
+
+ @classmethod
+ def _run_tf_func(cls, tf_func, inputs, attrs):
+ """ Run Tensorflow function.
+ Use only acceptable attributes of function from attrs.
+
+ :param tf_func: Tensorflow function.
+ :param inputs: Inputs.
+ :param attrs: Attributes.
+ :return: Tensor.
+ """
+ if IS_PYTHON3:
+ params = list(inspect.signature(tf_func).parameters.keys())
+ else:
+ # use closure to get args for function using decorator
+ if tf_func.__closure__ is not None:
+ while "__wrapped__" in tf_func.func_dict:
+ tf_func = tf_func.func_dict["__wrapped__"]
+ params = inspect.getargspec(tf_func).args
+ else:
+ params = inspect.getargspec(tf_func).args
+
+ attrs = cls._process_attrs(attrs)
+ attrs = {p: v for p, v in attrs.items() if p in params}
+ kwargs = dict(zip(params, inputs))
+ ambiguous_arguments = any(kwargs.get(p) is not None and v is not None
+ for p, v in attrs.items())
+ if ambiguous_arguments:
+ raise TypeError('Ambiguous arguments for {}()'.format(tf_func.__name__))
+ kwargs.update((p, v) for p, v in attrs.items() if v is not None)
+ return tf_func(**kwargs)
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/handlers/handler.py b/pt2tf/onnx-tensorflow/onnx_tf/handlers/handler.py
new file mode 100644
index 0000000..03e1868
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/handlers/handler.py
@@ -0,0 +1,114 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import inspect
+
+from onnx import defs
+from onnx.backend.test.runner import BackendIsNotSupposedToImplementIt
+
+import onnx_tf.common as common
+
+
+class Handler(object):
+ """ This class is base handler class.
+ Base backend and frontend base handler class inherit this class.
+
+ All operator handler MUST put decorator @onnx_op to register corresponding op.
+ """
+
+ ONNX_OP = None
+
+ DOMAIN = defs.ONNX_DOMAIN
+ VERSION = 0
+ SINCE_VERSION = 0
+ PARTIAL_SUPPORT = False
+ PS_DESCRIPTION = ''
+
+ @classmethod
+ def check_cls(cls):
+ if not cls.ONNX_OP:
+ common.logger.warning(
+ "{} doesn't have ONNX_OP. "
+ "Please use Handler.onnx_op decorator to register ONNX_OP.".format(
+ cls.__name__))
+
+ @classmethod
+ def args_check(cls, node, **kwargs):
+ """ Check args. e.g. if shape info is in graph.
+ Raise exception if failed.
+
+ :param node: NodeProto for backend.
+ :param kwargs: Other args.
+ """
+ pass
+
+ @classmethod
+ def handle(cls, node, **kwargs):
+ """ Main method in handler. It will find corresponding versioned handle method,
+ whose name format is `version_%d`. So prefix `version_` is reserved in onnx-tensorflow.
+ DON'T use it for other purpose.
+
+ :param node: NodeProto for backend.
+ :param kwargs: Other args.
+ :return: TensorflowNode for backend.
+ """
+ ver_handle = getattr(cls, "version_{}".format(cls.SINCE_VERSION), None)
+ if ver_handle:
+ cls.args_check(node, **kwargs)
+ return ver_handle(node, **kwargs)
+
+ raise BackendIsNotSupposedToImplementIt("{} version {} is not implemented.".format(node.op_type, cls.SINCE_VERSION))
+
+ @classmethod
+ def get_versions(cls):
+ """ Get all support versions.
+
+ :return: Version list.
+ """
+ versions = []
+ for k, v in inspect.getmembers(cls, inspect.ismethod):
+ if k.startswith("version_"):
+ versions.append(int(k.replace("version_", "")))
+ return versions
+
+ @staticmethod
+ def onnx_op(op):
+ return Handler.property_register("ONNX_OP", op)
+
+ @staticmethod
+ def tf_func(func):
+ return Handler.property_register("TF_FUNC", func)
+
+ @staticmethod
+ def domain(d):
+ return Handler.property_register("DOMAIN", d)
+
+ @staticmethod
+ def partial_support(ps):
+ return Handler.property_register("PARTIAL_SUPPORT", ps)
+
+ @staticmethod
+ def ps_description(psd):
+ return Handler.property_register("PS_DESCRIPTION", psd)
+
+ @staticmethod
+ def property_register(name, value):
+
+ def deco(cls):
+ if inspect.isfunction(value) and not common.IS_PYTHON3:
+ setattr(cls, name, staticmethod(value))
+ else:
+ setattr(cls, name, value)
+ return cls
+
+ return deco
+
+
+domain = Handler.domain
+onnx_op = Handler.onnx_op
+tf_func = Handler.tf_func
+partial_support = Handler.partial_support
+ps_description = Handler.ps_description
+property_register = Handler.property_register
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/opset_version.py b/pt2tf/onnx-tensorflow/onnx_tf/opset_version.py
new file mode 100644
index 0000000..46aeee3
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/opset_version.py
@@ -0,0 +1,256 @@
+backend_opset_version = {
+ 'Abs': [1, 6],
+ 'Acos': [7],
+ 'Acosh': [9],
+ 'Adagrad': [],
+ 'Adam': [],
+ 'Add': [1, 6, 7],
+ 'And': [1, 7],
+ 'ArgMax': [1, 11, 12],
+ 'ArgMin': [1, 11, 12],
+ 'ArrayFeatureExtractor': [],
+ 'Asin': [7],
+ 'Asinh': [9],
+ 'Atan': [7],
+ 'Atanh': [9],
+ 'AveragePool': [1, 7, 10, 11],
+ 'BatchNormalization': [1, 6, 7, 9],
+ 'Binarizer': [],
+ 'BitShift': [11],
+ 'Cast': [1, 6, 9],
+ 'CastMap': [],
+ 'CategoryMapper': [],
+ 'Ceil': [1, 6],
+ 'Celu': [],
+ 'Clip': [1, 6, 11, 12],
+ 'Compress': [9, 11],
+ 'Concat': [1, 4, 11],
+ 'ConcatFromSequence': [],
+ 'Constant': [1, 9, 11, 12],
+ 'ConstantFill': [1],
+ 'ConstantOfShape': [9],
+ 'Conv': [1, 11],
+ 'ConvInteger': [10],
+ 'ConvTranspose': [1, 11],
+ 'Cos': [7],
+ 'Cosh': [9],
+ 'CumSum': [11],
+ 'DepthToSpace': [1, 11],
+ 'DequantizeLinear': [10],
+ 'Det': [11],
+ 'DictVectorizer': [],
+ 'Div': [1, 6, 7],
+ 'Dropout': [1, 6, 7, 10],
+ 'DynamicQuantizeLinear': [11],
+ 'Einsum': [],
+ 'Elu': [1, 6],
+ 'Equal': [1, 7, 11],
+ 'Erf': [9],
+ 'Exp': [1, 6],
+ 'Expand': [8],
+ 'EyeLike': [9],
+ 'FeatureVectorizer': [],
+ 'Flatten': [1, 9, 11],
+ 'Floor': [1, 6],
+ 'GRU': [1, 3, 7],
+ 'Gather': [1, 11],
+ 'GatherElements': [11],
+ 'GatherND': [11],
+ 'Gemm': [1, 6, 7, 9, 11],
+ 'GlobalAveragePool': [1],
+ 'GlobalLpPool': [1, 2],
+ 'GlobalMaxPool': [1],
+ 'Gradient': [],
+ 'GraphCall': [],
+ 'Greater': [1, 7, 9],
+ 'GreaterOrEqual': [],
+ 'HardSigmoid': [1, 6],
+ 'Hardmax': [1, 11],
+ 'Identity': [1],
+ 'If': [1, 11],
+ 'ImageScaler': [1],
+ 'Imputer': [],
+ 'InstanceNormalization': [1, 6],
+ 'IsInf': [10],
+ 'IsNaN': [9],
+ 'LRN': [1],
+ 'LSTM': [1, 7],
+ 'LabelEncoder': [],
+ 'LeakyRelu': [1, 6],
+ 'Less': [1, 7, 9],
+ 'LessOrEqual': [],
+ 'LinearClassifier': [],
+ 'LinearRegressor': [],
+ 'Log': [1, 6],
+ 'LogSoftmax': [1, 11],
+ 'Loop': [1, 11],
+ 'LpNormalization': [1],
+ 'LpPool': [1, 2, 11],
+ 'MatMul': [1, 9],
+ 'MatMulInteger': [10],
+ 'Max': [1, 6, 8],
+ 'MaxPool': [1, 8, 10, 11, 12],
+ 'MaxRoiPool': [],
+ 'MaxUnpool': [9, 11],
+ 'Mean': [1, 6, 8],
+ 'MeanVarianceNormalization': [1, 9],
+ 'Min': [1, 6, 8],
+ 'Mod': [10],
+ 'Momentum': [],
+ 'Mul': [1, 6, 7],
+ 'Multinomial': [],
+ 'Neg': [1, 6],
+ 'NegativeLogLikelihoodLoss': [],
+ 'NonMaxSuppression': [10, 11],
+ 'NonZero': [9],
+ 'Normalizer': [],
+ 'Not': [1],
+ 'OneHot': [9, 11],
+ 'OneHotEncoder': [],
+ 'Or': [1, 7],
+ 'PRelu': [1, 6, 7, 9],
+ 'Pad': [1, 2, 11],
+ 'Pow': [1, 7],
+ 'QLinearConv': [10],
+ 'QLinearMatMul': [10],
+ 'QuantizeLinear': [10],
+ 'RNN': [1, 7],
+ 'RandomNormal': [1],
+ 'RandomNormalLike': [1],
+ 'RandomUniform': [1],
+ 'RandomUniformLike': [1],
+ 'Range': [11],
+ 'Reciprocal': [1, 6],
+ 'ReduceL1': [1, 11],
+ 'ReduceL2': [1, 11],
+ 'ReduceLogSum': [1, 11],
+ 'ReduceLogSumExp': [1, 11],
+ 'ReduceMax': [1, 11, 12],
+ 'ReduceMean': [1, 11],
+ 'ReduceMin': [1, 11, 12],
+ 'ReduceProd': [1, 11],
+ 'ReduceSum': [1, 11],
+ 'ReduceSumSquare': [1, 11],
+ 'Relu': [1, 6],
+ 'Reshape': [1, 5],
+ 'Resize': [10, 11],
+ 'ReverseSequence': [10],
+ 'RoiAlign': [],
+ 'Round': [11],
+ 'SVMClassifier': [],
+ 'SVMRegressor': [],
+ 'Scaler': [],
+ 'Scan': [8, 9, 11],
+ 'Scatter': [9],
+ 'ScatterElements': [11],
+ 'ScatterND': [11],
+ 'Selu': [1, 6],
+ 'SequenceAt': [11],
+ 'SequenceConstruct': [11],
+ 'SequenceEmpty': [11],
+ 'SequenceErase': [11],
+ 'SequenceInsert': [11],
+ 'SequenceLength': [11],
+ 'Shape': [1],
+ 'Shrink': [9],
+ 'Sigmoid': [1, 6],
+ 'Sign': [9],
+ 'Sin': [7],
+ 'Sinh': [9],
+ 'Size': [1],
+ 'Slice': [1, 10, 11],
+ 'Softmax': [1, 11],
+ 'SoftmaxCrossEntropyLoss': [],
+ 'Softplus': [1],
+ 'Softsign': [1],
+ 'SpaceToDepth': [1],
+ 'Split': [1, 2, 11],
+ 'SplitToSequence': [],
+ 'Sqrt': [1, 6],
+ 'Squeeze': [1, 11],
+ 'StringNormalizer': [],
+ 'Sub': [1, 6, 7],
+ 'Sum': [1, 6, 8],
+ 'Tan': [7],
+ 'Tanh': [1, 6],
+ 'TfIdfVectorizer': [9],
+ 'ThresholdedRelu': [1, 10],
+ 'Tile': [1, 6],
+ 'TopK': [1, 10, 11],
+ 'Transpose': [1],
+ 'TreeEnsembleClassifier': [],
+ 'TreeEnsembleRegressor': [],
+ 'Unique': [],
+ 'Unsqueeze': [1, 11],
+ 'Upsample': [7, 9],
+ 'Where': [9],
+ 'Xor': [1, 7],
+ 'ZipMap': []
+}
+
+backend_partial_support = {
+ 'Cast': 'Cast string to float32/float64/int32/int64 are not supported in '
+ 'Tensorflow.',
+ 'Clip': 'Clip input in uint64 is not supported in Tensorflow.',
+ 'ConvTranspose': 'ConvTranspose with dilations != 1, or transposed '
+ 'convolution for 4D or higher are not supported in '
+ 'Tensorflow.',
+ 'CumSum': 'CumSum inputs in uint32/uint64 are not supported in Tensorflow.',
+ 'Equal': 'Equal inputs in uint16/uint32/uint64 are not supported in '
+ 'Tensorflow.',
+ 'GRU': 'GRU with clip or GRU with linear_before_reset, or GRU not using '
+ 'sigmoid for z and r, or GRU using Elu as the activation function '
+ 'with alpha != 1, or GRU using HardSigmoid as the activation '
+ 'function with alpha != 0.2 or beta != 0.5 are not supported in '
+ 'TensorFlow.',
+ 'LSTM': 'LSTM not using sigmoid for `f`, or LSTM not using the same '
+ 'activation for `g` and `h` are not supported in Tensorflow.',
+ 'MaxPool': 'MaxPoolWithArgmax with pad is None or incompatible mode, or '
+ 'MaxPoolWithArgmax with 4D or higher input, orMaxPoolWithArgmax '
+ 'with column major are not supported in Tensorflow.',
+ 'Mod': 'Mod Dividend or Divisor in int8/int16/uint8/uint16/uint32/uint64 '
+ 'are not supported in Tensorflow.',
+ 'OneHot': 'OneHot indices in '
+ 'uint16/uint32/uint64/int8/int16/float16/float/double, or OneHot '
+ 'depth in '
+ 'uint8/uint16/uint32/uint64/int8/int16/int64/float16/float/double '
+ 'are not supported in Tensorflow.',
+ 'RNN': 'RNN with clip is not supported in Tensorflow.',
+ 'Resize': 'Resize required 4D input in Tensorflow. For opset 11, only the '
+ 'following attributes and inputs conbination are supported in '
+ 'Tensorflow:\n'
+ '\t1. mode=nearest, '
+ 'coordinate_transformation_mode=align_corners, '
+ 'nearest_mode=round_prefer_ceil, can use scales(*) or sizes.\n'
+ '\t2. mode=nearest, coordinate_transformation_mode=asymmetric, '
+ 'nearest_mode=floor, can use scales(*) or sizes.\n'
+ '\t3. mode=nearest, '
+ 'coordinate_transformation_mode=tf_half_pixel_for_nn, '
+ 'nearest_mode=floor, can use scales(*) or sizes.\n'
+ '\t4. mode=linear, coordinate_transformation_mode=align_corners, '
+ 'can use scales(*) or sizes.\n'
+ '\t5. mode=linear, coordinate_transformation_mode=asymmetric, '
+ 'can use scales(*) or sizes.\n'
+ '\t6. mode=linear, coordinate_transformation_mode=half_pixel, '
+ 'can use scales(*) or sizes.\n'
+ '\t7. mode=cubic, coordinate_transformation_mode=align_corners, '
+ 'cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or '
+ 'sizes.\n'
+ '\t8. mode=cubic, coordinate_transformation_mode=asymmetric, '
+ 'cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or '
+ 'sizes.\n'
+ '\t9. mode=cubic, coordinate_transformation_mode=half_pixel, '
+ 'cubic_coeff_a=-0.5, exclude_outside=1, can use scales(*) or '
+ 'sizes.\n'
+ '\t10. mode=nearest, '
+ 'coordinate_transformation_mode=tf_crop_and_resize, '
+ 'extrapolation_value=any_float_value, '
+ 'nearest_mode=round_prefer_ceil, can use scales or sizes.\n'
+ '\t11. mode=linear, '
+ 'coordinate_transformation_mode=tf_crop_and_resize, '
+ 'extrapolation_value=any_float_value, can use scales or sizes.\n'
+ '\t- Note (*): The accuracy of your model will go down, if the '
+ 'height and the width of the new sizes(scales * origial sizes) '
+ 'are not in whole numbers.',
+ 'Upsample': 'Upsample required 4D input in Tensorflow.'
+}
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/pb_wrapper.py b/pt2tf/onnx-tensorflow/onnx_tf/pb_wrapper.py
new file mode 100644
index 0000000..7b7bcaf
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/pb_wrapper.py
@@ -0,0 +1,461 @@
+import inspect
+from itertools import chain
+
+import numpy as np
+from onnx import NodeProto
+from onnx import TensorProto
+from onnx import ValueInfoProto
+from onnx import numpy_helper
+from onnx.helper import make_graph
+from onnx.helper import make_tensor
+from onnx.helper import make_tensor_value_info
+from onnx.helper import mapping
+import tensorflow as tf
+from tensorflow.core.framework.attr_value_pb2 import AttrValue
+from tensorflow.core.framework.node_def_pb2 import NodeDef
+
+from onnx_tf.common import attr_converter
+from onnx_tf.common import attr_translator
+from onnx_tf.common import CONST_MINUS_ONE_INT32
+from onnx_tf.common import CONST_ONE_FP32
+from onnx_tf.common import CONST_ONE_INT32
+from onnx_tf.common import CONST_ZERO_INT32
+from onnx_tf.common import IS_PYTHON3
+from onnx_tf.common import logger
+from onnx_tf.common.data_type import any_dtype_to_onnx_dtype
+
+
+class TensorflowNode(object):
+
+ def __init__(self,
+ node=None,
+ name=None,
+ inputs=None,
+ outputs=None,
+ attr=None,
+ domain=None,
+ op_type=None):
+ # storing a reference to the original protobuf object
+ if node is None:
+ self.node = None
+ self.name = name or ""
+ self.inputs = inputs or []
+ self.attr = attr or {}
+ self.domain = domain or ""
+ self.op_type = op_type or ""
+ self.outputs = outputs or self.get_outputs_names()
+ elif isinstance(node, (OnnxNode, NodeProto)):
+ self._load_onnx_node(node)
+ elif isinstance(node, NodeDef):
+ self._load_tf_node(node)
+
+ def _load_onnx_node(self, node):
+ if isinstance(node, NodeProto):
+ node = OnnxNode(node)
+ self.name = node.name
+ self.inputs = node.inputs
+ self.outputs = node.outputs
+ self.attr = node.attrs
+ self.domain = node.domain
+ self.op_type = node.op_type
+
+ def _load_tf_node(self, node):
+ self.node = node
+ self.name = node.name
+ self.inputs = list(node.input)
+ self.attr = {}
+ for key, val in node.attr.items():
+ new_val = attr_translator.translate_tf(key, val)
+ if isinstance(new_val, AttrValue):
+ new_val = attr_converter.convert_tf(new_val)
+ self.attr[key] = new_val
+ splitted_op_name = node.op.split(".")
+ self.domain = "" if len(splitted_op_name) == 1 else ".".join(
+ splitted_op_name[:-1])
+ self.op_type = splitted_op_name[-1]
+ self.outputs = self.get_outputs_names()
+
+ def get_outputs_names(self, num=None):
+ """ Helper method to get outputs names.
+ e.g. tf.split: [Split, Split:1, Split:2]
+
+ :param num: Force to get `num` outputs names.
+ :return: List of outputs names.
+ """
+ if num is None:
+ if "_output_shapes" in self.attr:
+ num = len(self.attr["_output_shapes"])
+ else:
+ num = 1
+ logger.warning("_output_shapes is not in node.attr. "
+ "The num of output is set to 1 for commonly. "
+ "It will cause problem with case of multiple outputs.")
+ return [
+ self.name + ":{}".format(i) if i > 0 else self.name for i in range(num)
+ ]
+
+
+class TensorflowGraph(object):
+
+ def __init__(self, graph_def, outputs=(), graph_name="graph"):
+ self._graph_name = graph_name
+ self._graph_def = self._process_graph_def(graph_def)
+ self._nodes = self._create_util_nodes() + [
+ TensorflowNode(node) for node in self.graph_def.node
+ ]
+ self._nodes_dict = {n.name: n for n in self._nodes}
+ self._outputs = outputs or self.get_output_node_names(self.graph_def)
+
+ @staticmethod
+ def _create_util_nodes():
+ util_nodes = [(CONST_MINUS_ONE_INT32, np.array([-1]).astype(np.int32)),
+ (CONST_ZERO_INT32, np.array([0]).astype(np.int32)),
+ (CONST_ONE_INT32, np.array([1]).astype(np.int32))]
+ return [
+ TensorflowNode(
+ op_type="Const",
+ name=name,
+ attr={
+ "value": value,
+ "dtype": any_dtype_to_onnx_dtype(value.dtype),
+ "_output_shapes": [value.shape]
+ }) for name, value in util_nodes
+ ]
+
+ def get_node_by_name(self, name):
+ node = self._nodes_dict.get(name, None)
+ if node is None:
+ raise ValueError(
+ "Node {} is not found in the graph provided".format(name))
+ return node
+
+ def _process_graph_def(self, graph_def):
+ if "_output_shapes" not in TensorflowNode(graph_def.node[0]).attr:
+ graph_def = self._add_infer_shapes(graph_def)
+ return graph_def
+
+ @staticmethod
+ def _add_infer_shapes(graph_def):
+ with tf.Graph().as_default():
+ with tf.Session(
+ config=tf.ConfigProto(
+ graph_options=tf.GraphOptions(infer_shapes=True))) as sess:
+ tf.import_graph_def(graph_def, name="")
+ return sess.graph_def
+
+ @staticmethod
+ def get_output_node_names(graph_def):
+ """Get output node names from GraphDef.
+
+ Args:
+ graph_def: GraphDef object.
+
+ Returns:
+ List of output node names.
+ """
+ input_names, output_names = set(), set()
+ for node in graph_def.node:
+ output_names.add(node.name)
+ input_names.update(set(node.input))
+ return list(output_names - input_names)
+
+ def update_nodes(self, nodes):
+ self._nodes = nodes
+ self._nodes_dict = {n.name: n for n in self._nodes}
+
+ @property
+ def graph_def(self):
+ return self._graph_def
+
+ @property
+ def graph_name(self):
+ return self._graph_name
+
+ @property
+ def nodes(self):
+ return self._nodes
+
+ @property
+ def nodes_dict(self):
+ return self._nodes_dict
+
+ @property
+ def outputs(self):
+ return self._outputs
+
+
+# TODO: Move this into ONNX main library
+class OnnxNode(object):
+ """
+ Reimplementation of NodeProto from ONNX, but in a form
+ more convenient to work with from Python.
+ """
+
+ def __init__(self, node):
+ self.name = str(node.name)
+ self.op_type = str(node.op_type)
+ self.domain = str(node.domain)
+ self.attrs = dict([(attr.name,
+ attr_translator.translate_onnx(
+ attr.name, attr_converter.convert_onnx(attr)))
+ for attr in node.attribute])
+ self.inputs = list(node.input)
+ self.outputs = list(node.output)
+ self.node_proto = node
+
+
+class OnnxGraph(object):
+ """ A helper class for making ONNX graph.
+ This class holds all information ONNX graph needs.
+ """
+
+ def __init__(self, name=None, graph_proto=None):
+ if graph_proto:
+ self._name = graph_proto.name
+ self._inputs_proto = list(graph_proto.input)
+ self._outputs_proto = list(graph_proto.output)
+ self._nodes_proto = list(graph_proto.node)
+ self._consts_proto = list(graph_proto.initializer)
+ self._value_info_proto = list(graph_proto.value_info)
+ self._consts = dict([(init.name, numpy_helper.to_array(init))
+ for init in graph_proto.initializer])
+ else:
+ self._name = name or ""
+ self._inputs_proto = []
+ self._outputs_proto = []
+ self._nodes_proto = []
+ self._consts = {}
+ self._consts_proto = []
+ self._value_info_proto = []
+ # Either way, data_type_cast_map is empty when initialized.
+ self._data_type_cast_map = {}
+
+ self._add_utility_constants()
+
+ def _add_utility_constants(self):
+ util_consts = {CONST_ONE_FP32: np.array([1.0]).astype(np.float32)}
+ # Add a few useful utility constants:
+ for name, value in util_consts.items():
+ self.add_const_explicit(name=name, value=value)
+ self.add_const_proto_explicit(
+ name=name, value=value, np_dtype=value.dtype)
+ self.add_input_proto_explicit(
+ name=name, shape=value.shape, np_dtype=value.dtype)
+
+ # This list holds the protobuf objects of type ValueInfoProto
+ # representing the input to the converted ONNX graph.
+ @property
+ def inputs_proto(self):
+ return self._inputs_proto
+
+ @inputs_proto.setter
+ def inputs_proto(self, inputs_proto):
+ self._inputs_proto = inputs_proto
+
+ @property
+ def all_node_inputs(self):
+ return list(chain.from_iterable(map(lambda p: p.input, self._nodes_proto)))
+
+ @property
+ def outputs(self):
+ return list(map(lambda p: p.name, self._outputs_proto))
+
+ @property
+ def outputs_proto(self):
+ return self._outputs_proto
+
+ # This list holds the protobuf objects of type NodeProto
+ # representing the ops in the converted ONNX graph.
+ @property
+ def nodes_proto(self):
+ return self._nodes_proto
+
+ @nodes_proto.setter
+ def nodes_proto(self, nodes_proto):
+ self._nodes_proto = nodes_proto
+
+ # This dictionary contains a map from the name of the constant
+ # op to the array of values it holds. This is useful because
+ # tensorflow is less eager to know about input values at
+ # graph construction time than ONNX. That is to say, some ONNX
+ # attributes are input tensors in TF. This dictionary extracts
+ # those values of constant tensors that are known at graph
+ # construction time.
+ @property
+ def consts(self):
+ return self._consts
+
+ @consts.setter
+ def consts(self, consts):
+ self._consts = consts
+
+ # Sometimes the constants are used as inputs to ops. This list
+ # holds initializers that creates global constant tensors available
+ # to be accessed by ops as inputs (as oppose to attributes which
+ # is supplied by the `consts` map above).
+ @property
+ def consts_proto(self):
+ return self._consts_proto
+
+ @consts_proto.setter
+ def consts_proto(self, consts_proto):
+ self._consts_proto = consts_proto
+
+ # A map holds nodes name and new data type. Will be used to
+ # process protos to match ONNX type constraints.
+ @property
+ def data_type_cast_map(self):
+ return self._data_type_cast_map
+
+ @data_type_cast_map.setter
+ def data_type_cast_map(self, data_type_cast_map):
+ self._data_type_cast_map = data_type_cast_map
+
+ # This list holds the protobuf objects of type ValueInfoProto
+ # representing the all nodes' outputs to the converted ONNX graph.
+ @property
+ def value_info_proto(self):
+ return self._value_info_proto
+
+ def add_input_proto_explicit(self,
+ name,
+ shape,
+ np_dtype=None,
+ tf_dtype=None,
+ onnx_dtype=None):
+ onnx_dtype = any_dtype_to_onnx_dtype(
+ np_dtype=np_dtype, tf_dtype=tf_dtype, onnx_dtype=onnx_dtype)
+ input_proto = make_tensor_value_info(name, onnx_dtype, shape)
+ self._inputs_proto.append(input_proto)
+
+ def add_input_proto(self, node):
+ name = node.name
+ onnx_dtype = node.attr["dtype"]
+ shape = node.attr["shape"] if node.op_type != "Const" else node.attr[
+ 'value'].shape
+ self.add_input_proto_explicit(name, shape, onnx_dtype=onnx_dtype)
+
+ def add_output_proto(self, node):
+ output_onnx_type = node.attr.get("T", TensorProto.BOOL)
+ for i, output_shape in enumerate(node.attr["_output_shapes"]):
+ output_name = node.name + ":{}".format(i) if i > 0 else node.name
+ self._outputs_proto.append(
+ make_tensor_value_info(output_name, output_onnx_type, output_shape))
+
+ def add_node_proto(self, node_proto):
+ if not isinstance(node_proto, (list, tuple)):
+ node_proto = [node_proto]
+ self._nodes_proto.extend(node_proto)
+
+ def remove_node_proto(self, names):
+ if not isinstance(names, (list, tuple)):
+ names = [names]
+ self._nodes_proto = list(
+ filter(lambda x: x.name not in names, self._nodes_proto))
+
+ def add_const_explicit(self, name, value):
+ self._consts[name] = value
+
+ def add_const(self, node):
+ self.add_const_explicit(node.name, node.attr["value"])
+
+ def add_const_proto_explicit(self,
+ name,
+ value,
+ np_dtype=None,
+ tf_dtype=None,
+ onnx_dtype=None):
+ onnx_dtype = any_dtype_to_onnx_dtype(
+ np_dtype=np_dtype, tf_dtype=tf_dtype, onnx_dtype=onnx_dtype)
+
+ const_dim = len(value.shape)
+
+ if const_dim == 0:
+ raw_values = [value.tolist()]
+ values = [value]
+ else:
+ raw_values = value.flatten().tolist()
+ values = value
+
+ shape = np.array(values).shape
+ const_proto = make_tensor(
+ name=name, data_type=onnx_dtype, dims=shape, vals=raw_values)
+ self._consts_proto.append(const_proto)
+
+ def add_const_proto(self, node):
+ self.add_const_proto_explicit(
+ node.name, node.attr["value"], onnx_dtype=node.attr["dtype"])
+
+ def add_value_info_proto(self, node):
+ node_onnx_type = node.attr.get("T", TensorProto.BOOL)
+ for i, output_shape in enumerate(node.attr["_output_shapes"]):
+ node_name = node.name + ":{}".format(i) if i > 0 else node.name
+ value_info_proto = make_tensor_value_info(node_name, node_onnx_type,
+ output_shape)
+ self._value_info_proto.append(value_info_proto)
+
+ # Remove proto in inputs_proto and consts_proto
+ # if proto is not used as input or an output in ONNX
+ def _clean_graph(self):
+ in_out = self.all_node_inputs + self.outputs
+ self._inputs_proto = list(
+ filter(lambda x: x.name in in_out, self.inputs_proto))
+ self._consts_proto = list(
+ filter(lambda x: x.name in in_out, self.consts_proto))
+
+ def _fix_data_type(self):
+ self.inputs_proto = self._data_type_caster(self.inputs_proto,
+ self.data_type_cast_map)
+ self.consts_proto = self._data_type_caster(self.consts_proto,
+ self.data_type_cast_map)
+
+ @classmethod
+ def _data_type_caster(cls, protos, data_type_cast_map):
+ """Cast to a new data type if node name is in data_type_cast_map.
+ Be used to process protos to match ONNX type constraints.
+
+ :param protos: Target protos.
+ TensorProto for inputs and ValueInfoProto for consts.
+ :param data_type_cast_map: A {node.name: new_data_type} dict.
+ :return: Processed protos.
+ """
+ if not data_type_cast_map:
+ return protos
+ result = []
+ for proto in protos:
+ new_proto = proto
+ if proto.name in data_type_cast_map:
+ new_data_type = data_type_cast_map[proto.name]
+ if type(proto) == TensorProto and proto.data_type != new_data_type:
+ field = mapping.STORAGE_TENSOR_TYPE_TO_FIELD[
+ mapping.TENSOR_TYPE_TO_STORAGE_TENSOR_TYPE[proto.data_type]]
+ vals = getattr(proto, field)
+ new_proto = make_tensor(
+ name=proto.name,
+ data_type=new_data_type,
+ dims=proto.dims,
+ vals=vals)
+ elif type(
+ proto
+ ) == ValueInfoProto and proto.type.tensor_type.elem_type != new_data_type:
+ new_proto.type.tensor_type.elem_type = new_data_type
+ result.append(new_proto)
+ return result
+
+ def make_graph_proto(self):
+ self._clean_graph()
+ self._fix_data_type()
+
+ if IS_PYTHON3:
+ params = list(inspect.signature(make_graph).parameters.keys())
+ else:
+ params = inspect.getargspec(make_graph).args
+
+ kwargs = {
+ "initializer": self.consts_proto,
+ "value_info": self.value_info_proto
+ }
+
+ return make_graph(self.nodes_proto, self._name, self.inputs_proto,
+ self.outputs_proto,
+ **dict([(k, kwargs[k]) for k in kwargs if k in params]))
diff --git a/pt2tf/onnx-tensorflow/onnx_tf/version.py b/pt2tf/onnx-tensorflow/onnx_tf/version.py
new file mode 100644
index 0000000..3284f5f
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/onnx_tf/version.py
@@ -0,0 +1,7 @@
+# This file is generated by setup.py. DO NOT EDIT!
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+version = '1.6.0'
+git_version = '6b9e76d6fca74d5ddbf47049b9670120abaaf70f'
diff --git a/pt2tf/onnx-tensorflow/setup.cfg b/pt2tf/onnx-tensorflow/setup.cfg
new file mode 100644
index 0000000..76200df
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/setup.cfg
@@ -0,0 +1,3 @@
+[metadata]
+description-file = README.md
+license-file = LICENSE
diff --git a/pt2tf/onnx-tensorflow/setup.py b/pt2tf/onnx-tensorflow/setup.py
new file mode 100644
index 0000000..97e33d6
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/setup.py
@@ -0,0 +1,59 @@
+import os
+import setuptools
+import subprocess
+from textwrap import dedent
+
+TOP_DIR = os.path.realpath(os.path.dirname(__file__))
+SRC_DIR = os.path.join(TOP_DIR, 'onnx_tf')
+
+with open(os.path.join(TOP_DIR, 'VERSION_NUMBER')) as version_file:
+ version = version_file.read().strip()
+
+if os.getenv('TRAVIS'):
+ # On travis, we install from source, therefore no need to specify version.
+ onnx_dep = "onnx"
+else:
+ # For user, we install the onnx release known to work with our release.
+ with open(os.path.join(TOP_DIR, 'ONNX_VERSION_NUMBER')) as onnx_version_file:
+ onnx_version = onnx_version_file.read().strip()
+ onnx_dep = "onnx>=" + onnx_version
+
+try:
+ git_version = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
+ cwd=TOP_DIR).decode('ascii').strip()
+except (OSError, subprocess.CalledProcessError):
+ git_version = None
+
+with open(os.path.join(SRC_DIR, 'version.py'), 'w') as f:
+ f.write(dedent('''\
+ # This file is generated by setup.py. DO NOT EDIT!
+ from __future__ import absolute_import
+ from __future__ import division
+ from __future__ import print_function
+ from __future__ import unicode_literals
+ version = '{}'
+ git_version = '{}'
+ '''.format(version, git_version)))
+
+setuptools.setup(
+ name='onnx-tf',
+ version=version,
+ description=
+ 'Tensorflow backend for ONNX (Open Neural Network Exchange).',
+ install_requires=[onnx_dep, "PyYAML"],
+ entry_points={
+ "console_scripts": [
+ "onnx-tf=onnx_tf.cli:main",
+ ],
+ },
+ url='https://github.com/onnx/onnx-tensorflow/',
+ author='Arpith Jacob, Tian Jin, Gheorghe-Teodor Bercea, Wenhao Hu',
+ author_email='tian.jin1@ibm.com',
+ license='Apache License 2.0',
+ packages=setuptools.find_packages(),
+ zip_safe=False,
+ classifiers=[
+ "Programming Language :: Python :: 2",
+ "Programming Language :: Python :: 3"
+ ]
+)
diff --git a/pt2tf/onnx-tensorflow/test/__init__.py b/pt2tf/onnx-tensorflow/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pt2tf/onnx-tensorflow/test/backend/__init__.py b/pt2tf/onnx-tensorflow/test/backend/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pt2tf/onnx-tensorflow/test/backend/test_dynamic_shape.py b/pt2tf/onnx-tensorflow/test/backend/test_dynamic_shape.py
new file mode 100644
index 0000000..752c856
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/backend/test_dynamic_shape.py
@@ -0,0 +1,305 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import numpy as np
+import tensorflow as tf
+import unittest
+
+from onnx_tf.backend import onnx_graph_to_tensorflow_rep
+from onnx_tf.common.legacy import legacy_opset_pre_ver
+from onnx import defs
+from onnx import helper
+from onnx import TensorProto
+
+# Run the following test in graph mode
+tf.compat.v1.disable_eager_execution()
+
+
+class TestDynamicShape(unittest.TestCase):
+ """ Tests for dynamic shape support
+ """
+
+ def _get_rnd_float32(self, low=-1.0, high=1.0, shape=None):
+ output = np.random.uniform(low, high, shape)
+ if shape is None:
+ return np.float32(output)
+ else:
+ return output.astype(np.float32)
+
+ def _get_rnd_int(self, low, high=None, shape=None, dtype=np.int32):
+ return np.random.randint(low, high, size=shape, dtype=dtype)
+
+ def test_arg_max(self):
+ if legacy_opset_pre_ver(12):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support select_last_index attribute for ArgMax that depends on shape.".format(
+ defs.onnx_opset_version()))
+ axis = 1
+ node_def = helper.make_node("ArgMax",
+ inputs=['X'],
+ outputs=['Y'],
+ axis=axis,
+ keepdims=0,
+ select_last_index=1)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, None])
+ ],
+ outputs=[
+ helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, None])
+ ])
+ x = np.array([[ 1, 2, 3, 5, 3, 4, 5, 1 ], [ 2, 9, 3, 5, 9, 4, 5, 1 ]])
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+ output = tf_rep.run({"X": x})
+ expected_output = np.argmax(np.flip(x, axis), axis=axis)
+ expected_output = x.shape[axis] - expected_output - 1
+ np.testing.assert_almost_equal(output['Y'], expected_output)
+
+ def test_arg_min(self):
+ if legacy_opset_pre_ver(12):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support select_last_index attribute for ArgMin that depends on shape.".format(
+ defs.onnx_opset_version()))
+ axis = 1
+ node_def = helper.make_node("ArgMin",
+ inputs=['X'],
+ outputs=['Y'],
+ axis=axis,
+ keepdims=0,
+ select_last_index=1)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, None])
+ ],
+ outputs=[
+ helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, None])
+ ])
+ x = np.array([[ 1, 2, 3, 5, 3, 4, 5, 1 ], [ 2, 7, 3, 5, 2, 4, 5, 6 ]])
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+ output = tf_rep.run({"X": x})
+ expected_output = np.argmin(np.flip(x, axis), axis=axis)
+ expected_output = x.shape[axis] - expected_output - 1
+ np.testing.assert_almost_equal(output['Y'], expected_output)
+
+ def _batch_normalization(self, x, mean, variance, bias, scale,
+ variance_epsilon):
+ inv = np.reciprocal(np.sqrt(variance + variance_epsilon))
+ if scale is not None:
+ inv *= scale
+ return x * inv + (bias - mean * inv if bias is not None else -mean * inv)
+
+ def test_batch_normalization(self):
+ if legacy_opset_pre_ver(6):
+ raise unittest.SkipTest("Backend doesn't support consumed flag")
+ node_def = helper.make_node("BatchNormalization",
+ ["X", "scale", "bias", "mean", "var"], ["Y"],
+ epsilon=0.001)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, None, None, None]),
+ helper.make_tensor_value_info("scale", TensorProto.FLOAT, [None]),
+ helper.make_tensor_value_info("bias", TensorProto.FLOAT, [None]),
+ helper.make_tensor_value_info("mean", TensorProto.FLOAT, [None]),
+ helper.make_tensor_value_info("var", TensorProto.FLOAT, [None])
+ ],
+ outputs=[
+ helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, None, None, None])
+ ])
+ x_shape = [3, 5, 4, 2]
+ param_shape = [5]
+ _param_shape = [1, 5, 1, 1]
+ x = self._get_rnd_float32(0, 1, shape=x_shape)
+ m = self._get_rnd_float32(0, 1, shape=param_shape)
+ _m = m.reshape(_param_shape)
+ v = self._get_rnd_float32(0, 1, shape=param_shape)
+ _v = v.reshape(_param_shape)
+ scale = self._get_rnd_float32(0, 1, shape=param_shape)
+ _scale = scale.reshape(_param_shape)
+ bias = self._get_rnd_float32(0, 1, shape=param_shape)
+ _bias = bias.reshape(_param_shape)
+ golden = self._batch_normalization(x, _m, _v, _bias, _scale, 0.001)
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+ output = tf_rep.run({"X": x, "scale": scale, "bias": bias, "mean": m, "var": v})
+ np.testing.assert_almost_equal(output["Y"], golden, decimal=5)
+
+ def test_conv_transpose(self):
+ # test dynamic batch size on transpose of 2d convolution
+ pads = [1, 1, 1, 1]
+ x_shape = [1, 3, 4, 6]
+ x = self._get_rnd_float32(shape=x_shape)
+ weight_shape = [3, 5, 2, 2]
+ weights = self._get_rnd_float32(shape=weight_shape)
+
+ node_def = helper.make_node("ConvTranspose", ["X", "weights"], ["Y"],
+ pads=pads)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT, [None, 3, 4, 6]),
+ helper.make_tensor_value_info("weights", TensorProto.FLOAT, weight_shape)
+ ],
+ outputs=[
+ helper.make_tensor_value_info("Y", TensorProto.FLOAT, [None, None, None, None])
+ ])
+
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+ output = tf_rep.run({"X": x, "weights": weights})
+
+ padh_left = weight_shape[2] - 1 - pads[0]
+ padh_right = weight_shape[2] - 1 - pads[1]
+ padw_left = weight_shape[3] - 1 - pads[2]
+ padw_right = weight_shape[3] - 1 - pads[3]
+
+ kh = weight_shape[2]
+ kw = weight_shape[3]
+ outh = x_shape[2] + padh_right + padh_right - (kh - 1)
+ outw = x_shape[3] + padw_right + padw_right - (kw - 1)
+
+ out_shape = [x_shape[0], weight_shape[1], outh, outw]
+
+ test_output = np.zeros(out_shape)
+ for b in range(0, x_shape[0]):
+ for m in range(0, weight_shape[1]):
+ for c in range(0, x_shape[1]):
+ for h in range(0, outh):
+ for w in range(0, outw):
+ for k1 in range(h, h + kh):
+ for k2 in range(w, w + kw):
+ if (k1 - padh_left >= 0 and k2 - padw_left >= 0):
+ test_output[b][m][h][w] += x[b][c][k1 - padh_left][
+ k2 - padw_left] * weights[c][m][kh + h - 1 -
+ k1][kw + w - 1 - k2]
+
+ np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)
+
+ def test_slice(self):
+ # test case 1 with normal inputs
+ axes = [0, 1, 2]
+ starts = [0, 0, 0]
+ ends = [2, 2, 2]
+
+ if legacy_opset_pre_ver(10):
+ node_def = helper.make_node("Slice", ["X"], ["S"],
+ axes=axes,
+ starts=starts,
+ ends=ends)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT,
+ [None, None, None])
+ ],
+ outputs=[
+ helper.make_tensor_value_info("S", TensorProto.FLOAT,
+ [None, None, None])
+ ])
+ else:
+ node_def = helper.make_node("Slice",
+ ["X", "starts", "ends", "axes"],
+ ["S"])
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT,
+ [None, None, None]),
+ helper.make_tensor_value_info("starts", TensorProto.INT32,
+ [None]),
+ helper.make_tensor_value_info("ends", TensorProto.INT32,
+ [None]),
+ helper.make_tensor_value_info("axes", TensorProto.INT32,
+ [None]),
+ ],
+ outputs=[
+ helper.make_tensor_value_info("S", TensorProto.FLOAT,
+ [None, None, None])
+ ])
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+
+ if legacy_opset_pre_ver(10):
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = tf_rep.run({"X": x})
+ np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])
+ else:
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = tf_rep.run({"X": x, "starts": starts, "ends": ends, "axes": axes})
+ np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])
+
+ # test case 2 with negative, out-of-bound and default inputs
+ axes = [0, 2]
+ starts = [0, -7]
+ ends = [-8, 20]
+ steps = [1, 1]
+
+ if legacy_opset_pre_ver(10):
+ node_def = helper.make_node("Slice", ["X"], ["S"],
+ axes=axes,
+ starts=starts,
+ ends=ends)
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT,
+ [None, None, None])
+ ],
+ outputs=[
+ helper.make_tensor_value_info("S", TensorProto.FLOAT,
+ [None, None, None])
+ ])
+ else:
+ node_def = helper.make_node("Slice",
+ ["X", "starts", "ends", "axes", "steps"],
+ ["S"])
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test_unknown_shape",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT,
+ [None, None, None]),
+ helper.make_tensor_value_info("starts", TensorProto.INT32,
+ [None]),
+ helper.make_tensor_value_info("ends", TensorProto.INT32,
+ [None]),
+ helper.make_tensor_value_info("axes", TensorProto.INT32,
+ [None]),
+ helper.make_tensor_value_info("steps", TensorProto.INT32,
+ [None]),
+ ],
+ outputs=[
+ helper.make_tensor_value_info("S", TensorProto.FLOAT,
+ [None, None, None])
+ ])
+ tf_rep = onnx_graph_to_tensorflow_rep(graph_def)
+ if legacy_opset_pre_ver(10):
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = tf_rep.run({"X": x})
+ np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])
+ else:
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = tf_rep.run({"X": x, "starts": starts, "ends": ends, "axes": axes, "steps": steps})
+ np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])
+
+ # test case 3 with non-default steps
+ axes = [0, 1, 2]
+ starts = [0, 0, 0]
+ ends = [2, 2, 2]
+ steps = [2, -2, -1]
+
+ if not legacy_opset_pre_ver(10):
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = tf_rep.run({"X": x, "starts": starts, "ends": ends, "axes": axes, "steps": steps})
+ np.testing.assert_almost_equal(output["S"], x[0:2:2, 0:2:-2, 0:2:-1])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pt2tf/onnx-tensorflow/test/backend/test_model.py b/pt2tf/onnx-tensorflow/test/backend/test_model.py
new file mode 100644
index 0000000..ffda278
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/backend/test_model.py
@@ -0,0 +1,157 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import unittest
+
+import numpy as np
+import onnx
+from onnx_tf.backend import prepare
+from onnx import helper
+from onnx import TensorProto
+
+from onnx_tf.common.legacy import legacy_onnx_pre_ver
+
+
+class TestModel(unittest.TestCase):
+ """ Tests for models
+ """
+
+ def _get_rnd(self, shape, low=-1.0, high=1.0):
+ return np.random.uniform(low, high, np.prod(shape)) \
+ .reshape(shape) \
+ .astype(np.float32)
+
+ def test_sequence_ops(self):
+ # test SequenceConstruct and SequenceAt
+ a = np.random.randn(2, 1, 2).astype(np.float32)
+ b = np.random.randn(1, 1, 2).astype(np.float32)
+ c = np.random.randn(3, 1, 2).astype(np.float32)
+ seq_construct_node = helper.make_node('SequenceConstruct', ['a', 'b', 'c'], ['S'])
+ seq_at_node = helper.make_node('SequenceAt', ['S','at'], ['Y'])
+ out_value_info = helper.make_tensor_value_info('Y',onnx.TensorProto.FLOAT,[None])
+ a_value_info = helper.make_tensor_value_info('a',onnx.TensorProto.FLOAT,[2, 1, 2])
+ b_value_info = helper.make_tensor_value_info('b',onnx.TensorProto.FLOAT,[1, 1, 2])
+ c_value_info = helper.make_tensor_value_info('c',onnx.TensorProto.FLOAT,[3, 1, 2])
+ at_value_info = helper.make_tensor_value_info('at',onnx.TensorProto.INT32,[])
+
+ graph = helper.make_graph([seq_construct_node, seq_at_node],
+ name='seq_construct_at_test',
+ inputs=[a_value_info, b_value_info, c_value_info, at_value_info],
+ outputs=[out_value_info])
+ model = helper.make_model(graph, producer_name='backend-test')
+ tf_rep = prepare(model)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'at':0})
+ np.testing.assert_almost_equal(output["Y"], a)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'at':-2})
+ np.testing.assert_almost_equal(output["Y"], b)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'at':2})
+ np.testing.assert_almost_equal(output["Y"], c)
+
+ # test SequenceEmpty, SequenceInsert, and SequenceAt
+ p = np.int32(0)
+ seq_empty_node = helper.make_node('SequenceEmpty', [], ['S'])
+ seq_insert_node1 = helper.make_node('SequenceInsert', ['S','a'], ['S1'])
+ seq_insert_node2 = helper.make_node('SequenceInsert', ['S1','b'], ['S2'])
+ seq_insert_node3 = helper.make_node('SequenceInsert', ['S2','c','p'], ['S3'])
+ seq_at_node = helper.make_node('SequenceAt', ['S3','at'], ['Y'])
+
+ p_value_info = helper.make_tensor_value_info('p',onnx.TensorProto.INT32,[])
+
+ graph = helper.make_graph([seq_empty_node, seq_insert_node1, seq_insert_node2, seq_insert_node3, seq_at_node],
+ name='seq_empty_insert_at_test',
+ inputs=[a_value_info, b_value_info, c_value_info, p_value_info, at_value_info],
+ outputs=[out_value_info])
+ model = helper.make_model(graph, producer_name='backend-test')
+ tf_rep = prepare(model)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'p':p, 'at':0})
+ np.testing.assert_almost_equal(output["Y"], c)
+
+ # test SequenceConstruct, SequenceErase, and SequenceLength
+ seq_construct_node = helper.make_node('SequenceConstruct', ['a', 'b', 'c'], ['S'])
+ seq_erase_node = helper.make_node('SequenceErase', ['S','p'], ['S1'])
+ seq_length_node = helper.make_node('SequenceLength', ['S1'], ['Y'])
+
+ graph = helper.make_graph([seq_construct_node, seq_erase_node, seq_length_node],
+ name='seq_construct_erase_length_test',
+ inputs=[a_value_info, b_value_info, c_value_info, p_value_info],
+ outputs=[out_value_info])
+ model = helper.make_model(graph, producer_name='backend-test')
+ tf_rep = prepare(model)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'p':p})
+ np.testing.assert_almost_equal(output["Y"], 2)
+
+ # test SequenceConstruct and SequenceErase
+ seq_construct_node = helper.make_node('SequenceConstruct', ['a', 'b', 'c'], ['S'])
+ seq_erase_node = helper.make_node('SequenceErase', ['S','p'], ['S1'])
+ seq_at_node = helper.make_node('SequenceAt', ['S1', 'at'], ['Y'])
+
+ graph = helper.make_graph([seq_construct_node, seq_erase_node, seq_at_node],
+ name='seq_construct_erase_test',
+ inputs=[a_value_info, b_value_info, c_value_info, p_value_info, at_value_info],
+ outputs=[out_value_info])
+ model = helper.make_model(graph, producer_name='backend-test')
+ tf_rep = prepare(model)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'p':p, 'at':0})
+ np.testing.assert_almost_equal(output["Y"], b)
+ output = tf_rep.run({'a':a, 'b':b, 'c':c, 'p':p, 'at':1})
+ np.testing.assert_almost_equal(output["Y"], c)
+
+ def test_relu_node_inplace(self):
+ X = np.random.randn(3, 2).astype(np.float32)
+ Y_ref = np.clip(X, 0, np.inf)
+
+ node_def = helper.make_node("Relu", ["X"], ["X1"])
+
+ graph_def = helper.make_graph(
+ [node_def],
+ name="test",
+ inputs=[helper.make_tensor_value_info("X", TensorProto.FLOAT, [3, 2])],
+ outputs=[
+ helper.make_tensor_value_info("X1", TensorProto.FLOAT, [3, 2])
+ ])
+ tf_rep = prepare(helper.make_model(graph_def))
+ output = tf_rep.run({"X": X})
+ np.testing.assert_almost_equal(output.X1, Y_ref)
+
+ def test_initializer(self):
+ if legacy_onnx_pre_ver(1, 2):
+ raise unittest.SkipTest(
+ "The current version of ONNX does not record correctly the opset of Cast."
+ )
+ X = np.array([[1, 2], [3, 4]]).astype(np.float32)
+ Y = np.array([[1, 2], [3, 4]]).astype(np.float32)
+ weight = np.array([[1, 0], [0, 1]])
+ graph_def = helper.make_graph(
+ [
+ helper.make_node("Add", ["X", "Y"], ["Z0"]),
+ helper.make_node("Cast", ["Z0"], ["Z"], to=TensorProto.FLOAT),
+ helper.make_node("Mul", ["Z", "weight"], ["W"]),
+ helper.make_node("Tanh", ["W"], ["W1"]),
+ helper.make_node("Sigmoid", ["W1"], ["W2"])
+ ],
+ name="test_initializer",
+ inputs=[
+ helper.make_tensor_value_info("X", TensorProto.FLOAT, (2, 2)),
+ helper.make_tensor_value_info("Y", TensorProto.FLOAT, (2, 2)),
+ helper.make_tensor_value_info("weight", TensorProto.FLOAT, (2, 2)),
+ ],
+ outputs=[
+ helper.make_tensor_value_info("W2", TensorProto.FLOAT, (2, 2))
+ ],
+ initializer=[
+ helper.make_tensor("weight", TensorProto.FLOAT, [2, 2],
+ weight.flatten().astype(float))
+ ])
+
+ def sigmoid(x):
+ return 1 / (1 + np.exp(-x))
+
+ W_ref = sigmoid(np.tanh((X + Y) * weight))
+ tf_rep = prepare(helper.make_model(graph_def))
+ output = tf_rep.run({"X": X, "Y": Y})
+ np.testing.assert_almost_equal(output["W2"], W_ref)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pt2tf/onnx-tensorflow/test/backend/test_node.py b/pt2tf/onnx-tensorflow/test/backend/test_node.py
new file mode 100644
index 0000000..832f1e8
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/backend/test_node.py
@@ -0,0 +1,3724 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import math
+import unittest
+import numpy as np
+import tensorflow as tf
+from onnx_tf.backend import onnx_graph_to_tensorflow_rep
+from onnx_tf.backend import run_node
+from onnx_tf.common import supports_device
+from onnx_tf.common.legacy import legacy_onnx_pre_ver, legacy_opset_pre_ver
+from onnx_tf.common.pooling_helper import py_pool
+from onnx import helper
+from onnx import TensorProto
+from onnx import defs
+
+
+class TestNode(unittest.TestCase):
+ """ Tests for nodes
+ """
+
+ def _get_rnd_float32(self, low=-1.0, high=1.0, shape=None):
+ output = np.random.uniform(low, high, shape)
+ if shape is None:
+ return np.float32(output)
+ else:
+ return output.astype(np.float32)
+
+ def _get_rnd_int(self, low, high=None, shape=None, dtype=np.int32):
+ return np.random.randint(low, high, size=shape, dtype=dtype)
+
+ def _elu(self, x):
+ # f(x) = alpha * (exp(x) - 1.) for x < 0,
+ # f(x) = x for x >= 0
+ if x < 0.:
+ return np.expm1(x)
+ return x
+
+ def _leaky_relu(self, x, alpha):
+ # f(x) = alpha * x for x < 0,
+ # f(x) = x for x >= 0
+ if x < 0.:
+ return alpha * x
+ return x
+
+ def test_abs(self):
+ node_def = helper.make_node("Abs", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.abs(x))
+
+ def test_acosh(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Acosh.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Acosh", ["X"], ["Y"])
+ x = self._get_rnd_float32(low=1.0, high=np.finfo(np.float32).max, shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.arccosh(x))
+
+ def test_add(self):
+ node_def = helper.make_node("Add", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[5, 10, 5, 5])
+ y = self._get_rnd_float32(shape=[10, 1, 1])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"],
+ np.add(x, y.reshape([1, 10, 1, 1])))
+
+ # node_def = helper.make_node("Add", ["A", "B"], ["C"], broadcast=1)
+ # a = self._get_rnd([10, 10])
+ # b = self._get_rnd([10, 10])
+ # output = run_node(node_def, [a, b])
+ # np.testing.assert_almost_equal(output["C"], np.add(a, b))
+
+ # node_def = helper.make_node("Add", ["A", "B"], ["C"], broadcast=1)
+ # a = self._get_rnd([10, 10])
+ # b = self._get_rnd([10,])
+ # output = run_node(node_def, [a, b])
+ # np.testing.assert_almost_equal(output["C"], np.add(a, b))
+
+ def test_arg_max(self):
+ for axis in [0, 1]:
+ node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0)
+ data = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [data])
+ np.testing.assert_almost_equal(output["reduced"],
+ np.argmax(data, axis=axis))
+ # test select_last_index
+ if not legacy_opset_pre_ver(12):
+ # select_last_index = 0
+ node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0,
+ select_last_index=0)
+ data = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [data])
+ np.testing.assert_almost_equal(output["reduced"],
+ np.argmax(data, axis=axis))
+ # select_last_index = 1
+ node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0,
+ select_last_index=1)
+ data = np.array([[1, 2, 3, 5, 3, 4, 5, 1], [2, 9, 3, 5, 9, 4, 5, 1]])
+ output = run_node(node_def, [data])
+ data = np.flip(data, axis)
+ result = np.argmax(data, axis=axis)
+ result = data.shape[axis] - result - 1
+ np.testing.assert_almost_equal(output["reduced"], result)
+
+ def test_arg_min(self):
+ for axis in [0, 1]:
+ node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0)
+ data = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [data])
+ np.testing.assert_almost_equal(output["reduced"],
+ np.argmin(data, axis=axis))
+ # test select_last_index
+ if not legacy_opset_pre_ver(12):
+ # select_last_index = 0
+ node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0,
+ select_last_index=0)
+ data = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [data])
+ np.testing.assert_almost_equal(output["reduced"],
+ np.argmin(data, axis=axis))
+ # select_last_index = 1
+ node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
+ axis=axis,
+ keepdims=0,
+ select_last_index=1)
+ data = np.array([[1, 2, 3, 5, 3, 4, 5, 1], [2, 7, 3, 5, 2, 4, 5, 6]])
+ output = run_node(node_def, [data])
+ data = np.flip(data, axis)
+ result = np.argmin(data, axis=axis)
+ result = data.shape[axis] - result - 1
+ np.testing.assert_almost_equal(output["reduced"], result)
+
+ def test_asinh(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Asinh.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Asinh", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.arcsinh(x))
+
+ def test_atanh(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Atanh.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Atanh", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.arctanh(x))
+
+ def _batch_normalization(self, x, mean, variance, bias, scale,
+ variance_epsilon):
+ inv = np.reciprocal(np.sqrt(variance + variance_epsilon))
+ if scale is not None:
+ inv *= scale
+ return x * inv + (bias - mean * inv if bias is not None else -mean * inv)
+
+ def test_batch_normalization(self):
+ if legacy_opset_pre_ver(6):
+ raise unittest.SkipTest("Backend doesn't support consumed flag")
+ node_def = helper.make_node("BatchNormalization",
+ ["X", "scale", "bias", "mean", "var"], ["Y"],
+ epsilon=0.001)
+ x_shape = [3, 5, 4, 2]
+ param_shape = [5]
+ _param_shape = [1, 5, 1, 1]
+ x = self._get_rnd_float32(0, 1, shape=x_shape)
+ m = self._get_rnd_float32(0, 1, shape=param_shape)
+ _m = m.reshape(_param_shape)
+ v = self._get_rnd_float32(0, 1, shape=param_shape)
+ _v = v.reshape(_param_shape)
+ scale = self._get_rnd_float32(0, 1, shape=param_shape)
+ _scale = scale.reshape(_param_shape)
+ bias = self._get_rnd_float32(0, 1, shape=param_shape)
+ _bias = bias.reshape(_param_shape)
+ golden = self._batch_normalization(x, _m, _v, _bias, _scale, 0.001)
+ output = run_node(node_def, [x, scale, bias, m, v])
+ np.testing.assert_almost_equal(output["Y"], golden, decimal=5)
+
+ def test_cast(self):
+ if legacy_onnx_pre_ver(1, 2) or legacy_opset_pre_ver(6):
+ test_cases = [("FLOAT", tf.float32), ("UINT8", tf.uint8),
+ ("INT8", tf.int8),
+ ("UINT16", tf.uint16), ("INT16", tf.int16),
+ ("INT32", tf.int32), ("INT64", tf.int64), ("BOOL", tf.bool),
+ ("FLOAT16", tf.float16), ("DOUBLE", tf.float64),
+ ("COMPLEX64", tf.complex64), ("COMPLEX128", tf.complex128)]
+ else:
+ test_cases = [(TensorProto.FLOAT, tf.float32),
+ (TensorProto.UINT8, tf.uint8), (TensorProto.INT8, tf.int8),
+ (TensorProto.UINT16, tf.uint16),
+ (TensorProto.INT16, tf.int16),
+ (TensorProto.INT32, tf.int32),
+ (TensorProto.INT64, tf.int64), (TensorProto.BOOL, tf.bool),
+ (TensorProto.FLOAT16, tf.float16),
+ (TensorProto.DOUBLE, tf.float64),
+ (TensorProto.COMPLEX64, tf.complex64),
+ (TensorProto.COMPLEX128, tf.complex128)]
+ if not legacy_opset_pre_ver(9):
+ test_cases.append((TensorProto.STRING, tf.string))
+ for ty, tf_type in test_cases:
+ node_def = helper.make_node("Cast", ["input"], ["output"], to=ty)
+ vector = [2, 3]
+ output = run_node(node_def, [vector])
+ np.testing.assert_equal(output["output"].dtype, tf_type)
+
+ if not legacy_opset_pre_ver(9):
+ test_cases2 = [(TensorProto.FLOAT, tf.float32),
+ (TensorProto.INT32, tf.int32),
+ (TensorProto.INT64, tf.int64),
+ (TensorProto.DOUBLE, tf.float64)]
+ for ty, tf_type in test_cases2:
+ node_def = helper.make_node("Cast", ["input"], ["output"], to=ty)
+ vector = ['2', '3']
+ output = run_node(node_def, [vector])
+ np.testing.assert_equal(output["output"].dtype, tf_type)
+
+ def test_ceil(self):
+ node_def = helper.make_node("Ceil", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.ceil(x))
+
+ def test_compress(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support Compress.".format(
+ defs.onnx_opset_version()))
+ axis = 1
+ node_def = helper.make_node("Compress",
+ inputs=['X', 'condition'],
+ outputs=['Y'],
+ axis=axis)
+ x = self._get_rnd_float32(shape=[5, 5, 5])
+ cond = np.array([1, 0, 1])
+ output = run_node(node_def, inputs=[x, cond])
+ np.testing.assert_almost_equal(output['Y'], np.compress(cond, x, axis=axis))
+
+ def test_concat(self):
+ shape = [10, 20, 5]
+ for axis in range(len(shape)):
+ node_def = helper.make_node("Concat", ["X1", "X2"], ["Y"], axis=axis)
+ x1 = self._get_rnd_float32(shape=shape)
+ x2 = self._get_rnd_float32(shape=shape)
+ output = run_node(node_def, [x1, x2])
+ np.testing.assert_almost_equal(output["Y"], np.concatenate((x1, x2),
+ axis))
+
+ def test_constant(self):
+ shape = [16, 16]
+ values = np.random.randn(*shape).flatten().astype(float)
+ const2_onnx = helper.make_tensor("const2", TensorProto.DOUBLE, shape,
+ values)
+ node_def = helper.make_node("Constant", [], ["Y"], value=const2_onnx)
+ output = run_node(node_def, [])
+ np.testing.assert_equal(output["Y"].shape, shape)
+ np.testing.assert_almost_equal(output["Y"].flatten(), values)
+
+ # test sparse tensor
+ if not legacy_opset_pre_ver(11):
+ expected = np.array([[1, 0, 0, 0], [0, 0, 2, 0], [0, 0, 0, 0]])
+ x = np.array([[0, 0], [1, 2]]).flatten().astype(np.int64)
+ values = helper.make_tensor("values", TensorProto.INT32, [2], [1, 2])
+ indices = helper.make_tensor("indices", TensorProto.INT64, [2, 2], x)
+ a = helper.make_sparse_tensor(values, indices, [3, 4])
+ node_def = helper.make_node("Constant", [], ["Y"], sparse_value=a)
+ output = run_node(node_def, [])
+ b = tf.sparse_to_dense(output["Y"].indices, output["Y"].dense_shape,
+ output["Y"].values)
+ result = b.eval(session=tf.Session())
+ np.testing.assert_equal(result, expected)
+
+ if not legacy_opset_pre_ver(12):
+ float_attr = 1.0
+ floats_attr = [1.0, 2.0, 3.0]
+ int_attr = np.int64(123)
+ ints_attr = [np.int64(4), np.int64(5), np.int64(6)]
+ string_attr = 'The Cat in the Hat'
+ strings_attr = [
+ 'Green Eggs and Ham', 'How the Grinch Stole Christmas!',
+ 'The Cat in the Hat Comes Back'
+ ]
+ testcases = [(helper.make_node("Constant", [], ["Y"],
+ value_float=float_attr), float_attr),
+ (helper.make_node("Constant", [], ["Y"],
+ value_floats=floats_attr), floats_attr),
+ (helper.make_node("Constant", [], ["Y"],
+ value_int=int_attr), int_attr),
+ (helper.make_node("Constant", [], ["Y"],
+ value_ints=ints_attr), ints_attr),
+ (helper.make_node("Constant", [], ["Y"],
+ value_string=string_attr), string_attr),
+ (helper.make_node("Constant", [], ["Y"],
+ value_strings=strings_attr), strings_attr)]
+ for node_def, expected in testcases:
+ output = run_node(node_def, [])
+ if isinstance(expected, str):
+ np.testing.assert_string_equal(output["Y"].decode('UTF-8'), expected)
+ elif isinstance(expected, list) and isinstance(expected[0], str):
+ for i in range(len(expected)):
+ np.testing.assert_string_equal(output['Y'][i].decode('UTF-8'),
+ expected[i])
+ else:
+ np.testing.assert_equal(output["Y"], expected)
+
+ def test_constant_fill(self):
+ if not legacy_opset_pre_ver(9):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ConstantFill.".format(
+ defs.onnx_opset_version()))
+ shape = [1, 2, 3, 4]
+ extra_shape = [5, 6]
+ value = 3.
+ node_def = helper.make_node(
+ "ConstantFill",
+ ["X"],
+ ["Y"],
+ value=value,
+ extra_shape=extra_shape,
+ dtype=1,
+ )
+ x = self._get_rnd_float32(shape=shape)
+ y = np.zeros(shape + extra_shape)
+ y.fill(value)
+ output = run_node(node_def, [x])
+ np.testing.assert_equal(output["Y"].dtype, tf.float32)
+ np.testing.assert_equal(output["Y"], y)
+
+ def test_constant_of_shape(self):
+ if defs.onnx_opset_version() < 9:
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ConstantOfShape.".format(
+ defs.onnx_opset_version()))
+ v = helper.make_tensor("value", TensorProto.FLOAT, [1], [1])
+ node_def = helper.make_node("ConstantOfShape", ["X"], ["Y"], value=v)
+ x = np.array([4, 3, 2])
+ output = run_node(node_def, inputs=[x])
+ np.testing.assert_almost_equal(output["Y"], np.ones(x, dtype=np.float32))
+ v = helper.make_tensor("value", TensorProto.INT32, [1], [0])
+ node_def = helper.make_node("ConstantOfShape", ["X"], ["Y"], value=v)
+ x = np.array([10, 6])
+ output = run_node(node_def, inputs=[x])
+ np.testing.assert_almost_equal(output["Y"], np.zeros(x, dtype=np.int32))
+
+ def test_conv(self):
+ device = "CUDA" if supports_device("CUDA") else "CPU"
+
+ N, C, H, W = 4, 3, 5, 5
+ x_shape = [N, C, H, W]
+ K, kH, kW = 6, 3, 3
+ weight_shape = [K, C, kH, kW]
+ node_def = helper.make_node("Conv", ["X", "weights"], ["Y"],
+ pads=[1, 1, 1, 1],
+ kernel_shape=[kH, kW])
+
+ x = self._get_rnd_float32(shape=x_shape)
+ weights = self._get_rnd_float32(shape=weight_shape)
+ output = run_node(node_def, [x, weights], device=device)
+
+ out_shape = [N, K, H, W]
+ test_output = np.zeros(out_shape)
+ for n in range(N):
+ for c in range(C):
+ for h in range(H):
+ for w in range(W):
+ for k in range(K):
+ for kh in range(kH):
+ for kw in range(kW):
+ h_in_range = (h - kH // 2 + kh) < H and (h - kH // 2 +
+ kh) >= 0
+ w_in_range = (w - kW // 2 + kw) < W and (w - kW // 2 +
+ kw) >= 0
+ if h_in_range and w_in_range:
+ test_output[n][k][h][w] += (
+ x[n][c][h - kH // 2 + kh][w - kW // 2 + kw] *
+ weights[k][c][kh][kw])
+
+ np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)
+
+ def test_conv_integer(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ConvInteger.".format(
+ defs.onnx_opset_version()))
+
+ # Test w_zero_point
+ x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
+ (1, 1, 3, 3))
+ w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
+ w_zero_point = np.int8(1)
+ y = np.array([16, 20, 28, 32]).astype(np.int32).reshape((1, 1, 2, 2))
+
+ node = helper.make_node("ConvInteger",
+ ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
+ kernel_shape=[2, 2],
+ pads=[0, 0, 0, 0],
+ dilations=[1, 1])
+ output = run_node(node, [x, w, np.int8(0), w_zero_point])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ # Test x_zero_point and w_zero_point
+ x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
+ (1, 1, 3, 3))
+ x_zero_point = np.int8(1)
+ w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
+ w_zero_point = np.int8(1)
+ y = np.array([12, 16, 24, 28]).astype(np.int32).reshape((1, 1, 2, 2))
+
+ node = helper.make_node("ConvInteger",
+ ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
+ kernel_shape=[2, 2],
+ pads=[0, 0, 0, 0],
+ dilations=[1, 1])
+ output = run_node(node, [x, w, x_zero_point, w_zero_point])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ # Test w_zero_point as 1d tensor
+ x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
+ (1, 1, 3, 3))
+ w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
+ w_zero_point = np.array([1]).astype(np.int8)
+ y = np.array([16, 20, 28, 32]).astype(np.int32).reshape((1, 1, 2, 2))
+
+ node = helper.make_node("ConvInteger",
+ ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
+ kernel_shape=[2, 2],
+ pads=[0, 0, 0, 0],
+ dilations=[1, 1])
+ output = run_node(node, [x, w, np.int8(0), w_zero_point])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ # Test w_zero_point as 1d tensor shape 2
+ x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]).astype(np.int8).reshape(
+ (1, 1, 3, 3))
+ w = np.array([2, 2, 2, 2, 2, 2, 2, 2]).astype(np.int8).reshape((2, 1, 2, 2))
+ w_zero_point = np.array([1, 2]).astype(np.int8)
+ y = np.array([12, 16, 24, 28, 0, 0, 0, 0]).astype(np.int32).reshape(
+ (1, 2, 2, 2))
+
+ node = helper.make_node("ConvInteger",
+ ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
+ kernel_shape=[2, 2],
+ pads=[0, 0, 0, 0],
+ dilations=[1, 1])
+ output = run_node(node, [x, w, np.int8(0), w_zero_point])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def test_conv_transpose(self):
+ device = "CUDA" if supports_device("CUDA") else "CPU"
+
+ pads = [1, 1]
+ node_def = helper.make_node("ConvTranspose", ["X", "weights"], ["Y"],
+ pads=pads)
+ x_shape = [1, 3, 4]
+ x = self._get_rnd_float32(shape=x_shape)
+ weight_shape = [3, 5, 2]
+ weights = self._get_rnd_float32(shape=weight_shape)
+ output = run_node(node_def, [x, weights], device=device)
+
+ padh_left = weight_shape[2]-1-pads[0]
+ padh_right = weight_shape[2]-1-pads[1]
+ kh = weight_shape[2]
+ outh = x_shape[2] + padh_right + padh_right - (kh - 1)
+
+ out_shape = [x_shape[0], weight_shape[1], outh]
+
+ test_output = np.zeros(out_shape)
+ for b in range(0, x_shape[0]):
+ for m in range(0, weight_shape[1]):
+ for c in range(0, x_shape[1]):
+ for h in range(0, outh):
+ for k in range(h , h + kh):
+ if (k - padh_left >= 0):
+ test_output[b][m][h] += x[b][c][k-padh_left] * weights[c][m][kh+h-1-k]
+
+ np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)
+
+ # test for spatial dimension of colnolution is 2
+ pads = [1, 1, 1, 1]
+ node_def = helper.make_node("ConvTranspose", ["X", "weights"], ["Y"],
+ pads=pads)
+ x_shape = [1, 3, 4, 6]
+ x = self._get_rnd_float32(shape=x_shape)
+ weight_shape = [3, 5, 2, 2]
+ weights = self._get_rnd_float32(shape=weight_shape)
+ output = run_node(node_def, [x, weights],device=device)
+
+ padh_left = weight_shape[2]-1-pads[0]
+ padh_right = weight_shape[2]-1-pads[1]
+ padw_left = weight_shape[3]-1-pads[2]
+ padw_right = weight_shape[3]-1-pads[3]
+
+ kh = weight_shape[2]
+ kw = weight_shape[3]
+ outh = x_shape[2] + padh_right + padh_right - (kh - 1)
+ outw = x_shape[3] + padw_right + padw_right - (kw - 1)
+
+ out_shape = [x_shape[0], weight_shape[1], outh, outw]
+
+ test_output = np.zeros(out_shape)
+ for b in range(0, x_shape[0]):
+ for m in range(0, weight_shape[1]):
+ for c in range(0, x_shape[1]):
+ for h in range(0, outh):
+ for w in range(0, outw):
+ for k1 in range(h , h + kh):
+ for k2 in range(w , w + kw):
+ if (k1 - padh_left >= 0 and k2 - padw_left >= 0):
+ test_output[b][m][h][w] += x[b][c][k1-padh_left][k2-padw_left] * weights[c][m][kh+h-1-k1][kw+w-1-k2]
+
+ np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)
+
+ def test_cosh(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Cosh.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Cosh", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.cosh(x))
+
+ def test_cumsum(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest("ONNX version {} doesn't support CumSum.".format(
+ defs.onnx_opset_version()))
+ x = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.int32)
+ axis = 0
+ node_def = helper.make_node("CumSum", ["x", "axis"], ["y"])
+ # note: if axis is not provided, np.cumsum() will compute over flattened array,
+ # which is different than the TensorFlow behavior
+ y = np.cumsum(x, axis).astype(np.int32)
+ output = run_node(node_def, [x, axis])
+ np.testing.assert_almost_equal(output["y"], y)
+
+ def test_depth_to_space(self):
+ node_def = helper.make_node("DepthToSpace", ["X"], ["Y"], blocksize=2)
+ x_shape = [1, 12, 1, 1]
+ x = self._get_rnd_float32(shape=x_shape)
+ output = run_node(node_def, [x])
+ x = np.transpose(x, (0, 2, 3, 1))
+ y = np.reshape(np.swapaxes(x.reshape(1, 1, 1, 2, 2, 3), 2, 3), (1, 2, 2, 3))
+ y = np.transpose(y, (0, 3, 1, 2))
+ np.testing.assert_almost_equal(output["Y"], y, decimal=5)
+
+ def test_dequantize_linear(self):
+ node_def = helper.make_node("DequantizeLinear",
+ ["x", "x_scale", "x_zero_point"], ["y"])
+ for x, x_zero_point in [[
+ self._get_rnd_int(-128, 127, [2, 6], np.int8),
+ self._get_rnd_int(-128, 127, dtype=np.int8)
+ ],
+ [
+ self._get_rnd_int(0, 255, [2, 6], np.uint8),
+ self._get_rnd_int(0, 255, dtype=np.uint8)
+ ],
+ [self._get_rnd_int(-512, 512, [2, 6]),
+ np.int32(0)]]:
+ x_scale = self._get_rnd_float32(-10., 10)
+ y = np.subtract(np.float32(x), np.float32(x_zero_point))
+ y = np.multiply(y, x_scale)
+ output = run_node(node_def, [x, x_scale, x_zero_point])
+ np.testing.assert_almost_equal(output["y"], y)
+
+ def test_div(self):
+ node_def = helper.make_node("Div", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[10, 10])
+ y = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.divide(x, y))
+
+ def test_dropout(self):
+ # Since current ONNX only support inference and
+ # dropout at inference mode is a no-op,
+ # therefore dropout is always a no-op operator
+ # in ONNX.
+ node_def = helper.make_node("Dropout", ["X"], ["Y"])
+ if legacy_opset_pre_ver(7):
+ # at inference mode, is_test is always set to 1
+ node_def = helper.make_node("Dropout", ["X"], ["Y"], is_test=1)
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ y = x
+ output = run_node(node_def, [x])
+ np.testing.assert_equal(output["Y"], y)
+
+ def test_dot(self):
+ # this op is removed
+ # remove this test in the future
+ return
+ node_def = helper.make_node("Dot", ["X", "Y"], ["Z"])
+ x = np.floor(self._get_rnd_float32(shape=[10, 10]))
+ y = np.floor(self._get_rnd_float32(shape=[10, 10]))
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.dot(x, y))
+
+ def test_dynamic_quantize_linear(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support DynamicQuantizeLinear.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("DynamicQuantizeLinear", ["X"],
+ ["Y", "Y_Scale", "Y_Zero_Point"])
+ x = self._get_rnd_float32(shape=[3, 4])
+ min_x = np.minimum(0, np.min(x))
+ max_x = np.maximum(0, np.max(x))
+ y_scale = np.float32((max_x - min_x) / (255 - 0)) # uint8 -> [0, 255]
+ y_zero_point = np.clip(round((0 - min_x) / y_scale), 0,
+ 255).astype(np.uint8)
+ y = np.clip(np.round(x / y_scale) + y_zero_point, 0, 255).astype(np.uint8)
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], y)
+ np.testing.assert_almost_equal(output["Y_Scale"], y_scale)
+ np.testing.assert_almost_equal(output["Y_Zero_Point"], y_zero_point)
+
+ def test_elu(self):
+ node_def = helper.make_node("Elu", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[100])
+ output = run_node(node_def, [x])
+ test_output = [self._elu(a) for a in x]
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_equal(self):
+ node_def = helper.make_node("Equal", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[5, 3, 3, 2])
+ y = self._get_rnd_float32(shape=[3, 3, 1])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_equal(output["Z"], np.equal(x,
+ np.reshape(y, [1, 3, 3, 1])))
+
+ def test_erf(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Erf.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Erf", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ exp_output = np.vectorize(math.erf)(x).astype(np.float32)
+ np.testing.assert_allclose(output['Y'], exp_output, rtol=1e-6, atol=1e-6)
+
+ def test_exp(self):
+ node_def = helper.make_node("Exp", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[100])
+ x = x - 3.6
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.exp(x))
+
+ def test_expand(self):
+ node_def = helper.make_node("Expand", ["X", "shape"], ["Y"])
+ x = [[True], [False], [True]]
+ shape = [2, 1, 6]
+ y = x * np.ones(shape, dtype=np.bool)
+ output = run_node(node_def, [x, shape])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def test_eye_like(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support EyeLike.".format(
+ defs.onnx_opset_version()))
+ for shape in [[6, 10], [10, 6]]:
+ for off_diagonal_offset in [-10, -6, -3, 0, 3, 6, 7, 10]:
+ node_def = helper.make_node("EyeLike", ['x'], ['y'],
+ dtype=1,
+ k=off_diagonal_offset)
+ x = self._get_rnd_int(0, 100, shape=shape)
+ y = np.eye(shape[0], shape[1], k=off_diagonal_offset, dtype=np.float32)
+ output = run_node(node_def, [x])
+ np.testing.assert_equal(output['y'], y)
+
+ def test_flatten(self):
+ shape = [10, 2, 3, 4, 5]
+ x = self._get_rnd_float32(shape=shape)
+ for axis in range(-len(shape), len(shape)):
+ node_def = helper.make_node("Flatten", ["X"], ["Y"], axis=axis)
+ output = run_node(node_def, [x])
+ if axis == 0:
+ new_shape = (1, -1)
+ else:
+ new_shape = (np.prod(shape[0:axis]).astype(int), -1)
+ np.testing.assert_almost_equal(output["Y"], np.reshape(x, new_shape))
+
+ def test_gather(self):
+ node_def = helper.make_node("Gather", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[10, 10])
+ y = [[0, 1], [1, 2]]
+ output = run_node(node_def, [x, y])
+ test_output = np.zeros((2, 2, 10))
+ for i in range(0, 2):
+ for j in range(0, 10):
+ test_output[0][i][j] = x[i][j]
+ for i in range(0, 2):
+ for j in range(0, 10):
+ test_output[1][i][j] = x[i + 1][j]
+ np.testing.assert_almost_equal(output["Z"], test_output)
+ if defs.onnx_opset_version() >= 11:
+ # test negative indices
+ y = [[-10, -9], [1, -8]]
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], test_output)
+ # test out of bound indices
+ for y in ([[-10, 11], [1, -8]], [[-10, -11], [1, -8]]):
+ try:
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], test_output)
+ raise AssertionError("Expected ValueError not raised for indices %d" %
+ str(y))
+ except tf.errors.InvalidArgumentError as e:
+ assert 'Gather indices are out of bound' in str(e), str(y)
+ # test non-0 and negative axis
+ axis = -3
+ node_def = helper.make_node("Gather", ["X", "Y"], ["Z"], axis=axis)
+ x = np.reshape(np.arange(5 * 4 * 3 * 2), (5, 4, 3, 2))
+ y = np.array([0, 1, 3])
+ test_output = np.take(x, y, axis=axis)
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], test_output)
+ # test axis attribute validation
+ for axis in [-5, 4, 10]:
+ try:
+ node_def = helper.make_node("Gather", ["X", "Y"], ["Z"], axis=axis)
+ run_node(node_def, [x, y])
+ raise AssertionError(
+ "Expected ValueError not raised for axis value %d" % axis)
+ except ValueError as e:
+ assert 'out of bounds' in str(e), str(e) + ' for axis ' + str(axis)
+
+ def test_gather_elements(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support GatherElements.".format(
+ defs.onnx_opset_version()))
+
+ data_dtype = np.int32
+ data = np.array([
+ [[10, 11], [12, 13], [14, 15], [16, 17], [18, 19]],
+ [[20, 21], [22, 23], [24, 25], [26, 27], [28, 29]],
+ [[30, 31], [32, 33], [34, 35], [36, 37], [38, 39]],
+ [[40, 41], [42, 43], [44, 45], [46, 47], [48, 49]],
+ ],
+ dtype=data_dtype)
+
+ # test default axis
+ indices = np.array([[[3, 0], [2, 0], [1, 0], [0, 0], [3, 1]]],
+ dtype=np.int64)
+ ref_output = np.array([[[40, 11], [32, 13], [24, 15], [16, 17], [48, 29]]],
+ dtype=data_dtype)
+ node_def = helper.make_node("GatherElements", ["data", "indices"],
+ ["outputs"])
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # test non-default axis
+ indices = np.array([[[3, 0], [2, 1], [1, 2], [0, 3]]], dtype=data_dtype)
+ ref_output = np.array([
+ [[16, 11], [14, 13], [12, 15], [10, 17]],
+ ],
+ dtype=data_dtype)
+ node_def = helper.make_node("GatherElements", ["data", "indices"],
+ ["outputs"],
+ axis=1)
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # test negative axis
+ indices = np.array([
+ [[1, 1, -2], [1, -2, 1], [-1, 1, -1], [-2, 1, -2], [-2, 1, 1]],
+ ],
+ dtype=data_dtype)
+ ref_output = np.array([
+ [[11, 11, 10], [13, 12, 13], [15, 15, 15], [16, 17, 16], [18, 19, 19]],
+ ],
+ dtype=data_dtype)
+ node_def = helper.make_node("GatherElements", ["data", "indices"],
+ ["outputs"],
+ axis=2)
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ def test_gather_nd(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support GatherND.".format(
+ defs.onnx_opset_version()))
+
+ # valid positive and negative indices for elements
+ data = np.array([[0, 1], [2, 3]], dtype=np.int64)
+ indices = np.array([[0, 0], [1, 1], [-1, -2]], dtype=np.int64)
+ ref_output = np.array([0, 3, 2], dtype=np.int64)
+ node_def = helper.make_node("GatherND", ["data", "indices"], ["outputs"])
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # valid positive and negative indices for slices
+ data = np.arange(16, dtype=np.int32).reshape([2, 2, 4])
+ indices = np.array([[0, 0], [-1, -2]], dtype=np.int64)
+ ref_output = np.array([[0, 1, 2, 3], [8, 9, 10, 11]], dtype=np.int32)
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+ indices = np.array([[[0, 0]], [[-1, 0]]], dtype=np.int64)
+ ref_output = np.array([[[0, 1, 2, 3]], [[8, 9, 10, 11]]], dtype=np.int32)
+ output = run_node(node_def, [data, indices])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # indices out of bounds
+ indices = np.array([[5, 0], [-1, -3]], dtype=np.int64)
+ with np.testing.assert_raises(tf.errors.InvalidArgumentError):
+ output = run_node(node_def, [data, indices])
+ indices = np.array([[1, 1, 6], [-2, -1, -9]], dtype=np.int32)
+ with np.testing.assert_raises(tf.errors.InvalidArgumentError):
+ output = run_node(node_def, [data, indices])
+
+ def test_gemm(self):
+ # Compute Y = alpha * A * B + beta * C
+ node_def = helper.make_node("Gemm", ["A", "B", "C"], ["Y"],
+ transA=0,
+ transB=0,
+ alpha=1.0,
+ beta=1.0)
+ x = np.floor(self._get_rnd_float32(shape=[10, 10]))
+ y = np.floor(self._get_rnd_float32(shape=[10, 10]))
+ z = np.floor(self._get_rnd_float32(shape=[10, 10]))
+ output = run_node(node_def, [x, y, z])
+ test_output = np.matmul(x, y) + z
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_global_average_pool(self):
+ # Image case: (N x C x H x W), where N is the batch size,
+ # C is the number of channels, and H and W are the height
+ # and the width of the data
+ #
+ # Non-image case: (N x C x D1 x D2 ... Dn)
+ #
+ # Output data tensor from pooling across the input tensor.
+ # Dimensions will be N x C x 1 x 1
+ node_def = helper.make_node("GlobalAveragePool", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[10, 10, 2, 3])
+ output = run_node(node_def, [x])
+ test_output = np.zeros([10, 10, 1, 1])
+ for i1 in range(0, 10):
+ for i2 in range(0, 10):
+ sum = 0
+ for j1 in range(0, 2):
+ for j2 in range(0, 3):
+ sum += x[i1][i2][j1][j2]
+ test_output[i1][i2][0][0] = sum / 6.
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_hardmax(self):
+ shape = [2, 3, 4, 5]
+ x = self._get_rnd_float32(shape=shape)
+ for axis in range(-len(shape), len(shape)):
+ node_def = helper.make_node("Hardmax", ["X"], ["Y"], axis=axis)
+ output = run_node(node_def, [x])
+ shape_in_2d = (np.prod(shape[0:axis]).astype(int),
+ np.prod(shape[axis:len(shape)]))
+ x_in_2d = np.reshape(x, shape_in_2d)
+ y = np.eye(x_in_2d.shape[1], dtype=x.dtype)[np.argmax(x_in_2d, axis=1)]
+ np.testing.assert_almost_equal(output["Y"], np.reshape(y, shape))
+
+ def test_if(self):
+ true_val = helper.make_tensor(name='true_tensor',
+ data_type=TensorProto.INT64,
+ dims=(),
+ vals=[np.int64(1)])
+ false_val = helper.make_tensor(name='false_tensor',
+ data_type=TensorProto.INT64,
+ dims=(),
+ vals=[np.int64(0)])
+ true_node = helper.make_node('Constant',
+ inputs=[],
+ outputs=['true'],
+ value=true_val)
+ false_node = helper.make_node('Constant',
+ inputs=[],
+ outputs=['false'],
+ value=false_val)
+
+ true_out = helper.make_tensor_value_info('true', TensorProto.INT64, [])
+ false_out = helper.make_tensor_value_info('false', TensorProto.INT64, [])
+
+ true_graph = helper.make_graph(nodes=[true_node],
+ name="true_graph",
+ inputs=[],
+ outputs=[true_out])
+ false_graph = helper.make_graph(nodes=[false_node],
+ name="false_graph",
+ inputs=[],
+ outputs=[false_out])
+
+ node_def = helper.make_node('If', ['cond'], ['outputs'],
+ then_branch=true_graph,
+ else_branch=false_graph)
+
+ for cond, exp in [[True, true_val], [False, false_val]]:
+ output = run_node(node_def, [cond])
+ np.testing.assert_equal(output['outputs'], exp.int64_data)
+
+ x = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
+ y = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
+ z = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
+ x_val = helper.make_tensor(name='x_tensor',
+ data_type=TensorProto.INT64,
+ dims=(),
+ vals=[x])
+ y_val = helper.make_tensor(name='y_tensor',
+ data_type=TensorProto.INT64,
+ dims=(),
+ vals=[y])
+ z_val = helper.make_tensor(name='z_tensor',
+ data_type=TensorProto.INT64,
+ dims=(),
+ vals=[z])
+ x_node = helper.make_node('Constant', inputs=[], outputs=['x'], value=x_val)
+ y_node = helper.make_node('Constant', inputs=[], outputs=['y'], value=y_val)
+ z_node = helper.make_node('Constant', inputs=[], outputs=['z'], value=z_val)
+ add_node = helper.make_node('Add', inputs=['x', 'y'], outputs=['sum'])
+ sub_node = helper.make_node('Sub', inputs=['x', 'y'], outputs=['diff'])
+ mul1_node = helper.make_node('Mul', inputs=['sum', 'z'], outputs=['prod1'])
+ mul2_node = helper.make_node('Mul', inputs=['diff', 'z'], outputs=['prod2'])
+
+ x_out = helper.make_tensor_value_info('x', TensorProto.INT64, [])
+ y_out = helper.make_tensor_value_info('y', TensorProto.INT64, [])
+ z_out = helper.make_tensor_value_info('z', TensorProto.INT64, [])
+ sum_out = helper.make_tensor_value_info('sum', TensorProto.INT64, [])
+ diff_out = helper.make_tensor_value_info('diff', TensorProto.INT64, [])
+ prod1_out = helper.make_tensor_value_info('prod1', TensorProto.INT64, [])
+ prod2_out = helper.make_tensor_value_info('prod2', TensorProto.INT64, [])
+
+ true_graph = helper.make_graph(nodes=[add_node, mul1_node],
+ name="true_graph",
+ inputs=[x_out, y_out, z_out],
+ outputs=[sum_out, prod1_out])
+ false_graph = helper.make_graph(nodes=[sub_node, mul2_node],
+ name="false_graph",
+ inputs=[x_out, y_out, z_out],
+ outputs=[diff_out, prod2_out])
+
+ less_node = helper.make_node('Less', inputs=['x', 'y'], outputs=['cond'])
+ if_node = helper.make_node('If',
+ inputs=['cond'],
+ outputs=['result'],
+ then_branch=true_graph,
+ else_branch=false_graph)
+
+ result_out = helper.make_tensor_value_info('result', TensorProto.INT64, [])
+
+ graph = helper.make_graph(
+ nodes=[x_node, y_node, z_node, less_node, if_node],
+ name="test_if",
+ inputs=[],
+ outputs=[result_out])
+
+ tf_rep = onnx_graph_to_tensorflow_rep(graph)
+ output = tf_rep.run({})
+ expected = [x + y, (x + y) * z] if x < y else [x - y, (x - y) * z]
+ np.testing.assert_equal(output['result'], expected)
+
+ def test_image_sacler(self):
+ # Input: (N x C x H x W), where N is the batch size,
+ # C is the number of channels, and H and W are the height
+ # and the width of the data
+ # Scale: (flout, default 1.0) the scale to apply
+ # Bias: applied to each channel, same size as C
+ # Output has same shape and type as input
+ x = self._get_rnd_float32(shape=[1, 3, 224, 224])
+ #random distribution over [0,1), so add 0.1
+ scale = np.random.rand(1)[0] + 0.1
+ bias = np.random.rand(3)
+ node_def = helper.make_node("ImageScaler", ["X"], ["Y"],
+ scale=scale,
+ bias=bias)
+ output = run_node(node_def, [x])
+ test_out = np.multiply(x, scale)
+ test_out = np.transpose(test_out, [0, 2, 3, 1])
+ test_out = np.add(test_out, bias)
+ test_out = np.transpose(test_out, [0, 3, 1, 2])
+ np.testing.assert_almost_equal(output["Y"], test_out)
+
+ def test_is_inf(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest("ONNX version {} doesn't support IsInf.".format(
+ defs.onnx_opset_version()))
+ input = np.array([-1.2, np.nan, np.inf, 2.8, np.NINF, np.inf],
+ dtype=np.float32)
+ expected_output = {
+ "node_def": np.isinf(input),
+ "node_def_neg_false": np.isposinf(input),
+ "node_def_pos_false": np.isneginf(input)
+ }
+ node_defs = {
+ "node_def":
+ helper.make_node("IsInf", ["X"], ["Y"]),
+ "node_def_neg_false":
+ helper.make_node("IsInf", ["X"], ["Y"], detect_negative=0),
+ "node_def_pos_false":
+ helper.make_node("IsInf", ["X"], ["Y"], detect_positive=0)
+ }
+ for key in node_defs:
+ output = run_node(node_defs[key], [input])
+ np.testing.assert_equal(output["Y"], expected_output[key])
+
+ def test_isnan(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support IsNaN.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("IsNaN", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 3])
+ x[0][1] = x[1][0] = x[2][2] = np.nan
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.isnan(x))
+
+ def test_global_lp_pool(self):
+ # Image case: (N x C x H x W), where N is the batch size,
+ # C is the number of channels, and H and W are the height
+ # and the width of the data
+ #
+ # Non-image case: (N x C x D1 x D2 ... Dn)
+ #
+ # Output data tensor from pooling across the input tensor.
+ # Dimensions will be N x C x 1 x 1
+ node_def = helper.make_node("GlobalLpPool", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[10, 10, 2, 3])
+ output = run_node(node_def, [x])
+ test_output = np.zeros([10, 10, 1, 1])
+ for i1 in range(0, 10):
+ for i2 in range(0, 10):
+ tmp = np.zeros([2, 3])
+ for j1 in range(0, 2):
+ for j2 in range(0, 3):
+ tmp[j1][j2] = x[i1][i2][j1][j2]
+ test_output[i1][i2][0][0] = np.linalg.norm(tmp)
+ np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)
+
+ def test_global_max_pool(self):
+ # Image case: (N x C x H x W), where N is the batch size,
+ # C is the number of channels, and H and W are the height
+ # and the width of the data
+ #
+ # Non-image case: (N x C x D1 x D2 ... Dn)
+ #
+ # Output data tensor from pooling across the input tensor.
+ # Dimensions will be N x C x 1 x 1
+ node_def = helper.make_node("GlobalMaxPool", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[10, 10, 2, 3])
+ output = run_node(node_def, [x])
+ test_output = np.zeros([10, 10, 1, 1])
+ for i1 in range(0, 10):
+ for i2 in range(0, 10):
+ max = x[i1][i2][0][0]
+ for j1 in range(0, 2):
+ for j2 in range(0, 3):
+ if max < x[i1][i2][j1][j2]:
+ max = x[i1][i2][j1][j2]
+ test_output[i1][i2][0][0] = max
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_less(self):
+ node_def = helper.make_node("Less", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[5, 3, 3, 2])
+ y = self._get_rnd_float32(shape=[3, 3, 1])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_equal(output["Z"], np.less(x, np.reshape(y,
+ [1, 3, 3, 1])))
+
+ def test_lp_normalization(self):
+ for ordr in range(1, 3):
+ node_def = helper.make_node("LpNormalization", ["X"], ["Y"], p=ordr)
+ x = self._get_rnd_float32(shape=[2, 2, 3, 2])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(
+ output["Y"],
+ x / np.expand_dims(np.linalg.norm(x, axis=-1, ord=ordr), -1),
+ rtol=1e-3)
+
+ def test_l_r_n(self):
+ # Each input value is divided by:
+ #
+ # (bias+(alpha/size)*sum(xi^2 for every xi in the local region))^beta
+ alpha = 2.0
+ beta = 1.0
+ bias = 5.0
+ size = 3
+ node_def = helper.make_node("LRN", ["X"], ["Y"],
+ alpha=alpha,
+ beta=beta,
+ bias=bias,
+ size=size)
+ x = self._get_rnd_float32(shape=[10, 2, 10, 10])
+ output = run_node(node_def, [x])
+ test_output = np.zeros([10, 10, 10, 2])
+ x = np.transpose(x, axes=[0, 2, 3, 1])
+ for i1 in range(0, 10):
+ for i2 in range(0, 10):
+ for j1 in range(0, 10):
+ for j2 in range(0, 2):
+ sqr_sum = 0.
+ # size of 3 means radius 1 in TF speak
+ # i.e. the immediate neighbouring values
+ # if "previous" neighbour exists
+ if j2 > 0:
+ sqr_sum += x[i1][i2][j1][j2 - 1] * x[i1][i2][j1][j2 - 1]
+ # current value
+ sqr_sum += x[i1][i2][j1][j2] * x[i1][i2][j1][j2]
+ # if "next" neighbour exists
+ if j2 < 2 - 1:
+ sqr_sum += x[i1][i2][j1][j2 + 1] * x[i1][i2][j1][j2 + 1]
+ test_output[i1][i2][j1][j2] = \
+ x[i1][i2][j1][j2] / ((bias + (alpha * 1. / size) * sqr_sum) ** beta)
+ test_output = np.transpose(test_output, axes=[0, 3, 1, 2])
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_floor(self):
+ node_def = helper.make_node("Floor", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[100])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.floor(x))
+
+ def test_leakyrelu(self):
+ node_def = helper.make_node("LeakyRelu", ["X"], ["Y"], alpha=0.8)
+ x = np.floor(self._get_rnd_float32(shape=[100]))
+ output = run_node(node_def, [x])
+ test_output = [self._leaky_relu(a, 0.8) for a in x]
+ np.testing.assert_almost_equal(output["Y"], test_output)
+
+ def test_log(self):
+ node_def = helper.make_node("Log", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[100])
+ x = x + 3.6
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.log(x))
+
+ def test_loop(self):
+ add1_node = helper.make_node('Add', inputs=['x', 'x'], outputs=['sum1'])
+ neg_node = helper.make_node('Neg', inputs=['sum1'], outputs=['neg_sum1'])
+ add2_node = helper.make_node('Add',
+ inputs=['y', 'neg_sum1'],
+ outputs=['sum2'])
+ add3_node = helper.make_node('Add',
+ inputs=['sum1', 'sum2'],
+ outputs=['sum3'])
+ less_node = helper.make_node('Less',
+ inputs=['sum1', 'sum2'],
+ outputs=['new_cond'])
+ greater_node = helper.make_node('Greater',
+ inputs=['sum1', 'sum2'],
+ outputs=['new_cond'])
+
+ m_in = helper.make_tensor_value_info('M', TensorProto.INT64, [])
+ cond_in = helper.make_tensor_value_info('cond', TensorProto.BOOL, [])
+ cond_int_in = helper.make_tensor_value_info('cond', TensorProto.INT32, [])
+ x_in = helper.make_tensor_value_info('x', TensorProto.INT32, [None])
+ y_in = helper.make_tensor_value_info('y', TensorProto.INT32, [None])
+
+ cond_out = helper.make_tensor_value_info('cond', TensorProto.BOOL, [])
+ new_cond_out = helper.make_tensor_value_info('new_cond', TensorProto.BOOL,
+ [])
+ sum1_out = helper.make_tensor_value_info('sum1', TensorProto.INT32, [None])
+ sum2_out = helper.make_tensor_value_info('sum2', TensorProto.INT32, [None])
+ sum3_out = helper.make_tensor_value_info('sum3', TensorProto.INT32, [None])
+
+ v1_initial = np.array([1, 1], dtype=np.int32)
+ v2_initial = np.array([100, 100], dtype=np.int32)
+
+ # test for loop
+ M = np.int64(10)
+ cond = True # value will be ignore because optional "cond" input will be skip
+ graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, add3_node],
+ name="for_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([1024, 1024], dtype=np.int32),
+ np.array([-1946, -1946], dtype=np.int32)
+ ]
+ scan_outputs = [
+ np.array([[100, 100], [98, 98], [94, 94], [86, 86], [70, 70], [38, 38],
+ [-26, -26], [-154, -154], [-410, -410], [-922, -922]],
+ dtype=np.int32)
+ ]
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test while loop
+ M = 0 # value will be ignore because optional "M" input will be skip
+ cond = v1_initial < v2_initial
+ graph = helper.make_graph(
+ nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
+ name="while_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop',
+ ['', 'cond', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([64, 64], dtype=np.int32),
+ np.array([-26, -26], dtype=np.int32)
+ ]
+ scan_outputs = [
+ np.array([[100, 100], [98, 98], [94, 94], [86, 86], [70, 70], [38, 38]],
+ dtype=np.int32)
+ ]
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test do-while loop
+ M = 0 # value will be ignore because optional "M" input will be skip
+ cond = 1
+ graph = helper.make_graph(
+ nodes=[add1_node, neg_node, add2_node, add3_node, greater_node],
+ name="do_while_loop_graph",
+ inputs=[m_in, cond_int_in, x_in, y_in],
+ outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop',
+ ['', 'cond', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([2, 2], dtype=np.int32),
+ np.array([98, 98], dtype=np.int32)
+ ]
+ scan_outputs = [np.array([[100, 100]], dtype=np.int32)]
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test for loop and while loop conbine
+ M = np.int64(4)
+ cond = v1_initial < v2_initial
+ graph = helper.make_graph(
+ nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
+ name="for_and_while_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop',
+ ['M', 'cond', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([16, 16], dtype=np.int32),
+ np.array([70, 70], dtype=np.int32)
+ ]
+ scan_outputs = [
+ np.array([[100, 100], [98, 98], [94, 94], [86, 86]], dtype=np.int32)
+ ]
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test for loop that doesn't run at all (M = 0)
+ M = np.int64(0)
+ cond = True # value will be ignore because optional "cond" input will be skip
+ graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, add3_node],
+ name="for_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([1, 1], dtype=np.int32),
+ np.array([100, 100], dtype=np.int32)
+ ]
+ scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2)
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test while loop that doesn't run at all (cond = False)
+ M = 0 # value will be ignore because optional "M" input will be skip
+ cond = False
+ graph = helper.make_graph(
+ nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
+ name="while_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop',
+ ['', 'cond', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([1, 1], dtype=np.int32),
+ np.array([100, 100], dtype=np.int32)
+ ]
+ scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2)
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # test while loop that doesn't have any scan_outputs
+ M = np.int64(4)
+ cond = v1_initial < v2_initial
+ graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, less_node],
+ name="while_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[new_cond_out, sum1_out, sum2_out])
+ node_def = helper.make_node('Loop',
+ ['M', 'cond', 'v1_initial', 'v2_initial'],
+ ['v_final'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ v_final = [
+ np.array([16, 16], dtype=np.int32),
+ np.array([70, 70], dtype=np.int32)
+ ]
+ np.testing.assert_almost_equal(output['v_final'], v_final)
+
+ # test for loop that doesn't run at all (M = 0)
+ # and the scan_outputs shape is not the same as the inputs
+ v1_initial = np.array([[1, 1, 1], [2, 2, 2]], dtype=np.int32)
+ v3_initial = np.array([[1, 1], [2, 2], [3, 3]], dtype=np.int32)
+ matmul_node = helper.make_node('MatMul',
+ inputs=['x', 'z'],
+ outputs=['product'])
+ x_in = helper.make_tensor_value_info('x', TensorProto.INT32, [None, None])
+ z_in = helper.make_tensor_value_info('z', TensorProto.INT32, [None, None])
+ sum1_out = helper.make_tensor_value_info('sum1', TensorProto.INT32,
+ [None, None])
+ z_out = helper.make_tensor_value_info('z', TensorProto.INT32, [None, None])
+ product_out = helper.make_tensor_value_info('product', TensorProto.INT32,
+ [None, None])
+
+ M = np.int64(0)
+ cond = True # value will be ignore because optional "cond" input will be skip
+ graph = helper.make_graph(nodes=[add1_node, matmul_node],
+ name="for_loop_graph",
+ inputs=[m_in, cond_in, x_in, z_in],
+ outputs=[cond_out, sum1_out, z_out, product_out])
+ node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v3_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ output = run_node(node_def, [M, cond, v1_initial, v3_initial])
+ v_final = [v1_initial, v3_initial]
+ scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2, 2)
+ for i in range(len(output['v_final'])):
+ np.testing.assert_almost_equal(output['v_final'][i], v_final[i])
+ np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)
+
+ # verify infinite loop will get exception
+ M = 0 # value will be ignore because optional "M" input will be skip
+ cond = True # value will be ignore because optional "cond" input will be skip
+ graph = helper.make_graph(
+ nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
+ name="while_loop_graph",
+ inputs=[m_in, cond_in, x_in, y_in],
+ outputs=[cond_out, sum1_out, sum2_out, sum3_out])
+ node_def = helper.make_node('Loop', ['', '', 'v1_initial', 'v2_initial'],
+ ['v_final', 'scan_outputs'],
+ body=graph)
+ try:
+ output = run_node(node_def, [M, cond, v1_initial, v2_initial])
+ raise AssertionError("Expected RuntimeError not raise when Loop inputs " +
+ "M and cond are both not set at the same time")
+ except RuntimeError as e:
+ assert "M and cond in Loop are not set" in str(e)
+
+ def test_matmul_integer(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support MatMulInteger.".format(
+ defs.onnx_opset_version()))
+
+ node_def = helper.make_node("MatMulInteger",
+ ["A", "B", "a_zero_point", "b_zero_point"],
+ ["Z"])
+ lower_bound = {np.uint8: 0, np.int8: -20}
+ for dtype in [np.uint8, np.int8]:
+ # A & B are 3-D tensor and a_zero_point & b_zero_point are scalar
+ A = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 3, 4),
+ dtype=dtype)
+ B = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 4, 6),
+ dtype=dtype)
+ a_zero_point = self._get_rnd_int(lower_bound[dtype], 20, dtype=dtype)
+ b_zero_point = self._get_rnd_int(lower_bound[dtype], 20, dtype=dtype)
+ A_minus_zero_point = np.subtract(A.astype(np.int32),
+ a_zero_point.astype(np.int32))
+ B_minus_zero_point = np.subtract(B.astype(np.int32),
+ b_zero_point.astype(np.int32))
+ z = np.matmul(A_minus_zero_point, B_minus_zero_point)
+ output = run_node(node_def, [A, B, a_zero_point, b_zero_point])
+ np.testing.assert_almost_equal(output["Z"], z)
+ # A & B are 4-D tensor and a_zero_point & b_zero_point are 1-D tensor
+ A = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 5, 3, 4),
+ dtype=dtype)
+ B = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 1, 4, 6),
+ dtype=dtype)
+ a_zero_point = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(A.shape[-2]),
+ dtype=dtype)
+ b_zero_point = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(B.shape[-1]),
+ dtype=dtype)
+ a_zero_point_with_reshape = np.reshape(a_zero_point, [A.shape[-2], 1])
+ A_minus_zero_point = np.subtract(
+ A.astype(np.int32), a_zero_point_with_reshape.astype(np.int32))
+ B_minus_zero_point = np.subtract(B.astype(np.int32),
+ b_zero_point.astype(np.int32))
+ z = np.matmul(A_minus_zero_point, B_minus_zero_point)
+ output = run_node(node_def, [A, B, a_zero_point, b_zero_point])
+ np.testing.assert_almost_equal(output["Z"], z)
+
+ node_def = helper.make_node("MatMulInteger", ["A", "B"], ["Z"])
+ for dtype in [np.uint8, np.int8]:
+ # A & B are 3-D tensor
+ A = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 3, 4),
+ dtype=dtype)
+ B = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 4, 6),
+ dtype=dtype)
+ z = np.matmul(A.astype(np.int32), B.astype(np.int32))
+ output = run_node(node_def, [A, B])
+ np.testing.assert_almost_equal(output["Z"], z)
+ # A & B are 4-D tensor
+ A = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 5, 3, 4),
+ dtype=dtype)
+ B = self._get_rnd_int(lower_bound[dtype],
+ 20,
+ shape=(2, 1, 4, 6),
+ dtype=dtype)
+ z = np.matmul(A.astype(np.int32), B.astype(np.int32))
+ output = run_node(node_def, [A, B])
+ np.testing.assert_almost_equal(output["Z"], z)
+
+ def test_max(self):
+ node_def = helper.make_node("Max", ["X1", "X2", "X3", "X4"], ["Z"])
+ x1 = self._get_rnd_float32(shape=[10, 10])
+ x2 = self._get_rnd_float32(shape=[10, 10])
+ x3 = self._get_rnd_float32(shape=[10, 10])
+ x4 = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [x1, x2, x3, x4])
+ test_output = np.maximum(np.maximum(np.maximum(x1, x2), x3), x4)
+ np.testing.assert_almost_equal(output["Z"], test_output)
+
+ def _test_pooling(self,
+ input_shape,
+ kernel_shape,
+ strides=None,
+ dilations=None,
+ pads=None,
+ auto_pad=None,
+ ceil_mode=None,
+ count_include_pad=None,
+ pooling_type="MAX",
+ input_dtype=np.float32,
+ p=None):
+
+ op = "MaxPool" if pooling_type.upper().startswith("MAX") else \
+ "AveragePool" if pooling_type.upper() == "AVG" else "LpPool"
+ node_def_kwargs = {
+ "op_type": op,
+ "inputs": ["X"],
+ "outputs": ["Y"],
+ "kernel_shape": kernel_shape
+ }
+
+ if strides is not None:
+ node_def_kwargs["strides"] = strides
+ if dilations is not None:
+ node_def_kwargs["dilations"] = dilations
+ if pads is not None:
+ node_def_kwargs["pads"] = pads
+ if auto_pad is not None:
+ node_def_kwargs["auto_pad"] = auto_pad
+ pads = auto_pad
+ if ceil_mode is not None:
+ node_def_kwargs["ceil_mode"] = ceil_mode
+ else:
+ ceil_mode = 0
+ if count_include_pad is not None:
+ node_def_kwargs["count_include_pad"] = count_include_pad
+ if p is not None:
+ node_def_kwargs["p"] = p
+
+ node_def = helper.make_node(**node_def_kwargs)
+
+ if input_dtype == np.float32:
+ x = self._get_rnd_float32(shape=input_shape)
+ else:
+ x = self._get_rnd_int(low = np.iinfo(input_dtype).min,
+ high = np.iinfo(input_dtype).max,
+ shape=input_shape, dtype=input_dtype)
+
+ x = self._get_rnd_float32(shape=input_shape)
+ output = run_node(node_def, [x])
+
+ test_output = py_pool(x,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ padding=pads,
+ ceil_mode=ceil_mode,
+ pooling_type=pooling_type,
+ include_indices=False,
+ p=p)
+
+ np.testing.assert_almost_equal(output["Y"], test_output,
+ decimal=5 if pooling_type=="LP" else 7)
+
+ def test_max_pool_2d(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+
+ input_shape = [10, 10, 4, 4]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides)
+
+ def test_max_pool_2d_same_lower(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+ auto_pad = "SAME_LOWER"
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad)
+
+ def test_max_pool_2d_ceil_same_lower(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [2, 1]
+ strides = [1, 2]
+ auto_pad = "SAME_LOWER"
+ ceil_mode = 1
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_2d_same_upper(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+ auto_pad = "SAME_UPPER"
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad)
+
+ def test_max_pool_2d_ceil(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_2d_dilations(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ node_def = helper.make_node("MaxPool", ["X"], ["Y"],
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations)
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations)
+
+ def test_max_pool_2d_dilations_ceil(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations nor ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_2d_dilations_pads(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ pads = [1, 1, 2, 2]
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ pads=pads)
+
+ def test_max_pool_2d_dilations_ceil_pads(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations nor ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ pads = [1, 1, 2, 2]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ pads=pads,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_2d_dilations_same_lower(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ auto_pad = "SAME_LOWER"
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ auto_pad=auto_pad)
+
+ def test_max_pool_2d_dilations_same_upper(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [2, 3]
+ strides = [4, 2]
+ dilations = [3, 5]
+ auto_pad = "SAME_UPPER"
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ auto_pad=auto_pad)
+
+ def test_max_pool_2d_dilations_ceil_pads_int8(self):
+ if legacy_opset_pre_ver(12):
+ raise unittest.SkipTest(
+ "ONNX version {} does not support int8 input type.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ pads = [1, 1, 2, 2]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 23, 23]
+ self._test_pooling(input_shape=input_shape, kernel_shape=kernel_shape,
+ strides=strides, dilations=dilations, pads=pads,
+ ceil_mode=ceil_mode, input_dtype=np.int8)
+
+ def test_max_pool_3d(self):
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides)
+
+ def test_max_pool_3d_dilations_ceil_pads(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations nor ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+ dilations = [3, 3, 3]
+ pads = [1, 1, 2, 2, 1, 1]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ pads=pads,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_3d_dilations_same_lower(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 1, 2]
+ strides = [2, 2, 1]
+ dilations = [3, 2, 5]
+ auto_pad = "SAME_LOWER"
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ auto_pad=auto_pad)
+
+ def test_max_pool_1d_dilations_ceil_pads(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations nor ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3]
+ strides = [2]
+ dilations = [3]
+ pads = [1, 2]
+ ceil_mode = 1
+
+ input_shape = [10, 3, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ pads=pads,
+ ceil_mode=ceil_mode)
+
+ def test_max_pool_1d(self):
+ kernel_shape = [3]
+ strides = [2]
+
+ input_shape = [10, 3, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides)
+
+ def test_max_pool_with_argmax_2d_dilations_ceil_pads(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support dilations nor ceil mode.".format(
+ defs.onnx_opset_version()))
+
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ dilations = [3, 3]
+ pads = [1, 1, 2, 2]
+ ceil_mode = True
+ node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ pads=pads,
+ ceil_mode=ceil_mode)
+
+ input_shape = [10, 1, 23, 23]
+ x = self._get_rnd_float32(shape=input_shape) - 2
+ output = run_node(node_def, [x])
+
+ test_output, test_ind = py_pool(x,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ dilations=dilations,
+ padding=pads,
+ ceil_mode=ceil_mode,
+ pooling_type="MAX")
+
+ np.testing.assert_almost_equal(output["Y"], test_output)
+ np.testing.assert_almost_equal(output["Ind"], test_ind)
+
+ def test_max_pool_with_argmax_3d(self):
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+ node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
+ kernel_shape=kernel_shape,
+ strides=strides)
+
+ input_shape = [10, 1, 23, 23, 23]
+ x = self._get_rnd_float32(shape=input_shape)
+ self.assertRaises(RuntimeError, run_node, node_def, [x])
+
+ def test_max_pool_4d(self):
+ kernel_shape = [3, 3, 3, 3]
+ strides = [2, 2, 2, 2]
+ node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
+ kernel_shape=kernel_shape,
+ strides=strides)
+
+ input_shape = [1, 1, 4, 4, 4, 4]
+ x = self._get_rnd_float32(shape=input_shape)
+ self.assertRaises(RuntimeError, run_node, node_def, [x])
+
+ def test_max_unpool(self):
+ input_shape = [10, 10, 4, 4]
+ x = self._get_rnd_float32(shape=input_shape)
+
+ X = helper.make_tensor_value_info('X', TensorProto.FLOAT, input_shape)
+ Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, input_shape)
+
+ node_def = helper.make_node("MaxPool", ["X"], ["Pool", "Indices"],
+ kernel_shape=[2, 2],
+ strides=[2, 2])
+ output_pool = run_node(node_def, [x])
+
+ node_def = helper.make_node("MaxUnpool", ["Pool", "Indices"], ["Y"],
+ kernel_shape=[2, 2],
+ strides=[2, 2])
+ output_unpool = run_node(node_def,
+ [output_pool["Pool"], output_pool["Indices"]])
+
+ test_output = np.zeros(input_shape)
+ for i1 in range(0, input_shape[0]):
+ for i2 in range(0, input_shape[1]):
+ for i3 in range(0, input_shape[2], 2):
+ for i4 in range(0, input_shape[3], 2):
+ max_val = float('-inf')
+ for j1 in range(i3, i3 + 2):
+ for j2 in range(i4, i4 + 2):
+ if x[i1][i2][j1][j2] > max_val:
+ max_val = x[i1][i2][j1][j2]
+ max_ind = (j1, j2)
+ j1, j2 = max_ind
+ test_output[i1][i2][j1][j2] = max_val
+ np.testing.assert_almost_equal(output_unpool["Y"], test_output)
+
+ def test_average_pool_1d(self):
+ kernel_shape = [3]
+ strides = [2]
+
+ input_shape = [10, 3, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="AVG")
+
+ def test_average_pool_2d(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+
+ input_shape = [10, 10, 4, 4]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="AVG")
+
+ def test_average_pool_2d_same_upper(self):
+ kernel_shape=[1, 2]
+ strides=[1, 2]
+ auto_pad="SAME_UPPER"
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad,
+ pooling_type="AVG")
+
+ def test_average_pool_3d(self):
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="AVG")
+
+ def test_lp2_pool_2d(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+ p = 2
+
+ input_shape = [10, 10, 4, 4]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp2_pool_2d_same_lower(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+ p = 2
+ auto_pad = "SAME_LOWER"
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp2_pool_2d_same_upper(self):
+ kernel_shape = [1, 2]
+ strides = [1, 2]
+ p = 2
+ auto_pad = "SAME_UPPER"
+
+ input_shape = [10, 10, 7, 7]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ auto_pad=auto_pad,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp2_pool_2d_pads(self):
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ p = 2
+ pads = [1, 1, 2, 2]
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pads=pads,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp2_pool_3d(self):
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+ p = 2
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp2_pool_1d(self):
+ kernel_shape = [3]
+ strides = [2]
+ p = 2
+
+ input_shape = [10, 3, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp3_pool_2d_pads(self):
+ kernel_shape = [3, 3]
+ strides = [2, 2]
+ p = 3
+ pads = [1, 1, 2, 2]
+
+ input_shape = [10, 3, 24, 24]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pads=pads,
+ pooling_type="LP",
+ p=p)
+
+ def test_lp3_pool_3d(self):
+ kernel_shape = [3, 3, 3]
+ strides = [2, 2, 2]
+ p = 3
+
+ input_shape = [10, 3, 23, 23, 23]
+ self._test_pooling(input_shape=input_shape,
+ kernel_shape=kernel_shape,
+ strides=strides,
+ pooling_type="LP",
+ p=p)
+
+ def test_mean_variance_normalization(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't have test for MeanVarianceNormalization".
+ format(defs.onnx_opset_version()))
+
+ input_data = self._get_rnd_float32(shape=[2, 2, 2, 2])
+ # Calculate expected output data using formula:
+ # (Input - Mean)/SD
+ mean = np.mean(input_data, keepdims=1, axis=(0, 2, 3))
+ std = np.std(input_data, keepdims=1, axis=(0, 2, 3))
+ expected_output = (input_data - mean) / std
+ # Testing without "axes" argument should default to axes=[0,2,3]
+ node_def = helper.make_node("MeanVarianceNormalization", ["X"], ["Y"])
+ output = run_node(node_def, [input_data])
+ np.testing.assert_almost_equal(output["Y"], expected_output, decimal=5)
+
+ def test_min(self):
+ node_def = helper.make_node("Min", ["X1", "X2", "X3", "X4"], ["Z"])
+ x1 = self._get_rnd_float32(shape=[10, 10])
+ x2 = self._get_rnd_float32(shape=[10, 10])
+ x3 = self._get_rnd_float32(shape=[10, 10])
+ x4 = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [x1, x2, x3, x4])
+ test_output = np.minimum(np.minimum(np.minimum(x1, x2), x3), x4)
+ np.testing.assert_almost_equal(output["Z"], test_output)
+
+ def test_mul(self):
+ node_def = helper.make_node("Mul", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[5, 10, 5, 5])
+ y = self._get_rnd_float32(shape=[10, 1, 1])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"],
+ np.multiply(x, y.reshape([1, 10, 1, 1])))
+
+ def test_mod(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest("ONNX version {} doesn't support Mod.".format(
+ defs.onnx_opset_version()))
+ x = self._get_rnd_float32(shape=[5, 5])
+ y = self._get_rnd_float32(shape=[5, 5])
+ node_def = helper.make_node("Mod", ["X", "Y"], ["Z"], fmod=0)
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.mod(x, y))
+ node_def = helper.make_node("Mod", ["X", "Y"], ["Z"], fmod=1)
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.fmod(x, y))
+
+ def test_neg(self):
+ node_def = helper.make_node("Neg", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.negative(x))
+
+ def test_non_zero(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support NonZero.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("NonZero", ["x"], ["y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ y = np.array(np.nonzero(x))
+ output = run_node(node_def, [x])
+ np.testing.assert_equal(output["y"], y)
+
+ def test_onehot(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support OneHot.".format(
+ defs.onnx_opset_version()))
+ indices = np.array([[0, 2], [1, 2], [0, 1]])
+ depth = np.int32(5)
+ on_value = 6.0
+ off_value = 2.0
+ values = np.array([off_value, on_value])
+ node_def = helper.make_node('OneHot',
+ inputs=['indices', 'depth', 'values'],
+ outputs=['y'],
+ axis=-1)
+ y = (np.arange(depth) == indices[..., None]).astype(int)
+ y = y * (on_value - off_value) + off_value
+ output = run_node(node_def, inputs=[indices, depth, values])
+ np.testing.assert_equal(output['y'], y)
+
+ def test_range(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest("ONNX version {} doesn't support Range.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Range", ['start', 'limit', 'delta'], ['y'])
+ # test positive_delta
+ start = self._get_rnd_int(low=0, high=3)
+ limit = self._get_rnd_int(low=10, high=30)
+ delta = np.int32(3)
+ output = run_node(node_def, [start, limit, delta])
+ np.testing.assert_equal(output['y'], range(start, limit, delta))
+ # test negative_delta
+ start = self._get_rnd_int(low=20, high=30)
+ limit = self._get_rnd_int(low=1, high=5)
+ delta = np.int32(-2)
+ output = run_node(node_def, [start, limit, delta])
+ np.testing.assert_equal(output['y'], range(start, limit, delta))
+
+ def test_resize(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support Resize with attributes: " +
+ "coordinate_transformation_mode, cubic_coeff_a, exclude_outside, " +
+ "extrapolation_value, nearest_mode and inputs: roi and sizes".format(
+ defs.onnx_opset_version()))
+ data = np.reshape(np.arange(1, 101, dtype=np.float32), [1, 1, 10, 10])
+ roi = np.array([], dtype=np.float32)
+
+ # resize_nearest_round_prefer_ceil_align_corners_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode='align_corners',
+ mode='nearest',
+ nearest_mode='round_prefer_ceil')
+ scales = np.array([1, 1, 0.9, 0.9], dtype=np.float32)
+ expected = np.array(
+ [[[[1, 2, 3, 4, 6, 7, 8, 9, 10], [11, 12, 13, 14, 16, 17, 18, 19, 20],
+ [21, 22, 23, 24, 26, 27, 28, 29, 30],
+ [31, 32, 33, 34, 36, 37, 38, 39, 40],
+ [51, 52, 53, 54, 56, 57, 58, 59, 60],
+ [61, 62, 63, 64, 66, 67, 68, 69, 70],
+ [71, 72, 73, 74, 76, 77, 78, 79, 80],
+ [81, 82, 83, 84, 86, 87, 88, 89, 90],
+ [91, 92, 93, 94, 96, 97, 98, 99, 100]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_nearest_round_prefer_ceil_align_corners_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode='align_corners',
+ mode='nearest',
+ nearest_mode='round_prefer_ceil')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1, 3, 4, 6, 7, 9, 10], [21, 23, 24, 26, 27, 29, 30],
+ [31, 33, 34, 36, 37, 39, 40], [51, 53, 54, 56, 57, 59, 60],
+ [61, 63, 64, 66, 67, 69, 70], [81, 83, 84, 86, 87, 89, 90],
+ [91, 93, 94, 96, 97, 99, 100]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_nearest_floor_asymmetric_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode='asymmetric',
+ mode='nearest',
+ nearest_mode='floor')
+ scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[1, 2, 3, 4, 6, 7, 8, 9], [11, 12, 13, 14, 16, 17, 18, 19],
+ [21, 22, 23, 24, 26, 27, 28, 29], [31, 32, 33, 34, 36, 37, 38, 39],
+ [51, 52, 53, 54, 56, 57, 58, 59], [61, 62, 63, 64, 66, 67, 68, 69],
+ [71, 72, 73, 74, 76, 77, 78, 79], [81, 82, 83, 84, 86, 87, 88, 89]]]
+ ],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_nearest_floor_asymmetric_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode='asymmetric',
+ mode='nearest',
+ nearest_mode='floor')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1, 2, 3, 5, 6, 8, 9], [11, 12, 13, 15, 16, 18, 19],
+ [21, 22, 23, 25, 26, 28, 29], [41, 42, 43, 45, 46, 48, 49],
+ [51, 52, 53, 55, 56, 58, 59], [71, 72, 73, 75, 76, 78, 79],
+ [81, 82, 83, 85, 86, 88, 89]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_nearest_floor_half_pixel_scales
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode='tf_half_pixel_for_nn',
+ mode='nearest',
+ nearest_mode='floor')
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[1, 2, 4, 5, 6, 7, 9, 10], [11, 12, 14, 15, 16, 17, 19, 20],
+ [31, 32, 34, 35, 36, 37, 39, 40], [41, 42, 44, 45, 46, 47, 49, 50],
+ [51, 52, 54, 55, 56, 57, 59, 60], [61, 62, 64, 65, 66, 67, 69, 70],
+ [81, 82, 84, 85, 86, 87, 89, 90], [91, 92, 94, 95, 96, 97, 99, 100]]]
+ ],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_nearest_floor_half_pixel_sizes
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode='tf_half_pixel_for_nn',
+ mode='nearest',
+ nearest_mode='floor')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1, 3, 4, 6, 7, 8, 10], [21, 23, 24, 26, 27, 28, 30],
+ [31, 33, 34, 36, 37, 38, 40], [51, 53, 54, 56, 57, 58, 60],
+ [61, 63, 64, 66, 67, 68, 70], [71, 73, 74, 76, 77, 78, 80],
+ [91, 93, 94, 96, 97, 98, 100]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_linear_align_corners_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode="align_corners",
+ mode='linear')
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[
+ 1., 2.2857141, 3.5714285, 4.857143, 6.142857, 7.428571, 8.714286,
+ 10.
+ ],
+ [
+ 13.857142, 15.142857, 16.428572, 17.714287, 19., 20.285715,
+ 21.571428, 22.857143
+ ],
+ [
+ 26.714287, 28., 29.285713, 30.571426, 31.857141, 33.142857,
+ 34.428574, 35.714283
+ ],
+ [
+ 39.57143, 40.857143, 42.14286, 43.428574, 44.714287, 46.,
+ 47.285717, 48.57143
+ ],
+ [
+ 52.428574, 53.714283, 55., 56.285713, 57.571426, 58.857143,
+ 60.142857, 61.428566
+ ],
+ [
+ 65.28571, 66.57143, 67.85714, 69.14285, 70.428566, 71.71428, 73.,
+ 74.28571
+ ],
+ [
+ 78.14286, 79.42857, 80.71428, 82., 83.28572, 84.57143, 85.85715,
+ 87.14286
+ ],
+ [
+ 91., 92.28571, 93.57143, 94.85715, 96.14285, 97.42857, 98.71429,
+ 100.
+ ]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)
+
+ # resize_linear_align_corners_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode="align_corners",
+ mode='linear')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1., 2.5, 4., 5.5, 7., 8.5, 10.],
+ [16., 17.5, 19., 20.5, 22., 23.5, 25.],
+ [31., 32.5, 34., 35.5, 37., 38.5, 40.],
+ [46., 47.5, 49., 50.5, 52., 53.5, 55.],
+ [61., 62.5, 64., 65.5, 67., 68.5, 70.],
+ [76., 77.5, 79., 80.5, 82., 83.5, 85.],
+ [91., 92.5, 94., 95.5, 97., 98.5, 100.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_linear_asymmetric_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode="asymmetric",
+ mode='linear')
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[1., 2.25, 3.5, 4.75, 6., 7.25, 8.5, 9.75],
+ [13.5, 14.75, 16., 17.25, 18.5, 19.75, 21., 22.25],
+ [26., 27.25, 28.5, 29.75, 31., 32.25, 33.5, 34.75],
+ [38.5, 39.75, 41., 42.25, 43.5, 44.75, 46., 47.25],
+ [51., 52.25, 53.5, 54.75, 56., 57.25, 58.5, 59.75],
+ [63.5, 64.75, 66., 67.25, 68.5, 69.75, 71., 72.25],
+ [76., 77.25, 78.5, 79.75, 81., 82.25, 83.5, 84.75],
+ [88.5, 89.75, 91., 92.25, 93.5, 94.75, 96., 97.25]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_linear_asymmetric_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode="asymmetric",
+ mode='linear')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array([[
+ [[1., 2.4285715, 3.857143, 5.285714, 6.714286, 8.142857, 9.571428],
+ [15.285715, 16.714287, 18.142857, 19.571428, 21., 22.42857, 23.857141],
+ [29.571428, 31., 32.428574, 33.857143, 35.285717, 36.714287, 38.14286],
+ [43.857143, 45.28571, 46.714283, 48.14286, 49.571434, 51., 52.42857],
+ [
+ 58.14286, 59.57143, 61.000004, 62.428574, 63.857143, 65.28572,
+ 66.71429
+ ], [72.42857, 73.85713, 75.28572, 76.71429, 78.14286, 79.57143, 81.],
+ [86.71429, 88.14285, 89.57143, 91., 92.42857, 93.85714, 95.28571]]
+ ]],
+ dtype=np.float32
+ ) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)
+
+ # resize_linear_half_pixel_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ mode='linear')
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[2.375, 3.625, 4.875, 6.125, 7.375, 8.625, 9.875, 11.125],
+ [14.875, 16.125, 17.375, 18.625, 19.875, 21.125, 22.375, 23.625],
+ [27.375, 28.625, 29.875, 31.125, 32.375, 33.625, 34.875, 36.125],
+ [39.875, 41.125, 42.375, 43.625, 44.875, 46.125, 47.375, 48.625],
+ [52.375, 53.625, 54.875, 56.125, 57.375, 58.625, 59.875, 61.125],
+ [64.875, 66.125, 67.375, 68.625, 69.875, 71.125, 72.375, 73.625],
+ [77.375, 78.625, 79.875, 81.125, 82.375, 83.625, 84.875, 86.125],
+ [89.875, 91.125, 92.375, 93.625, 94.875, 96.125, 97.375, 98.625]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_linear_half_pixel_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ mode='linear')
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array([[[[
+ 3.357143, 4.785714, 6.214286, 7.642857, 9.071428, 10.5, 11.928572
+ ], [
+ 17.642857, 19.071428, 20.5, 21.92857, 23.357141, 24.785713, 26.214285
+ ], [
+ 31.928572, 33.357143, 34.785713, 36.214287, 37.642857, 39.071426, 40.5
+ ], [
+ 46.214287, 47.642857, 49.071426, 50.5, 51.928574, 53.357143, 54.785713
+ ], [60.5, 61.928577, 63.357147, 64.78572, 66.21429, 67.64286, 69.07143
+ ], [
+ 74.78572, 76.21429, 77.64285, 79.07143, 80.50001, 81.92857, 83.35715
+ ], [89.07143, 90.5, 91.928566, 93.35715, 94.78571, 96.21428, 97.64285]]]
+ ],
+ dtype=np.float32
+ ) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)
+
+ # resize_cubic_align_corners_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode="align_corners",
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[
+ 1., 2.285714, 3.5714293, 4.857143, 6.142857, 7.4285717, 8.714287,
+ 10.
+ ],
+ [
+ 13.857139, 15.142854, 16.42857, 17.714281, 18.999994, 20.28571,
+ 21.571426, 22.857138
+ ],
+ [
+ 26.71429, 28.000004, 29.285723, 30.571436, 31.85715, 33.142864,
+ 34.428577, 35.71429
+ ],
+ [
+ 39.57143, 40.857143, 42.142864, 43.428574, 44.714287, 46.000004,
+ 47.28572, 48.57143
+ ],
+ [
+ 52.42857, 53.714287, 55., 56.285717, 57.571423, 58.857143,
+ 60.14286, 61.42857
+ ],
+ [
+ 65.28571, 66.57144, 67.85715, 69.14285, 70.428566, 71.71429,
+ 73.00001, 74.28571
+ ],
+ [
+ 78.14287, 79.42857, 80.7143, 82.00001, 83.28573, 84.571434,
+ 85.857155, 87.14287
+ ],
+ [
+ 91., 92.28572, 93.57144, 94.85715, 96.14285, 97.42858, 98.714294,
+ 100.
+ ]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)
+
+ # resize_cubic_align_corners_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode="align_corners",
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1., 2.5, 4., 5.5, 7., 8.5, 10.],
+ [16., 17.5, 19., 20.5, 22., 23.5, 25.],
+ [31., 32.5, 34., 35.5, 37., 38.5, 40.],
+ [46., 47.5, 49., 50.5, 52., 53.5, 55.],
+ [61., 62.5, 64., 65.5, 67., 68.5, 70.],
+ [76., 77.5, 79., 80.5, 82., 83.5, 85.],
+ [91., 92.5, 94., 95.5, 97., 98.5, 100.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # resize_cubic_asymmetric_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode="asymmetric",
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[1., 2.25, 3.5, 4.75, 6., 7.25, 8.5, 9.832117],
+ [13.5, 14.75, 16., 17.25, 18.5, 19.75, 21., 22.332117],
+ [26., 27.25, 28.5, 29.75, 31., 32.25, 33.5, 34.83212],
+ [38.5, 39.75, 41., 42.25, 43.5, 44.75, 46., 47.332115],
+ [51., 52.25, 53.5, 54.75, 56., 57.25, 58.5, 59.83212],
+ [63.5, 64.75, 66., 67.25, 68.5, 69.75, 71., 72.332115],
+ [76., 77.25, 78.5, 79.75, 81., 82.25, 83.5, 84.832115],
+ [
+ 89.32117, 90.57117, 91.82117, 93.07117, 94.32117, 95.57117,
+ 96.82117, 98.15329
+ ]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)
+
+ # resize_cubic_asymmetric_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode="asymmetric",
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[1., 2.4285715, 3.8571432, 5.285714, 6.7142863, 8.142857, 9.66485],
+ [
+ 15.285712, 16.714287, 18.142855, 19.571426, 21., 22.428568,
+ 23.950563
+ ],
+ [
+ 29.57143, 31.000004, 32.428574, 33.857147, 35.285713, 36.714283,
+ 38.236282
+ ],
+ [
+ 43.857143, 45.285717, 46.714287, 48.142868, 49.571434, 50.999992,
+ 52.52199
+ ],
+ [
+ 58.142864, 59.57144, 61.000004, 62.428585, 63.857155, 65.28572,
+ 66.80771
+ ],
+ [
+ 72.428566, 73.85715, 75.28572, 76.714294, 78.14285, 79.57143,
+ 81.09343
+ ],
+ [
+ 87.6485, 89.07708, 90.505646, 91.93422, 93.36279, 94.79135,
+ 96.313354
+ ]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)
+
+ # resize_cubic_half_pixel_scales
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
+ expected = np.array([[
+ [[
+ 1.8098788, 3.1112535, 4.3612537, 5.6112533, 6.8612533, 8.111254,
+ 9.361254, 10.662629
+ ],
+ [14.823625, 16.125, 17.375, 18.625, 19.875, 21.125, 22.375, 23.676373],
+ [27.323626, 28.625, 29.875, 31.125, 32.375, 33.625, 34.875, 36.176376],
+ [39.823627, 41.125, 42.375, 43.625, 44.875, 46.125, 47.375, 48.676376],
+ [52.323624, 53.625, 54.875, 56.125, 57.375, 58.625, 59.875, 61.176373],
+ [64.82362, 66.125, 67.375, 68.625, 69.875, 71.125, 72.375, 73.67638],
+ [77.32362, 78.625, 79.875, 81.125, 82.375, 83.625, 84.875, 86.17638],
+ [
+ 90.33737, 91.63874, 92.88875, 94.13875, 95.38875, 96.63875,
+ 97.88875, 99.190125
+ ]]
+ ]],
+ dtype=np.float32
+ ) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)
+
+ # resize_cubic_half_pixel_sizes
+ node_def = helper.make_node("Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ mode='cubic',
+ cubic_coeff_a=-0.5,
+ exclude_outside=1)
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array([[[
+ [2.52846, 4.0323663, 5.460938, 6.889509, 8.318081, 9.746653, 11.250559],
+ [
+ 17.567522, 19.071426, 20.499996, 21.928568, 23.357141, 24.785715,
+ 26.28962
+ ],
+ [
+ 31.853237, 33.357143, 34.785713, 36.21429, 37.64286, 39.07143,
+ 40.575344
+ ],
+ [46.13895, 47.642857, 49.071434, 50.5, 51.928566, 53.357147, 54.861053],
+ [
+ 60.42467, 61.92858, 63.357147, 64.78572, 66.214294, 67.64287,
+ 69.14677
+ ],
+ [
+ 74.710396, 76.214294, 77.64286, 79.07144, 80.50001, 81.92858,
+ 83.432495
+ ],
+ [89.749466, 91.253365, 92.68193, 94.1105, 95.53907, 96.96766, 98.47156]
+ ]]],
+ dtype=np.float32
+ ) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_allclose(output['Y'], expected, rtol=1e-2, atol=1e-6)
+
+ # crop_and_resize_nearest with scales
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ coordinate_transformation_mode='tf_crop_and_resize',
+ mode='nearest',
+ nearest_mode='round_prefer_ceil',
+ extrapolation_value=-20.0)
+ roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
+ scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[46., 48., 49., -20., -20., -20., -20., -20.],
+ [56., 58., 59., -20., -20., -20., -20., -20.],
+ [66., 68., 69., -20., -20., -20., -20., -20.],
+ [76., 78., 79., -20., -20., -20., -20., -20.],
+ [86., 88., 89., -20., -20., -20., -20., -20.],
+ [96., 98., 99., -20., -20., -20., -20., -20.],
+ [-20., -20., -20., -20., -20., -20., -20., -20.],
+ [-20., -20., -20., -20., -20., -20., -20., -20.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # crop_and_resize_nearest with sizes
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ coordinate_transformation_mode='tf_crop_and_resize',
+ mode='nearest',
+ nearest_mode='round_prefer_ceil',
+ )
+ roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[46., 48., 50., 0., 0., 0., 0.], [56., 58., 60., 0., 0., 0., 0.],
+ [66., 68., 70., 0., 0., 0., 0.], [76., 78., 80., 0., 0., 0., 0.],
+ [86., 88., 90., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0.],
+ [0., 0., 0., 0., 0., 0., 0.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_almost_equal(output["Y"], expected)
+
+ # crop_and_resize_linear with scales
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales'],
+ outputs=['Y'],
+ mode='linear',
+ coordinate_transformation_mode='tf_crop_and_resize',
+ extrapolation_value=20.0)
+ roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
+ scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
+ expected = np.array(
+ [[[[42.4, 43.814285, 45.228573, 20., 20., 20., 20., 20.],
+ [52.685715, 54.100002, 55.514286, 20., 20., 20., 20., 20.],
+ [62.971436, 64.38572, 65.80001, 20., 20., 20., 20., 20.],
+ [73.25715, 74.67143, 76.08572, 20., 20., 20., 20., 20.],
+ [83.54286, 84.957146, 86.37143, 20., 20., 20., 20., 20.],
+ [93.82858, 95.24287, 96.65715, 20., 20., 20., 20., 20.],
+ [20., 20., 20., 20., 20., 20., 20., 20.],
+ [20., 20., 20., 20., 20., 20., 20., 20.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales])
+ np.testing.assert_allclose(output["Y"], expected, rtol=1e-6, atol=1e-6)
+
+ # crop_and_resize_linear with sizes
+ node_def = helper.make_node(
+ "Resize",
+ inputs=['X', 'roi', 'scales', 'sizes'],
+ outputs=['Y'],
+ mode='linear',
+ coordinate_transformation_mode='tf_crop_and_resize',
+ extrapolation_value=50.0)
+ roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
+ scales = np.array([], dtype=np.float32)
+ sizes = np.array([1, 1, 7, 7], dtype=np.int64)
+ expected = np.array(
+ [[[[42.4, 44.05, 45.7, 50., 50., 50., 50.],
+ [54.4, 56.050003, 57.700005, 50., 50., 50., 50.],
+ [66.40001, 68.05, 69.700005, 50., 50., 50., 50.],
+ [78.40001, 80.05001, 81.700005, 50., 50., 50., 50.],
+ [90.40001, 92.05, 93.70001, 50., 50., 50., 50.],
+ [50., 50., 50., 50., 50., 50., 50.],
+ [50., 50., 50., 50., 50., 50., 50.]]]],
+ dtype=np.float32) # expected value is calculated by onnx-runtime
+ output = run_node(node_def, [data, roi, scales, sizes])
+ np.testing.assert_allclose(output["Y"], expected, rtol=1e-6, atol=1e-6)
+
+ def test_round(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest("ONNX version {} doesn't support Round.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Round", ["X"], ["Y"])
+ x = self._get_rnd_float32(-20.0, 20.0, shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.round(x))
+
+ def test_qLinearMatMul(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support QLinearMatMul.".format(
+ defs.onnx_opset_version()))
+
+ def qLinearMatMul(a, a_scale, a_zero_point, b, b_scale, b_zero_point,
+ y_scale, y_zero_point):
+ y_dtype = y_zero_point.dtype
+ # reshape 1-D a_scale, a_zero_point, y_scale and
+ # y_zero_point so it can broadcast in arithmetic
+ # operations later
+ a_scale_shape = a_scale.shape
+ if a_scale_shape and a_scale_shape[0] > 1:
+ a_scale = np.reshape(a_scale, [a_scale_shape[0], 1])
+ a_zero_point = np.reshape(a_zero_point, [a_scale_shape[0], 1])
+ y_scale_shape = y_scale.shape
+ if y_scale_shape and y_scale_shape[0] > 1:
+ y_scale = np.reshape(y_scale, [y_scale_shape[0], 1])
+ y_zero_point = np.reshape(y_zero_point, [y_scale_shape[0], 1])
+ # cast everything to float32
+ a = a.astype(np.float32)
+ a_zero_point = a_zero_point.astype(np.float32)
+ b = b.astype(np.float32)
+ b_zero_point = b_zero_point.astype(np.float32)
+ y_zero_point = y_zero_point.astype(np.float32)
+ # dequantize a and b
+ dequantized_a = np.subtract(a, a_zero_point)
+ dequantized_a = np.multiply(dequantized_a, a_scale)
+ dequantized_b = np.subtract(b, b_zero_point)
+ dequantized_b = np.multiply(dequantized_b, b_scale)
+ # matmul a and b
+ x = np.matmul(dequantized_a, dequantized_b)
+ # quantize x
+ y = np.divide(x, y_scale)
+ y = np.round(y)
+ y = np.add(y, y_zero_point)
+ y = np.clip(y, np.iinfo(y_dtype).min, np.iinfo(y_dtype).max)
+ y = y.astype(y_dtype)
+ return y
+
+ node_def = helper.make_node('QLinearMatMul', [
+ 'a', 'a_scale', 'a_zero_point', 'b', 'b_scale', 'b_zero_point',
+ 'y_scale', 'y_zero_point'
+ ], ['y'])
+ for dtype in [np.int8, np.uint8]:
+ low = np.iinfo(dtype).min
+ high = np.iinfo(dtype).max
+ a = self._get_rnd_int(low, high, [3, 4, 5, 6], dtype)
+ a_scale = self._get_rnd_float32(-0.005, 0.005, [5])
+ a_zero_point = self._get_rnd_int(low, high, [5], dtype)
+ b = self._get_rnd_int(low, high, [3, 4, 6, 2], dtype)
+ b_scale = self._get_rnd_float32(-0.005, 0.005, [2])
+ b_zero_point = self._get_rnd_int(low, high, [2], dtype)
+ y_scale = self._get_rnd_float32(-0.05, 0.05, [5])
+ y_zero_point = self._get_rnd_int(low, high, [5], dtype)
+ y = qLinearMatMul(a, a_scale, a_zero_point, b, b_scale, b_zero_point,
+ y_scale, y_zero_point)
+ output = run_node(node_def, [
+ a, a_scale, a_zero_point, b, b_scale, b_zero_point, y_scale,
+ y_zero_point
+ ])
+ np.testing.assert_almost_equal(output['y'], y)
+
+ def test_relu(self):
+ node_def = helper.make_node("Relu", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.maximum(x, 0))
+
+ def test_pad(self):
+ x = self._get_rnd_float32(shape=[100, 100])
+ if legacy_opset_pre_ver(11): # for opset = 1 or 2
+ # mode = constant
+ node_def = helper.make_node("Pad", ["X"], ["Y"],
+ mode="constant",
+ pads=[1, 1, 1, 1],
+ value=2.0)
+ output = run_node(node_def, [x])
+ y = np.pad(x, ((1, 1), (1, 1)), 'constant', constant_values=(2, 2))
+ np.testing.assert_almost_equal(output["Y"], y)
+ # mode = reflect and edge
+ for mode in ['edge', 'reflect']:
+ node_def = helper.make_node("Pad", ["X"], ["Y"],
+ mode=mode,
+ pads=[1, 1, 1, 1])
+ output = run_node(node_def, [x])
+ y = np.pad(x, ((1, 1), (1, 1)), mode)
+ np.testing.assert_almost_equal(output["Y"], y)
+ else: # for opset = 11
+ # mode = constant
+ node_def = helper.make_node("Pad", ["X", "pads", "constant_values"],
+ ["Y"],
+ mode="constant")
+ pads = np.array([1, 1, 1, 1], dtype=np.int64)
+ constant_values = 2.0
+ output = run_node(node_def, [x, pads, constant_values])
+ y = np.pad(x, ((1, 1), (1, 1)), 'constant', constant_values=(2, 2))
+ np.testing.assert_almost_equal(output["Y"], y)
+ # mode = reflect and edge
+ for mode in ['edge', 'reflect']:
+ node_def = helper.make_node("Pad", ["X", "pads"], ["Y"], mode=mode)
+ output = run_node(node_def, [x, pads])
+ y = np.pad(x, ((1, 1), (1, 1)), mode)
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def test_qlinearconv(self):
+ if legacy_opset_pre_ver(10):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support QLinearConv.".format(
+ defs.onnx_opset_version()))
+
+ # Test w_scale and w_zero_point as scalar
+ node_def = helper.make_node("QLinearConv",
+ inputs=[
+ "x", "x_scale", "x_zero_point", "w",
+ "w_scale", "w_zero_point", "y_scale",
+ "y_zero_point"
+ ],
+ outputs=["Y"])
+ x = np.array([
+ [255, 174, 162, 25, 203, 168, 58],
+ [15, 59, 237, 95, 129, 0, 64],
+ [56, 242, 153, 221, 168, 12, 166],
+ [232, 178, 186, 195, 237, 162, 237],
+ [188, 39, 124, 77, 80, 102, 43],
+ [127, 230, 21, 83, 41, 40, 134],
+ [255, 154, 92, 141, 42, 148, 247],
+ ],
+ dtype=np.uint8).reshape((1, 1, 7, 7))
+ x_scale = np.float32(0.00369204697)
+ x_zero_point = np.uint8(132)
+
+ w = np.array([0], dtype=np.uint8).reshape((1, 1, 1, 1))
+ w_scale = np.float32(0.00172794575)
+ w_zero_point = np.uint8(255)
+
+ y = np.array([
+ [0, 81, 93, 230, 52, 87, 197],
+ [240, 196, 18, 160, 126, 255, 191],
+ [199, 13, 102, 34, 87, 243, 89],
+ [23, 77, 69, 60, 18, 93, 18],
+ [67, 216, 131, 178, 175, 153, 212],
+ [128, 25, 234, 172, 214, 215, 121],
+ [0, 101, 163, 114, 213, 107, 8],
+ ],
+ dtype=np.uint8).reshape((1, 1, 7, 7))
+ y_scale = np.float32(0.00162681262)
+ y_zero_point = np.uint8(123)
+
+ output = run_node(node_def, [
+ x, x_scale, x_zero_point, w, w_scale, w_zero_point, y_scale,
+ y_zero_point
+ ])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def test_quantize_linear(self):
+ node_def = helper.make_node("QuantizeLinear",
+ ["x", "y_scale", "y_zero_point"], ["y"])
+ for x in [
+ self._get_rnd_float32(-512., 512., [2, 6]),
+ self._get_rnd_int(-512, 512, [2, 6])
+ ]:
+ y_scale = self._get_rnd_float32(-10., 10.)
+ for y_zero_point in [
+ self._get_rnd_int(-128, 127, dtype=np.int8),
+ self._get_rnd_int(0, 255, dtype=np.uint8)
+ ]:
+ y = np.divide(x, y_scale)
+ y = np.round(y)
+ y = np.add(y, y_zero_point)
+ if y_zero_point.dtype.type is np.int8:
+ y = np.clip(y, -128, 127).astype(np.int8)
+ else:
+ y = np.clip(y, 0, 255).astype(np.uint8)
+ output = run_node(node_def, [x, y_scale, y_zero_point])
+ np.testing.assert_almost_equal(output["y"], y)
+
+ def test_reciprocal(self):
+ node_def = helper.make_node("Reciprocal", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], 1.0 / x)
+
+ def test_reduce_l1(self):
+ node_def = helper.make_node("ReduceL1", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"],
+ np.linalg.norm(x, 1, (1, 2), True))
+
+ def test_reduce_log_sum_exp(self):
+ node_def = helper.make_node("ReduceLogSumExp", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.log(
+ np.sum(np.exp(x), axis=(1, 2),
+ keepdims=True)),
+ rtol=1e-3)
+
+ def test_reduce_max(self):
+ node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.max(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ # test tensor(uint8), tensor(int8)
+ node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_int(0, 100, [5, 10, 10, 3], np.uint8)
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.max(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_int(-100, 100, [5, 10, 10, 3], np.int8)
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.max(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_reduce_mean(self):
+ node_def = helper.make_node("ReduceMean", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.mean(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_reduce_min(self):
+ node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.min(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ # test tensor(uint8), tensor(int8)
+ node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_int(0, 100, [5, 10, 10, 3], np.uint8)
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.min(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_int(-100, 100, [5, 10, 10, 3], np.int8)
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.min(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_reduce_prod(self):
+ node_def = helper.make_node("ReduceProd", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[1, 5, 5, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.prod(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_reduce_sum(self):
+ node_def = helper.make_node("ReduceSum", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.sum(x, (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_reduce_sum_square(self):
+ node_def = helper.make_node("ReduceSumSquare", ["X"], ["Y"], axes=[1, 2])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"],
+ np.sum(np.square(x), (1, 2), keepdims=True),
+ rtol=1e-3)
+
+ def test_pow(self):
+ node_def = helper.make_node("Pow", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=1000) / 2.0 + 0.5
+ y = self._get_rnd_float32(shape=1000) / 2.0 + 0.5
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.power(x, y))
+
+ def test_reshape(self):
+ x = self._get_rnd_float32(shape=100)
+ shape = [10, 10]
+ if defs.onnx_opset_version() < 5:
+ node_def = helper.make_node("Reshape", ["X"], ["Z"], shape=shape)
+ output = run_node(node_def, [x])
+ else:
+ node_def = helper.make_node("Reshape", ["X", "Y"], ["Z"])
+ output = run_node(node_def, [x, shape])
+
+ np.testing.assert_almost_equal(output["Z"], x.reshape([10, 10]))
+
+ def test_reshape_with_copy(self):
+ x = self._get_rnd_float32(shape=[10, 20 * 30])
+ shape = [0, 20, 30]
+ if defs.onnx_opset_version() < 5:
+ node_def = helper.make_node("Reshape", ["X"], ["Z"], shape=shape)
+ output = run_node(node_def, [x])
+ else:
+ node_def = helper.make_node("Reshape", ["X", "Y"], ["Z"])
+ output = run_node(node_def, [x, shape])
+
+ np.testing.assert_almost_equal(output["Z"], x.reshape([10, 20, 30]))
+
+ def test_selu(self):
+ node_def = helper.make_node("Selu", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ alpha = 1.6732
+ gamma = 1.0507
+ x[x <= 0] = gamma * (alpha * np.exp(x[x <= 0]) - alpha)
+ x[x > 0] = gamma * x[x > 0]
+ np.testing.assert_allclose(output["Y"], x, rtol=1e-3, atol=1e-7)
+
+ def _run_scan_node(self,
+ initial,
+ x1,
+ x2,
+ input_shape,
+ output_shape,
+ scan_input_axes=None,
+ scan_input_directions=None,
+ scan_output_axes=None,
+ scan_output_directions=None,
+ sequence_lens=None,
+ directions=None):
+ """
+ Subgraph looks like this.
+
+ [const1] state_in concat1_in concat2_in_
+ \ | \ /
+ \--------[Add] [Concat]
+ | |
+ | concat_out
+ | |
+ | [Add]----------[const1]
+ | |
+ | add_out_1
+ | |
+ | [Split]
+ | / | | \
+ state_out split1_out ... split4_out
+ """
+ val_1 = helper.make_tensor(
+ name='const_tensor',
+ data_type=TensorProto.FLOAT,
+ dims=[1],
+ vals=[1],
+ )
+ constant_node = helper.make_node("Constant", [], ["const_1"], value=val_1)
+ state_add_node = helper.make_node("Add", ["state_in", "const_1"],
+ ["state_out"])
+ concat_node = helper.make_node("Concat", ["concat1_in", "concat2_in"],
+ ["concat_out"],
+ axis=0)
+ add_node = helper.make_node("Add", ["concat_out", "const_1"], ["add_out"])
+ split_node = helper.make_node(
+ "Split", ["add_out"],
+ ["split1_out", "split2_out", "split3_out", "split4_out"])
+
+ state_in = helper.make_tensor_value_info('state_in', TensorProto.FLOAT, [1])
+ concat1_in = helper.make_tensor_value_info('concat1_in', TensorProto.FLOAT,
+ input_shape)
+ concat2_in = helper.make_tensor_value_info('concat2_in', TensorProto.FLOAT,
+ input_shape)
+ state_out = helper.make_tensor_value_info('state_out', TensorProto.FLOAT,
+ [1])
+ split1_out = helper.make_tensor_value_info('split1_out', TensorProto.FLOAT,
+ output_shape)
+ split2_out = helper.make_tensor_value_info('split2_out', TensorProto.FLOAT,
+ output_shape)
+ split3_out = helper.make_tensor_value_info('split3_out', TensorProto.FLOAT,
+ output_shape)
+ split4_out = helper.make_tensor_value_info('split4_out', TensorProto.FLOAT,
+ output_shape)
+
+ scan_body = helper.make_graph(
+ [constant_node, state_add_node, concat_node, add_node, split_node],
+ "scan_body",
+ [state_in, concat1_in, concat2_in],
+ [state_out, split1_out, split2_out, split3_out, split4_out],
+ )
+
+ node_kwargs = {
+ "op_type": "Scan",
+ "inputs": ["initial", "x1", "x2"],
+ "outputs": ["y", "z1", "z2", "z3", "z4"],
+ "num_scan_inputs": 2,
+ "body": scan_body
+ }
+ if sequence_lens is not None:
+ node_kwargs["inputs"] = ["" if sequence_lens is str else "seq_lens"
+ ] + node_kwargs["inputs"]
+
+ if scan_input_axes is not None:
+ node_kwargs["scan_input_axes"] = scan_input_axes
+ if scan_input_directions is not None:
+ node_kwargs["scan_input_directions"] = scan_input_directions
+ if scan_output_axes is not None:
+ node_kwargs["scan_output_axes"] = scan_output_axes
+ if scan_output_directions is not None:
+ node_kwargs["scan_output_directions"] = scan_output_directions
+ if directions is not None:
+ node_kwargs["directions"] = directions
+
+ scan_node = helper.make_node(**node_kwargs)
+
+ if sequence_lens is None:
+ inputs = [initial, x1, x2]
+ else:
+ inputs = [sequence_lens, initial, x1, x2]
+
+ return run_node(scan_node, inputs)
+
+ def test_scan_v8(self):
+ if legacy_opset_pre_ver(8) or not legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[5, 1]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[5, 20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[5, 20, 6, 2])
+
+ directions = [0, 1]
+ sequence_lens = np.array([15, 20, 14, 18, 20]).astype(np.int32)
+
+ Y = initial + (np.shape(x1)[1] if sequence_lens is str else \
+ np.reshape(sequence_lens,[-1, 1]))
+ x1_out = x1 + 1
+ # left-right flip x2 (reverse direction)
+ x2_out = x2[:, ::-1] + 1
+
+ Z = np.concatenate([x1_out, x2_out], 2)
+ if sequence_lens is not str:
+ for batch in range(len(sequence_lens)):
+ # zero pad from the sequence_lens
+ shape = list(np.shape(Z[batch]))
+ seq_len = sequence_lens[batch]
+
+ zero_pad = np.zeros([shape[0] - seq_len] + shape[1:])
+ Z[batch] = np.concatenate([Z[batch][:seq_len], zero_pad])
+
+ output = self._run_scan_node(initial,
+ x1,
+ x2, [6, 4], [3, 2],
+ sequence_lens=sequence_lens,
+ directions=directions)
+ output_z = np.concatenate(
+ [output["z1"], output["z2"], output["z3"], output["z4"]], 2)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scan(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[2]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+
+ Y = initial + np.shape(x1)[0]
+ Z = np.concatenate([x1, x2], 1) + 1
+
+ output = self._run_scan_node(initial, x1, x2, [6, 2], [3, 2])
+ output_z = np.concatenate(
+ [output["z1"], output["z2"], output["z3"], output["z4"]], 1)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scan_input_directions(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+
+ Y = initial + np.shape(x1)[0]
+ Z = np.concatenate([x1[::-1], x2], 1) + 1
+
+ output = self._run_scan_node(initial,
+ x1,
+ x2, [6, 2], [3, 2],
+ scan_input_directions=[1, 0])
+ output_z = np.concatenate(
+ [output["z1"], output["z2"], output["z3"], output["z4"]], 1)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scan_input_axes(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+
+ Y = initial + np.shape(x1)[1]
+ x1_transpose = np.transpose(x1, (1, 0, 2))
+ x2_transpose = np.transpose(x2, (1, 0, 2))
+ Z = np.concatenate([x1_transpose, x2_transpose], 1) + 1
+
+ output = self._run_scan_node(initial,
+ x1,
+ x2, [3, 2], [10, 2],
+ scan_input_axes=[1, 1])
+ output_z = np.concatenate(
+ [output["z1"], output["z2"], output["z3"], output["z4"]], 1)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scan_output_directions(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+
+ Y = initial + np.shape(x1)[0]
+ Z = np.concatenate([x1, x2], 1) + 1
+
+ output = self._run_scan_node(initial,
+ x1,
+ x2, [6, 2], [3, 2],
+ scan_output_directions=[1, 0, 0, 1])
+ output_z = np.concatenate(
+ [output["z1"][::-1], output["z2"], output["z3"], output["z4"][::-1]], 1)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scan_output_axes(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} not supported.".format(
+ defs.onnx_opset_version()))
+
+ initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
+ x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+ x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
+
+ Y = initial + np.shape(x1)[0]
+ Z = np.concatenate([x1, x2], 1) + 1
+ Z = np.transpose(Z, (1, 0, 2))
+
+ output = self._run_scan_node(initial,
+ x1,
+ x2, [10, 2], [3, 2],
+ scan_output_axes=[1, 1, 1, 1])
+ output_z = np.concatenate(
+ [output["z1"], output["z2"], output["z3"], output["z4"]], 0)
+
+ np.testing.assert_almost_equal(output["y"], Y)
+ np.testing.assert_almost_equal(output_z, Z)
+
+ def test_scatter_elements1(self):
+ data = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
+ indices = np.array([[1, 3]], dtype=np.int64)
+ updates = np.array([[1.1, 2.1]], dtype=np.float32)
+ axis = 1
+ ref_output = np.array([[1.0, 1.1, 3.0, 2.1, 5.0]], dtype=np.float32)
+
+ if legacy_opset_pre_ver(11):
+ node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
+ ["outputs"],
+ axis=axis)
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+ else:
+ node_def = helper.make_node("ScatterElements",
+ ["data", "indices", "updates"], ["outputs"],
+ axis=axis)
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ def test_scatter_elements2(self):
+ data = np.array([
+ [0.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0],
+ [0.0, 0.0, 0.0],
+ ],
+ dtype=np.float32)
+ indices = np.array([
+ [1, 0, 2],
+ [0, 2, 1],
+ ], dtype=np.int64)
+ updates = np.array([
+ [1.0, 1.1, 1.2],
+ [2.0, 2.1, 2.2],
+ ], dtype=np.float32)
+ ref_output = np.array([
+ [2.0, 1.1, 0.0],
+ [1.0, 0.0, 2.2],
+ [0.0, 2.1, 1.2],
+ ],
+ dtype=np.float32)
+
+ if legacy_opset_pre_ver(11):
+ node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
+ ["outputs"])
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+ else:
+ node_def = helper.make_node("ScatterElements",
+ ["data", "indices", "updates"], ["outputs"])
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ def test_scatter_elements3(self):
+ # indices out of bounds
+ data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32)
+ indices = np.array([[0, 1, 2]], dtype=np.int64)
+ updates = np.array([[1.1, 2.1, 3.1]], dtype=np.float32)
+
+ if legacy_opset_pre_ver(11):
+ node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
+ ["outputs"])
+ else:
+ node_def = helper.make_node("ScatterElements",
+ ["data", "indices", "updates"], ["outputs"])
+ with np.testing.assert_raises(tf.errors.InvalidArgumentError):
+ output = run_node(node_def, [data, indices, updates])
+
+ def test_scatter_nd(self):
+ if legacy_opset_pre_ver(11):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support ScatterND.".format(
+ defs.onnx_opset_version()))
+
+ # valid positve and negative indices for elements
+ data = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.float32)
+ indices = np.array([[4], [3], [1], [7]], dtype=np.int64)
+ updates = np.array([9, 10, 11, 12], dtype=np.float32)
+ ref_output = np.array([1, 11, 3, 10, 9, 6, 7, 12], dtype=np.float32)
+ node_def = helper.make_node("ScatterND", ["data", "indices", "updates"],
+ ["outputs"])
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # valid positive and negative indices for slices
+ data = np.reshape(np.arange(1, 25, dtype=np.float32), [2, 3, 4])
+ indices = np.array([[-2, -1], [1, 0]], dtype=np.int64)
+ updates = np.array([[39, 40, 41, 42], [43, 44, 45, 46]], dtype=np.float32)
+ ref_output = np.array(
+ [[[1, 2, 3, 4], [5, 6, 7, 8], [39, 40, 41, 42]],
+ [[43, 44, 45, 46], [17, 18, 19, 20], [21, 22, 23, 24]]],
+ dtype=np.float32)
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+ indices = np.array([[-1]], dtype=np.int64)
+ updates = np.array([[[43, 44, 45, 46], [47, 48, 49, 50], [51, 52, 53, 54]]],
+ dtype=np.float32)
+ ref_output = np.array(
+ [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
+ [[43, 44, 45, 46], [47, 48, 49, 50], [51, 52, 53, 54]]],
+ dtype=np.float32)
+ output = run_node(node_def, [data, indices, updates])
+ np.testing.assert_almost_equal(output["outputs"], ref_output)
+
+ # indices out of bounds
+ indices = np.array([[0, 1, 2], [-1, -1, -3], [-2, -3, -4], [0, 2, -5]],
+ dtype=np.int64)
+ updates = np.array([37, 52, 30, 39], dtype=np.float32)
+ with np.testing.assert_raises(tf.errors.InvalidArgumentError):
+ output = run_node(node_def, [data, indices, updates])
+ indices = np.array([[0, 1], [-1, -1], [-2, -4]], dtype=np.int64)
+ updates = np.array([[35, 36, 37, 38], [51, 52, 53, 54], [31, 32, 33, 34]],
+ dtype=np.float32)
+ with np.testing.assert_raises(tf.errors.InvalidArgumentError):
+ output = run_node(node_def, [data, indices, updates])
+
+ def test_shape(self):
+ node_def = helper.make_node("Shape", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_allclose(output["Y"], np.shape(x))
+
+ def test_shrink(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Shrink.".format(
+ defs.onnx_opset_version()))
+
+ node_def = helper.make_node("Shrink", ["X"], ["Y"], bias=1.5, lambd=1.5)
+
+ X = np.arange(-2.0, 2.1, dtype=np.float32)
+ Y = np.array([-0.5, 0, 0, 0, 0.5], dtype=np.float32)
+ output = run_node(node_def, [X])
+ np.testing.assert_almost_equal(output["Y"], Y)
+
+ def test_sigmoid(self):
+ node_def = helper.make_node("Sigmoid", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], 1 / (1 + np.exp(-x)))
+
+ def test_sign(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Sign.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Sign", ["X"], ["Y"])
+ x = self._get_rnd_float32(-10, 10, [3, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.sign(x))
+
+ def test_sinh(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Sinh.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Sinh", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.sinh(x))
+
+ def test_size(self):
+ node_def = helper.make_node("Size", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[5, 10, 10, 3])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.size(x))
+
+ def test_slice(self):
+ # test case 1 with normal inputs
+ axes = [0, 1, 2]
+ starts = [0, 0, 0]
+ ends = [2, 2, 2]
+ steps = [1, 1, 1]
+
+ if legacy_opset_pre_ver(10):
+ node_def = helper.make_node("Slice", ["X"], ["S"],
+ axes=axes,
+ starts=starts,
+ ends=ends)
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])
+ else:
+ node_def = helper.make_node("Slice",
+ ["X", "starts", "ends", "axes", "steps"],
+ ["S"])
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x, starts, ends, axes, steps])
+ np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])
+
+ # test case 2 with negative, out-of-bound and default inputs
+ axes = [0, 2]
+ starts = [0, -7]
+ ends = [-8, 20]
+
+ if legacy_opset_pre_ver(10):
+ node_def = helper.make_node("Slice", ["X"], ["S"],
+ axes=axes,
+ starts=starts,
+ ends=ends)
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])
+ else:
+ node_def = helper.make_node("Slice", ["X", "starts", "ends", "axes"],
+ ["S"])
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x, starts, ends, axes])
+ np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])
+
+ # test case 3 with non-default steps
+ axes = [0, 1, 2]
+ starts = [0, 0, 0]
+ ends = [2, 2, 2]
+ steps = [2, -2, -1]
+
+ if legacy_opset_pre_ver(10) == False:
+ node_def = helper.make_node("Slice",
+ ["X", "starts", "ends", "axes", "steps"],
+ ["S"])
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x, starts, ends, axes, steps])
+ np.testing.assert_almost_equal(output["S"], x[0:2:2, 0:2:-2, 0:2:-1])
+
+ def test_softplus(self):
+ node_def = helper.make_node("Softplus", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.log(np.exp(x) + 1), decimal=5)
+
+ def test_softsign(self):
+ node_def = helper.make_node("Softsign", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[3, 4, 5])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], x / (1 + np.abs(x)))
+
+ def test_space_to_depth(self):
+ node_def = helper.make_node("SpaceToDepth", ["X"], ["Y"], blocksize=2)
+ x_shape = [1, 3, 2, 2]
+ x = self._get_rnd_float32(shape=x_shape)
+ output = run_node(node_def, [x])
+ x = np.transpose(x, (0, 2, 3, 1))
+ y = np.reshape(np.swapaxes(x.reshape(1, 1, 1, 1, 1, 12), 2, 3),
+ (1, 1, 1, 12))
+ y = np.transpose(y, (0, 3, 1, 2))
+ np.testing.assert_allclose(output["Y"], y, rtol=1e-3)
+
+ def test_split(self):
+ split = [3, 3, 4]
+ node_def = helper.make_node("Split", ["X"],
+ ["Z%i" % i for i in range(len(split))],
+ axis=0,
+ split=split)
+ x = self._get_rnd_float32(shape=[100]).reshape([10, 10])
+
+ output = run_node(node_def, [x])
+ for a, b in zip(list(output), np.split(x, np.cumsum(split))[:-1]):
+ np.testing.assert_almost_equal(a, b)
+
+ # test axis out of bound
+ node_def = helper.make_node("Split", ["X"],
+ ["Z%i" % i for i in range(len(split))],
+ axis=3,
+ split=split)
+ with np.testing.assert_raises(ValueError):
+ output = run_node(node_def, [x])
+
+ def test_sqrt(self):
+ node_def = helper.make_node("Sqrt", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000]) + 1.0
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.sqrt(x), decimal=5)
+
+ def test_squeeze(self):
+ node_def = helper.make_node("Squeeze", ["X"], ["Y"], axes=[2])
+ x = np.array([[[0], [1], [2]]])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.squeeze(x, axis=2))
+
+ def test_sub(self):
+ node_def = helper.make_node("Sub", ["X", "Y"], ["Z"])
+ x = self._get_rnd_float32(shape=[10, 10])
+ y = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [x, y])
+ np.testing.assert_almost_equal(output["Z"], np.subtract(x, y))
+
+ def test_sum(self):
+ node_def = helper.make_node("Sum", ["X1", "X2", "X3", "X4"], ["Z"])
+ x1 = self._get_rnd_float32(shape=[10, 10])
+ x2 = self._get_rnd_float32(shape=[10, 10])
+ x3 = self._get_rnd_float32(shape=[10, 10])
+ x4 = self._get_rnd_float32(shape=[10, 10])
+ output = run_node(node_def, [x1, x2, x3, x4])
+ test_output = x1 + x2 + x3 + x4
+ np.testing.assert_almost_equal(output["Z"], test_output)
+
+ def test_tanh(self):
+ node_def = helper.make_node("Tanh", ["X"], ["Y"])
+ x = self._get_rnd_float32(shape=[1000]) + 1.0
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.tanh(x), decimal=5)
+
+ def test_tfidf_vectorizer(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest(
+ "ONNX version {} doesn't support TfIdfVectorizer.".format(
+ defs.onnx_opset_version()))
+
+ def run_test_ints():
+ node_def = helper.make_node("TfIdfVectorizer", ["X"], ["Y"],
+ mode=mode,
+ min_gram_length=min_gram_len,
+ max_gram_length=max_gram_len,
+ max_skip_count=max_skip,
+ ngram_counts=ngram_counts,
+ ngram_indexes=ngram_indexes,
+ weights=weights,
+ pool_int64s=pool_int64s)
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def run_test_strings():
+ node_def = helper.make_node("TfIdfVectorizer", ["X"], ["Y"],
+ mode=mode,
+ min_gram_length=min_gram_len,
+ max_gram_length=max_gram_len,
+ max_skip_count=max_skip,
+ ngram_counts=ngram_counts,
+ ngram_indexes=ngram_indexes,
+ weights=weights,
+ pool_strings=pool_strings)
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ # test 2d inputs with 3 elements, output contains 1-grams and 2-grams
+ x = np.array([[1, 1, 3, 3, 3, 7], [8, 6, 7, 5, 6, 8], [8, 6, 7, 5, 6,
+ 8]]).astype(np.int32)
+ y = np.array([[0., 3., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 1., 0., 1.],
+ [0., 0., 1., 0., 1., 0., 1.]]).astype(np.float32)
+ ngram_counts = np.array([0, 4]).astype(np.int64)
+ ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
+ pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
+ min_gram_len = 1
+ max_gram_len = 2
+ max_skip = 0
+ mode = 'TF'
+ weights = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
+ run_test_ints()
+
+ # test 1d inputs with indexes in non-default order, max_skip=3, output 2-grams
+ x = np.array([1, 1, 3, 3, 3, 7, 8, 6, 7, 5, 6, 8]).astype(np.int32)
+ y = np.array([0., 1., 0., 1., 0., 0., 2.]).astype(np.float32)
+ ngram_counts = np.array([0, 4]).astype(np.int64)
+ ngram_indexes = np.array([5, 0, 2, 4, 1, 6, 3]).astype(np.int64)
+ pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
+ min_gram_len = 2
+ max_gram_len = 2
+ max_skip = 3
+ mode = 'TF'
+ weights = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
+ run_test_ints()
+
+ # test IDF mode with weights, max_skip=5, output contains 1-grams and 2-grams
+ x = np.array([[1, 1, 3, 3, 3, 7], [8, 6, 7, 5, 6, 8]]).astype(np.int32)
+ y = np.array([[0., 0.1, 0., 0., 0., 0., 0.],
+ [0., 0., 0.1, 0., 0.5, 0.5, 0.5]]).astype(np.float32)
+ ngram_counts = np.array([0, 4]).astype(np.int64)
+ ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
+ pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
+ min_gram_len = 1
+ max_gram_len = 2
+ max_skip = 5
+ mode = 'IDF'
+ weights = np.array([0.1, 0.1, 0.1, 0.1, 0.5, 0.5, 0.5])
+ run_test_ints()
+
+ # test strings inputs, max_skip=5, output contains 1-grams and 2-grams
+ x = np.array(['a', 'a', 'b', 'b', 'b', 'c', 'd', 'e', 'c', 'f', 'e', 'd'])
+ y = np.array([0., 3., 1., 0., 1., 3., 1.]).astype(np.float32)
+ ngram_counts = np.array([0, 4]).astype(np.int64)
+ ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
+ pool_strings = np.array(['x', 'b', 'f', 'y', 'f', 'e', 'c', 'd', 'e', 'c'])
+ min_gram_len = 1
+ max_gram_len = 2
+ max_skip = 5
+ mode = 'TF'
+ run_test_strings()
+
+ def test_thresholded_relu(self):
+ alpha = 2.0
+ node_def = helper.make_node("ThresholdedRelu", ["X"], ["Y"], alpha=alpha)
+ x = self._get_rnd_float32(-3.0, 3.0, [10])
+ y = np.clip(x, alpha, np.inf)
+ y[y == alpha] = 0
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], y)
+
+ def test_tile(self):
+ if legacy_onnx_pre_ver(1, 2):
+ raise unittest.SkipTest(
+ "The current version of ONNX does not record correctly the opset of Tile."
+ )
+ node_def = helper.make_node("Tile", ["X1", "X2"], ["Z"])
+ x = self._get_rnd_float32(shape=[3, 5, 5, 3])
+ repeats = [1, 1, 2, 1]
+ output = run_node(node_def, [x, repeats])
+ np.testing.assert_allclose(output["Z"], np.tile(x, repeats), rtol=1e-3)
+
+ def test_transpose(self):
+ node_def = helper.make_node("Transpose", ["X"], ["Y"], perm=[0, 2, 1])
+ x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
+ output = run_node(node_def, [x])
+ np.testing.assert_almost_equal(output["Y"], np.transpose(x, (0, 2, 1)))
+
+ def test_topk(self):
+ x = np.arange(15, dtype=np.float32).reshape(3, 5)
+ values = np.array([[4, 3], [9, 8], [14, 13]], dtype=np.float32)
+ indices = np.array([[4, 3], [4, 3], [4, 3]], dtype=np.int64)
+ if legacy_opset_pre_ver(10): # for opset = 1
+ node_def = helper.make_node("TopK", ["x"], ["values", "indices"], k=2)
+ output = run_node(node_def, [x])
+ elif legacy_opset_pre_ver(11): # for opset = 10
+ k = np.array([2], dtype=np.int64)
+ node_def = helper.make_node("TopK", ["x", "k"], ["values", "indices"])
+ output = run_node(node_def, [x, k])
+ else: # for opset = 11
+ x = np.array([[3, 2, 5, 10, 7], [12, 15, 10, 7, 20], [21, 16, 5, 3, 6]],
+ dtype=np.float32)
+ values = np.array([[3, 2], [10, 7], [5, 3]], dtype=np.float32)
+ indices = np.array([[0, 1], [2, 3], [2, 3]], dtype=np.int64)
+ k = np.array([2], dtype=np.int64)
+ node_def = helper.make_node("TopK", ["x", "k"], ["values", "indices"],
+ largest=0,
+ sorted=0)
+ output = run_node(node_def, [x, k])
+ np.testing.assert_almost_equal(output["values"], values)
+ np.testing.assert_almost_equal(output["indices"], indices)
+
+ def test_where(self):
+ if legacy_opset_pre_ver(9):
+ raise unittest.SkipTest("ONNX version {} doesn't support Where.".format(
+ defs.onnx_opset_version()))
+ node_def = helper.make_node("Where", ["C", "X", "Y"], ["Z"])
+ c = np.array([[1, 0], [1, 1]], dtype=np.bool)
+ x = np.array([[1, 2], [3, 4]], dtype=np.float32)
+ y = np.array([[9, 8], [7, 6]], dtype=np.float32)
+ output = run_node(node_def, [c, x, y])
+ np.testing.assert_almost_equal(output["Z"], np.where(c, x, y))
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pt2tf/onnx-tensorflow/test/backend/test_onnx_backend.py b/pt2tf/onnx-tensorflow/test/backend/test_onnx_backend.py
new file mode 100644
index 0000000..2266427
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/backend/test_onnx_backend.py
@@ -0,0 +1,119 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import os
+import re
+import unittest
+
+import onnx.backend.test
+
+from onnx import defs
+
+from onnx_tf import opset_version
+from onnx_tf.backend import TensorflowBackend
+from onnx_tf.common.legacy import legacy_onnx_pre_ver
+from onnx_tf.common.legacy import legacy_opset_pre_ver
+
+def get_onnxtf_supported_ops():
+ return opset_version.backend_opset_version
+
+def get_onnx_supported_ops():
+ onnx_ops_dict = {}
+ for schema in defs.get_all_schemas():
+ onnx_ops_dict[schema.name] = {
+ 'version': schema.since_version,
+ 'deprecated': schema.deprecated
+ }
+ return onnx_ops_dict
+
+# This is a pytest magic variable to load extra plugins
+pytest_plugins = 'onnx.backend.test.report',
+
+backend_test = onnx.backend.test.BackendTest(TensorflowBackend, __name__)
+
+# The test cases excluded below should be considered permanent restrictions
+# based on the TensorFlow implementation. Unimplemented operators will raise
+# a BackendIsNotSupposedToImplementIt exception so that their test cases
+# will pass and show a verbose message stating it was effectively skipped.
+
+# https://github.com/onnx/onnx/issues/349
+backend_test.exclude(r'[a-z,_]*GLU[a-z,_]*')
+
+# TF does not support dialation and strides at the same time:
+# Will produce strides > 1 not supported in conjunction with dilation_rate > 1
+backend_test.exclude(r'[a-z,_]*dilated_strided[a-z,_]*')
+backend_test.exclude(r'[a-z,_]*Conv2d_dilated[a-z,_]*')
+
+# TF does not have column major max_pool_with_argmax
+backend_test.exclude(
+ r'[a-z,_]*maxpool_with_argmax_2d_precomputed_strides[a-z,_]*')
+
+# PRelu OnnxBackendPyTorchConvertedModelTest has wrong dim for broadcasting
+backend_test.exclude(r'[a-z,_]*PReLU_[0-9]d_multiparam[a-z,_]*')
+
+# TF does not support int8, int16, uint8, uint16, uint32, uint64 for
+# tf.floormod and tf.truncatemod
+backend_test.exclude(r'test_mod_[a-z,_]*uint[0-9]+')
+backend_test.exclude(r'test_mod_[a-z,_]*int(8|(16))+')
+
+# TF only support uint8, int32, int64 for indices and int32 for depth in
+# tf.one_hot
+backend_test.exclude(r'test_onehot_[a-z,_]*')
+
+# TF doesn't support most of the attributes in resize op
+# test_node.py will cover the test
+backend_test.exclude(r'test_resize_[a-z,_]*')
+
+# range is using loop in the model test but all the outputs datatype are
+# missing in the body attribute of the loop
+backend_test.exclude(
+ r'test_range_float_type_positive_delta_expanded[a-z,_]*')
+backend_test.exclude(
+ r'test_range_int32_type_negative_delta_expanded[a-z,_]*')
+
+# skip all the cumsum testcases because all the axis in the testcases
+# are created as a 1-D 1 element tensor, but the spec clearly state
+# that axis should be a 0-D tensor(scalar)
+backend_test.exclude(r'test_cumsum_[a-z,_]*')
+
+if legacy_opset_pre_ver(7):
+ backend_test.exclude(r'[a-z,_]*Upsample[a-z,_]*')
+
+if 'TRAVIS' in os.environ:
+ backend_test.exclude('test_vgg19')
+ backend_test.exclude('zfnet512')
+
+if legacy_onnx_pre_ver(1, 2):
+ # These following tests fails by a tiny margin with onnx<1.2:
+ backend_test.exclude('test_operator_add_broadcast_cpu')
+ backend_test.exclude('test_operator_add_size1_broadcast_cpu')
+ backend_test.exclude('test_operator_add_size1_right_broadcast_cpu')
+ backend_test.exclude('test_operator_add_size1_singleton_broadcast_cpu')
+ backend_test.exclude('test_averagepool_3d_default_cpu')
+ # Do not support consumed flag:
+ backend_test.exclude('test_batch_normalization')
+ # Do not support RNN testing on onnx<1.2 due to incorrect tests:
+ backend_test.exclude(r'test_operator_rnn_cpu')
+ backend_test.exclude(r'test_operator_lstm_cpu')
+ backend_test.exclude(r'test_operator_rnn_single_layer_cpu')
+
+# The onnx test for cast, float to string, does not work
+if not legacy_opset_pre_ver(9):
+ backend_test.exclude(r'[a-z,_]*cast[a-z,_]*')
+
+if not legacy_opset_pre_ver(10):
+ # Do not support dilations != 1 for ConvTranspose, test is added in opset 10
+ backend_test.exclude(r'[a-z,_]*convtranspose_dilations[a-z,_]*')
+
+# some NLL test cases do not use the `NegativeLogLikelihoodLoss` operator
+# however they use the `where` operator which has some restrictions in TF 1.x
+# (x,y tensors must have same shape, broadcastable shapes not supported)
+backend_test.exclude(r'test_negative_log_likelihood_loss_[a-z,_]*')
+
+# import all test cases at global scope to make them visible to python.unittest
+globals().update(backend_test.enable_report().test_cases)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pt2tf/onnx-tensorflow/test/download_model.sh b/pt2tf/onnx-tensorflow/test/download_model.sh
new file mode 100644
index 0000000..bf7c4ab
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/download_model.sh
@@ -0,0 +1,28 @@
+mkdir -p ../../onnx_models/
+
+wget https://s3.amazonaws.com/download.onnx/models/bvlc_alexnet.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf bvlc_alexnet.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/densenet121.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf densenet121.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/inception_v1.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf inception_v1.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/inception_v2.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf inception_v2.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/resnet50.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf resnet50.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/shufflenet.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf shufflenet.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/squeezenet.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf squeezenet.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/vgg16.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf vgg16.tar.gz && popd
+
+wget https://s3.amazonaws.com/download.onnx/models/vgg19.tar.gz --directory-prefix=../../onnx_models/
+pushd ../../onnx_models/ && tar -xzf vgg19.tar.gz && popd
\ No newline at end of file
diff --git a/pt2tf/onnx-tensorflow/test/test_cli.py b/pt2tf/onnx-tensorflow/test/test_cli.py
new file mode 100644
index 0000000..8df16c4
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/test/test_cli.py
@@ -0,0 +1,79 @@
+import inspect
+import os
+import subprocess
+import unittest
+
+import onnx
+from onnx.backend.test.runner import Runner
+from onnx.backend.test.case.model import TestCase
+
+from onnx_tf.backend import TensorflowBackend
+from onnx_tf.common import IS_PYTHON3
+from onnx_tf.common.legacy import legacy_onnx_pre_ver
+
+_ONNX_MODELS = [(
+ "mobilenetv2-1.0",
+ "https://s3.amazonaws.com/onnx-model-zoo/mobilenet/mobilenetv2-1.0/mobilenetv2-1.0.tar.gz"
+)]
+
+
+class TestCli(unittest.TestCase):
+
+ @staticmethod
+ def prepare_model(model_name, url):
+ if legacy_onnx_pre_ver(1, 5, 0):
+ prepare_model_data = Runner._prepare_model_data
+ else:
+ prepare_model_data = Runner.prepare_model_data
+ if IS_PYTHON3:
+ params = list(
+ inspect.signature(prepare_model_data).parameters.keys())
+ else:
+ params = inspect.getargspec(prepare_model_data).args
+ runner_class = Runner
+ if params[0] == "self":
+ runner_class = Runner(TensorflowBackend)
+ if legacy_onnx_pre_ver(1, 5, 0):
+ prepare_model_data = runner_class._prepare_model_data
+ else:
+ prepare_model_data = runner_class.prepare_model_data
+ if legacy_onnx_pre_ver(1, 4, 0):
+ tc = TestCase(
+ name="test_{}".format(model_name),
+ model_name=model_name,
+ url=url,
+ model_dir=None,
+ model=None,
+ data_sets=None,
+ kind='real')
+ else:
+ tc = TestCase(
+ name="test_{}".format(model_name),
+ model_name=model_name,
+ url=url,
+ model_dir=None,
+ model=None,
+ data_sets=None,
+ kind='real',
+ rtol=1e-3,
+ atol=1e-7)
+ return prepare_model_data(model_test=tc)
+
+ def test_convert_to_tf(self):
+ if legacy_onnx_pre_ver(1, 2, 1):
+ raise unittest.SkipTest(
+ "The current version of ONNX uses dead model link.")
+ for model_name, url in _ONNX_MODELS:
+ model_dir = self.prepare_model(model_name, url)
+ subprocess.check_call([
+ "onnx-tf",
+ "convert",
+ "-i",
+ os.path.join(model_dir, '{}.onnx'.format(model_name)),
+ "-o",
+ os.path.join(model_dir, '{}.pb'.format(model_name)),
+ ])
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/pt2tf/onnx-tensorflow/third_party/__init__.py b/pt2tf/onnx-tensorflow/third_party/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/pt2tf/onnx-tensorflow/third_party/get_info.py b/pt2tf/onnx-tensorflow/third_party/get_info.py
new file mode 100644
index 0000000..4aa20bb
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/third_party/get_info.py
@@ -0,0 +1,110 @@
+# Copyright 2015: Mirantis Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+import sys
+
+PARAM_OR_RETURNS_REGEX = re.compile(":(?:param|returns)")
+RETURNS_REGEX = re.compile(":returns: (?P.*)", re.S)
+PARAM_REGEX = re.compile(":param (?P[\*\w]+): (?P.*?)"
+ "(?:(?=:param)|(?=:return)|(?=:raises)|\Z)", re.S)
+
+
+def trim(docstring):
+ """trim function from PEP-257"""
+ if not docstring:
+ return ""
+ # Convert tabs to spaces (following the normal Python rules)
+ # and split into a list of lines:
+ lines = docstring.expandtabs().splitlines()
+ # Determine minimum indentation (first line doesn't count):
+ indent = sys.maxsize
+ for line in lines[1:]:
+ stripped = line.lstrip()
+ if stripped:
+ indent = min(indent, len(line) - len(stripped))
+ # Remove indentation (first line is special):
+ trimmed = [lines[0].strip()]
+ if indent < sys.maxsize:
+ for line in lines[1:]:
+ trimmed.append(line[indent:].rstrip())
+ # Strip off trailing and leading blank lines:
+ while trimmed and not trimmed[-1]:
+ trimmed.pop()
+ while trimmed and not trimmed[0]:
+ trimmed.pop(0)
+
+ # Current code/unittests expects a line return at
+ # end of multiline docstrings
+ # workaround expected behavior from unittests
+ if "\n" in docstring:
+ trimmed.append("")
+
+ # Return a single string:
+ return "\n".join(trimmed)
+
+
+def reindent(string):
+ return "\n".join(l.strip() for l in string.strip().split("\n"))
+
+
+def parse_docstring(docstring):
+ """Parse the docstring into its components.
+
+ :returns: a dictionary of form
+ {
+ "short_description": ...,
+ "long_description": ...,
+ "params": [{"name": ..., "doc": ...}, ...],
+ "returns": ...
+ }
+ """
+
+ short_description = long_description = returns = ""
+ params = []
+
+ if docstring:
+ docstring = trim(docstring)
+
+ lines = docstring.split("\n", 1)
+ short_description = lines[0]
+
+ if len(lines) > 1:
+ long_description = lines[1].strip()
+
+ params_returns_desc = None
+
+ match = PARAM_OR_RETURNS_REGEX.search(long_description)
+ if match:
+ long_desc_end = match.start()
+ params_returns_desc = long_description[long_desc_end:].strip()
+ long_description = long_description[:long_desc_end].rstrip()
+
+ if params_returns_desc:
+ params = [
+ {"name": name, "doc": trim(doc)}
+ for name, doc in PARAM_REGEX.findall(params_returns_desc)
+ ]
+
+ match = RETURNS_REGEX.search(params_returns_desc)
+ if match:
+ returns = reindent(match.group("doc"))
+
+ return {
+ "short_description": short_description,
+ "long_description": long_description,
+ "params": params,
+ "returns": returns
+ }
diff --git a/pt2tf/onnx-tensorflow/util/get_version.py b/pt2tf/onnx-tensorflow/util/get_version.py
new file mode 100644
index 0000000..26548af
--- /dev/null
+++ b/pt2tf/onnx-tensorflow/util/get_version.py
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+from __future__ import unicode_literals
+
+import sys
+import onnx
+import tensorflow
+import onnx_tf
+
+print("Python version:")
+print(sys.version)
+print("ONNX version:")
+print(onnx.version.version)
+print("ONNX-TF version:")
+print(onnx_tf.__version__)
+print("Tensorflow version:")
+print(tensorflow.__version__)
diff --git a/pt2tf/pt2onnx.py b/pt2tf/pt2onnx.py
new file mode 100644
index 0000000..86ad5cb
--- /dev/null
+++ b/pt2tf/pt2onnx.py
@@ -0,0 +1,19 @@
+import torch
+from efficientnet_pytorch import EfficientNet
+
+# Specify which model to use
+model_name = 'efficientnet-b3'
+image_size = EfficientNet.get_image_size(model_name)
+print('Image size: ', image_size)
+
+# Load model
+model = EfficientNet.from_pretrained(model_name)
+model.set_swish(memory_efficient=False)
+model.eval()
+print('Model image size: ', model._global_params.image_size)
+
+# Dummy input for ONNX
+dummy_input = torch.randn(1, 3, 300, 300)
+
+# Export with ONNX
+torch.onnx.export(model, dummy_input, f"{model_name}.onnx", verbose=True)
diff --git a/pt2tf/requirements.txt b/pt2tf/requirements.txt
new file mode 100644
index 0000000..4ef847f
--- /dev/null
+++ b/pt2tf/requirements.txt
@@ -0,0 +1,30 @@
+absl-py==0.10.0
+astor==0.8.1
+efficientnet-pytorch==0.7.0
+future==0.18.2
+gast==0.2.2
+google-pasta==0.2.0
+grpcio==1.32.0
+h5py==2.10.0
+importlib-metadata==1.7.0
+Keras-Applications==1.0.8
+Keras-Preprocessing==1.1.2
+Markdown==3.2.2
+numpy==1.19.2
+onnx==1.7.0
+-e git+https://github.com/onnx/onnx-tensorflow.git@6b9e76d6fca74d5ddbf47049b9670120abaaf70f#egg=onnx_tf
+opt-einsum==3.3.0
+Pillow==7.2.0
+protobuf==3.13.0
+PyYAML==5.3.1
+six==1.15.0
+tensorboard==1.15.0
+tensorflow==1.15.0
+tensorflow-estimator==1.15.1
+termcolor==1.1.0
+torch==1.6.0
+torchvision==0.7.0
+typing-extensions==3.7.4.3
+Werkzeug==1.0.1
+wrapt==1.12.1
+zipp==3.1.0