ONNX機械学習モデル変換・推論

【概要】scikit-learnモデルをONNX形式に変換し推論性能を比較する。ONNXの相互運用性と効果を確認。RandomForestClassifierでの変換精度と速度変化を確認。


目次

概要

背景と課題

機械学習フレームワーク(TensorFlow、PyTorch、scikit-learn等)は独自のモデル形式を持つため、フレームワーク間でのモデル共有には互換性の課題が存在する。学習環境と推論環境が異なる場合、モデルの移行が困難になる。

ONNX(Open Neural Network Exchange)の役割

ONNXは異なる機械学習フレームワーク間でのモデル相互運用を可能にするオープンスタンダード形式である。モデルを計算グラフ(ノードとエッジで表現される処理の流れ図)として表現し、標準化されたオペレーター(数学的演算処理)と型システム(データ型の定義体系)を使用してモデル構造を定義する。

出典: Bai, J., Lu, F., Zhang, K., et al. (2019). ONNX: Open Neural Network Exchange. GitHub repository. https://github.com/onnx/onnx

技術的制約

全てのモデルタイプがサポートされるわけではなく、フレームワーク固有の機能は変換時に制限される場合がある。scikit-learnでは主要な分類・回帰アルゴリズムがサポートされている。

応用分野

クラウドで学習したモデルをエッジデバイスで実行、Webブラウザでの機械学習推論、異なる開発チーム間でのモデル共有、モバイルアプリケーションへのモデルデプロイメント、リアルタイム推論システムでの活用が可能である。

学習目標

scikit-learnで学習したモデルをONNX形式に変換し、推論速度と精度の変化を測定することで、モデル変換技術の特性を理解する。

事前準備

Python, Windsurfをインストールしていない場合の手順(インストール済みの場合は実行不要)。

  1. 管理者権限でコマンドプロンプトを起動(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。
  2. 以下のコマンドをそれぞれ実行する(winget コマンドは1つずつ実行)。

REM Python をシステム領域にインストール
winget install --scope machine --id Python.Python.3.12 -e --silent
REM Windsurf をシステム領域にインストール
winget install --scope machine --id Codeium.Windsurf -e --silent
REM Python のパス設定
set "PYTHON_PATH=C:\Program Files\Python312"
set "PYTHON_SCRIPTS_PATH=C:\Program Files\Python312\Scripts"
echo "%PATH%" | find /i "%PYTHON_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_PATH%" /M >nul
echo "%PATH%" | find /i "%PYTHON_SCRIPTS_PATH%" >nul
if errorlevel 1 setx PATH "%PATH%;%PYTHON_SCRIPTS_PATH%" /M >nul
REM Windsurf のパス設定
set "WINDSURF_PATH=C:\Program Files\Windsurf"
if exist "%WINDSURF_PATH%" (
    echo "%PATH%" | find /i "%WINDSURF_PATH%" >nul
    if errorlevel 1 setx PATH "%PATH%;%WINDSURF_PATH%" /M >nul
)

必要なPythonパッケージのインストール

コマンドプロンプトを管理者として実行(手順:Windowsキーまたはスタートメニュー > cmd と入力 > 右クリック > 「管理者として実行」)し、以下を実行する。


pip install scikit-learn sklearn-onnx onnxruntime numpy

プログラムコード


# ONNX変換による機械学習モデル推論性能比較プログラム
# 特徴技術名: ONNX (Open Neural Network Exchange)
# 出典: Bai, J., Lu, F., Zhang, K., et al. (2019). ONNX: Open Neural Network Exchange. arXiv preprint arXiv:1712.03213.
# 特徴機能: クロスプラットフォーム・クロスフレームワークでのモデル相互運用性。異なる機械学習フレームワーク間でモデルを共有・実行可能にする標準フォーマット
# 学習済みモデル: なし(プログラム内でRandomForestClassifierを学習)
# 方式設計:
#   - 関連利用技術: scikit-learn (機械学習ライブラリ、RandomForestClassifierを提供)、skl2onnx (scikit-learnモデルのONNX変換ツール)、ONNX Runtime (高性能推論エンジン)
#   - 入力と出力: 入力: Irisデータセット(プログラム内で自動読み込み)、出力: 推論性能比較結果(精度、推論時間、速度比)
#   - 処理手順: 1) Irisデータセットの読み込みと分割、2) RandomForestClassifierの学習、3) scikit-learnモデルでの推論と性能測定、4) ONNXフォーマットへの変換、5) ONNX Runtimeでの推論と性能測定、6) 両モデルの性能比較
#   - 前処理、後処理: 前処理: データ型をfloat32に変換(ONNX推論時)、後処理: 推論結果の精度計算と実行時間の比較
#   - 追加処理: なし
#   - 調整を必要とする設定値: N_ESTIMATORS (RandomForestの決定木数、デフォルト10)、RANDOM_SEED (再現性のための乱数シード、デフォルト42)
# 将来方策: N_ESTIMATORSの最適値を決定するため、複数の値で精度と推論時間のトレードオフを評価する機能の実装
# その他の重要事項: Windows環境での動作確認済み、ONNX変換により推論速度が変化する可能性がある
# 前準備: pip install scikit-learn sklearn-onnx onnxruntime numpy

import numpy as np
import time
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from skl2onnx import convert_sklearn
from skl2onnx.common.data_types import FloatTensorType
import onnxruntime as ort

# 定数定義
RANDOM_SEED = 42  # 再現性のための乱数シード
N_ESTIMATORS = 10  # RandomForestの決定木数
TEST_SIZE = 0.25  # テストデータの割合
N_RUNS = 10  # 推論時間測定の実行回数
TARGET_OPSET = 12  # ONNX opsetバージョン

# プログラム開始時の概要表示
print('=== ONNX変換による機械学習モデル推論性能比較プログラム ===')
print('scikit-learnのRandomForestClassifierをONNX形式に変換し、')
print('推論性能(精度・実行時間)を比較します。')
print()

# データセット読み込みと分割
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=TEST_SIZE, random_state=RANDOM_SEED
)

