Ultralytics YOLO27: ウェイトリストに登録する。
Get Started
高性能YOLOデータセットの構築·ラベリング·レッスン 5/10
レッスン初級

データセットの品質管理

スクリプトでラベル、クラスバランス、重複、分割の網羅性を監査し、モデルにラベル漏れの可能性を指摘させ、トレーニング前に見つかった問題を修正します。

ラベルの修正は、ハイパーパラメーターの調整より精度向上につながることがよくあります。チームはmAPを1~2ポイント上げる設定に何週間も費やした後、監査を半日行っただけで、あるクラスの一部にずっと誤ったラベルが付いていたと分かることがあります。品質管理では、トレーニング実行によってラベルの不具合が、信頼すべきでない数値に変わる前に、ラベリングガイドの見落としを検出します。

成果

自動ラベル監査とモデル支援による疑わしい箇所のレビューを実施し、見つかった問題を修正して、結果を簡潔なQCメモに記録します。

短期集中コース
すでに使い方をご存じの方は、こちらをご覧ください。
  1. インスタンス数(クラス別)、背景画像の割合、無効なクラス、重複、trainに含まれないクラスの数え上げをスクリプト化します。

  2. 事前学習済みモデルに、対応するラベルがない確信度の高い検出をフラグ付けさせ、目視で確認します。

  3. クラス不均衡は、まずデータ収集で、次にオーバーサンプリングで、最後に損失の重み付けで解消します。

  4. 確認した内容と修正した内容を記録します。

実践的

7つのチェック項目#

最初のトレーニング実行前と、大規模なラベリングバッチの後に、7項目すべてを確認してください。アノテーション付きデータの前処理ガイドでは、そのうちいくつかを詳しく説明しています。

#チェック項目検出できる問題検出方法
1ラベル漏れアノテーターが見落としたオブジェクト下記の疑わしい箇所のレビュー、目視サンプル
2誤ったクラスまたは無効なクラスクラスの入れ替わり、クラス一覧にないID監査スクリプト、初回実行後の混同行列
3ボックスが緩すぎる余白があるボックスや、端が切れているボックス目視サンプル、監査で検出する極小ボックス
4重複完全なコピーと、ほぼ同一のフレーム完全なコピーは監査スクリプトで検出します。ほぼ重複する画像は、分割ごとに色分けしたUltralytics Platformのクラスタリングビューで確認します。
5クラス不均衡仕様の目標を大きく下回るクラス監査スクリプト、Platformのチャートタブ
6分割の網羅性とリークある分割に存在しないクラス、2つの分割に含まれる同一クリップ監査スクリプト、グループ化した分割(次のレッスン)
7背景画像の割合空の画像が少なすぎる、または多すぎる監査スクリプト、目標値は0~10%

数え上げを自動化する#

チェック項目2、4、5、6、7は数え上げの問題なので、スクリプト化します。この監査では、train: と val: が画像フォルダーを指す、検出用データYAMLを読み込みます。各分割のクラス別インスタンス数と背景画像数を数え、無効なクラスID、極小ボックス、重複するラベル行、バイト単位で同一の画像にフラグを付け、valにはあるもののtrainには一度も現れないクラスを一覧表示します。

import hashlib
from collections import Counter
from pathlib import Path

from ultralytics.data.utils import IMG_FORMATS, check_det_dataset, img2label_paths

data = check_det_dataset("coco8.yaml")  # downloads COCO8 on first run; swap in your data YAML
names, seen = data["names"], {}

for split in ("train", "val"):
    images = sorted(p for p in Path(data[split]).rglob("*.*") if p.suffix[1:].lower() in IMG_FORMATS)
    instances, hashes, problems, background = Counter(), Counter(), [], 0
    for img, label in zip(images, img2label_paths([str(p) for p in images])):
        hashes[hashlib.md5(img.read_bytes()).hexdigest()] += 1
        rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
        background += not rows
        if len(rows) != len({tuple(r) for r in rows}):
            problems.append(f"{img.name}: duplicate label rows")
        for r in rows:
            cls, w, h = int(r[0]), float(r[3]), float(r[4])
            if cls not in names:
                problems.append(f"{img.name}: class {cls} is not in names")
            elif w * h < 1e-4:
                problems.append(f"{img.name}: tiny {names[cls]} box")
            instances[cls] += 1
    seen[split] = set(instances)
    duplicates = sum(n - 1 for n in hashes.values())
    print(f"{split}: {len(images)} images, {background} background, {duplicates} duplicate images")
    for cls, n in instances.most_common():
        print(f"  {names.get(cls, cls):>12}  {n}")
    for problem in problems:
        print("  !", problem)

