Fine-Tune kleiner Modelle mit Hugging Face Trainer

1. Übersicht

Dieser Artikel führt Sie durch das Fine-Tuning eines kleinen Modells (DistilBERT) zur Klassifizierung von Stimmungen in Produktbewertungen mit der Trainer-API der transformers-Bibliothek. Nach dem Lesen werden Sie wissen, wie Sie einen Datensatz vorbereiten, tokenisieren, TrainingArguments konfigurieren, trainieren, evaluieren und Modelle für die Inferenz speichern.

Laufendes Beispiel in diesem Artikel: OrderReview – eine Bewertung eines Kunden (Customer) für eine Bestellung (Order), gekennzeichnet mit positive/negative.

2. Umgebung einrichten

Installieren Sie die erforderlichen Bibliotheken. Sie benötigen keine leistungsstarke GPU; kleine Modelle wie DistilBERT laufen auf der CPU auch mit kleinen Datensätzen ordnungsgemäß, allerdings langsamer.

pip install transformers datasets evaluate accelerate torch scikit-learn

Hinweis: Wenn Ihr Rechner eine GPU hat, installieren Sie die entsprechende PyTorch-Version mit CUDA für deutlich schnelleres Training. Mit CPU sollten Sie die Anzahl der Datenzeilen und Epochen begrenzen, um die Demo nicht zu lange zu machen.

3. Datensatz vorbereiten

Anstatt einen riesigen Datensatz zu laden, erstellen wir einen kleinen Beispieldatensatz mit simulierten Bestellbewertungen, um das Konzept zu veranschaulichen. In der Praxis können Sie stattdessen load_dataset("imdb") oder Ihre eigenen CSV-Daten verwenden.

from datasets import Dataset

reviews = [
    {"text": "Lieferung schnell, sorgfältig verpackt, sehr zufrieden", "label": 1},
    {"text": "Produkt fehlerhaft, langsame Lieferung, nicht wie beschrieben", "label": 0},
    {"text": "Gute Qualität, angemessener Preis, werde es wieder kaufen", "label": 1},
    {"text": "Schlechter Service, langsamer Kundensupport", "label": 0},
    {"text": "Sehr zufrieden mit dieser Bestellung, 5 Sterne", "label": 1},
    {"text": "Produkt beschädigt erhalten, sehr enttäuscht", "label": 0},
]

dataset = Dataset.from_list(reviews)
dataset = dataset.train_test_split(test_size=0.33, seed=42)
print(dataset)
DatasetDict({
    train: Dataset({features: ['text', 'label'], num_rows: 4})
    test: Dataset({features: ['text', 'label'], num_rows: 2})
})

Dataset.from_list konvertiert eine Liste von Dictionaries in einen Hugging Face Dataset. train_test_split teilt ihn in Train- und Test-Sets auf — bei echten Datensätzen sollten Sie mehrere tausend Zeilen haben, dies ist nur ein Beispiel für den Verarbeitungsablauf.

4. Daten tokenisieren

Das Modell versteht Text nicht direkt, ein Tokenizer ist erforderlich, um ihn in input_ids und attention_mask umzuwandeln.

from transformers import AutoTokenizer

checkpoint = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(checkpoint)

def tokenize_fn(batch):
    return tokenizer(batch["text"], truncation=True, padding="max_length", max_length=32)

tokenized_dataset = dataset.map(tokenize_fn, batched=True)
print(tokenized_dataset["train"][0].keys())
dict_keys(['text', 'label', 'input_ids', 'attention_mask'])
  • truncation=True: schneidet Sätze ab, die länger als max_length sind.
  • padding="max_length": füllt Token auf, damit alle Sätze die gleiche Länge haben, was die parallele Batch-Verarbeitung erleichtert.
  • map(..., batched=True): wendet die Tokenize-Funktion batchweise an, schneller als Zeile für Zeile.

Hinweis: Das englische DistilBERT ist in der Praxis nicht für das Fine-Tuning von vietnamesischen Reviews geeignet. Es wird hier zur technischen Demonstration verwendet; für vietnamesische Daten wählen Sie einen Checkpoint wie vinai/phobert-base.

5. Modell laden und Labels definieren

from transformers import AutoModelForSequenceClassification

label2id = {"negative": 0, "positive": 1}
id2label = {v: k for k, v in label2id.items()}

model = AutoModelForSequenceClassification.from_pretrained(
    checkpoint,
    num_labels=2,
    id2label=id2label,
    label2id=label2id,
)

