Skip to main content
Blog
Tutorial
Oct 19, 20266 min

Fix YOLO data.yaml and Dataset Not Found Errors

Why Ultralytics YOLO cannot find your images or labels, what "label class exceeds nc" means, and how path resolution, folder naming, names, nc, and cache files actually work.

Most YOLO training failures in the first hour have nothing to do with the model. The images are fine, the labels are fine, and training still stops before the first epoch because data.yaml points somewhere unexpected, or because the labels are not where Ultralytics looks for them.

This guide walks through the usual causes in the order you tend to hit them. The behavior described here was checked against the Ultralytics documentation and the current ultralytics source code. Exact message wording can change between releases, so match on the meaning, not the punctuation.

A layout that works

Start from a known-good structure and compare yours against it:

dataset/
├── data.yaml
├── images/
│   ├── train/   img_001.jpg, img_002.jpg, ...
│   └── val/     img_101.jpg, ...
└── labels/
    ├── train/   img_001.txt, img_002.txt, ...
    └── val/     img_101.txt, ...
path: /absolute/path/to/dataset
train: images/train
val: images/val
names:
  0: car
  1: truck

train and val point at image folders, never at label folders. Ultralytics finds the labels on its own, using a rule covered below.

1. "Dataset images not found"

When a train or val path does not exist, Ultralytics stops with a message saying the dataset images were not found and naming the missing path. It also mentions the dataset download directory, which is a hint about how relative paths are resolved.

Here is how the paths are built:

  1. train, val, and test are joined to path. train: images/train with path: /data/cars becomes /data/cars/images/train.
  2. If path is missing, the folder containing the YAML file is used as the root.
  3. If path is relative and does not exist relative to your current working directory, Ultralytics looks for it inside its datasets_dir setting instead.

Step 3 explains most "it works in the terminal but not in my notebook" reports. A relative path depends on where you launch Python. When it is not found there, the error shows a path under datasets_dir, which can look unrelated to your project.

Fix: use an absolute path, or remove path and keep data.yaml in the dataset root. Run yolo settings to see where datasets_dir points.

If train or val is a .txt file listing images, entries starting with ./ resolve relative to that .txt file, not to path.

2. "No labels found"

Ultralytics does not read a labels path from data.yaml. It derives each label path from the image path: it replaces the last /images/ folder in the path with /labels/ and swaps the file extension for .txt.

dataset/images/train/img_001.jpg  →  dataset/labels/train/img_001.txt

If none of those label files exist, Ultralytics reports "No labels found" for the split, and current releases stop training at that point. Common causes:

  • Wrong folder name. The rule matches images exactly, in lowercase. Folders named Images, imgs, or JPEGImages do not match. When the path has no /images/ folder at all, Ultralytics looks for the .txt file next to the image instead.
  • Mismatched subfolders. images/train paired with labels/training will not match.
  • Different file stems. img_001.jpg needs img_001.txt, not img_001.jpg.txt or IMG_001.txt.
  • Labels in the wrong format. COCO JSON or Pascal VOC XML sitting in labels/ is not read. Convert them to YOLO TXT first.

This short script uses the same rule to report which images have no label file:

import os
import sys
from pathlib import Path

IMAGE_SUFFIXES = {".jpg", ".jpeg", ".png", ".bmp", ".webp", ".tif", ".tiff"}
images_dir = Path(sys.argv[1]).resolve()
sa, sb = f"{os.sep}images{os.sep}", f"{os.sep}labels{os.sep}"

images = [p for p in images_dir.rglob("*") if p.suffix.lower() in IMAGE_SUFFIXES]
missing = []
for image in images:
    label = Path(sb.join(str(image).rsplit(sa, 1)).rsplit(".", 1)[0] + ".txt")
    if not label.is_file():
        missing.append(label)

print(f"{len(images)} images, {len(missing)} without a label file")
for label in missing[:10]:
    print("  expected:", label)

Run it as python check_label_paths.py dataset/images/train. A few missing files can be fine, because an image with no objects does not need a label file. If almost every image is missing one, the folder layout is the problem.

The scan line Ultralytics prints before training is worth reading too. It reports images, backgrounds, and corrupt files. If nearly everything counts as background, your labels are not being found.

3. names: list or dict

Ultralytics accepts names in either form:

names: [car, truck]
names:
  0: car
  1: truck

