Ultralytics YOLO27: Inscreve-te na lista de espera.
Get Started
Fundamentos de visão computacional·Lê as métricas·Aula 8/10
Aulainiciante

Divisões que dizem a verdade

Encontra imagens quase duplicadas que criam fugas entre treino e validação e divide os dados pela unidade que será nova na implantação.

Se um modelo obtém 95% na validação e 60% em produção, normalmente é porque a divisão dos dados enganou. Imagens quase idênticas — fotogramas adjacentes de vídeo, a mesma câmara no mesmo dia — ficaram nos dois lados, pelo que a validação testou a memorização em vez da capacidade de generalização. Vais encontrar exatamente essa fuga num conjunto de dados real e depois corrigi-la com uma divisão por grupos.

Resultado

Deteta imagens quase duplicadas entre divisões, divide os dados por uma chave de grupo e confirma que nenhum grupo aparece em mais do que uma divisão.

Via rápida
Se já sabes orientar-te, aqui está a versão resumida.
  1. O treino ajusta os pesos, a validação orienta as decisões e o teste apresenta os resultados uma única vez.

  2. Verifica se há imagens quase idênticas entre divisões antes de confiar em qualquer resultado de validação.

  3. Encontra a chave de grupo: clipe, câmara, dia ou sujeito.

  4. Baralha os grupos, não as imagens, e depois confirma que cada classe aparece em todas as divisões.

  5. Congela o conjunto de teste e consulta-o uma única vez por experiência concluída.

Prático

Três divisões, três funções#

DivisãoQuem aprende com elaQuando a consultas
TreinoO modelo: os pesos são ajustados aquiA cada época
ValidaçãoVocê: hiperparâmetros, épocas, tamanho do modelo, limiaresApós cada experimento
TesteNinguém: ele informa o número finalUma vez, depois que todas as decisões forem tomadas

Cada decisão que você toma ao observar uma divisão vaza um pouco dela para o modelo. Esse é o papel do conjunto de validação. Ajuste o modelo com o conjunto de teste e o número deixa de ser uma estimativa honesta — você terá sobreajustado às próprias escolhas. No Ultralytics, model.val(split="test") avalia a divisão de teste definida no YAML do seu conjunto de dados.

Encontre o vazamento em um conjunto de dados real#

As imagens de pílulas médicas têm os nomes Frame_0.jpg, Frame_4.jpg, Frame_8.jpg — são quadros de um vídeo. Isso é um sinal de alerta: quadros adjacentes são quase idênticos, e uma divisão aleatória os coloca nos dois conjuntos. Verifique comparando pequenas impressões digitais em escala de cinza de cada imagem de validação com cada imagem de treinamento:

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

Uma diferença de 0.3 numa escala de 0–255 significa que as duas imagens são praticamente iguais. Abra val/Frame_24.jpg e train/Frame_308.jpg lado a lado e você verá as mesmas cartelas de comprimidos do mesmo ângulo. 17 das 23 imagens de validação têm uma gêmea no conjunto de treinamento, então a pontuação de validação mede, em grande parte, o quanto o modelo se lembra de imagens que já viu. Isso é aceitável para um conjunto de dados de demonstração rápida — e é exatamente o que você não deve colocar em produção.

Caixa de diálogo Redistribute Splits da Ultralytics Platform para medical-pills, definida como 80% treinamento, 20% validação e 0% teste

A mesma armadilha existe em qualquer ferramenta. A descrição da própria caixa de diálogo diz que ela reatribui aleatoriamente cada imagem, por isso é ideal quando as imagens são fotos independentes — e causa vazamento quando são quadros de vídeo. Primeiro, execute uma verificação de quase duplicatas e mantenha a proporção do conjunto de teste acima de 0% se quiser um número final que ninguém ajustou.

Divida por grupo#

A regra: as divisões devem ser independentes na unidade que será nova na implantação.

DadosDividir por
VídeoClipe ou sessão de gravação
Conjunto de câmeras fixasCâmera ou local
Cenas que mudam com o tempoDia ou semana
Dados médicos, esportivos ou de pessoasPaciente, atleta ou participante
Sequências de fotosEvento de captura

Os nomes dos arquivos geralmente contêm a chave do grupo. Este exemplo cria 900 imagens de 6 câmeras e compara as duas estratégias:

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']

Com a divisão por imagem, a validação nunca vê uma câmera nova. Com a divisão por câmera, a pontuação estima o que acontece com uma câmera que o modelo nunca viu — o número de que você precisa.

Armadilhas#

Embaralhamentos aleatórios em dados correlacionados

Sintoma: validação muito melhor que o primeiro teste em campo. Solução: encontre a chave de agrupamento — muitas vezes oculta nos nomes dos arquivos ou nos metadados de captura —, faça a divisão com base nela e execute a verificação de quase duplicatas para confirmar.

Uma classe ausente na validação

Sintoma: uma classe rara sem AP ou AP calculada com base em duas instâncias. Solução: depois da divisão por grupos, conte cada classe em cada partição e troque grupos inteiros até que todas as classes apareçam em proporções semelhantes. sklearn.model_selection.StratifiedGroupKFold automatiza esse processo.

Ajustar com base no conjunto de teste

Sintoma: o valor do teste aumenta gradualmente a cada experimento. Solução: congele o conjunto de teste, selecione-o com condições semelhantes às de produção e informe os resultados uma vez por experimento concluído. Em conjuntos de dados pequenos, calcule a média de várias divisões com validação cruzada k-fold em vez de reutilizar uma só.

Divida o conjunto de dados corretamente aborda proporções e como aplicar tudo isso a um conjunto de dados completo.

Experimente

Execute a verificação de quase duplicatas no seu conjunto de dados (ou em medical-pills). Registre quantas imagens de validação têm uma quase gêmea no conjunto de treinamento. Depois, defina a chave do grupo, divida os dados por ela e execute a verificação de vazamento da solução nas novas listas de arquivos. O resultado esperado é a contagem de quase gêmeas antes e depois, e a verificação de vazamento deve imprimir uma lista vazia.

Concluído quando
Você concluiu a aula quando todos estes critérios forem atendidos.
  • Você tem a contagem de quase gêmeas da divisão atual.

  • Você definiu a chave do grupo e refez a divisão com base nela.

  • A verificação de vazamento imprime uma lista vazia para as novas divisões.

Mostrar solução

Uma verificação de vazamento que funciona com qualquer lista de nomes de arquivos, desde que receba uma função que extraia a chave do grupo:

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)

# Substitua estas listas pelos nomes dos seus arquivos, por exemplo, [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]))  # agrupar por câmera
['cam01']

cam01 aparece tanto em train quanto em val, então esta divisão tem vazamento. Mova todas as imagens cam01 para uma única divisão e execute novamente até que seja impresso []. Para medical-pills, em que os quadros de um vídeo mostram novamente a mesma perspectiva, os nomes dos arquivos não contêm uma chave de grupo clara: grave novos vídeos de validação de embalagens diferentes ou agrupe as imagens pela impressão digital de quase duplicatas e atribua cada grupo inteiro a uma única divisão.

Próximos passos

Com divisões honestas definidas, a seguir você vai interpretar as métricas que elas produzem.

Entre para salvar seu progresso