Ultralytics YOLO27: Join the waitlist.
Get Started
Building High-Performance YOLO Datasets·Train and Iterate·Lesson 8/10
Lessonbeginner

Know When the Dataset Is Ready

Gate the first training run on eight pass/fail checks, automate the three a script can verify, and know which fix each failure calls for.

Every project reaches the moment the team wants to train. Sometimes the dataset is ready; sometimes it still has a split leak or an under-labeled class that will waste a week of GPU time and produce a number nobody should trust. Eight checks, each scored yes, no, or documented exception, make that call objective instead of a matter of mood.

Outcome

Score your dataset on the eight-item readiness check, with a script verifying the three countable checks, and produce either a go decision or a ranked list of fixes.

Fast Track
If you already know your way around, here's the short version.
  1. Score all eight checks: yes, no, or documented exception.

  2. Let readiness.py score volume, leakage, split coverage, and background share.

  3. Fix every no in the data, not with hyperparameters.

  4. Ship with a known gap only when its expected impact is written down.

Hands-on

The eight checks#

#CheckPass criteriaIf it fails, go back to
1Clear objectiveGoal, task, classes, and an acceptance test are written down and agreedStart With the Business Objective
2Defined classesEach class has a definition and a named look-alike it excludesObjective and labeling guide
3Representative imagesEvery spec scenario meets its quota, mostly from production sourcesCollect High-Quality, Representative Data
4Sufficient volume≥ 1,500 images and ≥ 10,000 instances per class, or a documented exceptionSpec and collection
5Consistent labelsGuide written, calibration round done, agreement ≥ 90%Annotation Best Practices
6QC completeAudit script clean, suspects reviewed, QC notes writtenDataset Quality Control
7Clean splitsGrouped split, no group in two splits, every class in every splitSplit the Dataset Correctly
8Background images0–10% of images are labeled-empty framesSpec and collection

The checklist gates the same decisions the data collection and annotation guide covers one by one; here they become a single go/no-go.

Automate the countable checks#

Checks 4, 7, and 8 are arithmetic, so a script scores them the same way every time. This one reads the practice set and the split lists written by group_split.py in the previous lesson:

from collections import Counter
from pathlib import Path

root = Path("datasets/practice")  # images/, labels/, and the train/val/test.txt lists from group_split.py
splits = {s: [Path(p).stem for p in (root / f"{s}.txt").read_text().split()] for s in ("train", "val", "test")}

def classes(stem):
    label = root / "labels" / f"{stem}.txt"
    return [int(r.split()[0]) for r in label.read_text().splitlines() if r.strip()] if label.exists() else []

stems = [s for split in splits.values() for s in split]
instances = Counter(c for s in stems for c in classes(s))
images = Counter(c for s in stems for c in set(classes(s)))
background = sum(not classes(s) for s in stems) / len(stems)
groups = {split: {s.rsplit("_", 1)[0] for s in names} for split, names in splits.items()}
per_split = {split: {c for s in names for c in classes(s)} for split, names in splits.items()}
shared = (groups["train"] & groups["val"]) | (groups["train"] & groups["test"]) | (groups["val"] & groups["test"])
weakest = min(instances, key=lambda c: (images[c], instances[c]))

checks = {
    "4 volume": (images[weakest] >= 1500 and instances[weakest] >= 10_000,
                 f"weakest class {weakest}: {images[weakest]} images, {instances[weakest]} instances"),
    "7 no shared groups": (not shared, f"{len(shared)} groups appear in more than one split"),
    "7 every class in every split": (all(per_split[s] == set(instances) for s in splits),
                                     ", ".join(f"{s} misses {len(set(instances) - per_split[s])}" for s in splits)),
    "8 background 0-10%": (background <= 0.10, f"{background:.1%} background images"),
}
for name, (ok, detail) in checks.items():
    print(f"{'PASS' if ok else 'FAIL'}  {name:<29} {detail}")
FAIL  4 volume                      weakest class 30: 1 images, 1 instances
PASS  7 no shared groups            0 groups appear in more than one split
FAIL  7 every class in every split  train misses 5, val misses 36, test misses 41
PASS  8 background 0-10%            1.6% background images

