前処理の最適化

はじめに

入力データがモデルの入力テンソルに完全に適合しない場合、データをモデルが期待する形式に変換する追加の操作/ステップが必要になります。この操作は “前処理” と呼ばれます。

次の例を考えてみましょう: ディープラーニング・モデルは、{1, 3, 224, 224} 形状、FP32 精度、RGB カラーチャネル順序の入力を想定しており、データの正規化 (平均を減算し、スケールファクターで除算) が必要です。ただし、640x480 BGR イメージしかありません (データは {480, 640, 3})。これは、次の操作が必要であることを意味します。

  • U8 バッファーを FP32 に変換します。

  • planar 形式への変換: {1, 480, 640, 3} から {1, 3, 480, 640} へ。

  • 画像のサイズを 640x480 から 224x224 に変更します。

  • モデルが RGB を期待しているため、BGR->RGB 変換を行います。

  • 各ピクセルで平均値を減算し、スケール係数で除算します。

../../../_images/preprocess_not_fit.png

実際の推論の前に、すべてのステップをアプリケーション・コードに手動で実装するのは比較的簡単ですが、前処理 API を使用することもできます。API を使用する利点は以下です。

  • 前処理 API の使い方は容易です。

  • 前処理ステップは実行グラフに統合され、常に CPU で実行されるわけではなく、選択したデバイス (CPU/GPU/など) で実行されます。これにより、選択したデバイスの使用率が向上し、常に良好になります。

前処理 API

直感的には、前処理 API は次の部分で構成されます。

  1. テンソル - 実際のユーザーのデータから、形状、レイアウト、精度、色の形式などのユーザーデータ形式を宣言します。

  2. 形状 - ユーザーデータに適用する前処理ステップのシーケンスを示します。

  3. モデル - モデルのデータ形式を指定します。通常、モデルの精度と形状はすでに判明しており、指定できるのはレイアウトなどの追加情報のみです。

モデルのグラフ変更は、モデルがドライブから読み取られた後、実際のデバイスにロードされる前に実行されます。

PrePostProcessor オブジェクト

ov::preprocess::PrePostProcessor クラスを使用すると、ディスクから読み取られたモデルの前処理ステップと後処理ステップを指定できます。

from openvino.preprocess import PrePostProcessor

ppp = PrePostProcessor(model)
 ov::Core core;
 std::shared_ptr<ov::Model> model = core.read_model(model_path);
 ov::preprocess::PrePostProcessor ppp(model);

ユーザーデータ形式を宣言

モデル/プリプロセッサーの特定の入力に対処するには、ov::preprocess::PrePostProcessor::input(input_name) メソッドを使用します。

from openvino.preprocess import ColorFormat
from openvino import Layout, Type
ppp.input(input_name).tensor() \
        .set_element_type(Type.u8) \
        .set_shape([1, 480, 640, 3]) \
        .set_layout(Layout('NHWC')) \
        .set_color_format(ColorFormat.BGR)
 ov::preprocess::InputInfo& input = ppp.input(input_name);
 input.tensor()
   .set_element_type(ov::element::u8)
   .set_shape({1, 480, 640, 3})
   .set_layout("NHWC")
   .set_color_format(ov::preprocess::ColorFormat::BGR);

以下は、指定されるすべての入力情報です。

  • 精度は U8 (符号なし 8 ビット整数) です。

  • データは、{1,480,640,3} 形状のテンソルを表します。

  • レイアウト は “NHWC” です。これは、height=480width=640channels=3 を意味します。

  • カラー形式は BGR です。

モデルレイアウトの宣言

モデル入力には、精度と形状に関する情報が含まれています。前処理 API はこれらの変更を目的としたものではありません。指定できるのは入力データのレイアウトだけです

# `model's input` already `knows` it's shape and data type, no need to specify them here
ppp.input(input_name).model().set_layout(Layout('NCHW'))
 // `model's input` already `knows` it's shape and data type, no need to specify them here
 input.model().set_layout("NCHW");

ここで、モデル入力の形状が {1,3,224,224} の場合、前処理により height=224width=224、および channels=3 を識別できます。height/ width 情報は resize に必要であり、channels は平均/スケールの正規化に必要です。

前処理ステップ

これで一連の前処理ステップを定義できます。

from openvino.preprocess import ResizeAlgorithm
ppp.input(input_name).preprocess() \
    .convert_element_type(Type.f32) \
    .convert_color(ColorFormat.RGB) \
    .resize(ResizeAlgorithm.RESIZE_LINEAR) \
    .mean([100.5, 101, 101.5]) \
    .scale([50., 51., 52.])
# .convert_layout(Layout('NCHW')) # Not needed, such conversion will be added implicitly
 input.preprocess()
   .convert_element_type(ov::element::f32)
   .convert_color(ov::preprocess::ColorFormat::RGB)
   .resize(ov::preprocess::ResizeAlgorithm::RESIZE_LINEAR)
   .mean({100.5, 101, 101.5})
   .scale({50., 51., 52.});
   // Not needed, such conversion will be added implicitly
   // .convert_layout("NCHW");

以下を実行します。

  1. U8 から FP32 精度への変換。

  2. 現在のカラー形式 BGR から RGB への変換。

  3. モデルを height/ width へサイズ変更。モデルが動的サイズ (例: {?, 3, ?, ?} を受け入れる場合、resize は画像サイズを変更する方法を認識しないことに注意してください。したがって、この場合、ターゲットの height/ width を指定する必要があります。詳細については、ov::preprocess::PreProcessSteps::resize() も参照してください。

  4. 各チャネルから平均を減算します。このステップでは、カラー形式はすでに RGB であるため、各 Red コンポーネントから 100.5 が減算され、各 Blue 成分から 101.5 が減算されます。

  5. 各ピクセルデータを適切なスケール値に分割します。この例では、Red の各コンポーネントは 50 で除算され、Green は 51 で除算され、Blue は 52 で除算されます。

  6. 最後の convert_layout ステップは、最後のレイアウト変換を指定する必要がないためコメントアウトされていることに注意してください。PrePostProcessor は、このような変換を自動的に実行します。

ステップをモデルに統合

前処理ステップが完了すると、最終モデルを構築できます。デバッグ目的で PrePostProcessor 構成を表示することができます。

print(f'Dump preprocessor: {ppp}')
model = ppp.build()
 std::cout << "Dump preprocessor: " << ppp << std::endl;
 model = ppp.build();

モデルは、{1, 480, 640, 3} の形状と BGR チャネル順序を持つ U8 入力を受け入れます。すべての変換ステップは実行グラフに統合されます。これで、アプリケーションでデータを操作することなく、モデルをデバイスにロードして画像をモデルに渡すことができるようになりました。