CoreML Toolsを理解するために、自分で作ったシンプルなモデルを使えば、自分で好きなようにモデルを変更して試せるので良いのではないかと思い試してみました。その手順を説明します。
作るもの
数字2組を与えたら、それらを足した結果を予測するモデルを作ります。
手順
今回のコードの完全版はこちらに保管してあります。
https://gist.github.com/TokyoYoshida/bab3d0396c05afce445852d2ae224cf4
1.Google Colabを起動する
Google Colaboratoryのサイトにアクセスします。
Google Colaboratory
2. 必要なものをインストール & インポートする
coremltoolsのバージョンにあわせてtensorflow, kerasもインストールしています。
!pip install tensorflow==1.14.0
!pip install -U coremltools
!pip install keras==2.2.4
そして必要なモジュールをインポートします。
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を使用する方法
!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表現と呼びます。
モデルは、シンプルな全結合レイヤーを3つ、活性化関数はsoftmaxを使用します。
参考:
Kerasで1桁の足し算
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))
上のコードを実行すると学習が始まります。
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
をアンインストールしないとエラーがでるので、アンインストールしておきます。
%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ではレイヤーが下から上に向かっていましたが、こちらは上から下に向かっていますね。
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つの数字の組み合わせを与えると、正しく計算できていることがわかります。
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に変換することもできます。)
model.save('my_model.h5')
読み込んで変換します。
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から変数名としてアクセスできるようになる
)
保存します。
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内でモデルを選択すると、プレビューを見ることができます。
入力がMultiArray型で2つのDoubeを取ります。これは例えば、2+3を予想させたいなら、[2,3]を与えます。
出力は、digitProbabilitiesは、Dictionaryで文字列がKey、DoubleがValueになっています。この項目は予想結果で、数字のラベルごとの確率が出力されます。
digitは、予想結果を数字のラベルに当てはめた結果です。
なお、MultiArray型は、Core ML内に定義されているモデルの入力や出力として使用する多次元配列です。
10. Core MLを使った推論のコードを書く
今回のモデルは、画像認識ではないのでVisionFrameworkなどは用いず、Core MLを直接操作します。
ViewControllerのviewDidLoadにコードを書いていきます。
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