Spaces:
Sleeping
Sleeping
Download src/models/train.py from Nickanas/PaySim-Fraud: direct link, hf CLI and curl.
- Browser
- Download file 3.27 kB
-
https://huggingface.co/spaces/Nickanas/PaySim-Fraud/resolve/main/src/models/train.py
- Command line
-
hf download hf://spaces/Nickanas/PaySim-Fraud/src/models/train.py
-
curl -L -o train.py https://huggingface.co/spaces/Nickanas/PaySim-Fraud/resolve/main/src/models/train.py
3.27 kB
| import mlflow | |
| import torch | |
| from sklearn.metrics import average_precision_score, precision_score, recall_score | |
| from src.models.evaluate import get_probs, metrics_at, best_threshold | |
| def train_one_epoch(model, dataloader, loss_fn, optimizer, device): | |
| model.train() | |
| train_loss, correct, n_samples, n_batches = 0.0, 0, 0, 0 | |
| all_probs, all_y = [], [] | |
| for X, y in dataloader: | |
| X, y = X.to(device), y.to(device) | |
| logits = model(X) | |
| loss = loss_fn(logits, y) | |
| optimizer.zero_grad() | |
| loss.backward() | |
| optimizer.step() | |
| train_loss += loss.item() | |
| n_batches += 1 | |
| preds = (logits > 0).float() | |
| correct += (preds == y).sum().item() | |
| n_samples += y.numel() | |
| all_probs.append(torch.sigmoid(logits).detach().cpu()) | |
| all_y.append(y.detach().cpu()) | |
| probs = torch.cat(all_probs).numpy().ravel() | |
| y_true = torch.cat(all_y).numpy().ravel() | |
| preds_np = (probs > 0.5).astype(int) | |
| return { | |
| "loss": train_loss / n_batches, | |
| "acc": correct / n_samples, | |
| "pr_auc": average_precision_score(y_true, probs), | |
| "precision": precision_score(y_true, preds_np, zero_division=0), | |
| "recall": recall_score(y_true, preds_np, zero_division=0), | |
| } | |
| def run_training(model, train_dl, test_dl, epochs, lr): | |
| torch.manual_seed(42) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| model.to(device) | |
| # pos_weight = negatives / positives, from the TRAIN split | |
| y_all = torch.cat([y for _, y in train_dl]) | |
| n_pos = y_all.sum().item() | |
| n_neg = y_all.numel() - n_pos | |
| pos_weight = torch.tensor([n_neg / max(n_pos, 1)], device=device) | |
| print(f"pos_weight = {pos_weight.item():.1f} ({int(n_pos)} pos / {int(n_neg)} neg)") | |
| loss_fn = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight) | |
| optimizer = torch.optim.Adam(model.parameters(), lr=lr) | |
| with mlflow.start_run(): | |
| for epoch in range(epochs): | |
| m = train_one_epoch(model, train_dl, loss_fn, optimizer, device) | |
| for k, v in m.items(): | |
| mlflow.log_metric(f"train_{k}", v, step=epoch) | |
| print(f"epoch {epoch}: loss={m['loss']:.4f} acc={m['acc']:.4f} " | |
| f"pr_auc={m['pr_auc']:.4f} P={m['precision']:.3f} R={m['recall']:.3f}") | |
| # ---------- evaluate on the held-out TEST set ---------- | |
| probs, y_true = get_probs(model, test_dl) | |
| # (a) the naive 0.5 cutoff | |
| base = metrics_at(probs, y_true, 0.5) | |
| print(f"\nTEST @0.50 : P={base['precision']:.3f} R={base['recall']:.3f} " | |
| f"F1={base['f1']:.3f} PR-AUC={base['pr_auc']:.3f}") | |
| # (b) the F1-best cutoff — same model, better trade-off | |
| thr = best_threshold(probs, y_true) | |
| tuned = metrics_at(probs, y_true, thr) | |
| print(f"TEST @{thr:.2f} : P={tuned['precision']:.3f} R={tuned['recall']:.3f} " | |
| f"F1={tuned['f1']:.3f}") | |
| # log BOTH so you can compare in MLflow | |
| for k, v in base.items(): | |
| mlflow.log_metric(f"test_{k}", v) | |
| for k, v in tuned.items(): | |
| mlflow.log_metric(f"test_tuned_{k}", v) | |
| return model |