Spaces:
Sleeping
Sleeping
Download src/evaluator.py from vettri06/emailspamclassifier: direct link, hf CLI and curl.
- Browser
- Download file 5.46 kB
-
https://huggingface.co/spaces/vettri06/emailspamclassifier/resolve/main/src/evaluator.py
- Command line
-
hf download hf://spaces/vettri06/emailspamclassifier/src/evaluator.py
-
curl -L -o evaluator.py https://huggingface.co/spaces/vettri06/emailspamclassifier/resolve/main/src/evaluator.py
5.46 kB
| """ | |
| Model evaluation module: | |
| Computes comprehensive multi-class metrics, security-oriented performance indices, | |
| confusion matrices, and visualization plots. | |
| """ | |
| import os | |
| import matplotlib | |
| matplotlib.use("Agg") # Headless backend | |
| import matplotlib.pyplot as plt | |
| import seaborn as sns | |
| import numpy as np | |
| import pandas as pd | |
| from typing import Dict, Any, List | |
| from sklearn.metrics import ( | |
| accuracy_score, precision_score, recall_score, f1_score, | |
| classification_report, confusion_matrix, roc_auc_score | |
| ) | |
| from src.data_loader import CLASS_NAMES, ID_TO_CLASS | |
| def evaluate_predictions( | |
| y_true: np.ndarray, | |
| y_pred: np.ndarray, | |
| y_proba: np.ndarray = None, | |
| dataset_name: str = "validation" | |
| ) -> Dict[str, Any]: | |
| """ | |
| Computes rigorous evaluation metrics with a focus on security metrics. | |
| For email threats, false negatives on 'Suspicious' can lead to breaches. | |
| """ | |
| acc = accuracy_score(y_true, y_pred) | |
| macro_p = precision_score(y_true, y_pred, average="macro", zero_division=0) | |
| macro_r = recall_score(y_true, y_pred, average="macro", zero_division=0) | |
| macro_f1 = f1_score(y_true, y_pred, average="macro", zero_division=0) | |
| weighted_p = precision_score(y_true, y_pred, average="weighted", zero_division=0) | |
| weighted_r = recall_score(y_true, y_pred, average="weighted", zero_division=0) | |
| weighted_f1 = f1_score(y_true, y_pred, average="weighted", zero_division=0) | |
| # Per-class metrics | |
| class_p = precision_score(y_true, y_pred, average=None, zero_division=0) | |
| class_r = recall_score(y_true, y_pred, average=None, zero_division=0) | |
| class_f1 = f1_score(y_true, y_pred, average=None, zero_division=0) | |
| cm = confusion_matrix(y_true, y_pred) | |
| report_str = classification_report( | |
| y_true, y_pred, target_names=CLASS_NAMES, digits=4, zero_division=0 | |
| ) | |
| metrics = { | |
| f"{dataset_name}_accuracy": float(acc), | |
| f"{dataset_name}_macro_precision": float(macro_p), | |
| f"{dataset_name}_macro_recall": float(macro_r), | |
| f"{dataset_name}_macro_f1": float(macro_f1), | |
| f"{dataset_name}_weighted_f1": float(weighted_f1), | |
| } | |
| # Add per-class specific metrics | |
| for idx, name in enumerate(CLASS_NAMES): | |
| clean_name = name.lower() | |
| metrics[f"{dataset_name}_{clean_name}_precision"] = float(class_p[idx]) | |
| metrics[f"{dataset_name}_{clean_name}_recall"] = float(class_r[idx]) | |
| metrics[f"{dataset_name}_{clean_name}_f1"] = float(class_f1[idx]) | |
| # Security false negative rate for Suspicious class (class index 2) | |
| suspicious_idx = 2 | |
| suspicious_fnr = 1.0 - float(class_r[suspicious_idx]) | |
| metrics[f"{dataset_name}_suspicious_false_negative_rate"] = float(suspicious_fnr) | |
| # Multi-class ROC-AUC if probabilities provided | |
| if y_proba is not None: | |
| try: | |
| auc = roc_auc_score(y_true, y_proba, multi_class="ovr", average="macro") | |
| metrics[f"{dataset_name}_roc_auc_ovr"] = float(auc) | |
| except Exception: | |
| metrics[f"{dataset_name}_roc_auc_ovr"] = 0.0 | |
| return { | |
| "metrics": metrics, | |
| "confusion_matrix": cm, | |
| "classification_report": report_str | |
| } | |
| def plot_confusion_matrix( | |
| cm: np.ndarray, | |
| model_name: str, | |
| output_path: str | |
| ) -> str: | |
| """ | |
| Renders and saves a high-resolution confusion matrix heatmap. | |
| """ | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| plt.figure(figsize=(7, 6)) | |
| # Normalize for percentage annotations | |
| cm_norm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis] | |
| annotations = np.empty_like(cm, dtype=object) | |
| for i in range(cm.shape[0]): | |
| for j in range(cm.shape[1]): | |
| annotations[i, j] = f"{cm[i, j]}\n({cm_norm[i, j]*100:.1f}%)" | |
| sns.heatmap( | |
| cm, | |
| annot=annotations, | |
| fmt="", | |
| cmap="Blues", | |
| xticklabels=CLASS_NAMES, | |
| yticklabels=CLASS_NAMES, | |
| cbar=True, | |
| annot_kws={"size": 11, "weight": "bold"} | |
| ) | |
| plt.title(f"Confusion Matrix: {model_name}", fontsize=13, pad=12, weight="bold") | |
| plt.xlabel("Predicted Class", fontsize=11, weight="bold") | |
| plt.ylabel("True Class", fontsize=11, weight="bold") | |
| plt.tight_layout() | |
| plt.savefig(output_path, dpi=200) | |
| plt.close() | |
| return output_path | |
| def plot_metric_comparisons( | |
| comparison_df: pd.DataFrame, | |
| output_path: str | |
| ) -> str: | |
| """ | |
| Plots a multi-bar chart comparing model performances across key metrics. | |
| """ | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| plt.figure(figsize=(10, 6)) | |
| # Melt dataframe for seaborn barplot | |
| metrics_to_plot = ["Macro F1", "Suspicious Recall", "Accuracy", "Weighted F1"] | |
| melted = comparison_df.melt( | |
| id_vars=["Model"], | |
| value_vars=[m for m in metrics_to_plot if m in comparison_df.columns], | |
| var_name="Metric", | |
| value_name="Score" | |
| ) | |
| sns.barplot( | |
| data=melted, | |
| x="Metric", | |
| y="Score", | |
| hue="Model", | |
| palette="crest" | |
| ) | |
| plt.title("Model Performance Benchmark Comparison", fontsize=13, weight="bold", pad=12) | |
| plt.ylim(0.0, 1.05) | |
| plt.ylabel("Score (0.0 - 1.0)", fontsize=11, weight="bold") | |
| plt.xlabel("Evaluation Metric", fontsize=11, weight="bold") | |
| plt.legend(loc="lower right") | |
| plt.grid(axis="y", linestyle="--", alpha=0.5) | |
| plt.tight_layout() | |
| plt.savefig(output_path, dpi=200) | |
| plt.close() | |
| return output_path | |