num_labels=2 erstellt einen neuen Classification-Head mit 2 Ausgängen (positive/negative), der auf dem vortrainierten Encoder-Teil aufgesetzt wird. id2label/label2id helfen dem Modell, menschenlesbare Labels statt nur Zahlen 0/1 zurückzugeben.

6. TrainingArguments konfigurieren

TrainingArguments sammelt alle Hyperparameter und Konfigurationen der Trainingsschleife.

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./order-review-model",
    eval_strategy="epoch",
    save_strategy="epoch",
    learning_rate=2e-5,
    per_device_train_batch_size=4,
    per_device_eval_batch_size=4,
    num_train_epochs=3,
    weight_decay=0.01,
    load_best_model_at_end=True,
    metric_for_best_model="f1",
    logging_steps=1,
)
  • eval_strategy="epoch": bewertet das Modell nach jeder Epoch statt nach jedem Step.
  • load_best_model_at_end=True: Nach dem Training lädt der Trainer automatisch den besten Checkpoint entsprechend metric_for_best_model erneut.
  • per_device_train_batch_size=4: kleine Batch-Größe, weil der Demo-Datensatz auch klein ist; mit echten Daten erhöhen Sie dies auf 16/32 je nach verfügbarem VRAM.

7. Metrik-Berechnungsfunktion definieren

Der Trainer benötigt eine compute_metrics-Funktion, um zu wissen, anhand welcher Kriterien er das Modell evaluieren soll.

import numpy as np
import evaluate

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

def compute_metrics(eval_pred):
    logits, labels = eval_pred
    predictions = np.argmax(logits, axis=-1)
    acc = accuracy.compute(predictions=predictions, references=labels)
    f1_score = f1.compute(predictions=predictions, references=labels)
    return {"accuracy": acc["accuracy"], "f1": f1_score["f1"]}

eval_pred ist ein Tuple (logits, labels), das der Trainer nach jeder Evaluation übergibt. np.argmax wählt das Label mit der höchsten Wahrscheinlichkeit (Logit) als Vorhersage.

8. Trainer initialisieren und trainieren

from transformers import Trainer, DataCollatorWithPadding

data_collator = DataCollatorWithPadding(tokenizer=tokenizer)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized_dataset["train"],
    eval_dataset=tokenized_dataset["test"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
)

trainer.train()
Epoch   Training Loss   Validation Loss   Accuracy   F1
1       0.693100        0.671204          0.500000   0.500000
2       0.654200        0.598311          1.000000   1.000000
3       0.581900        0.520447          1.000000   1.000000

DataCollatorWithPadding handhabt dynamisches Padding pro Batch (effizienter als festes Padding auf max_length beim Tokenisieren, aber hier ist bereits gepolstert, also kümmert sich der Collator hauptsächlich um die Batch-Tensor-Erstellung). trainer.train() führt die Schleife Forward → Backward → Optimizer-Step → Loss-Logging aus und evaluiert automatisch gemäß der konfigurierten eval_strategy.

8.1. Trainingsfortschritt verfolgen

Wenn Sie detailliertere Protokolle an TensorBoard ausgeben möchten, fügen Sie report_to="tensorboard" zu TrainingArguments hinzu und führen Sie dann tensorboard --logdir ./order-review-model/runs aus. Mit einem kleinen Datensatz wie in dieser Demo ist die Console-Ausgabe ausreichend, um zu überprüfen, ob der Verlust kontinuierlich sinkt.

9. Modell evaluieren und speichern

metrics = trainer.evaluate()
print(metrics)

trainer.save_model("./order-review-model/final")
tokenizer.save_pretrained("./order-review-model/final")
{'eval_loss': 0.5204, 'eval_accuracy': 1.0, 'eval_f1': 1.0, 'eval_runtime': 0.12, ...}

trainer.evaluate() führt das Training erneut auf eval_dataset aus und gibt ein Metric-Dictionary zurück. save_model speichert Config und Gewichte; vergessen Sie nicht, den dazugehörigen Tokenizer mit save_pretrained zu speichern, um Vokabularprobleme beim erneuten Laden zu vermeiden.

10. Inferenz mit dem Fine-tuned Modell

Verwenden Sie pipeline, um das gerade trainierte Modell schnell zu testen.

from transformers import pipeline

classifier = pipeline(
    "text-classification",
    model="./order-review-model/final",
    tokenizer="./order-review-model/final",
)

