2019年10月16日水曜日

TF2.0のKerasでPost-training quantization

修正


(2019.11.25)@PINTO03091 さんから指摘いただいたFull Integer quantのrepresentative_data_genのコード、説明の誤りを修正(ありがとうございます)。


目的


TensorFlow2.0がリリースされたので、
  • Keras modelから Post-training quantizationでTF-Lite modelへの変換を試してみる。
  • いろいろなTF-Lite quantization modelへの変換を試してみる。
  • それぞれのTF-Lite quantization modelの特性を確認してみる。

動機


以前、TF-2.0rc1でtf.kerasのMobileNet v2をfine-tuinginし、Post-training quantizationするノートブックを作った。
TF2.0がリリースされたので、このノートブックをもとにモデルを変換して、いろいろなTF-Lite model を比較してみようと思った。


バージョン情報

  • TensorFlow: 2.0.0
  • Edge TPU Compiler version 2.0.267685300
  • Edge TPU runtime and Python API library: 2.12.1 (September 2019)
  • Raspbian: 10.1
  • Jetson Nano: JetPack 4.2.2

環境

  • モデルの生成、学習はGoogle Colab上で行う。
  • 推論の実行は、Google Colab(CPU, GPU)、Raspberry Pi 3 B+、Jetson Nano。
  • Raspberry Pi 3 B+、Jetson Nanoにインストールする TF Lite 2.0 は @PINTO03091さんのTensorflowLite-binを利用。
    なお、Jetson NanoのPython3のバージョン3.6であるため、pyenvをつかって3.7を用意する。


Keras Modelの作成・学習


ソースについては、keras-post-training-quantization.ipynbをベースとする。
学習データは、tensorflow_datasets(tfsd)のtf_flower データセットを使用する。


試したKeras モデル


今回、試したモデルはImage Classificationの以下の4つ。チョイスについてなにか深い意味はない。
  • 独自のCNN 
  • MobileNet v2 1.0
  • Inception v3
  • DenseNet121
(ResNetが無いのは、Fine-tuningしてもval_accuracyがほぼランダムになってしまったため諦めた... なんでだろう?データが少ない?) 


独自のCNNモデル


