2019年6月23日日曜日

Edge TPUで"Co-compiling multiple models"を試す

動機


Edge TPU Compilerには複数のモデルを同時にコンパイルすることができる。同時にコンパイルすることで、複数のモデルを1つのEdge TPUで同時に実行するときにパフォーマンスが向上することができるとある。なのでどの程度パフォーマンスが向上するのか計測してみた。

"Co-compiling multiple models"
https://coral.withgoogle.com/docs/edgetpu/compiler/

まとめとかいろいろ


(USB AcceleratorでかつUSB2.0の場合)
  • 複数モデルを連続して使用する(複数回の推論を行う)場合は、Edge TPU Compilerの同時コンパイルは有効。
  • 複数のモデルがEdge TPUのメモリに収まるかが重要。
    Edge TPUのメモリに収まらなかったモデルの推論の処理時間が遅くなってしまう。どちらのモデルを優先で使うかを考えてコンパイルする順序をかえる。このため、コンパイルする順序を変える(小さいモデルを先にする)と改善する(場合がある)。
  • ただし、Edge TPUのメモリに収まらなかった場合でも、同時コンパイルしない場合と比べ、トータルの処理時間は早くなる。
  • Dev Board(PCIe)やUSB3.0と接続した場合、推論の時間がどの程度変わってくるか不明(どちらの環境も持っていないので...)。


環境

  • Raspberry Pi 3 B+
  • Edge TPU USB Accelerator(接続は"最大動作周波数")
  • Edge TPU API Release1.9.2 (Apr 2019)
  • Edge TPU Compiler (Offline compiler) Version 1.0.249710469
  • 使用モデルはPre-trained model


複数モデルのコンパイル


使用モデル


Pre-trained modelのTF-Lite modelを使用。
項番の順にコンパイルに指定及び、推論を行う。
  1. Model1 : mobilenet_v2_1.0_224_inat_bird_quant.tflite
  2. Model2 : mobilenet_v2_1.0_224_inat_plant_quant.tflite

コンパイル


コンパイルはoff-line compilerのみ。コマンドライン引数で複数のモデルを指定する。コンパイル後、Edge TPU モデルが生成される。1つのモデルにまとめられるわけではなく、指定したTF-Liteモデルの分、生成される(最初は1つにまとめられると思っていた)。

edgetpu_compiler Model1 Model2


コマンドライン引数とコンパイル時の結果は以下の通り。

# edgetpu_compiler -o output/ -s mobilenet_v2_1.0_224_inat_bird_quant.tflite mobilenet_v2_1.0_224_inat_plant_quant.tflite
Edge TPU Compiler version 1.0.249710469
INFO: Initialized TensorFlow Lite runtime.

Models compiled successfully in 1390 ms.

Input model: mobilenet_v2_1.0_224_inat_bird_quant.tflite
Input size: 3.37MiB
Output model: output/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.tflite
Output size: 3.89MiB
On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 3.75MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 65
Operation log: output/mobilenet_v2_1.0_224_inat_bird_quant_edgetpu.log

Operator                       Count      Status

FULLY_CONNECTED                1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
ADD                            10         Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU

Input model: mobilenet_v2_1.0_224_inat_plant_quant.tflite
Input size: 4.76MiB
Output model: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.tflite
Output size: 5.22MiB
On-chip memory available for caching model parameters: 3.16MiB
On-chip memory used for caching model parameters: 2.49MiB
Off-chip memory used for streaming uncached model parameters: 2.59MiB
Number of Edge TPU subgraphs: 1
Total number of operations: 65
Operation log: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.log

Operator                       Count      Status

ADD                            10         Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU

Edge TPUのメモリのサイズ(On-chip memory)に注目すると、2つのモデルがEdge TPUのメモリに割り当てられている。そして、2つのモデルをコンパイルした場合、Edge TPUのメモリに収まり切らないため、2つ目のモデルのはみ出す分が、"Off-chip memory"(外部メモリ)割り当てられている。


1つ目のモデルのコンパイル
  • 1つ目のモデルがすべて、Edge TPUのメモリに割り当てられている(3.75Mib)。
  • Edge TPUのメモリの空きは3.16MiB(6.19 - 3.75Mib)。

On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 3.75MiB
Off-chip memory used for streaming uncached model parameters: 0.00B


2つ目のモデルのコンパイル
  • 2つ目のモデルのうち2.49Mib分が、Edge TPUの空きメモリから割り当てられる。
    このとき、Edge TPUの空きメモリは3.16Mibなので0.67Mib分の余白あり。
  • 外部メモリに2.59Mibが割り当てられた。

