{"record":{"id":"26878673e97c7942","repo":"ultralytics/ultralytics","slug":"dataset-has-only-len-train-records-image-s-and","errorCode":null,"errorMessage":"Dataset has only {len(train_records)} image(s) and no 'val' split. Need at least 2 images to auto-split into train/val.","messagePattern":"Dataset has only (.+?) image\\(s\\) and no 'val' split\\. Need at least 2 images to auto-split into train/val\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"ultralytics/data/converter.py","lineNumber":958,"sourceCode":"\n    # Hash-qualified dirs allow identical datasets to reuse downloads while preventing changed datasets from mutating\n    # files that another training job may still be reading.\n    dataset_dir = output_path / f\"{ndjson_path.stem}-{_hash}\"\n    metadata_path = dataset_dir / (\".ndjson.yaml\" if is_classification else \"data.yaml\")\n    if metadata_path.is_file():\n        try:\n            if (cached := YAML.load(metadata_path)).get(\"hash\") == _hash and cached.get(\"complete\") is True:\n                return dataset_dir if is_classification else metadata_path\n        except Exception:\n            pass\n    splits = {record[\"split\"] for record in image_records}\n    if not is_classification:\n        if \"train\" not in splits:\n            raise ValueError(f\"Dataset missing required 'train' split. Found splits: {sorted(splits)}\")\n        if \"val\" not in splits:\n            train_records = [r for r in image_records if r.get(\"split\") == \"train\"]\n            if len(train_records) < 2:\n                raise ValueError(\n                    f\"Dataset has only {len(train_records)} image(s) and no 'val' split. \"\n                    f\"Need at least 2 images to auto-split into train/val.\"\n                )\n            random.Random(0).shuffle(train_records)  # local RNG to avoid mutating global training seed\n            val_count = max(1, len(train_records) // 10)\n            for r in train_records[:val_count]:\n                r[\"split\"] = \"val\"\n            splits.add(\"val\")\n            LOGGER.warning(\n                f\"WARNING ⚠️ No 'val' split found in dataset. \"\n                f\"Auto-splitting {len(train_records)} images into {len(train_records) - val_count} train, {val_count} val. \"\n                f\"For best results, manually assign validation images in Platform dataset page.\"\n            )\n\n    inferred_nc = None\n\n    if not is_classification:\n        class_ids = {","sourceCodeStart":940,"sourceCodeEnd":976,"githubUrl":"https://github.com/ultralytics/ultralytics/blob/0449ea011cfd6c9a0d50a0bf1043aca5190cd476/ultralytics/data/converter.py#L940-L976","documentation":"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.","triggerScenarios":"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.","commonSituations":"Minimal repro/test datasets; demos with one image; export truncation leaving a lone record after filtering.","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."],"exampleFix":"# before: single record {\"split\": \"train\", \"file\": \"only.jpg\", ...}\n\n# after: two records\n{\"split\": \"train\", \"file\": \"a.jpg\", ...}\n{\"split\": \"train\", \"file\": \"b.jpg\", ...}  # auto-split makes b.jpg the val image","handlingStrategy":"validation","validationCode":"splits = [r.get(\"split\") for r in image_records]\nif \"val\" not in splits and splits.count(\"train\") < 2:\n    raise ValueError(\"need >= 2 train images when no val split is provided\")","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["converter","ndjson","split","minimum-data"],"backgroundTag":null,"analyzedSha":"0449ea011cfd6c9a0d50a0bf1043aca5190cd476","analyzedAt":"2026-08-15T02:34:13.413Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}