result = classifier("Lieferung pünktlich, Produkt schöner als erwartet")
print(result)
[{'label': 'positive', 'score': 0.9123}]

pipeline übernimmt das Tokenisieren, Forward-Pass und Softmax, um das Label mit Score (Konfidenzwahrscheinlichkeit) auszugeben. Dies ist der schnellste Weg, um ein Modell in einen kleineren Service zu integrieren, beispielsweise einen PaymentService, der Kundenfeedback automatisch klassifizieren muss.

11. Tests für Modellverhalten schreiben

Mit dem Demo-Datensatz können Sie einfache Tests mit pytest schreiben, um sicherzustellen, dass die Pipeline korrekt funktioniert und keine Regression auftritt, wenn Sie den Checkpoint ändern.

import pytest
from transformers import pipeline

@pytest.fixture(scope="module")
def classifier():
    return pipeline(
        "text-classification",
        model="./order-review-model/final",
        tokenizer="./order-review-model/final",
    )

def test_positive_review_is_classified_correctly(classifier):
    result = classifier("Großartiges Produkt, schnelle Lieferung")[0]

    assert result["label"] == "positive"
    assert result["score"] > 0.5

def test_negative_review_is_classified_correctly(classifier):
    result = classifier("Produkt fehlerhaft, Lieferung verspätet, sehr enttäuscht")[0]

    assert result["label"] == "negative"
    assert result["score"] > 0.5

scope="module" sorgt dafür, dass die Pipeline das Modell nur einmal für alle Tests in der Datei lädt und verhindert so zeitraubende erneute Ladevorgänge für jeden Test. Dieser Test überprüft nur das High-Level-Verhalten (korrekte Label-Richtung) und sollte nicht zur Modellqualitätsbewertung verwendet werden — das ist Aufgabe des Evaluationsschritts oben.

12. Häufig auftretende Fehler

  • Vergessenes padding/truncation: Sätze, die länger als max_length sind, führen zu Laufzeitfehlern, da die Tensoren im Batch unterschiedliche Größen haben.
  • Falsches num_labels: Wenn der Datensatz 3 Klassen hat, aber Sie num_labels=2 deklarieren, funktioniert der Trainer immer noch, aber die Ergebnisse sind völlig falsch, da der Classification-Head nicht genug Ausgänge hat.
  • Zu kleiner Datensatz und hohe F1-Erwartungen bei echten Daten: Das Beispiel in diesem Artikel dient nur zur Illustration des Code-Flows, 4 Trainingszeilen reichen nicht für das Modell, um zu verallgemeinern. Nehmen Sie nicht das hier erreichte accuracy=1.0 als Maßstab.
  • seed nicht gesetzt: Trainingsergebnisse unterscheiden sich zwischen Läufen, was den Vergleich bei der Hyperparameter-Abstimmung erschwert. Verwenden Sie set_seed(42) aus transformers vor dem Training.
  • Großes Modell (base/large) für schwache Hardware: Beginnen Sie mit der distilbert/tiny-Version, um schnelle Entwicklungszyklen zu ermöglichen, wechseln Sie nur zu größeren Modellen, wenn Sie Ihre Pipeline finalisiert haben.

Wann man Trainer NICHT verwenden sollte: Wenn Sie die Trainingsschleife vollständig anpassen müssen (z. B. komplexe Multi-Task-Verluste, benutzerdefinierte Scheduler, die nicht einfach über Callbacks ausdrückbar sind), ist das Schreiben einer reinen PyTorch-Schleife oder die direkte Verwendung von accelerate flexibler als der Versuch, alles in den Trainer zu pressen.

13. Fazit

Die Trainer-API ermöglicht es Ihnen, kleine Modelle zu fine-tunen, ohne selbst eine Train/Eval-Schleife schreiben zu müssen – Sie müssen nur Datensatz, Tokenizer, Modell und TrainingArguments vorbereiten. Bei einfachen Klassifizierungsproblemen wie Bestellbewertungen ist der Standard-Workflow: Tokenisieren → TrainingArguments konfigurieren → compute_metrics definieren → Trainer initialisieren → train() → evaluate() → save_model(). Wenn Sie später mit komplexeren echten Daten arbeiten, müssen Sie nur den Datensatz und Checkpoint austauschen; der Rest der Pipeline bleibt praktisch unverändert.

huggingfacetransformersfine-tuningnlppython