Simon
Model evealuation (#10)
a9f0e5a
Raw
History Blame Contribute Delete
8.91 kB
"""
Model evaluation utilities for phishing detection.
This module provides:
- Model evaluation metrics
- Feature importance analysis
- Model comparison utilities
"""
import logging
from typing import Dict, Any, List
import pandas as pd
import numpy as np
from sklearn.metrics import (
accuracy_score, precision_score, recall_score, f1_score,
roc_auc_score, confusion_matrix, classification_report
)
from sklearn.model_selection import cross_val_score
logger = logging.getLogger(__name__)
def evaluate_model(
model,
X: pd.DataFrame,
y: pd.Series
) -> Dict[str, float]:
"""
Evaluate a model on any dataset (train/val/test).
Simple function that works with any X and y - no need for separate
functions for train/val/test.
Args:
model: Trained model
X: Features (can be train, val, or test)
y: True labels (can be train, val, or test)
Returns:
Dictionary of evaluation metrics
"""
# Predictions
y_pred = model.predict(X)
# Probability predictions for ROC-AUC
if hasattr(model, 'predict_proba'):
y_proba = model.predict_proba(X)[:, 1]
else:
y_proba = y_pred
# Calculate metrics
metrics = {
'Accuracy': accuracy_score(y, y_pred),
'Precision': precision_score(y, y_pred),
'Recall': recall_score(y, y_pred),
'F1 Score': f1_score(y, y_pred),
'ROC-AUC': roc_auc_score(y, y_proba)
}
return metrics
def evaluate_model_detailed(
model,
X: pd.DataFrame,
y: pd.Series,
model_name: str = "Model",
dataset_name: str = "Dataset"
) -> Dict[str, Any]:
"""
Detailed evaluation with metrics, confusion matrix, and classification report.
Works with any dataset (train/val/test).
Args:
model: Trained model
X: Features
y: True labels
model_name: Name of model for logging
dataset_name: Name of dataset for logging (e.g., "Test", "Validation")
Returns:
Dictionary with metrics and confusion matrix
"""
# Get basic metrics
metrics = evaluate_model(model, X, y)
# Confusion matrix
y_pred = model.predict(X)
cm = confusion_matrix(y, y_pred)
# Log detailed results
logger.info("\n" + "=" * 80)
logger.info(f"{model_name} - {dataset_name.upper()} SET PERFORMANCE")
logger.info("=" * 80)
logger.info(f" Accuracy: {metrics['Accuracy']:.4f}")
logger.info(f" Precision: {metrics['Precision']:.4f}")
logger.info(f" Recall: {metrics['Recall']:.4f}")
logger.info(f" F1 Score: {metrics['F1 Score']:.4f}")
logger.info(f" ROC-AUC: {metrics['ROC-AUC']:.4f}")
logger.info(f"\nConfusion Matrix:")
logger.info(f" TN: {cm[0,0]} FP: {cm[0,1]}")
logger.info(f" FN: {cm[1,0]} TP: {cm[1,1]}")
logger.info("\nClassification Report:")
logger.info("\n" + classification_report(y, y_pred,
target_names=['Legitimate', 'Phishing']))
return {
**metrics,
'confusion_matrix': cm
}
def compare_models(
models: Dict[str, Any],
X_train: pd.DataFrame,
X_val: pd.DataFrame,
y_train: pd.Series,
y_val: pd.Series,
cv_folds: int = 5
) -> pd.DataFrame:
"""
Compare multiple models with train/val metrics and cross-validation.
Args:
models: Dictionary of trained models
X_train: Training features
X_val: Validation features
y_train: Training labels
y_val: Validation labels
cv_folds: Number of cross-validation folds
Returns:
DataFrame with model comparison metrics sorted by validation accuracy
"""
logger.info("=" * 80)
logger.info("Comparing Models...")
logger.info("=" * 80)
results = []
for name, model in models.items():
logger.info(f"\nEvaluating {name}...")
# Use the same evaluate_model() for both train and val
train_metrics = evaluate_model(model, X_train, y_train)
val_metrics = evaluate_model(model, X_val, y_val)
# Cross-validation
cv_scores = cross_val_score(model, X_train, y_train, cv=cv_folds, scoring='accuracy')
results.append({
'Model': name,
'Train Accuracy': train_metrics['Accuracy'],
'Val Accuracy': val_metrics['Accuracy'],
'Precision': val_metrics['Precision'],
'Recall': val_metrics['Recall'],
'F1 Score': val_metrics['F1 Score'],
'ROC-AUC': val_metrics['ROC-AUC'],
'CV Mean': cv_scores.mean(),
'CV Std': cv_scores.std(),
'Overfit (Train-Val)': train_metrics['Accuracy'] - val_metrics['Accuracy']
})
# Log results
logger.info(f" Train Accuracy: {train_metrics['Accuracy']:.4f}")
logger.info(f" Val Accuracy: {val_metrics['Accuracy']:.4f}")
logger.info(f" Precision: {val_metrics['Precision']:.4f}")
logger.info(f" Recall: {val_metrics['Recall']:.4f}")
logger.info(f" F1 Score: {val_metrics['F1 Score']:.4f}")
logger.info(f" ROC-AUC: {val_metrics['ROC-AUC']:.4f}")
logger.info(f" CV Mean ± Std: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}")
results_df = pd.DataFrame(results)
results_df = results_df.sort_values('Val Accuracy', ascending=False)
return results_df
def analyze_feature_importance(
models: Dict[str, Any],
feature_names: List[str]
) -> tuple[pd.DataFrame, pd.DataFrame]:
"""
Extract and analyze feature importance from models.
Args:
models: Dictionary of trained models
feature_names: List of feature names
Returns:
Tuple of (importance_df, mean_importance_df)
"""
logger.info("=" * 80)
logger.info("Analyzing Feature Importance...")
logger.info("=" * 80)
importance_data = []
for name, model in models.items():
if hasattr(model, 'feature_importances_'):
# Tree-based models
importances = model.feature_importances_
logger.info(f"\n{name} - Feature Importances (tree-based):")
for feature, importance in zip(feature_names, importances):
importance_data.append({
'Model': name,
'Feature': feature,
'Importance': importance,
'Type': 'Tree-based'
})
logger.info(f" {feature}: {importance:.4f}")
elif hasattr(model, 'coef_'):
# Linear models
coef = model.coef_[0] if len(model.coef_.shape) > 1 else model.coef_
importances = np.abs(coef)
logger.info(f"\n{name} - Feature Importances (absolute coefficients):")
for feature, importance in zip(feature_names, importances):
importance_data.append({
'Model': name,
'Feature': feature,
'Importance': importance,
'Type': 'Coefficient-based'
})
logger.info(f" {feature}: {importance:.4f}")
else:
logger.info(f"\n{name} - No feature importance available")
if not importance_data:
logger.warning("No models with feature importance found!")
return pd.DataFrame(), pd.DataFrame()
# Create DataFrame
importance_df = pd.DataFrame(importance_data)
# Calculate mean importance across all models
mean_importance = importance_df.groupby('Feature')['Importance'].agg(['mean', 'std']).reset_index()
mean_importance.columns = ['Feature', 'Mean Importance', 'Std Importance']
mean_importance = mean_importance.sort_values('Mean Importance', ascending=False)
logger.info("\n" + "=" * 80)
logger.info("MEAN FEATURE IMPORTANCE ACROSS ALL MODELS")
logger.info("=" * 80)
logger.info("\n" + mean_importance.to_string(index=False))
return importance_df, mean_importance
def select_best_model(results_df: pd.DataFrame, models: Dict[str, Any], metric: str = 'Val Accuracy') -> tuple[str, Any]:
"""
Select the best model based on a metric.
Args:
results_df: DataFrame with model evaluation results
models: Dictionary of trained models
metric: Metric to use for selection (default: 'Val Accuracy')
Returns:
Tuple of (best_model_name, best_model)
"""
best_model_name = results_df.iloc[0]['Model']
best_model = models[best_model_name]
logger.info("\n" + "=" * 80)
logger.info(f"SELECTED BEST MODEL (based on {metric}): {best_model_name}")
logger.info(f" Val Accuracy: {results_df.iloc[0]['Val Accuracy']:.4f}")
logger.info(f" F1 Score: {results_df.iloc[0]['F1 Score']:.4f}")
logger.info(f" ROC-AUC: {results_df.iloc[0]['ROC-AUC']:.4f}")
logger.info("=" * 80)
return best_model_name, best_model