目的
前回まで
  このときに使ったTensorFlow Lite モデル(TF-Lite
  モデル)はmetadataが追加されたモデルを使用した(
TensorFlow Hubから入手したモデル)。
 
  今回はTF-Lite モデルにmetadataを自分で追加して、TensorFlow Lite Support Task
  Library(TF Lite Support Task Library)で推論することをやってみる。
Metadataとは?
既にあるTF-Lite モデルに補足の情報を追加することができる。
追加できる情報
以下の情報が追加できる。
  
    - モデル全体の説明(概要、著作権、作成者などの情報)
 
    - 入力の説明(画像フォーマット、正規化、入力幅の情報)
 
    - 出力の説明とラベル(説明やラベルとのマッピング)
 
  
  
 
Metaデータを追加する利点
  
    - 
      配布するモデルファイルの作成者、著作権を明示できる。
TF-Lite
      モデルは端末にダウンロードが前提。
公開・非公開に関わらず情報があるとありがたい。
     
    - 
      TF Lite Support Task
      Libraryを使うと入力、出力情報から必要な前・後処理を行ってくれる。
    
 
    
      - 前処理: リサイズ、正規化、モデルのInput(Float, Int)
 
      - 後処理: ラベルとのマッピング
 
    
    - 
      推論コードのラッパーを自動生成してくれる。
TensorFlowのドキュメント「メタデータを使用してモデルインターフェイスを生成する」
(まだよく理解していないので後で試す)
     
  
 
Medadataを追加するには?
TF Lite SupportのAPI(Python)を利用する。
Object detectionモデルにmetadataを組み込む
このモデルはFloatモデルで、InputもFloat32である。
  なお、Metadataを追加しない状態でTF Lite Support Task
  Libraryで推論するとエラーとなってしまう。
  
    これは、TF Lite Support Task
    LibraryがInputの正規化するための情報が無いため。
  
  
 
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=/home/pi/ssdlite_mobiledet_cpu.tflite \
    --num_thread=4 --score_threshold=0.5
Detection failed: Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images.
  モデルにmetadataを追加して推論ができることまで確認する。
参考のコード・サンプル
Metadetaを追加するライブラリはここで実装。
  
  
  MetadataはFlatBuffersのスキーマで定義。
  
  
 
よく使われるモデルのラッパーの定義。
(Image classification、Object detection、Image segmentation)
使い方はドキュメントはテストコードを参照。
作成したサンプル
Object detectionモデルにmetadataを書き込むサンプルを作成した。
  
  
  前回のtflite-supportのforkリポジトリに追加。
  
  
 
実装
Pythonでの実装の概要を説明。
必要なimport
from tensorflow_lite_support.metadata import metadata_schema_py_generated as _metadata_fb
from tensorflow_lite_support.metadata.python.metadata_writers import metadata_info
from tensorflow_lite_support.metadata.python.metadata_writers import object_detector
from tensorflow_lite_support.metadata.python.metadata_writers import writer_utils
  サンプルではMetadataPopulatorForObjectDetectorクラスの_create_metadataメソッドでmetadataを生成。
    # Creates model info.
    self.general_md = metadata_info.GeneralMd(
      name=self.model_info.name,
      version=self.model_info.version,
      description=("Identify which of a known set of objects might be present "
                   "and provide information about their positions within the "
                   "given image or a video stream."),
      author="Test",
      licenses=("Apache License. Version 2.0 "
                 "http://www.apache.org/licenses/LICENSE-2.0.")
    )
    # Load model to buffer.
    self.model_buffer = self._load_file(self.model_file_path)
    # Creates input info.
    self.input_md = metadata_info.InputImageTensorMd(
      name="normalized_input_image_tensor",
      description=("Input image to be classified. The expected image is {0} x {1}, with "
                   "three channels (red, blue, and green) per pixel. Each value in the "
                   "tensor is a single byte between {2} and {3}.".format(
                     self.model_info.image_width, self.model_info.image_height,
                     self.model_info.image_min, self.model_info.image_max)),
        norm_mean=self.model_info.mean,
        norm_std=self.model_info.std,
        color_space_type=_metadata_fb.ColorSpaceType.RGB,
        tensor_type=writer_utils.get_input_tensor_types(self.model_buffer)[0])
  
    - 
      ラベルファイルのパスを渡してあげると、ラベルも一緒に追加してくれる。
    
 
  
 
    # Creates output info.
    self.output_category_md = metadata_info.CategoryTensorMd(
        name="category",
        description="The categories of the detected boxes.",
        label_files=[
            metadata_info.LabelFileMd(file_path=file_path)
            for file_path in self.label_file_path
        ])
  戻り値のMetadataWriterの
populateメソッドを呼び出すことでMetadataを追加したモデルを得ることができる。あとはバイナリファイルに書き込むだけ。
 
    self.writer = object_detector.MetadataWriter.create_from_metadata_info(
        model_buffer=self.model_buffer, general_md=self.general_md,
        input_md=self.input_md, output_category_md=self.output_category_md)
    model_with_metadata = self.writer.populate()
    with open(self.export_model_path, "wb") as f:
      f.write(model_with_metadata)