The grouped split fixed the leak, but the practice set is nowhere near ready: its weakest class (30, skis) has a single labeled instance, and val can't score half the classes. That's the expected verdict for 128 demo images, and exactly the verdict this check exists to deliver before a week of training.

A clean script is not a clean checklist

The script covers what can be counted. Checks 1, 2, 3, 5, and 6 need a person: an agreed objective, written definitions, spec coverage, a measured agreement score, and QC notes. Score them by hand, with one line of evidence each.

Decide: train, ship with a gap, or fix#

When a check fails, the tempting next step is almost always something other than fixing the data:

Tempting shortcutWhy it doesn't work
"Try a bigger model."A larger model learns label noise just as faithfully.
"Tune the learning rate."No optimizer setting adds a missing class or scenario.
"Train for more epochs."More epochs on narrow data overfit faster.
"Add more augmentation."Augmentation can't create scenarios the data never contained.

Three almost-ready patterns#

PatternFailing checkWhat it looks likeFix
Volume without variance35,000 images, one camera, no edge casesCollect from more cameras, sites, and times before training
Variance without consistency5Annotators disagree on box extent and occlusionFinish the guide, calibrate, and relabel the affected sample
Quiet leakage7Random per-image split on video framesRe-split by clip, camera, or day; expect val mAP to drop, because that drop is reality

Ship with a known gap#

Often the right call is a focused first model before every check is green: a pilot, a prototype, or a v1 on a subset of cameras. A model in production shows what's actually wrong faster than polishing the dataset in the dark. When you ship with a gap, write down three things:

  • Which check failed.
  • The expected impact, for example "no night data; expect lower recall after dark".
  • The collection plan that closes it while v1 runs.
An exception without an impact is just a skipped check

"Volume: exception" tells the next person nothing. "Volume: pallet at 700 instances, expect lower pallet recall in crowded bays; 2,000 more labeled by June" is a managed risk. Every exception needs the expected impact on the acceptance test.

Rerun before every retrain#

Model quality over time: quality holds while new labeled data arrives, then decays after the expected point of decay, raising the question of whether to retrain now or wait

A dataset that was ready for v1 is not automatically ready for v3. New classes, new sources, and new annotators can each break a check that used to pass, and production data drifts while new labels pile up. Rerun readiness.py and rescore the manual checks before every retrain; it takes minutes, and skipping it can cost a week of training on a broken dataset.

Try It

Run readiness.py on your split dataset (or the practice set), then score the five manual checks by hand with one line of evidence each. Rank every failing check by how much of the acceptance test it puts at risk. Deliverable: a scored eight-row checklist with a go decision or a ranked fix list, saved next to the QC notes.

Done When
You've finished the lesson when all of these are true.
  • All eight checks have a score and one line of evidence; the three countable ones come from the script.

  • Every exception states its expected impact on the acceptance test and a plan to close it.

  • The decision (train, ship with a documented gap, or fix first) is written down with the checklist.

Show solution

A scored checklist for the practice set:

#CheckScoreEvidence
1Clear objectiveyesobjective.md and acceptance_test.py from lesson 1
2Defined classesnoCOCO's 80 names have no written definitions or excluded look-alikes
3Representative imagesnocoverage.py: cam2–cam4 below quota
4Sufficient volumenoreadiness.py: weakest class has 1 image
5Consistent labelsnoAgreement 47% in the calibration round
6QC completepartialAudit run; suspects in 000000000438.jpg still open
7Clean splitsnoNo shared groups, but val misses 36 of 71 classes
8Background imagesyes1.6%

Decision: fix first. Ranked by risk to the acceptance test: (1) cut the class list to the classes the objective needs, which shrinks checks 2, 4, and 7 at once; (2) collect more groups for those classes; (3) finish the guide rule on distant people and relabel. A demo set fails this check, and it should.

What's next

Once the checklist passes, the next lesson runs a baseline fine-tune and turns its results into the next dataset fix.