データセット仕様を定義する
収集前にシナリオ、割り当て数、ばらつき、エッジケース、背景画像を計画し、Ultralyticsのデータ量目標に照らして計画規模を決めます。
仕様のないチームは、明るい昼間に1台のカメラで撮影した写真を5,000枚集めた結果、運用開始初日の夕暮れにモデルが失敗する事態に陥りがちです。データセット仕様は画像の買い物リストです。モデルが対応すべきすべてのシナリオ、それぞれに必要な画像数、オーバーサンプリングする希少ケースをまとめます。作成にかかるのは1時間ですが、収集作業にどこまで労力をかけるかを左右します。
実践的
仕様の位置づけ#
仕様は目標とカメラをつなぐ橋渡しです。データ収集の指針となり、最初のモデルが目標に届かなかったときに立ち返るページでもあります。
ループが戻る先は学習設定ではなく仕様です。目標未達の原因は通常、仕様で想定していなかったシナリオにあります。コンピュータービジョンプロジェクトの手順ガイドでも、同じライフサイクルを説明しています。
シナリオを行ごとに記述します#
シナリオとは、モデルが対応すべき撮影条件の組み合わせです。割り当て数とともに行にまとめると、誰かがカメラを手に取る前に不足が明らかになります。
| シナリオ | カメラ | 場所 | 時間帯 | 条件 | 割り当て数 | 説明 |
|---|---|---|---|---|---|---|
| 昼間の荷積み場 | cam-1~cam-4 | A~Cバース | 08:00–17:00 | 任意 | 1,200 | 最繁忙時間帯 |
| 夜間の荷積み場 | cam-1~cam-4 | A~Cバース | 22:00–04:00 | 任意 | 400 | ナトリウム灯、人が少ない |
| 雨天時の荷積み | cam-2、cam-3 | Bバース | 任意 | 雨天のみ | 200 | まれだが重要 |
| 積み重ねた | 任意 | 任意 | 任意 | 高さ2段超のパレット | 150 | 背後にいる人が隠れる |
| 背景 | 任意 | 任意 | 任意 | 空の荷積み場 | 150 | オブジェクトなし、ラベルなし |
書き出せないシナリオは、モデルが学習時に目にしないシナリオです。そして、それが本番で失敗するシナリオになります。
データ量目標に照らして仕様の規模を決めます#
Ultralyticsのトレーニング結果を向上させるヒントでは、本番向けの経験則を紹介しています。
| 目標 | 推奨事項 |
|---|---|
| クラスごとの画像数 | ≥ 1,500 |
| クラスごとのラベル付きインスタンス数 | ≥ 10,000 |
| 背景画像(ラベルなし) | データセットの0~10%。COCOでは約1%です |
| 多様性 | 異なる時間帯、季節、天候、照明、角度、撮影元、カメラ |
本番モデルの基準はこの数値です。クラスごとにラベル付きオブジェクトが数百件あれば実験を始められます。また、最初のモデルを作ることで、どのシナリオでデータを追加する必要があるかを最も早く把握できます。データセットが基準にどれだけ届いていないかを確認するには、数を数えます。このスクリプトは、COCOから自動ダウンロードされる128枚の画像セット、COCO128で実行できます。
from collections import Counter
from pathlib import Path
from ultralytics.data.utils import IMG_FORMATS, check_det_dataset, img2label_paths
IMAGES_PER_CLASS, INSTANCES_PER_CLASS = 1500, 10_000 # Ultralytics production targets
data = check_det_dataset("coco128.yaml") # downloads COCO128 (~7 MB) on first run; swap in your data YAML
images = [p for p in Path(data["train"]).rglob("*.*") if p.suffix[1:].lower() in IMG_FORMATS]
image_count, instance_count, background = Counter(), Counter(), 0
for label in map(Path, img2label_paths([str(p) for p in images])):
classes = [int(r.split()[0]) for r in label.read_text().splitlines() if r.strip()] if label.exists() else []
instance_count.update(classes)
image_count.update(set(classes))
background += not classes
print(f"{len(images)} images, {background} background ({background / len(images):.0%}, target 0-10%)")
print(f"{'class':>12} {'images':>7} {'instances':>10} {'of target':>10}")
ranked = instance_count.most_common()
for cls, n in ranked[:3] + ranked[-3:]: # best and worst covered classes
coverage = min(image_count[cls] / IMAGES_PER_CLASS, n / INSTANCES_PER_CLASS)
print(f"{data['names'][cls]:>12} {image_count[cls]:>7} {n:>10} {coverage:>10.1%}")
never_labeled = [name for i, name in data["names"].items() if i not in instance_count]
print(f"{len(never_labeled)} classes in names with zero labels: {never_labeled[:4]} ...")128 images, 2 background (2%, target 0-10%)
class images instances of target
person 61 254 2.5%
car 12 46 0.5%
cup 10 36 0.4%
bear 1 1 0.0%
banana 1 1 0.0%
skis 1 1 0.0%
9 classes in names with zero labels: ['fire hydrant', 'parking meter', 'sheep', 'cow'] ...結果の読み方:
- カバー率は、画像比率とインスタンス比率の小さい方です。クラスには両方が必要だからです。最もカバー率の高いクラスである
personでも、本番基準の2.5%にとどまっています。 - インスタンスが1件だけのクラス(
bear、banana、skis)は学習にも評価にも使えません。独自のデータセットでは、データを収集するか、v1から除外してください。 - ラベルが0件のクラスはYAMLに記載されていますが、一度も描画されていません。それでもモデルの容量はそのクラスの予測に使われます。
namesから削除するか、学習前にデータを収集してください。
ばらつきの要因を網羅します#

