MacでTensorFlow Liteを実装する【2019年版】

MacでTensorFlow Liteを動かすまでの流れを解説していきます。

#環境
動作確認済の環境は以下の通りです。
・macOS Catalina バージョン10.15
・Python 3.7.4
・conda 4.7.12
・TensorFlow 1.15.0
・keras 2.2.4

#環境構築
以下のURLよりAnacondaをインストール
https://www.anaconda.com/distribution/
 <img width="1390" alt="スクリーンショット 2019-11-29 10.42.24.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/e41ca970-9cd7-3d66-8c60-1df1d4e5699d.png">
ダウンロードしたインストーラパッケージをダブルクリックして起動します。 利用規約に同意し、保存先を決めてインストールします。
HomeからJupyter Notebookを起動します。
<img width="1293" alt="スクリーンショット 2019-11-29 10.49.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/65c6881c-baec-bb96-a5ac-f66ab22d71e1.png">
作業場所を決めて、NEW→Python3でipynbファイルを作成します。
<img width="1188" alt="スクリーンショット 2019-11-29 10.52.39.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/c7008564-24c5-682f-3071-0cfbfb38bb17.png">
ファイルを開くとこのようにプログラムを書ける画面に移ります。ここにプログラムを書いていきます。
<img width="1188" alt="スクリーンショット 2019-11-29 11.00.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/1f3e7bf9-dffb-b5b8-4b16-e0680084909d.png">
次にTensorFlowを動作させるためのパッケージをインストールします。
<img width="1283" alt="スクリーンショット 2019-11-29 11.08.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/ee1dfa54-ff1a-d585-c7c3-d014d214453a.png">
「Enviroments」→「Create」で新しい環境を作ります。base(root)を使用する場合は不要です。
検索条件を「Not installed」に変更し、「tensorflow」と検索します。
そして出てきたパッケージの「keras」、「tensorflow」を選択し、Applyをクリックします。
<img width="1170" alt="スクリーンショット 2019-11-29 11.25.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/4de3d13a-1afb-ca1b-567a-17f257380e87.png">
先程のようにJupyterを起動して次のようにプログラムを書いて実行します。
実行は「control + Enter」もしくは「Shift + Enter」です。 
エラーが出ていないことがわかります。使い方はざっとこんな感じです。

追記
nomkl、matplotlib、pillowもインストールしておいてください。
nomklはtensorflowを実行する際にカーネルが死ぬのを防げるみたいです。
matplotlibは画像を表示するために使用します。
pillowは画像をロードするために使用します。

#モデルを構築する
TensorFlow Liteのモデルを生成するために、まずはTensorFlowのモデルを作る必要があります。
今回は、cifar10というデータセットを使用します。
https://www.cs.toronto.edu/~kriz/cifar.html
cifar10は、6万枚の画像にラベル付けされたデータセットです。飛行機、自動車、鳥、猫、鹿、犬、カエル、馬、船、トラックに分けられています。これを学習させ、画像分類できるモデルを作っていきます。

以下、画像を学習させるためのコードです。
epoch数を20に設定しているのでかなり時間がかかります。

