diff --git a/dnnv/_manage/linux/verifiers/nnenum.py b/dnnv/_manage/linux/verifiers/nnenum.py index 9e868876..fae14f5a 100644 --- a/dnnv/_manage/linux/verifiers/nnenum.py +++ b/dnnv/_manage/linux/verifiers/nnenum.py @@ -105,14 +105,15 @@ def run(self, env: Environment, dependency: Dependency): "pip install --upgrade pip", ( "pip install" - ' "numpy>=1.19,<1.22"' - ' "onnx>=1.8,<1.11"' - ' "onnxruntime>=1.7,<1.11"' - ' "scipy>=1.4.1<1.8"' - ' "threadpoolctl==2.1.0"' - ' "skl2onnx==1.7.0"' - ' "swiglpk"' - ' "termcolor"' + ' "numpy>=1.21,<1.24"' + ' "onnx>=1.10,<1.12"' + ' "onnxruntime>=1.10,<1.13"' + ' "scipy>=1.7<1.10"' + ' "threadpoolctl==3.1.0"' + ' "skl2onnx==1.12"' + ' "swiglpk~=5.0"' + ' "termcolor==1.1.0"' + ' "packaging==21.3"' ' "protobuf<=3.20"' ), f"cd {cache_dir}", diff --git a/dnnv/_manage/linux/verifiers/verinet.py b/dnnv/_manage/linux/verifiers/verinet.py index fc305167..251f5287 100644 --- a/dnnv/_manage/linux/verifiers/verinet.py +++ b/dnnv/_manage/linux/verifiers/verinet.py @@ -123,6 +123,7 @@ def run(self, env: Environment, dependency: Dependency): "pip install" ' "numba>=0.50,<0.60"' ' "onnx>=1.8,<1.11"' + ' "protobuf<=3.20"' ' "torch>=1.8,<1.9"' ' "torchvision>=0.9,<0.10"' ), diff --git a/dnnv/nn/converters/onnx.py b/dnnv/nn/converters/onnx.py index 39d73c53..48f4c9e5 100644 --- a/dnnv/nn/converters/onnx.py +++ b/dnnv/nn/converters/onnx.py @@ -11,6 +11,10 @@ from ..visitors import OperationVisitor +class OnnxConverterError(Exception): + pass + + def convert(op_graph: OperationGraph, *, add_missing_optional_inputs=False): converter = OnnxConverter( op_graph, add_missing_optional_inputs=add_missing_optional_inputs @@ -79,7 +83,7 @@ def _to_onnx_proto( ) -> Union[onnx.NodeProto, onnx.TensorProto, onnx.ValueInfoProto]: if isinstance(value, Operation): return self.visit(value) - if isinstance(value, np.ndarray): + if isinstance(value, (np.ndarray, np.number)): tensor_proto = onnx.numpy_helper.from_array(value, name=opname) self.initializer.append(tensor_proto) return tensor_proto @@ -184,6 +188,29 @@ def visit_Cast(self, operation: operations.Cast) -> onnx.NodeProto: return node + def visit_Clip(self, operation: operations.Clip) -> onnx.NodeProto: + op_type = str(operation) + idx = self.op_counts[op_type] = self.op_counts[op_type] + 1 + opname = f"{op_type}_{idx}" + + x = self._to_onnx_proto(operation.x, f"{opname}.x") + inputs = [x.name] + if operation.min is not None: + min = self._to_onnx_proto(operation.min, f"{opname}.min") + inputs.extend([min.name]) + if operation.max is not None: + max = self._to_onnx_proto(operation.max, f"{opname}.max") + inputs.extend([max.name]) + + node = onnx.helper.make_node( + op_type, + inputs=inputs, + outputs=[opname], + name=opname, + ) + + return node + def visit_Concat(self, operation: operations.Concat) -> onnx.NodeProto: idx = self.op_counts["Concat"] = self.op_counts["Concat"] + 1 opname = f"Concat_{idx}" @@ -508,6 +535,26 @@ def visit_OutputSelect(self, operation: operations.OutputSelect) -> onnx.NodePro return node + def visit_ReduceL2(self, operation: operations.ReduceL2) -> onnx.NodeProto: + op_type = str(operation) + idx = self.op_counts[op_type] = self.op_counts[op_type] + 1 + opname = f"{op_type}_{idx}" + + x = self._to_onnx_proto(operation.x, f"{opname}.x") + axes = operation.axes + keepdims = operation.keepdims + + node = onnx.helper.make_node( + op_type, + inputs=[x.name], + axes=axes, + keepdims=keepdims, + outputs=[opname], + name=opname, + ) + + return node + def visit_Relu(self, operation: operations.Relu) -> onnx.NodeProto: idx = self.op_counts["Relu"] = self.op_counts["Relu"] + 1 opname = f"Relu_{idx}" @@ -576,6 +623,25 @@ def visit_Split(self, operation: operations.Split) -> onnx.NodeProto: return node + def visit_Squeeze(self, operation: operations.Squeeze) -> onnx.NodeProto: + op_type = str(operation) + idx = self.op_counts[op_type] = self.op_counts[op_type] + 1 + opname = f"{op_type}_{idx}" + + x = self._to_onnx_proto(operation.x, f"{opname}.x") + inputs = [x.name] + if operation.axes is not None: + axes = self._to_onnx_proto(operation.axes, f"{opname}.axes") + if axes.data_type != onnx.TensorProto.INT64: + raise OnnxConverterError("Squeeze axes should be int64.") + inputs.extend([axes.name]) + + node = onnx.helper.make_node( + op_type, inputs=inputs, outputs=[opname], name=opname + ) + + return node + def visit_Slice(self, operation: operations.Slice) -> onnx.NodeProto: op_type = str(operation) idx = self.op_counts[op_type] = self.op_counts[op_type] + 1 @@ -648,3 +714,21 @@ def visit_Transpose(self, operation: operations.Transpose) -> onnx.NodeProto: ) return node + + def visit_Upsample(self, operation: operations.Upsample) -> onnx.NodeProto: + op_type = str(operation) + idx = self.op_counts[op_type] = self.op_counts[op_type] + 1 + opname = f"{op_type}_{idx}" + + x = self._to_onnx_proto(operation.x, f"{opname}.x") + inputs = [x.name] + if operation.scales is not None: + scales = self._to_onnx_proto(operation.scales, f"{opname}.scales") + inputs.extend([scales.name]) + mode = operation.mode + + node = onnx.helper.make_node( + op_type, inputs=inputs, outputs=[opname], name=opname, mode=mode + ) + + return node diff --git a/dnnv/nn/converters/tensorflow.py b/dnnv/nn/converters/tensorflow.py index 8c0bd3b8..551fb447 100644 --- a/dnnv/nn/converters/tensorflow.py +++ b/dnnv/nn/converters/tensorflow.py @@ -208,6 +208,21 @@ def cast_func(*inputs): return cast_func + def visit_Clip(self, operation): + x_ = operation.x + if isinstance(x_, Operation): + x_ = self.visit(x_) + _min = operation.min + _max = operation.max + + @self._cached + def clip_func(*inputs): + x = _concretize([x_], inputs) + x = tf.clip_by_value(x, _min, _max) + return x + + return clip_func + def visit_Concat(self, operation): tensors_ = [] for x in operation.x: @@ -227,20 +242,24 @@ def visit_Conv(self, operation): x_ = operation.x if isinstance(x_, Operation): x_ = self.visit(x_) + w_ = operation.w + if isinstance(w_, Operation): + w_ = self.visit(w_) @self._cached def conv_func(*inputs): - x = _concretize([x_], inputs) + x, weights = _concretize([x_, w_], inputs) if len(operation.kernel_shape) != 2: raise NotImplementedError( "Non 2d convolutions are not currently supported." ) - weights = operation.w + if not isinstance(weights, np.ndarray): + weights = np.array(weights) if operation.b is not None: bias = operation.b else: bias = np.zeros((weights.shape[0],), dtype=weights.dtype) - assert np.all(operation.dilations == 1) + assert np.all(operation.group == 1) num_pads = len(operation.pads) pads = tuple( zip( @@ -258,6 +277,7 @@ def conv_func(*inputs): weights.transpose((2, 3, 1, 0)), operation.strides, padding="VALID", + dilations=operation.dilations, ), bias, ) @@ -417,11 +437,13 @@ def visit_Expand(self, operation): x_ = operation.x if isinstance(x_, Operation): x_ = self.visit(x_) + shape_ = operation.shape + if isinstance(shape_, Operation): + shape_ = self.visit(shape_) @self._cached def expand_func(*inputs): - x = _concretize([x_], inputs) - shape = operation.shape + x, shape = _concretize([x_, shape_], inputs) result = x * tf.ones(shape, x.dtype) return result @@ -685,6 +707,21 @@ def pad_func(*inputs): return pad_func + def visit_ReduceL2(self, operation): + x_ = operation.x + if isinstance(x_, Operation): + x_ = self.visit(x_) + axes = operation.axes + keepdims = operation.keepdims + + @self._cached + def reduceL2_func(*inputs): + x = _concretize([x_], inputs) + x = tf.norm(x, ord=2, axis=axes, keepdims=keepdims) + return x + + return reduceL2_func + def visit_Relu(self, operation): x_ = operation.x if isinstance(x_, Operation): @@ -738,6 +775,7 @@ def resize_func(*inputs): assert operation.coordinate_transformation_mode in [ "asymmetric", "tf_crop_and_resize", + "align_corners", ] assert operation.mode in ["nearest", "linear"] assert operation.exclude_outside == 0 @@ -873,6 +911,22 @@ def split_func(*inputs): return split_func + def visit_Squeeze(self, operation): + x_ = operation.x + if isinstance(x_, Operation): + x_ = self.visit(x_) + axes = operation.axes + if isinstance(axes, np.ndarray): + axes = axes.tolist() + + @self._cached + def squeeze_func(*inputs): + x = _concretize([x_], inputs) + x = tf.squeeze(x, axis=axes) + return x + + return squeeze_func + def visit_Sub(self, operation): a_ = operation.a if isinstance(a_, Operation): @@ -948,3 +1002,21 @@ def unsqueeze_func(*inputs): return x return unsqueeze_func + + def visit_Upsample(self, operation): + x_ = operation.x + if isinstance(x_, Operation): + x_ = self.visit(x_) + scales = operation.scales + mode = operation.mode + + @self._cached + def upsample_func(*inputs): + x = _concretize([x_], inputs) + scaled_dim = [int(sd) for sd in x.shape[2:] * scales[2:]] + xr = tf.transpose(x, perm=[0, 2, 3, 1]) + xr = tf.image.resize(xr, scaled_dim, method="nearest") + xr = tf.transpose(xr, perm=[0, 3, 1, 2]) + return xr + + return upsample_func diff --git a/dnnv/nn/operations/tensor.py b/dnnv/nn/operations/tensor.py index 8ce8bd2d..f87d7ae6 100644 --- a/dnnv/nn/operations/tensor.py +++ b/dnnv/nn/operations/tensor.py @@ -19,6 +19,23 @@ def from_onnx(cls, onnx_node, *inputs): return cls(*inputs, to=to, name=onnx_node.name) +class Clip(Operation): + def __init__(self, x, min=None, max=None, *, name: Optional[str] = None): + super().__init__(name=name) + self.x = x + self.min = min + self.max = max + + @classmethod + def from_onnx(cls, onnx_node, *inputs): + attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} + if len(inputs) == 1: + _min = attributes.get("min") + _max = attributes.get("max") + return cls(*inputs, min=_min, max=_max, name=onnx_node.name) + return cls(*inputs, name=onnx_node.name) + + class Concat(Operation): def __init__(self, x, axis, *, name: Optional[str] = None): super().__init__(name=name) @@ -256,6 +273,48 @@ def from_onnx(cls, onnx_node, *inputs): return cls(*inputs, axes=axes, name=onnx_node.name) +class ReduceL2(Operation): + def __init__(self, x, axes, keepdims, *, name: Optional[str] = None): + super().__init__(name=name) + self.x = x + self.axes = axes + self.keepdims = keepdims + + @classmethod + def from_onnx(cls, onnx_node, *inputs): + attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} + axes = attributes.get("axes") + keepdims = attributes.get("keepdims") + return cls(*inputs, axes=axes, keepdims=keepdims, name=onnx_node.name) + + +class Squeeze(Operation): + def __init__(self, x, axes, *, name: Optional[str] = None): + super().__init__(name=name) + self.x = x + self.axes = axes + + @classmethod + def from_onnx(cls, onnx_node, *inputs): + attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} + axes = attributes.get("axes") + return cls(*inputs, axes=axes, name=onnx_node.name) + + +class Upsample(Operation): + def __init__(self, x, scales, mode, *, name: Optional[str] = None): + super().__init__(name=name) + self.x = x + self.scales = scales + self.mode = mode + + @classmethod + def from_onnx(cls, onnx_node, *inputs): + attributes = {a.name: as_numpy(a) for a in onnx_node.attribute} + mode = attributes.get("mode") + return cls(*inputs, mode=mode, name=onnx_node.name) + + __all__ = [ "Cast", "Concat", @@ -272,4 +331,8 @@ def from_onnx(cls, onnx_node, *inputs): "Tile", "Transpose", "Unsqueeze", + "ReduceL2", + "Clip", + "Squeeze", + "Upsample", ] diff --git a/dnnv/nn/visitors.py b/dnnv/nn/visitors.py index 501c225d..0c984389 100644 --- a/dnnv/nn/visitors.py +++ b/dnnv/nn/visitors.py @@ -132,6 +132,14 @@ def visit_Cast(self, operation: operations.Cast) -> None: self.print_op_id(operation) print(f"Cast({self.get_op_id(operation.x)}, to={operation.to})") + def visit_Clip(self, operation: operations.Clip) -> None: + self.generic_visit(operation) + self.print_op_id(operation) + print( + "Clip(%s, min=%s, max=%s)" + % (self.get_op_id(operation.x), operation.min, operation.max) + ) + def visit_Concat(self, operation: operations.Concat) -> None: self.generic_visit(operation) self.print_op_id(operation) @@ -268,6 +276,14 @@ def visit_Pad(self, operation: operations.Pad) -> None: self.print_op_id(operation) print(f"Pad({self.get_op_id(operation.x)}, pads={operation.pads})") + def visit_ReduceL2(self, operation: operations.ReduceL2) -> None: + self.generic_visit(operation) + self.print_op_id(operation) + print( + "ReduceL2(%s, axes=%s, keepdims=%s)" + % (self.get_op_id(operation.x), operation.axes, operation.keepdims) + ) + def visit_Relu(self, operation: operations.Relu) -> None: self.generic_visit(operation) self.print_op_id(operation) @@ -312,6 +328,11 @@ def visit_Shape(self, operation: operations.Shape) -> None: self.print_op_id(operation) print(f"Shape({self.get_op_id(operation.x)})") + def visit_Sign(self, operation: operations.Sign) -> None: + self.generic_visit(operation) + self.print_op_id(operation) + print(f"Sign({self.get_op_id(operation.x)})") + def visit_Sigmoid(self, operation: operations.Sigmoid) -> None: self.generic_visit(operation) self.print_op_id(operation) @@ -339,11 +360,6 @@ def visit_Slice(self, operation: operations.Slice) -> None: ")" ) - def visit_Sign(self, operation: operations.Sign) -> None: - self.generic_visit(operation) - self.print_op_id(operation) - print(f"Sign({self.get_op_id(operation.x)})") - def visit_Softmax(self, operation: operations.Softmax) -> None: self.generic_visit(operation) self.print_op_id(operation) @@ -357,6 +373,11 @@ def visit_Split(self, operation: operations.Sub) -> None: % (self.get_op_id(operation.x), operation.axis, operation.split) ) + def visit_Squeeze(self, operation: operations.Squeeze) -> None: + self.generic_visit(operation) + self.print_op_id(operation) + print("Squeeze(%s, axes=%s)" % (self.get_op_id(operation.x), operation.axes)) + def visit_Sub(self, operation: operations.Sub) -> None: self.generic_visit(operation) self.print_op_id(operation) @@ -385,6 +406,13 @@ def visit_Unsqueeze(self, operation: operations.Unsqueeze) -> None: self.print_op_id(operation) print(f"Unsqueeze({self.get_op_id(operation.x)}, axes={operation.axes})") + def visit_Upsample(self, operation: operations.Upsample) -> None: + self.generic_visit(operation) + self.print_op_id(operation) + print( + "Upsample(%s, scales=%s)" % (self.get_op_id(operation.x), operation.scales) + ) + __all__ = [ "OperationVisitor", diff --git a/dnnv/properties/transformers/propagate_constants.py b/dnnv/properties/transformers/propagate_constants.py index a5da3d5b..57f73a9a 100644 --- a/dnnv/properties/transformers/propagate_constants.py +++ b/dnnv/properties/transformers/propagate_constants.py @@ -6,6 +6,8 @@ from dnnv.nn.graph import OperationGraph +from dnnv.nn.graph import OperationGraph + from ..expressions import ( ArithmeticExpression, AssociativeExpression, diff --git a/tests/unit_tests/test_nn/test_converters/test_onnx/test_Clip.py b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Clip.py new file mode 100644 index 00000000..4ad67fa1 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Clip.py @@ -0,0 +1,105 @@ +import numpy as np +import onnxruntime.backend + +from dnnv.nn.converters.onnx import * +from dnnv.nn.operations import * + + +def test_Clip(): + x = np.array([-2, 0, 2]).astype(np.float32) + min_val = np.float32(-1) + max_val = np.float32(1) + y = np.clip(x, min_val, max_val) # expected output [-1., 0., 1.] + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + min_val = np.float32(-5) + max_val = np.float32(5) + x = np.array([-1, 0, 1]).astype(np.float32) + y = np.array([-1, 0, 1]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + x = np.array([-6, 0, 6]).astype(np.float32) + y = np.array([-5, 0, 5]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + x = np.array([-1, 0, 6]).astype(np.float32) + y = np.array([-1, 0, 5]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + +def test_Clip_Default(): + min_val = np.float32(0) + max_val = np.inf + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + min_val = -np.inf + max_val = np.float32(0) + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + min_val = None + max_val = None + x = np.array([-1, 0, 1]).astype(np.float32) + y = np.array([-1, 0, 1]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + +def test_Clip_Default_int8(): + min_val = np.int8(0) + max_val = np.int8(np.iinfo(np.int8).max) + x = np.random.randn(3, 4, 5).astype(np.int8) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) + + min_val = np.int8(np.iinfo(np.int8).min) + max_val = np.int8(0) + x = np.random.randn(3, 4, 5).astype(np.int8) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, min_val, max_val]) + assert np.all(output == y) diff --git a/tests/unit_tests/test_nn/test_converters/test_onnx/test_ReduceL2.py b/tests/unit_tests/test_nn/test_converters/test_onnx/test_ReduceL2.py new file mode 100644 index 00000000..61fb2910 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_onnx/test_ReduceL2.py @@ -0,0 +1,128 @@ +import numpy as np +import onnxruntime.backend +import pytest + +from dnnv.nn.converters.onnx import * +from dnnv.nn.operations import * + + +def test_ReduceL2(): + shape = [3, 2, 2] + axes = None + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt(np.sum(a=np.square(data), axis=axes, keepdims=keepdims == 1)) + # [[[25.49509757]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt(np.sum(a=np.square(data), axis=axes, keepdims=keepdims == 1)) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + +@pytest.mark.xfail +def test_ReduceL2_do_not_keep_dims(): + shape = [3, 2, 2] + axes = [2] + keepdims = 0 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[2.23606798, 5.], + # [7.81024968, 10.63014581], + # [13.45362405, 16.2788206]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + +@pytest.mark.xfail +def test_ReduceL2_keep_dims(): + shape = [3, 2, 2] + axes = [2] + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # print(data) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[[2.23606798], [5.]] + # [[7.81024968], [10.63014581]] + # [[13.45362405], [16.2788206 ]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + +@pytest.mark.xfail +def test_ReduceL2_negative_axes_keepdims(): + shape = [3, 2, 2] + axes = [-1] + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[[2.23606798], [5.]] + # [[7.81024968], [10.63014581]] + # [[13.45362405], [16.2788206 ]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [data]) + assert np.all(output == reduced) diff --git a/tests/unit_tests/test_nn/test_converters/test_onnx/test_Squeeze.py b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Squeeze.py new file mode 100644 index 00000000..85eea5d7 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Squeeze.py @@ -0,0 +1,27 @@ +import numpy as np +import onnxruntime.backend + +from dnnv.nn.converters.onnx import * +from dnnv.nn.operations import * + + +def test_Squeeze(): + x = np.random.randn(1, 3, 4, 5).astype(np.float32) + axes = np.array([0], dtype=np.int64) + y = np.squeeze(x, axis=0) + + op = Squeeze(x, axes=axes) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, []) + assert np.all(output == y) + + +def test_Squeeze_negative_axes(): + x = np.random.randn(1, 3, 1, 5).astype(np.float32) + axes = np.array([-2], dtype=np.int64) + y = np.squeeze(x, axis=-2) + + op = Squeeze(x, axes=axes) + onnx_model = convert(OperationGraph([op])) + output = onnxruntime.backend.run(onnx_model, [x, axes]) + assert np.all(output == y) diff --git a/tests/unit_tests/test_nn/test_converters/test_onnx/test_Upsample.py b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Upsample.py new file mode 100644 index 00000000..333a5353 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_onnx/test_Upsample.py @@ -0,0 +1,40 @@ +import numpy as np +import onnxruntime.backend +import pytest + +from dnnv.nn.converters.onnx import * +from dnnv.nn.operations import * + + +@pytest.mark.xfail +def test_Upsample(): + data = np.array( + [ + [ + [ + [1, 2], + [3, 4], + ] + ] + ], + dtype=np.float32, + ) + scales = np.array([1.0, 1.0, 2.0, 3.0], dtype=np.float32) + output = np.array( + [ + [ + [ + [1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2], + [3, 3, 3, 4, 4, 4], + [3, 3, 3, 4, 4, 4], + ] + ] + ], + dtype=np.float32, + ) + + op = Upsample(data, scales=scales, mode="nearest") + onnx_model = convert(OperationGraph([op])) + result = onnxruntime.backend.run(onnx_model, [data, scales], mode="nearest") + assert np.all(result == output) diff --git a/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Clip.py b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Clip.py new file mode 100644 index 00000000..2f9cdc06 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Clip.py @@ -0,0 +1,104 @@ +import numpy as np + +from dnnv.nn.converters.tensorflow import * +from dnnv.nn.operations import * + + +def test_Clip(): + x = np.array([-2, 0, 2]).astype(np.float32) + min_val = np.float32(-1) + max_val = np.float32(1) + y = np.clip(x, min_val, max_val) # expected output [-1., 0., 1.] + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + min_val = np.float32(-5) + max_val = np.float32(5) + x = np.array([-1, 0, 1]).astype(np.float32) + y = np.array([-1, 0, 1]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + x = np.array([-6, 0, 6]).astype(np.float32) + y = np.array([-5, 0, 5]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + x = np.array([-1, 0, 6]).astype(np.float32) + y = np.array([-1, 0, 5]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + +def test_Clip_Default(): + min_val = np.float32(0) + max_val = np.inf + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + min_val = -np.inf + max_val = np.float32(0) + x = np.random.randn(3, 4, 5).astype(np.float32) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + min_val = None + max_val = None + x = np.array([-1, 0, 1]).astype(np.float32) + y = np.array([-1, 0, 1]).astype(np.float32) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + +def test_Clip_Default_int8(): + min_val = np.int8(0) + max_val = np.iinfo(np.int8).max + x = np.random.randn(3, 4, 5).astype(np.int8) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + min_val = np.iinfo(np.int8).min + max_val = np.int8(0) + x = np.random.randn(3, 4, 5).astype(np.int8) + y = np.clip(x, min_val, max_val) + + op = Clip(x, min=min_val, max=max_val) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) diff --git a/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_ReduceL2.py b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_ReduceL2.py new file mode 100644 index 00000000..72c13672 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_ReduceL2.py @@ -0,0 +1,123 @@ +import numpy as np + +from dnnv.nn.converters.tensorflow import * +from dnnv.nn.operations import * + + +def test_ReduceL2(): + shape = [3, 2, 2] + axes = None + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt(np.sum(a=np.square(data), axis=axes, keepdims=keepdims == 1)) + # [[[25.49509757]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt(np.sum(a=np.square(data), axis=axes, keepdims=keepdims == 1)) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + +def test_ReduceL2_do_not_keep_dims(): + shape = [3, 2, 2] + axes = [2] + keepdims = 0 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[2.23606798, 5.], + # [7.81024968, 10.63014581], + # [13.45362405, 16.2788206]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + +def test_ReduceL2_keep_dims(): + shape = [3, 2, 2] + axes = [2] + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # print(data) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[[2.23606798], [5.]] + # [[7.81024968], [10.63014581]] + # [[13.45362405], [16.2788206 ]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + +def test_ReduceL2_negative_axes_keepdims(): + shape = [3, 2, 2] + axes = [-1] + keepdims = 1 + + data = np.reshape(np.arange(1, np.prod(shape) + 1, dtype=np.float32), shape) + # [[[1., 2.], [3., 4.]], [[5., 6.], [7., 8.]], [[9., 10.], [11., 12.]]] + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + # [[[2.23606798], [5.]] + # [[7.81024968], [10.63014581]] + # [[13.45362405], [16.2788206 ]]] + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) + + np.random.seed(0) + data = np.random.uniform(-10, 10, shape).astype(np.float32) + reduced = np.sqrt( + np.sum(a=np.square(data), axis=tuple(axes), keepdims=keepdims == 1) + ) + + op = ReduceL2(data, axes=axes, keepdims=keepdims) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == reduced) diff --git a/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Squeeze.py b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Squeeze.py new file mode 100644 index 00000000..a9bd097f --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Squeeze.py @@ -0,0 +1,26 @@ +import numpy as np + +from dnnv.nn.converters.tensorflow import * +from dnnv.nn.operations import * + + +def test_Squeeze(): + x = np.random.randn(1, 3, 4, 5).astype(np.float32) + axes = 0 + y = np.squeeze(x, axis=axes) + + op = Squeeze(x, axes=axes) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) + + +def test_Squeeze_negative_axes(): + x = np.random.randn(1, 3, 1, 5).astype(np.float32) + axes = -2 + y = np.squeeze(x, axis=axes) + + op = Squeeze(x, axes=axes) + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == y) diff --git a/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Upsample.py b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Upsample.py new file mode 100644 index 00000000..5a43c1f6 --- /dev/null +++ b/tests/unit_tests/test_nn/test_converters/test_tensorflow/test_Upsample.py @@ -0,0 +1,37 @@ +import numpy as np + +from dnnv.nn.converters.tensorflow import * +from dnnv.nn.operations import * + + +def test_Upsample(): + data = np.array( + [ + [ + [ + [1, 2], + [3, 4], + ] + ] + ], + dtype=np.float32, + ) + scales = np.array([1.0, 1.0, 2.0, 3.0], dtype=np.float32) + output = np.array( + [ + [ + [ + [1, 1, 1, 2, 2, 2], + [1, 1, 1, 2, 2, 2], + [3, 3, 3, 4, 4, 4], + [3, 3, 3, 4, 4, 4], + ] + ] + ], + dtype=np.float32, + ) + + op = Upsample(data, scales=scales, mode="nearest") + tf_op = TensorflowConverter().visit(op) + result = tf_op().numpy() + assert np.all(result == output) diff --git a/tests/unit_tests/test_nn/test_visitors/test_PrintVisitor.py b/tests/unit_tests/test_nn/test_visitors/test_PrintVisitor.py index e6abc9c2..6afedf22 100644 --- a/tests/unit_tests/test_nn/test_visitors/test_PrintVisitor.py +++ b/tests/unit_tests/test_nn/test_visitors/test_PrintVisitor.py @@ -94,6 +94,18 @@ def test_Cast(capsys): assert captured.out == expected_output +def test_Clip(capsys): + input_op = Input((1, 5), np.dtype(np.float32)) + clip_op = Clip(input_op, min=0, max=1) + PrintVisitor().visit(clip_op) + captured = capsys.readouterr() + expected_output = """\ +Input_0 : Input((1, 5), dtype=float32) +Clip_0 : Clip(Input_0, min=0, max=1) +""" + assert captured.out == expected_output + + def test_Concat(capsys): input_op = Input((1, 5), np.dtype(np.float32)) concat_op = Concat([input_op, input_op], axis=1) @@ -431,6 +443,28 @@ def test_Pad(capsys): assert captured.out == expected_output +def test_ReduceL2(capsys): + input_op = Input((1, 5), np.dtype(np.float32)) + reduceL2_op = ReduceL2(input_op, axes=[1], keepdims=1) + PrintVisitor().visit(reduceL2_op) + captured = capsys.readouterr() + expected_output = """\ +Input_0 : Input((1, 5), dtype=float32) +ReduceL2_0 : ReduceL2(Input_0, axes=1, keepdims=1) +""" + assert captured.out == expected_output + + input_op = Input((1, 5), np.dtype(np.float32)) + reduceL2_op = ReduceL2(input_op, axes=[1, 2], keepdims=1) + PrintVisitor().visit(reduceL2_op) + captured = capsys.readouterr() + expected_output = """\ +Input_0 : Input((1, 5), dtype=float32) +ReduceL2_0 : ReduceL2(Input_0, axes=(1, 2), keepdims=1) +""" + assert captured.out == expected_output + + def test_Relu(capsys): input_op = Input((1, 5), np.dtype(np.float32)) relu_op = Relu(input_op) @@ -601,6 +635,30 @@ def test_Softmax(capsys): assert captured.out == expected_output +def test_Squeeze(capsys): + input_op = Input((1, 5), np.dtype(np.float32)) + squeeze_op = Squeeze(input_op, axes=0) + PrintVisitor().visit(squeeze_op) + captured = capsys.readouterr() + expected_output = """\ +Input_0 : Input((1, 5), dtype=float32) +Squeeze_0 : Squeeze(Input_0, axes=0) +""" + assert captured.out == expected_output + + +def test_Upsample(capsys): + input_op = Input((1, 5), np.dtype(np.float32)) + upsample_op = Upsample(input_op, scales=2, mode="nearest") + PrintVisitor().visit(upsample_op) + captured = capsys.readouterr() + expected_output = """\ +Input_0 : Input((1, 5), dtype=float32) +Upsample_0 : Upsample(Input_0, scales=2) +""" + assert captured.out == expected_output + + def test_Sub(capsys): input_op = Input((1, 5), np.dtype(np.float32)) sub_op = Sub(input_op, np.float32(2))