CoreMLのモデルについて

もうめっきり寒くなりましたね・・。
年末ということで、今年の振り返りを心の中でしつつ、Advent Calendarの記事を書きたいと思います！

前回の私の記事がCoreMLの概要説明だったのですが、
今度は「CoreMLのモデルについて」少し見ていこうと思います。

# モチベーション

CoreMLについてちょっと興味があったので、もう少し調べようと思ったのと、モデルといってもどの程度のレイヤーに対応できてるのかなーと思ったので、調べてみました。

# CoreML Model

## 公式ページについて

[こちら](https://developer.apple.com/documentation/coreml/getting_a_core_ml_model) を参照。

## model format

[こちら](https://apple.github.io/coremltools/coremlspecification/) のドキュメントが公式になります。

.mlmodelが拡張子となりますが、中身は[Protocol Buffers](https://developers.google.com/protocol-buffers/)形式になっています。
※TensorFlowとかもProtobuf形式になっていますよね。([こちら](https://www.tensorflow.org/guide/extend/model_files#protocol_buffers)を参照）

少し中身を見てみましょう。
モデルの大元（Model）は[こちら](https://apple.github.io/coremltools/coremlspecification/sections/Model.html#model)のドキュメントに詳細が記載されています。

一部を下記に転記すると、以下となります。

```protobuf
message Model {
    int32 specificationVersion = 1;
    ModelDescription description = 2;

    // start at 200 here
    // model specific parameters:
    oneof Type {
        // pipeline starts at 200
        PipelineClassifier pipelineClassifier = 200;
        PipelineRegressor pipelineRegressor = 201;
        Pipeline pipeline = 202;

        // regressors start at 300
        GLMRegressor glmRegressor = 300;
        SupportVectorRegressor supportVectorRegressor = 301;
        TreeEnsembleRegressor treeEnsembleRegressor = 302;
        NeuralNetworkRegressor neuralNetworkRegressor = 303;
        BayesianProbitRegressor bayesianProbitRegressor = 304;

        // classifiers start at 400
        GLMClassifier glmClassifier = 400;
        SupportVectorClassifier supportVectorClassifier = 401;
        TreeEnsembleClassifier treeEnsembleClassifier = 402;
        NeuralNetworkClassifier neuralNetworkClassifier = 403;

        // generic models start at 500
        NeuralNetwork neuralNetwork = 500;

        ....

        // CoreML provided models
        CoreMLModels.TextClassifier textClassifier = 2000;
        CoreMLModels.WordTagger wordTagger = 2001;
        CoreMLModels.VisionFeaturePrint visionFeaturePrint = 2002;
    }
}
```

oneof指定なので、モデルのタイプは一つのみ指定可能になっています。
※例えば、NeuralNetoworkであれば、neuralNetworkの変数にモデルの詳細を入れればいい事になります。
後半（2000番台）については、CoreMLで提供されているモデルタイプが指定で出来るようになっていますね。

NeuralNetworkについて、もう少し掘り下げてみます。
[こちら](https://apple.github.io/coremltools/coremlspecification/sections/NeuralNetwork.html)に詳細が記載されています。

```protobuf
message NeuralNetwork {
    repeated NeuralNetworkLayer layers = 1;
    repeated NeuralNetworkPreprocessing preprocessing = 2;
}
```

NeuralNetworkLayerに各オペレーター（ActivationLayer、ConvolutionLayer、FCLayerなど）を指定することが可能です。
NeuralNetworkPreprocessingはプリプロセスなので、入力データにたいして、前準備的なことを指定することが出来ます。
※ドキュメントを読むと、NeuralNetworkImageScaler（イメージを拡大して、バイアスを足し合わせる）とNeuralNetworkMeanImage（画像データの平均分を各要素から減算しているっぽいが、詳細は不明）があるようです。

実際に指定できるオペレーターですが、下記に記述します。

```protobuf
message NeuralNetworkLayer {
    string name = 1;  //descriptive name of the layer
    repeated string input = 2;
    repeated string output = 3;

    oneof layer {
        // start at 100 here
        ConvolutionLayerParams convolution = 100;

        PoolingLayerParams pooling = 120;

        ActivationParams activation = 130;

        InnerProductLayerParams innerProduct = 140;
        EmbeddingLayerParams embedding = 150;

        //normalization related layers
        BatchnormLayerParams batchnorm = 160;
        MeanVarianceNormalizeLayerParams mvn = 165;
        L2NormalizeLayerParams l2normalize = 170;
        SoftmaxLayerParams softmax = 175;
        LRNLayerParams lrn = 180;

        CropLayerParams crop = 190;
        PaddingLayerParams padding = 200;
        UpsampleLayerParams upsample = 210;

        ResizeBilinearLayerParams resizeBilinear = 211;
        CropResizeLayerParams cropResize = 212;

        UnaryFunctionLayerParams unary = 220;

        //elementwise operations
        AddLayerParams add = 230;
        MultiplyLayerParams multiply = 231;

        AverageLayerParams average = 240;
        ScaleLayerParams scale = 245;

        BiasLayerParams bias = 250;
        MaxLayerParams max = 260;
        MinLayerParams min = 261;

        DotProductLayerParams dot = 270;
        ReduceLayerParams reduce = 280;
        LoadConstantLayerParams loadConstant = 290;

        //data reorganization
        ReshapeLayerParams reshape = 300;
        FlattenLayerParams flatten = 301;
        PermuteLayerParams permute = 310;
        ConcatLayerParams concat = 320;
        SplitLayerParams split = 330;
        SequenceRepeatLayerParams sequenceRepeat = 340;

        ReorganizeDataLayerParams reorganizeData = 345;
        SliceLayerParams slice = 350;

        //Recurrent Layers
        SimpleRecurrentLayerParams simpleRecurrent = 400;
        GRULayerParams gru = 410;
        UniDirectionalLSTMLayerParams uniDirectionalLSTM = 420;
        BiDirectionalLSTMLayerParams biDirectionalLSTM = 430;

        // Custom (user-implemented) Layer
        CustomLayerParams custom = 500;
    }
}
```

こう見ると、基本的なレイヤーは指定できる感じですね。
※Activationレイヤーについては、ActivationParamsから選択できるActivation Function（活性化関数）を確認出来ます。

また、ConvolutionParamsを見てみます。

```protobuf
message ConvolutionLayerParams {
    uint64 outputChannels = 1;

    uint64 kernelChannels = 2;

    uint64 nGroups = 10;

    repeated uint64 kernelSize = 20;

    repeated uint64 stride = 30;

    repeated uint64 dilationFactor = 40;

    oneof ConvolutionPaddingType {
        ValidPadding valid = 50;
        SamePadding same = 51;
    }

    bool isDeconvolution = 60;

    bool hasBias = 70;

    WeightParams weights = 90;
    WeightParams bias = 91;

    repeated uint64 outputShape = 100;
}
```

Deconvolutionの指定やら、DilatedConvolutionが利用できることが分かります。
※Deconvolutionは畳み込み（Convolution）をする前に入力データサイズを拡大（元データの周囲に空白を追加したり）してから畳み込みを行うのに対し、DilatedConvolutionはフィルタサイズを拡大（やはり同様に空白を追加したり）してから畳み込みを行います。（似たような事をやっているように見えて、実は異なることをやっています。）

詳細は[こちら](https://github.com/vdumoulin/conv_arithmetic)のリポジトリが参考になります。

## 既存モデルの変換について

今度は既存モデルの変換が出来ないかを見てみます。

実はCoreML用のモデル変換ツールが提供されています。
[こちら](https://github.com/apple/coremltools)になります。

公式のドキュメントだと、[こちら](https://developer.apple.com/documentation/coreml/converting_trained_models_to_core_ml)を見る形になります。

ちょっと探したのですが、CoreML（.mlmodel）のpretrainedなモデルが見つからなかったんですよね・・。（有名な[caffe model zoo](http://caffe.berkeleyvision.org/model_zoo.html)みたいに何かしら提供してるのかなーと思ったのですが、、）

NeuralNetworkだと対応しているフレームワークは下記ですね。
* Caffe v1
* Keras 1.2.2+

Caffeが対応しているので、zooからモデルを引っ張ってきて、試しに動作を見ることは出来そうですね。
※もちろん、対応していないオペレーターを含むモデルであれば、インポートに失敗すると思います。ここは色々と実験してみたいところ。

## onnx形式はどうなの？

最近では、DeepLearningの各フレームワークで共通に使える（としようとしている）フォーマットがいくつかあって、その一つに[onnx](https://github.com/onnx/onnx)があります。

※ちなみに各フレームワークのonnx向けのコンバーターは[こちら](https://github.com/onnx/tutorials)から見ることが出来ます。

上記を見ると、CoreMLもonnxのimport/exportに対応していることが分かります。
※中身は細かく追えてないので、別途追ってみたいと思います。

# 最後に

CoreMLのモデルについて、ちょっと情報をまとめてみました。
※onnxやcaffeのモデルを利用できるので、割とiOS向けにDeepLearningの学習済みモデルを入れやすいと思います。

まだ実際にCoreMLを使ってないので、ぼちぼち使って動作を見てみようと思います。
※CPU向けのAcceleratorもあるので、他の推論最適化を行うフレームワーク（TVM、OpenVINO、Tensorflow XLAなど）と速度比較をしてみたい所。

明日は@diescakeさんが再び登壇です！お楽しみに！