FraudTrainer / app.py
eaglelandsonce's picture
Update app.py
891de88 verified
Raw
History Blame Contribute Delete
12.9 kB
"""
app.py - Gradio interface for PyTorch fraud detection demo
Requirements:
pip install torch scikit-learn pandas numpy gradio
Run:
python app.py
Then open the URL that Gradio prints (usually http://127.0.0.1:7860)
"""
import os
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import gradio as gr
# =========================
# 1. Load or create data
# =========================
CSV_PATH = "synthetic_fraud_transactions_1000.csv" # adjust if needed
def load_or_create_data(path: str) -> pd.DataFrame:
if os.path.exists(path):
print(f"[INFO] Loading existing CSV: {path}")
return pd.read_csv(path)
print(f"[INFO] CSV not found at {path}. Generating synthetic data...")
np.random.seed(42)
n = 1000
# Generate synthetic features
amount = np.random.lognormal(mean=3, sigma=0.7, size=n) # skewed positive
amount_log = np.log1p(amount)
is_foreign_currency = np.random.binomial(1, 0.1, size=n)
txn_hour_of_day = np.random.randint(0, 24, size=n)
# Time since last txn in seconds (0 to 2 days, skewed toward small)
time_since_last_txn_sec = np.random.exponential(scale=2 * 60 * 60, size=n)
time_since_last_txn_sec = np.clip(time_since_last_txn_sec, 10, 2 * 24 * 60 * 60).astype(int)
# Distance between transactions in km (0–10,000)
geo_distance_from_last_txn_km = np.random.exponential(scale=50, size=n)
geo_distance_from_last_txn_km = np.clip(geo_distance_from_last_txn_km, 0, 10000)
# Transactions and spend in windows
txn_count_last_10min = np.random.poisson(lam=0.3, size=n)
total_spend_last_24h = np.random.gamma(shape=2.0, scale=50.0, size=n)
is_new_merchant_for_card = np.random.binomial(1, 0.2, size=n)
merchant_fraud_rate_30d = np.random.beta(a=1.2, b=50, size=n) # mostly low rates
is_new_device_for_card = np.random.binomial(1, 0.15, size=n)
num_cards_on_device_24h = np.random.poisson(lam=1.1, size=n)
# Simple fraud label: higher probability when risky patterns occur
base_prob = 0.02
risk_score = (
0.03 * (time_since_last_txn_sec < 120)
+ 0.04 * (geo_distance_from_last_txn_km > 500)
+ 0.05 * is_new_device_for_card
+ 0.04 * is_new_merchant_for_card
+ 0.03 * (num_cards_on_device_24h > 3)
+ 0.03 * is_foreign_currency
)
fraud_prob = np.clip(base_prob + risk_score, 0, 0.9)
fraud_label = (np.random.rand(n) < fraud_prob).astype(int)
df = pd.DataFrame(
{
"amount": amount.round(2),
"amount_log": amount_log.round(4),
"is_foreign_currency": is_foreign_currency,
"txn_hour_of_day": txn_hour_of_day,
"time_since_last_txn_sec": time_since_last_txn_sec,
"geo_distance_from_last_txn_km": geo_distance_from_last_txn_km.round(2),
"txn_count_last_10min": txn_count_last_10min,
"total_spend_last_24h": total_spend_last_24h.round(2),
"is_new_merchant_for_card": is_new_merchant_for_card,
"merchant_fraud_rate_30d": merchant_fraud_rate_30d.round(4),
"is_new_device_for_card": is_new_device_for_card,
"num_cards_on_device_24h": num_cards_on_device_24h,
"fraud_label": fraud_label,
}
)
# Save so future runs reuse the same data
df.to_csv(path, index=False)
print(f"[INFO] Synthetic dataset saved to: {path}")
return df
df = load_or_create_data(CSV_PATH)
feature_cols = [
"amount",
"amount_log",
"is_foreign_currency",
"txn_hour_of_day",
"time_since_last_txn_sec",
"geo_distance_from_last_txn_km",
"txn_count_last_10min",
"total_spend_last_24h",
"is_new_merchant_for_card",
"merchant_fraud_rate_30d",
"is_new_device_for_card",
"num_cards_on_device_24h",
]
target_col = "fraud_label"
X = df[feature_cols].values.astype(np.float32)
y = df[target_col].values.astype(np.float32)
X_train, X_val, y_train, y_val = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_val_scaled = scaler.transform(X_val)
# =========================
# 2. PyTorch Dataset & Model
# =========================
class FraudDataset(Dataset):
def __init__(self, X, y):
self.X = torch.from_numpy(X).float()
self.y = torch.from_numpy(y).float()
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
train_dataset = FraudDataset(X_train_scaled, y_train)
val_dataset = FraudDataset(X_val_scaled, y_val)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False)
class FraudNet(nn.Module):
def __init__(self, input_dim):
super(FraudNet, self).__init__()
self.net = nn.Sequential(
nn.Linear(input_dim, 64),
nn.BatchNorm1d(64),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(64, 32),
nn.BatchNorm1d(32),
nn.ReLU(),
nn.Dropout(0.2),
nn.Linear(32, 1), # binary logit
)
def forward(self, x):
return self.net(x).squeeze(1)
input_dim = len(feature_cols)
model = FraudNet(input_dim)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
# Handle class imbalance with pos_weight
pos_weight_value = (len(y_train) - y_train.sum()) / (y_train.sum() + 1e-6)
pos_weight = torch.tensor(pos_weight_value, dtype=torch.float32).to(device)
criterion = nn.BCEWithLogitsLoss(pos_weight=pos_weight)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-4)
def train_one_epoch(model, loader, optimizer, criterion, device):
model.train()
running_loss = 0.0
for X_batch, y_batch in loader:
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)
optimizer.zero_grad()
logits = model(X_batch)
loss = criterion(logits, y_batch)
loss.backward()
optimizer.step()
running_loss += loss.item() * X_batch.size(0)
return running_loss / len(loader.dataset)
def evaluate(model, loader, criterion, device):
model.eval()
running_loss = 0.0
all_logits = []
all_labels = []
with torch.no_grad():
for X_batch, y_batch in loader:
X_batch = X_batch.to(device)
y_batch = y_batch.to(device)
logits = model(X_batch)
loss = criterion(logits, y_batch)
running_loss += loss.item() * X_batch.size(0)
all_logits.append(logits.cpu())
all_labels.append(y_batch.cpu())
all_logits = torch.cat(all_logits)
all_labels = torch.cat(all_labels)
probs = torch.sigmoid(all_logits)
preds = (probs >= 0.5).int()
accuracy = (preds == all_labels.int()).float().mean().item()
return running_loss / len(loader.dataset), accuracy
# =========================
# 3. Train the model once
# =========================
EPOCHS = 15 # keep small so startup is fast
for epoch in range(1, EPOCHS + 1):
train_loss = train_one_epoch(model, train_loader, optimizer, criterion, device)
val_loss, val_acc = evaluate(model, val_loader, criterion, device)
print(
f"Epoch {epoch:02d} | "
f"Train Loss: {train_loss:.4f} | "
f"Val Loss: {val_loss:.4f} | "
f"Val Acc: {val_acc:.4f}"
)
model.eval() # set evaluation mode
# =========================
# 4. Inference function
# =========================
def predict_fraud(
amount,
is_foreign_currency,
txn_hour_of_day,
time_since_last_txn_min,
geo_distance_from_last_txn_km,
txn_count_last_10min,
total_spend_last_24h,
is_new_merchant_for_card,
merchant_fraud_rate_30d,
is_new_device_for_card,
num_cards_on_device_24h,
):
"""
This function will be called by the Gradio interface.
It builds a feature vector, scales it, runs the model,
and returns a probability + label string.
"""
# Derived features
amount_log = np.log1p(amount)
time_since_last_txn_sec = time_since_last_txn_min * 60.0
# Build feature vector in the SAME ORDER as feature_cols
x = np.array(
[
amount,
amount_log,
float(is_foreign_currency),
float(txn_hour_of_day),
time_since_last_txn_sec,
geo_distance_from_last_txn_km,
float(txn_count_last_10min),
total_spend_last_24h,
float(is_new_merchant_for_card),
merchant_fraud_rate_30d,
float(is_new_device_for_card),
float(num_cards_on_device_24h),
],
dtype=np.float32,
).reshape(1, -1)
# Scale
x_scaled = scaler.transform(x)
# To torch
x_tensor = torch.from_numpy(x_scaled).float().to(device)
with torch.no_grad():
logit = model(x_tensor)
prob = torch.sigmoid(logit).item()
label = "FRAUD" if prob >= 0.5 else "LEGIT"
explanation = (
f"Predicted probability of fraud: **{prob:.3f}**\n\n"
f"Model classification: **{label}** (threshold = 0.5)"
)
return label, prob, explanation
# =========================
# 5. Gradio UI
# =========================
with gr.Blocks(title="Fraud Detection Demo (PyTorch + Gradio)") as demo:
gr.Markdown(
"# 💳 Fraud Detection Demo\n"
"Play with transaction features and see how the model classifies them."
)
with gr.Row():
with gr.Column():
amount = gr.Slider(
minimum=1,
maximum=2000,
value=50,
step=1,
label="Transaction amount (in card currency)",
)
is_foreign_currency = gr.Checkbox(
value=False, label="Foreign currency?"
)
txn_hour_of_day = gr.Slider(
minimum=0,
maximum=23,
value=14,
step=1,
label="Transaction hour of day (0–23)",
)
time_since_last_txn_min = gr.Slider(
minimum=0.1,
maximum=2880,
value=60,
step=1,
label="Time since last transaction (minutes, up to 2 days)",
)
geo_distance_from_last_txn_km = gr.Slider(
minimum=0,
maximum=10000,
value=10,
step=1,
label="Distance from last transaction (km)",
)
txn_count_last_10min = gr.Slider(
minimum=0,
maximum=20,
value=1,
step=1,
label="Number of transactions in last 10 minutes",
)
with gr.Column():
total_spend_last_24h = gr.Slider(
minimum=0,
maximum=10000,
value=200,
step=10,
label="Total spend in last 24h",
)
is_new_merchant_for_card = gr.Checkbox(
value=False, label="Is this a new merchant for this card?"
)
merchant_fraud_rate_30d = gr.Slider(
minimum=0.0,
maximum=0.5,
value=0.02,
step=0.01,
label="Merchant fraud rate (last 30 days)",
)
is_new_device_for_card = gr.Checkbox(
value=False, label="Is this a new device for this card?"
)
num_cards_on_device_24h = gr.Slider(
minimum=1,
maximum=20,
value=1,
step=1,
label="Number of different cards on this device in last 24h",
)
predict_btn = gr.Button("Predict Fraud Risk")
label_out = gr.Textbox(label="Model Label (FRAUD / LEGIT)", interactive=False)
prob_out = gr.Number(label="Fraud Probability", precision=4, interactive=False)
explanation_out = gr.Markdown(label="Explanation")
predict_btn.click(
fn=predict_fraud,
inputs=[
amount,
is_foreign_currency,
txn_hour_of_day,
time_since_last_txn_min,
geo_distance_from_last_txn_km,
txn_count_last_10min,
total_spend_last_24h,
is_new_merchant_for_card,
merchant_fraud_rate_30d,
is_new_device_for_card,
num_cards_on_device_24h,
],
outputs=[label_out, prob_out, explanation_out],
)
if __name__ == "__main__":
demo.launch()