Sıfırdan Model Eğitimi: Hugging Face Trainer API Kullanım Rehberi

Makine öğrenmesi dünyasında “model eğitimi” denince akla hemen karmaşık matematiğin, onlarca bağımlılığın ve saatlerce süren denemeler yanılmalar gelir. Hugging Face’in Trainer API’si bu süreci önemli ölçüde sadeleştiriyor, ama bu “kolaylaştı, artık anlamak gerekmiyor” anlamına gelmiyor. Aksine, ne yaptığını bilmeden Trainer’ı kullanmak sizi çok daha hızlı bir şekilde duvara çarptırır. Bu yazıda sıfırdan bir model eğitim sürecini, gerçek dünyada karşılaştığım sorunlar ve çözümleriyle birlikte ele alacağım.

Ortam Kurulumu: Temel Olmadan Devam Etme

Trainer API’ye geçmeden önce ortamın doğru kurulduğundan emin olmak şart. Bunu atladığınızda sonradan “neden GPU kullanılmıyor” veya “neden mixed precision çalışmıyor” diye saatlerce uğraşırsınız.

# Conda ortamı oluştur
conda create -n hf_trainer python=3.10 -y
conda activate hf_trainer

# Temel paketleri kur
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
pip install transformers datasets evaluate accelerate
pip install scikit-learn tensorboard

# CUDA erişimini doğrula
python -c "import torch; print(f'CUDA: {torch.cuda.is_available()}, Cihaz: {torch.cuda.get_device_name(0)}')"

Burada dikkat edilmesi gereken nokta: PyTorch ve CUDA versiyonlarının uyumu. CUDA 11.8 için cu118, CUDA 12.1 için cu121 kullanmanız gerekiyor. Bunu yanlış kurduğunuzda Trainer sessiz sedasız CPU’ya düşer, siz de neden bu kadar yavaş diye hayrete düşersiniz.

Veri Seti Hazırlama: Trainer’ın Asıl Gücü Burada

Trainer API’nin en güzel taraflarından biri Hugging Face datasets kütüphanesiyle kusursuz entegrasyon. Ama kendi verinizi getiriyorsanız bazı kurallara uymak zorundasınız.

from datasets import Dataset, DatasetDict
import pandas as pd

# CSV'den kendi veri setinizi oluşturun
df = pd.read_csv("metin_siniflandirma.csv")  # 'text' ve 'label' kolonları

# Dataset nesnesine dönüştür
dataset = Dataset.from_pandas(df)

# Train/validation split
split = dataset.train_test_split(test_size=0.15, seed=42)
dataset_dict = DatasetDict({
    "train": split["train"],
    "validation": split["test"]
})

print(dataset_dict)
# DatasetDict({
#     train: Dataset({features: ['text', 'label'], num_rows: 8500})
#     validation: Dataset({features: ['text', 'label'], num_rows: 1500})
# })

Gerçek projede karşılaştığım bir senaryo paylaşayım: Türkçe müşteri yorumları üzerinde duygu analizi yapıyorduk. Veri setinin yüzde 80’i olumlu, yüzde 20’si olumsuzdu. Bu dengesizliği fark etmeden eğitimi başlatırsanız model her şeyi “olumlu” tahmin edip yüzde 80 doğrulukla sizi yanıltır. Bunun çözümü için stratified split şart:

from sklearn.model_selection import train_test_split

# Stratified split için sklearn kullan
train_texts, val_texts, train_labels, val_labels = train_test_split(
    df["text"].tolist(),
    df["label"].tolist(),
    test_size=0.15,
    stratify=df["label"].tolist(),
    random_state=42
)

train_dataset = Dataset.from_dict({"text": train_texts, "label": train_labels})
val_dataset = Dataset.from_dict({"text": val_texts, "label": val_labels})

Tokenizasyon: Doğru Yapılmazsa Her Şey Çöker

Tokenizasyon adımı çoğu zaman geçiştirilen ama kritik bir süreç. Özellikle max_length parametresi burada hayat kurtarır veya batırır.

from transformers import AutoTokenizer

model_checkpoint = "dbmdz/bert-base-turkish-cased"
tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

def tokenize_function(examples):
    return tokenizer(
        examples["text"],
        padding="max_length",
        truncation=True,
        max_length=128,
        return_tensors=None  # Dataset için None bırak
    )

