File size: 4,854 Bytes
41504b2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""
Inference example for engin-dalga/tr-factbench-electra-lora

Loads the LoRA adapter on top of the exact base-model revision used during training
and runs factuality verification on a short example.

Requirements:
    pip install torch transformers peft

Usage:
    python inference_example.py
"""

import json
import torch
from pathlib import Path
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel

# ── Configuration ─────────────────────────────────────────────────────────────
BASE_MODEL = "dbmdz/electra-base-turkish-cased-discriminator"
BASE_MODEL_REVISION = "44ba696463e4d853078f3094cec76ba4e924cfd1"
ADAPTER_REPO = "engin-dalga/tr-factbench-electra-lora"   # Hugging Face Hub ID

LABEL_MAPPING = {
    0: "supported",
    1: "partially_supported",
    2: "contradicted",
    3: "unverifiable",
}

MAX_LENGTH = 512
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"

# ── Example input ──────────────────────────────────────────────────────────────
EXAMPLE = {
    "context": (
        "TΓΌrkiye'de lisanslΔ± hekimlerin tΔ±p fakΓΌltesinden mezun olmasΔ± zorunludur. "
        "Tıp fakülteleri en az altı yıllık eğitim vermektedir."
    ),
    "question": "Türkiye'de hekim olmak için ne kadar eğitim gereklidir?",
    "claim": "Türkiye'de hekim olmak için en az altı yıllık tıp eğitimi zorunludur.",
}


def load_model(adapter_path: str | None = None):
    """
    Load base model + LoRA adapter.

    Args:
        adapter_path: Local path to adapter directory (optional).
                      If None, loads from Hugging Face Hub.
    """
    print(f"Loading tokenizer from: {adapter_path or ADAPTER_REPO}")
    tokenizer = AutoTokenizer.from_pretrained(
        adapter_path or ADAPTER_REPO,
        revision=None,  # adapter repo, not base model
    )

    print(f"Loading base model: {BASE_MODEL}@{BASE_MODEL_REVISION}")
    base_model = AutoModelForSequenceClassification.from_pretrained(
        BASE_MODEL,
        revision=BASE_MODEL_REVISION,
        num_labels=len(LABEL_MAPPING),
        ignore_mismatched_sizes=True,
    )

    print(f"Loading LoRA adapter from: {adapter_path or ADAPTER_REPO}")
    model = PeftModel.from_pretrained(base_model, adapter_path or ADAPTER_REPO)
    model.eval().to(DEVICE)
    return model, tokenizer


def format_input(context: str, question: str, claim: str) -> tuple[str, str]:
    """Format context and question+claim into the two text_a / text_b strings used during training."""
    text_a = f"[CONTEXT] {context}"
    text_b = f"[QUESTION] {question} [CLAIM] {claim}"
    return text_a, text_b


def predict(model, tokenizer, context: str, question: str, claim: str) -> dict:
    text_a, text_b = format_input(context, question, claim)
    enc = tokenizer(
        text_a, text_b,
        max_length=MAX_LENGTH,
        truncation=True,
        padding=True,
        return_tensors="pt",
    )
    enc = {k: v.to(DEVICE) for k, v in enc.items()}

    with torch.no_grad():
        logits = model(**enc).logits

    probs = torch.softmax(logits, dim=-1).squeeze().cpu().tolist()
    pred_id = int(torch.argmax(logits, dim=-1).item())
    return {
        "predicted_label": LABEL_MAPPING[pred_id],
        "probabilities": {LABEL_MAPPING[i]: round(p, 4) for i, p in enumerate(probs)},
    }


def verify_classifier_head(model) -> None:
    """Quick sanity check: confirm the classifier head is inside the adapter (modules_to_save)."""
    classifier_params = [
        n for n, _ in model.named_parameters()
        if "classifier" in n or "modules_to_save" in n
    ]
    if not classifier_params:
        raise RuntimeError(
            "Classifier head not found in adapter parameters. "
            "The adapter may be incomplete β€” retrain with modules_to_save=['classifier', 'score']."
        )
    print(f"[OK] Classifier head found in adapter ({len(classifier_params)} parameters).")


if __name__ == "__main__":
    model, tokenizer = load_model()
    verify_classifier_head(model)

    result = predict(model, tokenizer, **EXAMPLE)

    print("\n── Inference result ──────────────────────────────────────────────────")
    print(f"Context : {EXAMPLE['context'][:80]}...")
    print(f"Question: {EXAMPLE['question']}")
    print(f"Claim   : {EXAMPLE['claim']}")
    print(f"\nPredicted label : {result['predicted_label']}")
    print("Probabilities:")
    for label, prob in result["probabilities"].items():
        print(f"  {label:<22}: {prob:.4f}")