プログラミング
自作AIをAndroidにのっける
動機近年、人工知能を利用したサービスが増えている。
例えばChat GPT。言語処理をしてくれるAIだが非常に精度がいい。APIも公開されており、気軽にサービスに組み込むことができる。他にも様々なAIが公開されており、人工知能を用いたサービスというのは珍しく無くなってきた。
しかし、提供されたAPIを叩いただけでは人工知能を使った感が薄く感じる。
自分でAIを用意してサービスに組み込んでみたいと思う人は少なくないはず。
自分でサーバーを用意してAPIを公開してもいいが、サーバーの維持費、通信費用などがかかってしまう。端末上で動かすことができればこの問題も解決できる。
そう思い探してみたところ、「Tensorflow Lite」に行き着いた。そこで、実際にこのライブラリを用いてAndroidアプリを作ってみたい。
Tensorflow Liteとは機械学習を行うためのライブラリとして「Tensorflow」というものがある。比較的自由に機械学習を行えるため世界中で愛されているが、モバイル端末上で実行するには少し重たすぎる。
そこでTensorflow Liteである。このライブラリを使うことでモバイル端末上(iOS、Android、Raspberry pi等)で利用できるくらいに軽量化される。
同じようにモバイル端末上で人工知能を利用するためのライブラリとして「ML Kit」というものも存在するが、これだと自分で作成したモデルを利用できない。その点ではこちらの方が(手間はかかるが)自由度が高い。
使い方下の方で小難しく書いているが使い方はものすごく簡単である。
1. tfliteモデルを用意するtfliteとはTensorflow Lite専用の型。
自分で作ってもいいし、ネットから拾ってきてもいい。ここでみんな配布してるのですぐ手に入る。
2. Androidにぶち込むimport機能を使って指定の場所にtfliteモデルを入れる。
3. 使う入れればAndroid側が勝手にモデル用のクラスを作ってくれる。だからその関数を叩けばいい
例えば以下のコード。
mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。
このmnistModelというのが用意されたモデル用クラス(のインスタンス)。ここに生えているprocess関数に入力値を渡してやれば出力が得られる。
これだけで完成。
ものすごく簡単にできることがわかると思う。
アプリ作成実際にAndroidアプリを作ってみる。
作ったアプリは以下の通り。
数字を手書きしてボタンを押せば人工知能で識別してくれるアプリ。
返送速度もいい感じ(これはエミュレーターだが、実機でやればもう少し早くなる)。
こんな感じのアプリを作成していく
1.モデルの作成まずは人工知能のモデルづくりから始める。
Tensorflow Liteでは.tfliteという独自の拡張子のモデルを用意する必要がある。これには以下の方法が存在する。
1. Tensorflow Hubから有志が作成したモデルを取得する
2. TFLite Model Makerを使って事前学習済みのモデルを学習させて取得する
3. Tensorflowでモデルを作成してそれをtflite形式に変換する
今回は3の方式で行う。
ここからはPythonで記述する。
まずはTensorflowでモデルを作成する。
 # モデル構造の設定
 model = tf.keras.models.Sequential([
  tf.keras.layers.Conv2D(48, kernel_size = (4, 4), activation="relu", input_shape=(28,28,1)),
  tf.keras.layers.BatchNormalization(),
  tf.keras.layers.Conv2D(96, kernel_size = (4, 4), activation="relu"),
  tf.keras.layers.BatchNormalization(),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(200),
  tf.keras.layers.BatchNormalization(),
  tf.keras.layers.Dense(10),
 ])
 
 loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
 model.compile(optimizer='adam',
        loss=loss_fn,
        metrics=['accuracy'])
 model.fit(train_input, train_label, epochs=5)
このモデルにMnistのデータセットを入れて学習させたところ、識別率98.84%になった。
これを一度SavedModel形式に変換する。
 model.save("mnist_model")
そこからconverterを用いてtflite形式に変換する
 converter = tf.lite.TFLiteConverter.from_saved_model("mnist_model")
 tflite_model = converter.convert()
 with open("mnist_model.tflite", 'wb') as o_:
   o_.write(tflite_model)
