前回の第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回です。連載のほかの回は次のとおりです(連載の一覧ページ)。
- 【YOLOで作る画像認識ツール 第0回】全体像と環境づくり:YOLOで最初の1枚を認識する
- 【YOLOで作る画像認識ツール 第1回】信頼度のしきい値とJSON出力:認識結果を「使える形」にする
- 【YOLOで作る画像認識ツール 第2回】認識結果を画像に描いて確認する:枠とラベルで「間違い」を見つけやすくする
- 【YOLOで作る画像認識ツール 第3回】種類ごとに何個あるか数えて、CSVに集計する
- 【YOLOで作る画像認識ツール 第4回】FastAPIで画像認識を「API」にして、ほかのシステムから使えるようにする
- 【YOLOで作る画像認識ツール 第5回】ブラウザから写真をアップロードして、認識結果をその場で見る
- 【YOLOで作る画像認識ツール 第6回】精度を数字で測る:正解データと、適合率・再現率
- 【YOLOで作る画像認識ツール 第7回】学習済みモデルにない対象を覚えさせる:追加学習の手順と、結果の正しい読み方
- 【YOLOで作る画像認識ツール 第8回】自信の低い結果だけ人が確認する:AIと人の役割分担を設計する
- 【YOLOで作る画像認識ツール 第9回】速く・軽くする:ONNXに変換してCPUで動かし、変換前後を比べる(この記事)
- 【YOLOで作る画像認識ツール 第10回】Dockerで動かす:設定・ログ・ヘルスチェックを運用に近い形にする
- 【YOLOで作る画像認識ツール 第11回(最終回)】運用の注意点とライセンス(AGPL-3.0):精度の低下に気づく仕組みと、連載のまとめ

