| """ |
| eval_scitail.py β v1 |
| Zero-shot out-of-domain evaluation of the SciFact-fine-tuned DeBERTa model |
| on the SciTail test set. |
| |
| Purpose: confirm that the MultiNLI β SciFact transfer learned generalizable |
| scientific reasoning, not just SciFact-specific claim memorization. |
| |
| No retraining. No fine-tuning on SciTail. Pure zero-shot transfer. |
| |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| PRE-FLIGHT (run these cells in Colab before executing this script) |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| # 1. Mount Drive (if not already mounted) |
| from google.colab import drive |
| drive.mount('/content/drive') |
| |
| # 2. Verify the best SciFact model is present |
| import os |
| path = '/content/drive/MyDrive/scifact_checkpoint/buss305-scifact-bestmodel' |
| print(os.listdir(path)) # should show model.safetensors, config.json etc. |
| |
| # 3. Run: |
| !python eval_scitail.py |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| Label compatibility note: |
| SciTail is binary: "entailment" / "neutral" |
| Our model has 3 outputs: SUPPORT(0) / NEI(1) / CONTRADICT(2) |
| |
| Mapping: |
| SciTail "entailment" β our SUPPORT (0) |
| SciTail "neutral" β our NEI (1) |
| |
| SciTail has no contradiction examples by design (it was built from |
| science exam questions, not adversarial pairs). We report: |
| 1. Binary F1 (SUPPORT vs NEI) β the directly comparable metric |
| 2. How often the model fires CONTRADICT on SciTail β should be low; |
| a high CONTRADICT rate would indicate the model is confused |
| 3. Macro-F1 over all 3 model classes for completeness |
| |
| A strong binary F1 (>0.70) with low CONTRADICT rate (<15%) confirms |
| genuine transfer of scientific NLI reasoning. |
| """ |
|
|
| import numpy as np |
| import torch |
| from datasets import load_dataset |
| from transformers import AutoTokenizer, AutoModelForSequenceClassification |
| from sklearn.metrics import ( |
| f1_score, classification_report, confusion_matrix |
| ) |
|
|
| |
| |
| |
|
|
| SCIFACT_MODEL = "/content/drive/MyDrive/scifact_checkpoint/buss305-scifact-bestmodel" |
| MAX_LENGTH = 256 |
| BATCH_SIZE = 32 |
|
|
| |
| ID2LABEL = {0: "SUPPORT", 1: "NOT_ENOUGH_INFO", 2: "CONTRADICT"} |
|
|
| |
| SCITAIL_LABEL_MAP = { |
| "entailment": 0, |
| "neutral": 1, |
| } |
|
|
| |
| |
| |
|
|
| print("Loading SciTail test set β¦") |
| |
| scitail = load_dataset("allenai/scitail", "snli_format", split="test") |
| print(f" SciTail test rows : {len(scitail)}") |
| print(f" Columns : {scitail.column_names}") |
| print(f" Label values : {set(scitail['gold_label'])}") |
|
|
| |
| scitail = scitail.filter(lambda x: x["gold_label"] in SCITAIL_LABEL_MAP) |
| print(f" Rows after filter : {len(scitail)}") |
|
|
| |
| true_labels = [SCITAIL_LABEL_MAP[x] for x in scitail["gold_label"]] |
|
|
| from collections import Counter |
| dist = Counter(true_labels) |
| print(f" Label distribution: SUPPORT={dist[0]} NEI={dist[1]}") |
|
|
| |
| |
| |
|
|
| print(f"\nLoading model from {SCIFACT_MODEL} β¦") |
| tokenizer = AutoTokenizer.from_pretrained(SCIFACT_MODEL) |
| model = AutoModelForSequenceClassification.from_pretrained(SCIFACT_MODEL) |
| model.eval() |
|
|
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| model.to(device) |
| print(f" Device: {device}") |
| print(f" Model labels: {model.config.id2label}") |
|
|
| |
| |
| |
|
|
| print("\nRunning inference on SciTail test set β¦") |
|
|
| premises = scitail["sentence1"] |
| hypotheses = scitail["sentence2"] |
| all_preds = [] |
| all_logits = [] |
|
|
| with torch.no_grad(): |
| for i in range(0, len(premises), BATCH_SIZE): |
| batch_p = premises[i : i + BATCH_SIZE] |
| batch_h = hypotheses[i : i + BATCH_SIZE] |
|
|
| enc = tokenizer( |
| batch_p, |
| batch_h, |
| truncation=True, |
| max_length=MAX_LENGTH, |
| padding=True, |
| return_tensors="pt", |
| ) |
| enc.pop("token_type_ids", None) |
| enc = {k: v.to(device) for k, v in enc.items()} |
|
|
| outputs = model(**enc) |
| logits = outputs.logits.cpu().numpy() |
| preds = np.argmax(logits, axis=-1) |
|
|
| all_preds.extend(preds.tolist()) |
| all_logits.extend(logits.tolist()) |
|
|
| if (i // BATCH_SIZE) % 10 == 0: |
| print(f" {i}/{len(premises)} β¦") |
|
|
| print(f" Done. {len(all_preds)} predictions made.") |
|
|
| |
| |
| |
|
|
| all_preds = np.array(all_preds) |
| true_arr = np.array(true_labels) |
|
|
| |
| |
| |
| contradict_count = (all_preds == 2).sum() |
| contradict_rate = contradict_count / len(all_preds) |
| print(f"\nββ CONTRADICT rate (should be low) ββββββββββββββββββββββββββ") |
| print(f" Model predicted CONTRADICT : {contradict_count} / {len(all_preds)} ({100*contradict_rate:.1f}%)") |
| if contradict_rate < 0.10: |
| print(f" β Low CONTRADICT rate β model is not over-triggering contradiction") |
| elif contradict_rate < 0.20: |
| print(f" β Moderate CONTRADICT rate β model somewhat over-triggers contradiction") |
| else: |
| print(f" β High CONTRADICT rate β model confused on binary-only domain") |
|
|
| |
| |
| |
| |
| binary_preds = np.where(all_preds == 0, 0, 1) |
| binary_true = true_arr |
|
|
| binary_f1 = f1_score(binary_true, binary_preds, average="macro") |
| binary_accuracy = (binary_preds == binary_true).mean() |
|
|
| print(f"\nββ Binary evaluation (SUPPORT vs NEI) βββββββββββββββββββββββ") |
| print(f" Macro-F1 : {binary_f1:.4f}") |
| print(f" Accuracy : {binary_accuracy:.4f}") |
| print() |
| print(classification_report( |
| binary_true, binary_preds, |
| target_names=["SUPPORT (entailment)", "NEI (neutral)"], |
| digits=4, |
| )) |
|
|
| |
| print(f"ββ Full 3-class prediction distribution βββββββββββββββββββββ") |
| pred_dist = Counter(all_preds.tolist()) |
| for lid in [0, 1, 2]: |
| print(f" {ID2LABEL[lid]:20s}: {pred_dist.get(lid, 0):5d} ({100*pred_dist.get(lid,0)/len(all_preds):.1f}%)") |
|
|
| |
| print(f"\nββ Confusion matrix (rows=true, cols=predicted) βββββββββββββ") |
| print(f" True labels: 0=SUPPORT(entailment) 1=NEI(neutral)") |
| print(f" Pred labels: 0=SUPPORT 1=NEI 2=CONTRADICT") |
| cm = confusion_matrix(true_arr, all_preds, labels=[0, 1, 2]) |
| print(f" SUPPORT NEI CONTRADICT") |
| for i, row_name in enumerate(["SUPPORT(true)", "NEI(true) "]): |
| if i < len(cm): |
| print(f" {row_name}: {cm[i]}") |
|
|
| |
| print(f"\nββ Interpretation βββββββββββββββββββββββββββββββββββββββββββ") |
| if binary_f1 >= 0.75: |
| print(f" β STRONG transfer (binary F1={binary_f1:.3f})") |
| print(f" Model generalises scientific NLI reasoning beyond SciFact.") |
| elif binary_f1 >= 0.60: |
| print(f" ~ MODERATE transfer (binary F1={binary_f1:.3f})") |
| print(f" Model shows partial generalisation. Some SciFact-specific patterns.") |
| else: |
| print(f" β WEAK transfer (binary F1={binary_f1:.3f})") |
| print(f" Model may have overfit SciFact domain.") |
|
|
| print("\nβ eval_scitail.py complete.") |