fund-flow-backend / eval_all_models.py
Aniket2006's picture
fix: resolve Mermaid rendering syntax errors, add client auto-fix engine, and cleanup imports
9d35684
Raw
History Blame Contribute Delete
9.24 kB
import os
import torch
import torch.nn.functional as F
import pandas as pd
import numpy as np
import xgboost as xgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_auc_score, f1_score, precision_score, recall_score, confusion_matrix
import networkx as nx
# Setup Python path and import configs
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from src.config_loader import get_config
from src.ml.gnn_trainer import GraphSAGEFraudDetector
cfg = get_config()['ml']
def eval_xgboost():
print("=== XGBoost Evaluation ===")
import pickle
with open("data/checkpoints/features.pkl", "rb") as f:
df = pickle.load(f)
feature_cols = [c for c in cfg['feature_cols'] if c in df.columns]
X = df[feature_cols].copy()
y = df['fraud_flag'].copy()
X_temp, X_hold, y_temp, y_hold = train_test_split(
X, y, test_size=cfg['val_size'], random_state=cfg['random_state'], stratify=y
)
model = xgb.XGBClassifier()
model.load_model(cfg['model_path'])
dtest = xgb.DMatrix(X_hold)
y_proba = model.get_booster().predict(dtest)
y_pred = (y_proba > 0.5).astype(int)
print(f"AUC: {roc_auc_score(y_hold, y_proba):.4f}")
print(f"F1: {f1_score(y_hold, y_pred, zero_division=0):.4f}")
print(f"Precision: {precision_score(y_hold, y_pred, zero_division=0):.4f}")
print(f"Recall: {recall_score(y_hold, y_pred, zero_division=0):.4f}")
print(f"Confusion Matrix:\n{confusion_matrix(y_hold, y_pred)}")
def eval_gnn():
print("\n=== GraphSAGE Evaluation ===")
import pickle
with open("data/checkpoints/features.pkl", "rb") as f:
df = pickle.load(f)
feature_cols = [c for c in cfg['feature_cols'] if c in df.columns]
# Load model to get x_mean and x_std
checkpoint = torch.load(cfg['gnn_model_path'], map_location='cpu', weights_only=False)
model = GraphSAGEFraudDetector(checkpoint['in_channels'], checkpoint['hidden_channels'])
model.load_state_dict(checkpoint['model_state_dict'])
model.eval()
x_mean = checkpoint['x_mean']
x_std = checkpoint['x_std']
node_list = sorted(list(df['account']))
node_to_idx = {n: i for i, n in enumerate(node_list)}
df_sorted = df.set_index('account').reindex(node_list)
x_numpy = df_sorted[feature_cols].fillna(0).values.astype(float)
y_numpy = df_sorted['fraud_flag'].fillna(0).values.astype(int)
x_scaled = (x_numpy - x_mean) / (x_std + 1e-8)
x_scaled = np.nan_to_num(x_scaled, nan=0.0)
x = torch.tensor(x_scaled, dtype=torch.float)
y = torch.tensor(y_numpy, dtype=torch.long)
# Load edges
edge_df = pd.read_parquet("data/checkpoints/graph_edges.parquet")
src = edge_df['source'].map(node_to_idx)
tgt = edge_df['target'].map(node_to_idx)
valid = src.notna() & tgt.notna()
src = src[valid].values.astype(int)
tgt = tgt[valid].values.astype(int)
all_src = np.concatenate([src, tgt])
all_tgt = np.concatenate([tgt, src])
edge_index = torch.tensor(np.stack([all_src, all_tgt]), dtype=torch.long)
with torch.no_grad():
out = model(x, edge_index)
probs = F.softmax(out, dim=1)[:, 1].cpu().numpy()
preds = (probs >= 0.5).astype(int)
# Since GNN uses random split, we just evaluate on the whole graph to see overall performance
print("Note: GNN split wasn't seeded/saved. Evaluating on full graph.")
print(f"AUC: {roc_auc_score(y_numpy, probs):.4f}")
print(f"F1: {f1_score(y_numpy, preds, zero_division=0):.4f}")
print(f"Precision: {precision_score(y_numpy, preds, zero_division=0):.4f}")
print(f"Recall: {recall_score(y_numpy, preds, zero_division=0):.4f}")
print(f"Confusion Matrix:\n{confusion_matrix(y_numpy, preds)}")
def eval_hybrid():
print("\n=== Hybrid Stack Evaluation ===")
from src.ml.hybrid_predictor import hybrid_predictor
import pickle
with open("data/checkpoints/features.pkl", "rb") as f:
df = pickle.load(f)
# Load graph
edge_df = pd.read_parquet("data/checkpoints/graph_edges.parquet")
edge_df['timestamp'] = pd.to_datetime(edge_df['timestamp'], errors='coerce').fillna(pd.Timestamp("2000-01-01"))
G = nx.MultiDiGraph()
for row in edge_df.itertuples(index=False):
G.add_edge(row.source, row.target, amount=row.amount, timestamp=row.timestamp)
# Let's evaluate on the holdout split from XGBoost (just as a representative test set)
y = df['fraud_flag']
_, df_hold = train_test_split(df, test_size=cfg['val_size'], random_state=cfg['random_state'], stratify=y)
accounts = df_hold['account'].tolist()
# Run hybrid prediction
scores_dict = hybrid_predictor.predict(G, accounts)
y_true = df_hold.set_index('account').loc[accounts]['fraud_flag'].values
probs = np.array([scores_dict.get(a, 0.0) for a in accounts])
preds = (probs >= 0.5).astype(int)
print("Evaluating on XGBoost Holdout set.")
print(f"AUC: {roc_auc_score(y_true, probs):.4f}")
print(f"F1: {f1_score(y_true, preds, zero_division=0):.4f}")
print(f"Precision: {precision_score(y_true, preds, zero_division=0):.4f}")
print(f"Recall: {recall_score(y_true, preds, zero_division=0):.4f}")
print(f"Confusion Matrix:\n{confusion_matrix(y_true, preds)}")
def eval_ensemble():
print("\n=== Blended Ensemble (XGB + GNN + Hybrid) ===")
from src.ml.hybrid_predictor import hybrid_predictor
import pickle
with open("data/checkpoints/features.pkl", "rb") as f:
df = pickle.load(f)
# 1. XGBoost Probs
feature_cols = [c for c in cfg['feature_cols'] if c in df.columns]
X = df[feature_cols].copy()
y = df['fraud_flag'].copy()
_, X_hold, _, y_hold = train_test_split(X, y, test_size=cfg['val_size'], random_state=cfg['random_state'], stratify=y)
xgb_model = xgb.XGBClassifier()
xgb_model.load_model(cfg['model_path'])
dtest = xgb.DMatrix(X_hold)
xgb_probs = xgb_model.get_booster().predict(dtest)
# 2. GNN Probs (Full graph mapped to holdout accounts)
checkpoint = torch.load(cfg['gnn_model_path'], map_location='cpu', weights_only=False)
gnn_model = GraphSAGEFraudDetector(checkpoint['in_channels'], checkpoint['hidden_channels'])
gnn_model.load_state_dict(checkpoint['model_state_dict'])
gnn_model.eval()
node_list = sorted(list(df['account']))
node_to_idx = {n: i for i, n in enumerate(node_list)}
df_sorted = df.set_index('account').reindex(node_list)
x_numpy = df_sorted[feature_cols].fillna(0).values.astype(float)
x_scaled = (x_numpy - checkpoint['x_mean']) / (checkpoint['x_std'] + 1e-8)
x_scaled = np.nan_to_num(x_scaled, nan=0.0)
edge_df = pd.read_parquet("data/checkpoints/graph_edges.parquet")
src = edge_df['source'].map(node_to_idx)
tgt = edge_df['target'].map(node_to_idx)
valid = src.notna() & tgt.notna()
edge_index = torch.tensor(np.stack([np.concatenate([src[valid].values.astype(int), tgt[valid].values.astype(int)]),
np.concatenate([tgt[valid].values.astype(int), src[valid].values.astype(int)])]), dtype=torch.long)
with torch.no_grad():
gnn_out = gnn_model(torch.tensor(x_scaled, dtype=torch.float), edge_index)
gnn_all_probs = F.softmax(gnn_out, dim=1)[:, 1].cpu().numpy()
gnn_prob_dict = {n: p for n, p in zip(node_list, gnn_all_probs)}
# 3. Hybrid Probs
edge_df['timestamp'] = pd.to_datetime(edge_df['timestamp'], errors='coerce').fillna(pd.Timestamp("2000-01-01"))
G = nx.MultiDiGraph()
for row in edge_df.itertuples(index=False):
G.add_edge(row.source, row.target, amount=row.amount, timestamp=row.timestamp)
_, df_hold = train_test_split(df, test_size=cfg['val_size'], random_state=cfg['random_state'], stratify=y)
accounts = df_hold['account'].tolist()
hybrid_scores_dict = hybrid_predictor.predict(G, accounts)
# Map all to the holdout set
gnn_probs = np.array([gnn_prob_dict.get(a, 0.0) for a in accounts])
hybrid_probs = np.array([hybrid_scores_dict.get(a, 0.0) for a in accounts])
y_true = df_hold.set_index('account').loc[accounts]['fraud_flag'].values
# 4. Blend using the server.py calibration weights (normalized to sum to 1 since we don't have orig heuristic score here)
# Weights in server.py: XGB(0.20), GNN(0.15), Hybrid(0.25) -> Total ML weight = 0.60
# Normalized weights: XGB(0.333), GNN(0.25), Hybrid(0.417)
blended_probs = (xgb_probs * (0.20/0.60)) + (gnn_probs * (0.15/0.60)) + (hybrid_probs * (0.25/0.60))
blended_preds = (blended_probs >= 0.5).astype(int)
print("Evaluating Ensemble (XGB 33%, GNN 25%, Hybrid 42%) on XGBoost Holdout set.")
print(f"AUC: {roc_auc_score(y_true, blended_probs):.4f}")
print(f"F1: {f1_score(y_true, blended_preds, zero_division=0):.4f}")
print(f"Precision: {precision_score(y_true, blended_preds, zero_division=0):.4f}")
print(f"Recall: {recall_score(y_true, blended_preds, zero_division=0):.4f}")
print(f"Confusion Matrix:\n{confusion_matrix(y_true, blended_preds)}")
if __name__ == "__main__":
eval_ensemble()