# map() ile tüm veri setine uygula
tokenized_train = train_dataset.map(
    tokenize_function,
    batched=True,
    batch_size=1000,
    remove_columns=["text"]
)

tokenized_val = val_dataset.map(
    tokenize_function,
    batched=True,
    batch_size=1000,
    remove_columns=["text"]
)

# Hugging Face Trainer için format ayarla
tokenized_train.set_format("torch", columns=["input_ids", "attention_mask", "label"])
tokenized_val.set_format("torch", columns=["input_ids", "attention_mask", "label"])

max_length konusunda pratik bir not: Eğitim verinizin token dağılımını analiz etmeden bunu seçmeyin. 512 koysanız da verilerinizin yüzde 95’i 100 token altındaysa hem bellek israf edersiniz hem eğitim yavaşlar. Dağılımı analiz etmek için şunu çalıştırın:

# Token uzunluk dağılımını incele
lengths = [len(tokenizer.encode(text)) for text in df["text"].tolist()]
import numpy as np
print(f"Ortalama: {np.mean(lengths):.0f}")
print(f"95. yüzdelik: {np.percentile(lengths, 95):.0f}")
print(f"Maksimum: {max(lengths)}")

Model Yükleme ve Yapılandırma

from transformers import AutoModelForSequenceClassification

num_labels = len(df["label"].unique())
id2label = {0: "olumsuz", 1: "nötr", 2: "olumlu"}
label2id = {v: k for k, v in id2label.items()}

model = AutoModelForSequenceClassification.from_pretrained(
    model_checkpoint,
    num_labels=num_labels,
    id2label=id2label,
    label2id=label2id
)

# Parametre sayısını kontrol et
total_params = sum(p.numel() for p in model.parameters())
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Toplam parametre: {total_params:,}")
print(f"Eğitilebilir parametre: {trainable_params:,}")

Eğer frozen backbone ile sadece classification head’i eğitmek istiyorsanız, ki bu transfer learning’in klasik yaklaşımı:

# Backbone'u dondur, sadece sınıflandırma katmanını eğit
for name, param in model.bert.named_parameters():
    param.requires_grad = False

# Doğrulama
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Eğitilebilir parametre (frozen backbone): {trainable:,}")

TrainingArguments: İşin Kalbi

TrainingArguments sınıfı onlarca parametre alıyor. Hepsini ezberlemenize gerek yok ama hangi parametrenin ne işe yaradığını anlamak kritik. Gereksiz parametreleri geçiştirmek yerine her birini açıklayarak geçeceğim:

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./turkce-duygu-modeli",

    # Eğitim süresi
    num_train_epochs=5,
    per_device_train_batch_size=32,
    per_device_eval_batch_size=64,

    # Öğrenme oranı ayarları
    learning_rate=2e-5,
    weight_decay=0.01,
    warmup_ratio=0.1,           # İlk %10'da LR yavaşça artır

    # Değerlendirme stratejisi
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    metric_for_best_model="f1",
    greater_is_better=True,

    # Performans optimizasyonları
    fp16=True,                   # Mixed precision - CUDA gerektirir
    dataloader_num_workers=4,
    group_by_length=True,        # Benzer uzunluktaki örnekleri grupla (padding azalır)

    # Loglama
    logging_dir="./logs",
    logging_steps=50,
    report_to="tensorboard",

    # Deterministik sonuçlar için
    seed=42,
    data_seed=42
)

Burada önemli bir nokta: group_by_length=True parametresi padding miktarını azaltarak eğitimi ciddi ölçüde hızlandırır. Özellikle değişken uzunluklu metin verilerinde bunu açmayı ihmal etmeyin.

warmup_ratio konusunda da bir deneyim aktarayım: BERT tabanlı modellerde öğrenme oranını baştan tam vermek modelin pre-trained ağırlıklarını “unutmasına” yol açabilir. Warmup ile başlangıçta düşük LR kullanarak bu riski azaltırsınız.

Metrik Fonksiyonu Tanımlama

Trainer API varsayılan olarak loss değerini izler, ama classification görevlerinde F1 ve accuracy olmadan değerlendirme eksik kalır:

import evaluate
import numpy as np

