LoginSignup
2
5

More than 3 years have passed since last update.

Kerasで作った簡単なモデルをCoreMLを使ってiOSで動作させる

Posted at

CoreML Toolsを理解するために、自分で作ったシンプルなモデルを使えば、自分で好きなようにモデルを変更して試せるので良いのではないかと思い試してみました。その手順を説明します。

作るもの

数字2組を与えたら、それらを足した結果を予測するモデルを作ります。

<図1>

手順

今回のコードの完全版はこちらに保管してあります。
https://gist.github.com/TokyoYoshida/bab3d0396c05afce445852d2ae224cf4

1.Google Colabを起動する

Google Colaboratoryのサイトにアクセスします。
Google Colaboratory

2. 必要なものをインストール & インポートする

coremltoolsのバージョンにあわせてtensorflow, kerasもインストールしています。

notebook
!pip install tensorflow==1.14.0
!pip install -U coremltools
!pip install keras==2.2.4

そして必要なモジュールをインポートします。

notebook
from keras.models import Sequential
from keras.layers import Dense, Activation
import numpy as np
import math
from keras.utils import np_utils
import keras

3. Tensorboardの準備をする

CoreML Toolsで変換をするとき、モデルの情報が必要になることがあります。
そのためにはある程度モデルを理解する必要がありますが、Tensorboardで可視化しておくと、モデルの情報を理解する助けになるため、その準備をしておきます。
(今回は自分でモデルを作っているので、モデルの情報は要りませんが)

参考:
[TF]KerasからTensorboardを使用する方法

notebook
!mkdir logs
tb_cb = keras.callbacks.TensorBoard(log_dir='./logs', histogram_freq=0, batch_size=32, write_graph=True, write_grads=False, write_images=False, embeddings_freq=0, embeddings_layer_names=None, embeddings_metadata=None)
cbks = [tb_cb]

4. モデルを作り学習させる

入力データして、2つの整数の組み合わせを1万組作成します。
教師データは今回は分類問題として扱うため、[0,1,2,3,4・・17,18]という18個の配列になります。(答えの組み合わせとして9 + 9 = 18が最大値のため)
この配列には、答えとなる部分だけに1が入り、それ以外は0が入ります。このような表現方法をOne-hot表現と呼びます。

<図2>
image.png

モデルは、シンプルな全結合レイヤーを3つ、活性化関数はsoftmaxを使用します。

参考:
Kerasで1桁の足し算

notebook
x = np.random.randint(0, 10, (10000,2))
y = np_utils.to_categorical(np.sum(x, axis=1))

model = Sequential()
model.add(Dense(512, activation='relu', input_dim=2))
model.add(Dense(256, activation='relu'))
model.add(Dense(y.shape[1]))
model.add(Activation("softmax"))
model.compile('rmsprop',
              'categorical_crossentropy',
              metrics=['accuracy'])


train_rate = 0.7
train_len = math.floor(len(x) * train_rate)
trainx = x[0:train_len]
trainy = y[0:train_len]
testx = x[train_len:]
testy = y[train_len:]

history = model.fit(trainx, trainy,
                    batch_size=128,
                    epochs=100,
                    verbose=1,
                    callbacks=cbks,
                    validation_data=(testx, testy))

上のコードを実行すると学習が始まります。

notebook
7000/7000 [==============================] - 0s 54us/step - loss: 0.1705 - acc: 0.9677 - val_loss: 0.0172 - val_acc: 1.0000
Epoch 99/100
7000/7000 [==============================] - 0s 53us/step - loss: 0.0804 - acc: 0.9804 - val_loss: 0.0069 - val_acc: 1.0000
Epoch 100/100
7000/7000 [==============================] - 0s 57us/step - loss: 0.0745 - acc: 0.9806 - val_loss: 0.0062 - val_acc: 1.0000

今回はトレーニングデータとテストデータは分割しているものの、実際にはかぶりがあるので、テストデータによる検証結果(val_acc)はあまり当てにならないです。

5. Tensorboardで可視化してみる

Tensroboardを読み込んで、実行します。
なぜかtensorboard-plugin-witをアンインストールしないとエラーがでるので、アンインストールしておきます。

notebook
%load_ext tensorboard
!pip uninstall tensorboard-plugin-wit
%tensorboard --logdir ./logs

学習の状況

グラフの情報

レイヤーは下に入力(dense_1)、上に出力(activation_1)というようにレイヤーが下から上に向かっています。

入力側(dense_1)を見てみます。

OperationがPlaceholderとなっており、ここにデータが入ることがわかります。
dtypeはDT_FLOATとなっています。今回のデータは整数で作っていますが、小数以下のデータも扱えます。
shapeは、{"dim":{"size":-1},"size":2]}となっています。つまり(-1,2)のshapeということですね。
-1は任意の値という意味になります。2は、2つの文字の組み合わせを入力しているためです。
<図.2>の入力データのところで、2つの数字を組み合わせxテストデータ数(任意)ということと一致します。

出力側(activation_1)を見てみます。
activation_1はsoftmax関数なので、1つ前のdense_3が出力した19個の数字に対してsoftmax関数の計算結果を出し、loss、metrics、trainingに出力しています。

