$ 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/YOLO26with theyolo26n.ptcheckpoint for fast iteration. - The code path is compatible with
transformers>=5.12.1and 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:
- actively maintained detector checkpoints
- one clean CLI/Python training path
- explicit version checks for your stack
- 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:
- Verify package versions and run a known-good smoke inference
- Convert your dataset to YOLO labels +
data.yaml - Fine-tune YOLO26 on your data
- 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:
transformersprints5.12.1or newerdetectionsis non-zero on the sample bus imageclassesis80for 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
confin 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
yolo26nand 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
transformersversion is>=5.12.1YOLO("yolo26n.pt")loads successfully- Sample inference returns boxes
data.yamlpoints to valid image/label folders- A training run produces
runs/detect/train/weights/best.pt yolo detect valruns 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
yolo26nfirst, then compareyolo26son 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:
- For a real-time detector overview, read YOLOv12 in Practice.
- For a transformer-native detection baseline, read DETR Explained.
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.