accuracy_metric = evaluate.load("accuracy")
f1_metric = evaluate.load("f1")

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)

    accuracy = accuracy_metric.compute(
        predictions=predictions,
        references=labels
    )

    f1 = f1_metric.compute(
        predictions=predictions,
        references=labels,
        average="weighted"  # Dengesiz sınıflar için weighted kullan
    )

    return {
        "accuracy": accuracy["accuracy"],
        "f1": f1["f1"]
    }

Trainer’ı Başlatma ve Eğitim

from transformers import Trainer, EarlyStoppingCallback

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_train,
    eval_dataset=tokenized_val,
    tokenizer=tokenizer,
    compute_metrics=compute_metrics,
    callbacks=[
        EarlyStoppingCallback(
            early_stopping_patience=2,    # 2 epoch iyileşmezse dur
            early_stopping_threshold=0.001
        )
    ]
)

# Eğitimi başlat
print("Eğitim başlıyor...")
train_result = trainer.train()

# Eğitim istatistiklerini kaydet
trainer.save_model()
trainer.log_metrics("train", train_result.metrics)
trainer.save_metrics("train", train_result.metrics)
trainer.save_state()

Eğitim Sonrası Değerlendirme ve Model Kaydetme

Eğitim bitti, ama iş bitmedi. Validation seti üzerinde final değerlendirme ve modeli doğru kaydetmek kritik:

# Validation seti üzerinde değerlendir
eval_metrics = trainer.evaluate()
print(f"Validation F1: {eval_metrics['eval_f1']:.4f}")
print(f"Validation Accuracy: {eval_metrics['eval_accuracy']:.4f}")
trainer.log_metrics("eval", eval_metrics)
trainer.save_metrics("eval", eval_metrics)

# Modeli ve tokenizer'ı kaydet
model.save_pretrained("./final-model")
tokenizer.save_pretrained("./final-model")

print("Model kaydedildi: ./final-model/")

Kaydedilen modeli daha sonra kullanmak için:

from transformers import pipeline

classifier = pipeline(
    "text-classification",
    model="./final-model",
    tokenizer="./final-model",
    device=0  # GPU kullan, CPU için -1
)

test_texts = [
    "Ürün çok kaliteli, kesinlikle tavsiye ederim",
    "Kargo çok geç geldi, hayal kırıklığı yaşadım",
    "Fiyatına göre idare eder"
]

results = classifier(test_texts)
for text, result in zip(test_texts, results):
    print(f"Metin: {text}")
    print(f"Tahmin: {result['label']} (Güven: {result['score']:.3f})n")

Çoklu GPU ile Eğitim: Distributed Training

Tek GPU yetmediğinde accelerate kütüphanesi devreye giriyor. Trainer API bunu neredeyse otomatik hallediyor, ama yapılandırma adımı atlanmamalı:

# Accelerate yapılandırmasını başlat
accelerate config

# Yapılandırmayı doğrula
accelerate test

# Çoklu GPU ile eğitimi başlat
accelerate launch --num_processes=4 train.py

train.py dosyanızda kod değişikliği neredeyse sıfır, sadece TrainingArguments içindeki ddp_find_unused_parameters parametresini false yapmanız yeterli. Trainer bu geçişi sizin yerinize yönetiyor.

Yaygın Hatalar ve Çözümleri

Yıllar içinde gördüğüm en sık hataları ve çözümlerini madde madde aktarayım:

CUDA Out of Memory hatası:

  • per_device_train_batch_size değerini yarıya indirin
  • gradient_accumulation_steps=4 ekleyerek efektif batch size’ı koruyun
  • fp16=True veya bf16=True aktif edin

Eğitim loss düşüyor ama validation loss artıyor:

  • Overfitting oluyor, weight_decay değerini artırın (0.01’den 0.1’e)
  • Dropout ekleyin veya mevcut dropout oranını artırın
  • Veri artırma (augmentation) tekniklerini deneyin

Loss hiç düşmüyor, NaN görüyorsunuz:

  • learning_rate çok yüksek olabilir, 10 kat düşürün
  • max_grad_norm=1.0 parametresini TrainingArguments‘a ekleyin
  • Verideki NaN değerlerini kontrol edin

Eğitim çok yavaş:

  • dataloader_num_workers değerini CPU core sayısına göre ayarlayın
  • group_by_length=True açık mı kontrol edin
  • fp16=True aktif mi kontrol edin