On-chip memory available for caching model parameters: 3.16MiB
On-chip memory used for caching model parameters: 2.49MiB
Off-chip memory used for streaming uncached model parameters: 2.59MiB


(ご指摘いただいて気づいたのだが)
Edge TPU Compilerはコンパイル時にモデルを識別するための"caching token"を付与する。推論の実行時にEdge TPUのメモリに展開されたモデルと、推論を行うモデルの"caching token"を比べて、異なれば推論を行うモデルをEdge TPUのメモリに展開する。このため、毎回異なるモデルでの推論を行った場合は、都度Edge TPUのメモリへの展開が行われるため、推論時間が遅くなってしまう。
複数モデルをコンパイルしたときは"caching token"が同一なのでEdge TPUのメモリが書き換わらないため、推論の処理時間が早くなる。
※より正確にはParameter data cachingを参照。


モデルの可視化


visualize.pyでコンパイルしたEdge TPU モデルを可視化すると、Tensorsが通常と異なることが分かる。
下の図は、通常のコンパイル(マルチモデルでない)した場合。
MoblieNetV2の各Tensorsが分かる。
MoblieNet V2 のPre-trained modelを可視化

下の図は、マルチモデルでコンパイルした場合。
Inputと中間のTensorsがすべて、map/TensorArrayStack/TensorArrayGatherV3となっている。
MoblieNet V2のマルチモデルのコンパイルした結果を可視化

map/TensorArrayStack/TensorArrayGatherV3はtensorflow::ops::TensorArrayGatherと思われるが、詳細は不明。


ベンチマーク


方法


Pythonスクリプトを自作して計測。コードはここ
単純に2つのモデルの推論を繰り返し行う。推論の処理時間はEdge TPUのAPI(get_inference_time)を使用。
計測の概要は以下。
  • モデルはImage Classificationのモデル。
  • 推論の処理時間を計測。比較用に
    モデルが1つの推論
    モデルが2つの推論・別々にコンパイルしたモデルを使用
    モデルが2つの推論・同じモデルを使用
    モデルが2つの推論・同時にコンパイルしたモデルを使用
  • 推論の初回と2回目以降は処理時間が異なる(初回はモデルを転送している)ので、別々に計測。
    初回の推論 : 10回計測した平均
    2回目以降の推論 : 100回計測した平均
    初回の推論時は、ClassificationEngineクラスの生成でかなり時間がかかるので回数を落としている。


初回の推論


[モデルが1つの場合]
推論時間平均[ms]
120.78

[モデルが2つの場合]

同時コンパイル平均推論時間[ms]
なし
Model1119.99
Model2159.26
なし
Model1, 2とも
同一モデル
Model1120.04
Model2119.96
あり
Model1120.14
Model2158.23

2回め以降の推論


[モデルが1つの場合]
推論時間平均[ms]
10.33

[モデルが2つの場合]

同時コンパイル平均推論時間[ms]
なし
Model1123.00
Model2163.27
なし
Model1, 2とも
同一モデル
Model110.33
Model210.34
あり
Model110.61
Model286.31


結果


初回の推論時

  • 同時コンパイルの有無に関係なく、Model1、2ともほぼ同じ処理時間がかかっている。
  • ただ、2つ目のモデルの処理時間が若干遅い(約40msほど遅い)。
  • Model1, 2に同一のモデルを指定した場合は、推論時間に差はない。
    Model2の推論時には、すでにモデルがEdge TPUのメモリに展開されているので処理時間は少なくなるはず?なぜか処理時間が変わらない結果となった。

2回目以降の推論時

  • 同時コンパイルしない場合は、初回の推論とほぼ同じぐらいの処理時間がかかっている。
    これは、推論ごとにEdge TPUのメモリの入れ替えが発生しているためと推測。
  • 同時コンパイルした場合は、効果が確認できる。
    1つ目のモデルは、モデルが1つの場合と同じ処理時間。
    2つ目のモデルは若干遅い(約70ms)これは、外部メモリからモデルをロードするためと推測。同時コンパイルしない場合と比べ、約190msの短縮。
  • Model1, 2に同一のモデルを指定した場合は、推論時間に差はない。
    Edge TPUのメモリの切り替えが発生しないためと推測。

その他


1つ目のモデルで大半のEdge TPUのメモリを使用してしまう場合の組み合わせを確認。

指定したTF-Liteモデルは以下。
Model1 : inception_v3_299_quant.tflite
Model2 : mobilenet_v2_1.0_224_inat_plant_quant.tflite

