2019年6月2日日曜日

Edge TPUで独自モデルを作る(その2)


Edge TPUで独自のモデルを作った(MNIST Edge TPU)ときにいろいろわかった点、つまずいた点をまとめてみたいその2
その1はこちら

TF-Lite モデルの生成とEdge TPU APIについてを説明。

TensorFlow Lite Convertor


tflite_convertコマンドでFreezeGraphしたモデルをTF-Liteモデルに変換する。
コマンドリファレンスはここを参照。

$ tflite_convert \
  --output_file=./logs/output_tflite_graph.tflite \
  --graph_def_file=./logs/freeze_minist_fully_connected_feed_graph.pb \
  --inference_type=QUANTIZED_UINT8 \
  --input_arrays=input_tensor \
  --output_arrays=softmax_tensor \
  --mean_values=0 \
  --std_dev_values=255 \
  --input_shapes="1,784"

パラメータの概要は
  •  inference_type
    "QUANTIZED_UINT8"を指定。Edge TPU Modelに変換する場合に指定する。
  • std_dev_values, mean_values
    入力がどのような値(範囲であったか)を指定(詳細は後述)。
  • input_arrays, output_arrays
    入力、出力ノードを指定。
  • input_shapes
    入力配列の形状(詳細は後述)。

std_dev_values, mean_valuesの指定


inference_typeにQUANTIZED_UINT8を指定した場合、入出力ともUINT8に量子化されれる。しかし、量子化される前の入力値(通常FLOATで正規化された値)の範囲を与えておく必要がある(inference_typeがQUANTIZED_UINT8の場合、逆量子化はされないとある)。
計算式は以下となっている。

real_value = (quantized_input_value - mean_value) / std_dev_value

real_valueは量子化される前の値、quantized_input_valueは量子化された値(推論時に入力)。
mean_valueは量子化される前の値で0.0(FLOAT)となるUINT8の値。
std_dev_valuesは(UINT8の最大- UINT8の最小) / (FLOATの最大 - FLOATの最小)。
詳細の解説はstackoverflowを参照。

例えば、
  • tensorflow/modelsのslimobject_detection
    -1.0から0.0の入力値であるため、mean_value=128、std_dev_value=128。
    (Edge TPUのサンプルから)
  • tensorflow/modelsのdeeplabや自作のMNIST Edge TPU
    0.0から1.0の入力値であるため、mean_value=0、std_dev_value=255

input_shapesの指定


Edge TPUのAPI(というか、TF-LiteのAPI)では、入力が1次元配列となるため、元の形状(バッチサイズ、入力配列の形状)を覚えておく必要がある。

MNIST Edge TPUの場合、バッチサイズは1、入力画像は28x28のグレースケールをFlattenした784の1次元配列なので、"1, 784"を指定する。

slim(Image Classification)の場合は、バッチサイズは1で入力画像は幅x高さのカラー画像になるので、"1, 幅, 高さ, 3"とかになる。

エラーで変換できない場合


コマンド実行時に、
"Either target a non-quantized output format, or change the input graph to contain min/max information, or pass --default_ranges_min= and --default_ranges_max= if you do not care about the accuracy of results."
で変換できない場合がある。
この場合は、以下を疑ってみる(すべての原因ではないので注意)
  • Quantization-aware trainingであるか?
    create_training_graph、create_eval_graphでモデルの書き換えを行っているか?
  • output_arraysが正しいか?
    出力ノードを指定していない場合、エラーとなるケースがある。
    summarize_graphなどで出力ノードを再確認。


作成したモデルをEdge TPU APIで使う


独自に作成したEdge TPU モデルは、edgetpu.basic.basic_engine.BasicEngineで推論を行う。
MNIST Edge TPUの推論のコードはここを参照。
  • コンストラクタの引数model_pathでモデルのファイルパスを指定
  • RunInferenceで推論を実行

RunInferenceメソッドの引数は、一次元のUINT8の配列である。
画像を入力する場合は、配列をflattenする必要がある。

戻り値は2つのタプルで、1つ目は推論時間(ミリ秒)、2つ目は出力のtensorである。
MNIST Edge TPUの場合は、10個の配列(0-9)で一番値が大きいindexが正解ラベルになる。このため、argmaxで正解ラベルを取得することができる(ここは、通常のTensorFlowとおなじ)。

なお、image classification用APIのedgetpu.classification.engineとobject detection用APIのedgetpu.detection.engineは、edgetpu.basic.basic_engine.BasicEngineを呼び出している。
ここここを参照)
なので、独自のモデルを使う場合の参考になると思う。

推論結果がいつも同じになる


どんな入力を与えても推論結果が常に同じ値になってしまうことがあった。
当初、std_dev_values, mean_valuesの指定が誤っていると思っていたが、実は他に原因があった。

学習をJupter Notebookで行っていたが、学習完了後、kernelをシャットダウンせず、FreezeGraph、TF-Lite Modelの生成を行っていた。このとき、GPUリソースは開放されていないので、実はFreezeGraph、TF-Lite Modelの生成に失敗(エラーが出力)していたが、モデルのファイル自体は生成されていた。
このモデルを使った場合は、常に推論結果が同じ値になる(もしかして初期値???)ことがわかった。。。


0 件のコメント:

コメントを投稿