tfliteに変換することで、モバイル端末でも問題なく動作するように軽量化が行われる。
ただし、軽量化する中で精度が低下することがある。
そこで、以下のコードで精度を計測する。
# tflite形式のモデルのインスタンス化
interpreter = tf.lite.Interpreter("MnistModel.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
# 識別結果が正しい個数
correct_num = 0
for i in range(len(test_input)):
# テストデータを入力の型に合わせる
input = test_input[i].reshape(input_details[0]['shape']).astype(np.float32)
interpreter.set_tensor(input_details[0]['index'], input)
interpreter.invoke()
preds_tf_lite = interpreter.get_tensor(output_details[0]['index'])
# 正答率を計算
correct_num += np.argmax(preds_tf_lite) == test_label[i]
print(f"accuracy: {correct_num / len(test_input)}")
識別率は98.84%で、低下は見受けられなかった。
こうしてモデルを用意できた。
詳細なコードは以下に示す。
https://colab.research.google.com/drive/18J0DxrKAMGnMvu0C2AYZlqxREkglFfNS?usp=sharing
2. アプリ部分の作成次にアプリの部分を作成する。
様々な記事を見てみると、識別部分まで全てActivityに記述しているものが散見された。
お試しで使う分にはこれで十分ではあるが、今回は普通にアプリに組み込んでみて問題なく使えるかをみてみたい。
そこで以下のアーキテクチャに沿って開発をした。
TFLiteModelは技術的要素が多く、バックエンドよりの処理であるためRepository層に押し込んでいる。
また、Bitmap型はAndroid独特の型である。今回はビジネスロジックが特定の技術に依存するのもどうかと思い一度Imageに変換している。ただし、その場合TensorBufferやTensorImageへの変換に苦労することになる。
TFLiteではBitmap -> TensorImageへの変換関数も用意してくれているため、Bitmapに依存するように書いても十分問題ないと思われる。
後は(TFLiteModel以外の部分を)この仕様書に合わせて頑張って実装する。
3. モデルとアプリの接続まずライブラリの依存関係を記述する。
version catalogに以下を記述。
 # Tensorflow Lite
 tensorflow-lite = { group = "org.tensorflow", name = "tensorflow-lite", version.ref = "tensorflow-lite" }
 tensorflow-lite-support = { group = "org.tensorflow", name = "tensorflow-lite-support", version.ref = "tensorflow-lite-support" }
 tensorflow-lite-metadata = { group = "org.tensorflow", name = "tensorflow-lite-metadata", version.ref = "tensorflow-lite-support" }
tensorflow-lite-gpuを導入することでGPUを使用することもできるようになるが、今回は省いている。
次にModelをAndroidのライブラリに入れていく。
以下の画像のように選択。
クリックすると以下のようなモーダルが出てくるため記述。
するとmlファイルが出現し、ここに格納される。
こちらのファイルを見ると、使い方が書かれている。
こちらに従ってコードを記述していく。
まずはModelのインスタンスの作成。
Dagger Hiltを使ってDIできるようにしておく。
 @Module
 @InstallIn(SingletonComponent::class)
 class ModelProvider {
   @Provides
   fun providesMnistModel(
     @ApplicationContext context: Context,
   ): MnistModel = MnistModel.newInstance(context)
 }
その後、Repositoryに以下を記述する。    override suspend fun classify(
        target: Image,
    ): ApiResult<ClassifyResult, DomainException> = withContext(dispatcher) {
        mnistModel
            .process(target.toTensorBuffer())
            .outputFeature0AsTensorBuffer
            .floatArray
            .let { buffer ->
                ClassifyResult(
                    zero = buffer[0],
                    one = buffer[1],
                    two = buffer[2],
                    three = buffer[3],
                    four = buffer[4],
                    five = buffer[5],
                    six = buffer[6],
                    seven = buffer[7],
                    eight = buffer[8],
                    nine = buffer[9],
                )
            }.let { ApiResult.Success(it) }
    }
    
    private fun Image.toTensorBuffer() = TensorBuffer
        .createFixedSize(intArrayOf(1, 28, 28), DataType.FLOAT32)
        .also { buffer -> buffer.loadArray(this.pixels.toFloatArray()) }
ぱっと見色々書いているように見えるが、識別自体はprocess関数を叩くだけでできており、非常に手軽である。
mnistModel.process(target.toTensorBuffer()) // これを叩くだけで識別結果が返ってくる。
ただデータの変換だけめんどくさい。実際残りのコードは全てデータの変換である。
Modelにmetadataを付与することでここら辺の記述はもう少し簡単にできるが、今回はこのまま行った。
以上で完成。結構簡単にできる。
完成したコードはこちら
https://github.com/Wansuko-cmd/mnist-application
感想モデル作るのはそこまで難しくない。最悪既存やつ使えばいいアプリとの接続は面倒。入力されたものを頑張ってBufferに変換していく必要がある。(これはTensorImageを使えば解決できそう)モデルがでかい。今回のモデルサイズは37.5MB。一応Firebaseと連携させることで初期インストールしなくてもよくなるが、それでもでかい自分の作ったモデルをアプリに載せるのは結構楽しい気になった人は是非Androidを使って組んでみてほしい。公式チュートリアルに従ってやれば一日でできるようになる。
内容を見る