1つのフレームに、近くと遠くにいる人、自転車、犬、車がすでに混在しています。実際の仕様では、この範囲を意図的に計画する必要があります。各要因について、仕様が網羅しているか、該当しない理由を記録してください。空欄の行は、本番で初めて気づくシナリオです。
| 次元 | 例 |
|---|---|
| 時間 | 昼、夜、夜明け、夕暮れ |
| 天候 | 晴天、曇天、雨、霧、雪 |
| 照明 | 明るい、薄暗い、まぶしさ、くっきりした影、複数の光源 |
| カメラ | モデル、レンズ、設置高、視野 |
| 地理 | 拠点、都市、国 |
| 人員とオペレーター | シフト、チーム、制服、保護具 |
| 製品バリエーション | SKU、パッケージの改訂版、色 |
| 物体のサイズ | 近距離で大きい、中距離、遠距離で小さい |
| 遮蔽 | 遮蔽なし、一部遮蔽、大きく遮蔽、ガラス越し |
| 機器の状態 | 汚れや曇りのあるレンズ、破損したパレット、摩耗した床の標示 |
量よりもばらつきが重要です。これらの行に分散した1,500枚の画像は、正午に1台のカメラで撮影した5,000枚より価値があります。
背景画像とエッジケースを計画する#
背景画像とは、空の荷積み場など、ラベル付けされた物体がないフレームです。物体が存在しないことをモデルに学習させ、誤検出を減らします。
Ultralytics YOLO形式では、背景画像はラベルファイルが空、またはラベルファイルが存在しない画像です。学習時のスキャン行(… 1200 images, 90 backgrounds, 0 corrupt)では、どちらもカウントされます。最初のエポックの前に、その数が目標の0~10%に収まっていることを確認します。
エッジケースには、大きな遮蔽、フレーム端で切れた物体、珍しい向き、積み重なった物体、フォークリフトのマストと鋼製の柱のような紛らわしい物体が含まれます。それぞれに独自の割当行を設けてください。
積み重ねたパレットの後ろに人がいるフレームが200枚に1枚なら、自然な頻度で収集したデータセットには1,500枚中およそ7枚しか含まれず、学習には少なすぎます。エッジケースは意図的にオーバーサンプリングします。割り当てる枚数は、実際の映像に占める割合より大幅に多くする必要があります。
紙の上ですべての状況を思いつくことはできません。仕様をおおむね整えたら、まずモデルをすばやく学習させ、その誤りから見落としていた状況を明らかにします。
解答を表示
割当数は画像数の目標だけでなく、インスタンス数の目標を基準に決めます。ある駐車場の車両カウンターに2つのクラスがあり、試験撮影では画像1枚あたり平均3台の車と0.5台のトラックが写るとします。
| クラス | 画像あたりのインスタンス数 | インスタンス10,000件に必要な画像数 | 最低ラインの1,500枚を満たすために必要な画像数 | 計画 |
|---|---|---|---|---|
car | 3.0 | ≈ 3,300 | 1,500 | 3,300枚 |
truck | 0.5 | ≈ 20,000 | 1,500 | トラックの出現頻度が10倍高い荷受け入口で、対象を絞って撮影 |
通常の映像で車は十分にカバーできます。トラックをランダムに撮影して目標を満たすには20,000枚が必要となるため、あらゆる映像を6倍集めるのではなく、専用のシナリオ行(荷受け入口、平日06:00~10:00)を仕様書に追加します。背景用として約5%の行(夜間の空の駐車場)を追加し、雪に覆われた状態とフレーム外にはみ出したトラックのエッジケース用の行も追加します。