コンパイルした結果。
(1つ目のinception_v3でほぼすべてのEdge TPUのメモリが専有されてしまう)

# edgetpu_compiler -o output/ -s inception_v1_224_quant.tflite mobilenet_v2_1.0_224_inat_plant_quant.tflite 
Edge TPU Compiler version 1.0.249710469
INFO: Initialized TensorFlow Lite runtime.

Models compiled successfully in 2590 ms.

Input model: inception_v1_224_quant.tflite
Input size: 6.38MiB
Output model: output/inception_v1_224_quant_edgetpu.tflite
Output size: 6.93MiB
On-chip memory available for caching model parameters: 6.53MiB
On-chip memory used for caching model parameters: 6.53MiB
Off-chip memory used for streaming uncached model parameters: 182.19KiB
Number of Edge TPU subgraphs: 1
Total number of operations: 83
Operation log: output/inception_v1_224_quant_edgetpu.log

Operator                       Count      Status

CONCATENATION                  9          Mapped to Edge TPU
CONV_2D                        58         Mapped to Edge TPU
RESHAPE                        1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
MAX_POOL_2D                    13         Mapped to Edge TPU

Input model: mobilenet_v2_1.0_224_inat_plant_quant.tflite
Input size: 4.76MiB
Output model: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.tflite
Output size: 6.04MiB
On-chip memory available for caching model parameters: 1.50KiB
On-chip memory used for caching model parameters: 1.25KiB
Off-chip memory used for streaming uncached model parameters: 5.86MiB
Number of Edge TPU subgraphs: 1
Total number of operations: 65
Operation log: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.log

Operator                       Count      Status

DEPTHWISE_CONV_2D              17         Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
ADD                            10         Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU


推論結果(2回目以降の推論)
同時コンパイルした場合は、しない場合と比べ、約170msの短縮。
ただし、Model2(MoblieNet V2)の処理時間はかなり遅い(1つだけのモデルで推論した場合は約10ms程度)。


同時コンパイル平均推論時間[ms]
なし

Model1
212.19

Model2
162.95
する

Model1
18.91

Model2
183.4


1つ目と2つ目のモデルを入れ替えた場合
Model1 : mobilenet_v2_1.0_224_inat_plant_quant.tflite
Model2 : inception_v1_244_quant.tflite

コンパイルした結果。
1つ目のMobileNetV2のモデルサイズは小さいため、2つ目のInception V3にもEdge TPUのメモリが割り当てられる(1.45MiB)。

# edgetpu_compiler -o output/ -s mobilenet_v2_1.0_224_inat_plant_quant.tflite inception_v1_224_quant.tflite 
Edge TPU Compiler version 1.0.249710469
INFO: Initialized TensorFlow Lite runtime.

Models compiled successfully in 2534 ms.

Input model: mobilenet_v2_1.0_224_inat_plant_quant.tflite
Input size: 4.76MiB
Output model: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.tflite
Output size: 5.22MiB
On-chip memory available for caching model parameters: 6.53MiB
On-chip memory used for caching model parameters: 5.08MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 65
Operation log: output/mobilenet_v2_1.0_224_inat_plant_quant_edgetpu.log

Operator                       Count      Status

ADD                            10         Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU

Input model: inception_v1_224_quant.tflite
Input size: 6.38MiB
Output model: output/inception_v1_224_quant_edgetpu.tflite
Output size: 6.93MiB
On-chip memory available for caching model parameters: 1.45MiB
On-chip memory used for caching model parameters: 1.45MiB
Off-chip memory used for streaming uncached model parameters: 5.16MiB
Number of Edge TPU subgraphs: 1
Total number of operations: 83
Operation log: output/inception_v1_224_quant_edgetpu.log

Operator                       Count      Status

RESHAPE                        1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
AVERAGE_POOL_2D                1          Mapped to Edge TPU
MAX_POOL_2D                    13         Mapped to Edge TPU
CONCATENATION                  9          Mapped to Edge TPU
CONV_2D                        58         Mapped to Edge TPU


推論結果(2回目以降の推論)
1つ目と2つ目のモデルを入れ替える前と比べて、約20msの短縮。
Model1(MobileNet V2)は、1つだけのモデルで推論した結果とほぼ変わらない。
Model2(Inception V1)の処理時間が遅くなっているがトータルとしてMoblieNet V2が早くなった分、改善している。

