Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
検出の先へ: セグメンテーション、Pose、OBB、分類·分類、組み合わせ、出荷·レッスン 8/10
レッスン中級

画像分類器を学習する

フォルダ形式のデータセットを用意し、MNIST160でyolo26n-cls.ptを学習して、確率とtop-1精度を確認し、信頼度の低い予測を人によるレビューに回します。

画像分類は、画像ごとに「これは何か」という1つの問いに答えます。必要なラベルはどのタスクよりも簡単なフォルダ名だけで、最も高速に動作します。そのため、検出器の後段に置く標準的な第2ステージであり、1フレームに1つの部品が映る検査ステーションに最適な最初のモデルです。データセットにYAMLはなく、フォルダ構成が仕様になります。また、分類器は、見たことのない画像に対しても必ず答えを返します。その答えをいつ信頼しないか判断することも、作業の半分です。

成果

MNIST160でyolo26n-cls.ptを学習し、top-1精度を報告し、result.probsからクラス確率を読み取り、確信度の低い画像をレビューに回す信頼度しきい値を選びます。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. 画像をtrain/<class>/とval/<class>/に入れます。フォルダ名がクラス名になり、アルファベット順に並びます。

  2. yolo classify train data=mnist160 model=yolo26n-cls.pt epochs=1 imgsz=64.

  3. top-1精度(metrics.top1)を基準にしてください。クラス数が5以下の場合、top-5は常に1.0です。

  4. result.probs.top1と.top1confを読み取り、信頼度しきい値を下回る予測を人に回します。

実践的

フォルダ形式のデータセット構成#

健康、さび病、うどんこ病のラベルが付いた3枚の葉を分類するUltralytics YOLO画像分類。それぞれに信頼度が表示されています

分類データセットはYAMLファイルではなくディレクトリです。data=にはルートフォルダを指定します。

parts/
├── train/
│   ├── good/      img_0001.jpg ...
│   ├── scratch/   img_0412.jpg ...
│   └── dent/      img_0733.jpg ...
└── val/
    ├── good/
    ├── scratch/
    └── dent/

インストールされたソースコードと分類データセットガイドに基づく、ローダーの動作は次のとおりです。

状況動作
クラスインデックスtrain/以下のフォルダ名をアルファベット順に並べる → dent=0、good=1、scratch=2
検証フォルダval/、次にvalidation/、次にvalid/を使用します。警告を出してtest/にフォールバックします。
train/フォルダはなく、クラスフォルダのみがある場合自動的にtrain/とval/に80/20で分割します

Ultralytics Platformでは、分類データセットには形状ではなく、画像ごとに画像レベルのラベルが1つ付与されます。

Ultralytics Platformの分類データセット。白い箱がコンベヤーに並び、クラスパネルから選択したclass2という画像レベルのラベルが1つ付いています

新しいフォルダーを追加するとクラス番号が振り直される

症状: クラスを追加した後、既存のインテグレーションがgoodの部分をdentとして報告します。 原因: bentという名前のフォルダーを追加すると、そのフォルダーが先頭に並び、その後のすべてのインデックスがずれます。 対処: インデックスは常にmodel.names(またはresult.names)を使って名前に対応付けます。インデックスをハードコードしないでください。

MNIST160で学習する#

MNIST160は手書き数字160枚のデータセットです。train/とtest/にそれぞれ80枚あり、各セットは10個の数字フォルダに分かれています。CPUのスモークテストにちょうどよい小ささです。val/フォルダがないため、ローダーが警告を出してtest/にフォールバックすることを確認してください。

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=1, imgsz=64, device="cpu")
metrics = model.val()
print(f"top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
WARNING ⚠️ Dataset 'split=val' not found, using 'split=test' instead.
val: .../datasets/mnist160/test... found 80 images in 10 classes ✅
                   all      0.162      0.463
top-1: 0.162  top-5: 0.463

1エポックでは、新しい10クラスのヘッドはほとんど改善しません。私たちの実行では、ノートPCのCPUで約10秒かけてepochs=10がtop-1 0.55、top-5 0.95に到達しました。処理は動作していますが、学習スケジュールが必要です。Classifyタスクガイドに、事前学習済みモデルのサイズが掲載されています。

Top-1は、最も確率の高いクラスが正解となる画像の割合です。Top-5は、確率上位5クラスのいずれかに正解クラスが含まれていれば、正解として数えます。クラスが大まかなものから非常に細分化されたものまである1,000クラスのImageNetでは、Top-5が参考になります。クラス数が5以下の場合は常に1.0となり、10クラスの場合でも判定は甘めです。top-1精度とクラスごとの混同行列を基準に判定してください。

哺乳類からハスキー、乗り物からトリマランまで、幅広いクラスから細分化されたクラスの順に並べられたImageNetのサンプル画像

コードで確率を読み取る#

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")  # ImageNet, 1,000 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

probs = result.probs
print(f"top-1: {result.names[probs.top1]} ({float(probs.top1conf):.2f})")
for i, conf in zip(probs.top5, probs.top5conf.tolist()):
    print(f"  {result.names[i]:20s} {conf:.3f}")
top-1: minibus (0.52)
  minibus              0.516
  police_van           0.369
  trolleybus           0.096
  minivan              0.006
  ambulance            0.003

probs.top1はインデックス、probs.top5はインデックスのリスト、probs.dataは確率ベクトル全体です。top-1の信頼度に注目してください。0.52はminibusとpolice_vanのどちらかを選ぶ際、コイントスで決めるのとほとんど変わりません。この値を基準に振り分けます。

確信度の低い予測を人に回す#

本番環境の分類器では、すべての画像で正解する必要はほとんどありません。分類器だけで判断すると決めた画像で正解する必要があります。このスクリプトは10エポックのモデルを学習し、異なる信頼度しきい値を適用した場合に、保留した80枚の数字画像でどうなるかを測定します。

from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=10, imgsz=64, device="cpu", plots=False)  # model now holds best.pt
print(model.names)  # folder names, sorted, became class indices

