概要
ラズパイでtensorflowのモデルを使って推論をする際にtensorflow/kerasとh5ファイルを使ってもできますが、
モデルの読み込みが遅い
という欠点があります。
また、ラズパイはCPUしか積んでいませんので推論のみであればCPUに最適化されたtensorflow liteを使うのがおすすめです。
本記事では
・ラズパイの環境構築方法
・tensorflow liteへのモデル変換方法
・実際にラズパイで推論
を紹介します。
前提条件
TF liteへの変換環境
windows10
tensorflow 2.3
python3.8
ラズパイの環境
Raspbian GNU/Linux 11 (bullseye)
ラズパイの事前の環境構築
pipを使ってtensorflow liteのモジュールをインストールしましょう。
公式サイトを見れば情報が載っていますがちょっとわかりづらいですので抜粋します。
pip3 install --extra-index-url https://google-coral.github.io/py-repo/ tflite_runtime
上記コマンドでインストール可能です。
また、実際に推論するために画像ファイルの読み込みが必要ですので、
opencvをインストールもしておきましょう。
sudo apt install libavutil56 libcairo-gobject2 libgtk-3-0 libqtgui4 libpango-1.0-0 libqtcore4 libavcodec58 libcairo2 libswscale5 libtiff5 libqt4-test libatk1.0-0 libavformat58 libgdk-pixbuf2.0-0 libilmbase23 libjasper1 libopenexr23 libpangocairo-1.0-0 libwebp6
sudo apt-get install libatlas-base-dev
sudo apt-get install libjasper-dev
sudo pip3 install opencv-python==4.1.0.25
tensorflow liteへのモデル変換方法
今回はEfficientNetB3のimagenetの重みのモデルを変換してみようと思います。
転移学習で自分で学習したモデルでもいけますので問題ありません。
コードは以下のようになります。
import tensorflow as tf
#モデルの読み込み
model = tf.keras.applications.EfficientNetB3(
include_top=True, weights='imagenet'
)
#TFliteモデルへの変換
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
open("EfB3_imagenet.tflite", "wb").write(tflite_model)
たった3行で変換することができます。
上記のコードを実行すると
EfB3_imagenet.tflite
というファイルができると思います。
これをラズパイに送って使うことになります。
ラズパイでのモデル読み込み・推論方法
tensorflow liteを使った推論の方法はオリジナルのtensorflowの推論方法より少し煩雑です。
ですので、1行ずつ解説していきたいと思います。
ライブラリのインポート
まずはTF liteのモデルを扱うためのライブラリと画像読み込みのためのopencv+numpyのインポートです。
#TF liteのライブラリインポート
import tflite_runtime.interpreter as tflite
#画像読み込みと推論のためのライブラリインポート
import numpy as np
import cv2
モデルの読み込み
オリジナルのtensorflowであればモデルを読み込めばそのまま推論できますが、tensorflow liteの場合には
allocate_tensors()
というコードを実行する必要があります。
#モデルの読み込み
interpreter = tflite.Interpreter(model_path="EfB3_arcface.tflite")
#モデルのテンソルへの割り当て
interpreter.allocate_tensors()
モデルを読み込んだらモデルへの入力と出力の詳細を確認することができます。
通常は自分が作ったモデルですので、
・画像の入力サイズ
・どのような出力になるか
などわかっている状態だと思いますが、ネットに転がっているモデルを読み込んだ際には
確認しておくと良いでしょう。
コードは以下のようになります。
#入力の詳細の取得
input_details = interpreter.get_input_details()
#出力の詳細の取得
output_details = interpreter.get_output_details()
中身は入力数だけ分の辞書型の変数が入ったリストとなっています(。
[入力詳細辞書1、入力詳細辞書2,・・・]
中身を確認すると
入力の場合は
[{'name': 'input_1',
'index': 0,
'shape': array([ 1, 300, 300, 3]),
'shape_signature': array([ -1, 300, 300, 3]),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}}]
色々と情報が載っていますが、量子化をしていない場合には
・shape:入力の配列の形状
・dtype:入力の配列の型
を確認しておけばよいです。
ここで注意点ですが、
オリジナルのtensorflowの場合にはdtypeがintであろうがfloat64であろうが内部で勝手に変換されて推論してくれますが、tensorflow liteの場合には型が一致していないとうまく動作しません。
ですので型はしっかり確認しておきましょう。
ここではfloat32が指定されているようです。
出力の場合には
[{'name': 'Identity',
'index': 780,
'shape': array([ 1, 1000]),
'shape_signature': array([ -1, 1000]),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}}]
出力の場合には
shape:出力の配列の形状
だけを見ておけば問題ありませんが、出力が複数のモデルの場合には出力の順番は確認しておくとよいと思います。
入力データの準備
推論する前に入力するための配列を準備しておきましょう。
#画像のパスの定義
img_path = "画像のパス"
#画像の読み込み
temp = cv2.imread(img_path)
#BGRの並びをRGBに変換
temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)
#画像のリサイズ
temp = cv2.resize(temp, (300, 300))
#バッチ次元の追加と型変換
input_data = np.expand_dims(temp, axis=0).astype("float32")
基本的にはオリジナルのtensorflowの時と同じように
1.画像読み込み(必要あればBGR→RGB変換)
2.画像のリサイズ
3.バッチサイズ分の次元の追加
をすればOKですが、先ほども言ったようにtensorflow liteは
型を合わせるのが大事
ですので、以下のように型を変換しています。
#バッチ次元の追加と型変換
input_data = np.expand_dims(temp, axis=0).astype("float32")
opencvで画像を読み込んだ場合にはuint8という型になっていますので、
先ほど確認したfloat32に変換するようにしています。
推論
最後はついに推論です。オリジナルのtensorflowの場合は1行で推論できますが、
tensorflow liteの場合には
・データのセット
・処理実行
・出力の取り出し
の3つを実行する必要があります。
コードとしては以下のようになります。
データのセット
#入力画像のセット
interpreter.set_tensor(input_details[0]['index'], input_data)
少し複雑に見えますがやっていることはシンプルで以下のように引数を設定することになります。
set_tensor(何番目の入力か?, 入力するデータ)
何番目の入力か?という部分を先ほどの入力の詳細から取ってきています。
input_detail[0][‘index’]の意味は
・リストから0番目の入力詳細の辞書をとってきて
・「index」というキーの値を取ってくる
という処理になります。
従いまして、先ほどの
[{'name': 'input_1',
'index': 0,
'shape': array([ 1, 300, 300, 3]),
'shape_signature': array([ -1, 300, 300, 3]),
'dtype': numpy.float32,
'quantization': (0.0, 0),
'quantization_parameters': {'scales': array([], dtype=float32),
'zero_points': array([], dtype=int32),
'quantized_dimension': 0},
'sparsity_parameters': {}}]
を確認すると「index」というキーには
0
という値が入っていることがわかります。
今回は入力が1つしかないので0番目(1個目)の入力
という意味となります。
処理実行
以下の1行で実行できます。
interpreter.invoke()
出力の取り出し
output_data = interpreter.get_tensor(output_details[0]['index'])
先ほどの入力と場合と同じで
何番目の出力か?
という情報を
get_tensor()
に与えれば出力を得られます。
出力はnumpy配列で返ってきますので後はargmaxしたり自由に処理をすればOKです。
全体のコード
最後に全体のコードを載せておきます。
基本的にはコピペで動くはずですが、画像データだけは自分で用意してください。
(imagenetのクラスに存在する画像)
import tflite_runtime.interpreter as tflite
import numpy as np
import cv2
import time
interpreter = tflite.Interpreter("EfB3_imagenet.tflite")
interpreter.allocate_tensors()
input_details = interpreter.get_input_details()
output_details = interpreter.get_output_details()
img_path = "画像パス"
temp = cv2.imread(img_path)
temp = cv2.cvtColor(temp, cv2.COLOR_BGR2RGB)
temp = cv2.resize(temp, (300, 300))
input_data = np.expand_dims(temp, axis=0).astype("float32")
interpreter.set_tensor(input_details[0]['index'], input_data)
interpreter.invoke()
output_data = interpreter.get_tensor(output_details[0]['index'])
print(output_data.argmax())
ラズパイでのモデル読み込み速度と推論速度
ちなみにラズパイ4で速度を確認すると以下のようになります。
処理 | 処理時間[秒] |
---|---|
モデル読み込み | 0.0046 |
推論 | 1.47 |
モデルの読み込みはもちろんことですが、efficientnetB3というそこそこ重いモデルでも結構速度が出ています。
最後に
いかがだったでしょうか。
ラズパイでAIモデルを動かすときにはtensorflow liteがお勧めですので是非試してみてください。
コメント