```
"""
必要なライブラリのインポートと画像の前処理
"""
from keras.models import Sequential
from keras.layers.convolutional import Conv2D
from keras.layers.pooling import MaxPool2D
from keras.layers.core import Dense,Activation,Dropout,Flatten
from keras.datasets import cifar10
from keras.utils import np_utils

 #cifar10をダウンロード
(x_train,y_train),(x_test,y_test)=cifar10.load_data()

#画像を0-1の範囲で正規化
x_train=x_train.astype('float32')/255.0
x_test=x_test.astype('float32')/255.0

#正解ラベルをOne-Hot表現に変換
y_train=np_utils.to_categorical(y_train,10)
y_test=np_utils.to_categorical(y_test,10)

"""
TensorFlowのモデルを構築
"""
model=Sequential()

model.add(Conv2D(32,(3,3),padding='same',input_shape=(32,32,3)))
model.add(Activation('relu'))
model.add(Conv2D(32,(3,3),padding='same'))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Conv2D(64,(3,3),padding='same'))
model.add(Activation('relu'))
model.add(Conv2D(64,(3,3),padding='same'))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size=(2,2)))
model.add(Dropout(0.25))

model.add(Flatten())
model.add(Dense(512))
model.add(Activation('relu'))
model.add(Dropout(0.5))
model.add(Dense(10,activation='softmax'))

model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])

history=model.fit(x_train,y_train,batch_size=128,nb_epoch=20,verbose=1,validation_split=0.1)

#モデルと重みを保存
json_string=model.to_json()
open('cifar10_cnn.json',"w").write(json_string)
model.save_weights('cifar10_cnn.h5')
model.save('cifar10_cnn_model.h5')

#モデルの表示
model.summary()

#評価
score=model.evaluate(x_test,y_test,verbose=0)
print('Test loss:',score[0])
print('Test accuracy:',score[1])
```

実行すると
「cifar10_cnn.h5」と「cifar10_cnn_model.h5」というファイルが生成されているかと思います。
「cifar10_cnn.h5」はモデルの重みのみが保存され、「cifar10_cnn_model.h5」はモデル構造と重みが保存されています。精度は78%でした。
以下のサイトを参考にするとより精度を上げられそうです。

