Hugging Face ile Nesne Tespiti Modeli Kullanımı

Makine öğrenmesi modellerini production ortamına almak, çoğu zaman teoriden çok daha karmaşık bir iş. Ancak nesne tespiti (object detection) söz konusu olduğunda, Hugging Face ekosistemi bu karmaşıklığın büyük bir kısmını soyutluyor. Geçen ay bir e-ticaret müşterimizin sunucularında ürün görseli sınıflandırma pipeline’ı kurdum ve bu süreçte edindiğim deneyimleri paylaşmak istedim. YOLO tabanlı modeller mi kullanacaksınız, DETR mi, yoksa GroundingDINO gibi daha yeni mimariler mi? Hangi modelin nerede işe yaradığını, deployment sırasında nelere dikkat etmek gerektiğini ve performans tuzaklarını anlatacağım.

Ortam Hazırlığı ve Gereksinimler

Başlamadan önce, GPU’lu bir sunucu mu yoksa CPU tabanlı bir ortam mı kullandığınıza karar vermeniz gerekiyor. Test ve geliştirme için CPU yeterli olsa da production’da ciddi gecikme sorunları yaşarsınız. Ben genellikle geliştirme ortamında CPU, staging ve production’da CUDA destekli GPU kullanıyorum.

Önce Python sanal ortamı oluşturup gerekli paketleri yükleyelim:

# Python sanal ortamı oluştur
python3 -m venv hf-detection-env
source hf-detection-env/bin/activate

# Temel bağımlılıkları yükle
pip install transformers torch torchvision
pip install Pillow requests accelerate
pip install timm  # Bazı modeller için gerekli

# CUDA kontrolü
python3 -c "import torch; print(f'CUDA mevcut: {torch.cuda.is_available()}')"
python3 -c "import torch; print(f'CUDA versiyonu: {torch.version.cuda}')"

Eğer CUDA çıktısı False dönüyorsa ve GPU’nuz varsa, nvidia-smi komutunu çalıştırıp sürücü durumunu kontrol edin. PyTorch CUDA versiyonunun sürücünüzle uyumlu olması şart. CUDA 11.8 için özel bir kurulum gerekebilir:

# CUDA 11.8 için PyTorch kurulumu
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118

# Kurulumu doğrula
python3 -c "import torch; print(torch.cuda.get_device_name(0))"

İlk Model: DETR ile Nesne Tespiti

Hugging Face üzerinde en yaygın kullanılan nesne tespit modellerinden biri Facebook’un DETR (Detection Transformer) modelidir. COCO dataset üzerinde eğitilmiş bu model, 80 farklı nesne kategorisini tanıyabiliyor. Basit bir script ile başlayalım:

# Basit DETR inferans scripti - detect_objects.py
cat > detect_objects.py << 'EOF'
from transformers import DetrImageProcessor, DetrForObjectDetection
import torch
from PIL import Image
import requests
import sys

# Model ve processor yükleme
model_name = "facebook/detr-resnet-50"
processor = DetrImageProcessor.from_pretrained(model_name)
model = DetrForObjectDetection.from_pretrained(model_name)

# GPU varsa kullan
device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)
model.eval()

def detect_objects(image_path_or_url, threshold=0.9):
    # Görseli yükle (URL veya lokal dosya)
    if image_path_or_url.startswith("http"):
        image = Image.open(requests.get(image_path_or_url, stream=True).raw)
    else:
        image = Image.open(image_path_or_url)
    
    # RGB'ye dönüştür (PNG'lerde alpha kanalı sorun çıkarabilir)
    image = image.convert("RGB")
    
    # Görüntüyü işle
    inputs = processor(images=image, return_tensors="pt")
    inputs = {k: v.to(device) for k, v in inputs.items()}
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    # Sonuçları decode et
    target_sizes = torch.tensor([image.size[::-1]])
    results = processor.post_process_object_detection(
        outputs, 
        target_sizes=target_sizes, 
        threshold=threshold
    )[0]
    
    detections = []
    for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
        box = [round(i, 2) for i in box.tolist()]
        detections.append({
            "label": model.config.id2label[label.item()],
            "confidence": round(score.item(), 3),
            "box": box
        })
    
    return detections

# Test
image_url = "http://images.cocodataset.org/val2017/000000039769.jpg"
results = detect_objects(image_url, threshold=0.85)

for det in results:
    print(f"[{det['confidence']:.2%}] {det['label']:15} -> bbox: {det['box']}")

EOF
python3 detect_objects.py