[2019/6/25 指摘を頂いて追記]
モデルを入れ替えることによって推論時間のトータルが改善することは意味がない。
(今回のケースではたまたま処理時間が改善しているように見えるだけであると推測)
モデルを入れ替える場合はどちらのモデルが使用頻度が高いか(Model1のほうが使用頻度が高ければ順序を先にする)を考慮したほうがメリットがある。


同時コンパイル平均推論時間[ms]
あり

Model1
11.18

Model2
169.28

2019年6月20日木曜日

Raspberry Pi 3 で Yocto (AArch64 TensorFlow Weston)

YoctoでRaspberry Pi 3 Aarch64にTensorFlowを組み込んでみる。
OpenEmbedded Layer にmeta-tensorflowのレシピが登録されていたことを発見したのが理由。

手順としては公式に記載のとおりなのだが、自分の環境ではリソースが足りないため少しレシピを変えた。
→TensorFlowのビルド(Bazel)が2つ同時(tensorflowとtensorflow-native)に走るため、メモリが足りなくなってしまう(おそらく、64Gとか必要なのかもしれない?)

手を加えたレシピは以下で公開。
https://github.com/NobuoTsukamoto/meta-tensorflow
手を加えたのは、
  • tensorflowとtensorflow-nativeが同時に走らないように、DEPENDを設定。
  • Bazelのビルドオプションにlocal_resourcesフラグをつけて、CPU/MEMのリソースを設定。

準備


TensorFlowをビルドするにはホストマシンに、追加でlibatomicをビルド環境にインストールする(自分の環境はFedora 30)。

$ sudo dnf install libatomic


ダウンロード・レイヤーの追加


master環境を引っ張ってくる。

$ git clone git://git.yoctoproject.org/poky.git
$ cd poky/
$ git clone git://git.openembedded.org/meta-openembedded
$ git clone git://git.yoctoproject.org/meta-raspberrypi
$ git clone https://github.com/NobuoTsukamoto/meta-tensorflow.git
$ cd ../
$ source poky/oe-init-build-env rpi-build
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-oe/
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-multimedia/
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-python/
$ bitbake-layers add-layer ../poky/meta-openembedded/meta-networking/
$ bitbake-layers add-layer ../poky/meta-raspberrypi/
$ bitbake-layers add-layer ../poky/meta-tensorflow/

Bitbake


conf/local.confに以下を追加する。
(AArch64ビルドの指定と、TensorFlowの追加)

MACHINE ?= "raspberrypi3-64"
IMAGE_INSTALL_append = " tensorflow"

あとは、bitbakeコマンドでビルド

Build Configuration:
BB_VERSION           = "1.43.0"
BUILD_SYS            = "x86_64-linux"
NATIVELSBSTRING      = "fedora-30"
TARGET_SYS           = "aarch64-poky-linux"
MACHINE              = "raspberrypi3-64"
DISTRO               = "poky"
DISTRO_VERSION       = "2.7+snapshot-20190618"
TUNE_FEATURES        = "aarch64 cortexa53 crc"
TARGET_FPU           = ""
meta                 
meta-poky            
meta-yocto-bsp       = "master:27d60c5a812774f0e5c43161e5b514c4aebdf301"
meta-oe              
meta-multimedia      
meta-python          
meta-networking      = "master:3b245e4fe85be62c309650e84d1aaacbcb0d5505"
meta-raspberrypi     = "master:40283f583b57d67798135ed4a94cb8b5288ea965"
meta-tensorflow      = "master:94dff656415a534cdfa899cca9e87bc01fe15341"

そういえば、TUNE_FEATURESが昔は"aarch64"だけだった気がする。

SDカード書き込みと起動確認


いつものようにSDカードに書き込みして起動。Python3からTensorFlowがimportできるか確認。

やった〜

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の生成に失敗(エラーが出力)していたが、モデルのファイル自体は生成されていた。
このモデルを使った場合は、常に推論結果が同じ値になる(もしかして初期値???)ことがわかった。。。


2019年6月1日土曜日

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

Edge TPUで独自のモデルを作った(MNIST Edge TPU)ときにいろいろわかった点、つまずいた点をまとめてみたい。ここに記載のコードはMNIST Edge TPUのものです。
(これらは自分の理解です。間違っていたらすみません。)

まずは、学習モデルの構築〜FreezeGraphまでを説明。
その2はこちら

Kerasではだめなの?


現時点:2019/5/31でKerasはQuantization-aware trainingをサポートしていない #27880
Issueでは2019の2Q Endに対応するとあるので、気長に待つしかない。


Edge TPU Modelを作るまでの流れ


学習モデルの構築からEdge TPU Modelを生成する流れを図にすると以下になる。
モデルの構築の流れ

