Early accessSome features may be unavailable
Back to Blog
adversarialfraudONNXML

Training Fraud Models with Adversarial Synthetic Data

VynFi 3.0's adversarial mode takes an ONNX model, probes its decision boundary, and generates targeted synthetic examples where the model is least confident. This post covers the full pipeline from model upload to augmented retraining.

VynFi Team · EngineeringApril 16, 20269 min read

Fraud detection models degrade in production for a predictable reason: adversaries adapt. The transactions that slip through your classifier today are precisely the ones that sit near its decision boundary — the region where the model is least confident and most vulnerable. Traditional augmentation (SMOTE, random oversampling) generates synthetic examples uniformly across the feature space, which wastes training budget on regions the model already handles well.

VynFi 3.0's adversarial mode flips this approach. You supply a trained model in ONNX format, and the engine probes its decision surface to identify boundary regions. It then generates synthetic financial transactions specifically in those regions — transactions that are structurally valid (balanced entries, proper document flows, Benford-compliant amounts) but sit exactly where the model struggles.

**DataSynth 3.1.1 update:** Adversarial training now pairs cleanly with the scheme-vs-direct fraud split. `is_fraud_propagated = true` entries (document ring fan-out) are structurally different from direct line-level injections, and the behavioural biases (weekend ×32, round-dollar ×170, post-close ×3,106 lift) ensure both populations carry real discriminative signal. Train two-stage detectors: generic boundary probe + propagation-stratified retrain via `client.jobs.fraud_split(job_id)`. See adversarial_fraud_training.py.

How Boundary Probing Works

The adversarial engine performs three steps. First, it runs a grid of synthetic examples through the ONNX model and collects prediction probabilities. Second, it identifies the decision boundary by finding the manifold where P(fraud) is close to 0.5 — the region of maximum uncertainty. Third, it uses a constrained sampling strategy to generate new examples concentrated around that boundary, subject to the structural validity constraints of the financial domain (entries must balance, dates must be sequential, reference chains must be intact).

The <code>boundary_sigma</code> parameter controls how tightly the generated examples cluster around the decision boundary. A smaller sigma produces examples very close to the boundary (harder cases); a larger sigma produces a broader spread (more coverage of the uncertain region).

Uploading a Model and Generating Adversarial Data

Python
import vynfi
client = vynfi.VynFi()
# Upload an ONNX fraud classifier
model = client.models.upload(
path="fraud_classifier_v7.onnx",
name="fraud_classifier_v7",
task="binary_classification",
target_class="fraud",
feature_schema={
"amount": "float",
"hour_of_day": "int",
"is_weekend": "bool",
"counterparty_risk_score": "float",
"days_since_account_open": "int",
"transaction_frequency_7d": "float",
},
)
# Generate adversarial examples near the decision boundary
job = client.jobs.create(
mode="adversarial",
model_id=model.id,
n_samples=10_000,
boundary_sigma=0.05,
sector="banking",
constraints={"balanced_entries": True, "benford_compliance": True},
)
result = client.jobs.wait(job.id)
archive = client.jobs.download_archive(result.id)

Analyzing the Boundary Region

The output includes a <code>boundary_analysis.json</code> file that describes the decision boundary geometry: which features contribute most to boundary proximity, cluster centers in the uncertain region, and the model's calibration curve across the generated samples. This analysis is useful even if you do not retrain — it tells you where your model is weakest.

Python
import pandas as pd
import json
# Load adversarial samples and boundary analysis
adversarial_df = pd.read_parquet(archive.file("adversarial_samples.parquet"))
analysis = json.loads(archive.text("boundary_analysis.json"))
print(f"Generated {len(adversarial_df)} adversarial samples")
print(f"Mean model confidence: {adversarial_df['model_prob'].mean():.3f}")
print(f"Samples within 0.1 of boundary: {(adversarial_df['model_prob'].between(0.4, 0.6)).sum()}")
# Top features driving boundary proximity
for feat in analysis["boundary_features"][:5]:
print(f" {feat['name']}: importance={feat['importance']:.3f}, range=[{feat['low']:.2f}, {feat['high']:.2f}]")

Retraining with Augmented Data

Python
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import precision_recall_fscore_support
import numpy as np
# Combine original training data with adversarial augmentation
original_train = pd.read_parquet("training_data.parquet")
augmented = pd.concat([original_train, adversarial_df], ignore_index=True)
features = ["amount", "hour_of_day", "is_weekend",
"counterparty_risk_score", "days_since_account_open",
"transaction_frequency_7d"]
X_aug = augmented[features].values
y_aug = augmented["label"].values
# Train on augmented data
model_v8 = GradientBoostingClassifier(n_estimators=500, max_depth=6)
model_v8.fit(X_aug, y_aug)
# Evaluate on held-out test set
X_test = pd.read_parquet("test_data.parquet")[features].values
y_test = pd.read_parquet("test_data.parquet")["label"].values
y_pred = model_v8.predict(X_test)
p, r, f1, _ = precision_recall_fscore_support(y_test, y_pred, pos_label=1, average="binary")
print(f"Precision: {p:.3f}, Recall: {r:.3f}, F1: {f1:.3f}")

In internal benchmarks, adversarial augmentation improved F1 scores on held-out boundary-region test sets by 12-18% compared to uniform SMOTE augmentation, while maintaining equivalent performance on the non-boundary majority of the distribution. The key advantage is efficiency: rather than generating millions of random synthetic examples and hoping some land near the boundary, adversarial mode targets precisely the region that matters.

Ready to try VynFi?

Start generating synthetic financial data with 10,000 free credits. No credit card required.