print("in val but never in train:", sorted(names[c] for c in seen["val"] - seen["train"] if c in names))
train: 4 images, 0 background, 0 duplicate images
        orange  4
          bowl  3
       giraffe  2
...
val: 4 images, 0 background, 0 duplicate images
        person  10
         horse  2
...
in val but never in train: ['dog', 'elephant', 'horse', 'person', 'umbrella']

最後の行が検出結果です。person はvalに10個のインスタンスがあり、trainには1つもありません。つまり、モデルが学習していないクラスをvalで評価しています。COCO8は8枚の画像からなるスモークテスト用データセットなので、ここでは想定どおりです。自分のデータセットで同じ状況があれば、トレーニング前に分割を修正するか、データを追加してください。問題がある場合は、各分割の下に ! 000000000025.jpg: class 99 is not in names または ! 000000000009.jpg: duplicate label rows として出力されます。

モデルに疑わしいラベルを指摘させる#

ラベル漏れは最も深刻な誤りであり、画像をスクロールして探すのが最も難しい問題です。事前学習済みモデルを使えば、候補を指摘できます。近くにラベルがない確信度の高い検出は、ラベル漏れかモデルの誤りのどちらかであり、どちらも確認する価値があります。これはCOCO128で実行します。

from pathlib import Path

import torch

from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset, img2label_paths
from ultralytics.utils.metrics import box_iou
from ultralytics.utils.ops import xywh2xyxy

data = check_det_dataset("coco128.yaml")  # swap in your data YAML
model = YOLO("yolo26n.pt")  # a pretrained model, or your own first fine-tune
images = sorted(Path(data["train"]).glob("*.jpg"))

suspects = []
for result, label in zip(model(images, conf=0.6, stream=True, verbose=False), img2label_paths(map(str, images))):
    rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
    boxes = xywh2xyxy(torch.tensor([[float(v) for v in r[1:5]] for r in rows]).reshape(-1, 4))
    iou = box_iou(result.boxes.xyxyn, boxes)  # model boxes vs labeled boxes
    for j, (cls, conf) in enumerate(zip(result.boxes.cls.int().tolist(), result.boxes.conf.tolist())):
        if not rows or iou[j].max() < 0.3:  # confident detection with no label anywhere near it
            suspects.append((conf, Path(result.path).name, model.names[cls]))

print(f"{len(suspects)} confident detections with no matching label; top 5:")
for conf, name, cls in sorted(suspects, reverse=True)[:5]:
    print(f"  {conf:.2f}  {cls:<12} {name}")
4 confident detections with no matching label; top 5:
  0.77  donut        000000000438.jpg
  0.75  person       000000000531.jpg
  0.71  donut        000000000438.jpg
  0.66  bottle       000000000436.jpg

128枚の画像のうち、スクリプトによってレビュー対象は3枚に絞られます。画像を開くと、次の2種類の結果が見つかります。

  • 000000000438.jpg は、ドーナツが12個あるボックスを示しています。確信度の高い検出2つが、ラベル付きのドーナツと重なっていません。ラベル漏れまたは位置の誤りがある候補です。
  • 000000000531.jpg は、テニスコートに映った撮影者の影を「person」として検出しています。ラベルは正しく、モデルが誤っています。だからこそ、疑わしい箇所を自動で追加せず、人がすべて確認する必要があります。

conf の値を下げると、より多くの候補を拾えます。また、自分のクラスを学習済みのモデルができたら、そちらに置き換えてください。

データセットのチャートを読む#

医療用錠剤データセットのUltralytics Platform「Charts」タブ: Split Distributionにはtrain画像80枚、validation画像20枚が表示され、Top Classesには注釈1,755件のpillと注釈0件のtabletが表示されている

