Core MLモデルの入力の型をMLMultiArrayから画像（CVPixelBuffer）に変更する

次のように普通にKerasでMNISTモデルをつくってcoremltoolsでCore MLモデルに変換すると、入力の型が`multiArrayType`（Core MLでは`MLMultiArray`）になる。

（Kerasでモデル構築）

```python
def create_keras_base_model(url):    
    import keras
    from keras.models import Sequential
    from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D
    
    keras.backend.clear_session()
    model = Sequential()
    model.add(Conv2D(32, kernel_size=(3, 3),
                     activation='relu',
                     input_shape=(28, 28, 1)))
    model.add(Conv2D(64, (3, 3), activation='relu'))
    model.add(MaxPooling2D(pool_size=(2, 2)))
    model.add(Dropout(0.25))
    model.add(Flatten())
    model.add(Dense(128, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(10, activation='softmax'))

    model.compile(loss=keras.losses.categorical_crossentropy,
                  optimizer=keras.optimizers.SGD(lr=0.01),
                  metrics=['accuracy'])

    model.save(url)

keras_model_path = './KerasMnist.h5'
create_keras_base_model(keras_model_path)
```

（coremltoolsで`.mlmodel`ファイルに変換）

```python
def convert_keras_to_mlmodel(keras_url, mlmodel_url):
    from keras.models import load_model
    keras_model = load_model(keras_url)
    
    from coremltools.converters import keras as keras_converter
    class_labels = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    mlmodel = keras_converter.convert(keras_model, input_names=['image'],
                                output_names=['digitProbabilities'],
                                class_labels=class_labels,
                                predicted_feature_name='digit')
    
    mlmodel.save(mlmodel_url)
     
coreml_model_path = './MNISTDigitClassifier.mlmodel'
convert_keras_to_mlmodel(keras_model_path , coreml_model_path)
```

（作成したモデルの入力形式を確認する）

```python
import coremltools
spec = coremltools.utils.load_spec(coreml_model_path)
builder = coremltools.models.neural_network.NeuralNetworkBuilder(spec=spec)

builder.inspect_input_features()
```

出力結果：

```
[Id: 0] Name: image
          Type: multiArrayType {
  shape: 1
  shape: 28
  shape: 28
  dataType: DOUBLE
}
```

形状が1x28x28、値がDOUBLE型の`multiArrayType`ということで、このままだと**いろいろとめんどくさい**。

たとえば入力型が画像じゃないと、Visionで扱えないので、Core ML単体で扱うことになる。

[Visionなしで、Core ML単体で使う - Qiita](https://qiita.com/shu223/items/5440060a4f5a9da4f331)

`MLMultiArray`の扱いは慣れてないと色々と戸惑うかもしれない。

[Core MLのモデルの出力がMLMultiArrayの場合のメモ｜shu223｜note](https://note.mu/shu223/n/n3aa7a92a93b3)

というわけで、画像型に変更する方法が以下：

## coremltoolsでmlmodelの入力の型を変更する

（入力の型を1x28x28のmulti arrayから28x28のグレースケール画像に変更）

```python
neuralnetwork_spec = builder.spec

# change the input so the model can accept 28x28 grayscale images
neuralnetwork_spec.description.input[0].type.imageType.width = 28
neuralnetwork_spec.description.input[0].type.imageType.height = 28

from coremltools.proto import FeatureTypes_pb2 as _FeatureTypes_pb2
grayscale = _FeatureTypes_pb2.ImageFeatureType.ColorSpace.Value('GRAYSCALE')
neuralnetwork_spec.description.input[0].type.imageType.colorSpace = grayscale
```

（確認）

```python
builder.inspect_input_features()
```

出力結果：

```
[Id: 0] Name: image
          Type: imageType {
  width: 28
  height: 28
  colorSpace: GRAYSCALE
}
```

`.mlmodel`ファイルをXcodeで見てみると、意図した通りになっている。

<img width="316" alt="Screen Shot 2019-11-18 at 9.25.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3180/b72a2e45-6583-0152-621b-97c7ff380f1b.png">

自動生成される入力クラスを見てみると、入力の型が`CVPixelBuffer`になっている。

```
var image: CVPixelBuffer
```

これでVisionでお手軽に扱える。

## 参考

全面的に`coremltools`公式リポジトリの`examples`にあるサンプルを参考にした。

https://github.com/apple/coremltools/tree/master/examples/updatable_models