Gerçeği Yansıtan Bölmeler
Eğitim ve doğrulama kümelerine sızan neredeyse yinelenen görüntüleri bul ve dağıtım sırasında yeni olacak birime göre bölme yap.
Bir model doğrulamada %95, üretimde %60 puan alıyorsa bölme genellikle yanıltıcıdır. Neredeyse aynı görüntüler (videodan ardışık kareler veya aynı gün aynı kamerayla çekilen görüntüler) her iki kümeye de düşmüştür; bu yüzden doğrulama, genelleme yerine ezberlemeyi sınamıştır. Gerçek bir veri kümesinde tam da bu sızıntıyı bulacak, ardından grup bölmesiyle düzelteceksin.
Uygulamalı
Üç bölme, üç görev#
| Bölme | Kim öğreniyor? | Ne zaman incelersin? |
|---|---|---|
| Eğitim | Model: ağırlıklar burada öğrenilir | Her epoch'ta |
| Doğrulama | Sen: hiperparametreler, epoch sayısı, model boyutu, eşikler | Her deneyden sonra |
| Test | Hiç kimse: yalnızca nihai sonucu bildirir | Tüm kararlar verildikten sonra, bir kez |
Bir veri bölümüne bakarak verdiğin her karar, o bölümdeki bilgilerin bir kısmını modele sızdırır. Doğrulama kümesinin görevi budur. Test kümesinde ayar yaparsan test sonucu artık dürüst bir tahmin olmaktan çıkar; kendi seçimlerine aşırı uyum sağlamış olursun. Ultralytics'te model.val(split="test"), veri kümesi YAML dosyandaki test bölümünü puanlar.
Gerçek bir veri kümesindeki sızıntıyı bul#
Tıbbi hap görselleri Frame_0.jpg, Frame_4.jpg, Frame_8.jpg şeklinde adlandırılmış; bunlar bir videodan alınan kareler. Bu bir uyarı işaretidir: Birbirine komşu kareler neredeyse aynıdır ve rastgele bir bölme bunları her iki tarafa da yerleştirir. Her doğrulama görselinin küçük gri tonlamalı parmak izini her eğitim görselininkiyle karşılaştırarak bunu kontrol et:
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.80–255 ölçeğinde 0,3'lük fark, iki görselin pratikte aynı görüntü olduğu anlamına gelir. val/Frame_24.jpg ve train/Frame_308.jpg dosyalarını yan yana aç; aynı açıdan çekilmiş aynı blister ambalajları göreceksin. 23 doğrulama görselinden 17'sinin eğitim kümesinde bir ikizi var. Dolayısıyla buradaki doğrulama puanı, çoğunlukla modelin daha önce gördüğü görselleri ne kadar iyi hatırladığını ölçüyor. Hızlı bir demo veri kümesi için bu sorun değil; ancak üretime asla böyle çıkmamalısın.

Aynı tuzak her araçta karşına çıkabilir. Bu iletişim kutusunun açıklaması, her görseli rastgele yeniden atadığını söylüyor; bu nedenle görsellerin bağımsız fotoğraflar olduğu durumlarda idealdir, ancak görseller video kareleriyse sızıntıya yol açar. Önce yinelenen görselleri kontrol et; kimsenin ayar yapmadığı bir nihai sonuç istiyorsan test payını %0'ın üzerinde tut.
Gruplara göre böl#
Kural: bölümler, dağıtım sırasında yeni olacak birime göre birbirinden bağımsız olmalı.
| Veri | Bölme ölçütü |
|---|---|
| Video | Klip veya kayıt oturumu |
| Sabit kamera filosu | Kamera veya konum |
| Zaman içinde değişen sahneler | Gün veya hafta |
| Tıbbi, spor veya kişi verileri | Hasta, sporcu veya denek |
| Ardışık fotoğraf çekimleri | Çekim olayı |
Dosya adları çoğu zaman grup anahtarını içerir. Bu örnek, 6 kameradan alınmış 900 görseli kullanarak iki stratejiyi karşılaştırıyor:
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']Görsel başına bölme yapıldığında doğrulama kümesi hiçbir zaman yeni bir kamera görmez. Kamera başına bölme yapıldığında ise puan, modelin daha önce hiç görmediği bir kamerada nasıl performans göstereceğini tahmin eder; ihtiyacın olan sonuç budur.
Yaygın Hatalar#
Belirti: doğrulama sonuçları ilk saha testinden çok daha iyi. Çözüm: grup anahtarını bul — genellikle dosya adlarında veya çekim üst verilerinde gizlidir — bu anahtara göre böl ve doğrulamak için yakın yinelenenleri kontrol et.
Belirti: AP değeri olmayan nadir bir sınıf veya yalnızca iki örnekten hesaplanan AP. Çözüm: grup bazında bölme yaptıktan sonra her bölmedeki sınıfları say ve her sınıf benzer oranlarda görünene kadar grupları bütün olarak değiştir. sklearn.model_selection.StratifiedGroupKFold bunu otomatikleştirir.
Belirti: test puanı deneyler boyunca yükselmeye devam ediyor. Çözüm: test kümesini sabitle, üretime benzer koşullardan oluştur ve tamamlanan her deney için yalnızca bir kez raporla. Küçük veri kümelerinde tek bir bölmeyi tekrar kullanmak yerine k-katlı çapraz doğrulama ile birkaç bölmenin ortalamasını al.
Veri Kümesini Doğru Bölme, oranları ve bunların tümünü eksiksiz bir veri kümesinde uygulamayı ele alıyor.
Çözümü göster
Grup anahtarını çıkaran bir işlev verildiğinde, herhangi bir dosya adı listesinde çalışan sızıntı kontrolü:
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)
# Bu listeleri kendi dosya adlarınla değiştir; ör. [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])) # kameraya göre grupla['cam01']cam01 hem train hem de val içinde görünüyor; yani bu bölmede sızıntı var. Her cam01 görselini tek bir bölüme taşı ve [] yazdırana kadar yeniden çalıştır. Tek bir videodan alınan karelerin aynı görüntüye tekrar tekrar dönüştüğü medical-pills veri kümesinde dosya adlarında temiz bir grup anahtarı yok: ya farklı ambalajları gösteren yeni doğrulama videoları kaydet ya da görselleri yinelenen görsel parmak izlerine göre kümelendirip kümelerin tamamını tek bir bölüme ata.