print('=== データセット情報 ===')
print(f'訓練データサイズ: {X_train.shape}')
print(f'テストデータサイズ: {X_test.shape}')
print(f'特徴量数: {X.shape[1]}')
print(f'クラス数: {len(np.unique(y))}')
print()

# メイン処理
print('=== scikit-learnモデル学習 ===')
sklearn_model = RandomForestClassifier(
    n_estimators=N_ESTIMATORS, random_state=RANDOM_SEED
)
sklearn_model.fit(X_train, y_train)

# scikit-learn推論と性能測定
sklearn_pred = sklearn_model.predict(X_test)
sklearn_accuracy = np.mean(sklearn_pred == y_test)

# 推論時間を複数回測定
sklearn_times = []
for _ in range(N_RUNS):
    start_time = time.time()
    sklearn_model.predict(X_test)
    sklearn_times.append(time.time() - start_time)
sklearn_time = np.mean(sklearn_times)

print(f'scikit-learn精度: {sklearn_accuracy:.4f}')
print(f'scikit-learn推論時間: {sklearn_time:.6f}秒 (平均/{N_RUNS}回)')
print()

print('=== ONNX変換 ===')
initial_types = [('input', FloatTensorType([None, X.shape[1]]))]
onnx_model = convert_sklearn(
    sklearn_model,
    initial_types=initial_types,
    target_opset=TARGET_OPSET
)

print('ONNX変換完了')
print(f'ONNXモデルサイズ: {len(onnx_model.SerializeToString())} bytes')
print(f'ONNX opsetバージョン: {TARGET_OPSET}')
print()

print('=== ONNX推論実行 ===')
# CPUプロバイダーを明示的に指定
onnx_session = ort.InferenceSession(
    onnx_model.SerializeToString(),
    providers=['CPUExecutionProvider']
)
input_name = onnx_session.get_inputs()[0].name

print(f'入力名: {input_name}')
print(f'入力形状: {onnx_session.get_inputs()[0].shape}')
print(f'出力形状: {onnx_session.get_outputs()[0].shape}')
print(f'実行プロバイダー: {onnx_session.get_providers()}')
print()

# ONNX推論と性能測定
onnx_pred = onnx_session.run(None, {input_name: X_test.astype(np.float32)})[0]
onnx_accuracy = np.mean(onnx_pred == y_test)

# 推論時間を複数回測定
onnx_times = []
for _ in range(N_RUNS):
    start_time = time.time()
    onnx_session.run(None, {input_name: X_test.astype(np.float32)})
    onnx_times.append(time.time() - start_time)
onnx_time = np.mean(onnx_times)

print(f'ONNX精度: {onnx_accuracy:.4f}')
print(f'ONNX推論時間: {onnx_time:.6f}秒 (平均/{N_RUNS}回)')
print()

# 結果出力
print('=== 結果比較 ===')
print(f"精度一致: {'はい' if np.array_equal(sklearn_pred, onnx_pred) else 'いいえ'}")

if sklearn_time > 0:
    speed_ratio = onnx_time / sklearn_time
    print(f'速度比較 (ONNX/scikit-learn): {speed_ratio:.2f}')
    if speed_ratio < 1.0:
        print(f'→ ONNXモデルが{1/speed_ratio:.2f}倍高速')
    elif speed_ratio > 1.0:
        print(f'→ scikit-learnモデルが{speed_ratio:.2f}倍高速')
    else:
        print('→ 両モデルの処理時間は同等')

print()
print('=== 結果の解釈 ===')
print('・精度一致: 両モデルが同一の予測結果を出力するかを確認')
print('・速度比較: ONNX変換による推論速度の変化を測定')
print('・ONNXの特徴: 異なる環境やフレームワーク間でのモデル互換性')
print(f'・測定条件: {N_RUNS}回実行の平均時間で比較')

使用方法

  1. 上記のプログラムコードを実行する

実験・探求のアイデア

機械学習モデル選択の実験

各モデルでONNX変換時の速度変化を比較し、モデルの種類による違いを観察する。

追加実験

  1. データセット変更: Iris以外のデータセット(Wine、Breast Cancer等)での性能比較
  2. モデルサイズ変更: n_estimatorsを10、50、100に変更し、モデル複雑度と変換効果の関係を調査
  3. 推論回数増加: 1000回の推論を実行し、累積時間での性能差を測定

研究のアイデア

  1. 変換精度の検証: 異なるデータセットでの精度保持率を調査し、ONNX変換の信頼性を確認
  2. モデル保存・読み込み: ONNXモデルをファイル保存し、別のプログラムで読み込む実験