$

$ teds read --post from-messy-labels-to-production-boxes-ultralytics-yolo26-transformers-5-12-1

From Messy Labels to Production Boxes: Ultralytics YOLO26 + Transformers 5.12.1

A modern, reproducible object detection workflow that replaces legacy SuperGradients/YOLO-NAS setups with Ultralytics YOLO26 and current Transformers tooling.

From Messy Labels to Production Boxes: Ultralytics YOLO26 + Transformers 5.12.1

TL;DR

  • This guide replaces the old SuperGradients + YOLO-NAS notebook flow with current Ultralytics YOLO.
  • The recommended default is Ultralytics/YOLO26 with the yolo26n.pt checkpoint for fast iteration.
  • The code path is compatible with transformers>=5.12.1 and gives you a repeatable pattern for almost any custom detection task.

Abstract

Most object detection tutorials fail in the same way: they work once, in one notebook, with one old stack. This post gives you a cleaner baseline that uses current Ultralytics YOLO models and keeps compatibility with modern transformers releases. You will validate your environment, run a known-good inference smoke test, convert custom data into YOLO format, and fine-tune/evaluate with a command path you can reuse across projects. By the end, you will have a workflow that is easy to debug, easy to adapt, and ready for real datasets instead of one-off demos.

Prerequisites

  • Python 3.10+
  • A CUDA GPU is recommended for training (CPU is fine for smoke tests)
  • Basic familiarity with bounding boxes and class labels
  • A dataset with images + bounding boxes (or a Hugging Face dataset that includes them)

Requirements

Use the smallest dependency set that runs the full flow:

pip install -U "ultralytics>=8.4.80" "transformers>=5.12.1" datasets pyyaml pillow packaging

Problem

The original draft depended on SuperGradients and yolo_nas_l, which creates maintenance friction today:

  • APIs and recipes drift over time
  • install paths are less predictable across environments
  • notebook-first structure hides reproducibility issues

For a reliable 2026 workflow, you want:

  1. actively maintained detector checkpoints
  2. one clean CLI/Python training path
  3. explicit version checks for your stack
  4. minimal glue code between data prep and training

Model Selection (Current-State Check)

I first followed a benchmark-driven selection approach. At the time of writing, Hugging Face benchmark:official datasets do not expose a dedicated object-detection leaderboard endpoint like language benchmarks do. So the fallback is:

  • inspect actively maintained object-detection models on the Hub
  • prioritize official upstream model cards
  • choose the most current, broadly supported default

Top official Ultralytics candidates on Hugging Face model metadata:

# Model Hub card Last modified (UTC) Downloads Recommendation
⭐1 YOLO26 Ultralytics/YOLO26 2026-06-26 6937 Use as default
2 YOLO11 Ultralytics/YOLO11 2026-06-26 8375 Great fallback
3 YOLOv8 Ultralytics/YOLOv8 2026-06-26 9541 Stable legacy baseline
4 YOLOv5 Ultralytics/YOLOv5 2026-06-26 842 Older baseline

If you are starting fresh, use yolo26n.pt first, then scale up (s/m/l/x) after your data pipeline is stable.

Approach

You will run this in four stages:

  1. Verify package versions and run a known-good smoke inference
  2. Convert your dataset to YOLO labels + data.yaml
  3. Fine-tune YOLO26 on your data
  4. Evaluate and ship the best checkpoint

Step 1: Verify Environment + Smoke Inference

from packaging.version import parse

import transformers
from ultralytics import YOLO


MIN_TRANSFORMERS = "5.12.1"
assert parse(transformers.__version__) >= parse(MIN_TRANSFORMERS), (
    f"Expected transformers>={MIN_TRANSFORMERS}, got {transformers.__version__}"
)

model = YOLO("yolo26n.pt")
results = model.predict(
    source="https://ultralytics.com/images/bus.jpg",
    imgsz=640,
    conf=0.25,
    verbose=False,
)

print("transformers:", transformers.__version__)
print("detections:", len(results[0].boxes))
print("classes:", len(model.names))

Expected signals:

  • transformers prints 5.12.1 or newer
  • detections is non-zero on the sample bus image
  • classes is 80 for COCO pretrained weights

Step 2: Convert a Hugging Face Dataset to YOLO Format

Use this when your dataset has image objects and COCO-style boxes ([x, y, width, height] in pixel space).

from pathlib import Path
from typing import Iterable

from datasets import load_dataset
import yaml


DATASET_ID = "detection-datasets/coco"
DATASET_CONFIG = "2017"
OUT_DIR = Path("yolo_data")


def resolve_class_names(dataset) -> list[str]:
    """Extract label names from a ClassLabel feature when available."""
    category_feature = dataset["train"].features["objects"]["category"].feature
    if hasattr(category_feature, "names"):
        return list(category_feature.names)
    raise ValueError("Could not auto-resolve class names. Provide CLASS_NAMES manually.")


