FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

プリにONNX Runtimeを組み込んだ話[C++]

この記事を読んでほしい人

  • ONNX Runtimeの使用を考えている人
  • ONNX RuntimeのC++環境(Windows版)での組み込み手順を知りたい人
  • 機械学習の推論フレームワーク選定に迷っている人
  • フリューのソフトウェアエンジニアがどのようなことをしているか興味がある人

はじめに

フリューでプリントシール(プリ)機開発を行っている三上です。
現在は画像処理の担当をしています。

さて、プリントシール機では機械学習を使用しています。
推論した結果を使い様々な加工を行っているわけです。

従来はTensorFlowを使用して推論を行っていました。
今回ONNX Runtimeを導入することにしました。

ONNXとは何か?

ONNX (Open Neural Network Exchange)とは異なる機械学習フレームワークのモデルを相互運用するためのフォーマットです。

ONNX Runtimeとは何か?

上記のONNXに則って作られたモデルを使用して推論を行うためのフレームワークです。

なぜONNXを導入したのか

検証の結果以下のメリットがあると分かりました。

  • 1つの推論フレームワークで様々なモデルが使用可能になる
  • ONNXモデルに変換した際の推論結果がほぼ同一である
  • 処理速度が高速である(CPU処理の場合)

以下のようなデメリットもありました。

  • メモリ使用量が2倍程度増加する

上記を踏まえ、プリントシール機では推論をCPUで行っており、速度がメモリより優先されるため導入を決定しました。

ONNXの導入~モデルを使って推論するまで

1.ONNX Runtimeをダウンロードする
GitHubの公式ページのReleasesから必要なファイルをダウンロードします。
github.com 今回はONNX Runtimeのバージョン1.15.0のWindows 64ビット版の圧縮ファイルをダウンロードしました。
onnxruntime-win-x64-1.15.0.zip

2.ランタイムの導入
ファイルを解凍して中身の確認を行います。
今回必要なファイルは下記です。

include/onnxruntime_c_api.h
include/onnxruntime_cxx_api.h
include/onnxruntime_cxx_inline.h
lib/onnxruntime.lib
lib/onnxruntime.dll

これらのファイルをプロジェクトに追加して準備を行います。

3.ONNX RuntimeのC++ 組み込み
最初にONNXのセッションを作成します。

const wchar_t* onnx_model_path = L"Project/Machine_Learning.onnx";  
Ort::Env env;
Ort::SessionOptions session_options;
//指定したONNXモデルからセッションを作成
Ort::Session session = Ort::Session(env, onnx_model_path, session_options);

ONNXのセッションを作成するには、 ONNXモデルファイルのパス、ONNX Runtimeに定義されている各種オプションオブジェクトが必要です。

  • const wchar_t* onnx_model_path
    ONNXモデルのパスをUTF-16で記述します。

  • Ort::Env env
    ONNXのセッション実行時に必要な環境のオブジェクトです。
    今回は空で使用します。

  • Ort::SessionOptions session_options
    セッションを行う際のオプションのオブジェクトです。
    今回は空で使用します。

次にセッションに渡すテンソルを作成します。
今回は入出力がRGBで256 * 256のテンソルを想定しています。

//ONNXモデルによってサイズを変更してください。  
std::vector<float> input_data( 1 * 3 * 256 * 256 );
std::vector<float> output_data(1 * 3 * 256 * 256 );

//input_dataに画像を格納する

//ONNXモデルによって格納する数値を変更してください。
const std::vector<int64_t>input_shapes{ 1, 3, 256, 256 };
const std::vector<int64_t> output_shapes{ 1, 3, 256, 256 };

Ort::MemoryInfo memory_info(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU));

//入出力のテンソルを作成する
Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memory_info, input_data.data(), input_data.size(), input_shapes.data(), input_shapes.size());
Ort::Value output_tensor = Ort::Value::CreateTensor<float>(memory_info, output_data.data(), output_data.size(), output_shapes.data(), output_shapes.size());

