MENU

問い合わせ


    【YOLOで作る画像認識ツール 第9回】速く・軽くする:ONNXに変換してCPUで動かし、変換前後を比べる

    前回の第8回では、自信の低い結果だけ人が確認するフローを作りました。ここからは運用に向けた話です。今回は、画像認識を速く・軽く動かす方法として、モデルをONNX(オニックス)という形式に変換します。

    画像認識は、GPU(画像処理用の高性能な部品)がないと遅いと思われがちです。しかし、小さなモデルなら、普通のパソコンのCPUでも実用的な速さで動きます。この回では、実際に速度を測って比べます。

    目次

    この回で作るもの

    • モデルをONNX形式に変換するスクリプト tools/export_onnx.py
    • .pt(元の形式)と .onnx(変換後)の速度を比べる tools/benchmark.py
    • 変換の前後で、結果が変わっていないかの確認

    ONNXとは:AIモデルの「共通の入れ物」

    学習したAIモデルは、通常、学習に使ったツール(今回はPyTorch)の形式で保存されます。この形式のままだと、動かす環境にも同じツールが必要です。

    ONNXは、AIモデルをツールに依存しない共通の形式で保存するための規格です。変換すると、次のようなメリットがあります。

    • 軽量な実行専用のソフト(ONNX Runtime)で動かせて、推論だけなら速く・省メモリになりやすい
    • PyTorchの巨大な部品を、動かす環境に入れなくて済む
    • Python以外(C#やJavaなど)のシステムからも使いやすい

    一方で、変換すると、結果が完全には同じにならないことがあります(後で確かめます)。

    実装

    変換する tools/export_onnx.py

    # tools/export_onnx.py(抜粋)
    def main(source: str | None = None) -> Path:
        model_path = Path(source) if source else ROOT / "models" / "yolo26n.pt"
        exported = Path(YOLO(str(model_path)).export(format="onnx", imgsz=640, simplify=True))
        print(f"変換しました: {exported}({exported.stat().st_size / 1e6:.1f} MB)")
        return exported
    python tools/export_onnx.py

    models/yolo26n.onnx が作られます(約9.9MB)。simplify=True は、モデルの中身を整理して、無駄を省く指定です。

    .onnx も同じ書き方で読めるようにする

    第0回から使っている Detector は、モデルのファイルを読むだけの作りでした。.onnx も同じコードで読めるよう、1行だけ変えます。

    # app/detector.py(変更部分)
    self._model = YOLO(str(model_path), task="detect")  # .pt でも .onnx でも同じ書き方で読める

    これで、環境変数 YOLO_MODEL_PATH にONNXのファイルを指定するだけで、モデルを切り替えられます。API・画面・評価のコードは、そのまま使えます。

    速度を測る tools/benchmark.py

    # tools/benchmark.py(抜粋)
    WARMUP, RUNS = 3, 20
    
    def measure(model: Path, image: str) -> tuple[float, int]:
        detector = Detector(model_path=model)
        for _ in range(WARMUP):  # 最初の数回は準備で遅いので測らない
            detector.detect(image)
        times = []
        for _ in range(RUNS):
            start = time.perf_counter()
            count = len(detector.detect(image))
            times.append((time.perf_counter() - start) * 1000)
        return statistics.median(times), count

    最初の数回は準備のため遅いので、測定から外します。20回測った中央値(真ん中の値)を使うのは、たまたま遅かった1回に結果が引きずられないようにするためです。

    動かして確かめる

    python tools/benchmark.py samples/fruits.jpg

    開発環境(CPU 4コア、GPUなし)での結果です。

    samples/fruits.jpg: 中央値(20回)
    yolo26n.pt       58.4 ms   検出 4 件   ファイル 5.5 MB
    yolo26n.onnx     27.2 ms   検出 5 件   ファイル 9.9 MB
    • 速度:.pt は約58ミリ秒、.onnx は約27ミリ秒。この環境では、約2倍速くなりました
    • ファイルの大きさ:ONNXのほうが大きくなりました(5.5MB → 9.9MB)。必ず小さくなるわけではありません
    • 検出件数:4件と5件で、同じではありません

    この速度は、この開発環境・この1枚の写真での結果です。写真の大きさ、CPUの種類、同時に動くほかの処理で変わります。自社の環境で測り直す必要があります。

    変換すると、結果が少し変わった

    ONNXの検出結果を見ます。

    5 件見つかりました(しきい値 0.25)
    - orange 信頼度 0.87 位置 (318, 246, 512, 477)
    - orange 信頼度 0.72 位置 (69, 42, 348, 473)
    - orange 信頼度 0.52 位置 (322, 120, 512, 306)
    - orange 信頼度 0.52 位置 (0, 276, 129, 477)
    - orange 信頼度 0.26 位置 (0, 95, 145, 242)

    .pt のときの結果(0.87・0.72・0.47・0.40)と比べると、次のような違いがあります。

    • 最も自信の高い2件は、ほぼ同じ(0.87と0.72)。位置も数ピクセルの差
    • 3件目以降は、信頼度が変わった(0.47→0.52、0.40→0.52)
    • しきい値ぎりぎりの1件(0.26。バナナの付近をorangeと答えたもの)が、新たに現れた

    第6回の評価コマンドで、ONNX版の精度も測ってみます。

    YOLO_MODEL_PATH=models/yolo26n.onnx python -m app.evaluate samples/ground_truth/fruits.json
    種類            TP  FP  FN  適合率  再現率
    orange           1   4   1    20%    50%
    全体              1   4   5    20%    17%

    .pt版は全体で適合率25%・再現率17%でした。ONNX版は適合率20%・再現率17%で、大きな差ではありませんが、同じではありません。誤検出が1件増えた分、適合率が少し下がりました。

    変換の前後で差が出る原因は、数値の計算方法の違い(浮動小数点の誤差)や、画像を縮小する処理の細かな差などが考えられます。1つには特定できませんが、変換したら、必ず同じテスト写真で評価し直す、というのが実務での鉄則です。

    テスト

    pytest -q      # 32件成功
    ruff check .   # 問題なし

    追加したテストは、.pt と .onnx が、最も自信のある結果について「同じ種類で、信頼度の差が0.05未満」であることを確認します(モデルの変換を済ませている場合のみ実行)。完全な一致は求めていません。

    つまずきやすい点

    • 変換に追加の部品が必要:onnx・onnxslim・onnxruntime を入れます(版は requirements.txt に固定)
    • 前処理の違い:変換したモデルに渡す画像の大きさ(今回は640ピクセル)は、固定になります。学習時と違う大きさを渡すと、結果が変わります
    • 速度が期待ほど出ない:CPUの性能、コア数、同時に動く処理の数で変わります。最初の数回が遅いことにも注意が必要です
    • GPUを使う場合は別の準備:GPUで動かすには、別のONNX Runtimeの版と、ドライバーの準備が必要です。この連載では扱いません

    発注者向けメモ:GPUが必要かは「枚数」と「応答時間」で決まる

    画像認識の見積もりでは、「GPUのサーバーが必要か」が、月額費用を大きく左右します。

    • 1日に数百枚、応答に数秒かかっても構わない → CPUだけで足りることが多い
    • 動画のようにリアルタイムで大量に処理したい → GPUが必要になりやすい

    小さなモデルなら、今回のようにCPUで数十ミリ秒で動きます。ただし、この数字は、モデルの大きさ・画像の大きさ・環境で大きく変わります。

    発注者がやること チェックリスト

    • ☐ 1日・1時間あたりの最大の処理枚数と、許容できる応答時間を決めた
    • ☐ 画像の大きさ(ピクセル数)と、撮影の頻度(連続か、たまにか)を整理した
    • ☐ 提案されたサーバー構成(CPU/GPU)の根拠を、開発会社に確認した
    • ☐ モデルを変換・軽量化したあとに、精度が変わっていないかの確認を、契約に入れた

    開発会社への質問例

    • 「想定する枚数と応答時間で、CPUだけで足りますか。GPUが必要な場合、その根拠を教えてください」
    • 「速度は、当社が使う写真の大きさで、実際に測った数字ですか」
    • 「モデルを変換・軽量化したとき、精度が変わっていないことを、どの写真で確認しますか」
    • 「サーバーの費用は、処理枚数が増えたときに、どう変わりますか」

    まとめと次回予告

    第9回では、次のことを行いました。

    • モデルをONNXに変換し、.pt と同じコードで動かせるようにした
    • 開発環境のCPUで、推論時間が約58ミリ秒から約27ミリ秒に短くなることを確認した
    • 変換すると結果が少し変わる(検出が4件から5件になる)ことを確認し、変換後の再評価が必要なことを整理した
    • GPUが必要かどうかは、処理枚数と応答時間で決まることを整理した

    次回の第10回では、ここまで作ったAPIをDocker(動かす環境ごと箱に詰める技術)で動かし、運用に近い形にします。

    この連載の記事一覧

    この記事は連載「YOLOで作る画像認識ツール」の1回です。連載のほかの回は次のとおりです(連載の一覧ページ)。

    システム制作・運用・保守のお問い合わせはこちら


      よかったらシェアしてね!
      • URLをコピーしました!
      • URLをコピーしました!

      この記事を書いた人

      株式会社THIRD HERO代表取締役 朝野貴朗
      Webシステム開発を中心に、toC向けサービスサイトの運営、ツール開発などを行ってまいりました。

      目次