Miscalibrated yolo26

Hello Everyone! I trained a yolo26 model to detect tiny objects, performance is decent-ish, with map@50 0.62, in line with other architectures I tried, the problem is the model has very low confidence, with maximum f1 archieved at p=0.003, making even the map calculation numerically questionable.
This is probably due to class imbalance, most image is background, with a tiny target somewhere, some images have no target at all.
With older models I could tune the parameter alpha in the focal loss to address class imbalance. Is there some similar parameter in the more involved yolo26 loss to weight less the true negatives? I am detecting single class if it is relevant.

With extreme foreground/background imbalance it’s pretty normal for the optimal F1 to land at a very low conf (your model is basically saying “almost everything is background”), and that doesn’t make mAP invalid—mAP is based on ranking predictions by score across thresholds, not on any single “reasonable” confidence value. For inference you can simply run with conf=0.003 (or whatever your PR/F1 curve suggests).

If you want to explicitly reduce the impact of true negatives / boost positives like you did with focal-loss alpha, Ultralytics YOLO26 doesn’t expose a simple “alpha knob” in the CLI, but you can do the equivalent by upweighting the positive class in the BCE classification loss (per-class pos_weight) via a custom trainer. The docs show the exact pattern here in the Customizing Trainer guide (Adding Class Weights).

For a single-class dataset, it’s as simple as setting a 1-element weight tensor, e.g. torch.tensor([10.0]), in init_criterion() (adapted from the doc example):

import torch
from torch import nn
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import RANK
from ultralytics.utils.loss import E2ELoss, v8DetectionLoss


class WeightedDetectionLoss(v8DetectionLoss):
    def __init__(self, model, class_weights=None, tal_topk=10, tal_topk2=None):
        super().__init__(model, tal_topk=tal_topk, tal_topk2=tal_topk2)
        if class_weights is not None:
            self.bce = nn.BCEWithLogitsLoss(
                pos_weight=class_weights.to(self.device),
                reduction="none",
            )


class WeightedE2ELoss(E2ELoss):
    def __init__(self, model, class_weights=None):
        super().__init__(model, loss_fn=lambda m, tal_topk=10, tal_topk2=None:
                         WeightedDetectionLoss(m, class_weights=class_weights, tal_topk=tal_topk, tal_topk2=tal_topk2))


class WeightedDetectionModel(DetectionModel):
    def init_criterion(self):
        # single-class: shape [1]
        class_weights = torch.tensor([10.0])
        return WeightedE2ELoss(self, class_weights=class_weights)


class WeightedTrainer(DetectionTrainer):
    def get_model(self, cfg=None, weights=None, verbose=True):
        model = WeightedDetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)
        if weights:
            model.load(weights)
        return model


model = YOLO("yolo26n.pt")
model.train(data="your.yaml", epochs=100, trainer=WeightedTrainer)

If you share your label stats (how many images are empty vs contain an object, plus object size distribution and imgsz), I can suggest a sane starting weight and a couple of training tweaks for tiny targets.