ultralytics/ultralytics · error · ValueError
Dataset has only {len(train_records)} image(s) and no 'val'
Error message
Dataset has only {len(train_records)} image(s) and no 'val' split. Need at least 2 images to auto-split into train/val. What it means
When a non-classification NDJSON dataset has no 'val' split, the converter auto-splits by deterministically (random.Random(0)) moving ~10% of train records (at least 1) to val. That requires at least 2 train records; with 0 or 1 it cannot form both splits and raises ValueError telling you the count found and the minimum.
Source
Thrown at ultralytics/data/converter.py:958
# Hash-qualified dirs allow identical datasets to reuse downloads while preventing changed datasets from mutating
# files that another training job may still be reading.
dataset_dir = output_path / f"{ndjson_path.stem}-{_hash}"
metadata_path = dataset_dir / (".ndjson.yaml" if is_classification else "data.yaml")
if metadata_path.is_file():
try:
if (cached := YAML.load(metadata_path)).get("hash") == _hash and cached.get("complete") is True:
return dataset_dir if is_classification else metadata_path
except Exception:
pass
splits = {record["split"] for record in image_records}
if not is_classification:
if "train" not in splits:
raise ValueError(f"Dataset missing required 'train' split. Found splits: {sorted(splits)}")
if "val" not in splits:
train_records = [r for r in image_records if r.get("split") == "train"]
if len(train_records) < 2:
raise ValueError(
f"Dataset has only {len(train_records)} image(s) and no 'val' split. "
f"Need at least 2 images to auto-split into train/val."
)
random.Random(0).shuffle(train_records) # local RNG to avoid mutating global training seed
val_count = max(1, len(train_records) // 10)
for r in train_records[:val_count]:
r["split"] = "val"
splits.add("val")
LOGGER.warning(
f"WARNING ⚠️ No 'val' split found in dataset. "
f"Auto-splitting {len(train_records)} images into {len(train_records) - val_count} train, {val_count} val. "
f"For best results, manually assign validation images in Platform dataset page."
)
inferred_nc = None
if not is_classification:
class_ids = {View on GitHub (pinned to 0449ea011c)
Solutions
- Provide at least 2 train images (practically far more) so auto-split can carve out a val image.
- Alternatively supply an explicit 'val' split record so no auto-split is needed.
- For single-image experiments, use direct predict on the image instead of dataset training.
Example fix
# before: single record {"split": "train", "file": "only.jpg", ...}
# after: two records
{"split": "train", "file": "a.jpg", ...}
{"split": "train", "file": "b.jpg", ...} # auto-split makes b.jpg the val image Defensive patterns
Strategy: validation
Validate before calling
splits = [r.get("split") for r in image_records]
if "val" not in splits and splits.count("train") < 2:
raise ValueError("need >= 2 train images when no val split is provided") Prevention
- Always ship an explicit val split (even a single image) in exported datasets to avoid depending on auto-split.
- Gate minimum dataset size in your data-collection tooling so one-off smoke datasets never reach the trainer.
When it happens
Trigger: A single-image (or zero-image) train-only dataset, e.g. a smoke-test export with one sample, or a pilot dataset collected before more data exists.
Common situations: Minimal repro/test datasets; demos with one image; export truncation leaving a lone record after filtering.
Related errors
- Invalid NDJSON split: {split!r}
- Dataset missing required 'train' split. Found splits: {sorte
- Pose dataset missing required 'kpt_shape'. See https://docs.
- Invalid NDJSON image name: {source_name!r}
- Invalid NDJSON classification ID: {class_id!r}
AI-assisted analysis of ultralytics/ultralytics@0449ea011c (2026-08-15).
Data as JSON: /api/errors/26878673e97c7942.
Report an issue: GitHub.