ONNXモデルの入力テンソル作成には以下が必要です。

  • std::vector input_data
    機械が推論するためのテンソルに変換する前の入力のデータです。 用途に合わせて使用者が準備します。
    今回は24bit画像の各画素値を入れます。

  • std::vector output_data
    推論が完了した際の戻り値を格納するための変数です。 input_dataと異なり特定のデータを入れる必要はないですが、モデルが出力するテンソルの要素数に合わせてサイズが確保されている必要があります。 今回は出力テンソルの次元数(1×3×256×256)だけサイズを確保します。

  • std::vector<int64_t> input_shapes
    入力のテンソルの次元を表す変数です。入力テンソルの形式に合わせて値を設定する必要があります。
    今回は24bit画像(バッチ数, 3(RGB), 画像幅, 画像高さ)にします。

  • std::vector<int64_t> output_shapes
    出力のテンソルの次元を表す変数です。出力テンソルの形式に合わせて値を設定します。
    今回は24bit画像(バッチ数, 3(RGB), 画像幅, 画像高さ)にします。

  • Ort::MemoryInfo memory_info
    テンソルの作成に必要なメモリの情報を表すオブジェクトです。
    今回はCPUで扱うためにOrt::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU)と設定します。

推論を行う箇所を記述します。
第4,第7引数の1Uの記述は推論を行う入出力テンソルの数です。今回はサンプルを入出力共1つのテンソルとしたので1Uと設定しました。

const char* input_names = "input";
const char* output_names = "output";
Ort::RunOptions run_options;
//実際に推論する
session.Run(run_options, &input_names , &input_tensor, 1U, &output_names , &output_tensor, 1U);
  • Ort::RunOptions run_options
    セッション実行時に必要なオプションです。
    今回は空で使用します。

  • const char* input_names
    入力テンソルの名前です。モデルで設定されている入力名を設定します。今回はinputを設定します。

  • const char* output_shapes
    出力テンソルの名前。モデルで設定されている出力名を設定します。今回はoutputを設定します。

コード全体を載せます。

#pragma once
#include "onnxruntime/onnxruntime_cxx_api.h"
#pragma comment(lib, "onnxruntime.lib")

void Test()
{
    //onnxファイルのパスを記述する
    const wchar_t* onnx_model_path = L"Project/Machine_Learning.onnx"

    Ort::Env env;
    Ort::SessionOptions sessionOptions;

    //指定したONNXモデルからセッションを作成
    Ort::Session session = Ort::Session(env, onnx_model_path, sessionOptions);

    //ONNXモデルによってサイズを変更してください。
    std::vector<float> input_data(1 * 3 * 256 * 256);
    std::vector<float> output_data(1 * 3 * 256 * 256);
    
    //input_dataに値を格納する

    //入出力のテンソルのサイズ
    //ONNXモデルによって格納する数値を変更してください。
    const std::vector<int64_t> input_shapes{ 1, 3, 256, 256 };
    const std::vector<int64_t> output_shapes{ 1, 3, 256, 256 };

    Ort::MemoryInfo memoryInfo(Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU));

    //入出力のテンソルを作成する
    Ort::Value input_tensor = Ort::Value::CreateTensor<float>(memoryInfo, input_data.data(), input_data.size(), input_shapes.data(), input_shapes.size());
    Ort::Value output_tensor = Ort::Value::CreateTensor<float>(memoryInfo, output_data.data(), output_data.size(), output_shapes.data(), output_shapes.size());

    //入出力のテンソルの名前
    const char* input_names = "input";
    const char* output_names = "output";

    Ort::RunOptions run_options;

    //実際に推論する
    session.Run(run_options, &input_names , &input_tensor, 1U, &output_names , &output_tensor, 1U);

    //ここで推論の結果を使用して何か行う
}

性能の比較

プリントシール機で使用している一部の処理をTensorFlowからONNXに置き換えた結果を以下に示します。

  • 推論時間
  • メモリ使用量

の比較を行います。 今回はCPUを使用して比較を行います。 以下に内容をまとめました。

テスト環境
以下の環境でテストを行いました。

  • Windows 10 Pro
  • Intel(R) Core(TM) i5-4670 CPU @ 3.40GHz 3.39 GHz
  • 搭載メモリ 8.00 GB

推論時間

TensorFlow ONNX Runtime
推論時間[ms] 612.9 359.6

40%も削減できました。

メモリ使用量
ピーク時のメモリ使用量を計測しています。

TensorFlow ONNX Runtime
メモリ使用量[MB] 232 559

おわりに

組み込みを無事完了させ推論時間を大幅に短縮させることに成功しました!!(パチパチ👏)
そのかわりメモリは多くなってしまいました💦
う~ん😿
今回はCPU版を組み込みしてみましたが機会があればGPU版にも挑戦してみたいですね~! この記事を見てONNX Runtimeの組み込みを行っていただければ幸いです!

最後まで読んでいただきありがとうございました。