İlk çalıştırmada model otomatik olarak ~/.cache/huggingface/hub/ dizinine indirilir. DETR-ResNet-50 modeli yaklaşık 160 MB tutuyor. Kurumsal ortamlarda bu cache dizinini merkezi bir NFS mount’a ya da shared storage’a almak mantıklı olabilir.

Cache Yönetimi ve Offline Kullanım

Production ortamında internete erişimi kısıtlı sunucular çok yaygın. Modeli bir kez indirip sonrasında offline kullanmak için şu yaklaşımı benimsiyorum:

# Modeli lokal dizine açıkça kaydet
python3 << 'EOF'
from transformers import DetrImageProcessor, DetrForObjectDetection

model_name = "facebook/detr-resnet-50"
save_path = "/opt/ml-models/detr-resnet-50"

print(f"Model indiriliyor: {model_name}")
processor = DetrImageProcessor.from_pretrained(model_name)
model = DetrForObjectDetection.from_pretrained(model_name)

# Lokal dizine kaydet
processor.save_pretrained(save_path)
model.save_pretrained(save_path)
print(f"Model kaydedildi: {save_path}")
print(f"Boyut: $(du -sh {save_path})")
EOF

# Kaydedilen dizinin boyutunu kontrol et
du -sh /opt/ml-models/detr-resnet-50/

# Offline ortamda yükle
python3 << 'EOF'
from transformers import DetrImageProcessor, DetrForObjectDetection

local_path = "/opt/ml-models/detr-resnet-50"
processor = DetrImageProcessor.from_pretrained(local_path, local_files_only=True)
model = DetrForObjectDetection.from_pretrained(local_path, local_files_only=True)
print("Model offline olarak yüklendi.")
EOF

local_files_only=True parametresi kritik. Bunu belirtmezseniz, model zaten lokal’de olsa bile Hugging Face Hub’a token kontrolü için bağlanmaya çalışabilir. Firewall’lı ortamlarda bu, gereksiz timeout beklemelerine yol açar.

YOLOv8 ile Karşılaştırmalı Yaklaşım

Hugging Face üzerinde artık Ultralytics’in YOLOv8 modelleri de mevcut. DETR Transformer tabanlı daha doğruyken, YOLO gerçek zamanlı uygulamalar için çok daha hızlı. Ben ikisini de test ediyorum ve ihtiyaca göre seçiyorum:

# YOLOv8 için ultralytics kütüphanesi gerekli
pip install ultralytics

# Hugging Face'den YOLOv8 modelini kullan
cat > yolo_detect.py << 'EOF'
from ultralytics import YOLO
import sys

# Hugging Face Hub'dan model yükleme
# Alternatif: yerel ağırlık dosyası da kullanılabilir
model = YOLO("yolov8n.pt")  # nano - en hafif versiyon
# model = YOLO("yolov8x.pt")  # extra large - en doğru versiyon

def yolo_detect(image_path, conf_threshold=0.25):
    results = model(image_path, conf=conf_threshold, verbose=False)
    
    detections = []
    for r in results:
        for box in r.boxes:
            detections.append({
                "label": model.names[int(box.cls)],
                "confidence": float(box.conf),
                "box": box.xyxy[0].tolist()
            })
    
    return detections

# Performans testi
import time
test_image = sys.argv[1] if len(sys.argv) > 1 else "test.jpg"

start = time.time()
results = yolo_detect(test_image)
elapsed = time.time() - start

print(f"İşlem süresi: {elapsed:.3f}s")
for det in results:
    print(f"[{det['confidence']:.2%}] {det['label']}")
EOF

Pratikte şunu gördüm: E-ticaret senaryosunda, ürün görseli analizi için DETR daha doğru sonuç verirken, güvenlik kamerası görüntülerinden gerçek zamanlı insan tespiti için YOLOv8n çok daha pratik bir seçenek. Gecikme farkı ciddi: DETR CPU’da bir görüntüde 2-4 saniye, YOLOv8n aynı ortamda 200-400ms.

Batch İşleme ile Verimlilik

Tek tek görüntü işlemek yerine batch işleme yaparak GPU kullanımını optimize etmek mümkün. Özellikle bir dizindeki yüzlerce görüntüyü işlemeniz gerekiyorsa bu yaklaşım çok işe yarıyor:

cat > batch_detect.py << 'EOF'
from transformers import DetrImageProcessor, DetrForObjectDetection
import torch
from PIL import Image
import os
import json
from pathlib import Path