def coco_to_yolo_xywh(box_xywh: Iterable[float], width: int, height: int) -> tuple[float, float, float, float]:
    """Convert absolute COCO box to normalized YOLO format."""
    x, y, w, h = box_xywh
    xc = (x + (w / 2.0)) / width
    yc = (y + (h / 2.0)) / height
    return xc, yc, w / width, h / height


def export_split(split_name: str) -> None:
    split = ds[split_name]
    image_dir = OUT_DIR / "images" / split_name
    label_dir = OUT_DIR / "labels" / split_name
    image_dir.mkdir(parents=True, exist_ok=True)
    label_dir.mkdir(parents=True, exist_ok=True)

    for idx, example in enumerate(split):
        image = example["image"]
        width, height = image.size
        stem = f"{idx:08d}"
        image_path = image_dir / f"{stem}.jpg"
        label_path = label_dir / f"{stem}.txt"
        image.save(image_path, format="JPEG", quality=95)

        lines: list[str] = []
        labels = example["objects"]["category"]
        boxes = example["objects"]["bbox"]
        for raw_label, raw_box in zip(labels, boxes):
            if isinstance(raw_label, str):
                if raw_label not in CLASS_TO_ID:
                    continue
                class_id = CLASS_TO_ID[raw_label]
            else:
                class_id = int(raw_label)

            if class_id < 0 or class_id >= len(CLASS_NAMES):
                continue

            xc, yc, bw, bh = coco_to_yolo_xywh(raw_box, width, height)
            lines.append(f"{class_id} {xc:.6f} {yc:.6f} {bw:.6f} {bh:.6f}")

        label_path.write_text("\n".join(lines), encoding="utf-8")


ds = load_dataset(DATASET_ID, DATASET_CONFIG)
CLASS_NAMES = resolve_class_names(ds)
CLASS_TO_ID = {name: idx for idx, name in enumerate(CLASS_NAMES)}
for split in ("train", "validation"):
    export_split(split)

data_yaml = {
    "path": str(OUT_DIR.resolve()),
    "train": "images/train",
    "val": "images/validation",
    "names": {idx: name for idx, name in enumerate(CLASS_NAMES)},
}

with (OUT_DIR / "data.yaml").open("w", encoding="utf-8") as handle:
    yaml.safe_dump(data_yaml, handle, sort_keys=False)

If your dataset does not expose a ClassLabel feature, define CLASS_NAMES manually and keep that mapping fixed for both training and evaluation.

Step 3: Fine-Tune YOLO26

Start with conservative defaults so you can debug quickly:

yolo detect train \
  model=yolo26n.pt \
  data=./yolo_data/data.yaml \
  imgsz=640 \
  epochs=50 \
  batch=16 \
  patience=20 \
  cos_lr=True \
  device=0

If you are CPU-only for a smoke run, switch device=0 to device=cpu.

Then evaluate:

yolo detect val \
  model=./runs/detect/train/weights/best.pt \
  data=./yolo_data/data.yaml \
  imgsz=640

Run predictions on holdout images:

yolo predict \
  model=./runs/detect/train/weights/best.pt \
  source=./my_holdout_images \
  conf=0.25 \
  save=True

Step 4: Export for Deployment

yolo export model=./runs/detect/train/weights/best.pt format=onnx dynamic=True

You can also export TensorRT, OpenVINO, and other formats depending on your target runtime.

Failure Modes and Caveats

  • Label format mismatch: most failed training runs come from incorrect box format (COCO vs YOLO normalized).
  • Class mapping drift: keep one single source of truth for class name to class ID mapping.
  • Overconfident filtering: high conf in early evaluation can hide true positives.
  • Small-object collapse: if tiny objects disappear, increase image size and inspect augmentation settings.
  • Domain shift: a clean validation score can still fail in production if camera angle/light differs from training data.

Practical Guidance

  • Start with yolo26n and only scale up after your data pipeline is stable.
  • Keep one tiny debug subset (50-200 images) to test config changes quickly.
  • Review false positives/false negatives every few epochs; do not rely only on one scalar metric.
  • Lock key versions in your project once a run is reproducible.

Sanity Check

  • transformers version is >=5.12.1
  • YOLO("yolo26n.pt") loads successfully
  • Sample inference returns boxes
  • data.yaml points to valid image/label folders
  • A training run produces runs/detect/train/weights/best.pt
  • yolo detect val runs without schema/label errors

Summary

If your goal is to solve “almost any” object detection task, the best move is not a bigger notebook - it is a cleaner baseline. Ultralytics YOLO26 gives you a current, maintained detector family, while transformers 5.12.1 compatibility keeps your stack aligned with modern Hugging Face tooling. Once your data format and class mapping are correct, this workflow scales cleanly from quick experiments to production checkpoints.

Next Steps

  • Run the workflow with yolo26n first, then compare yolo26s on the same dataset.
  • Add a small error-analysis script to track recurring false positives by class.
  • Export ONNX and benchmark latency before choosing a deployment target.

Related reading:

If your detection project is stuck between labels, models, and production requirements, talk to us. We can help find the constraint and build the path from dataset cleanup to usable boxes.

Resources