それぞれ生成されるファイルの内容は
  • CheckPoints:
    学習した結果の重みが保存されてるファイル。
  • GraphDef:
    モデルの構造のみが記録されたファイル。
  • FreezeGraph:
    CheckPoints+GraphDef。
    モデルの構造と学習した結果の重みを保存する。
    ProtocolBuffers形式である。
  • TF-Lite Model:
    量子化したTF-Liteモデル。TensorFlow Lite Converterで生成。
  • Edge TPU Model
    Edge TPU で動作するモデル。Edge TPU Compilerで生成。


Quantization-aware training


Edge TPUモデルを生成する場合は"量子化学習"が必要になる。
詳細は、Quantization-aware trainingを参照。

学習モデルと推論モデルの構築時、モデル(Graph)を書き換える(量子化を意識したノードを追加)ためにAPIを呼び出す必要がある。
モデルを書き換える前に呼び出しても意味がない。

学習モデルの構築時

学習モデルの構築は通常と何ら代わりはない。量子化学習を行う場合は、tf.contrib.quantize.create_training_graphでモデルの書き換えを行う。
これにより、量子化学習に必要なノードがモデルに追加される。

  • loss関数を指定した直後(tf.losses.sparse_softmax_cross_entropyなど)に指定を行う。
    (勘違いかもしれないが、training Opsの指定後だとうまく変換できなかった?)
  • 引数のquant_delayは量子化学習を開始するstep数を指定する。
    通常、学習が十分学習が進んだころ(曲線が寝たぐらい)から開始するのがよいとある。または、すでに通常の学習モデルがある場合は、同じデータセットで最初から(quant_delay=0)である程度の学習を行ってもよいと思われる(Fine-tuningのようなもの?DeepLabquant_delay=0でPASCAL-VOCの再学習を行っている)。

    with tf.Graph().as_default():
        # Generate placeholders for the images and labels.
        images_placeholder, labels_placeholder = placeholder_inputs(batch_size)

        # Build a Graph that computes predictions from the inference model.
        logits = mnist_inference(images_placeholder, hidden1, hidden2)

        # Add to the Graph the Ops for loss calculation.
        loss = mnist_loss(logits, labels_placeholder)
    
        # Quantization-aware training
        if quantize_delay >= 0:
            print('tf.contrib.quantize.create_training_graph')
            tf.contrib.quantize.create_training_graph(quant_delay=quantize_delay)
            
        # Add to the Graph the Ops that calculate and apply gradients.
        train_op = mnist_training(loss, learning_rate)

推論モデルの構築

推論モデルも同様にモデル構築した直後に指定を行う。
tf.contrib.quantize.create_eval_graphモデルの書き換えを行う。

with tf.Graph().as_default() as graph:
    # Generate placeholders for the images
    placeholder = tf.placeholder(name='input_tensor', dtype=tf.float32, shape=(1, IMAGE_PIXELS))
    
    # Build a Graph that computes predictions from the inference model.
    logits = mnist_inference(placeholder, hidden1, hidden2)

    tf.nn.softmax(logits, name="softmax_tensor")

    # Rewrites an eval input_graph in place for simulated quantization.
    if quantize_delay >= 0:
        tf.contrib.quantize.create_eval_graph()


推論モデルを構築するときに注意すること


あまりサンプルもなくちょっと戸惑ったところ(Checkpointがあれば推論はできるので問題ないから?)。
基本的には学習モデルを構築したコードを流用すればよい。以下の点に注意。
  • tf.constantでモデルを構築しない。
    学習モデルと同じようにtf.Variableで構築する。
    tf.constantで構築すると、create_eval_graphで量子化モデルに書き換わらない。
  • 学習モデルでは、活性化関数とloss関数がセットになったAPIを指定している
    例)
    TensorFlowのMNISTの場合、tf.losses.sparse_softmax_cross_entropyを使っている。この場合、活性化関数はsoftmaxなので、推論モデルではtf.nn.softmaxを指定する。
  • プレースホルダー、活性化関数(出力)には名前をつける。
    FreezeGraph, TF-Liteへの変換の際、必要になるので予め指定しておいたほうがよい。

FreezeGraph


freeze_graphコマンドでCheckPointsとGraphDefをマージする。
基本的に量子化学習に関係することはなく、ほとんど通常と同じである。
freeze_graphコマンドはPython APIも用意されている。
DeepLabのdeeplab/export_model.pyfreeze_graph_with_def_protosを使ってFreeze Graphを行っている(ただ、APIリファレンスはない?)。

その2に続く)