model_name = "/opt/ml-models/detr-resnet-50"
processor = DetrImageProcessor.from_pretrained(model_name, local_files_only=True)
model = DetrForObjectDetection.from_pretrained(model_name, local_files_only=True)

device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)
model.eval()

def process_batch(image_paths, batch_size=8, threshold=0.85):
    all_results = {}
    
    for i in range(0, len(image_paths), batch_size):
        batch_paths = image_paths[i:i + batch_size]
        images = []
        valid_paths = []
        
        for path in batch_paths:
            try:
                img = Image.open(path).convert("RGB")
                images.append(img)
                valid_paths.append(path)
            except Exception as e:
                print(f"UYARI: {path} açılamadı: {e}")
                continue
        
        if not images:
            continue
        
        # Batch olarak işle
        inputs = processor(images=images, return_tensors="pt", padding=True)
        inputs = {k: v.to(device) for k, v in inputs.items()}
        
        with torch.no_grad():
            outputs = model(**inputs)
        
        # Her görüntü için sonuçları işle
        target_sizes = torch.tensor([img.size[::-1] for img in images])
        batch_results = processor.post_process_object_detection(
            outputs, 
            target_sizes=target_sizes, 
            threshold=threshold
        )
        
        for path, result in zip(valid_paths, batch_results):
            detections = []
            for score, label, box in zip(result["scores"], result["labels"], result["boxes"]):
                detections.append({
                    "label": model.config.id2label[label.item()],
                    "confidence": round(score.item(), 3),
                    "box": [round(x, 1) for x in box.tolist()]
                })
            all_results[str(path)] = detections
        
        print(f"İşlendi: {min(i + batch_size, len(image_paths))}/{len(image_paths)}")
    
    return all_results

# Kullanım
image_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("./images")
image_files = list(image_dir.glob("*.jpg")) + list(image_dir.glob("*.png"))

print(f"Toplam görüntü: {len(image_files)}")
results = process_batch(image_files, batch_size=4)

# Sonuçları JSON'a kaydet
output_file = "detection_results.json"
with open(output_file, "w", encoding="utf-8") as f:
    json.dump(results, f, ensure_ascii=False, indent=2)

print(f"Sonuçlar kaydedildi: {output_file}")
EOF

Batch size’ı ayarlarken GPU belleğinizi göz önünde bulundurun. 8GB VRAM ile batch_size=8 genellikle güvenli. 4GB VRAM’de batch_size=4’e düşürmenizi tavsiye ederim. Out of memory hatası alırsanız şu şekilde dinamik bir çözüm ekleyebilirsiniz:

# OOM hatası durumunda batch boyutunu otomatik küçülten wrapper
python3 << 'EOF'
import torch

def safe_batch_inference(model, processor, images, device, initial_batch_size=8):
    batch_size = initial_batch_size
    
    while batch_size >= 1:
        try:
            inputs = processor(images=images[:batch_size], return_tensors="pt", padding=True)
            inputs = {k: v.to(device) for k, v in inputs.items()}
            
            with torch.no_grad():
                outputs = model(**inputs)
            
            print(f"Başarılı batch size: {batch_size}")
            return outputs, batch_size
            
        except torch.cuda.OutOfMemoryError:
            print(f"OOM hatası, batch_size={batch_size} -> {batch_size // 2} deneniyor")
            torch.cuda.empty_cache()
            batch_size = batch_size // 2
    
    raise RuntimeError("Batch size 1'de bile OOM hatası. GPU belleği yetersiz.")

print("Safe batch inference fonksiyonu hazır.")
EOF

REST API ile Servis Haline Getirme

Production ortamında modeli FastAPI ile bir servis olarak sunmak en yaygın yaklaşım. Systemd servisi olarak çalıştırmak da işleri kolaylaştırıyor:

# FastAPI servisini kur
pip install fastapi uvicorn python-multipart

# Servis dosyası oluştur
cat > /opt/detection-service/app.py << 'EOF'
from fastapi import FastAPI, File, UploadFile, HTTPException
from transformers import DetrImageProcessor, DetrForObjectDetection
import torch
from PIL import Image
import io
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI(title="Nesne Tespit Servisi")

# Başlangıçta model yükle
MODEL_PATH = "/opt/ml-models/detr-resnet-50"
device = "cuda" if torch.cuda.is_available() else "cpu"

logger.info(f"Model yükleniyor... Device: {device}")
processor = DetrImageProcessor.from_pretrained(MODEL_PATH, local_files_only=True)
model = DetrForObjectDetection.from_pretrained(MODEL_PATH, local_files_only=True)
model = model.to(device)
model.eval()
logger.info("Model hazır.")