[CIFAR-10でaccuracy95%--CNNで精度を上げるテクニック--](https://qiita.com/yy1003/items/c590d1a26918e4abe512)
[10層の畳み込みニューラルネットワークでCIFAR-10のValidation Accuracy9割を達成する](https://qiita.com/koshian2/items/e66a7ee9bf60ca24c940)

#実際に画像分類してみる

保存したモデルを使って画像分類していきます。
まず、予測する画像を用意します。
<img width="868" alt="スクリーンショット 2019-11-29 13.01.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/f783753a-6d9d-ca5a-abab-99aae5639d33.png">

コードに書かれている階層に合わせてフォルダを作成し、その中に画像を入れ、以下のコードを実行します。

```
"""
拾った画像を使って予測する
"""
from keras.models import model_from_json
import matplotlib.pyplot as plt
from keras.preprocessing.image import img_to_array, load_img
from tensorflow.python.keras.models import load_model

#画像読み込み
temp_img=load_img("./images/airplane1.jpeg",target_size=(32,32))

#画像を配列に変換し0-1で正規化
temp_img_array=img_to_array(temp_img)
temp_img_array=temp_img_array.astype('float32')/255.0
temp_img_array=temp_img_array.reshape((1,32,32,3))

#学習済みのモデルと重みを読み込む
json_string=open('cifar10_cnn.json').read()
model=model_from_json(json_string)
model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy'])
model.load_weights('cifar10_cnn.h5')
# model = load_model('cifar10_cnn_model.h5')

#モデルを表示
model.summary()

#画像を予想
img_pred=model.predict_classes(temp_img_array)
print('\npredict_classes=',img_pred)
print('model=',model)

plt.imshow(temp_img)
plt.title('pred:{}'.format(img_pred))
plt.show()

"""
0 - airplane
1 - automobile
2 - bird
3 - cat
4 - deer
5 - dog
6 - frog
7 - horse
8 - ship
9 - truck
"""
```

上手くいくと、画像とインデックス番号が出力されます。
<img width="507" alt="スクリーンショット 2019-11-29 13.04.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/bdd49684-8035-cbc4-6667-2ceeec5a459d.png">
↑こんな感じ
インデックス番号と画像が一致していることがわかります。画像分類成功です。

#TensorFlow Lite用モデルに変換

そして、先程生成したモデルをTensorFlowLite用に変換します。

```
#既存のKeras用モデル(cifar10_cnn_model.h5)から、TensorFlow Lite用モデル(cifar10_cnn.tflite)を作成

import tensorflow as tf

if __name__ == '__main__':
    converter = tf.lite.TFLiteConverter.from_keras_model_file("cifar10_cnn_model.h5")
    tflite_model = converter.convert()
    open("cifar10_cnn.tflite", "wb").write(tflite_model)

```
こちらのコードを実行すると「cifar10_cnn.tflite」が生成されます。
これがTensorFlowLite用のモデルです。

このモデルを使用して、画像分類してみます。

```
#TensorFlow Lite用モデルを使って、入力画像からジャンル識別する

import tensorflow as tf
import numpy as np
from keras.models import model_from_json
import matplotlib.pyplot as plt
from keras.preprocessing.image import img_to_array, load_img
from tensorflow.python.keras.models import load_model

if __name__ == '__main__':
    # prepara input image
    #画像読み込み
    temp_img=load_img("./images/dog1.jpeg",target_size=(32,32))

    #画像を配列に変換し0-1で正規化
    temp_img_array=img_to_array(temp_img)
    img=temp_img_array.astype('float32')/255.0
    img=temp_img_array.reshape((1,32,32,3))

    # load model
    interpreter = tf.lite.Interpreter(model_path="cifar10_cnn.tflite")
    interpreter.allocate_tensors()
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()

    # set input tensor
    interpreter.set_tensor(input_details[0]['index'], img)

    # run
    interpreter.invoke()

    # get outpu tensor
    probs = interpreter.get_tensor(output_details[0]['index'])

    # print result
    result = np.argmax(probs[0])
    score = probs[0][result]
    print("予測した画像インデックス：{} [{:.2f}]".format(result, score)) 
    
    plt.imshow(temp_img)
    plt.title('pred:{}'.format(img_pred))
    plt.show()

"""
0 - airplane
1 - automobile
2 - bird
3 - cat
4 - deer
5 - dog
6 - frog
7 - horse
8 - ship
9 - truck
"""
```
こちらも上手くいくと画像とインデックス番号が出力されます。
<img width="282" alt="スクリーンショット 2019-11-29 13.20.47.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303209/d1ff1b8c-554a-5cb2-cd69-3023c0d04410.png">

#TensorflowとTensorflowLiteの比較
以下の比較は画像データによって左右されますので参考程度にしてください。
画像を予測するためにかかった時間をそれぞれ計測しました。

```
keras用のモデル
経過時間：0.8839559555053711
経過時間：0.6288352012634277
経過時間：0.5877768993377686
経過時間：0.5789699554443359
経過時間：0.5908827781677246
経過時間：0.7207329273223877
経過時間：0.7104830741882324
経過時間：0.6035618782043457
経過時間：0.5244758129119873
経過時間：0.5348677635192871
平均経過時間：0.636454225
```

```
TensorflowLite用のモデル
経過時間：0.27948904037475586
経過時間：0.05380606651306152
経過時間：0.022572994232177734
経過時間：0.06809115409851074
経過時間：0.07050800323486328
経過時間：0.06940007209777832
経過時間：0.12052798271179199
経過時間：0.17615199089050293
経過時間：0.12544798851013184
経過時間：0.027255773544311523
平均経過時間：0.101325107
```
TensorflowLiteの方が早いですね。
しかしその分精度に差があります。

keras用のモデルで予測できなかった画像はCatのみでした。
一方でTensorflowLiteのモデルでは、airplane、Bird、cat、Frogが予測することができませんでした。
TensorflowLite用に変換したモデルはかなり精度が下がっているようです。

99.4%の精度を持つMNISTのモデルで実行した場合はkeras用もTensorflowLite用も同じくらい予測することができました。TensorflowLiteを使用する場合は、かなり高い精度を持つモデルを用意する必要がありそうです。

#参考
[初心者に優しくないTensorflow Lite の公式サンプル](https://qiita.com/yohachi/items/434f0da356161e82c242)
[KerasでCNNを構築しCIFAR-10の画像分類をしてみよう](https://www.y-shinno.com/keras-cifar10/)