Nesne tespiti anlatan her eğitim aynı yerden başlıyor: al sana dataset, al sana eğitim komutu, bak doğruluk oranına. Halbuki asıl zaman oraya gitmiyor. Gerçek hayatta kimse eline etiketlenmiş bir dataset tutuşturmuyor. İyi ihtimalle elinde görseller oluyor, çoğu zaman onlar da yok, ve bir yığın fotoğrafı modelin öğrenebileceği bir şeye çevirmek bütün öğleden sonrayı yiyen kısım.
Bu işin gerçekte neye benzediğini görmek istedim. Bilerek küçük bir problem seçtim: yeniden kullanılabilir bir matarayı tek kullanımlık pet şişeden ayırt eden bir dedektör. İki sınıf, yayınlanacak bir ürün yok, deadline yok. Sadece pipeline, baştan sona, kendi donanımımda.
Yazı bu pipeline’ı sırayla anlatıyor: görsel toplama, etiketleme, eğitim, test. İlerledikçe teknikleşiyor, ve en faydalı kısımlar bir şeyin bozulduğu üç yer. İlgin nereye kadar götürürse oraya kadar oku, ilk yarısı kendi başına ayakta duruyor.
TL; DR
YOLO26 ile matara ve pet şişeyi ayırt eden bir model eğittim. Elle tek bir bounding box çizmedim. Görseller görsel aramadan geldi, kutuları daha büyük bir hazır model çizdi, ben sadece onun hatalarını düzelttim. Düzeltmeyi de tam bu iş için yazdığım küçük bir araçla yaptım. Eğitim RTX 3090’da beş dakika sürdü. Sonuç mAP50 0.692.
Yazının kapsadıkları:
- Eğitim görseli toplama, ve arama sorgularının neden sandığından çok daha önemli olduğu
- Pseudo-labeling: büyük bir modele küçük bir model için veri etiketletmek
- İlk denemem datasetin yarısını neden çöpe attı, ve bunu nasıl yakaladım
- Genel amaçlı etiketleme programı yerine tek işe özel araç yazmak
- Eğitim eğrisini okumak, ve mAP50 ile mAP50-95’in aslında ne konuda anlaşamadığı
- cuDNN sürüm çakışması, yani çalışan bir GPU kurulumunun bozulmasının en yaygın yolu
Önce, hangi YOLO?
YOLO, “You Only Look Once” demek. 2015’ten beri üzerine eklenerek gelen bir nesne tespit modeli ailesi. Nesne tespiti şu iki işi aynı anda yapmak: her görselde nesnelerin nerede olduğunu bulmak (bir dikdörtgen, adı bounding box) ve her birinin ne olduğunu söylemek.
Güncel sürüm YOLO26, Ultralytics Ocak 2026’da çıkardı. Burada iki özelliği önemli.
Birincisi uçtan uca çalışması. Eski dedektörler aynı nesne için onlarca üst üste binen kutu tahmin edip, sonra Non-Maximum Suppression (NMS) denen bir temizlik adımıyla en iyisini seçiyordu. YOLO26 bu adımı mimarinin içine aldı, tek seferde temiz çıktı veriyor. Bu çoğunlukla deploy tarafını sadeleştiriyor ama yanlış ayarlanacak daha az düğme kalması da bir kazanç.
İkincisi beş boyutta gelmesi: n (nano) ile x (extra large) arası. Bu sandığından çok daha önemli çıkacak, ama tahmin ettiğin sebepten değil.
İki sınıflı bir problemde mimari neredeyse hiç fark etmiyor. Sonucu belirleyen veri. O yüzden aşağıdakilerin neredeyse tamamı veriyle ilgili.
Kamera olmadan görsel toplamak
Elimde fotoğraf yoktu. Görseller DuckDuckGo görsel aramasından, küçük bir scraper ile geldi. On altı sorgu, her biri kırk civarı sonuç, kopyalar ve bozuk olanlar elendikten sonra 500 görsel.
İşin ilginç kısmı sorguların kendisi. Akla gelen ilk arama “water bottle”. Onu yazarsan e-ticaret ürün fotoğrafı alırsın: beyaz arka plan, ortalanmış nesne, stüdyo ışığı, hiç bağlam yok. Böyle bir veriyle eğitilmiş model gerçek bir masaya gerçek bir kamera tuttuğun an çöker, çünkü webcam karesi katalog fotoğrafına hiç benzemez. Eğittiğin veri ile üzerinde çalıştırdığın veri arasındaki bu farkın adı domain gap, ve scrape edilmiş datasetlerin hayal kırıklığı yaratmasının bir numaralı sebebi.
Bu yüzden sorgular bilerek bağlam taşıyor:
QUERIES = {
"matara": [
"person drinking from reusable water bottle",
"stainless steel water bottle on desk",
"gym water bottle on bench",
"hiking water bottle backpack outdoors",
...
],
"pet_sise": [
"person holding plastic water bottle",
"plastic water bottle on table",
"disposable plastic bottle on desk",
...
],
}
Bağlamlı sorgulara rağmen sonuç yine dağınık. Bir sorgu, "plastic water bottle car cup holder", neredeyse tamamen araba bardaklığı ürün fotoğrafı getirdi, karede şişe bile yok. Bazı sonuçlar 3D render, bazıları infografik, birkaçı da tek karede elli şişe olan geri dönüşüm yığını. Bunu o an bilmiyordum, öğrenmek bir sonraki adımdı.
Etiketlemeden etiketlemek
İnsanları şaşırtan kısım burası: hiç kutu çizmedim, bu projede hiçbir insan çizmedi.
COCO, 2014’ten kalma, 80 sınıfta yaklaşık 200 bin insan tarafından etiketlenmiş görselden oluşan yaygın bir nesne tespit dataseti. Sınıflarından biri bottle. Hazır gelen her YOLO modeli COCO üzerinde eğitilmiş durumda, yani hazır bir YOLO görsellerdeki şişeleri zaten buluyor, benden hiçbir emek istemeden.
O zaman etiketleme adımı şuna dönüşüyor: hazır modeli kendi görsellerimin üzerinde koştur, tahmin ettiği kutuları al, YOLO’nun etiket formatında yaz. Bunun adı pseudo-labeling, ve kutuların kimden geldiğine bir saniye bakmaya değer:
COCO (2014) insanlar 200 binden fazla görseli elle etiketledi
↓
YOLO26 o insan etiketlerinden öğrendi
↓
benim 500 görselim model öğrendiğini buraya uyguladı
↓
557 kutu benim sonra düzelttiklerim
Datasetimdeki kutuları aslında on yıldan uzun süre önce COCO’yu etiketleyen insanlar çizdi. Emekleri bir ağırlık dosyasına sıkışmış, o model de aynı işi yeni görsellerde tekrarlıyor.
Sınıf ataması ayrı bir numara. Hazır model bottle biliyor ama benim önemsediğim ayrımı hiç duymamış. Ben de sınıfı, görselin hangi klasöre scrape edildiğine bakarak tahmin ettim: raw/matara/ klasöründen gelene sınıf 0, raw/pet_sise/ klasöründen gelene sınıf 1. Klasörü arama sorgusu belirlediği için bu tahmin çoğunlukla doğru. Yanlışları sonra ben düzelteceğim, ve yanlış bir etiketi düzeltmek sıfırdan kutu çizmekten çok daha hızlı.
İlk hata: datasetin yarısını çöpe attım
İlk denemem 500 görselden 254 kullanılabilir görsel çıkardı. Datasetin neredeyse yarısında hiç kutu bulunamamıştı ve ben onları kullanılamaz sayacaktım.
Silmeden önce elenenlerin bir contact sheet’ini yapıp baktım. İki dakika sürdü ve projenin en değerli iki dakikasıydı. Elenen yığın apaçık şişe doluydu: pembe fonda tutulan çelik matara, turkuaz matarasından su içen çocuk, tahtanın üzerinde ezilmiş pet şişe, laptopun yanındaki termos. Hatalı olan veri değil, benim pipeline’ımdı.
Sebebini bulmak için modeli, elenenlerin altmışında, sınıf filtresi kaldırılmış ve confidence eşiği düşürülmüş halde tekrar koşturdum. Dönen sınıfları saydım:
person 48
bottle 18
cup 18
cell phone 10
toothbrush 9
bench 8
vase 6
Tek tabloda iki ayrı hata görünüyor.
Birinci hata: confidence eşiğim çok yüksekti. O altmış görselin on sekizinde model bottle etiketli bir tahmin zaten bulmuştu. Benim conf=0.25 kesme noktam onları atmıştı.
İkinci hata: COCO’nun şişe tanımı benimkinden dar. On sekiz sonuç cup, altı sonuç vase olarak döndü. COCO’nun bottle sınıfı ağırlıklı olarak plastik ve cam içecek şişesi. Çelik bir termos onlara benzemiyor, bardağa ya da vazoya benziyor, model de öyle diyor. Bu modelin hatası değil. Ona öğretilen tanım bu.
İkinci hatanın çözümü bir yerine üç COCO sınıfını kabul etmekti. Kutunun sadece yeri doğru olsun yeter, sınıfı zaten klasörden geliyor:
WANT_CLASSES = [39, 41, 75] # bottle, cup, vase
Ama asıl önemli düzeltme, hiç aklıma gelmemiş olan diğeriydi.
Etiketlemeyi büyük modele, çalışmayı küçüğe
Ön etiketleme için yolo26n, yani nano modeli kullanmıştım. Düşüncesizceydi. Ön etiketleme çevrimdışı bir iş. Bir kere çalışıyor, benim makinemde, bekleyen kullanıcı yok. Hız hiç önemli değil, dolayısıyla en küçük ve en zayıf modeli seçmek bana hiçbir şey kazandırmadı, doğruluk kaybettirdi.
Farkı aynı altmış görselde ölçtüm:
| Model | Confidence | Kutu bulunan görsel |
|---|---|---|
yolo26n | 0.15 | 23 / 60 |
yolo26x | 0.15 | 44 / 60 |
yolo26x | 0.25 | 44 / 60 |
Extra large model neredeyse ikiye katlıyor. Model yeterince iyi olduğunda confidence eşiğinin önemsizleşmesi de kendi başına faydalı bir sinyal.
Bu kalıp makine öğrenmesinin her yerinde karşına çıkıyor: büyük, yavaş, pahalı bir model etiket üretir; küçük, hızlı, ucuz bir model o etiketlerle eğitilir. Pahalı model bir kere çalışır. Ucuz olan sonsuza kadar. Bu projede veriyi yolo26x etiketledi, sonuçla yolo26s eğitildi.
İki düzeltmeden sonra aynı 500 görsel, 254 yerine 412 kullanılabilir görsel verdi.
Bir karar daha not etmeye değer: modelin hiçbir şey bulamadığı görsellerden 25 tanesini bilerek sakladım. Bunlar negatif örnek, yani hiç şişe içermeyen kareler. Araba bardaklığı ürün fotoğrafları mükemmel negatif örnek, çünkü şişeye benziyorlar ama şişe değiller. Her görselinde hedef nesne bulunan bir dataset, modele “her yerde bulunacak bir şey vardır” alışkanlığı kazandırıyor, o da boş sahnelerde hayali tespitler üretiyor.
Tek iş için bir araç
Şimdi insan gereken kısım: 557 kutu, her birinde belki yüzde 85 doğrulukla bir sınıf tahmini, artı kabaca her altı kutudan biri şişe olmayan bir şeyin üzerine çizilmiş.
Buradaki refleks genel amaçlı bir etiketleme uygulaması olurdu, Roboflow ya da Label Studio gibi. İyi araçlar. Ama genel durum için tasarlanmışlar: tüm görseli göster, kullanıcı nesneyi bulsun, kutuya tıklasın, menüden sınıf seçsin. Kutu başına on beş ile yirmi saniye. 557 kutu için üç saate yakın.
Halbuki benim işim genel durum değildi. Kutular zaten çizilmişti ve çoğu doğruydu. Gerçek sorular sadece “bu hangi sınıf” ve “bu kutu çöp mü” idi. Bu üç olası cevap demek, üç cevap da üç tuş demek.
Ben de 509 satırlık, tek dosyalık bir araç yazdım. Sayfayı Python’un kendi http.server’ı sunuyor, Pillow her kutuyu istendiğinde görselden kesiyor, arayüz de aynı dosyanın içinde bir HTML string. Framework yok, ek bağımlılık yok.
Ekranda tek bir kutu var, kesilmiş ve büyütülmüş, etrafında biraz bağlam bırakılmış. Yanında da tüm sahnenin küçük bir paneli duruyor, kırpma tek başına yetmediğinde ona bakılıyor. Sonra:
1 matara 2 pet_sise x bu kutuyu sil
z geri al s atla
Her tuş anında bir JSON dosyasına yazılıyor, yani iş yarıda bırakılıp devam edilebiliyor. Karar başına yirmi saniye yerine iki saniye. Tüm tur yaklaşık yirmi beş dakika sürdü.
Bedeli: kutunun geometrisini düzeltemiyorsun, sadece kötü kutuyu silebiliyorsun. Kutuların dörtte üçü zaten iyiyken bu doğru takas, çünkü kötü bir kutuyu silmek ucuz, yenisini çizmek değil.
Sonradan ikinci bir ekran ekledim: her kutuyu atandığı sınıfa göre üç ızgarada gruplayan bir galeri görünümü. Bu, tek tek bakmanın yapamadığı bir şeyi yapıyor. 208 matarayı yan yana görünce aralarında olmaması gereken üç beş tanesi anında sırıtıyor. Sıralı inceleme tek tek hataları yakalıyor, ızgara incelemesi tutarsızlığı yakalıyor.
556 kararın sonucu: 107 kutu silindi, 66 sınıf düzeltildi, geriye 412 görselde 450 kutu kaldı. 208 matara, 242 pet_sise, artı negatif görevi gören 53 kutusuz görsel.
Eğitim
329 eğitim ve 83 doğrulama görseline bölündükten sonra, asıl eğitim tek bir çağrı:
from ultralytics import YOLO
model = YOLO("yolo26s.pt")
model.train(
data="dataset_final/dataset.yaml",
epochs=100,
imgsz=640,
batch=16,
device=0,
cache=True,
patience=30,
)
İki şey not etmeye değer.
yolo26s.pt boş bir model değil. COCO üzerinde eğitilmiş ağırlıklarla geliyor, yani kenarları, dokuları, şekilleri ve “bir nesne nerede biter” kavramını zaten biliyor. Biz sadece son katmanları iki yeni sınıfa uyarlıyoruz. Bunun adı fine-tuning, ve 329 görselin işe yarar bir şey üretebilmesinin tek sebebi bu. Sıfırdan olsaydı on binlerce görsel gerekirdi.
Doğrulama seti, modelin eğitim sırasında hiç görmediği 83 görsel. “Bu iyi mi” sorusunun tek dürüst cevabı orada, çünkü eğitim verisini ezberlemiş bir model o veride kusursuz görünür.
RTX 3090’da 100 epoch beş dakika üç saniye sürdü.
Eğrilerin söyledikleri
En iyi checkpoint’in nihai sayıları:
| mAP50 | precision | recall | |
|---|---|---|---|
| genel | 0.692 | 0.785 | 0.642 |
| matara | 0.704 | 0.748 | 0.707 |
| pet_sise | 0.679 | 0.822 | 0.577 |
Terimleri açalım, çünkü gerçekten farklı hataları ölçüyorlar:
Precision, model bir şey dediğinde ne sıklıkla haklı olduğu. 0.785, tespitlerin yaklaşık beşte birinin yanlış olması demek.
Recall, var olanın ne kadarını bulabildiği. 0.642, üçte birini kaçırıyor demek. pet_sise için recall 0.577, yani pet şişelerin yüzde 40’ından fazlasını kaçırıyor, ama precision’ı 0.822. Düz Türkçesi: bir şeye pet şişe dediğinde genelde haklı, ama epey pet şişeyi gözden kaçırıyor.
mAP50 (“mean Average Precision at IoU 0.50”) özet skor. Tahmin edilen kutunun gerçek kutuyla en az yüzde 50 örtüşmesi şartıyla hesaplanıyor. mAP50-95 ise aynı ölçümü yüzde 50’den yüzde 95’e kadar farklı örtüşme eşiklerinde ortalıyor, yani kutuların ne kadar sıkı oturduğunu çok daha sıkı sınıyor.
Ve burada eğitim logunda ilginç bir şey vardı:
| epoch | mAP50 | mAP50-95 |
|---|---|---|
| 1 | 0.212 | 0.171 |
| 10 | 0.495 | 0.363 |
| 29 | 0.713 | 0.595 |
| 50 | 0.655 | 0.563 |
| 75 | 0.676 | 0.577 |
| 100 | 0.674 | 0.603 |
mAP50 epoch 29’da zirve yaptı ve bir daha oraya dönmedi. mAP50-95 ise tırmanmaya devam edip en iyisine epoch 81’de ulaştı.
İki metrik farklı şeyleri anlatıyor ve anlaşmayı bıraktılar. Epoch 29’dan sonra model daha fazla şişe bulmuyordu. Zaten bulduklarının etrafına daha sıkı kutular çiziyordu. Gevşek eşikteki performans platoya vurmuşken, sıkı eşikteki performans hâlâ iyileşiyordu.
Bu aynı zamanda patience=30 ayarının neden hiç devreye girmediğini açıklıyor. 30 epoch boyunca iyileşme olmazsa dursun demiştim. Ama Ultralytics erken durdurma için mAP50’yi takip etmiyor, ağırlığı mAP50-95’e verilmiş bileşik bir skoru takip ediyor. O ölçüye göre model hâlâ iyileşiyordu, dolayısıyla eğitim devam etti.
Peki 0.692 iyi mi? Açık konuşmak gerekirse vasat. Çalışıyor, çalıştığını görebiliyorsun, ama production’a çıkacak bir dedektör 0.85 ve üzerini ister. 329 kirli, internetten toplanmış görsel ve toplam iki saatlik emek için kabaca beklediğim yerde. Bunu iyileştirmenin yolu daha büyük model değil, daha çok ve daha temiz veri, özellikle pet_sise recall’ü için.
En çok zaman yiyen hata
Eğitim ilk denemede başlamadı. Şununla çöktü:
RuntimeError: CUDNN_BACKEND_TENSOR_DESCRIPTOR cudnnFinalize failed
ptrDesc->finalize() cudnn_status: CUDNN_STATUS_SUBLIBRARY_VERSION_MISMATCH
cuDNN, NVIDIA’nın sinir ağları için GPU primitifleri kütüphanesi. Donanımda convolution işlemini fiilen yapan şey. Yüklenmezse hiçbir şey eğitilmez.
Sürücü sorunsuzdu, 595.84 ve CUDA 13.2, fazlasıyla yeni. Sorun cuDNN’in o makinede iki kere var olmasıydı. Sistemde /lib/x86_64-linux-gnu altında 9.25 kuruluydu, projenin sanal ortamındaki pip paketi ise 9.20 getiriyordu.
Modern cuDNN tek dosya değil. Alt kütüphanelere bölünmüş durumda: libcudnn_ops, libcudnn_cnn, libcudnn_graph, libcudnn_engines_precompiled ve dahası. Dinamik yükleyici bir kısmını bir yerden, bir kısmını diğerinden çözdü, ve sürümler birbirini tutmadı. Hata mesajı tam olarak bunu söylüyor, yeter ki alt kütüphanenin ne olduğunu biliyor ol.
İki dizini listelemek durumu netleştirdi. Sistem sürümünde pip sürümünde hiç olmayan iki kütüphane vardı:
$ ls /lib/x86_64-linux-gnu/libcudnn*.so.9 | xargs -n1 basename
libcudnn_adv.so.9
libcudnn_cnn.so.9
libcudnn_engines_precompiled.so.9
libcudnn_engines_runtime_compiled.so.9
libcudnn_engines_tensor_ir.so.9 # sadece sistem kopyasında
libcudnn_ext.so.9 # sadece sistem kopyasında
libcudnn_graph.so.9
libcudnn_heuristic.so.9
libcudnn_ops.so.9
libcudnn.so.9
Çözüm bütün alt kütüphaneleri tek tutarlı setten yüklemekti, yani tek satırlık bir environment değişkeni:
export LD_LIBRARY_PATH=/lib/x86_64-linux-gnu
Sistemdekini silmek yerine bilerek ona yönlendirdim. O makine aynı zamanda yerel LLM stack’imi çalıştırıyor, ve alakasız bir projeyi düzeltmek için sistem CUDA kütüphanesini kaldırmak, bir şeyi düzeltirken iki şeyi bozmanın yolu.
Bunun içinde ikinci ve küçük bir tuzak daha vardı. İlk refleksim pip paketini uv pip install -U nvidia-cudnn-cu13 ile yükseltmekti. Çalışmış gibi göründü, sonra torch hâlâ eski sürümü raporladı. Sebebi şu: uv run her çalıştırmada ortamı lock dosyasına göre yeniden senkronluyor, yani elle yaptığım kurulum script daha başlamadan sessizce geri alınıyordu. Bir uv projesinde paketi elle kurmak tutmuyor. uv add ile pyproject.toml’a yazılması gerekiyor.
Çalıştığını görmek
Son adım zaten işin bütün amacıydı: kameraya bir şişe tutup etrafında kutunun belirmesini izlemek.
Modeli çalıştıran makine uzaktan bağlandığım bir Mac mini, dolayısıyla kendi webcam’i bana bir işe yaramıyordu. Onun yerine küçük bir sayfa yazdım: izleyicinin tarayıcı kamerasından kare alıyor, her kareyi sunucuya gönderiyor, dönen kutuları bir canvas katmanına çiziyor. Model Mac mini’de çalışıyor, kamera ise ben neredeysem orada, telefonum dahil.
Tek engel: tarayıcılar kamera API’si olan getUserMedia’yı güvensiz bağlantılarda reddediyor. IP üzerinden düz HTTP ile kameraya erişemiyorsun, kullanıcı neye tıklarsa tıklasın.
Tailscale bunu hiç sertifika işi olmadan çözdü:
tailscale serve --bg 8010
Python sunucusu hâlâ 127.0.0.1:8010’da düz HTTP konuşuyor. Tailscale önüne geçip tailnet için geçerli bir HTTPS ucu sunuyor. İnternete açılan port yok, elle üretilen sertifika yok, ve kamera API’si tatmin olmuş oluyor.
Modelin sonunda nerede çalıştığı da dikkat çekici: eğitim RTX 3090’da oldu ama çıkarım Mac mini’de koşuyor. best.pt 19 MB’lık tek bir dosya. Eğitim bittikten sonra pahalı donanımın resimde işi kalmıyor. Eğitim pahalı ve bir kerelik, çıkarım ucuz ve sürekli.
Tespit 3090’da görsel başına 1.6 ms sürüyor, Mac’te de canlı kamera akışına rahat yetiyor.
Özet
Toplam yedi dosyada 1020 satır, ve uçtan uca yaklaşık iki saat. Çoğu modele değil veriye gitti.
Geriye dönüp baktığımda bu oyuncak problemin ötesine geçen kısımlar şunlar:
-
Kolay olan kısım model. Eğitim tek fonksiyon çağrısı ve beş dakikaydı. Geri kalan her şey 450 doğru kutunun var olmasını sağlamaktı.
-
Hiçbir veriyi silmeden önce ona bak. İki dakikalık bir contact sheet, datasetin yarısına sessizce mal olacak bir hatayı yakaladı. Bakmasaydım hiç öğrenemeyecektim, çünkü eğitim her iki durumda da başarıyla tamamlanırdı.
-
Model boyutunu işe göre seç, alışkanlığa göre değil. Çevrimdışı etiketleme eldeki en büyük modeli kullanmalı. Canlı çıkarım işini gören en küçüğünü. Ben ikisinde de aynı refleksi kullandım ve karşılığında hiçbir şey kazanmadan doğruluk kaybettim.
-
Tek iş yapan bir araç, her işi yapan bir aracı yenebilir. 509 satırlık amaca özel triyaj, üç saatlik genel amaçlı etiketlemeyi yirmi beş dakikaya indirdi.
-
Normalde birlikte hareket eden metrikler, ayrıştıklarında bilgi verir. mAP50 düzleşirken mAP50-95’in yükselmeye devam etmesi, “daha çok nesne bulmak” ile “daha iyi kutu çizmek” arasındaki fark. Eğitim eğrisi modelin tam olarak ne zaman birinden diğerine geçtiğini gösterdi.
Dedektör yayınlanacak kalitede değil, zaten hiç olmayacaktı. Ama çalışıyor, bir öğleden sonra sürdü, ve neden çalıştığının her parçası görünür durumda.