Ultralytics YOLO27: Join the waitlist.
Get Started
Building High-Performance YOLO Datasets·Train and Iterate·Lesson 9/10
Lessonbeginner

First Fine-Tune and the Iteration Loop

Fine-tune pretrained YOLO26 weights with defaults, rank classes by AP with miss and confusion counts, and turn the weakest class into the next dataset fix.

The practice run in this lesson scores 0.66 mAP50, which sounds like a learning-rate problem. One per-class table says otherwise: cup has 14 labeled objects in val and the model finds none of them. That's a data problem, and no hyperparameter would have fixed it. Treat the first run as a diagnostic that tells you which class to fix next.

Outcome

Fine-tune a baseline, rank classes by AP with how many objects were missed or confused, and write one dataset action sized in images.

Fast Track
If you already know your way around, here's the short version.
  1. Fine-tune the pretrained YOLO26 checkpoint for your task with default settings.

  2. Rank classes by AP50-95 and count each one's misses and confusions.

  3. Missed objects point to coverage or missing labels; confusions point to class definitions.

  4. Fix the data, retrain with identical settings and split, and compare.

Hands-on

Start from pretrained weights#

Training from scratch learns every feature from new data, while transfer learning fine-tunes a pretrained network for the new task

Fine-tuning starts from weights that already recognize edges, textures, and shapes, so it converges faster and needs far less data than training from random weights. Pick the YOLO26 checkpoint that matches your task:

TaskPretrained checkpointPretrained on
Detectyolo26n.ptCOCO
Segmentyolo26n-seg.ptCOCO
Semanticyolo26n-sem.ptCityscapes
Poseyolo26n-pose.ptCOCO keypoints
OBByolo26n-obb.ptDOTAv1
Classifyyolo26n-cls.ptImageNet
Depthyolo26n-depth.ptA large indoor and outdoor depth mix

The backbone and neck transfer as they are; the task head is partly reinitialized to match your class count. Training from scratch only pays off with very large datasets in very unusual domains. Start with the nano (n) model to get a baseline quickly, and scale to larger sizes only after the data loop stops paying off. The fine-tuning guide covers what happens inside.

Run the baseline#

Train with defaults; the point of the first run is a baseline, not a record. This trains on the practice set split in Split the Dataset Correctly, first writing its data YAML:

from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset
from ultralytics.utils import YAML

names = check_det_dataset("coco128.yaml")["names"]  # the practice set keeps COCO's class names
YAML.save("practice.yaml", {"path": "datasets/practice", "train": "train.txt", "val": "val.txt", "test": "test.txt", "names": names})

model = YOLO("yolo26n.pt")  # pretrained on COCO; use the checkpoint for your task
model.train(data="practice.yaml", epochs=3, imgsz=320)  # a real run: epochs=100, imgsz=640
                 Class     Images  Instances      Box(P          R      mAP50  mAP50-95)
                   all         20        143      0.614        0.6      0.664      0.486

The run folder (runs/detect/train/) now holds weights/best.pt. Only three arguments are worth considering on the first run:

ArgumentDefaultWhen to change
epochs100Fewer for tiny datasets; 200–300 for very large ones
imgsz6401024 or 1280 if many objects are small in the frame
batch16-1 for auto-batch; lower on out-of-memory errors

Train Your First Model covers the training call and its live log in detail.

Rank classes and read the misses#

The overall mAP hides where the problem is. Rank classes by AP50-95, and use the confusion matrix to split each class's errors into objects the model missed entirely and objects it called another class:

from ultralytics import YOLO

model = YOLO("runs/detect/train/weights/best.pt")  # your fine-tuned checkpoint
metrics = model.val(data="practice.yaml", imgsz=320, verbose=False)  # same data YAML and imgsz as training

