Safie Engineers' Blog!

Safieのエンジニアが書くブログです

SOTAセグメンテーションモデル PP-MobileSeg をSNPEで動かす

はじめに

セーフィー株式会社の 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)](https://github.com/PaddlePaddle/PaddleSeg) / Apache License 2.0

引用元: 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を用いたプロダクトの開発を行っています!

© Safie Inc.