uTensorで始めるEdge AI
この記事はArm Treasure Data Advent Calendar 13日目の記事です。今回はArmの開発する人工知能のためのソリューション、uTensorを紹介します。
人工知能というと巨大なデータセンターで大規模データを処理する機械学習モデルを想像するかもしれません。一般的に精度の高い、複雑なモデルを訓練するためには巨大な計算能力が欠かせないためです。
しかし、機械学習アプリを作る者にとってモデルを訓練することだけが仕事ではありません。それを実際に使用して推論をする必要があるのです。推論はやはりサーバサイドで走るかもしれませんし、ブラウザやモバイルデバイスあるいは組み込み機器かもしれません。モデルを訓練する段階とは違った技術要件が必要になります。
また近年ではEdge AIという言葉がさけばれ、サーバサイドだけではなくデータを集めたよりユーザに近いところで機械学習を走らせてしまおうという考え方があります。これには組み込み機器特有の限られたネットワーク帯域を効率的に使いたかったり、プライバシーの観点からデータをサーバサイドに送らずにモデルを訓練したいという要求からきたものです。
そういったEdgeサイドでの機械学習ソリューションを実現するために開発されている技術がuTensor (マイクロテンソル)です。今回はArm Mbed上で動く人工知能用のモジュールuTensorを動かしてみたいと思います。
uTensorとは
uTensorとはArm Mbed環境上で動く機械学習モジュールです。Mbed OSをサポートしているArm Cortexマイクロコントローラ上で動きます。
MbedはArmのマイクロコントローラで動くアプリケーションの開発を実現してくれる統合的な開発環境で、この環境を利用するとハードウェアの詳細を知らずにC/C++で書いたコードをマイクロコントローラ上で動かすことができます。
uTensorは推論用のライブラリで、TensorFlowなどで訓練し、圧縮された(freeze)モデルを読み込ませることができます。全体的な手順は下記のようになっており、TensorFlowのモデルをuTensorのcode generatorに食わせると、uTensorの中間表現に変換され、計算グラフの最適化や定数の埋め込みなどが行われます。最終的にはMbed上で動作するC/C++のコードが生成されます。
それでは実際にやってみましょう。
1. TensorFlowのモデルを用意する
まず元となるモデルを用意します。今回は全体の流れを試してみるため、とても簡単な計算グラフをひとつ構成してみます。2x2のテンソルに対して定数行列をひとつ足すだけの計算グラフです。モデルはSavedModelフォーマットで保存します。
import tensorflow as tf
input = tf.placeholder(tf.int32, shape=(2, 2), name='input')
a = tf.constant([[1, 2], [3, 4]], name='a')
c = a + input
init = tf.global_variables_initializer()
with tf.Session() as sess:
sess.run(init)
ret = sess.run(c, feed_dict={input: [[1, 2], [3, 4]]})
print(ret)
tf.saved_model.simple_save(sess, './saved_model', inputs={"input": input}, outputs={"output": c})
このSavedModelをfreezeと呼ばれる工程を経て一つのProtocol Bufferファイルにまとめます。--output_node_names で指定するノード名はaddですが、saved_model_cliツールでも確認することができます。
$ freeze_graph --input_saved_model_dir saved_model \
--output_node_names add \
--output_graph freezed_model.pb
$ saved_model_cli show --dir ./saved_model --all
MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:
signature_def['serving_default']:
The given SavedModel SignatureDef contains the following input(s):
inputs['input'] tensor_info:
dtype: DT_INT32
shape: (2, 2)
name: input:0
The given SavedModel SignatureDef contains the following output(s):
outputs['output'] tensor_info:
dtype: DT_INT32
shape: (2, 2)
name: add:0
Method name is: tensorflow/serving/predict
2. Arm Mbed環境を用意する
さてこのfreeze modelからuTensorのコードを生成するのですが、その前にMbedのプロジェクトを先に作っておきましょう。プロジェクトの作成、コンパイルなどはmbed-cliというコマンドラインツールを使って行うことができます。
まずArm用にコードを生成する場合にはコンパイラが必要ですが、macOSの場合にはHomebrewを使ってArm GCC Toolchainをインストールするのが最も簡単でしょう。下記のようにインストールできます。
$ brew tap ArmMbed/homebrew-formulae
$ brew install arm-none-eabi-gcc
$arm-none-eabi-g++ -v
Using built-in specs.
COLLECT_GCC=arm-none-eabi-g++
COLLECT_LTO_WRAPPER=/usr/local/Cellar/arm-none-eabi-gcc/9-2019-q4-major/gcc/bin/../lib/gcc/arm-none-eabi/9.2.1/lto-wrapper
Target: arm-none-eabi
// ...
gcc version 9.2.1 20191025 (release) [ARM/arm-9-branch revision 277599] (GNU Tools for Arm Embedded Processors 9-2019-q4-major)
次にMbedプロジェクトの雛形を作成します。
# プロジェクトの雛形を作成
$ mbed new utensor-simulation
# ディレクトリの中にMbed OSのパッケージなどが含まれています。
$ cd utensor-simulation
最後にuTensorのランタイムをこのMbed環境にインストールします。インストールにはmbed addコマンドを使ってGitHubのレポジトリを参照することができます。
$ mbed add https://github.com/uTensor/uTensor
これでMbed環境の事前準備は完了です。
3. uTensorのコードを生成する
ステップ1で作成したfreeze modelからuTensorのコードを生成します。生成にはutensor-cliというコマンドラインツールを使います。outputとなるノード名と出力先ディレクトリを指定します。
$ utensor-cli convert freezed_model.pb \
--output-nodes=add \
--model-dir utensor-simulation/models
utensor-simulation/modelsの下に下記の3ファイルが作成されているはずです。
- freezed_model.cpp: 計算グラフの実装
- freezed_model.hpp: uTensorランタイムのヘッダファイルなど
- freezed_model_weight.hpp: 重みパラメタ
今回は重みパラメタはありませんが、定義していた定数行列がfreezed_model_weight.hppに含まれているはずです。uTensorではモデルの訓練を行わないためこのように定数としてパラメタが埋め込まれます。
// Auto generated by utensor-cli
#include <stdint.h>
const int inline_a_0 [ 4 ] = { 1, 2, 3, 4, };
4. Simulatorで動かしてみる
最後にこのモデルを使ったアプリケーションを走らせたいのですが、Arm Mbedをサポートしているマイクロコントローラが必要なので今回はシミュレータを使ってみましょう。まず下記のEmscriptenをインストールします。これはクロスコンパイルに必要です。
# Emscriptenをインストール
$ git clone https://github.com/emscripten-core/emsdk.git
$ cd emsdk
$ emsdk install sdk-1.38.21-64bit
$ emsdk activate sdk-1.38.21-64bit
次にmbed-simulatorというツールを下記のようにインストールしてください。
$ npm install mbed-simulator -g
これでsimulatorを起動するとアプリケーションのエントリーポイント(main.cpp)からアプリケーションが起動します。今回は下記のようなアプリケーションを書いてみましょう。
#include "models/freezed_model.hpp" //gernerated model file
#include "tensor.hpp" //useful tensor classes
#include "mbed.h"
#include <stdio.h>
Serial pc(USBTX, USBRX, 115200); //baudrate := 115200
int main(void)
{
printf("Simple Add Model\n");
Context ctx; //creating the context class, the stage where inferences take place
//wrapping the input data in a tensor class
int input_data[4] = {1, 2, 3, 4};
Tensor* input_x = new WrappedRamTensor<int>({2, 2}, input_data);
get_freezed_model_ctx(ctx, input_x); // pass the tensor to the context
ctx.eval(); //trigger the inference
S_TENSOR output_tensor = ctx.get("add:0"); // getting a reference to the output tensor
const int* outputs = output_tensor->read<int>(0, 4); //getting the result back
printf("output[0]: %d\r\n", outputs[0]);
printf("output[1]: %d\r\n", outputs[1]);
printf("output[2]: %d\r\n", outputs[2]);
printf("output[3]: %d\r\n", outputs[3]);
return 0;
}
元々のコードとほぼ同じで計算グラフに2x2の行列を与えて足し合わせてもらいます。結果の表示はprintfを使うとsimulatorのコンソールにでます。それでは動かしてみます。
$ mbed-simulator . -c "-std=c++14"
下記のような画面がブラウザに表示されれば成功です。[[1, 2], [3, 4]] + [[1, 2], [3, 4]]の結果が正しく表示されています。
まとめ
今回はとてもシンプルな計算グラフをuTensorで動かしてみましたが、基本的にはオペレータがuTensor側でサポートされていれば更に複雑なモデルでも走らせることができます。使用したソースコードはこちらに置きましたのでぜひ試してみてください。
画像