データセットの品質管理
スクリプトでラベル、クラスバランス、重複、分割の網羅性を監査し、モデルにラベル漏れの可能性を指摘させ、トレーニング前に見つかった問題を修正します。
ラベルの修正は、ハイパーパラメーターの調整より精度向上につながることがよくあります。チームはmAPを1~2ポイント上げる設定に何週間も費やした後、監査を半日行っただけで、あるクラスの一部にずっと誤ったラベルが付いていたと分かることがあります。品質管理では、トレーニング実行によってラベルの不具合が、信頼すべきでない数値に変わる前に、ラベリングガイドの見落としを検出します。
実践的
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.jpg128枚の画像のうち、スクリプトによってレビュー対象は3枚に絞られます。画像を開くと、次の2種類の結果が見つかります。
000000000438.jpgは、ドーナツが12個あるボックスを示しています。確信度の高い検出2つが、ラベル付きのドーナツと重なっていません。ラベル漏れまたは位置の誤りがある候補です。000000000531.jpgは、テニスコートに映った撮影者の影を「person」として検出しています。ラベルは正しく、モデルが誤っています。だからこそ、疑わしい箇所を自動で追加せず、人がすべて確認する必要があります。
conf の値を下げると、より多くの候補を拾えます。また、自分のクラスを学習済みのモデルができたら、そちらに置き換えてください。
データセットのチャートを読む#

Ultralytics Platformのデータセットチャートタブでは、分割の分布、クラス数、ボックスの統計情報をプロットします(データセットのドキュメント)。このスクリーンショットから、すでにQC上の問題が分かります。クラス一覧には2つのクラスがありますが、tablet の注釈数は1,755件中0件です。ラベルがないクラスは学習できません。トレーニング前にラベルを付けるか、クラス一覧から削除してください。
適切な順序でクラス不均衡を解消する#
forklift ████████████████████████ 8000 instances
person ████████████████ 5400
pallet ██ 700 ← red flagこのような偏りがあると、フォークリフトには高い確信度を示す一方で、パレットを見落とすモデルになります。解決策は次の3つです。順番に試してください。
- 少数クラスのデータをさらに収集し、ラベルを付けます。 常にこれが最善の方法です。すべてのクラスで、画像1,500枚以上、インスタンス10,000件以上を目指してください。
- trainのみでオーバーサンプリングします。 少数クラスを含む画像をトレーニング用ファイル一覧に複数回記載し、各エポックでその画像をより多く使います。valとtestには複製を含めないでください。
- 損失に重みを付けます。
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解答を表示
このレッスンの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に合格しません。そのクラスのメトリクスが、モデルが学習する機会のなかった対象を評価するためです。