Ultralytics YOLO27: Join the waitlist.
Get Started
Lessonbeginner

Define the Dataset Specification

Plan scenarios, quotas, variance, edge cases, and background images before collection, and size the plan against Ultralytics' volume targets.

Teams without a spec end up with 5,000 well-lit daytime photos from one camera and a model that fails at dusk on its first day in production. A dataset specification is a shopping list for images: every scenario the model must handle, how many images each needs, and which rare cases to over-sample. Writing it takes an hour, and it decides where collection effort goes.

Outcome

Write a scenario table with quotas that covers your variance dimensions and edge cases, and measure how far any labeled dataset is from the production volume targets.

Fast Track
If you already know your way around, here's the short version.
  1. Write scenarios as rows: camera, location, time, and condition, each with a quota.

  2. Size quotas against ≥1,500 images and ≥10,000 labeled instances per class for production.

  3. Give every known edge case its own row, and over-sample it.

  4. Reserve 0–10% of images as background with no objects.

Hands-on

Where the spec sits#

The spec is the bridge between the objective and the camera. It drives collection, and it is the page you return to when the first model misses its target:

The loop goes back to the spec, not to the training settings: a missed target usually means a scenario the spec didn't cover. The steps of a CV project guide describes the same lifecycle.

Write scenarios as rows#

A scenario is one combination of capture conditions the model must handle. Rows with quotas make gaps visible before anyone picks up a camera:

ScenarioCamerasLocationsTime of dayConditionQuotaNotes
dock-daycam-1 to cam-4bays A–C08:00–17:00any1,200busiest hours
dock-nightcam-1 to cam-4bays A–C22:00–04:00any400sodium lighting, fewer people
loading-raincam-2, cam-3bay Banyrain only200rare but critical
stackedanyanyanypallets > 2 high150hides people behind them
backgroundanyanyanyempty dock150no objects, no labels

A scenario you can't write down is one the model won't see in training, and it will be the one that fails in production.

Size the spec against the volume targets#

The Ultralytics tips for best training results give production rules of thumb:

TargetRecommendation
Images per class≥ 1,500
Labeled instances per class≥ 10,000
Background images (no labels)0–10% of the dataset; COCO has about 1%
VarietyDifferent times, seasons, weather, lighting, angles, sources, and cameras

These are the bar for a production model. A few hundred labeled objects per class is enough to start experimenting, and that first model is the fastest way to learn which scenarios need more data. To see how far a dataset is from the bar, count it. This script runs on COCO128, a 128-image slice of COCO that downloads automatically:

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'] ...

How to read it:

  • Coverage is the smaller of the image ratio and the instance ratio, because a class needs both. Even person, the best-covered class, sits at 2.5% of the production bar.
  • One-instance classes (bear, banana, skis) cannot be learned or measured. In your dataset, either collect them or drop them from v1.
  • Classes with zero labels are listed in the YAML but never drawn. The model still spends capacity predicting them. Remove them from names, or collect them before training.

Cover the variance dimensions#

Ultralytics YOLO detection on a waterfront promenade: people and cyclists near the camera, a dog, and cars at different distances, each with a labeled box and confidence score

One frame already mixes near and far people, bicycles, a dog, and cars. A real spec has to plan for that range on purpose. For each dimension, write down whether the spec covers it or why it doesn't apply; empty rows are scenarios you'll discover in production instead:

DimensionExamples
TimeDay, night, dawn, dusk
WeatherSun, overcast, rain, fog, snow
LightingBright, dim, glare, hard shadows, mixed sources
CameraModel, lens, mounting height, field of view
GeographySites, cities, countries
People and operatorsShifts, teams, uniforms, protective gear
Product variantsSKUs, packaging revisions, colors
Object sizeNear and large, mid-range, far and small
OcclusionClear, partial, heavy, behind glass
Equipment conditionDirty or fogged lenses, damaged pallets, worn floor markings

Variance matters more than volume: 1,500 images spread across these rows are worth more than 5,000 from one camera at noon.

Plan background images and edge cases#

Background images are frames with no labeled objects, such as an empty dock. They teach the model that nothing is there and reduce false positives.

How a background image is stored

In the Ultralytics YOLO format, a background image is simply an image with an empty label file, or none at all. Training counts both in its scan line (… 1200 images, 90 backgrounds, 0 corrupt). Check that number against your 0–10% target before the first epoch.

Edge cases are heavy occlusion, objects cut off at the frame edge, unusual orientations, stacked objects, and look-alikes such as a forklift mast next to a steel column. Give each one its own quota row.

Natural frequency starves edge cases

If 1 frame in 200 shows a person behind stacked pallets, a dataset collected at natural frequency holds about 7 such frames out of 1,500, which is far too few to learn from. Over-sample edge cases on purpose: their quota rows should be much larger than their share of real footage.

The spec doesn't have to be complete

You won't think of every scenario on paper. Get the spec mostly right, train a first model quickly, and let its mistakes reveal the scenarios you missed.

Try It

Draft a scenario table for your project with 5–10 rows, each with a numeric quota, plus a background row and one row per known edge case. Review it with someone who works at the deployment site and add every scenario they name. Then run volume_gap.py on any labeled data you already have (or COCO128) and note your best and worst covered classes. Deliverable: spec.md with the table and the coverage numbers.

Done When
You've finished the lesson when all of these are true.
  • The scenario table has at least 5 rows, each with a numeric quota, plus background and edge-case rows.

  • At least 5 of the 10 variance dimensions are covered or explicitly ruled out.

  • Quotas add up to at least the per-class image and instance targets, or the gap is documented as a v1 exception.

Show solution

Quotas come from the instance target, not just the image target. Suppose a parking-lot counter has two classes and pilot footage averages 3 cars and 0.5 trucks per image:

ClassInstances per imageImages needed for 10,000 instancesImages needed for the 1,500-image floorPlan
car3.0≈ 3,3001,5003,300 images
truck0.5≈ 20,0001,500Targeted capture at the loading entrance, where trucks are 10× more common

Cars are covered by ordinary footage. Trucks would need 20,000 random images, so the spec adds a dedicated scenario row (loading entrance, weekdays 06:00–10:00) instead of collecting six times more of everything. Add a background row of about 5% (empty lot at night) and edge-case rows for snow cover and trucks partly outside the frame.

What's next

With the spec written, the next lesson collects images that actually fill it.