Ultralytics Platformのデータセットチャートタブでは、分割の分布、クラス数、ボックスの統計情報をプロットします(データセットのドキュメント)。このスクリーンショットから、すでにQC上の問題が分かります。クラス一覧には2つのクラスがありますが、tablet の注釈数は1,755件中0件です。ラベルがないクラスは学習できません。トレーニング前にラベルを付けるか、クラス一覧から削除してください。

適切な順序でクラス不均衡を解消する#

   forklift  ████████████████████████ 8000 instances
   person    ████████████████ 5400
   pallet    ██ 700                              ← red flag

このような偏りがあると、フォークリフトには高い確信度を示す一方で、パレットを見落とすモデルになります。解決策は次の3つです。順番に試してください。

  1. 少数クラスのデータをさらに収集し、ラベルを付けます。 常にこれが最善の方法です。すべてのクラスで、画像1,500枚以上、インスタンス10,000件以上を目指してください。
  2. trainのみでオーバーサンプリングします。 少数クラスを含む画像をトレーニング用ファイル一覧に複数回記載し、各エポックでその画像をより多く使います。valとtestには複製を含めないでください。
  3. 損失に重みを付けます。 cls_pw トレーニング引数(デフォルトは0.0、逆頻度による完全な重み付けでは最大1.0)を使うと、希少クラスの分類損失の重みが上がります。1と2を試した後にのみ使用してください。
スコアを上げるために難しい画像を削除しない

検証データからぼやけた画像、混雑した画像、判断が難しい画像を削除すれば、mAPは上がりますが、モデルの性能は変わらず、評価の誠実さが損なわれます。実際の配備環境で撮影された画像なら残し、代わりにラベルを修正します。本当の外れ値(誤り9)と完全な重複画像だけを削除します。

QCメモを記録する#

数行で十分です。数か月後に顧客や監査担当者から「何を確認しましたか?」と聞かれたときに答えられるようにします。

Dataset: forklift-v1   Audit date: 2026-05-09
Images: 4,820   Labels: 18,442   Background: 380 (7.9%)
Audit script: 3 invalid class IDs fixed, 41 duplicate label rows removed
Suspect review: 62 flagged, 19 missing labels added, 43 model errors ignored
Visual sample: 120 images, 4 mislabeled (3.3%), fixed
Split check: grouped by camera-day, every class in every split
Imbalance: pallet 2.5x below person; collecting more next round
Ready to train: yes
試してみる

qc_audit.py と suspect_labels.py を自分のデータYAML(またはCOCO8とCOCO128)に指定して実行します。報告された問題をすべて修正し、疑わしい画像をすべて開いてラベルの誤りかモデルの誤りかを判断してから、両方のスクリプトを再実行します。成果物は、修正前後の数値を記録した qc-notes.md です。

完了条件
以下のすべてを満たすと、レッスンは完了です。
  • 監査スクリプトで、無効なクラス、重複するラベル行、trainに含まれないクラスが報告されていません。

  • モデル支援によるレビューで見つかった疑わしい箇所をすべて開き、ラベル修正またはモデルの誤りとして対処しています。

  • QCメモに、確認内容、見つかった問題、修正内容が記録されています。

解答を表示

このレッスンの2つのデモ実行に関するQCメモです。

Dataset: COCO8 + COCO128 demo   Audit date: 2026-05-09
qc_audit.py (COCO8): 0 invalid classes, 0 duplicate rows, 0 duplicate images
  FINDING: dog, elephant, horse, person, umbrella appear in val only
  ACTION: re-split by group or collect these classes into train before training
suspect_labels.py (COCO128, conf 0.6): 4 suspects in 3 images
  000000000438.jpg: 2 donuts without matching labels -> relabel the donut box
  000000000531.jpg: "person" is the photographer's shadow -> model error, label kept
  000000000436.jpg: bottle -> open and decide
Ready to train: no, fix the val-only classes first

最後の行に注目してください。個々のラベルがすべて正しくても、valにしか存在しないクラスがあるデータセットはQCに合格しません。そのクラスのメトリクスが、モデルが学習する機会のなかった対象を評価するためです。

次のステップ

ラベルが整ったら、次のレッスンではデータセットを分割し、検証スコアが記憶ではなく本番環境を反映するようにします。