ラッパーを使っているので簡単にmetadataを追加できる。
  def get_metadata_json(self):
    return self.writer.get_metadata_json()
  環境の準備
  
  ホストPCでMetadataを書き込む(Windows、LinuxどちらもOK)。
  今回はWindowsで実施。
  
  
    TF Lite Support Task
    LibraryのPythonパッケージは0.1.0が公開されている(2021.01.30時点)。
  
  
  TensorFlowはCPUのみで問題ない。
  
  $ pip install tflite-support-nightly
$ pip install tensorflow
  
  
 
Metadataを組み込むモデル、ラベルファイル
モデル
model.tfliteをssdlite_mobiledet_cpu.tfliteにリネームして使用。
$ wget http://download.tensorflow.org/models/object_detection/ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ tar xf ssdlite_mobiledet_cpu_320x320_coco_2020_05_19.tar.gz
$ mv ssdlite_mobiledet_cpu_320x320_coco_2020_05_19/model.tflite ssdlite_mobiledet_cpu.tflite
ラベル
coco datasetsのラベルを記載したファイル(labelmap.txt)を用意する。
Metadataを組み込む
リポジトリをclone後、サンプルのスクリプトを実行する。
引数は追加するモデル、ラベルファイル、出力ディレクトリ。
$ git clone https://github.com/NobuoTsukamoto/tflite-support.git
$ cd tensorflow_lite_support\examples\metadata
$ mkdir model_with_metadata
$ python metadata_writer_for_object_detection.py \
    --model_file=PATH_TO\ssdlite_mobiledet_cpu.tflite \
    --label_file=PATH_TO\labelmap.txt \
    --export_directory=.\model_with_metadata
model_with_metadataディレクトリに以下が生成される。
  
    - ssdlite_mobiledet_cpu.tflite 👈 metadataを追加したモデル
 
    - ssdlite_mobiledet_cpu.json 👈 追加したmetadataのJSONファイル
 
  
 
Metadataを確認
Netronと生成したJSONファイルを確認。
Netron
  Input(normalized_input_image_tensor)を選択すると追加したmetadataの情報が表示される。
  
    
      
         
       | 
    
    
      | 
        Metadataを追加したモデル
       | 
    
  
追加前と比べるとよくわかる。
  
    
      
         
       | 
    
    
      | 
        Metadataを追加する前のオリジナルのモデル
       | 
    
  
JSON
出力したmetadataのJSON。
Netronでは表示できない内容も確認できる。
{
  "name": "SSDLite with MobileDet-CPU",
  "description": "Identify which of a known set of objects might be present and provide information about their positions within the given image or a video stream.",
  "version": "v1",
  "subgraph_metadata": [
    {
      "input_tensor_metadata": [
        {
          "name": "normalized_input_image_tensor",
          "description": "Input image to be classified. The expected image is 320 x 320, with three channels (red, blue, and green) per pixel. Each value in the tensor is a single byte between 0 and 255.",
          "content": {
            "content_properties_type": "ImageProperties",
            "content_properties": {
              "color_space": "RGB"
            }
          },
          "process_units": [
            {
              "options_type": "NormalizationOptions",
              "options": {
                "mean": [
                  127.5
                ],
                "std": [
                  127.5
                ]
              }
            }
          ],
          "stats": {
            "max": [
              1.0
            ],
            "min": [
              -1.0
            ]
          }
        }
      ],
      "output_tensor_metadata": [
        {
          "name": "location",
          "description": "The locations of the detected boxes.",
          "content": {
            "content_properties_type": "BoundingBoxProperties",
            "content_properties": {
              "index": [
                1,
                0,
                3,
                2
              ],
              "type": "BOUNDARIES"
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          }
        },
        {
          "name": "category",
          "description": "The categories of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          },
          "associated_files": [
            {
              "name": "labelmap.txt",
              "description": "Labels for categories that the model can recognize.",
              "type": "TENSOR_VALUE_LABELS"
            }
          ]
        },
        {
          "name": "score",
          "description": "The scores of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            },
            "range": {
              "min": 2,
              "max": 2
            }
          },
          "stats": {
          }
        },
        {
          "name": "number of detections",
          "description": "The number of the detected boxes.",
          "content": {
            "content_properties_type": "FeatureProperties",
            "content_properties": {
            }
          },
          "stats": {
          }
        }
      ],
      "output_tensor_groups": [
        {
          "name": "detection_result",
          "tensor_names": [
            "location",
            "category",
            "score"
          ]
        }
      ]
    }
  ],
  "author": "Test",
  "license": "Apache License. Version 2.0 http://www.apache.org/licenses/LICENSE-2.0."
}
Metadataを組み込んだモデルを使ってみる
前回のブログで使ったサンプルプログラム(Object
  Detector)を使って推論してみる。
 
モデルをRaspberry Pi 4に転送して推論する。
$ ./bazel-bin/tensorflow_lite_support/examples/task/vision/pi/object_detector_capture \
    --model_path=/home/pi/ssdlite_mobiledet_cpu.tflite \
    --num_thread=4 --score_threshold=0.5
Detection failed: Input tensor has type kTfLiteFloat32: it requires specifying NormalizationOptions metadata to preprocess input images.
今度はエラーがなく、推論できる👍
感想
推論のコードが変更せずに利用できることはとても良いことだと思う。
  (ただ、個人で使う場合、metadataを追加するコストを考えるとあまりメリットがない気も、、、Input,
  Outputを統一してしまえばいいし、、、)