test = Path(model.trainer.data["test"])  # mnist160/test, the split the loader validated on
images = sorted(test.rglob("*.png"))
results = model(images, imgsz=64, verbose=False)
truth = [p.parent.name for p in images]

for threshold in (0.0, 0.3, 0.6):
    kept = [(r, t) for r, t in zip(results, truth) if r.probs.top1conf >= threshold]
    correct = sum(r.names[r.probs.top1] == t for r, t in kept)
    print(f"threshold {threshold:.1f}: auto-accept {len(kept):2d}/{len(images)}, accuracy on accepted {correct / max(len(kept), 1):.0%}")
{0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
threshold 0.0: auto-accept 80/80, accuracy on accepted 55%
threshold 0.3: auto-accept 41/80, accuracy on accepted 68%
threshold 0.6: auto-accept 10/80, accuracy on accepted 80%

各行はトレードオフを示しています。しきい値を上げると、採用する予測の精度は高まりますが、人による確認に回す画像も増えます。0.6の場合、このモデルだけで判断できる数字画像は80枚中10枚にすぎません。まだ何も自動化できる段階ではなく、必要なのは巧妙なしきい値ではなく、追加の学習です。学習ラウンドごとに同じ表を再計算してください。実現できる精度と、各しきい値で発生するレビュー作業量の両方がわかります。

判断基準

保留データで測定し、受け入れ画像の精度が要件を満たす範囲で最も低いしきい値を選びます。次に、そのしきい値によって発生するレビュー量をチームが処理できることを確認します。難しい場合は、モデルを改善します。

よくある落とし穴#

「該当なし」クラスがない

症状: 空のステーション、間違った部品、またはレンズを覆う手に対して、確信度の高いクラスラベルが付与されます。 原因: softmaxの確率の合計は常に1になるため、分類器は必ずいずれかのクラスを選びます。 対処: 該当する画像だけを入れたother/フォルダーを追加し、確信度の低いtop1conf予測はレビューに回します。

新しいフォルダーで事前学習済みヘッドを検証する

症状: 学習前の事前学習済みモデルをデータセットで評価すると、スコアがほぼ0になります。 原因: yolo26n-cls.ptは1,000個のImageNetインデックスを予測しますが、データセットのフォルダーではインデックス0~9を異なる意味で再利用しています。MNIST160では、フォルダー0がインデックス0ですが、事前学習済みヘッドはこれをtenchと判定します。この方法ではtop-1が0.000になることを確認しました。 対処: まずファインチューニングを行い、その後で検証します。

データ漏洩を起こすフォルダー

症状: 検証時のtop-1は1.0近くなのに、本番環境では大幅に低下します。 原因: train/とval/に、同じ部品を撮影したほぼ同一の写真が含まれています。 対処: データセットを正しく分割するの例のように、部品単位またはセッション単位で分割します。

分類器の事前学習時の入力サイズは640ではなくimgsz=224です。大きなフレーム内の小さな物体には、より大きなimgszを使うよりも、検出してから分類する方法(次のレッスン)のほうが適しています。

試してみる

mnist160でyolo26n-cls.ptを1エポック学習し、次にimgsz=64で10エポック学習して、両方の実行結果についてtop-1とtop-5を記録します。フォルダ名がクラスインデックスに変換されたことを確認するため、model.namesを出力してください。次にroute.pyを実行し、自動採用した画像で75%以上の精度を求める要件に対して、適用するしきい値を選びます。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • 1エポックと10エポックの実行結果について、top-1とtop-5を取得しています。

  • model.namesを出力し、アルファベット順のインデックス規則と、5クラスの問題でtop-5が役に立たない理由を説明できます。

  • しきい値、自動採用した画像での精度、レビューに回す80枚中の画像数を示しました。

解答を表示
from ultralytics import YOLO

for epochs in (1, 10):
    model = YOLO("yolo26n-cls.pt")
    model.train(data="mnist160", epochs=epochs, imgsz=64, device="cpu")
    metrics = model.val()
    print(f"{epochs:2d} epochs  top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
print(model.names)  # {0: '0', 1: '1', ..., 9: '9'}

実行結果は、1エポックでtop-1 0.162、top-5 0.463、10エポックでtop-1 0.550、top-5 0.950でした。5クラスの場合、正解は必ず上位5クラスに含まれるため、top-5は定義上1.0となり、何の情報も得られません。

自動採用した画像で75%の精度を得るには、route.pyによると0.6(採用10枚で80%)がしきい値となり、80枚中70枚をレビューに回すことになります。これが正直な答えです。しきい値は精度要件を満たしますが、モデルはまだ実用的ではありません。実運用のしきい値を選ぶ前に、さらに学習して表を再計算してください。

次のステップ

次はタスクを連結します。検出器に分類器をつなぐ方法と、ポーズモデルにトラッカーをつなぐ方法です。