Ultralytics YOLO27: Iscriviti alla lista d'attesa.
Get Started
Fondamenti della visione artificiale·Leggi le metriche·Lezione 8/10
Lezioneprincipiante

Suddivisioni che dicono la verità

Individua le immagini quasi duplicate che si infiltrano tra train e validation e suddividi i dati in base all'unità che sarà nuova in fase di distribuzione.

Se un modello ottiene il 95% in validation e il 60% in produzione, di solito è perché la suddivisione ha fornito un quadro ingannevole. Immagini quasi identiche — fotogrammi adiacenti di un video, ripresi dalla stessa fotocamera nello stesso giorno — sono finite da entrambe le parti, perciò la validation ha misurato la memorizzazione, non la capacità di generalizzare. Individuerai proprio questa fuga di dati in un dataset reale e poi la correggerai suddividendo per gruppi.

Esito

Individua le immagini quasi duplicate tra le suddivisioni, suddividi usando una chiave di gruppo e verifica che nessun gruppo compaia in più di una suddivisione.

Percorso rapido
Se sai già come muoverti, ecco la versione breve.
  1. Train calcola i pesi, validation guida le decisioni, test fornisce un risultato una sola volta.

  2. Controlla che non ci siano immagini quasi identiche tra le suddivisioni prima di fidarti di qualsiasi punteggio di validation.

  3. Individua la chiave di gruppo: clip, fotocamera, giorno o soggetto.

  4. Mescola i gruppi, non le immagini, poi verifica che ogni classe sia presente in ogni suddivisione.

  5. Congela il test set e usalo una sola volta per ogni esperimento completato.

Pratico

Tre suddivisioni, tre funzioni#

SuddivisioneChi apprende da essaQuando la esamini
TrainIl modello: qui vengono adattati i pesiA ogni epoca
ValidazioneTu: iperparametri, epoche, dimensioni del modello, soglieDopo ogni esperimento
TestNessuno: riporta il numero finaleUna sola volta, dopo aver preso ogni decisione

Ogni decisione che prendi osservando uno split ne fa trapelare una piccola parte nel modello. È questo il compito del set di validazione. Se ottimizzi sul set di test, il suo numero smette di essere una stima onesta: hai eseguito l'overfitting sulle tue stesse scelte. In Ultralytics, model.val(split="test") valuta lo split di test del tuo dataset YAML.

Trova la fuga di dati in un dataset reale#

Le immagini di medical-pills si chiamano Frame_0.jpg, Frame_4.jpg, Frame_8.jpg: sono fotogrammi di un video. È un campanello d'allarme: i fotogrammi consecutivi sono quasi identici e uno split casuale li distribuisce in entrambi i set. Verificalo confrontando le impronte in scala di grigi, di dimensioni ridotte, di ogni immagine di validazione con quelle di ogni immagine di training:

from pathlib import Path

import numpy as np
from PIL import Image
from ultralytics.data.utils import check_det_dataset

data = check_det_dataset("medical-pills.yaml")

def thumbnail(path):
    """A 32x18 grayscale fingerprint: enough to spot near-identical frames."""
    return np.asarray(Image.open(path).convert("L").resize((32, 18)), dtype=float)

train = {p.name: thumbnail(p) for p in sorted(Path(data["train"]).glob("*.jpg"))}
pairs = []
for p in sorted(Path(data["val"]).glob("*.jpg")):
    v = thumbnail(p)
    name, diff = min(((n, np.abs(v - t).mean()) for n, t in train.items()), key=lambda x: x[1])
    pairs.append((diff, p.name, name))

pairs.sort()
print(f"{sum(d < 5 for d, _, _ in pairs)} of {len(pairs)} val images have a near-twin in train (difference < 5)")
for diff, val_name, train_name in pairs[:3]:
    print(f"  {val_name} ~ {train_name}  difference {diff:.1f}")
17 of 23 val images have a near-twin in train (difference < 5)
  Frame_344.jpg ~ Frame_56.jpg  difference 0.3
  Frame_28.jpg ~ Frame_312.jpg  difference 0.5
  Frame_24.jpg ~ Frame_308.jpg  difference 1.8

Una differenza di 0.3 su una scala da 0 a 255 significa che le due immagini sono praticamente identiche. Apri val/Frame_24.jpg e train/Frame_308.jpg affiancate e vedrai le stesse confezioni blister dalla stessa angolazione. 17 delle 23 immagini di validazione hanno un gemello nel set di training, quindi il punteggio di validazione misura soprattutto quanto bene il modello ricorda immagini già viste. Va bene per un rapido dataset dimostrativo, ma è esattamente ciò che non devi distribuire.

Finestra di dialogo Ridistribuisci gli split di Ultralytics Platform per medical-pills, impostata su 80% training, 20% validazione e 0% test

La stessa trappola esiste in qualsiasi strumento. La descrizione della finestra di dialogo dice che riassegna casualmente ogni immagine: è quindi ideale quando le immagini sono fotografie indipendenti, ma causa fughe di dati quando sono fotogrammi video. Esegui prima un controllo dei quasi-duplicati e mantieni la quota del test sopra lo 0% se vuoi un numero finale su cui nessuno abbia ottimizzato.

Dividi per gruppi#

La regola: gli split devono essere indipendenti rispetto all'unità che sarà nuova in fase di deployment.