Both mean the same thing. With the dict form, keys must run from 0 to the number of classes minus one, with no gaps. A dict that starts at 1, or skips an index, is rejected with an error about invalid class indices. This happens often when class IDs are copied from a COCO file, where category IDs usually start at 1.

The order matters more than the format. Index 0 in names must be the class your label files call 0.

4. nc does not match names

nc is optional when names is present. If you include both, they must agree. A data.yaml with nc: 3 and two names fails with an error saying the names length and nc must match.

Fix: delete nc and let the length of names define the class count. One less number to keep in sync.

5. "Label class exceeds dataset class count"

This one does not always stop training. When a label file contains a class ID equal to or greater than the number of names, Ultralytics treats that label file as corrupt and skips the image with a warning that the label class exceeds the dataset class count and listing the valid range. If every image is skipped, training then fails because no valid images remain.

Typical causes:

  • Labels exported with 1-based IDs, so the last class gets an index that does not exist.
  • A class removed from names while its labels were left in place.
  • Two datasets merged without a shared class list.

Fix: count the IDs that actually appear in your labels, then either add the missing names or rewrite the IDs. Our guide on how to remap YOLO class IDs includes a safe script for that.

6. Stale .cache files

After the first scan, Ultralytics saves the parsed labels in a .cache file next to each labels folder, such as labels/train.cache. Recent releases compare file paths, sizes, and modification times and rescan when something changed. Older releases compared only paths and total size, so an edit that kept file sizes the same could be missed.

If you changed labels and training still reports the old numbers, delete the cache files and run again:

find dataset/labels -maxdepth 1 -name "*.cache" -delete

The trade-off is one extra scan, which is cheap next to training on outdated labels.

7. Windows paths

Windows paths cause two separate problems in YAML:

  • Backslashes inside double quotes are escape sequences. In path: "C:\new\train", YAML reads \n as a newline and \t as a tab, and the path breaks silently. Other combinations fail to parse.
  • Copied paths with mixed separators are hard to read and easy to get wrong.

Fix: use forward slashes, which work on Windows: path: C:/datasets/cars. If you must use backslashes, leave the value unquoted or use single quotes.

The images → labels rule works on Windows too. It uses the operating system's separator, but the folder name still has to be exactly images.

8. YAML indentation and syntax

YAML errors usually look unrelated to YOLO. Watch for these:

  • Tabs. YAML does not allow tabs for indentation. Use spaces.
  • Missing space after the colon. 0:car is not a key and value. Under names:, lines like 0:car and 1:dog merge into one string, and Ultralytics rejects names because it is not a list or dict.
  • Uneven indentation. Every entry under names: needs the same indent.
  • Smart quotes pasted from documents or chat tools. YAML does not treat them as quotes, so they become part of the class name or path. Retype them in a plain text editor.

Start from a generated data.yaml

If you are converting from another format, let the converter write the file. The free Format Converter supports COCO, YOLO, Pascal VOC, CVAT, LabelMe, CSV, TSV, and JSONL. Its YOLO export writes classes.txt and data.yaml and assigns class IDs from 0 in order of first appearance. Check that the order is the one you want before training.

Need separate train and val sets from a single COCO file? The Dataset Splitter creates stratified, seeded splits in your browser. Split first, then convert each subset into its own images/ and labels/ folders.

References

FAQ

Why does YOLO say my dataset images are not found?

A train or val path does not exist after being joined to path. Relative path values depend on your working directory and fall back to the Ultralytics datasets_dir setting, so an absolute path is the most reliable fix.

Where does YOLO look for label files?

It replaces the last /images/ folder in each image path with /labels/ and changes the extension to .txt. Labels must mirror the image folders, with matching file names.

What does "label class exceeds dataset class count" mean?

A label file uses a class ID that is equal to or higher than the number of names in data.yaml. Ultralytics skips those images, so fix the IDs or add the missing class names.

Do I need nc in data.yaml?

No. Ultralytics takes the class count from names. If you keep nc, it must equal the number of names, or loading the dataset fails.

Should I delete labels.cache after changing labels?

Yes, it is the safe default. Recent releases usually detect label changes, but deleting the cache guarantees a fresh scan and costs only a few seconds to minutes depending on dataset size.

Take the next step with your dataset.

Use local AI, managed cloud labeling, and team review in LabelOp. Export your work or train with your own Vast.ai account.

Start free