Repository: PaddlePaddle/Paddle2ONNX

Issue:
Add some common used operators' conversion 



Discussion:

Resolving patch:
diff --git a/fluid_onnx/ops.py b/fluid_onnx/ops.py
--- a/fluid_onnx/ops.py
+++ b/fluid_onnx/ops.py
@@ -13,6 +13,7 @@
 # limitations under the License.
 
 import sys
+from functools import partial
 from onnx import TensorProto
 from onnx.helper import make_node, make_tensor
 from paddle.fluid.executor import fetch_var
@@ -56,8 +57,10 @@
 """
 
 
-def abs_op():
-    pass
+def activation_ops(act_type, operator, block):
+    inputs, _, outputs = op_io_info(operator)
+    return make_node(
+        act_type, inputs=inputs.values()[0], outputs=outputs.values()[0])
 
 
 def and_op():
@@ -75,13 +78,6 @@ def argmin_op():
     pass
 
 
-def averagepool_op():
-    """
-    Need to support more pad mode.
-    """
-    pass
-
-
 def batch_norm_op(operator, block):
     inputs, attrs, outputs = op_io_info(operator)
 
@@ -122,10 +118,6 @@ def cast_op():
     pass
 
 
-def ceil_op():
-    pass
-
-
 def clip_op():
     pass
 
@@ -172,10 +164,6 @@ def depthtospace_op():
     pass
 
 
-def div_op():
-    pass
-
-
 def dropout_op(operator, block):
     inputs, attrs, outputs = op_io_info(operator)
     scale_input = [outputs['Out'][0] + '@dropout']
@@ -195,20 +183,10 @@ def dropout_op(operator, block):
     return (dropout_op, scale_op)
 
 
-def elementwise_add_op(operator, block):
-    inputs, attrs, outputs = op_io_info(operator)
-    return make_node(
-        'Add',
-        inputs=inputs['X'] + inputs['Y'],
-        outputs=outputs['Out'],
-        axis=attrs['axis'],
-        broadcast=1)
-
-
-def elementwise_mul_op(operator, block):
+def elementwise_ops(op_type, operator, block):
     inputs, attrs, outputs = op_io_info(operator)
     return make_node(
-        'Mul',
+        op_type,
         inputs=inputs['X'] + inputs['Y'],
         outputs=outputs['Out'],
         axis=attrs['axis'],
@@ -223,18 +201,10 @@ def equal_op():
     pass
 
 
-def exp_op():
-    pass
-
-
 def flatten_op():
     pass
 
 
-def floor_op():
-    pass
-
-
 def gru_op():
     pass
 
@@ -247,23 +217,22 @@ def gemm_op():
     pass
 
 
-def globalaveragepool_op():
-    pass
-
-
 def globallppool_op():
     pass
 
 
-def globalmaxpool_op():
-    pass
-
-
 def greater_op():
     pass
 
 
-def hardsigmoid_op():
+def hardsigmoid_op(operator, block):
+    inputs, attrs, outputs = op_io_info(operator)
+    return make_node(
+        'HardSigmoid',
+        inputs=inputs['X'],
+        outputs=outputs['Out'],
+        alpha=0.2,
+        beta=0.5)
     pass
 
 
@@ -370,13 +339,6 @@ def max_op():
     pass
 
 
-def maxpool_op():
-    """
-    Need to support broadcast.
-    """
-    pass
-
-
 def maxroipool_op():
     pass
 
@@ -459,8 +421,29 @@ def randomuniformlike_op():
     pass
 
 
-def reciprocal_op():
-    pass
+def reduce_ops(op_type, operator, block):
+    inputs, attrs, outputs = op_io_info(operator)
+    rank = len(block.vars[inputs['X'][0]].shape)
+    dim = attrs['dim']
+    axes = [dim if dim >= 0 else rank + dim]
+    reduce_out = [outputs['Out'][0] + '@reduce_0'] if attrs[
+        'reduce_all'] else outputs
+    reduce_node = make_node(
+        op_type,
+        inputs=inputs['X'],
+        outputs=reduce_out,
+        axes=axes,
+        keepdims=attrs['keep_dim'])
+    if attrs['reduce_all'] is True:
+        axes = range(rank) if attrs['keep_dim'] else range(rank - 1)
+        reduce_all_node = make_node(
+            op_type,
+            inputs=reduce_out,
+            outputs=outputs,
+            axes=axes,
+            keepdims=attrs['keep_dim'])
+        return (reduce_node, reduce_all_node)
+    return reduce_node
 
 
 def reducel1_op():
@@ -479,35 +462,14 @@ def reducelogsumexp_op():
     pass
 
 
-def reducemax_op():
-    pass
-
-
-def reducemean_op():
-    pass
-
-
-def reducemin_op():
-    pass
-
-
 def reduceprod_op():
     pass
 
 
-def reducesum_op():
-    pass
-
-
 def reducesumsquare_op():
     pass
 
 
-def relu_op(operator, block):
-    inputs, _, outputs = op_io_info(operator)
-    return make_node('Relu', inputs=inputs['X'], outputs=outputs['Out'])
-
-
 def reshape_op():
     pass
 
@@ -520,11 +482,6 @@ def shape_op():
     pass
 
 
-def sigmoid_op(operator, block):
-    inputs, _, outputs = op_io_info(operator)
-    return make_node('Sigmoid', inputs=inputs['X'], outputs=outputs['Out'])
-
-
 def size_op():
     pass
 
@@ -538,14 +495,6 @@ def softmax_op(operator, block):
     return make_node('Softmax', inputs=inputs['X'], outputs=outputs['Out'])
 
 
-def softplus_op():
-    pass
-
-
-def softsign_op():
-    pass
-
-
 def spacetodepth_op():
     pass
 
@@ -570,11 +519,6 @@ def sum_op():
     pass
 
 
-def tanh_op(operator, block):
-    inputs, attrs, outputs = op_io_info(operator)
-    return make_node('Tanh', inputs=inputs['X'], outputs=outputs['Out'])
-
-
 def tile_op():
     pass
 
@@ -604,18 +548,14 @@ def xor_op():
 
 node_maker = {
     # Paddle op name : (ONNX op name, modifier)
-    'abs': ('Abs', abs_op),
-    'elementwise_add': elementwise_add_op,
-    'elementwise_mul': elementwise_mul_op,
-
+    'abs': partial(activation_ops, 'Abs'),
     # '': 'And', # ?
     # 'ArgMax', NEEDS ATTENTION.
     # 'ArgMin', NEEDS ATTENTION.
-    '': ('AveragePool', averagepool_op),
     'batch_norm': batch_norm_op,
     'cast': ('Cast', cast_op),
-    # 'Ceil', NEEDS ATTENTION.
-    'cast': ('Clip', clip_op),
+    'ceil': partial(activation_ops, 'Ceil'),
+    'clip': ('Clip', clip_op),
     'concat': ('Concat', concat_op),
     'constant': constant_op,
     'conv2d': conv2d_op,
@@ -624,28 +564,30 @@ def xor_op():
     '': 'ConvTranspose',
     '': 'DepthToSpace',
     'depthwise_conv2d': conv2d_op,
-    '': 'Div',
     'dropout': dropout_op,
+    'elementwise_add': partial(elementwise_ops, 'Add'),
+    'elementwise_div': partial(elementwise_ops, 'Div'),
+    'elementwise_mul': partial(elementwise_ops, 'Mul'),
+    'elementwise_pow': partial(elementwise_ops, 'Pow'),
+    'elementwise_sub': partial(elementwise_ops, 'Sub'),
     '': 'Elu',
     '': 'Equal',
-    '': 'Exp',
+    'exp': partial(activation_ops, 'Exp'),
     '': 'Flatten',
-    # 'Floor', NEEDS ATTENTION.
+    'floor': partial(activation_ops, 'Floor'),
     '': 'GRU',
     '': 'Gather',
     '': 'Gemm',
-    '': 'GlobalAveragePool',
     '': 'GlobalLpPool',
-    '': 'GlobalMaxPool',
     '': 'Greater',
-    '': 'HardSigmoid',
+    'hard_sigmoid': 'HardSigmoid',  # Caffe2 error
     # 'Hardmax', NEEDS ATTENTION.
     # 'InstanceNormalization', NEEDS ATTENTION.
     '': 'LRN',
     '': 'LSTM',
     '': 'LeakyRelu',
     '': 'Less',
-    '': 'Log',
+    'log': partial(activation_ops, 'Log'),
     ',': 'LogSoftmax',
     '': 'LpNormalization',
     '': 'LpPool',
@@ -662,40 +604,38 @@ def xor_op():
     '': 'PRelu',
     '': 'Pad',
     'pool2d': pool2d_op,
-    '': 'Pow',
     ',': 'RNN',
     '': 'RandomNormal',
     # 'RandomNormalLike', NEEDS ATTENTION.
     # 'RandomUniform', NEEDS ATTENTION.
     # 'RandomUniformLike', NEEDS ATTENTION.
-    '': 'Reciprocal',
+    'reciprocal': partial(activation_ops, 'Reciprocal'),
     '': 'ReduceL1',
     '': 'ReduceL2',
     ',': 'ReduceLogSum',
     ',': 'ReduceLogSumExp',
-    '': 'ReduceMax',
-    '': 'ReduceMean',
-    '': 'ReduceMin',
-    # 'ReduceProd', NEEDS ATTENTION.
-    '': 'ReduceSum',
+    'reduce_max': partial(reduce_ops, 'ReduceMax'),
+    'reduce_mean': partial(reduce_ops, 'ReduceMean'),
+    'reduce_min': partial(reduce_ops, 'ReduceMin'),
+    '': partial(reduce_ops, 'ReduceProd'),  # Caffe2 error
+    'reduce_sum': partial(reduce_ops, 'ReduceSum'),
     ',': 'ReduceSumSquare',
-    'relu': relu_op,
+    'relu': partial(activation_ops, 'Relu'),
     '': 'Reshape',
     # 'Selu', NEEDS ATTENTION.
     '': 'Shape',
-    'sigmoid': sigmoid_op,
+    'sigmoid': partial(activation_ops, 'Sigmoid'),
     '': 'Size',
     # 'Slice', NEEDS ATTENTION.
     'softmax': softmax_op,
-    '': 'Softplus',
-    '': 'Softsign',
+    'softplus': partial(activation_ops, 'Softplus'),
+    'softsign': partial(activation_ops, 'Softsign'),
     '': 'SpaceToDepth',
     '': 'Split',
-    '': 'Sqrt',
+    'sqrt': partial(activation_ops, 'Sqrt'),
     # 'Squeeze', NEEDS ATTENTION.
-    'elementwise_sub': ('Sub', sub_op),
     '': 'Sum',
-    'tanh': tanh_op,
+    'tanh': partial(activation_ops, 'Tanh'),
     '': 'Tile',
     '': 'TopK',
     '': 'Transpose',


Test patch:
diff --git a/tests/test_activation_ops.py b/tests/test_activation_ops.py
new file mode 100644
--- /dev/null
+++ b/tests/test_activation_ops.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2018 PaddlePaddle Authors. 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 unittest
+import numpy as np
+from op_test import OpTest
+
+
+class TestAbsOp(OpTest):
+    def setUp(self):
+        X = np.random.random((13, 15)).astype("float32")
+        self.inputs = {'X': X}
+        self.outputs = {'Out': np.zeros((1, 1)).astype("float32")}
+        self.init_op_type()
+
+    def init_op_type(self):
+        self.op_type = 'abs'
+
+    def test_check_output(self):
+        self.check_output()
+
+
+class TestCeilOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'ceil'
+
+
+class TestExpOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'exp'
+
+
+class TestFloorOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'floor'
+
+
+class TestLogOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'log'
+
+
+class TestReciprocalOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'reciprocal'
+
+
+class TestReluOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'relu'
+
+
+class TestSigmoidOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'sigmoid'
+
+
+class TestSoftplusOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'softplus'
+
+
+class TestSoftsignOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'softsign'
+
+
+class TestSqrtOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'sqrt'
+
+
+class TestTanhOp(TestAbsOp):
+    def init_op_type(self):
+        self.op_type = 'tanh'
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test_elementwise_mul_op.py b/tests/test_elementwise_mul_op.py
deleted file mode 100644
--- a/tests/test_elementwise_mul_op.py
+++ /dev/null
@@ -1,46 +0,0 @@
-#  Copyright (c) 2018 PaddlePaddle Authors. 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 unittest
-import numpy as np
-from op_test import OpTest
-
-
-class TestElementwiseMulOp(OpTest):
-    def setUp(self):
-        self.op_type = "elementwise_mul"
-        self.inputs = {
-            'X': np.random.uniform(0.1, 1, [11, 13]).astype(np.float32),
-            'Y': np.random.uniform(0.1, 1, [11, 13]).astype(np.float32)
-        }
-        self.outputs = {'Out': np.zeros((1, 1))}
-
-    def test_check_output(self):
-        self.check_output()
-
-
-class TestElementwiseMulOp_broadcast(TestElementwiseMulOp):
-    def setUp(self):
-        self.op_type = "elementwise_mul"
-        self.inputs = {
-            'X': np.random.rand(2, 3, 4, 5).astype(np.float64),
-            'Y': np.random.rand(3, 4).astype(np.float64)
-        }
-
-        self.attrs = {'axis': 1}
-        self.outputs = {'Out': np.zeros((1, 1))}
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/tests/test_elementwise_add_op.py b/tests/test_elementwise_ops.py
similarity index 60%
rename from tests/test_elementwise_add_op.py
rename to tests/test_elementwise_ops.py
--- a/tests/test_elementwise_add_op.py
+++ b/tests/test_elementwise_ops.py
@@ -19,19 +19,42 @@
 
 class TestElementwiseAddOp(OpTest):
     def setUp(self):
-        self.op_type = "elementwise_add"
+        self.init_op_type()
         self.attrs = {"axis": 1}
 
         self.inputs = {
-            'X': np.random.random((2, 1)).astype(np.float32),
-            'Y': np.random.random((1, )).astype(np.float32)
+            'X': np.random.random((4, 2)).astype(np.float32),
+            'Y': np.random.random((2, )).astype(np.float32)
         }
 
         self.outputs = {'Out': np.zeros((1, 1))}
 
+    def init_op_type(self):
+        self.op_type = "elementwise_add"
+
     def test_check_output(self):
         self.check_output()
 
 
+class TestElementwiseSubOp(TestElementwiseAddOp):
+    def init_op_type(self):
+        self.op_type = "elementwise_sub"
+
+
+class TestElementwiseMulOp(TestElementwiseAddOp):
+    def init_op_type(self):
+        self.op_type = "elementwise_mul"
+
+
+class TestElementwiseDivOp(TestElementwiseAddOp):
+    def init_op_type(self):
+        self.op_type = "elementwise_div"
+
+
+class TestElementwisePowOp(TestElementwiseAddOp):
+    def init_op_type(self):
+        self.op_type = "elementwise_pow"
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/test_reduce_ops.py b/tests/test_reduce_ops.py
new file mode 100644
--- /dev/null
+++ b/tests/test_reduce_ops.py
@@ -0,0 +1,74 @@
+#   Copyright (c) 2018 PaddlePaddle Authors. 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 unittest
+import numpy as np
+from op_test import OpTest
+
+
+class TestReduceSumOp(OpTest):
+    def setUp(self):
+        self.init_op_type()
+        self.init_keep_dim()
+        self.init_reduce_all()
+        self.inputs = {'X': np.random.random((5, 6, 7, 8)).astype("float32")}
+        self.attrs = {
+            'dim': 2,
+            'keep_dim': self.keep_dim,
+            'reduce_all': self.reduce_all
+        }
+        self.outputs = {'Out': np.zeros((1, 1))}
+
+    def init_op_type(self):
+        self.op_type = "reduce_sum"
+
+    def init_keep_dim(self):
+        self.keep_dim = True
+
+    def init_reduce_all(self):
+        self.reduce_all = False
+
+    def test_check_output(self):
+        self.check_output(decimal=4)
+
+
+class TestReduceMeanOp(TestReduceSumOp):
+    def init_op_type(self):
+        self.op_type = "reduce_mean"
+
+    def init_reduce_all(self):
+        self.reduce_all = True
+
+
+class TestReduceMaxOp(TestReduceSumOp):
+    def init_op_type(self):
+        self.op_type = "reduce_max"
+
+    def init_keep_dim(self):
+        self.keep_dim = False
+
+    def init_reduce_all(self):
+        self.reduce_all = True
+
+
+class TestReduceMinOp(TestReduceSumOp):
+    def init_op_type(self):
+        self.op_type = "reduce_min"
+
+    def init_keep_dim(self):
+        self.keep_dim = False
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/test_tanh_op.py b/tests/test_tanh_op.py
deleted file mode 100644
--- a/tests/test_tanh_op.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright (c) 2018 PaddlePaddle Authors. 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 unittest
-import numpy as np
-from op_test import OpTest
-
-
-class TestTanhOp(OpTest):
-    def setUp(self):
-        X = np.random.random((3, 5)).astype("float32")
-        self.inputs = {'X': X}
-        self.outputs = {'Out': np.random.random((1, 1)).astype("float32")}
-        self.op_type = 'tanh'
-
-    def test_check_output(self):
-        self.check_output()
-
-
-if __name__ == '__main__':
-    unittest.main()