def test_tf2keras_shared_range_dim(use_symbol): # Test examples in https://coremltools.readme.io/docs/flexible-inputs import tensorflow as tf input_dim = 3 # None denotes seq_len dimension x1 = tf.keras.Input(shape=(None,input_dim), name="seq1") x2 = tf.keras.Input(shape=(None,input_dim), name="seq2") y = x1 + x2 keras_model = tf.keras.Model(inputs=[x1, x2], outputs=[y]) # One RangeDim shared by two inputs if use_symbol: seq_len_dim = ct.RangeDim(symbol='seq_len') else: # symbol is optional seq_len_dim = ct.RangeDim() seq1_input = ct.TensorType(name="seq1", shape=(1, seq_len_dim, input_dim)) seq2_input = ct.TensorType(name="seq2", shape=(1, seq_len_dim, input_dim)) mlmodel = ct.convert(keras_model, inputs=[seq1_input, seq2_input]) batch = 1 seq_len = 5 test_input_x1 = np.random.rand(batch, seq_len, input_dim).astype(np.float32) test_input_x2 = np.random.rand(batch, seq_len, input_dim).astype(np.float32) expected_val = keras_model([test_input_x1, test_input_x2]) if ct.utils._is_macos(): results = mlmodel.predict({ "seq1": test_input_x1, "seq2": test_input_x2}) np.testing.assert_allclose(results["Identity"], expected_val, rtol=1e-4, atol=1e-3)
def test_torch_outofbound_range_dim(use_symbol): import torch num_tokens = 3 embedding_size = 5 class TestModule(torch.nn.Module): def __init__(self): super(TestModule, self).__init__() self.embedding = torch.nn.Embedding(num_tokens, embedding_size) def forward(self, x): return self.embedding(x) model = TestModule() model.eval() example_input = torch.randint(high=num_tokens, size=(3,), dtype=torch.int64) traced_model = torch.jit.trace(model, example_input) if use_symbol: seq_len_dim = ct.RangeDim(symbol='len', lower_bound=3, upper_bound=5) else: # symbol is optional seq_len_dim = ct.RangeDim(lower_bound=3, upper_bound=5) seq_input = ct.TensorType(name="input", shape=(seq_len_dim,), dtype=np.int64) mlmodel = ct.convert( traced_model, inputs=[seq_input], ) if ct.utils._is_macos(): result = mlmodel.predict( {"input": example_input.detach().numpy().astype(np.float32)} ) # Verify outputs expected = model(example_input) name = list(result.keys())[0] np.testing.assert_allclose(result[name], expected.detach().numpy()) # seq_len below/above lower_bound/upper_bound with pytest.raises(RuntimeError, match=r"not compatible with the model\'s feature"): example_input2 = torch.randint(high=num_tokens, size=(99,), dtype=torch.int64) result = mlmodel.predict( {"input": example_input2.detach().numpy().astype(np.float32)} ) with pytest.raises(RuntimeError, match=r"not compatible with the model\'s feature"): example_input2 = torch.randint(high=num_tokens, size=(2,), dtype=torch.int64) result = mlmodel.predict( {"input": example_input2.detach().numpy().astype(np.float32)} )
def test_torch_range_dim(use_symbol): import torch num_tokens = 3 embedding_size = 5 class TestModule(torch.nn.Module): def __init__(self): super(TestModule, self).__init__() self.embedding = torch.nn.Embedding(num_tokens, embedding_size) def forward(self, x): return self.embedding(x) model = TestModule() model.eval() example_input = torch.randint(high=num_tokens, size=(2, ), dtype=torch.int64) traced_model = torch.jit.trace(model, example_input) if use_symbol: seq_len_dim = ct.RangeDim(symbol='seq_length') else: # symbol is optional seq_len_dim = ct.RangeDim() seq_input = ct.TensorType(name="input", shape=(seq_len_dim, ), dtype=np.int64) mlmodel = ct.convert( traced_model, inputs=[seq_input], ) if ct.utils._is_macos(): result = mlmodel.predict( {"input": example_input.detach().numpy().astype(np.float32)}) # Verify outputs expected = model(example_input) name = list(result.keys())[0] np.testing.assert_allclose(result[name], expected.detach().numpy()) # Try example of different length example_input2 = torch.randint(high=num_tokens, size=(99, ), dtype=torch.int64) result = mlmodel.predict( {"input": example_input2.detach().numpy().astype(np.float32)}) expected = model(example_input2) name = list(result.keys())[0] np.testing.assert_allclose(result[name], expected.detach().numpy())
def test_tf2keras_outofbound_range_dim(use_symbol): # Test examples in https://coremltools.readme.io/docs/flexible-inputs import tensorflow as tf input_dim = 3 # None denotes seq_len dimension x = tf.keras.Input(shape=(None, input_dim), name="seq") y = x * 2 keras_model = tf.keras.Model(inputs=[x], outputs=[y]) if use_symbol: seq_len_dim = ct.RangeDim(symbol='sequence_len', lower_bound=3, upper_bound=5) else: seq_len_dim = ct.RangeDim(lower_bound=3, upper_bound=5) seq_input = ct.TensorType(name="seq", shape=(1, seq_len_dim, input_dim)) mlmodel = ct.convert(keras_model, inputs=[seq_input]) # seq_len is within bound batch = 1 seq_len = 3 test_input_x = np.random.rand(batch, seq_len, input_dim).astype(np.float32) expected_val = keras_model([test_input_x]) if ct.utils._is_macos(): results = mlmodel.predict({"seq": test_input_x}) np.testing.assert_allclose(results["Identity"], expected_val, rtol=1e-4, atol=1e-3) # seq_len below/above lower_bound/upper_bound with pytest.raises( RuntimeError, match=r"not compatible with the model\'s feature"): seq_len = 2 test_input_x = np.random.rand(batch, seq_len, input_dim).astype(np.float32) results = mlmodel.predict({"seq": test_input_x}) with pytest.raises( RuntimeError, match=r"not compatible with the model\'s feature"): seq_len = 6 test_input_x = np.random.rand(batch, seq_len, input_dim).astype(np.float32) results = mlmodel.predict({"seq": test_input_x})
def test_tf2keras_optional_input(): # Test examples in https://coremltools.readme.io/docs/flexible-inputs import tensorflow as tf input_dim = 3 # None denotes seq_len dimension x1 = tf.keras.Input(shape=(None, input_dim), name="optional_input") x2 = tf.keras.Input(shape=(None, input_dim), name="required_input") y = x1 + x2 keras_model = tf.keras.Model(inputs=[x1, x2], outputs=[y]) seq_len_dim = ct.RangeDim() default_value = np.ones((1, 2, input_dim)).astype(np.float32) optional_input = ct.TensorType( name="optional_input", shape=(1, seq_len_dim, input_dim), default_value=default_value, ) required_input = ct.TensorType( name="required_input", shape=(1, seq_len_dim, input_dim), ) mlmodel = ct.convert(keras_model, inputs=[optional_input, required_input]) batch = 1 seq_len = 2 test_input_x2 = np.random.rand(batch, seq_len, input_dim).astype(np.float32) expected_val = keras_model([default_value, test_input_x2]) if ct.utils._is_macos(): results = mlmodel.predict({"required_input": test_input_x2}) np.testing.assert_allclose(results["Identity"], expected_val, rtol=1e-4)
def test_fully_dynamic_inputs(): """ All dims of the inputs are dynamic, and write to slice to one of the inputs. """ import torch class Model(torch.nn.Module): def __init__(self, index): super(Model, self).__init__() self.index = index def forward(self, x, y): x[:, int(self.index.item())] = 0.0 y = y.unsqueeze(0) return y, x model = Model(torch.tensor(3)) scripted_model = torch.jit.script(model) mlmodel = ct.convert( scripted_model, inputs=[ ct.TensorType("x", shape=(ct.RangeDim(), ct.RangeDim())), ct.TensorType("y", shape=(ct.RangeDim(), ct.RangeDim())) ], ) # running predict() is supported on macOS if ct.utils._is_macos(): x, y = torch.rand(2, 4), torch.rand(1, 2) torch_res = model(x, y) results = mlmodel.predict({ "x": x.cpu().detach().numpy(), "y": y.cpu().detach().numpy() }) np.testing.assert_allclose(torch_res[0], results['y.3']) np.testing.assert_allclose(torch_res[1], results['x']) x, y = torch.rand(1, 6), torch.rand(2, 3) torch_res = model(x, y) results = mlmodel.predict({ "x": x.cpu().detach().numpy(), "y": y.cpu().detach().numpy() }) np.testing.assert_allclose(torch_res[0], results['y.3']) np.testing.assert_allclose(torch_res[1], results['x'])
def inject_range_dim(v): if isinstance(v, int): return v if isinstance(v, str): vv = v.split("..") user_assert( len(vv) == 2 and vv[0].isnumeric() and vv[1].isnumeric(), "Can not parse shape element {}. Excepted RangeDim, e.g. 1..50". format(v)) return ct.RangeDim(lower_bound=int(vv[0]), upper_bound=int(vv[1])) raise ValueError("Can not parse shape element {}, type {}".format( v, type(v)))
def test_image_input_rangedim(self, convert_to): example_input = torch.rand(1, 3, 50, 50) * 255 traced_model = torch.jit.trace(TestConvModule().eval(), example_input) input_shape = ct.Shape(shape=(1, 3, ct.RangeDim(25, 100, default=45), ct.RangeDim(25, 100, default=45))) 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 == 45 assert spec.description.input[0].type.imageType.height == 45 assert spec.description.input[ 0].type.imageType.imageSizeRange.widthRange.lowerBound == 25 assert spec.description.input[ 0].type.imageType.imageSizeRange.widthRange.upperBound == 100 _assert_torch_coreml_output_shapes(model, spec, traced_model, example_input, is_image_input=True)
def test_multiarray_input_rangedim(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.Shape(shape=(1, 3, ct.RangeDim(25, 100, default=45), ct.RangeDim(25, 100, default=45))) 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, 45, 45 ] assert spec.description.input[ 0].type.multiArrayType.shapeRange.sizeRanges[2].lowerBound == 25 assert spec.description.input[ 0].type.multiArrayType.shapeRange.sizeRanges[2].upperBound == 100 _assert_torch_coreml_output_shapes(model, spec, traced_model, example_input)
def test_torch_optional_input(): import torch num_tokens = 3 embedding_size = 5 class TestModule(torch.nn.Module): def __init__(self): super(TestModule, self).__init__() self.embedding = torch.nn.Embedding(num_tokens, embedding_size) def forward(self, x, y): return self.embedding(x) + y model = TestModule() model.eval() example_input = [ torch.randint(high=num_tokens, size=(2, ), dtype=torch.int64), torch.rand(1), ] traced_model = torch.jit.trace(model, example_input) required_input = ct.TensorType(name="required_input", shape=(ct.RangeDim(), ), dtype=np.int64) default_value = np.array([3]).astype(np.float32) optional_input = ct.TensorType(name="optional_input", shape=(1, ), default_value=default_value) mlmodel = ct.convert( traced_model, inputs=[required_input, optional_input], ) if ct.utils._is_macos(): result = mlmodel.predict({ "required_input": example_input[0].detach().numpy().astype(np.float32) }) # Verify outputs torch_default_value = torch.tensor([3]) expected = model(example_input[0].detach(), torch_default_value) name = list(result.keys())[0] np.testing.assert_allclose(result[name], expected.detach().numpy())
def test_mil_ranged_image_with_default(self): input_shape = [ ct.ImageType(name="x", shape=(1, 3, 10, ct.RangeDim(10, 30, default=20))) ] 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" ) == "imageSizeRange", "Expected imageSizeRange 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 == 20, "expected [H, W] == [10, 20], got [{}, {}] instead".format( spec_H, spec_W) spec_H_range = [ input_spec[0].type.imageType.imageSizeRange.heightRange.lowerBound, input_spec[0].type.imageType.imageSizeRange.heightRange.upperBound ] spec_W_range = [ input_spec[0].type.imageType.imageSizeRange.widthRange.lowerBound, input_spec[0].type.imageType.imageSizeRange.widthRange.upperBound ] assert spec_H_range == [10, 10], "Ranged height mismatch" assert spec_W_range == [10, 30], "Ranged width mismatch"
def test_mil_ranged_multiarray_with_default(self): input_shape = [ ct.TensorType(name="x", shape=(1, 3, 10, ct.RangeDim(10, 30, default=20))) ] 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" ) == "shapeRange", "Expected shapeRange in ShapeFlexibility" spec_default_shape = [ s for s in input_spec[0].type.multiArrayType.shape ] ranged_shapes = [(1, 1), (3, 3), (10, 10), (10, 30)] spec_ranged_shapes = [] for range_dim in input_spec[ 0].type.multiArrayType.shapeRange.sizeRanges: spec_ranged_shapes.append( tuple([range_dim.lowerBound, range_dim.upperBound])) assert spec_default_shape == [ 1, 3, 10, 20 ], "Expected default shape to be [1, 3, 10, 20], got {} instead".format( str(spec_default_shape)) assert spec_ranged_shapes == ranged_shapes, "Enumerated shape mismatch"
def test_torch_range_dim_lstm(variable_length): """ This example shows how to run LSTM with previous hidden / cell states """ import torch import coremltools as ct input_size = 3 hidden_size = 2 class TestNet(torch.nn.Module): def __init__(self): super(TestNet, self).__init__() self.lstm = torch.nn.LSTM(input_size, hidden_size, 1) def forward(self, x, hidden_state, cell_state): # LSTM takes in previous hidden and cell states. The first # invokation usually have zero vectors as initial states. output, (new_hidden_state, new_cell_state) = \ self.lstm(x, (hidden_state, cell_state)) # LSTM hidden / cell states are returned to be managed by the # caller (and is fed in as inputs in the next call). return output, new_hidden_state, new_cell_state model = TestNet() model.eval() seq_len = 2 # we'll make seq_len dynamic later batch = 1 input_shape = (seq_len, batch, input_size) rand_input = torch.rand(*input_shape) h_shape = (1, batch, hidden_size) rand_h0 = torch.rand(*h_shape) rand_c0 = torch.rand(*h_shape) traced_model = torch.jit.trace(model, (rand_input, rand_h0, rand_c0)) # ct.RangeDim() tells coremltools that this dimension can change for # each inference example (aka "runtime-determined"). If the sequence # length is always the same (e.g., 2 step LSTM would have seq_len == 2) # Note that fixed-length models usually run slightly faster # than variable length models. ct_seq_len = ct.RangeDim() if variable_length else seq_len seq_input = ct.TensorType(shape=(ct_seq_len, batch, input_size), name="seq_input") h_input = ct.TensorType(shape=h_shape, name="h_input") c_input = ct.TensorType(shape=h_shape, name="c_input") mlmodel = ct.convert( traced_model, inputs=[seq_input, h_input, c_input], ) if ct.utils._is_macos(): result = mlmodel.predict({ "seq_input": rand_input.detach().numpy().astype(np.float32), "h_input": rand_h0.detach().numpy().astype(np.float32), "c_input": rand_c0.detach().numpy().astype(np.float32), }) # Verify outputs expected = model(rand_input, rand_h0, rand_c0) names = list(result.keys()) names.sort() np.testing.assert_allclose(result[names[0]], expected[0].detach().numpy(), atol=1e-4) np.testing.assert_allclose(result[names[1]], expected[1].detach().numpy(), atol=1e-4) np.testing.assert_allclose(result[names[2]], expected[2].detach().numpy(), atol=1e-4) # Try example of different length if variable_length: seq_len = 10 input_shape = (seq_len, batch, input_size) rand_input = torch.rand(*input_shape) result = mlmodel.predict({ "seq_input": rand_input.detach().numpy().astype(np.float32), "h_input": rand_h0.detach().numpy().astype(np.float32), "c_input": rand_c0.detach().numpy().astype(np.float32), }) expected = model(rand_input, rand_h0, rand_c0) names = list(result.keys()) names.sort() np.testing.assert_allclose(result[names[0]], expected[0].detach().numpy(), atol=1e-4) np.testing.assert_allclose(result[names[1]], expected[1].detach().numpy(), atol=1e-4) np.testing.assert_allclose(result[names[2]], expected[2].detach().numpy(), atol=1e-4)
# Convert to Core ML model = ct.convert([tf_model], source='tensorflow') x = np.random.rand(1, 256, 256, 3) tf_out = model.predict([x]) # convert functions predict_model = tf.saved_model.load('./models/magenta_colab/predict_incepv3') transfer_model = tf.saved_model.load('./models/magenta_colab/transfer_incepv3') concrete_func = predict_model.signatures[ tf.saved_model.DEFAULT_SERVING_SIGNATURE_DEF_KEY] print(concrete_func.inputs) concrete_func.inputs[0].set_shape([1, 256, 256, 3]) # Range for the sequence dimension is "arbitary" input_shape = ct.Shape(shape=(1, ct.RangeDim(), ct.RangeDim(), 3)) model_input = ct.TensorType(shape=input_shape) # Convert the model predict_mlmodel = ct.convert(model=[concrete_func], source='tensorflow', inputs=[model_input]) predict_model = tf.saved_model.load('./models/magenta_colab/predict_incepv3') predict_model.summary() model = ct.convert(predict_model, source='tensorflow') # def convert_coreml_model_dynamic(saved_model_path,
# model(data) # transforms.ToPILImage()(image[0]).show(command='fim') # to_visualize = ['gray', 'hint', 'hint_ab', 'fake_entr', # 'real', 'fake_reg', 'real_ab', 'fake_ab_reg', ] # visuals = util.get_subset_dict( # model.model.get_current_visuals(), to_visualize) # for key, value in visuals.items(): # print(key) # transforms.ToPILImage()(value[0]).show(command='fim') output = model(img, hint) output = util.lab2rgb(output, opt=opt) transforms.ToPILImage()(output[0]).show(command='fim') traced_model = torch.jit.trace(model, (img, hint), check_trace=False) mlmodel = ct.convert( model=traced_model, inputs=[ ct.TensorType(name="image", shape=ct.Shape(shape=(1, 3, ct.RangeDim(1, 4096), ct.RangeDim(1, 4096)))), ct.TensorType(name="hint", shape=ct.Shape(shape=(1, 3, ct.RangeDim(1, 4096), ct.RangeDim(1, 4096)))), ]) mlmodel.save("~/color.mlmodel")