| """ | |
| eval.py - RUN after pipeline.py | |
| evaluation metrics for the model | |
| """ | |
| import json | |
| import numpy as np | |
| import torch | |
| from sklearn.metrics import precision_recall_fscore_support, f1_score, accuracy_score | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent.parent | |
| from Model import SkillClassifier | |
| sys.path.insert(0, '.') | |
| data = np.load('prep/prepared_data.npz') | |
| X_train = data['X_train'] | |
| y_train = data['y_train'] | |
| X_test = data['X_test'] | |
| y_test = data['y_test'] | |
| with open('prep/label_vocab.json') as f: | |
| VOCAB = json.load(f) | |
| model = SkillClassifier(X_train.shape[1], len(VOCAB)) | |
| model.load_state_dict(torch.load(ROOT / 'model_out' / 'skill_classifier.pt', map_location='cpu')) | |
| model.eval() | |
| with torch.no_grad(): | |
| logits = model(torch.tensor(X_test, dtype=torch.float32)) | |
| probs = torch.sigmoid(logits).numpy() | |
| THRESHOLD = 0.5 | |
| preds = (probs >= THRESHOLD).astype(int) | |
| precision, recall, f1, support = precision_recall_fscore_support( | |
| y_test, | |
| preds, | |
| average=None, | |
| zero_division=0 | |
| ) | |
| micro_f1 = f1_score(y_test, preds, average='micro', zero_division=0) | |
| macro_f1 = f1_score(y_test, preds, average='macro', zero_division=0) | |
| print("\n Model Evaluation Metrics\n") | |
| print(f"{'label':25s}{'support':10s}{'precision':12s}{'recall':10s}{'f1':6s}") | |
| for lbl, p, r, f, s in zip(VOCAB, precision, recall, f1, support): | |
| if s > 0: | |
| print(f"{lbl:25s}{int(s):<10d}{p:<12.2f}{r:<10.2f}{f:.2f}") | |
| print(f"\nMicro-F1: {micro_f1:.3f} | Macro-F1: {macro_f1:.3f}") | |
| print("\n=== PER-LABEL ACCURACY ===") | |
| print(f"{'label':25s}{'accuracy%':12s}{'support':10s}{'trap?':6s}") | |
| is_right = 0 | |
| is_wrong = 0 | |
| for i, lbl in enumerate(VOCAB): | |
| label_acc = accuracy_score(y_test[:, i], preds[:, i]) | |
| s = int(support[i]) | |
| always_zero_acc = 1.0 - (y_test[:, i].sum() / len(y_test)) | |
| is_trap = always_zero_acc >= label_acc - 0.01 | |
| trap_flag = "Wrong" if is_trap else "Right" | |
| print(f"{lbl:25s}{label_acc*100:<12.1f}{s:<10d}{trap_flag}") | |
| if trap_flag == 'Right': | |
| is_right += 1 | |
| else: | |
| is_wrong += 1 | |
| total_labels = is_right + is_wrong | |
| print("Right : \n", is_right) | |
| print("Wrong : \n", is_wrong) | |
| print("Total Labels : \n", total_labels) | |
| key_acc = (is_right / total_labels) * 100 | |
| print(f"Keyword Accuracy : {round(key_acc, 2)}%\n") | |
| train_freq = y_train.mean(axis=0) | |
| baseline_preds = np.tile((train_freq >= 0.3).astype(int), (len(y_test), 1)) | |
| baseline_micro_f1 = f1_score(y_test, baseline_preds, average='micro', zero_division=0) | |
| baseline_macro_f1 = f1_score(y_test, baseline_preds, average='macro', zero_division=0) | |
| print(f"\nBASELINE: \n") | |
| baseline_labels = [lbl for lbl, f in zip(VOCAB, train_freq) if f >= 0.3] | |
| print(f"Baseline always predicts: {baseline_labels}") | |
| print(f"Baseline Micro-F1: {baseline_micro_f1:.3f} | Macro-F1: {baseline_macro_f1:.3f}") | |
| print("\nVERDICT \n") | |
| if micro_f1 > baseline_micro_f1 + 0.05: | |
| print("Model meaningfully beats the naive baseline.") | |
| else: | |
| print("Model is roughly tied with (or worse than) just guessing the most") | |