TensorBoard ile İzleme

Eğitimi kör uçmadan takip etmek için TensorBoard’u mutlaka kullanın:

# Eğitim devam ederken başka bir terminal açıp çalıştırın
tensorboard --logdir=./logs --port=6006

# Uzak sunucuda çalışıyorsanız SSH tunnel kurun
ssh -L 6006:localhost:6006 kullanici@sunucu_ip

Metrikler için neye bakacaksınız:

  • train/loss: Sürekli düşmeli, ani sıçramalar veri sorununa işaret eder
  • eval/loss: Train loss ile arasındaki makas açılıyorsa overfitting var
  • eval/f1: En önemli metriğiniz budur, bu değer üzerinden karar verin
  • train/learning_rate: Warmup sonrası yavaş düşüşü görmeli, ani değişimler sorun işareti

Hyperparameter Tuning: Sistematik Yaklaşım

Parametreleri deneme yanılmayla bulmak yerine sistematik yaklaşım benimseyin. Optuna entegrasyonu Trainer API’de yerleşik geliyor:

def model_init(trial=None):
    return AutoModelForSequenceClassification.from_pretrained(
        model_checkpoint,
        num_labels=num_labels
    )

def hp_space(trial):
    return {
        "learning_rate": trial.suggest_float("learning_rate", 1e-5, 5e-5, log=True),
        "per_device_train_batch_size": trial.suggest_categorical(
            "per_device_train_batch_size", [16, 32, 64]
        ),
        "weight_decay": trial.suggest_float("weight_decay", 0.0, 0.3),
        "warmup_ratio": trial.suggest_float("warmup_ratio", 0.0, 0.2)
    }

# Hyperparameter search başlat
best_run = trainer.hyperparameter_search(
    direction="maximize",
    backend="optuna",
    hp_space=hp_space,
    n_trials=10,
    compute_objective=lambda metrics: metrics["eval_f1"]
)

print(f"En iyi parametreler: {best_run.hyperparameters}")

Bu yaklaşımla 10 farklı parametre kombinasyonunu otomatik test edebilir ve en iyi sonucu veren konfigürasyonu bulabilirsiniz. Production’a geçmeden önce bu adımı atlamayın.

Modeli Hugging Face Hub’a Yükleme

Modeli Hub’a yüklemek hem paylaşım hem de deployment açısından kolaylık sağlar:

# HF token ile giriş yap
huggingface-cli login

# Token'ı ortam değişkeni olarak da verebilirsiniz
export HF_TOKEN="hf_xxxxxxxxxxxxx"
# Hub'a yükle
trainer.push_to_hub(
    commit_message="Türkçe duygu analizi modeli - BERT based",
    language="tr",
    finetuned_from=model_checkpoint,
    tags=["text-classification", "turkish", "sentiment-analysis"]
)

# Ya da doğrudan model nesnesiyle
model.push_to_hub("kullanici-adiniz/turkce-duygu-modeli")
tokenizer.push_to_hub("kullanici-adiniz/turkce-duygu-modeli")

Sonuç

Trainer API, doğru kullanıldığında araştırmacının ve sysadmin’in hayatını gerçekten kolaylaştırıyor. Ama bu yazıda gösterdiğim her adımın bir gerekçesi var: stratified split dengesiz veriyle baş etmek için, group_by_length training hızı için, EarlyStoppingCallback gereksiz GPU saatlerine para yakmamak için.

Pratikte en sık yapılan hatalar teknik değil, süreç hataları: token uzunluk dağılımını analiz etmeden max_length seçmek, metrik fonksiyonu olmadan eğitmek, TensorBoard’u açmayı unutmak. Bu küçük ama kritik adımları atlamadığınızda Trainer API gerçekten güçlü bir araç haline geliyor.

Buraya kadar anlattıklarım bir sentiment analysis görevi üzerine kuruluydu, ancak aynı yapı question answering, token classification, text generation gibi görevler için de geçerli. Model tipi ve loss fonksiyonu değişiyor, Trainer’ın temel mantığı aynı kalıyor. Bunu kavradıktan sonra Hugging Face ekosistemi size çok daha açık ve anlaşılır gelecek.

Bir yanıt yazın

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