@app.post("/detect")
async def detect_objects(
    file: UploadFile = File(...),
    threshold: float = 0.85
):
    if not file.content_type.startswith("image/"):
        raise HTTPException(status_code=400, detail="Sadece görüntü dosyaları kabul edilir.")
    
    contents = await file.read()
    image = Image.open(io.BytesIO(contents)).convert("RGB")
    
    inputs = processor(images=image, return_tensors="pt")
    inputs = {k: v.to(device) for k, v in inputs.items()}
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    target_sizes = torch.tensor([image.size[::-1]])
    results = processor.post_process_object_detection(
        outputs, target_sizes=target_sizes, threshold=threshold
    )[0]
    
    detections = []
    for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
        detections.append({
            "label": model.config.id2label[label.item()],
            "confidence": round(score.item(), 3),
            "bbox": [round(x, 1) for x in box.tolist()]
        })
    
    return {"detections": detections, "count": len(detections)}

@app.get("/health")
async def health_check():
    return {"status": "ok", "device": device, "model": MODEL_PATH}
EOF

# Systemd service dosyası
cat > /etc/systemd/system/detection-service.service << 'EOF'
[Unit]
Description=Nesne Tespit API Servisi
After=network.target

[Service]
Type=simple
User=mlservice
WorkingDirectory=/opt/detection-service
Environment="PATH=/opt/hf-detection-env/bin"
ExecStart=/opt/hf-detection-env/bin/uvicorn app:app --host 0.0.0.0 --port 8080 --workers 1
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable detection-service
systemctl start detection-service
systemctl status detection-service

Worker sayısını 1’de tutmanızı şiddetle tavsiye ederim. Her worker kendi model kopyasını belleğe yükler. 2 worker ile bellek tüketimi ikiye katlanır. Yüksek istek sayısı için workers artırmak yerine birden fazla servis instance’ı arkasına bir load balancer koymak çok daha sağlıklı.

Performans Profilleme ve İzleme

Servisi production’a almadan önce performans profilini çıkarmak kritik. Ben genellikle şu basit yük testi scriptini kullanıyorum:

# Basit yük testi
cat > load_test.sh << 'EOF'
#!/bin/bash
SERVICE_URL="http://localhost:8080/detect"
TEST_IMAGE="./test_image.jpg"
CONCURRENT=5
REQUESTS=50

echo "Yük testi başlıyor..."
echo "Concurrent: $CONCURRENT, Toplam istek: $REQUESTS"

for i in $(seq 1 $REQUESTS); do
    (
        START=$(date +%s%3N)
        HTTP_CODE=$(curl -s -o /tmp/response_$i.json 
            -w "%{http_code}" 
            -X POST 
            -F "file=@${TEST_IMAGE}" 
            "${SERVICE_URL}?threshold=0.85")
        END=$(date +%s%3N)
        ELAPSED=$((END - START))
        echo "İstek $i: HTTP $HTTP_CODE, Süre: ${ELAPSED}ms"
    ) &
    
    # Her CONCURRENT istekte bir bekle
    if [ $((i % CONCURRENT)) -eq 0 ]; then
        wait
    fi
done

wait
echo "Yük testi tamamlandı."
EOF
chmod +x load_test.sh
./load_test.sh

Gerçek Dünya Senaryosu: Depo Envanter Takibi

Müşterimizin deposunda forklift ve palet tespiti için kurduğumuz sistemden bahsedeyim. Güvenlik kameralarından gelen RTSP stream’lerini işlemek için şu yaklaşımı kullandık:

# RTSP stream'den frame yakala ve işle
pip install opencv-python

cat > rtsp_detector.py << 'EOF'
import cv2
import torch
from transformers import DetrImageProcessor, DetrForObjectDetection
from PIL import Image
import time
import sys

# Hedef sınıflar - COCO'dan ilgili olanlar
TARGET_LABELS = {"person", "car", "truck", "forklift"}

model_path = "/opt/ml-models/detr-resnet-50"
processor = DetrImageProcessor.from_pretrained(model_path, local_files_only=True)
model = DetrForObjectDetection.from_pretrained(model_path, local_files_only=True)
device = "cuda" if torch.cuda.is_available() else "cpu"
model = model.to(device)
model.eval()

rtsp_url = sys.argv[1] if len(sys.argv) > 1 else "rtsp://camera_ip:554/stream"
cap = cv2.VideoCapture(rtsp_url)