Sequentialで単純にモデルを構築(Dropoutを入れてもFull Integer quant modelや Edge TPU modelを生成できる)。IMG_SIZEは112。
(サイズを224にすると、Edge TPU Modelに変換できなくなった... Edge TPU Compilerがモデルのサイズを意識しているのか?)
model = tf.keras.Sequential([
    tf.keras.layers.Conv2D(32, (3, 3), padding='same',
                           input_shape=(IMG_SIZE, IMG_SIZE, 3), activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2), padding='same'),
    tf.keras.layers.Dropout(0.25),

    tf.keras.layers.Conv2D(64, (3, 3), padding='same', activation='relu'),
    tf.keras.layers.MaxPooling2D(pool_size=(2, 2), padding='same'),
    tf.keras.layers.Dropout(0.25),

    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dropout(0.4),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

MobileNet v2


Pre-trained modelをFine-tuningする。IMG_SIZEは224。
base_model = MobileNetV2(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

Inception v3


こちらも同様。IMG_SIZEは229。
base_model = InceptionV3(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

DenseNet121


DenseNetはEdge TPUで動かしたことがなかったのでチョイス。IMG_SIZEは224。
base_model = DenseNet121(include_top=False,
                         weights='imagenet',
                         input_shape=(IMG_SIZE, IMG_SIZE, 3))

model = tf.keras.Sequential([
    base_model,
    tf.keras.layers.GlobalAveragePooling2D(),
    tf.keras.layers.Dense(info.features['label'].num_classes, activation='softmax')
])

モデルの保存


学習したモデルはh5形式で保存する。
# Save keras model
model.save(os.path.join(models_dir, 'xxx.h5'))



Keras ModelからTF-Lite Modelへの変換


TF-Lite model には、
  • 量子化されていないTF-Lite model 
  • 量子化された Quantization model
の2種類が存在する。
量子化することでモデルのサイズを TF-Lite model よりさらに削減することができる。

Quantization modelはPost-training quantizationもしくは、Quantization-aware trainingで作成することができる。TF2.0では、Post-training quantizationのみサポートされているため、今回は、Post-training quantizationを扱う。

さらに Post-training quantizationで量子化できるモデルは、以下の4種類が存在する。
  • Weight quantization model
  • Float16 quantization model
  • Integer quantization model
  • Full integer quantization model

また、Full integer quant model(Integer quant model) から Edge TPUで動作可能なEdge TPU modelを作成することができる。


Python API を使った TF-Lite modelへの変換


TensorFlow Lite converter Python APIを使って保存した Keras modelをTF-Lite model に変換する。


TF-Lite Model


保存したKeras modelをload_modelでロード、from_keras_modelでconverterを取得、convert で変換する。
特に変換する際のパラメータは必要ない。
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

tflite_model = converter.convert()

tflite_file = models_dir/'mobilenet_v2.tflite'
tflite_file.write_bytes(tflite_model)

Weight quantization Model


"hybrid" quantizationとも呼ばれる方法。
重みのみを量子化して、推論時は浮動小数点演算で行われる。
  • 量子化することで、モデルサイズが1/4程度になる。
  • 推論の精度の低下は少なく、元のモデルとほぼ同等。
  • 推論の処理時間も早くなる。
  • モバイル(Android、iOS)やサーバー(クラウド)での実行を想定?

モデルの変換時、optimizationsフラグにOPTIMIZE_FOR_SIZEを指定する。
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
tflite_weight_quant_model = converter.convert()

tflite_weight_model_quant_file = models_dir/'mobilenet_v2_weight_quant.tflite'
tflite_weight_model_quant_file.write_bytes(tflite_weight_quant_model)

Float16 quantization Model


重みをFloat16に量子化する方法。
  • モデルサイズは1/2程度になる。
  • GPU delegateが可能。
    Andorid、iOSなどGPUで推論の処理時間が早くなる。
  • CPUでも実行可能。
  • モバイルでの実行を想定。

モデルの変換時、supported_typesフラグにtf.float16を指定する。

※TF1.xでは、supported_typesにtf.lite.constants.FLOAT16を指定する。tf.lite.constants.FLOAT16はTF2.0では削除されたので、tf.float16を指定すればよいはず?
loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]

tflite_fp16_quant_model = converter.convert()

tflite_fp16_model_quant_file = models_dir/'mobilenet_v2_fp16_quant.tflite'
tflite_fp16_model_quant_file.write_bytes(tflite_fp16_quant_model)

Integer quantization Model


重みとアクティベーションの完全な整数量子化。
  • モデルサイズが1/4程度になる。
  • メモリ使用量の削減、推論時間の高速化。
  • 入力と出力は Float となる。
    (TF-Lite、Weight quant、Float16 quant modelと同じインターフェースで実行できる)
  • モバイルやエッジ(ARM CPU)での実行を想定。

モデルの変換時、optimizationsフラグにDEFAULT 指定する。
入力のキャリブレーション(入力データがどの範囲を取りうるのかを調整)が必要。RepresentativeDataset に入力データを返すジェネレーターを指定する。

以下では、representative_data_genが入力データを返すジェネレーター。
ここでは、学習時に1/255としているため、元の画素である0-255に戻していることに注意。学習時のデータの範囲(0.0-1.0)を入力する。UINT8(0-255)に戻したときに取りうる範囲を決める。
def representative_data_gen():
  for batch in test.take(255):
    yield [batch[0]]

loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_data_gen

tflite_full_integer_quant_model = converter.convert()

tflite_full_integer_model_quant_file = models_dir/'mobilenet_v2_integer_quant.tflite'
tflite_full_integer_model_quant_file.write_bytes(tflite_full_integer_quant_model)

Full integer quantization Model


Integer quant modelと同様だが、完全な整数量子化を行う。
モデルの入力・出力とも整数となる。このため、推論時は他のモデルと異なるインターフェースとなる。

モデルの変換時、基本的にはInteger quant modelと同様で、それ以外にinference_input_type、inference_output_typeにtf.uint8を指定することで、Full integer quant modelとなる。

なお、inference_input_type、inference_output_typeは、TF2.0では削除されているため、TF2.0のインターフェースでは、Full integer quant modelとならない(Integer quantization modelになってしまう。)このため、tf.compat.v1.lite.TFLiteConverter.from_keras_model_fileをつかってモデルをロードして変換する。

最初、インターフェースが削除されていたことに気がつかず、この件がわからず悩んでいた(バグなんて思っていた)。
def representative_data_gen():
  for batch in test.take(255):
    yield [batch[0]]

loaded_model = tf.keras.models.load_model(os.path.join(models_dir, 'mobilenet_v2.h5'))
converter = tf.lite.TFLiteConverter.from_keras_model(loaded_model)

converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.uint8
converter.inference_output_type = tf.uint8
converter.representative_dataset = representative_data_gen

tflite_full_integer_quant_model = converter.convert()

tflite_full_integer_model_quant_file = models_dir/'mobilenet_v2_full_integer_quant.tflite'
tflite_full_integer_model_quant_file.write_bytes(tflite_full_integer_quant_model)

Edge TPU モデル


Full Integer quant modelからEdge TPU modelに変換が可能。
September 2019 Updatesからは、Integer  quant modelからの変換も可能になった。
Keras modelのpost-training quant modelからの変換を強化したとあるので、これもその一つなのだろうと推測。

"We've released a minor update to the Edge TPU Compiler (version 2.0.2xx) with improved support for post-training quantization—especially those built with Keras"
(Coral の September 2019 Updates から抜粋)

Edge TPU Compiler version 2.0.267685300であれば、Integer  quant modelからの変換ができる。ただし、edgetpu.classification.engineのインターフェイスは利用できない。このため、今回はFull Integer quant modelから変換したモデルのみ使用している。


Integer quant modelを変換すると、2つのOpeがCPU側にオフロードされている。
これは、入力・出力のFloatをIntに変換するためのOpeと思われる。
  • QUANTIZE
  • DEQUANTIZE
edgetpu_compiler -s --out_dir /content/models /content/models/mobilenet_v2_integer_quant.tflite
Edge TPU Compiler version 2.0.267685300

Model compiled successfully in 307 ms.

Input model: /content/models/mobilenet_v2_integer_quant.tflite
Input size: 2.58MiB
Output model: /content/models/mobilenet_v2_integer_quant_edgetpu.tflite
Output size: 2.77MiB
On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 2.71MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 72
Operation log: /content/models/mobilenet_v2_integer_quant_edgetpu.log

Model successfully compiled but not all operations are supported by the Edge TPU. A percentage of the model will instead run on the CPU, which is slower. If possible, consider updating your model to use only operations supported by the Edge TPU. For details, visit g.co/coral/model-reqs.
Number of operations that will run on Edge TPU: 70
Number of operations that will run on CPU: 2

Operator                       Count      Status

ADD                            10         Mapped to Edge TPU
QUANTIZE                       1          Operation is otherwise supported, but not mapped due to some unspecified limitation
PAD                            5          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU
DEQUANTIZE                     1          Operation is working on an unsupported data type
MEAN                           1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU


なお、Full Integer quant modelを変換すると、CPU側にオフロードされるOpeはない。

edgetpu_compiler -s --out_dir /content/models /content/models/mobilenet_v2_full_integer_quant1.tflite
Edge TPU Compiler version 2.0.267685300

Model compiled successfully in 300 ms.

Input model: /content/models/mobilenet_v2_full_integer_quant1.tflite
Input size: 2.58MiB
Output model: /content/models/mobilenet_v2_full_integer_quant1_edgetpu.tflite
Output size: 2.77MiB
On-chip memory available for caching model parameters: 6.91MiB
On-chip memory used for caching model parameters: 2.71MiB
Off-chip memory used for streaming uncached model parameters: 0.00B
Number of Edge TPU subgraphs: 1
Total number of operations: 72
Operation log: /content/models/mobilenet_v2_full_integer_quant1_edgetpu.log

Operator                       Count      Status

MEAN                           1          Mapped to Edge TPU
SOFTMAX                        1          Mapped to Edge TPU
FULLY_CONNECTED                1          Mapped to Edge TPU
ADD                            10         Mapped to Edge TPU
QUANTIZE                       2          Mapped to Edge TPU
PAD                            5          Mapped to Edge TPU
CONV_2D                        35         Mapped to Edge TPU
DEPTHWISE_CONV_2D              17         Mapped to Edge TPU


推論


Pythonでの推論コードは以下。
# モデルをロードしてinterpreterを取得
interpreter = tf.lite.Interpreter(model_path=path_to_mode_file)
interpreter.allocate_tensors()

# set_tensorで入力画像を設定、invokeで推論を実行する
interpreter.set_tensor(interpreter.get_input_details()[0]["index"], image)
interpreter.invoke()

# 推論結果の取り出し。predictions に推論結果(各ラベルの予測値)となる
predictions = interpreter.get_tensor(interpreter.get_output_details()[0]["index"])

TF-Lite、Weight、Float16、Integer quant modelは、入力は学習時と同じサイズで正規化して入力する。
MobileNet v2の場合、入力は[1, 224, 224, 3]で0.0 - 1.0で正規化したFloat32で入力する。
im = Image.open(path_to_image_file)
im = im.resize((224, 224))
im = np.array(im, np.float32)
im = im / 255.0
im = image[np.newaxis, :, :] # 1, 224, 224, 3にするため

Full Integer quant modelは、正規化せずUINT8(0 - 255)で入力する。
im = Image.open(path_to_image_file)
im = im.resize((224, 224))
im = np.array(im, np.uint8)
im = image[np.newaxis, :, :] # 1, 224, 224, 3にするため


それぞれのモデルの比較


作成したそれぞれのモデルについて、
  • ファイルサイズ
  • 精度
  • 推論時間(Google Colab、Raspberry Pi3 B+、Jetson Nano、Edge TPU)
を比較してみる。

なお、DenseNetのEdge TPU modelは作成できなかった。
※September 2019 Updates によって、コンパイルできなくなった模様。古いコンパイラ使えとある...

モデルのファイルサイズを比較


ファイルサイズを比較すると、
TF-Lite model > Float16 quant model > Weight、Integer、Full integer quant model

TF-Lite modelから比較すると、
  • Weight、Integer、Full Integer quantization modelは、1/2〜1/4程度。
  • Float16 Integer quantization modelは、同じもしくは、1/2程度。
  • モデルによって小さくなるサイズが異なる(が傾向は同じ)。

Model
File size [MB]
Keras model
TF-Lite model
Post quantization model
Edge TPU model
Weight quant Float16 quantInteger quantFull integer quant
Original CNN197502550252525
MobileNet v2 1.016924333
Inception v3127842142222223
DenseNet121362771477

各モデルのサイズを比較


精度の比較


各モデルの精度を比較する。
実行は Google Colab のCPU上での実行。
データセットを分割し、学習、バリデーションに使ってない残りのテストデータで評価した。
ここでは、精度の良さは関係なく、Keras model に対してどの程度、低下があるかを確認する。Edge TPU model は Integer、Full Integer quant modelと同等であるので省略。
  • TF-Lite、Float16 quant model は Keras modelとほぼ同等で精度の低下なし
    Float16 quant model はGPU delegateの場合については未確認。
  • Weight quant model は本来精度の低下は少ないはずだが、MobileNet v2,、Inception v3で精度が大幅に低下した。
    独自のCNNや DenseNetは精度の低下がないことから、モデルの構造が影響していると推測。
  • Integer quant、Full Integer quant model は、若干の精度低下が確認できる。
    ただし、(これは対象にもよるだろうが)低下は0.01 〜 0.02程度。
  • 実際にモデルを導入する際は、必ずテストデータで精度を確認する必要がある。


Model
Top-1 Accuracy
Keras model
TF-Lite Model model
Post quantization model
Weight quant Float16 quantInteger quantFull integer quant
Original CNN0.56390.56390.56940.56110.55560.5556
MobileNet v2 1.00.75830.75830.45830.75560.73890.7417
Inception v30.85000.85000.82780.85000.84170.8444
DenseNet1210.85280.85280.85560.85000.87780.8778

各モデルの精度を比較


処理時間の比較


各モデルの推論の処理時間を比較する。
  • Keras modelの場合は、predictの前後で計測。
  • 各TF-Lite modelの場合は、set_tensor、invoke、get_tensorのまでの区間で計測。
  • Raspberry Pi、Jetson Nanoでは set_num_threadによるマルチスレッドの効果を確認する。

Google Colab 上での実行


Keras modelは Tesla K80、各TF-Lite modelはCPU(Intel(R) Xeon(R) CPU @ 2.20GHz)での実行となる。
  • TF-Lite modelとFloat16 quant modelは同じ処理時間となる。
  • Weight quant modelはTF-Lite modelより処理時間がかかる。
  • Integer、Full Integer quant modelはかなり処理時間がかかる。
    これは、x86_64に最適化されていないためである。
  • サーバー(クラウド)での実行は、TF-Liteもしくは Weight quant model が適している。


Model
Time per inference, in milliseconds (iterations = 20)
Keras model
TF-Lite model
Post quantization model
Weight quant Float16 quant modelInteger quant modelFull integer quant model
Original CNN5452838612011199
MobileNet v2 1.07840864212281229
Inception v312737912283762231311450
DenseNet1211312446772441150311293

推論の処理時間(Google Corab CPU, GPU)

Raspberry Pi 3 B+ での実行


  • Integer、Full Integer quant modelは、整数量子化による効果が確認できる。
  • Weight quant modelは、モデルによってTF-Lite modelとの差がある。
    独自のCNNモデルのみ処理時間が早くなっているが、それ以外のモデルでは遅くなっている。おそらく、モデルの構造に影響すると推測。
  • また、Weight quant model はマルチスレッドの効果がほとんど無いことも確認できる。
    これは、マルチスレッドをサポートしていないためである。
  • TF-Lite、Float16 quant、Integer、Full integer quant model でマルチスレッドの効果が確認できる。ただし、Jetson Nanoもだが、3スレッド以上は効果が小さくなる。このため、モバイルなどのメニーコアのCPUなどでむやみにスレッド数を指定しても効果はない可能性がある。

Model
set_num_threads
Time per inference, in milliseconds (iterations = 20)
TF-Lite model
Post quantization model
Weight quantFloat16 quantInteger quantFull integer quant
Original CNN
1216942145860
2181831823636
3172811693131
4169801692928
MobileNet v2 1.0
1366452361272274
2239418240154153
3251422240118116
42184152179998
Inception v3
1212924622169841845
2117524581168456458
38242457824327329
47042452702263266
DenseNet121
125842670253411651187
2148126531493715719
3116426531176571568
4113026531156509498


推論の処理時間(Raspberry Pi 3 B+ Original CNN)

推論の処理時間(Raspberry Pi 3 B+ MobileNet v2 1.0

推論の処理時間(Raspberry Pi 3 B+ Inception v3

推論の処理時間(Raspberry Pi 3 B+ DenseNet121

Jetson Nanoでの実行


  • こちらも、Raspberry Pi と同じ特性が見られる。
  • Raspberry Pi 3 B+と比較すると、おおよそ1/2〜1/3の処理時間である。
    これは、CPU、メモリ性能とOS(32 or 64 bit)が影響している。
    また、最適化も影響している可能性もある(TF2.0以降にARM 32bitの最適化がかなり入っていそうで、差が縮む可能性あり)
  • エッジ(ARM CPU)では、Integer quant、Full integer quant modelが適していそう(精度とのトレードオフ)。
    精度を重視するのであれば、TF-Lite modelが適している。

Model
set_num_threads
Time per inference, in milliseconds (iterations = 20)
TF-Lite model
Post quantization
Weight quantFloat16 quantInteger quantFull integer quant
Original CNN
16250783030
24543601616
34142551212
43941541010
MobileNet v2 1.0
11522331519898
289214896060
378213784747
483212824141
Inception v3
146434390461519061887
226784381271110341031
3200543642030754760
4182043601836608683
DenseNet121
1128615211301566574
27301525729360362
35641520570295293
45201523517262261

推論の処理時間(Jetson Nano Original CNN

推論の処理時間(Jetson Nano MobileNet v2 1.0

推論の処理時間(Jetson Nano Inception v3

推論の処理時間(Jetson Nano DenseNet121

Edge TPU(USB Accelerator)での実行


Raspberry Pi、Jetson Nanoでの実行を比較
  • USB2.0、3.0の効果が確認できる。

Time per inference, in milliseconds (iterations = 20)
ModelDeviceEdge TPU model
Original CNN
Raspberry Pi 3 B+706
Jetson Nano63
MobileNet v2 1.0
Raspberry Pi 3 B+13
Jetson Nano3
Inception v3
Raspberry Pi 3 B+488
Jetson Nano46




その他

  • Float16 quant modelはAndroidの PU delegateで確認したい。
    (持っているスマホでできるかな?)
  • DenseNetのEdge TPU modelの実行を確認したい。
    (次のアップデートで対応されるかな?)
  • Weight quant modelの精度を落ちる理由は?
    (Issueで報告してみる?)
  • Raspberry Pi 3 B+の64bitってどうなの?
  • Object detection modelを試してみたい。
  • tflite_convertはどうなった?

最後に

Post training quantizationについて、学習から推論まで一通りをまとめてみた。
精度と処理時間(とファイルサイズ)はトレードオフの関係にあるため、H/Wや目的にあわせてチョイスする必要がある。
TensorFlow Lite 2019 Roadmapにもあるとおり、まだ大幅な機能追加があるため、将来この内容は役に立たない可能性があることに注意(ただし、大きな流れは変更ないはず?)。

0 件のコメント:

コメントを投稿