def get_shape(inp: dict): if 'shape' not in inp: raise ValueError("shape key not found") shape = inp['shape'] default_shape = inp.get('default_shape') if not isinstance(shape, list): raise ValueError("shape should be list, found {}, type {}".format( shape, type(shape))) list_items = 0 for s in shape: if isinstance(s, list): list_items += 1 is_enum_shapes = list_items == len(shape) # All or None shapes element shoud be list # Regular shape [1,20,20,3] # Enumerated Shapes [[1,20,20,3], [1,40,40,3]] user_assert(list_items == 0 or is_enum_shapes, "Can not parse shape {}".format(shape)) if is_enum_shapes: enum_shapes = [transform_shape(s) for s in shape] print("enum_shapes:", enum_shapes) return ct.EnumeratedShapes(shapes=enum_shapes, default=default_shape) # Replace string elements such as "1..50" with RangeDim(1, 50) tx_shape = transform_shape(shape) print("tx_shape:", tx_shape) return ct.Shape(shape=tx_shape, default=default_shape)
def test_multiarray_input_enumerated(self, convert_to): if convert_to == "mlprogram" and ct.utils._macos_version() < (12, 0): return example_input = torch.rand(1, 3, 50, 50) * 100 traced_model = torch.jit.trace(TestConvModule().eval(), example_input) input_shape = ct.EnumeratedShapes(shapes=[[1, 3, 25, 25], [1, 3, 50, 50], [1, 3, 67, 67]], default=[1, 3, 67, 67]) model = ct.convert(traced_model, inputs=[ct.TensorType(shape=input_shape)], convert_to=convert_to) spec = model.get_spec() assert list(spec.description.input[0].type.multiArrayType.shape) == [ 1, 3, 67, 67 ] assert list(spec.description.input[0].type.multiArrayType. enumeratedShapes.shapes[0].shape) == [1, 3, 67, 67] assert len(spec.description.input[0].type.multiArrayType. enumeratedShapes.shapes) == 3 _assert_torch_coreml_output_shapes(model, spec, traced_model, example_input)
def _convert_to_mil_type(shape, dtype, name: str): mil_shape = shape if _check_enumerated_shape(shape): mil_shape = ct.EnumeratedShapes(shape) ml_type = TensorType(shape=mil_shape, dtype=torch_to_mil_types[dtype]) ml_type.name = name return ml_type
def test_image_input_enumerated(self, convert_to): if convert_to == "mlprogram" and ct.utils._macos_version() < (12, 0): return example_input = torch.rand(1, 3, 50, 50) * 255 traced_model = torch.jit.trace(TestConvModule().eval(), example_input) input_shape = ct.EnumeratedShapes(shapes=[[1, 3, 25, 25], [1, 3, 50, 50], [1, 3, 67, 67]], default=[1, 3, 67, 67]) model = ct.convert(traced_model, inputs=[ct.ImageType(shape=input_shape)], convert_to=convert_to) spec = model.get_spec() assert spec.description.input[0].type.imageType.width == 67 assert spec.description.input[0].type.imageType.height == 67 assert len(spec.description.input[0].type.imageType.enumeratedSizes. sizes) == 3 assert spec.description.input[0].type.imageType.enumeratedSizes.sizes[ 0].width == 25 assert spec.description.input[0].type.imageType.enumeratedSizes.sizes[ 0].height == 25 _assert_torch_coreml_output_shapes(model, spec, traced_model, example_input, is_image_input=True)
def test_mil_enumerated_image(self): enumerated_shapes = tuple([(1, 3, 10, 10), (1, 3, 10, 20), (1, 3, 10, 30)]) input_shape = [ ct.ImageType(name="x", shape=ct.EnumeratedShapes(shapes=enumerated_shapes)) ] mlmodel = ct.convert(self.basic_network, source="milinternal", convert_to="mlprogram", inputs=input_shape) input_spec = mlmodel.get_spec().description.input assert len(input_spec) == 1, "1 input expected, got {} instead".format( len(input_spec)) assert input_spec[ 0].name == "x", "input name in MLModel is {}, 'x' is expected".format( input_spec[0].name) assert input_spec[0].type.WhichOneof( "Type") == "imageType", "Expected imageType, got {}".format( input_spec[0].type.WhichOneof("Type")) assert input_spec[0].type.imageType.WhichOneof( "SizeFlexibility" ) == "enumeratedSizes", "Expected enumeratedShapes in ShapeFlexibility" spec_H = input_spec[0].type.imageType.height spec_W = input_spec[0].type.imageType.width assert spec_H == 10 and spec_W == 10, "expected [H, W] == [10, 10], got [{}, {}] instead".format( spec_H, spec_W) spec_enumerated_shapes = set() for enumerated in input_spec[0].type.imageType.enumeratedSizes.sizes: spec_enumerated_shapes.add( tuple([1, 3, enumerated.height, enumerated.width])) assert spec_enumerated_shapes == set( enumerated_shapes), "Enumerated shape mismatch"
def test_torch_enumerated_shapes(): import torch in_channels = 3 out_channels = 2 kernel_size = 3 class TestModule(torch.nn.Module): def __init__(self): super(TestModule, self).__init__() self.conv = torch.nn.Conv2d(in_channels, out_channels, kernel_size) def forward(self, x): return self.conv(x) model = TestModule() model.eval() example_input = torch.randn(1, 3, 28, 28) traced_model = torch.jit.trace(model, example_input) shapes = [(1, 3, 28, 28), (1, 3, 56, 56)] enumerated_shapes = ct.EnumeratedShapes(shapes=shapes) tensor_input = ct.TensorType(name="input", shape=enumerated_shapes) mlmodel = ct.convert( traced_model, inputs=[tensor_input], ) if ct.utils._is_macos(): result = mlmodel.predict( {"input": example_input.detach().numpy().astype(np.float32)}, useCPUOnly=True, ) # Verify outputs expected = model(example_input) name = list(result.keys())[0] np.testing.assert_allclose(result[name], expected.detach().numpy(), rtol=1e-3, atol=1e-4) # Test (1, 3, 56, 56) shape (can't verify numerical parity with Torch # which doesn't support enumerated shape) test_input_x = np.random.rand(*shapes[1]).astype(np.float32) results = mlmodel.predict({"input": test_input_x}) # Test with a wrong shape with pytest.raises( RuntimeError, match=r"not compatible with the model\'s feature"): test_input_x = np.random.rand(1, 3, 29, 29).astype(np.float32) results = mlmodel.predict({"input": test_input_x})
def test_torch_image_enumerated_shapes(): import torch import torchvision torch_model = torchvision.models.mobilenet_v2().features torch_model.eval() example_input = torch.rand(1, 3, 256, 256) traced_model = torch.jit.trace(torch_model, example_input) input_shapes = ct.EnumeratedShapes(shapes=[(1, 3, 256, 256), (1, 3, 224, 224)]) image_input = ct.ImageType(shape=input_shapes, bias=[-1, -1, -1], scale=1 / 127) model = ct.convert(traced_model, inputs=[image_input]) assert model is not None spec = model.get_spec() assert len(spec.description.input[0].type.imageType.enumeratedSizes.sizes) == 2
def test_tf2_image_enumerated_shapes(): import tensorflow as tf keras_model = tf.keras.applications.MobileNetV2( input_shape=(None, None, 3,), classes=1000, include_top=False, ) input_shapes = ct.EnumeratedShapes(shapes=[(1, 192, 192, 3), (1, 224, 224, 3)]) image_input = ct.ImageType(shape=input_shapes, bias=[-1,-1,-1], scale=1/127) model = ct.convert(keras_model, inputs=[image_input]) assert model is not None spec = model.get_spec() assert len(spec.description.input[0].type.imageType.enumeratedSizes.sizes) == 2
def test_tf2keras_enumerated_shapes(): # Test examples in https://coremltools.readme.io/docs/flexible-inputs import tensorflow as tf input_shape = (28, 28, 3) # None denotes seq_len dimension x = tf.keras.Input(shape=input_shape, name="input") C_out = 2 kHkW = 3 y = tf.keras.layers.Conv2D(C_out, kHkW, activation='relu', input_shape=input_shape)(x) keras_model = tf.keras.Model(inputs=[x], outputs=[y]) # One RangeDim shared by two inputs shapes = [(1, 28, 28, 3), (1, 56, 56, 3)] enumerated_shapes = ct.EnumeratedShapes(shapes=shapes) tensor_input = ct.TensorType(name="input", shape=enumerated_shapes) mlmodel = ct.convert(keras_model, inputs=[tensor_input]) # Test (1, 28, 28, 3) shape test_input_x = np.random.rand(*shapes[0]).astype(np.float32) expected_val = keras_model([test_input_x]) if ct.utils._is_macos(): results = mlmodel.predict({"input": test_input_x}) np.testing.assert_allclose(results["Identity"], expected_val, rtol=1e-2) # Test (1, 56, 56, 3) shape (can't verify numerical parity with Keras # which doesn't support enumerated shape) test_input_x = np.random.rand(*shapes[1]).astype(np.float32) results = mlmodel.predict({"input": test_input_x}) # Test with a wrong shape with pytest.raises( RuntimeError, match=r"not compatible with the model\'s feature"): test_input_x = np.random.rand(1, 29, 29, 3).astype(np.float32) results = mlmodel.predict({"input": test_input_x})
def test_mil_enumerated_multiarray_with_default(self): enumerated_shapes = tuple([(1, 3, 10, 10), (1, 3, 10, 20), (1, 3, 10, 30)]) input_shape = [ ct.TensorType(name="x", shape=ct.EnumeratedShapes(shapes=enumerated_shapes, default=(1, 3, 10, 30))) ] mlmodel = ct.convert(self.basic_network, source="milinternal", convert_to="mlprogram", inputs=input_shape) input_spec = mlmodel.get_spec().description.input assert len(input_spec) == 1, "1 input expected, got {} instead".format( len(input_spec)) assert input_spec[ 0].name == "x", "input name in MLModel is {}, 'x' is expected".format( input_spec[0].name) assert input_spec[0].type.WhichOneof( "Type" ) == "multiArrayType", "Expected multiArrayType, got {}".format( input_spec[0].type.WhichOneof("Type")) assert input_spec[0].type.multiArrayType.WhichOneof( "ShapeFlexibility" ) == "enumeratedShapes", "Expected enumeratedShapes in ShapeFlexibility" spec_default_shape = [ s for s in input_spec[0].type.multiArrayType.shape ] spec_enumerated_shapes = set() for enumerated in input_spec[ 0].type.multiArrayType.enumeratedShapes.shapes: spec_enumerated_shapes.add(tuple([s for s in enumerated.shape])) assert spec_default_shape == [ 1, 3, 10, 30 ], "Expected default shape to be [1, 3, 10, 10], got {} instead".format( str(spec_default_shape)) assert spec_enumerated_shapes == set( enumerated_shapes), "Enumerated shape mismatch"