はじめに
セーフィー株式会社の AI Vision グループでテックリードを務めます橋本貴博です。セーフィーの一部のAIネットワークカメラは、Snapdragon Neural Processing Engine(SNPE)をランタイムに使ってエッジ推論を行っています。この記事では、SOTA セグメンテーションモデル PP-MobileSeg を SNPEで動かす方法を解説したいと思います。
PP-MobileSegとは?
PP-MobileSeg は、2023年4月に発表された、モバイル向けセマンティックセグメンテーションのSOTAモデルです。ADE20Kデータセットで学習がなされており、既存モデルと比較して低レイテンシ、かつ、認識性能(mIoU)が高いことが分かります。論文は こちら (arXiv) から読めます。
引用元: PaddlePaddle/PaddleSeg (GitHub) / Apache License 2.0
モデル変換
大まかな流れ
PP-MobileSegのPyTorchモデルをONNXモデルに変換します。ONNXモデルで移植性向上のためのノード編集を行なった後、ONNXモデルからSNPEのネイティブフォーマット(DLC形式)に変換します。
PyTorch から ONNX への変換
mmsegmentation リポジトリ v1.1.2 で 公開(2023年9月)されている PP-MobileSeg-Tiny の PyTorchモデルを利用します。はじめに、Get started: Install and Run MMSeg を参考に環境構築を済ませておきます。 公式マニュアル に記載の手順にしたがって作業すれば、PyTorchモデルからONNXモデルに変換できます。
ONNX モデルの修正
ONNXモデルで使われている HardSigmoid オペレータ は、SNPEでサポートされないため、単純なオペレータの組み合わせに変換します。SNPEでサポートされるオペレータは 公式リファレンス から確認できます。 活性化関数に HardSigmoid が使われている。https://netron.app/ で可視化。
まず、今回のノード編集で利用する ONNX の opset バージョンを確認しておきます。opset versionによってオペレータの定義が異なるため、入力モデルの opset バージョンに揃えることにします。以下のスクリプトはモデルを読み込み opset バージョンを表示します。
import onnx model = onnx.load(input_path) for opset in model.opset_import: print(f"Model opset version: {opset.version}")
今回使用するモデルの opset バージョンは 11 ということが分かります。
model opset version: 11
次に、HardSigmoid オペレータを変換します。HardSigmoid の定義は、入力を x
、出力を y
として、
y = max(0, min(1, alpha * x + beta)) # alpha, beta はパラメータ
なので、ONNXの Mul、Add、Clip オペレータ を用いて分解できます。
x1 = Mul(alpha, x) x2 = Add(x1, beta) y = Clip(x2, 0, 1)
以下のスクリプトは、Mul、Add、Clip を用いて、ONNXモデルのすべての HardSigmoid を消去します。13か所で HardSigmoid が使われていることが分かります。
from onnx import TensorProto, defs, helper count = 0 for pos, node in enumerate(model.graph.node): if node.op_type == "HardSigmoid": count += 1 # Count HardSigmoid operator # Create Mul node and insert n0_name = node.name x0_name = node.input[0] alpha = node.attribute[0].f alpha_name = n0_name + "_alpha" alpha_tensor = helper.make_tensor(alpha_name, TensorProto.FLOAT, [1], [alpha]) y0_name = n0_name + "_mul" multiply_node = helper.make_node("Mul", [x0_name, alpha_name], [y0_name]) model.graph.node.insert(pos, multiply_node) # Create Add node and insert y1_name = n0_name + "_add" beta_name = n0_name + "_beta" beta_tensor = helper.make_tensor(beta_name, TensorProto.FLOAT, [1], [0.5]) add_node = helper.make_node("Add", [y0_name, beta_name], [y1_name]) model.graph.node.insert(pos + 1, add_node) # Create Clip node and insert y2_name = node.output[0] min_name = n0_name + "_min" max_name = n0_name + "_max" min_tensor = helper.make_tensor(min_name, TensorProto.FLOAT, [1], [0.0]) max_tensor = helper.make_tensor(max_name, TensorProto.FLOAT, [1], [1.0]) clip_node = helper.make_node("Clip", [y1_name, min_name, max_name], [y2_name]) model.graph.node.insert(pos + 2, clip_node) model.graph.node.remove(node) # Remove HardSigmoid node model.graph.initializer.extend([alpha_tensor, beta_tensor, min_tensor, max_tensor]) print(f"# hardsigmoid nodes: {count}") # Outputs 13
作成されたモデルを検証して保存します。
onnx.checker.check_model(model) # Validation onnx.save(model, output_path) # Save
変換後のONNXモデルを見ると、HardSigmoid オペレータが、Mul、Add、Clip オペレータに置き換わっていることが確認できます。
HardSigmoid が Mul/Add/Clip に変換されている。https://netron.app/ で可視化。
ONNX から DLC への変換
SNPE SDKに含まれるツール を使って、ONNXモデルからDLCモデルに変換が可能です。モデル実行時の入力テンソルのサイズを固定します。
snpe-onnx-to-dlc --input_network model.onnx --input_dim "input" 1,3,512,512
推論結果
当社のAIネットワークカメラ(Safie One)にモデルをインストールし、推論を行います。クラウド経由でモデルをアップロードできます。
SafieOne
推論が動作する様子をWebアプリ(Safie Viewer)から確認します。ここでは人物クラスを水色で表示しています。人物の輪郭が抽出されていることが分かります。
人物が水色でセグメンテーションされている
むすび
セマンティックセグメンテーションのSOTAモデル PP-MobileSeg をAI ネットワークカメラで動かしました。ONNXモデルからHardSigmoid オペレータを除去し、DLCモデルに変換する方法を詳しく解説しました。最後に、AIネットワークカメラ上の推論結果をWebアプリから確認しました。
セーフィーではエッジAIを用いたプロダクトの開発を行っています!