#1.動機
Elixirと言う関数型言語がある。この言語には、Nervesと言う組込みシステム向けソリューションがあり、Raspberry Piなどのボード・コンピューターを関数型言語で軽快にコントロールすることができる。巷で流行りの関数型言語の中でもひときわ異色を放つ存在だ。
一方、AIテクノロジーである Deep Learningは、パワフルなコンピュータ資源を持つクラウド・サーバーから、より貧弱なエッジ・コンピューターに、その応用フィールドを広げようとしている[*1]。どうやら、本格的なIOTワールドの夜明けが近づきつつあるようだ。
そんな昨今、「Elixir × TensorFlow lite」なるアイデアを試してみない手はないだろう。
この記事(前編/後編)は、そんなアイデアの Feasibility(実現可能性)テストの備忘録である。尤も、ボード・コンピューターではなく、より作業性の良い PC/Windows10上で「Elixir × TensorFlow lite」を試している。悪しからずm(_ _)m
[*1]NVIDIAのJetsonや Googleの Edge TPUの様に、エッジ・コンピューターをパワフルにしようという流れもあるのだが…
#2.イントロダクション
この一連の記事の構成は下記の通りである。
- 前編: Windows10向けの TensorFlow liteライブラリを作成する
残念ながら Windows向けのオフィシャルなビルド手順は公開されていない - 後編: 簡単なMnistアプリを作成し、「Elixir × TensorFlow lite」を試す
Elixir/Erlangの他言語拡張機能 Portを用いて TensorFlowインタープリタを利用する
#3.MinGW 64bitで TensorFlow liteライブラリをビルド
イントロダクションで触れた通り、今日現在 Googleは TensorFlow liteの Windows10向けビルド手順を公開していないようだ。しかし、幸いなことに Linux向けのビルド・スクリプトは用意されている。そう、Windows下でPOSIX系のツールチェインが利用できる MSys2/MinGW64を用いれば TensorFlow liteをビルド出来そうだ。
試してみると、次の3項目を解決する必要があるが、無事 Windows10向け TensorFlow liteライブラリをビルドすることが出来た。
- mman.hが見つからない
- byteswap.hが見つからない
- benchmark-lib.aのビルドで、コマンドラインが長すぎてエラーする
では、作業を始めよう。
まず最初に、TensorFlowのリポジトリを複製し、添付のスクリプトで依存ファイルをダウンロードする。
$ git clone https://github.com/tensorflow/tensorflow.git tensorflow_src
$ pushd tensorflow_src/tensorflow/lite/tools/make
$ ./download_dependencies.sh
$ popd
次に、下記のリポジトリを複製し、mmanをビルド/インストールする。
https://github.com/witwall/mman-win32
$ git clone https://github.com/witwall/mman-win32
$ cd mman-win32
$ ./configure --libdir=/mingw64/x86_64-w64-mingw32/lib --incdir=/mingw64/x86_64-w64-mingw32/include
$ make
$ make install
参考文献[2]を参考に下記をダウンロードする。
https://github.com/icecoobe/sbc-windows/blob/master/byteswap.h
$ cp byteswap.h /migw64/x86_64-w64-mingw32/include
最後に、Makefile中の$(BENCHMARK_LIB)のルールを、コマンドライン・バッファの長さに収まるように修正する。
$ cd tensorflow_src/tensorflow/lite/tools/make
$ vim Makefile
/^\$(BENCHMARK_LIB)
$(BENCHMARK_LIB) : $(LIB_PATH) $(BENCHMARK_LIB_OBJS)
@mkdir -p $(dir $@)
$(AR) $(ARFLAGS) $(BENCHMARK_LIB) $(LIB_OBJS) $(BENCHMARK_LIB_OBJS)
$(BENCHMARK_LIB) : $(LIB_PATH) $(BENCHMARK_LIB_OBJS)
@mkdir -p $(dir $@)
$(AR) $(ARFLAGS) $(BENCHMARK_LIB) $(LIB_OBJS)
$(AR) $(ARFLAGS) $(BENCHMARK_LIB) $(BENCHMARK_LIB_OBJS)
以上で準備が整ったので、TensorFlow liteのビルドを行おう。
$./build_lib.sh
ライブラリは、"libtensorflow-lite.a"という名前で、
tensorflow_src/tensorflow/lite/tools/make/gen/windows_x86_64/lib
に置かれる。また、お隣のディレクトリ
tensorflow_src/tensorflow/lite/tools/make/gen/windows_x86_64/bin
には、ベンチマーク・ツールや最小構成のTensorFlow liteアプリ "minimal.exe"が出来上がっている。
#4.TensorFlow liteライブラリの動作検証
さて、前編の締め括りとして、Windows10上で TensorFlow liteが期待通りに動作するかどうかを確かめておく。
おあつらえ向きに、既にTensorFlow liteアプリ"minimal.exe"が手元にあるので、これで動作確認を行おう。あと必要なモノのは、学習済みのモデルだ。TensorFlow liteに与えるモデルは、TensorFlowのモデルを lite用にコンバートして作成するそうだ。Google Colaboratoryに転がっている出来合いのモデルを拾ってきても良いのだが、折角の機会なので自前で用意してみよう。
下記のPythonコードは、おなじみの LeNetによる MNISTの画像認識だ。このコードを実行すると、MNISTの学習データでモデルを訓練し、テストデータでモデルの評価を行い、最後にそのモデル(Keras)を TensorFlow liteのモデルに変換した "mnist.tfl"が得られるようになっている。
※コードの実行にあたって注意点がある。確証はないのだが、モデル・コンバートを行うためには CUDAのインストールが必要なようだ。GPU無しの環境で実行すると、エラーが発生し中断してしまった(T_T)。ここは、大人しく Google Colaboratoryを利用するのが無難だろう。
import os
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Activation, Flatten, Dropout, Dense
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import TensorBoard
import numpy as np
def lenet(input_shape, num_classes):
model = Sequential()
#
model.add(Conv2D(20, kernel_size=5, padding="same", activation="relu", input_shape=input_shape))
model.add(MaxPooling2D(pool_size=(2,2)))
model.add(Conv2D(50, kernel_size=5, padding="same", activation="relu"))
model.add(MaxPooling2D(pool_size=(2,2)))
#
model.add(Flatten())
model.add(Dense(500, activation="relu"))
model.add(Dense(num_classes))
model.add(Activation("softmax"))
return model
class MNISTDataset():
def __init__(self):
self.image_shape = (28, 28, 1)
self.num_classes = 10
def get_batch(self):
(x_train, y_train),(x_test, y_test) = tf.keras.datasets.mnist.load_data()
x_train, x_test = [self.preprocess(d) for d in [x_train, x_test]]
y_train, y_test = [self.preprocess(d, label_data=True) for d in [y_train, y_test]]
return x_train, y_train, x_test, y_test
def preprocess(self, data, label_data=False):
if label_data:
data = tf.keras.utils.to_categorical(data, self.num_classes)
else:
data = data.astype("float32")
data /= 255
shape = (data.shape[0],) + self.image_shape
data = data.reshape(shape)
return data
class Trainer():
def __init__(self, model, loss, optimizer):
self._target = model
self._target.compile(loss=loss, optimizer=optimizer, metrics=["accuracy"])
self.verbose = 1
self.log_dir = os.path.join(".", "logdir")
def train(self, x_train, y_train, batch_size, epochs, validation_split):
if os.path.exists(self.log_dir):
import shutil
shutil.rmtree(self.log_dir)
os.mkdir(self.log_dir)
self._target.fit(
x_train, y_train,
batch_size=batch_size, epochs=epochs,
validation_split=validation_split,
callbacks=[TensorBoard(log_dir=self.log_dir)],
verbose=self.verbose
)
dataset = MNISTDataset()
model = lenet(dataset.image_shape, dataset.num_classes)
x_train, y_train, x_test, y_test = dataset.get_batch()
trainer = Trainer(model, loss="categorical_crossentropy", optimizer=Adam())
trainer.train(x_train, y_train, batch_size=128, epochs=12, validation_split=0.2)
score = model.evaluate(x_test, y_test, verbose=0)
print("Test loss:", score[0])
print("Test accuracy:", score[1])
# save keras model
model.save('mnist.h5')
# save tensorflow-lite model
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open("mnist.tfl", "wb") as f:
f.write(tflite_model)
それでは、"minimal.exe"に "mnist.tfl"を与えて実行してみよう。
……よくわからないが、それなりに動いていそうかな(^^;)
$./minimal.exe
minimal <tflite model>
$./minimal.exe mnist.tfl
=== Pre-invoke Interpreter State ===
Interpreter has 30 tensors and 8 nodes
Inputs: 0
Outputs: 17
Tensor 0 conv2d_input kTfLiteFloat32 kTfLiteArenaRw 3136 bytes ( 0.0 MB) 1 28 28 1
Tensor 1 sequential/conv2d/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 80 bytes ( 0.0 MB) 20
Tensor 2 sequential/conv2d_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 200 bytes ( 0.0 MB) 50
Tensor 3 sequential/dense/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 2000 bytes ( 0.0 MB) 500
Tensor 4 sequential/dense_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 40 bytes ( 0.0 MB) 10
Tensor 5 sequential/flatten/Const kTfLiteInt32 kTfLiteMmapRo 8 bytes ( 0.0 MB) 2
Tensor 6 sequential/dense/MatMul kTfLiteFloat32 kTfLiteMmapRo 4900000 bytes ( 4.7 MB) 500 2450
Tensor 7 sequential/dense_1/MatMul kTfLiteFloat32 kTfLiteMmapRo 20000 bytes ( 0.0 MB) 10 500
Tensor 8 sequential/conv2d/Conv2D kTfLiteFloat32 kTfLiteMmapRo 2000 bytes ( 0.0 MB) 20 5 5 1
Tensor 9 sequential/conv2d_1/Conv2D kTfLiteFloat32 kTfLiteMmapRo 100000 bytes ( 0.1 MB) 50 5 5 20
Tensor 10 sequential/conv2d/Relu;sequential/conv2d/BiasAdd;sequential/conv2d/Conv2D;sequential/conv2d/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteArenaRw 62720 bytes ( 0.1 MB) 1 28 28 20
Tensor 11 sequential/max_pooling2d/MaxPool kTfLiteFloat32 kTfLiteArenaRw 15680 bytes ( 0.0 MB) 1 14 14 20
Tensor 12 sequential/conv2d_1/Relu;sequential/conv2d_1/BiasAdd;sequential/conv2d_1/Conv2D;sequential/conv2d_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteArenaRw 39200 bytes ( 0.0 MB) 1 14 14 50
Tensor 13 sequential/max_pooling2d_1/MaxPool kTfLiteFloat32 kTfLiteArenaRw 9800 bytes ( 0.0 MB) 1 7 7 50
Tensor 14 sequential/flatten/Reshape kTfLiteFloat32 kTfLiteArenaRw 9800 bytes ( 0.0 MB) 1 2450
Tensor 15 sequential/dense/Relu;sequential/dense/BiasAdd kTfLiteFloat32 kTfLiteArenaRw 2000 bytes ( 0.0 MB) 1 500
Tensor 16 sequential/dense_1/BiasAdd kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 17 Identity kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 18 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 19 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 20 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 21 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 22 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 23 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 24 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 25 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 26 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 27 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 28 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 2000 bytes ( 0.0 MB) 25 20
Tensor 29 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 100000 bytes ( 0.1 MB) 500 50
Node 0 Operator Builtin Code 3 CONV_2D
Inputs: 0 8 1
Outputs: 10
Temporaries: 28
Node 1 Operator Builtin Code 17 MAX_POOL_2D
Inputs: 10
Outputs: 11
Node 2 Operator Builtin Code 3 CONV_2D
Inputs: 11 9 2
Outputs: 12
Temporaries: 29
Node 3 Operator Builtin Code 17 MAX_POOL_2D
Inputs: 12
Outputs: 13
Node 4 Operator Builtin Code 22 RESHAPE
Inputs: 13 5
Outputs: 14
Node 5 Operator Builtin Code 9 FULLY_CONNECTED
Inputs: 14 6 3
Outputs: 15
Node 6 Operator Builtin Code 9 FULLY_CONNECTED
Inputs: 15 7 4
Outputs: 16
Node 7 Operator Builtin Code 25 SOFTMAX
Inputs: 16
Outputs: 17
=== Post-invoke Interpreter State ===
Interpreter has 30 tensors and 8 nodes
Inputs: 0
Outputs: 17
Tensor 0 conv2d_input kTfLiteFloat32 kTfLiteArenaRw 3136 bytes ( 0.0 MB) 1 28 28 1
Tensor 1 sequential/conv2d/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 80 bytes ( 0.0 MB) 20
Tensor 2 sequential/conv2d_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 200 bytes ( 0.0 MB) 50
Tensor 3 sequential/dense/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 2000 bytes ( 0.0 MB) 500
Tensor 4 sequential/dense_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteMmapRo 40 bytes ( 0.0 MB) 10
Tensor 5 sequential/flatten/Const kTfLiteInt32 kTfLiteMmapRo 8 bytes ( 0.0 MB) 2
Tensor 6 sequential/dense/MatMul kTfLiteFloat32 kTfLiteMmapRo 4900000 bytes ( 4.7 MB) 500 2450
Tensor 7 sequential/dense_1/MatMul kTfLiteFloat32 kTfLiteMmapRo 20000 bytes ( 0.0 MB) 10 500
Tensor 8 sequential/conv2d/Conv2D kTfLiteFloat32 kTfLiteMmapRo 2000 bytes ( 0.0 MB) 20 5 5 1
Tensor 9 sequential/conv2d_1/Conv2D kTfLiteFloat32 kTfLiteMmapRo 100000 bytes ( 0.1 MB) 50 5 5 20
Tensor 10 sequential/conv2d/Relu;sequential/conv2d/BiasAdd;sequential/conv2d/Conv2D;sequential/conv2d/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteArenaRw 62720 bytes ( 0.1 MB) 1 28 28 20
Tensor 11 sequential/max_pooling2d/MaxPool kTfLiteFloat32 kTfLiteArenaRw 15680 bytes ( 0.0 MB) 1 14 14 20
Tensor 12 sequential/conv2d_1/Relu;sequential/conv2d_1/BiasAdd;sequential/conv2d_1/Conv2D;sequential/conv2d_1/BiasAdd/ReadVariableOp/resource kTfLiteFloat32 kTfLiteArenaRw 39200 bytes ( 0.0 MB) 1 14 14 50
Tensor 13 sequential/max_pooling2d_1/MaxPool kTfLiteFloat32 kTfLiteArenaRw 9800 bytes ( 0.0 MB) 1 7 7 50
Tensor 14 sequential/flatten/Reshape kTfLiteFloat32 kTfLiteArenaRw 9800 bytes ( 0.0 MB) 1 2450
Tensor 15 sequential/dense/Relu;sequential/dense/BiasAdd kTfLiteFloat32 kTfLiteArenaRw 2000 bytes ( 0.0 MB) 1 500
Tensor 16 sequential/dense_1/BiasAdd kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 17 Identity kTfLiteFloat32 kTfLiteArenaRw 40 bytes ( 0.0 MB) 1 10
Tensor 18 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 19 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 20 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 21 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 22 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 23 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 24 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 25 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 26 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 27 (null) kTfLiteNoType kTfLiteMemNone 0 bytes ( 0.0 MB) (null)
Tensor 28 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 2000 bytes ( 0.0 MB) 25 20
Tensor 29 (null) kTfLiteFloat32 kTfLiteArenaRwPersistent 100000 bytes ( 0.1 MB) 500 50
Node 0 Operator Builtin Code 3 CONV_2D
Inputs: 0 8 1
Outputs: 10
Temporaries: 28
Node 1 Operator Builtin Code 17 MAX_POOL_2D
Inputs: 10
Outputs: 11
Node 2 Operator Builtin Code 3 CONV_2D
Inputs: 11 9 2
Outputs: 12
Temporaries: 29
Node 3 Operator Builtin Code 17 MAX_POOL_2D
Inputs: 12
Outputs: 13
Node 4 Operator Builtin Code 22 RESHAPE
Inputs: 13 5
Outputs: 14
Node 5 Operator Builtin Code 9 FULLY_CONNECTED
Inputs: 14 6 3
Outputs: 15
Node 6 Operator Builtin Code 9 FULLY_CONNECTED
Inputs: 15 7 4
Outputs: 16
Node 7 Operator Builtin Code 25 SOFTMAX
Inputs: 16
Outputs: 17
#5.次回予告
次回は、関数型言語 Elixirから TensorFlow liteを呼び出す仕組みを作り、今回用意した LeNet MNISTのモデルで簡単な手書き数字認識アプリを作ってみる。
#参考文献
[1] TensorFlow Lite ガイド
[2] Windows向けtensorflowliteのビルド(for gcc on MinGW)