FRAME_SKIP = 10  # Her 10 frame'de bir tespit yap
frame_count = 0
last_detection_time = 0

print(f"Stream açıldı: {rtsp_url}")
print(f"Device: {device}")

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        print("Frame okunamadı, yeniden bağlanılıyor...")
        time.sleep(2)
        cap = cv2.VideoCapture(rtsp_url)
        continue
    
    frame_count += 1
    if frame_count % FRAME_SKIP != 0:
        continue
    
    # BGR -> RGB dönüşümü
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
    image = Image.fromarray(rgb_frame)
    
    inputs = processor(images=image, return_tensors="pt")
    inputs = {k: v.to(device) for k, v in inputs.items()}
    
    with torch.no_grad():
        outputs = model(**inputs)
    
    target_sizes = torch.tensor([image.size[::-1]])
    results = processor.post_process_object_detection(
        outputs, target_sizes=target_sizes, threshold=0.80
    )[0]
    
    current_time = time.time()
    for score, label, box in zip(results["scores"], results["labels"], results["boxes"]):
        label_name = model.config.id2label[label.item()]
        if label_name in TARGET_LABELS:
            print(f"[{current_time:.0f}] TESPIT: {label_name} ({score:.2%}) - Frame: {frame_count}")

cap.release()
EOF

Bu senaryoda FRAME_SKIP parametresi kritik. Tüm frame’leri işlemek CPU/GPU’yu boğar. Güvenlik kamerası senaryosunda her 10 frame’de bir tespit yapmak genellikle yeterli, ancak hız tespiti gibi kritik uygulamalarda bu değeri düşürmeniz gerekebilir.

Sık Karşılaşılan Hatalar ve Çözümleri

Pratik deneyimlerimden derlediğim en sık karşılaşılan problemler:

“Model is not found” hatası: local_files_only=True kullanırken model yolunun tam olarak doğru yazıldığından emin olun. Symlink kullanıyorsanız, transformers bazen symlink’leri düzgün çözemez. Gerçek dizin yolunu kullanın.

“RuntimeError: Expected all tensors to be on the same device”: Model GPU’da ama input tensörleriniz CPU’da. inputs = {k: v.to(device) for k, v in inputs.items()} satırını unutmayın.

Görüntü boyutu kaynaklı OOM: Çok büyük görüntüler (4K+) işlenirken bellek taşabilir. Processor’a max_size parametresi verin:

python3 << 'EOF'
from transformers import DetrImageProcessor

# Maksimum 800px ile sınırla
processor = DetrImageProcessor.from_pretrained(
    "/opt/ml-models/detr-resnet-50",
    size={"shortest_edge": 480, "longest_edge": 800},
    local_files_only=True
)
print("Processor boyut kısıtlamasıyla yüklendi.")
EOF

Hugging Face token gerektiren modeller: Bazı özel modeller için HF_TOKEN environment variable’ı gerekir. Bunu systemd service dosyanıza ekleyin: Environment="HF_TOKEN=hf_xxxx". Token’ı asla kaynak koduna gömmeyın.

Model indirme proxy arkasında çalışmıyor: Kurumsal ağlarda HTTP_PROXY ve HTTPS_PROXY environment variable’larını ayarlayın ve Hugging Face Hub için proxy exception listesine huggingface.co ekleyin.

Sonuç

Hugging Face ekosistemi, nesne tespiti gibi karmaşık ML iş yüklerini production’a almayı gerçekten demokratikleştirdi. Ancak “notebook’ta çalıştı” ile “production’da güvenilir çalışıyor” arasındaki mesafe hala ciddi. Cache yönetimi, offline kullanım, bellek optimizasyonu ve servis izleme konularını baştan doğru planlamak, ilerleyen dönemde büyük headache’lerden kurtarıyor.

Model seçiminde kural basit: Doğruluk öncelikliyse DETR veya benzeri Transformer tabanlı modeller, hız öncelikliyse YOLOv8. Batch işleme ile GPU kullanımını optimize edin ve her zaman modeli lokal’e kayıt edip local_files_only=True ile kullanın. Sistemd üzerinden servis olarak çalıştırmak, yeniden başlatma ve log yönetimi açısından hayat kurtarıyor.

Sonraki adım olarak model quantization ve TensorRT optimizasyonuna bakmanızı tavsiye ederim. Özellikle INT8 quantization ile DETR’de %40-60 hız artışı elde ettim, doğruluk kaybı ise yüzde birkaç puanla sınırlı kaldı.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir