TensorFlowのモデルをIntelのMovidius Neural Compute Stickを使ってRaspberryPiで動作させるメモ
概要
Raspberry PiでTensorFlow使って画像認識してしたい!
でもRaspberry PiのCPUでTensorFlow動かしても死ぬほど遅い
そこでIntelのMovidiusをRPIにぶっさすことで,超高速に推論ができるというものです.
これを動かすのにとても苦労したので,メモとして残しておきます.
ちなみに,推論しかできません.学習は別のコンピュータでやりましょう.
手順としては,
- ハイスペックなコンピュータでTensorFlowを使って学習
- TensorFlowの学習済みモデルを保存して,Movidiusで動くグラフにコンパイル
- グラフをRPIに持ってきて,Movidiusで推論
という方法になります.
この記事について
以下,やってる途中に書いたメモのコピペです.
説明するために書いてるわけじゃないのでかなり読みずらいと思います.
質問がありましたらTwitterまで
とーくん (@KTokunn) | Twitter
長ったらしいメモなので真面目に読むことはお勧めしません
ctrl+fでエラーメッセージのキーワードで検索してお読みください.
やったこと
- TensorFlow Low LayerのMnist (deep mnist)のサンプルをMovidiusで実行
- mvNCCompile,mvNCProfile,mvNCCheckが動くようにライブラリを書き換え (なぜか私の環境ではこれらのコンパイラが正しく動作しなかった)
- mvNCCheckに画像を読み込ませるやつはむっちゃごり押し
あとは,やたら出力とかをdetailsタグでまとめたので
Movidius NCSDK について
NCSDKとは
Movidius NCSDKはMovidius用にモデルを変換・チェックするためのコンパイラと、モデルを利用して推測を行うためのAPIを含むSDKのこと。
mvNCCompile, mvNCCheck, mvNCProfile
Movidius NCSDKのバージョン
- NCSDK1
- NCSDK2
NCSDK2に含まれるNCAPI v2ではAPIの関数が異なるため互換はない。
今回は情報が多いのでNCSDK1を使っていくことにします.
なんか,SDK1だと16bit floatしか使えないのに対してSDK2だと32bit float使えるらしい(?)
Movidius NCSDKをTensorFlowから使う方法
- TensorFlowでプログラムを書く。
- モデルを保存する。
- TensorFlowからモデルを開いて編集をして、もう一度保存する。
- 保存したモデルをMovidius用のgraphにコンパイルする。
- graphをMovidiusに転送して実行する。
NCSCKのインストール
詳しくは https://movidius.github.io/ncsdk/install.html
Raspberry Pi 3で行う場合にはそれぞれ4時間程度かかる。
NCSDK1
wget https://ncs-forum-uploads.s3.amazonaws.com/ncsdk/ncsdk-02_05_00_02-full/ncsdk-2.05.00.02.tar.gz
NCSDK2
wget https://ncs-forum-uploads.s3.amazonaws.com/ncsdk/ncsdk-01_12_00_01-full/ncsdk-1.12.00.01.tar.gz
共通
tar xvf ncsdk-* cd ncsdk-* make install make examples
Exampleの実行 (v1, v2)
ncsdk-*/examples/tensorflow/inception_v3
にInception-V3のExampleがある。
ファイル構成
Makefile inception-v3.py run.py categories.txt inputsize.txt
動作
make
でinception-v3.pyとmvNCCompileが呼び出される。
inception-v3.py
は学習済みのInception-V3モデルのダウンロード、ckpt.meta
ファイルの保存を行う。
モデルはtensorflow.contrib.slim.nets
からダウンロードされる。
mvNCCompile
はckpt.metaからgraph
ファイルを作り出す。
make run
でrun.pyが呼び出される。
run.pyではgraphファイルをMovius上にロードして一枚の画像の推論を行い、結果を出力する。
動作結果
Number of categories: 1001 Start download to NCS... ******************************************************************************* inception-v3 on NCS ******************************************************************************* 547 electric guitar 0.9883 403 acoustic guitar 0.00772 715 pick, plectrum, plectron 0.001509 421 banjo 0.000926 820 stage 0.0006595 ******************************************************************************* Finished
V1のAPIでもV2のAPIでも正しく動作し、electric guitarの出力が得られた。
. Movidiusなし [10.55, 2.15, 2.16, 2.10, 3.90, 2.12, n/a, n/a, n/a, n/a] (10回実施、単位は秒) RAM使用率 90%程度 CPU使用率 90%程度 predictを実行するとdmesgにUnder-voltage detected!と表示されることがある。表示された場合にはpredictにかかる時間が長くなる。 複数回続けて実行すると電力不足のためかシステムが落ちる。(今回の結果では7回目以降) 2. Movidiusあり [0.62, 0.59, 0.60, 0.61, 0.61, 0.61, 0.61, 0.61, 0.61, 0.61] (10回実施、単位は秒) RAM使用率 10%程度 CPU使用率 5%程度
任意のTensorFlowモデルのコンパイル (自作モデルのコンパイル)
学習
自作のTensorFlowモデルをMovidius用にコンパイルするためには、ソースコードの編集を行いMovidius用にする必要がある。
1. 入力のPlaceholderに名前を付ける
2. tf.train.Saver()
を使って学習済みネットワークを保存する
これによって、
****.index ****.data-00000-of-00001 ****.meta
の3つのファイルが生成される。
コンパイル可能なファイルに変換
次に、生成された学習済みモデルをもう一度開いてMovidiusでコンパイルが可能なモデルを出力する。 修正点は次の通り 1. 出力の活性化関数に名前を付ける 2. 入力以外のPlaceholderをすべて削除する 3. dropoutを削除する 4. 学習用データの読み込みを削除する 5. loss, training, accuracyなど学習用のコードを削除する
こられの変更をしたコードで、保存したモデルをrestore
で開き、またsave
する。
これによって、
****_inference.index ****_inference.data-00000-of-00001 ****_inference.meta
の3つのファイルが生成される。
コンパイル
mvNCCompile
コマンドを使って保存した****.meta
ファイルをgraph
ファイルに変換する。
mvNCCompile ****_inference.meta -s 12 -in input -on output -o ****_inference.graph
この時、input
とoutput
にはそれぞれ指定したinput nodeとoutput nodeの名前を入れる。
実行結果
TensorflowのExampleにあるdeep_mnist.pyを試した。
tokunn@tokunn-VirtualBox 11:31:44 [~/Documents/MovidiusTensorflow/mnist] $ mvNCCompile mnist_inference.meta -s 12 -in input -on output -o mnist_inference.graph mvNCCompile v02.00, Copyright @ Movidius Ltd 2016 /usr/local/lib/python3.5/dist-packages/tensorflow/python/framework/ops.py:766: DeprecationWarning: builtin type EagerTensor has no __module__ attribute EagerTensor = c_api.TFE_Py_InitEagerTensor(_EagerTensorBase) /usr/local/lib/python3.5/dist-packages/tensorflow/python/util/tf_inspect.py:45: DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() instead if d.decorator_argspec is not None), _inspect.getargspec(target)) Traceback (most recent call last): File "/usr/local/bin/mvNCCompile", line 118, in <module> create_graph(args.network, args.inputnode, args.outputnode, args.outfile, args.nshaves, args.inputsize, args.weights) File "/usr/local/bin/mvNCCompile", line 104, in create_graph net = parse_tensor(args, myriad_config) File "/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py", line 293, in parse_tensor if have_first_input(strip_tensor_id(node.outputs[0].name)): IndexError: list index out of range
ファイル変換までは問題なく動くが、コンパイル時にmvNCCompile内でIndexErrorを起こして止まってしまう。 配列の要素を確認せずにアクセスしていることが原因。
ライブラリの書き換え
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
を編集して、回避する。
やってよいのかどうかは不明。
< if have_first_input(strip_tensor_id(node.outputs[0].name)): --- > # ******* EDIT ****** > print(len(node.outputs)) > if len(node.outputs) and have_first_input(strip_tensor_id(node.outputs[0].name)):
これによってmvNCCompileは通って、graphファイルも生成されるようになった。
tokunn@tokunn-VirtualBox 11:36:34 [~/Documents/MovidiusTensorflow/mnist] $ mvNCCompile mnist_inference.meta -s 12 -in input -on output -o mnist_inference.graph mvNCCompile v02.00, Copyright @ Movidius Ltd 2016 /usr/local/lib/python3.5/dist-packages/tensorflow/python/framework/ops.py:766: DeprecationWarning: builtin type EagerTensor has no __module__ attribute EagerTensor = c_api.TFE_Py_InitEagerTensor(_EagerTensorBase) /usr/local/lib/python3.5/dist-packages/tensorflow/python/util/tf_inspect.py:45: DeprecationWarning: inspect.getargspec() is deprecated, use inspect.signature() instead if d.decorator_argspec is not None), _inspect.getargspec(target)) 1 1 1 1 0 1 /usr/local/bin/ncsdk/Controllers/FileIO.py:52: UserWarning: You are using a large type. Consider reducing your data sizes for best performance "Consider reducing your data sizes for best performance\033[0m")
Movidiusで動作確認 (v1)
実際にどうさせてみる。 以下、特筆なしの場合、APIはv1を使用。
pi@raspberrypi:~/week2/workspace $ python3 prediction_byMovidius4mvncV1.py Start prediting ... 2 2 8 2 2 2 2 2 2 2 2 2 8 2 2 2 2 2 2 0 2 2 2 2 2 2 8 8 2 2 0 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 0 8 2 2 2 2 2 2 2 0 2 2 2 2 2 2 8 8 0 2 0 2 2 2 2 2 8 2 0 2 2 2 2 2 2 2 2 0 0 2 2 8 2 2 2 2 2 2 0 8 2 2 2 2 2 2 2 2 2 2 2 2 0 2 2 8 2 2 2 2 2 0 2 2 2 2 8 2 2 2 2 2 2 2 2 2 2 2 0 2 2 2 2 8 2 2 2 0 2 2 2 8 8 2 2 2 2 2 2 2 2 2 2 2 2 8 2 2 2 8 2 2 2 2 8 2 2 2 2 2 2 2 8 2 0 2 2 2 0 2 2 8 2 2 2 0 2 0 8 2 2 2 2 2 2 2 8 2 2 2 8 2 8 2 2 2 2 2 2 8 2 2 2 8 2 2 8 8 2 2 2 0 2 2 2 0 0 2 2 2 2 2 8 2 0 2 2 8 2 2 8 2 2 2 2 2 0 2 2 8 2 2 2 2 2 2 0 8 0 2 2 0 2 0 8 0 2 2 2 2 2 2 0 8 2 2 0 2 2 2 2 2 0 2 2 2 2 2 2 2 0 2 0 2 2 2 8 2 2 2 2 2 2 2 0 2 2 0 2 2 8 2 2 8 2 2 0 2 0 2 0 2 2 2 0 2 0 2 2 8 2 2 2 0 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 8 2 2 2 8 2 2 2 2 0 2 2 2 8 2 2 0 2 2 2 2 2 0 2 2 2 2 2 2 0 8 2 2 2 8 2 0 2 2 2 2 2 2 2 0 2 2 2 2 2 2 2 2 2 2 8 2 2 2 2 8 2 2 2 2 2 2 0 0 2 2 2 2 2 0 8 2 2 2 2 2 2 2 2 2 2 2 8 2 2 2 2 8 2 8 2 2 0 2 2 2 2 2 2 2 0 2 2 2 2 0 2 2 2 2 8 2 8 2 2 2 2 2 2 2 2 8 2 2 2 2 8 2 2 2 2 2 2 2 Time : 4.403196096420288 (500 images)
正しくない結果が出力された。
1~9までの9枚で確認する。
pi@raspberrypi:~/week2/workspace $ vim prediction_byMovidius4mvncV1.py pi@raspberrypi:~/week2/workspace $ python3 prediction_byMovidius4mvncV1.py ../../JPEGImages/pickup/00628.jpg ../../JPEGImages/pickup/00042.jpg ../../JPEGImages/pickup/00025.jpg ../../JPEGImages/pickup/00652.jpg ../../JPEGImages/pickup/00013.jpg ../../JPEGImages/pickup/00520.jpg ../../JPEGImages/pickup/00663.jpg ../../JPEGImages/pickup/00683.jpg ../../JPEGImages/pickup/00433.jpg Start prediting ... [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] Time : 0.11402153968811035 (9 images)
なぜか4と7が100%"8"だと認識されて、それ以外は100%"2"だと認識される。 逆順に入れてみる。
pi@raspberrypi:~/week2/workspace $ vim prediction_byMovidius4mvncV1.py pi@raspberrypi:~/week2/workspace $ python3 prediction_byMovidius4mvncV1.py ../../JPEGImages/pickup/00433.jpg ../../JPEGImages/pickup/00683.jpg ../../JPEGImages/pickup/00663.jpg ../../JPEGImages/pickup/00520.jpg ../../JPEGImages/pickup/00013.jpg ../../JPEGImages/pickup/00652.jpg ../../JPEGImages/pickup/00025.jpg ../../JPEGImages/pickup/00042.jpg ../../JPEGImages/pickup/00628.jpg Start prediting ... [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] Time : 0.11403870582580566 (9 images)
やはり4と7が100%"8"だと認識されて、それ以外は100%"2"。
もう一度最初から
学習
deepmnist.pyをjupyter-notebookで実行。
saving graph to: /tmp/tmpc_vjn3sg step 0, training accuracy 0.08 step 10, training accuracy 0.4 step 20, training accuracy 0.46 (略) step 470, training accuracy 0.96 step 480, training accuracy 0.92 step 490, training accuracy 0.96 test accuracy 0.939
Test Accuracyは0.939で正しく学習できている。
出力ファイルが正しく保存されていることを確認。
tokunn@tokunn-VirtualBox 13:49:04 [~/Documents/MovidiusTensorflow/mnist0911] $ ls -1 MNIST_data/ checkpoint conv4movidius.ipynb deepmnist.ipynb mnist_model.data-00000-of-00001 mnist_model.index mnist_model.meta output/
さらに、Movidius用に保存しなおし。
tokunn@tokunn-VirtualBox 13:51:43 [~/Documents/MovidiusTensorflow/mnist0911] $ ls -1 MNIST_data/ checkpoint conv4movidius.ipynb deepmnist.ipynb mnist_inference.data-00000-of-00001 mnist_inference.index mnist_inference.meta mnist_model.data-00000-of-00001 mnist_model.index mnist_model.meta output/
コンパイルを実行。
mvNCCompile mnist_inference.meta -s 12 -in input -on output -o mnist_inference.graph
RPIでMovidiusでpredictしてみる。
pi@raspberrypi:~/week2/mnist0911 $ python3 prediction_byMovidius4mvncV1.py ../../JPEGImages/pickup/00433.jpg ../../JPEGImages/pickup/00683.jpg ../../JPEGImages/pickup/00663.jpg ../../JPEGImages/pickup/00520.jpg ../../JPEGImages/pickup/00013.jpg ../../JPEGImages/pickup/00652.jpg ../../JPEGImages/pickup/00025.jpg ../../JPEGImages/pickup/00042.jpg ../../JPEGImages/pickup/00628.jpg Start prediting ... [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 1. 0. 0. 0. 0. 0. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] [0. 0. 0. 0. 0. 0. 0. 0. 1. 0.] Time : 0.1024773120880127 (10 images)
やはり変わらず。
RPI3じゃなくて実マシンでやってみる
Rspberry Piが悪いかもしれないから、実際のLinuxマシン(Ubuntu 16.04 - nanase)でやってみる。
tokunn@nanase 7:11:40 [~/Documents/week2/0911] $ python3 prediction_byMovidius4mvncV1.py ERROR 1: libgrass_vector.7.4.0.so: cannot open shared object file: No such file or directory ERROR 1: libgrass_vector.7.4.0.so: cannot open shared object file: No such file or directory ERROR 1: libgrass_dgl.7.4.0.so: cannot open shared object file: No such file or directory ERROR 1: libgrass_dgl.7.4.0.so: cannot open shared object file: No such file or directory Image path : ./JPEGImages/*.jpg Start prediting ... [ 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] [ 1. 0. 0. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] [ 3.35454941e-04 0.00000000e+00 0.00000000e+00 0.00000000e+00 1.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00] [ 0. 0. 0. 0. 0. 1. 0. 0. 0. 0.] [ 0. 0. 1. 0. 0. 0. 0. 0. 0. 0.] Time : 0.0796973705291748 (10 images)
やっぱり何かおかしい。
Rspberry Piだから起きている訳ではない。
おそらく問題はmvNCCompileで生成されたgraphファイルなのでは?
Tensorflowで保存したファイルが正しいかを確認する。
jupyter-notebook上で、mnist_modelからrestoreしてpredictさせてみる。
INFO:tensorflow:Restoring parameters from ./output/mnist_model test accuracy 0.9477
問題なくpredictできている。
mvNCCompileに入れているファイルが正しいかどうかを確認する
これが正しく動いていれば、問題はmvNCCompile。
もしくは突っ込んだファイルがダメか。
jupyter-notebook上で、mnist_inferenceからrestoreしてpredictさせてみる。
INFO:tensorflow:Restoring parameters from ./output/mnist_inference test accuracy 0.9477
こちらも問題なくpredictできている。 ---> 問題はmvNCCompile周辺
mvNCCheckでモデルの確認をしてみる
mvNCCheckを使って、モデルの確認を行う。
tokunn@nanase 8:20:05 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 USB: Transferring Data... USB: Myriad Execution Finished USB: Myriad Connection Closing. USB: Myriad Connection Closed. Result: (1, 1, 10) 1) 8 0.69141 2) 2 0.10852 3) 4 0.075806 4) 3 0.047607 5) 5 0.028824 Expected: (1, 10) 1) 8 0.685481 2) 2 0.111384 3) 4 0.078136 4) 3 0.0483064 5) 5 0.0283811 ------------------------------------------------------------ Obtained values ------------------------------------------------------------ Obtained Min Pixel Accuracy: 0.8644439280033112% (max allowed=2%), Pass Obtained Average Pixel Accuracy: 0.19093991722911596% (max allowed=1%), Pass Obtained Percentage of wrong values: 0.0% (max allowed=0%), Pass Obtained Pixel-wise L2 error: 0.325355674229958% (max allowed=1%), Pass Obtained Global Sum Difference: 0.013088561594486237 ------------------------------------------------------------
Result(Movidiusの出力)とExpected(TensorFlowの出力)がほぼ同じであることから、正しく動作していると考えられる。
ということは、問題なのは推論に使っているNCAPIのほう?
mvNCCheckを使って認識してみる
mvNCCheckに-iオプションを付けることで自分で画像ファイルを指定して入力することができる。
tokunn@nanase 8:58:09 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../JPEGImages/pickup/00013.jpg mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 Traceback (most recent call last): File "/usr/local/bin/mvNCCheck", line 152, in <module> quit_code = check_net(args.network, args.image, args.inputnode, args.outputnode, args.nshaves, args.inputsize, args.weights, args) File "/usr/local/bin/mvNCCheck", line 130, in check_net net = parse_tensor(args, myriad_config, file_gen=True) File "/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py", line 266, in parse_tensor int(shape[3]), IndexError: list index out of range
ライブラリ内の/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
でまたIndexError: list index out of range
が発生。
いい加減TensorFlowParserはリストにアクセスする前に要素数をチェックしていただきたい。
とりあえず204行目のdebug=Trueを有効化して、shapeを表示つするように追記。
204c204 < # debug = True --- > debug = True 263a264 > print("Input image shape", shape)
これを実行して、
tokunn@nanase 9:25:28 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../JPEGImages/pickup/00013.jpg mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 Input image shape [1, 784] Traceback (most recent call last): File "/usr/local/bin/mvNCCheck", line 152, in <module> quit_code = check_net(args.network, args.image, args.inputnode, args.outputnode, args.nshaves, args.inputsize, args.weights, args) File "/usr/local/bin/mvNCCheck", line 130, in check_net net = parse_tensor(args, myriad_config, file_gen=True) File "/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py", line 267, in parse_tensor int(shape[3]), IndexError: list index out of range
の結果を得る。
Input shapeは[1, 784]であることがわかる。
-i [画像]のオプションなしで実行すると、
tokunn@nanase 9:26:30 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 Input image shape [1, 784] 0 Const Const OUT: Const:0 /usr/local/bin/ncsdk/Controllers/TensorFlowParser.py line no.290 1 VariableV2 Variable OUT: Variable:0 (略) BiasAdd 75 Softmax output IN: fc2/add:0 OUT: output:0 Softmax (略) Result: (1, 1, 10) 1) 8 0.32178 2) 4 0.22034 3) 2 0.14233 4) 0 0.10773 5) 3 0.1059 (略)
のように正しく実行される。
Input shapeは[1, 784]で、エラーが起きたときと同じである。
あれ、なんでこれCNNなのに1次元([1,784]の2次元)で入力してるんだこれ?
[1,28,28]じゃないのかな
-> ちゃんと入力してからreshapeして28x28にしてた。
こちらとしては2次元([1, 784])で入力したいが、TensorFlowParserは最低でも4次元はないとお気に召さないらしい。
でも、内部で乱数生成しているときには何にも起きないのなんで?
とりあえず、問題のparse_img()の定義を探してみる。
tokunn@nanase 9:55:12 [/opt/movidius/NCSDK/ncsdk-x86_64/tk/Models] $ find / -type f 2>/dev/null | grep .py | xargs grep parse_img 2>/dev/null /usr/local/bin/ncsdk/Controllers/MiscIO.py:def parse_img(path, new_size, raw_scale=1, mean=None, channel_swap=None): /opt/movidius/NCSDK/ncsdk-armv7l/tk/Controllers/MiscIO.py:def parse_img(path, new_size, raw_scale=1, mean=None, channel_swap=None): /opt/movidius/NCSDK/ncsdk-x86_64/tk/Controllers/MiscIO.py:def parse_img(path, new_size, raw_scale=1, mean=None, channel_swap=None):
/usr/local/bin/ncsdk/Controllers/MiscIO.py
にあるらしい。
# 定義 227 def parse_img(path, new_size, raw_scale=1, mean=None, channel_swap=None): 228 """ 229 Parse an image with the Python Imaging Libary and convert to 4D numpy array 234 """
確かにあった。
ちなみに呼び出しは、
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
# 呼び出し 266: input_data = parse_img(image, 267- [int(shape[0]), 268- int(shape[3]), 269- int(shape[1]), 270- int(shape[2])], 271- raw_scale=arguments.raw_scale, 272- mean=arguments.mean, 273- channel_swap=arguments.channel_swap)
となっている。
呼び出しの変数をどうしたらいいのか知りたいので、parse_img()を追いかける。
/usr/local/bin/ncsdk/Controllers/MiscIO.py
248 if path.split(".")[-1].lower() in ["png", "jpeg", "jpg", "bmp", "gif"]: 250 greyscale = True if new_size[2] == 1 else False
どうやらnew_size[2]
(配列の3番目)はカラーチャンネルを確かめているらしい。
というわけでint(shape[1])
は今回は1となる。
第一引数のpath(呼び出し側ではimage)は画像ファイルへのパスを表している。
279 if (len(data.shape) == 2): 280 # Add axis for greyscale images (size 1) 281 data = data[:, :, np.newaxis] 282 283 data = skimage.transform.resize(data, new_size[2:]) 284 data = np.transpose(data, (2, 0, 1)) 285 data = np.reshape(data, (1, data.shape[0], data.shape[1], data.shape[2])) 286 287 data *= raw_scale
まず、grayscaleの場合にはshapeが(x, y)のみなので、1次元追加することで3次元としている。
これでshapeは(x, y, カラーチャンネル数)となる。
次にresizeの指定にnew_size[2]
とnew_size[3]
を使っている。
呼び出し側で、int(shape[1])
とint(shape[2])
となるわけだが、int(shape[1])
はグレースケールか否かを表しているのでは?
newsizeが使われているのはこの2か所のみ。
どういうこったい
次に、(x, y, カラーチャンネル数)を(カラーチャンネル数, x, y)になるように軸を入れ替えする。
ちなみに、正しく動作しているランダム値の時のinput_data
(TensorFlowParser内)は、
259 input_data = np.random.uniform(0, 1, shape)
となっているから、最終的にはinput_data
のshapeは(1,784)のもとの形となるべき。
これらの情報から引数の予想を立てる。
まず、new_image以外に入るべき値はそのままでよい。
次に、new_imageに入れるべきshapeについてであるが、通常は画像は(枚数, x, y, カラーチャンネル数)
のようになっているであろうと想像がつく。
これを呼び出し側では[枚数, カラーチャンネル数, x, y]
の形にして入力している。
よって、入力は[1, 1, shape[0], shape[1]]
となる。
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
268,271c268,271 < [int(shape[0]), < int(shape[3]), < int(shape[1]), < int(shape[2])], --- > [int(1), > int(1), > int(shape[0]), > int(shape[1])],
ただし、このままでは
250 greyscale = True if new_size[2] == 1 else False
の時にxの値を見てgreyscaleを判断してしまう。
なので、new_size[2]
ではなく、new_size[1]
書き換える。誤植?
/usr/local/bin/ncsdk/Controllers/MiscIO.py
250c250 < greyscale = True if new_size[2] == 1 else False --- > greyscale = True if new_size[1] == 1 else False
このパッチを当てて実行すると、
tokunn@nanase 11:16:22 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../JPEGImages/pickup/00013.jpg mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 Input image shape [1, 784] image path ../JPEGImages/pickup/00013.jpg /usr/local/lib/python3.6/dist-packages/skimage/transform/_warps.py:84: UserWarning: The default mode, 'constant', will be changed to 'reflect' in skimage 0.15. warn("The default mode, 'constant', will be changed to 'reflect' in " Traceback (most recent call last): File "/usr/local/bin/mvNCCheck", line 152, in <module> quit_code = check_net(args.network, args.image, args.inputnode, args.outputnode, args.nshaves, args.inputsize, args.weights, args) File "/usr/local/bin/mvNCCheck", line 130, in check_net net = parse_tensor(args, myriad_config, file_gen=True) File "/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py", line 274, in parse_tensor channel_swap=arguments.channel_swap) File "/usr/local/bin/ncsdk/Controllers/MiscIO.py", line 290, in parse_img data[0] = data[0][np.argsort(channel_swap), :, :] IndexError: index 2 is out of bounds for axis 0 with size 1
と新しいエラーが出る。
なので、とりあえずパッチを当てる。
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
272,274c272,274 < raw_scale=arguments.raw_scale, < mean=arguments.mean, < channel_swap=arguments.channel_swap) --- > raw_scale=1, > mean=None, > channel_swap=None)
これで実行すると、
tokunn@nanase 11:41:03 [~/Documents/week2/0911/output] $ mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../JPEGImages/pickup/00013.jpg mvNCCheck v02.00, Copyright @ Movidius Ltd 2016 input_data shape (1, 1, 1, 784) Traceback (most recent call last): File "/usr/local/bin/mvNCCheck", line 152, in <module> quit_code = check_net(args.network, args.image, args.inputnode, args.outputnode, args.nshaves, args.inputsize, args.weights, args) File "/usr/local/bin/mvNCCheck", line 130, in check_net net = parse_tensor(args, myriad_config, file_gen=True) File "/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py", line 281, in parse_tensor res = outputTensor.eval(feed_dict={inputnode + ':0' : input_data}) File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/ops.py", line 570, in eval return _eval_using_default_session(self, feed_dict, self.graph, session) File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/framework/ops.py", line 4455, in _eval_using_default_session return session.run(tensors, feed_dict) File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/client/session.py", line 889, in run run_metadata_ptr) File "/usr/local/lib/python3.6/dist-packages/tensorflow/python/client/session.py", line 1096, in _run % (np_val.shape, subfeed_t.name, str(subfeed_t.get_shape()))) ValueError: Cannot feed value of shape (1, 1, 784, 1) for Tensor 'input:0', which has shape '(1, 784)'
とエラーが出る。
終わりがない。
これやる意味あるのか?
別にmvNCCheckで画像を与えなくてもよくない?
PythonでのAPI追いかけるべきでは?
mvNCCheckのランダムでgraphは正しいと思ったけど、そもそも出力されたグラフがおかしかったら、tensorflowでやったやつもおかしな結果を出力するのでは
DAY2
やっぱり4次元にしたところで入力は2次元なので入力できない。
さらに、雑なパッチを当てる。
これでほかの画像は入力できないし、ほかのネットワークにも対応できない。
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
204c204 < debug = True --- > # debug = True 277a278 > input_data = input_data.transpose([0, 1, 3, 2])[0][0] 279a281 > #print(input_data) 297d298 < print("/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py line no.290")
実行してみる。
tokunn@nanase 23:49:44 [~/Documents/week2/0912/output] $ for i in $(ls ../JPEGImages/pickup/); do echo $i; mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../JPEGImages/pickup/$i 2>/dev/null; done 1.jpg Result: (1, 1, 10) 1) 2 0.28613 Expected: (1, 10) 1) 2 0.281931 ------------------------------------------------------------ 2.jpg Result: (1, 1, 10) 1) 2 0.81348 Expected: (1, 10) 1) 2 0.81391 ------------------------------------------------------------ 3.jpg Result: (1, 1, 10) 1) 2 0.87158 Expected: (1, 10) 1) 2 0.870968 ------------------------------------------------------------ 4.jpg Result: (1, 1, 10) 1) 3 0.39771 Expected: (1, 10) 1) 3 0.40986 ------------------------------------------------------------ 5.jpg Result: (1, 1, 10) 1) 2 0.70898 Expected: (1, 10) 1) 2 0.712083 ------------------------------------------------------------ 6.jpg Result: (1, 1, 10) 1) 5 0.77686 Expected: (1, 10) 1) 5 0.773194 ------------------------------------------------------------ 7.jpg Result: (1, 1, 10) 1) 5 0.49268 Expected: (1, 10) 1) 5 0.499106 ------------------------------------------------------------ 8.jpg Result: (1, 1, 10) 1) 2 0.95605 Expected: (1, 10) 1) 2 0.955554 ------------------------------------------------------------ 9.jpg Result: (1, 1, 10) 1) 2 0.50293 Expected: (1, 10) 1) 2 0.494139 ------------------------------------------------------------
やっぱりほとんどの数字が2だと出力される。
ただし、mvNCCheckからの場合には確率は100%ではない。
PythonAPIから呼び出しても、mvNCCheckから呼び出しても同じ結果が返ってくる。
ということは変換したgraphファイルがおかしいということがわかる。
mvNCCompileに渡したファイルか、mvNCCompileのどちらかが間違えている。
deep_mnistのサンプルをgithub上のコードでやってみる
deep_mnistのサンプルがgithub上にあった。 https://github.com/ashwinvijayakumar/ncappzoo/tree/mnist/tensorflow/mnist
動く。
mvNCCompileのパッチを元に戻しても大丈夫。
自分で用意したgraphとJPEGにしても動く。
あれ
これは、私がAPIを正しく使えていないのでは?
(もちろんmvNCCheck -i のほうはIndexError)
ただ、Intelのサンプルは確実に間違えていた。
原因
- 黒地に白字で学習させてたのに、推論の時に白地に黒字の画像でやっていた
- 画像を読み込んだ後に255で割るのを忘れていた
- mvNCCheckに画像を読み込ませる機能は動かない
ずっと、おかしな出力だと思っていたものは正しい出力であった。
結論 (動作した方法・ソースコード)
3つの問題を解決することができた。
1. TensorFlowのモデルをmvNCCompileでコンパイルできない
-> TensorFlowParser.pyの書き換え
2. mvNCCheckに-iオプションで画像を読み込ませることができるらしいが、動かない
-> TensorFlowParser.pyとMiscIO.pyをモデルに合わせて書き換え(汎用性なし)
3. 推論の結果がおかしい
-> 入力がおかしい
mvNCCompileの書き換え
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
の290行目付近を編集して、配列の要素を確認してからアクセスるようにする。
< if have_first_input(strip_tensor_id(node.outputs[0].name)): --- > if len(node.outputs) and have_first_input(strip_tensor_id(node.outputs[0].name)):
これで、mvNCCompileは通るようになる。
mvNCCheckの書き換え
/usr/local/bin/ncsdk/Controllers/MiscIO.py
の250行目付近を編集。
250c250 < greyscale = True if new_size[2] == 1 else False --- > greyscale = True if new_size[1] == 1 else False
/usr/local/bin/ncsdk/Controllers/TensorFlowParser.py
の270行目付近を編集。
265,271c265,271 < [int(shape[0]), < int(shape[3]), < int(shape[1]), < int(shape[2])], < raw_scale=arguments.raw_scale, < mean=arguments.mean, < channel_swap=arguments.channel_swap) --- > [int(1), > int(1), > int(shape[0]), > int(shape[1])], > raw_scale=1, > mean=None, > channel_swap=None) 272a273 > input_data = input_data.transpose([0, 1, 3, 2])[0][0]
これで
mvNCCheck mnist_inference.meta -s 12 -in input -on output -of mnist_inference.graph -i ../github_deep_mnist/ncappzoo/data/digit_images/one.png -S 255
で動き出す。ただし結果は?。入力する画像を加工すべき?
メインプログラム(学習用)
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf import tempfile def deepnn(x): with tf.name_scope('reshape'): x_image = tf.reshape(x, [-1, 28, 28, 1]) # -1 = number of x with tf.name_scope('conv1'): W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) with tf.name_scope('pool1'): h_pool1 = max_pool_2x2(h_conv1) with tf.name_scope('conv2'): W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2= bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) with tf.name_scope('pool1'): h_pool2 = max_pool_2x2(h_conv2) with tf.name_scope('fc1'): W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) with tf.name_scope('dropout'): keep_prob = tf.placeholder(tf.float32) h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) with tf.name_scope('fc2'): W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2 return y_conv, keep_prob def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME') def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) # random return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) # all 0.1 return tf.Variable(initial) def main(): mnist = input_data.read_data_sets('MNIST_data', one_hot=True) x = tf.placeholder(tf.float32, [None, 784], name='input') y_ = tf.placeholder(tf.float32, [None, 10]) y_conv, keep_prob = deepnn(x) with tf.name_scope('loss'): cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_, logits=y_conv) cross_entropy = tf.reduce_mean(cross_entropy) with tf.name_scope('adam_optimizer'): train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) with tf.name_scope('accuracy'): correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1)) correct_prediction = tf.cast(correct_prediction, tf.float32) accuracy = tf.reduce_mean(correct_prediction) graph_location = tempfile.mkdtemp() print('saving graph to: %s' % graph_location) train_writer = tf.summary.FileWriter(graph_location) train_writer.add_graph(tf.get_default_graph()) saver = tf.train.Saver() with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for i in range(500): batch = mnist.train.next_batch(50) if i % 10 == 0: train_accuracy = accuracy.eval(feed_dict={x: batch[0], y_: batch[1], keep_prob: 1.0}) print('step %d, training accuracy %g' % (i, train_accuracy)) train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5}) print('test accuracy %g' % accuracy.eval(feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) graph_location = "." save_path = saver.save(sess, graph_location + "/mnist_model") main()
モデル変換用
from tensorflow.examples.tutorials.mnist import input_data import tensorflow as tf import tempfile def deepnn(x): with tf.name_scope('reshape'): x_image = tf.reshape(x, [-1, 28, 28, 1]) # -1 = number of x with tf.name_scope('conv1'): W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) with tf.name_scope('pool1'): h_pool1 = max_pool_2x2(h_conv1) with tf.name_scope('conv2'): W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2= bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) with tf.name_scope('pool1'): h_pool2 = max_pool_2x2(h_conv2) with tf.name_scope('fc1'): W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024]) h_pool2_flat = tf.reshape(h_pool2, [-1, 7*7*64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) with tf.name_scope('fc2'): W_fc2 = weight_variable([1024, 10]) b_fc2 = bias_variable([10]) y_conv = tf.matmul(h_fc1, W_fc2) + b_fc2 return y_conv def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME') def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME') def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) # random return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) # all 0.1 return tf.Variable(initial) def main(): x = tf.placeholder(tf.float32, [None, 784], name='input') y_conv = deepnn(x) output = tf.nn.softmax(y_conv, name='output') saver = tf.train.Saver() with tf.Session() as sess: sess.run(tf.global_variables_initializer()) sess.run(tf.local_variables_initializer()) saver.restore(sess, '.' + '/output/mnist_model') saver.save(sess, '.' + '/output/mnist_inference') main()
コンパイル
mvNCCompile mnist_inference.meta -s 12 -in input -on output -o mnist_inference.graph
もしinputとoutputのノードの名前がinputとoutpuであれば、-inと-onいらないかもしれない。
Movidiusでの推論
import mvnc.mvncapi as mvnc import numpy as np from PIL import Image import cv2 import time, sys, os import glob IMAGE_DIR_NAME = './JPEGImages/pickup/' #IMAGE_DIR_NAME = 'github_deep_mnist/ncappzoo/data/digit_images' def predict(input): print("Start prediting ...") devices = mvnc.EnumerateDevices() device = mvnc.Device(devices[0]) device.OpenDevice() # Load graph file data with open('./output/mnist_inference.graph', 'rb') as f: graph_file_buffer = f.read() # Initialize a Graph object graph = device.AllocateGraph(graph_file_buffer) start = time.time() for i in range(len(input)): # Write the tensor to the input_fifo and queue an inference graph.LoadTensor(input[i], None) output, userobj = graph.GetResult() print(np.argmax(output), end=' ') stop = time.time() print('') print("Time : {0} ({1} images)".format(stop-start, len(input))) graph.DeallocateGraph() device.CloseDevice() return output if __name__ == '__main__': print("Image path : {0}".format(os.path.join(IMAGE_DIR_NAME, '*.jpg'))) jpg_list = glob.glob(os.path.join(IMAGE_DIR_NAME, '*.jpg')) if not len(jpg_list): print("No image file") sys.exit() jpg_list.reverse() print([i.split('/')[-1] for i in jpg_list]) img_list = [] for n in jpg_list: image = cv2.imread(n) image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) image = cv2.bitwise_not(image) iamge = cv2.resize(image, (28, 28)) img_list.append(image) img_list = np.asarray(img_list)[:10] * (1.0/255.0) print("imgshape ", img_list.shape) predict(img_list.astype(np.float16))