matrix = metrics.confusion_matrix.matrix  # rows: predicted class, columns: true class; last row = background
for ap, cls in sorted(zip(metrics.box.ap, metrics.box.ap_class_index))[:5]:  # weakest five classes first
    total, missed, correct = matrix[:, cls].sum(), matrix[-1, cls], matrix[cls, cls]
    print(f"{model.names[cls]:>8}  AP50-95={ap:.3f}  {total:2.0f} labeled: {correct:2.0f} found, "
          f"{missed:2.0f} missed, {total - correct - missed:2.0f} called another class")
     car  AP50-95=0.000   4 labeled:  0 found,  4 missed,  0 called another class
    fork  AP50-95=0.000   1 labeled:  0 found,  0 missed,  1 called another class
 handbag  AP50-95=0.025   1 labeled:  1 found,  0 missed,  0 called another class
     cup  AP50-95=0.039  14 labeled:  0 found, 10 missed,  4 called another class
    sink  AP50-95=0.075   1 labeled:  1 found,  0 missed,  0 called another class

Each error type points to a different dataset fix:

  • Too few val instances (fork, handbag, sink, with 1 each): the AP is noise. Don't act on a class until val holds at least a few dozen of its instances; collect more val groups first.
  • Missed (car 4 of 4, cup 10 of 14): the model sees no object at all. Check that scenario's coverage in train and look for missing labels with suspect_labels.py.
  • Called another class (cup 4 of 14): the class definitions overlap. Open those images, sharpen the definitions, and name the look-alike in the guide.

cup is the actionable finding here: 14 labeled instances, and none found.

Read the run folder as data feedback#

Ultralytics Platform training charts for a segmentation model: mAP50, mAP50-95, precision, and recall summary cards above per-epoch precision, recall, and mAP50-95 curves

Every training run writes these files, and Ultralytics Platform plots the same curves live during cloud training:

FileWhat it tells you about the data
confusion_matrix_normalized.pngWhich classes are confused with each other or missed as background
BoxPR_curve.pngPer-class precision-recall; a low curve marks a weak class
val_batch0_pred.jpgPredictions on real val images; look for systematic misses
labels.jpgClass counts and box sizes the model trained on
results.pngLoss and metric curves; val loss rising while train loss falls signals overfitting

Validate and Diagnose explains each plot; here, the question is always what the data is missing.

The data-first loop#

A typical iteration: pick the weakest class with enough val instances, audit about 50 images that contain it, fix the data, retrain with the same settings and split, and compare per-class AP. Then move to the next weakest class.

Change one thing per retrain

If you add 300 images and raise imgsz in the same retrain, you can't tell which change moved the score. Keep settings and split identical while iterating on data, so every comparison isolates one data change.

When to reach for hyperparameters#

After two or three data iterations, once the remaining weak classes no longer trace back to coverage, labels, or definitions, tuning can buy the last few points. The fine-tuning guide covers layer freezing, learning rates, and optimizer choice. Tune before the data is right, though, and you spend weeks optimizing a model of the wrong dataset.

Once a model passes acceptance_test.py on the locked test set, Ultralytics YOLO in Production covers export and serving, and Monitor Health and Accuracy and Automate the Retraining Loop in Build with Ultralytics Platform cover the retraining loop.

Try It

Fine-tune your task's YOLO26 checkpoint with default settings, then run rank_classes.py on best.pt. Skip classes with too few val instances, audit about 50 images of the weakest remaining class, and decide whether its errors are misses or confusions. Deliverable: one dataset action (collect, relabel, or redefine) with the number of images it affects.

Done When
You've finished the lesson when all of these are true.
  • A default-settings baseline has produced best.pt and a ranked per-class table.

  • The weakest class with enough val instances is named, with its miss and confusion counts.

  • One dataset action is written with the number of images it affects, and no hyperparameter has changed yet.

Show solution

From the practice run, cup is the class to act on: 14 labeled in val, 0 found, 10 missed, 4 called another class. A sized plan:

FindingDataset actionSize
10 of 14 cups missedCheck train coverage: count train images with cups by camera-day; collect cups in the groups that have none40 new images with cups, across 4 camera-days
4 of 14 called another classOpen them in val_batch0_pred.jpg and the confusion matrix; if they're called wine glass or bowl, add both look-alikes to the cup definitionRelabel about 30 train images against the new rule
1-instance classesDon't act yet; add val groups that contain themNext collection round

Then retrain with epochs, imgsz, and the split unchanged, rerun rank_classes.py, and compare cup AP before and after. If it rises, the data was the bottleneck; if it doesn't, audit the next 50 images before touching a hyperparameter.

What's next

The last lesson packages the whole course into a one-page checklist you can run with a customer at every project kickoff.