はじめに
こんにちは!YOLO11が思ったよりも凄い手軽に使えたので、物体認識をちょっと試してみようとおもって触ったメモになります。
本記事では、YOLO11を組み込み環境や本番環境で動かすための最適化手法について、2回シリーズで詳しく解説します。前編では、YOLO11の基本的な使い方から、量子化やONNX変換、TensorRTでの精度をどう確認するかまでを実践的に説明します。
「YOLO11を実際のプロダクトに組み込みたいけど、どこから始めればいいの?」という疑問にお答えする内容です。
今回の内容(前編)
- YOLO11の概要と従来モデルとの違い
- 最小限のデータセットでの動作確認
- 組み込み視点での最適化が必要な理由
- PyTorch → ONNX → TensorRT の変換手順
- 精度低下の検証方法
- 次回予告:SageMakerパイプラインでの本格運用
YOLO11とは?
YOLO11は、Ultralytics社が開発した最新の物体検出モデルで、従来のYOLOシリーズと比較して以下の改善が図られています:
- 精度の向上: mAP(mean Average Precision)が従来モデルより向上
- 推論速度の最適化: より効率的なアーキテクチャ設計
- 軽量化: エッジデバイスでの動作を考慮した設計
- 多様なタスク対応: 物体検出、分類、セグメンテーションに対応
特に組み込み環境やリアルタイム処理が求められる用途で心強いアップデートですが、自分が昔触っていたわかりやすいYOLOではなく、、、
C3k2,,,SPPF,,,C2PSA,,,等なんかすごい強化されていて詳細はしっかり勉強しないとなぁ、、という感じでした。本記事ではこれら深堀は扱いません。
なぜ組み込み環境では最適化が必要なのか
実際のプロダクトでAIモデルを動かす際、以下の制約に直面することが多いです:
1. ハードウェアリソースの制約
- メモリ容量: 組み込みデバイスでは数GB程度が上限
- 計算性能: GPUが使えない、またはエントリーレベルのGPUのみ
- 消費電力: バッテリー駆動デバイスでは電力効率が重要
2. レスポンス要件
- リアルタイム性: カメラ映像のリアルタイム解析
- スループット: 大量データの連続処理
3. コスト制約
- ハードウェアコスト: 高性能GPUは製品コストを押し上げる
- ランニングコスト: クラウド推論のAPIコール課金
これらの制約を解決するため、モデルの最適化が不可欠となります。
但し最適化は精度低下とのトレードオフになることが多いので、今回は特に調整をあまりしない状態で変換してみた際に、実際にどの程度変化するのかを確認します。
最小限のデータセットでYOLO11を動かしてみる
(下記にJupyterノートブックファイルを共有しています。本記事ではJupyterから抜粋しているため!などが先頭についてます。)
1. 環境構築
まずは、必要なライブラリをインストールして基本環境を整えます。
本記事はSageMakerのNotebookで実施しました。
# 必要なライブラリのインストール
!pip install ultralytics torch torchvision onnxruntime-gpu
# 環境設定
import os
import subprocess
from pathlib import Path
# プロジェクトルートの設定
try:
REPO_ROOT = subprocess.check_output(["git", "rev-parse", "--show-toplevel"]).decode().strip()
except Exception:
REPO_ROOT = "/workspace/yolo11-project"
# ディレクトリ構造の作成
os.makedirs(f"{REPO_ROOT}/.ultralytics", exist_ok=True)
os.makedirs(f"{REPO_ROOT}/runs", exist_ok=True)
os.makedirs(f"{REPO_ROOT}/datasets", exist_ok=True)
# Ultralytics設定
os.environ["YOLO_CONFIG_DIR"] = f"{REPO_ROOT}/.ultralytics"
2. 動作環境の確認
最適化を行う前に、現在の環境を確認します。
# GPU/CUDA環境チェック
import torch
import ultralytics
import onnxruntime as ort
print("=== 環境情報 ===")
print(f"torch: {torch.__version__}")
print(f"CUDA: {torch.version.cuda}")
print(f"GPU利用可能: {torch.cuda.is_available()}")
print(f"ultralytics: {ultralytics.__version__}")
print(f"ONNXRuntime providers: {ort.get_available_providers()}")
# nvidia-smi確認
try:
result = subprocess.check_output(["nvidia-smi"]).decode()
print("NVIDIA-SMI:")
print(result.splitlines()[2]) # GPUドライバー情報
except Exception as e:
print(f"nvidia-smi not available: {e}")
期待する出力例、環境によって変わります。:
=== 環境情報 ===
torch: 2.6.0
CUDA: 12.6
GPU利用可能: True
ultralytics: 8.3.203
ONNXRuntime providers: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'CPUExecutionProvider']
3. coco8でYOLO11のなんちゃって転移学習
YOLO11の動作を確認するため、軽量なサンプルデータセット(COCO8)を使用します。
from ultralytics import YOLO
# Smallest YOLO11 model
model = YOLO("yolo11s.pt")
# Train: coco8.yaml is downloaded and cached automatically
train_results = model.train(data="coco8.yaml", epochs=5, imgsz=640, device=0, project=str(home/"runs"))
print("Training finished. Best weights at:", train_results.save_dir)
4. Sampleの画像で推論してみる
from ultralytics import YOLO
from PIL import Image
import requests, io, glob
# Download a sample
url = "https://ultralytics.com/images/bus.jpg"
bus = Image.open(io.BytesIO(requests.get(url, timeout=30).content))
model = YOLO(best_pt)
pred = model.predict(source=bus, save=True, imgsz=640, device=0, verbose=False, project=str(home/"runs"))
# Show last predicted image
pred_dir_list = sorted(glob.glob(f"{home}/runs/predict*"))
assert pred_dir_list, "No predict output found."
last_dir = pred_dir_list[-1]
# find first .jpg in that folder
import os
jpgs = [p for p in glob.glob(f"{last_dir}/*.jpg")]
display(Image.open(jpgs[0]))
print("Predictions saved to:", last_dir)
PyTorch -> ONNX -> TensorRTの最適化の3段階アプローチ
では、実際に最小データセットで学習したYOLOのモデル使って、実際に最適化変換をした際にどの様に精度や速度が変化するかを見ていきます。
Step 1: PyTorchモデルでベースライン測定
from ultralytics import YOLO
import time
# YOLO11 nanoモデルの読み込み
model = YOLO('yolo11n.pt')
# ベースライン性能測定(COCO8データセット使用)
results = model.val(data='coco8.yaml', imgsz=640, device=0)
# 主要メトリクスの記録
pytorch_map50 = results.box.map50
pytorch_map = results.box.map
print(f"PyTorch mAP50: {pytorch_map50:.4f}")
print(f"PyTorch mAP50-95: {pytorch_map:.4f}")
# 推論時間も測定
import time
start_time = time.time()
_ = model('https://ultralytics.com/images/bus.jpg')
pytorch_inference_time = (time.time() - start_time) * 1000
print(f"PyTorch推論時間: {pytorch_inference_time:.1f}ms")
Step 2: PyTorch -> ONNX変換と精度検証
# ONNXエクスポート(FP16最適化有効)
model.export(format='onnx', imgsz=640, optimize=True, half=True)
# ONNX版での性能測定
onnx_model = YOLO('yolo11n.onnx')
onnx_results = onnx_model.val(data='coco8.yaml', imgsz=640, device=0)
# 精度比較
onnx_map50 = onnx_results.box.map50
onnx_map = onnx_results.box.map
print(f"ONNX mAP50: {onnx_map50:.4f} (差分: {onnx_map50-pytorch_map50:+.4f})")
print(f"ONNX mAP50-95: {onnx_map:.4f} (差分: {onnx_map-pytorch_map:+.4f})")
# 推論時間測定
start_time = time.time()
_ = onnx_model('https://ultralytics.com/images/bus.jpg')
onnx_inference_time = (time.time() - start_time) * 1000
print(f"ONNX推論時間: {onnx_inference_time:.1f}ms (高速化率: {pytorch_inference_time/onnx_inference_time:.1f}x)")
Step 3: ONNX -> TensorRT変換と精度検証
ここが最も高い速度向上が期待できる段階です。
# TensorRT変換(FP16精度)
try:
model.export(format='engine', imgsz=640, half=True, device=0)
# TensorRT版での性能測定
trt_model = YOLO('yolo11n.engine')
trt_results = trt_model.val(data='coco8.yaml', imgsz=640, device=0)
# 精度比較
trt_map50 = trt_results.box.map50
trt_map = trt_results.box.map
print(f"TensorRT mAP50: {trt_map50:.4f} (差分: {trt_map50-pytorch_map50:+.4f})")
print(f"TensorRT mAP50-95: {trt_map:.4f} (差分: {trt_map-pytorch_map:+.4f})")
# 推論時間測定
start_time = time.time()
_ = trt_model('https://ultralytics.com/images/bus.jpg')
trt_inference_time = (time.time() - start_time) * 1000
print(f"TensorRT推論時間: {trt_inference_time:.1f}ms (高速化率: {pytorch_inference_time/trt_inference_time:.1f}x)")
except Exception as e:
print(f"TensorRT変換に失敗しました: {e}")
精度低下の確認方法
最適化による精度への影響を適切に評価することが重要です。
1. 定量的評価
# 精度比較用の関数
def compare_models(original_results, optimized_results, model_name):
original_map50 = original_results.box.map50
optimized_map50 = optimized_results.box.map50
accuracy_loss = original_map50 - optimized_map50
accuracy_loss_percent = (accuracy_loss / original_map50) * 100
print(f"{model_name} 精度変化:")
print(f" mAP50: {original_map50:.4f} → {optimized_map50:.4f}")
print(f" 精度低下: {accuracy_loss:.4f} ({accuracy_loss_percent:.2f}%)")
# 許容範囲の判定(例:2%以下の低下なら許容)
if accuracy_loss_percent <= 2.0:
print(f" 判定: ✓ 許容範囲内")
else:
print(f" 判定: ⚠ 要注意({accuracy_loss_percent:.1f}%の低下)")
# 比較実行
compare_models(results, onnx_results, "ONNX")
# compare_models(results, trt_results, "TensorRT") # TensorRT成功時
2. 実際の画像での確認
# 同じ画像で各モデルの結果を比較
test_image = 'https://ultralytics.com/images/bus.jpg'
pytorch_results = model(test_image)
onnx_results = onnx_model(test_image)
# 検出されたオブジェクト数の比較
print(f"PyTorch検出数: {len(pytorch_results[0].boxes)}")
print(f"ONNX検出数: {len(onnx_results[0].boxes)}")
# 信頼度の分布確認
pytorch_confs = [float(box.conf) for box in pytorch_results[0].boxes]
onnx_confs = [float(box.conf) for box in onnx_results[0].boxes]
print(f"PyTorch平均信頼度: {sum(pytorch_confs)/len(pytorch_confs):.3f}")
print(f"ONNX平均信頼度: {sum(onnx_confs)/len(onnx_confs):.3f}")
実際の測定結果例
私の環境での測定結果:
| フォーマット | mAP50 | mAP50-95 | 推論速度 (ms) | ファイルサイズ | 精度低下 |
|---|---|---|---|---|---|
| PyTorch | 0.627 | 0.372 | 12.3 | 5.2MB | - |
| ONNX | 0.626 | 0.371 | 8.7 | 5.1MB | 0.16% |
| TensorRT | 0.625 | 0.370 | 4.2 | 3.8MB | 0.32% |
重要なポイント:
- TensorRTで約3倍の高速化を実現
- 精度低下は0.3%程度でまぁ許容範囲内
- ファイルサイズも約27%削減
組み込み環境での実装のポイント
1. メモリ使用量の監視
import psutil
import torch
def monitor_memory():
# システムメモリ
memory_info = psutil.virtual_memory()
print(f"システムメモリ使用量: {memory_info.percent}%")
# GPU メモリ(CUDA利用可能時)
if torch.cuda.is_available():
gpu_memory = torch.cuda.memory_allocated() / 1024**3
print(f"GPU メモリ使用量: {gpu_memory:.2f} GB")
# 各変換段階でメモリ使用量を確認
monitor_memory()
2. バッチサイズの調整
# リソース制約環境でのバッチサイズ最適化
def find_optimal_batch_size(model, test_input_size=(3, 640, 640)):
batch_sizes = [1, 2, 4, 8, 16]
optimal_batch = 1
for batch_size in batch_sizes:
try:
dummy_input = torch.randn(batch_size, *test_input_size)
_ = model(dummy_input)
optimal_batch = batch_size
print(f"バッチサイズ {batch_size}: OK")
except RuntimeError as e:
if "out of memory" in str(e):
print(f"バッチサイズ {batch_size}: メモリ不足")
break
return optimal_batch
3. TensorRT環境構築の簡易化
ローカル環境でのTensorRT構築が困難な場合のDocker活用法:
# NVIDIA NGC TensorRTコンテナの使用
docker run --gpus all -it --rm \
-v $(pwd):/workspace \
nvcr.io/nvidia/tensorrt:24.09-py3
# コンテナ内でのYOLO11最適化
yolo export model=yolo11n.pt format=engine half=True
yolo val model=yolo11n.engine data=coco8.yaml imgsz=640 device=0
本記事を作成するうえで発生したよくある問題と対処法のメモ
1. メモリ不足エラー
CUDA out of memory
対処法:
- バッチサイズの削減
-
torch.cuda.empty_cache()の実行 - より小さなモデル(nano → micro)の使用
2. 精度低下が大きい場合
対処法:
- FP32精度での変換を試行
- キャリブレーションデータセットの品質確認
- より多くのサンプルでの検証
まとめ
本記事では、YOLO11の基本的な使い方から最適化の必要性、そして実際の変換手順まで解説しました。
重要なポイント:
- 組み込み環境では計算リソースとメモリ制約への対応が必要
- ONNX変換で約30%、TensorRTで約3倍の高速化が可能
- 精度低下は通常1%以下で実用的
- 段階的な最適化により精度への影響を監視することが重要
次回予告:SageMakerパイプラインでの本格運用
次回は、今回紹介した最適化手法をAWS SageMakerのパイプラインで自動化し、本番環境での継続的な運用を実現する方法を詳しく解説します。
- SageMaker Processing Jobでの大規模最適化
- パイプラインでの自動品質チェック
- リアルタイム推論エンドポイントの構築
- コスト最適化のベストプラクティス
お楽しみに!
参考資料:
何か質問があれば、コメントでお気軽にどうぞ。
