Satyam311's picture
Clean deploy: numpy vector search, binaries excluded
d4ec2fa
Raw History Blame Contribute Delete
4.33 kB
"""
Ticket 6 — Evaluation & Confusion Matrix Report (ATA classifier).
Runs the trained model against the TEST split (never used for training or
checkpoint selection -- that was val) and produces:
- overall accuracy / macro-F1 (comparable to the PRD's success metric)
- per-class precision/recall/F1 (exposes which classes are actually weak,
which a single headline number hides)
- a confusion matrix image
- a metrics JSON, for the Model Performance Dashboard (Ticket 16) to read later
"""
import json
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import torch
from sklearn.metrics import (
accuracy_score,
classification_report,
confusion_matrix,
precision_recall_fscore_support,
)
from transformers import AutoModelForSequenceClassification, AutoTokenizer
MODEL_DIR = "models/ata_classifier"
TEST_CSV = "data/processed/test.csv"
MAX_LENGTH = 256
OUT_DIR = "reports/ata_classifier"
def main():
Path(OUT_DIR).mkdir(parents=True, exist_ok=True)
tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR, local_files_only=True)
model = AutoModelForSequenceClassification.from_pretrained(MODEL_DIR, local_files_only=True)
model.eval()
with open(f"{MODEL_DIR}/label_map.json") as f:
label_map = json.load(f)
id2label = {int(k): v for k, v in label_map["id2label"].items()}
label2id = label_map["label2id"]
df = pd.read_csv(TEST_CSV)
df = df[df["ata_chapter_label"].notna()].reset_index(drop=True)
print(f"Test examples (ATA-labeled): {len(df)}")
true_labels = df["ata_chapter_label"].astype(str).tolist()
y_true = [label2id[t] for t in true_labels]
y_pred = []
with torch.no_grad():
for text in df["narrative_text"].tolist():
inputs = tokenizer(text, truncation=True, max_length=MAX_LENGTH, return_tensors="pt")
logits = model(**inputs).logits
pred_id = int(torch.argmax(logits, dim=1).item())
y_pred.append(pred_id)
acc = accuracy_score(y_true, y_pred)
precision, recall, f1, support = precision_recall_fscore_support(
y_true, y_pred, average="macro", zero_division=0
)
print(f"\n=== TEST SET RESULTS (never used in training or checkpoint selection) ===")
print(f"Accuracy: {acc:.1%} (PRD target: >=80%)")
print(f"Macro F1: {f1:.3f}")
print(f"Macro Precision: {precision:.3f} Macro Recall: {recall:.3f}")
labels_present = sorted(set(y_true) | set(y_pred))
target_names = [id2label[i] for i in labels_present]
report_dict = classification_report(
y_true, y_pred, labels=labels_present, target_names=target_names,
zero_division=0, output_dict=True
)
print("\nPer-class report:")
print(classification_report(y_true, y_pred, labels=labels_present, target_names=target_names, zero_division=0))
cm = confusion_matrix(y_true, y_pred, labels=labels_present)
fig, ax = plt.subplots(figsize=(10, 9))
im = ax.imshow(cm, cmap="Blues")
ax.set_xticks(range(len(target_names)))
ax.set_yticks(range(len(target_names)))
ax.set_xticklabels(target_names, rotation=90)
ax.set_yticklabels(target_names)
ax.set_xlabel("Predicted")
ax.set_ylabel("True")
ax.set_title("ATA Chapter Classifier — Confusion Matrix (Test Set)")
for i in range(len(target_names)):
for j in range(len(target_names)):
if cm[i, j] > 0:
ax.text(j, i, str(cm[i, j]), ha="center", va="center",
color="white" if cm[i, j] > cm.max() / 2 else "black", fontsize=7)
fig.colorbar(im)
fig.tight_layout()
fig.savefig(f"{OUT_DIR}/confusion_matrix.png", dpi=150)
print(f"\nSaved confusion matrix to {OUT_DIR}/confusion_matrix.png")
metrics_out = {
"test_accuracy": acc,
"test_macro_f1": f1,
"test_macro_precision": precision,
"test_macro_recall": recall,
"n_test_examples": len(df),
"per_class": report_dict,
"prd_target_accuracy": 0.80,
"meets_prd_target": acc >= 0.80,
}
with open(f"{OUT_DIR}/metrics.json", "w") as f:
json.dump(metrics_out, f, indent=2)
print(f"Saved metrics JSON to {OUT_DIR}/metrics.json")
if __name__ == "__main__":
main()