モデルの情報は、kerasのsummaryメソッドでも出力できます。
Tensorboardではレイヤーが下から上に向かっていましたが、こちらは上から下に向かっていますね。

notebook
model.summary()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 512)               1536      
_________________________________________________________________
dense_2 (Dense)              (None, 256)               131328    
_________________________________________________________________
dense_3 (Dense)              (None, 19)                4883      
_________________________________________________________________
activation_1 (Activation)    (None, 19)                0         
=================================================================
Total params: 137,747
Trainable params: 137,747
Non-trainable params: 0

6. Notebook上で予想してみる

iOSで動作させる前に、Notebook上でうまく動作しているか確認します。
適当に2つの数字の組み合わせを与えると、正しく計算できていることがわかります。

notebook
np.argmax(model.predict(np.array([[7,6]])),axis=1)
// array([13])
np.argmax(model.predict(np.array([[1,3]])),axis=1)
// array([4])

7. Core MLに変換する

上で作ったモデルを保存しておきます。
(これをしなくても既にモデルは得られているのでそのままCore MLに変換することもできます。)

notebook
model.save('my_model.h5')

読み込んで変換します。

notebook
from keras.models import load_model
keras_model = load_model('my_model.h5')
from coremltools.converters import keras as converter

# 予想結果の数字の分類ラベルを作る["0","1","2"..."18"]
class_labels = np.arange(0, 19).astype('unicode').tolist()

# 変換
mlmodel = converter.convert(keras_model, # 変換対象のモデル
  output_names=['digitProbabilities'], # 予想の出力に名前をつける。swiftから変数名としてアクセスできるようになる
  class_labels=class_labels, # 予想結果の分類ラベル
  predicted_feature_name='digit' # 分類の出力に名前をつける。swiftから変数名としてアクセスできるようになる
)

保存します。

notebook
coreml_model_path = 'my_model.mlmodel'
mlmodel.save(coreml_model_path)

このあたりのコードは、こちらの本に書いてあるものをそのまま使っています。

Core ML Tools実践入門 - iOS × DEEP LEARNING

8. Core MLモデル(.mlmodelファイル)をダウンロードする

Notebookから下のように選択して「ダウンロード」を選択してダウンロードします。

9. XcodeのプロジェクトにDrag & Dropする

Xcodeを起動して、プロジェクトの作成から「Single View App」を作成します。

今回作ったプロジェクトはgithubにあるので、こちらを使っても良いです。
TokyoYoshida/CoreMLSimpleTest

プロジェクトの、任意の位置に.mlmodelファイルをDrag & Dropします。

Xcode内でモデルを選択すると、プレビューを見ることができます。
image.png

入力がMultiArray型で2つのDoubeを取ります。これは例えば、2+3を予想させたいなら、[2,3]を与えます。
出力は、digitProbabilitiesは、Dictionaryで文字列がKey、DoubleがValueになっています。この項目は予想結果で、数字のラベルごとの確率が出力されます。
digitは、予想結果を数字のラベルに当てはめた結果です。

なお、MultiArray型は、Core ML内に定義されているモデルの入力や出力として使用する多次元配列です。

10. Core MLを使った推論のコードを書く

今回のモデルは、画像認識ではないのでVisionFrameworkなどは用いず、Core MLを直接操作します。
ViewControllerのviewDidLoadにコードを書いていきます。

ViewController.swift
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        let model = my_model()
        // 予想したい数字のペアをつくる
        let inputArray = try! MLMultiArray([2,3])
        let inputToModel: my_modelInput = my_modelInput(input1: inputArray)
        // 推論する
        if let prediction = try? model.prediction(input: inputToModel) {
            // 結果の出力
            print(prediction.digit)
            print(prediction.digitProbabilities)
        }
    }
}

11. 実行する

アプリを実行すると無駄にアプリの画面が出ますが、気にせずXcodeのOutput欄を見ます。

結果出力
5
["13": 1.401298464324817e-45, "7": 4.403268860642129e-08, "16": 0.0, "12": 1.401298464324817e-45, "10": 1.401298464324817e-45, "4": 2.876720373024e-06, "11": 1.401298464324817e-45, "1": 1.2956196287086532e-23, "6": 6.624156412726734e-06, "8": 6.452452973902557e-18, "15": 1.401298464324817e-45, "2": 7.265933324842114e-14, "0": 1.0373160919090815e-33, "18": 0.0, "9": 1.7125880512063084e-34, "17": 0.0, "3": 1.129986526746086e-15, "14": 1.401298464324817e-45, "5": 0.9999904632568359]

2+3を与えているので、5が推論されていますね。
digitProbabilitiesの出力では、各ラベルをキーとして確率が出力されています。
5である確率は0.9999904632568359なので、ほぼ1になっています。
それ以外の数字、例えば13である確率は、1.401298464324817e-45となっていますがこれは浮動小数の表現で1.401298464324817✕10の-45乗なのでほぼゼロという結果になっています。

最後に

NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshida

Twitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5