{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "_DDaAex5Q7u-" }, "source": [ "##### Copyright 2019 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "id": "W1dWWdNHQ9L0" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "6Y8E0lw5eYWm" }, "source": [ "# 訓練後の整数量子化" ] }, { "cell_type": "markdown", "metadata": { "id": "CIGrZZPTZVeO" }, "source": [ "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org で表示\n", " Google Colab で実行\n", " GitHub でソースを表示\n", "ノートブックをダウンロード
" ] }, { "cell_type": "markdown", "metadata": { "id": "BTC1rDAuei_1" }, "source": [ "## 概要\n", "\n", "整数量子化は、32 ビット浮動小数点数(重みや活性化出力など)を最も近い 8 ビット固定小数点数に変換する最適化ストラテジーです。これにより、より小さなモデルが生成され、推論速度が増加するため、[マイクロコントローラー](https://www.tensorflow.org/lite/microcontrollers)といった性能の低いデバイスにとって貴重となります。このデータ形式は、[Edge TPU](https://coral.ai/) などの整数のみのアクセラレータでも必要とされています。\n", "\n", "このチュートリアルでは、MNIST モデルを新規にトレーニングし、それを TensorFlow Lite ファイルに変換して、[トレーニング後量子化](https://www.tensorflow.org/lite/performance/post_training_quantization)を使用して量子化します。最後に、変換されたモデルの精度を確認し、元の浮動小数点モデルと比較します。\n", "\n", "モデルをどれくらい量子化するかについてのオプションには、実際いくつかあります。他のストラテジーでは、一部のデータが浮動小数点数のままとなることがありますが、このチュートリアルでは、すべての重みと活性化出力を 8 ビット整数データに変換する「全整数量子化」を実行します。\n", "\n", "さまざまな量子化ストラテジーについての詳細は、[TensorFlow Lite モデルの最適化](https://www.tensorflow.org/lite/performance/model_optimization)をご覧ください。\n" ] }, { "cell_type": "markdown", "metadata": { "id": "dDqqUIZjZjac" }, "source": [ "## セットアップ" ] }, { "cell_type": "markdown", "metadata": { "id": "I0nR5AMEWq0H" }, "source": [ "入力テンソルと出力テンソルの両方を量子化するには、TensorFlow 2.3 で追加された API を使用する必要があります。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "WsN6s5L1ieNl" }, "outputs": [], "source": [ "import logging\n", "logging.getLogger(\"tensorflow\").setLevel(logging.DEBUG)\n", "\n", "import tensorflow as tf\n", "import numpy as np\n", "print(\"TensorFlow version: \", tf.__version__)" ] }, { "cell_type": "markdown", "metadata": { "id": "2XsEP17Zelz9" }, "source": [ "## TensorFlow モデルを生成する" ] }, { "cell_type": "markdown", "metadata": { "id": "5NMaNZQCkW9X" }, "source": [ "[MNIST データセット](https://www.tensorflow.org/datasets/catalog/mnist)から、数字を分類する単純なモデルを構築します。\n", "\n", "モデルのトレーニングは 5 エポックしか行わないため、時間はかかりません。およそ 98% の精度に達します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "eMsw_6HujaqM" }, "outputs": [], "source": [ "# Load MNIST dataset\n", "mnist = tf.keras.datasets.mnist\n", "(train_images, train_labels), (test_images, test_labels) = mnist.load_data()\n", "\n", "# Normalize the input image so that each pixel value is between 0 to 1.\n", "train_images = train_images.astype(np.float32) / 255.0\n", "test_images = test_images.astype(np.float32) / 255.0\n", "\n", "# Define the model architecture\n", "model = tf.keras.Sequential([\n", " tf.keras.layers.InputLayer(input_shape=(28, 28)),\n", " tf.keras.layers.Reshape(target_shape=(28, 28, 1)),\n", " tf.keras.layers.Conv2D(filters=12, kernel_size=(3, 3), activation='relu'),\n", " tf.keras.layers.MaxPooling2D(pool_size=(2, 2)),\n", " tf.keras.layers.Flatten(),\n", " tf.keras.layers.Dense(10)\n", "])\n", "\n", "# Train the digit classification model\n", "model.compile(optimizer='adam',\n", " loss=tf.keras.losses.SparseCategoricalCrossentropy(\n", " from_logits=True),\n", " metrics=['accuracy'])\n", "model.fit(\n", " train_images,\n", " train_labels,\n", " epochs=5,\n", " validation_data=(test_images, test_labels)\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "KuTEoGFYd8aM" }, "source": [ "## TensorFlow Lite モデルに変換する" ] }, { "cell_type": "markdown", "metadata": { "id": "xl8_fzVAZwOh" }, "source": [ "次に、TensorFlow Lite [Converter](https://www.tensorflow.org/lite/models/convert) を使用して、トレーニング済みのモデルを TensorFlow Lite 形式に変換し、様々な程度で量子化を適用できます。\n", "\n", "量子化のバージョンの中には、一部のデータを浮動小数点数のフォーマットに残すものもあることに注意してください。そのため、以下のセクションでは、完全に int8 または unit8 データのモデルを得るまで、各オプションの量子化の量を増加しています。(各セクションのコードは、オプションごとにすべての量子化のステップを確認できるように、重複していることに注意してください。)\n", "\n", "まず、量子化なしで変換されたモデルです。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "_i8B2nDZmAgQ" }, "outputs": [], "source": [ "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n", "\n", "tflite_model = converter.convert()" ] }, { "cell_type": "markdown", "metadata": { "id": "7BONhYtYocQY" }, "source": [ "TensorFlow Lite モデルになってはいますが、すべてのパラメータデータには 32 ビット浮動小数点値が使用されています。" ] }, { "cell_type": "markdown", "metadata": { "id": "jPYZwgZTwJMT" }, "source": [ "### ダイナミックレンジ量子化による変換\n" ] }, { "cell_type": "markdown", "metadata": { "id": "Hjvq1vpJd4U_" }, "source": [ "では、デフォルトの `optimizations` フラグを有効にして、すべての固定パラメータ(重みなど)を量子化しましょう。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "HEZ6ET1AHAS3" }, "outputs": [], "source": [ "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n", "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n", "\n", "tflite_model_quant = converter.convert()" ] }, { "cell_type": "markdown", "metadata": { "id": "o5wuE-RcdX_3" }, "source": [ "重みが量子化されたためモデルは多少小さくなりましたが、他の変数データはまだ浮動小数点数フォーマットのままです。" ] }, { "cell_type": "markdown", "metadata": { "id": "UgKDdnHQEhpb" }, "source": [ "### 浮動小数点数フォールバック量子化による変換" ] }, { "cell_type": "markdown", "metadata": { "id": "rTe8avZJHMDO" }, "source": [ "変数データ(モデル入力/出力やレイヤー間の中間データ)を量子化するには、[`RepresentativeDataset`](https://www.tensorflow.org/api_docs/python/tf/lite/RepresentativeDataset) を指定する必要があります。これは、代表値を示すのに十分な大きさのある一連の入力データを提供するジェネレータ関数です。コンバータがすべての変数データのダイナミックレンジを推測できるようにします。(トレーニングや評価データセットとは異なり、このデータセットは一意である必要はありません。)複数の入力をサポートするために、それぞれの代表的なデータポイントはリストで、リストの要素はインデックスに従ってモデルに供給されます。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "FiwiWU3gHdkW" }, "outputs": [], "source": [ "def representative_data_gen():\n", " for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):\n", " # Model has only one input so each data point has one element.\n", " yield [input_value]\n", "\n", "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n", "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n", "converter.representative_dataset = representative_data_gen\n", "\n", "tflite_model_quant = converter.convert()" ] }, { "cell_type": "markdown", "metadata": { "id": "_GC3HFlptf7x" }, "source": [ "すべての重みと変数データが量子化されたため、元の TensorFlow Lite モデルにくらべてはるかに小さくなりました。\n", "\n", "ただし、従来的に浮動小数点数モデルの入力テンソルと出力テンソルを使用するアプリケーションとの互換性を維持するために、TensorFlow Lite Converter は、モデルの入力テンソルと出力テンソルを浮動小数点数に残しています。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "id1OEKFELQwp" }, "outputs": [], "source": [ "interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)\n", "input_type = interpreter.get_input_details()[0]['dtype']\n", "print('input: ', input_type)\n", "output_type = interpreter.get_output_details()[0]['dtype']\n", "print('output: ', output_type)" ] }, { "cell_type": "markdown", "metadata": { "id": "RACBJuj2XO8x" }, "source": [ "互換性を考慮すれば、大抵においては良いことではありますが、Edge TPU など、整数ベースの演算のみを実行するデバイスには対応していません。\n", "\n", "さらに、上記のプロセスでは、TensorFlow Lite が演算用の量子化の実装を含まない場合、その演算を浮動小数点数フォーマットに残す可能性があります。このストラテジーでは、より小さく効率的なモデルを得られるように変換を完了することが可能ですが、やはり、整数のみのハードウェアには対応しません。(この MNIST モデルのすべての op には量子化された実装が含まれています。)\n", "\n", "そこで、エンドツーエンドの整数限定モデルを確実に得られるよう、パラメータをいくつか追加する必要があります。" ] }, { "cell_type": "markdown", "metadata": { "id": "FQgTqbvPvxGJ" }, "source": [ "### 整数限定量子化による変換" ] }, { "cell_type": "markdown", "metadata": { "id": "mwR9keYAwArA" }, "source": [ "入力テンソルと出力テンソルを量子化し、量子化できない演算に遭遇したらコンバーターがエラーをスローするようにするには、追加パラメータをいくつか使用して、モデルを変換し直します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "kzjEjcDs3BHa" }, "outputs": [], "source": [ "def representative_data_gen():\n", " for input_value in tf.data.Dataset.from_tensor_slices(train_images).batch(1).take(100):\n", " yield [input_value]\n", "\n", "converter = tf.lite.TFLiteConverter.from_keras_model(model)\n", "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n", "converter.representative_dataset = representative_data_gen\n", "# Ensure that if any ops can't be quantized, the converter throws an error\n", "converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]\n", "# Set the input and output tensors to uint8 (APIs added in r2.3)\n", "converter.inference_input_type = tf.uint8\n", "converter.inference_output_type = tf.uint8\n", "\n", "tflite_model_quant = converter.convert()" ] }, { "cell_type": "markdown", "metadata": { "id": "wYd6NxD03yjB" }, "source": [ "内部の量子化は上記と同じままですが、入力テンソルと出力テンソルが整数フォーマットになっているのがわかります。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "PaNkOS-twz4k" }, "outputs": [], "source": [ "interpreter = tf.lite.Interpreter(model_content=tflite_model_quant)\n", "input_type = interpreter.get_input_details()[0]['dtype']\n", "print('input: ', input_type)\n", "output_type = interpreter.get_output_details()[0]['dtype']\n", "print('output: ', output_type)" ] }, { "cell_type": "markdown", "metadata": { "id": "TO17AP84wzBb" }, "source": [ "これで、モデルの入力テンソルと出力テンソルに整数データを強いようする整数量子化モデルを得られました。[Edge TPU](https://coral.ai) などの整数限定ハードウェアに対応しています。" ] }, { "cell_type": "markdown", "metadata": { "id": "sse224YJ4KMm" }, "source": [ "### モデルをファイルとして保存する" ] }, { "cell_type": "markdown", "metadata": { "id": "4_9nZ4nv4b9P" }, "source": [ "モデルを他のデバイスにデプロイするには、`.tflite` ファイルが必要となります。そこで、変換されたモデルをファイルに保存して、以下の推論を実行する際に読み込んでみましょう。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "BEY59dC14uRv" }, "outputs": [], "source": [ "import pathlib\n", "\n", "tflite_models_dir = pathlib.Path(\"/tmp/mnist_tflite_models/\")\n", "tflite_models_dir.mkdir(exist_ok=True, parents=True)\n", "\n", "# Save the unquantized/float model:\n", "tflite_model_file = tflite_models_dir/\"mnist_model.tflite\"\n", "tflite_model_file.write_bytes(tflite_model)\n", "# Save the quantized model:\n", "tflite_model_quant_file = tflite_models_dir/\"mnist_model_quant.tflite\"\n", "tflite_model_quant_file.write_bytes(tflite_model_quant)" ] }, { "cell_type": "markdown", "metadata": { "id": "9t9yaTeF9fyM" }, "source": [ "## TensorFlow Lite モデルを実行する" ] }, { "cell_type": "markdown", "metadata": { "id": "L8lQHMp_asCq" }, "source": [ "では、TensorFlow Lite [`Interpreter`](https://www.tensorflow.org/api_docs/python/tf/lite/Interpreter) を使用して推論を実行し、モデルの精度を比較しましょう。\n", "\n", "まず、特定のモデルと画像を使って推論を実行し、予測を返す関数が必要です。\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "X092SbeWfd1A" }, "outputs": [], "source": [ "# Helper function to run inference on a TFLite model\n", "def run_tflite_model(tflite_file, test_image_indices):\n", " global test_images\n", "\n", " # Initialize the interpreter\n", " interpreter = tf.lite.Interpreter(model_path=str(tflite_file))\n", " interpreter.allocate_tensors()\n", "\n", " input_details = interpreter.get_input_details()[0]\n", " output_details = interpreter.get_output_details()[0]\n", "\n", " predictions = np.zeros((len(test_image_indices),), dtype=int)\n", " for i, test_image_index in enumerate(test_image_indices):\n", " test_image = test_images[test_image_index]\n", " test_label = test_labels[test_image_index]\n", "\n", " # Check if the input type is quantized, then rescale input data to uint8\n", " if input_details['dtype'] == np.uint8:\n", " input_scale, input_zero_point = input_details[\"quantization\"]\n", " test_image = test_image / input_scale + input_zero_point\n", "\n", " test_image = np.expand_dims(test_image, axis=0).astype(input_details[\"dtype\"])\n", " interpreter.set_tensor(input_details[\"index\"], test_image)\n", " interpreter.invoke()\n", " output = interpreter.get_tensor(output_details[\"index\"])[0]\n", "\n", " predictions[i] = output.argmax()\n", "\n", " return predictions\n" ] }, { "cell_type": "markdown", "metadata": { "id": "2opUt_JTdyEu" }, "source": [ "### 1つの画像に対してモデルを検証する\n" ] }, { "cell_type": "markdown", "metadata": { "id": "QpPpFPaz7eEM" }, "source": [ "次に、浮動小数点数モデルと量子化モデルのパフォーマンスを比較します。\n", "\n", "- `tflite_model_file` は、浮動小数点数データを持つ元の TensorFlow Lite モデルです。\n", "- `tflite_model_quant_file` は、整数限定量子化を使用して変換した最後のモデルです(入力と出力に unit8 データを使用します)。\n", "\n", "もう一つ、予測を出力する関数を作成しましょう。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "zR2cHRUcUZ6e" }, "outputs": [], "source": [ "import matplotlib.pylab as plt\n", "\n", "# Change this to test a different image\n", "test_image_index = 1\n", "\n", "## Helper function to test the models on one image\n", "def test_model(tflite_file, test_image_index, model_type):\n", " global test_labels\n", "\n", " predictions = run_tflite_model(tflite_file, [test_image_index])\n", "\n", " plt.imshow(test_images[test_image_index])\n", " template = model_type + \" Model \\n True:{true}, Predicted:{predict}\"\n", " _ = plt.title(template.format(true= str(test_labels[test_image_index]), predict=str(predictions[0])))\n", " plt.grid(False)" ] }, { "cell_type": "markdown", "metadata": { "id": "A5OTJ_6Vcslt" }, "source": [ "では、浮動小数点数モデルをテストします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "iTK0x980coto" }, "outputs": [], "source": [ "test_model(tflite_model_file, test_image_index, model_type=\"Float\")" ] }, { "cell_type": "markdown", "metadata": { "id": "o3N6-UGl1dfE" }, "source": [ "今度は量子化されたモデル(uint8データを使用する)を検証します:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "rc1i9umMcp0t" }, "outputs": [], "source": [ "test_model(tflite_model_quant_file, test_image_index, model_type=\"Quantized\")" ] }, { "cell_type": "markdown", "metadata": { "id": "LwN7uIdCd8Gw" }, "source": [ "### モデルを評価する" ] }, { "cell_type": "markdown", "metadata": { "id": "RFKOD4DG8XmU" }, "source": [ "このチュートリアルの冒頭で読み込んだすテスト画像をすべて使用して、両方のモデルを実行しましょう。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "05aeAuWjvjPx" }, "outputs": [], "source": [ "# Helper function to evaluate a TFLite model on all images\n", "def evaluate_model(tflite_file, model_type):\n", " global test_images\n", " global test_labels\n", "\n", " test_image_indices = range(test_images.shape[0])\n", " predictions = run_tflite_model(tflite_file, test_image_indices)\n", "\n", " accuracy = (np.sum(test_labels== predictions) * 100) / len(test_images)\n", "\n", " print('%s model accuracy is %.4f%% (Number of test samples=%d)' % (\n", " model_type, accuracy, len(test_images)))" ] }, { "cell_type": "markdown", "metadata": { "id": "xnFilQpBuMh5" }, "source": [ "浮動小数点数モデルを評価します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "T5mWkSbMcU5z" }, "outputs": [], "source": [ "evaluate_model(tflite_model_file, model_type=\"Float\")" ] }, { "cell_type": "markdown", "metadata": { "id": "Km3cY9ry8ZlG" }, "source": [ "uint8データを使用した完全に量子化されたモデルで評価を繰り返します:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "-9cnwiPp6EGm" }, "outputs": [], "source": [ "evaluate_model(tflite_model_quant_file, model_type=\"Quantized\")" ] }, { "cell_type": "markdown", "metadata": { "id": "L7lfxkor8pgv" }, "source": [ "これで、浮動小数点数モデルと比較し、ほぼ同じ制度を持つ整数量子化モデルが得られました。\n", "\n", "他の量子化ストラテジーについての詳細は、[TensorFlow Lite モデルの量子化](https://www.tensorflow.org/lite/performance/model_optimization)をご覧ください。" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "post_training_integer_quant.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 0 }