DatiDividi per
VideoClip o sessione di registrazione
Parco di telecamere fisseTelecamera o sito
Scene che cambiano nel tempoGiorno o settimana
Dati medici, sportivi o relativi a personePaziente, atleta o soggetto
Raffiche di fotoEvento di acquisizione

I nomi dei file spesso contengono la chiave del gruppo. Questo esempio crea 900 immagini da 6 telecamere e confronta le due strategie:

import random
from collections import defaultdict

# File names carry the unit that must never straddle splits: camera_day_frame.jpg
images = [f"cam{c:02d}_day{d}_{f:04d}.jpg" for c in range(1, 7) for d in range(1, 4) for f in range(50)]

# WRONG: shuffle individual frames, so near-identical neighbors land in both splits
random.seed(0)
shuffled = random.sample(images, len(images))
train, val = shuffled[: int(0.8 * len(images))], shuffled[int(0.8 * len(images)) :]
shared = {i.split("_")[0] for i in train} & {i.split("_")[0] for i in val}
print(f"per-image split: {len(shared)} of 6 cameras appear in both train and val")

# RIGHT: shuffle whole cameras, then expand them back into images
groups = defaultdict(list)
for image in images:
    groups[image.split("_")[0]].append(image)
cameras = random.sample(sorted(groups), len(groups))
train = [i for cam in cameras[:5] for i in groups[cam]]
val = [i for cam in cameras[5:] for i in groups[cam]]
print(f"per-camera split: train {len(train)} images, val {len(val)} images, val cameras {cameras[5:]}")
per-image split: 6 of 6 cameras appear in both train and val
per-camera split: train 750 images, val 150 images, val cameras ['cam05']

Con lo split per immagine, la validazione non vede mai una telecamera nuova. Con lo split per telecamera, il punteggio stima cosa accade con una telecamera mai vista dal modello: il numero che ti serve.

Insidie#

Mescolamento casuale su dati correlati

Sintomo: la validazione è molto migliore del primo test sul campo. Soluzione: individua la chiave di raggruppamento, spesso nascosta nei nomi dei file o nei metadati di acquisizione, suddividi i dati in base a essa ed esegui il controllo dei quasi duplicati per confermare.

Una classe assente dalla validazione

Sintomo: una classe rara senza AP, oppure AP calcolata su due istanze. Soluzione: dopo la suddivisione in gruppi, conta le istanze di ogni classe in ciascuna suddivisione e scambia gruppi interi finché tutte le classi non compaiono in proporzioni simili. sklearn.model_selection.StratifiedGroupKFold automatizza questa procedura.

Regolare i parametri sul set di test

Sintomo: il punteggio del test aumenta a poco a poco con ogni esperimento. Soluzione: congela il set di test, ricavalo da condizioni simili a quelle di produzione e riportane i risultati una volta per ogni esperimento completato. Con dataset piccoli, calcola la media su più suddivisioni usando la convalida incrociata k-fold invece di riutilizzare sempre la stessa.

Dividere correttamente il dataset tratta i rapporti e come applicare tutto questo a un dataset completo.

Provalo

Esegui il controllo dei quasi-duplicati sul tuo dataset (o su medical-pills). Registra quante immagini di validazione hanno un quasi-gemello nel set di training. Poi definisci la chiave del gruppo, dividi nuovamente i dati in base a essa ed esegui il controllo delle fughe di dati della soluzione sui nuovi elenchi di file. Il risultato da consegnare è il numero di quasi-gemelli prima e dopo, insieme al controllo delle fughe di dati che restituisce un elenco vuoto.

Criteri di completamento
La lezione è completata quando tutte le seguenti condizioni sono soddisfatte.
  • Hai il conteggio dei quasi-gemelli per lo split attuale.

  • Hai definito la chiave del gruppo e diviso nuovamente i dati in base a essa.

  • Il controllo delle fughe di dati restituisce un elenco vuoto per i nuovi split.

Mostra la soluzione

Un controllo delle fughe di dati che funziona con qualsiasi elenco di nomi di file, data una funzione che estrae la chiave del gruppo:

def leaked_groups(splits, key):
    """Return every group key that appears in more than one split."""
    owner, leaks = {}, set()
    for split, files in splits.items():
        for name in files:
            group = key(name)
            if owner.setdefault(group, split) != split:
                leaks.add(group)
    return sorted(leaks)

# Sostituisci questi elenchi con i nomi dei tuoi file, ad es. [p.name for p in Path("images/train").glob("*.jpg")]
splits = {
    "train": ["cam01_day1_0001.jpg", "cam01_day1_0002.jpg", "cam02_day1_0001.jpg"],
    "val": ["cam03_day2_0001.jpg", "cam01_day1_0003.jpg"],
    "test": ["cam04_day3_0001.jpg"],
}
print(leaked_groups(splits, key=lambda name: name.split("_")[0]))  # raggruppa per telecamera
['cam01']

cam01 compare sia in train sia in val, quindi questo split presenta una fuga di dati. Sposta tutte le immagini cam01 in un unico split e ripeti finché non viene stampato []. Per medical-pills, dove i fotogrammi di uno stesso video riprendono la stessa inquadratura, i nomi dei file non contengono una chiave di gruppo affidabile: registra nuovi video di validazione con confezioni diverse oppure raggruppa le immagini in base all'impronta dei quasi-duplicati e assegna interi gruppi a un singolo split.

Prossimi passi

Ora che gli split sono affidabili, nel passaggio successivo leggerai le metriche che producono.

Accedi per salvare i progressi