Fraud scoring turns transaction features into a probability that a review system can rank, challenge, or block. A Keras binary classifier is a compact way to test that scoring path when the input is a tabular dataset and the positive class is much smaller than normal transaction traffic.
A synthetic transaction table keeps the run repeatable without customer records. The training code builds numeric risk features, weights the minority class through class_weight, tracks PR-AUC with precision and recall, and saves the trained model as a native .keras file.
Class weights affect the loss during training, while the score threshold controls how many transactions land in a manual review queue. Keep that threshold tied to a holdout set and review capacity; lowering it should raise recall only when the extra false positives are acceptable.
Related: How to set the Keras backend
Related: How to compile a model in Keras
Steps to train a Keras fraud classifier:
- Create train_fraud_classifier.py with a synthetic transaction dataset and weighted classifier.
- train_fraud_classifier.py
import os from pathlib import Path os.environ.setdefault("KERAS_BACKEND", "jax") import keras import numpy as np keras.utils.set_random_seed(7) rng = np.random.default_rng(42) rows = 6000 amount = rng.lognormal(mean=3.2, sigma=0.9, size=rows).astype("float32") hour = rng.integers(0, 24, size=rows).astype("float32") country_risk = rng.beta(1.2, 8.0, size=rows).astype("float32") merchant_risk = rng.beta(1.4, 7.0, size=rows).astype("float32") device_age_days = rng.exponential(scale=20.0, size=rows).astype("float32") chargeback_count = rng.poisson(0.12, size=rows).astype("float32") night_purchase = ((hour < 6) | (hour > 22)).astype("float32") amount_log = np.log1p(amount) amount_z = (amount_log - amount_log.mean()) / amount_log.std() fraud_score = ( -5.5 + 1.2 * amount_z + 6.4 * country_risk + 5.7 * merchant_risk + 1.2 * night_purchase + 1.6 * (device_age_days < 2) + 1.5 * (chargeback_count > 0) ) fraud_probability = 1.0 / (1.0 + np.exp(-fraud_score)) target = rng.binomial(1, fraud_probability).astype("float32") features = np.column_stack( [ amount_log, hour / 23.0, country_risk, merchant_risk, np.log1p(device_age_days), chargeback_count, night_purchase, ] ).astype("float32") order = rng.permutation(rows) train_size = int(rows * 0.8) train_index = order[:train_size] validation_index = order[train_size:] x_train = features[train_index] y_train = target[train_index] x_validation = features[validation_index] y_validation = target[validation_index] negative = float((y_train == 0).sum()) positive = float((y_train == 1).sum()) total = negative + positive class_weight = { 0: total / (2.0 * negative), 1: total / (2.0 * positive), } normalizer = keras.layers.Normalization() normalizer.adapt(x_train) model = keras.Sequential( [ keras.layers.Input(shape=(x_train.shape[1],)), normalizer, keras.layers.Dense(32, activation="relu"), keras.layers.Dropout(0.2), keras.layers.Dense(16, activation="relu"), keras.layers.Dense(1, activation="sigmoid"), ] ) model.compile( optimizer=keras.optimizers.Adam(learning_rate=0.005), loss=keras.losses.BinaryCrossentropy(), metrics=[ keras.metrics.AUC(curve="PR", name="pr_auc"), keras.metrics.Precision(name="precision"), keras.metrics.Recall(name="recall"), ], jit_compile="auto", ) history = model.fit( x_train, y_train, validation_data=(x_validation, y_validation), epochs=8, batch_size=128, class_weight=class_weight, verbose=0, ) validation_probability = model.predict( x_validation, batch_size=256, verbose=0, ).ravel() threshold = 0.35 validation_prediction = validation_probability >= threshold actual_positive = y_validation == 1 actual_negative = y_validation == 0 true_positive = int(np.logical_and(validation_prediction, actual_positive).sum()) false_positive = int(np.logical_and(validation_prediction, actual_negative).sum()) false_negative = int(np.logical_and(~validation_prediction, actual_positive).sum()) true_negative = int(np.logical_and(~validation_prediction, actual_negative).sum()) threshold_precision = true_positive / max(true_positive + false_positive, 1) threshold_recall = true_positive / max(true_positive + false_negative, 1) model_path = Path("fraud_classifier.keras") model.save(model_path, overwrite=True) loaded_model = keras.saving.load_model(model_path) sample_transactions = np.array( [ [np.log1p(38.20), 14 / 23.0, 0.04, 0.08, np.log1p(120.0), 0.0, 0.0], [np.log1p(620.00), 2 / 23.0, 0.54, 0.61, np.log1p(0.5), 2.0, 1.0], ], dtype="float32", ) sample_scores = loaded_model.predict(sample_transactions, verbose=0).ravel() final_epoch = len(history.history["loss"]) final_val_pr_auc = history.history["val_pr_auc"][-1] final_val_precision = history.history["val_precision"][-1] final_val_recall = history.history["val_recall"][-1] print(f"backend: {keras.config.backend()}") print(f"train rows: {len(x_train)}") print(f"validation rows: {len(x_validation)}") print(f"training fraud rate: {y_train.mean():.3f}") print(f"class weights: legit={class_weight[0]:.2f}, fraud={class_weight[1]:.2f}") print(f"epochs completed: {final_epoch}") print(f"val pr_auc: {final_val_pr_auc:.3f}") print(f"val precision@0.5: {final_val_precision:.3f}") print(f"val recall@0.5: {final_val_recall:.3f}") print( f"threshold 0.35 counts: tp={true_positive}, " f"fp={false_positive}, fn={false_negative}, tn={true_negative}" ) print( "threshold 0.35 metrics: " f"precision={threshold_precision:.3f}, recall={threshold_recall:.3f}" ) print(f"saved model: {model_path.name}") print( "sample fraud scores: " f"routine={sample_scores[0]:.3f}, suspicious={sample_scores[1]:.3f}" )
The KERAS_BACKEND assignment must happen before import keras. Install Keras and the selected backend in the project environment before running the script.
Related: How to install Keras with pip
Related: How to set the Keras backend
Related: How to compile a model in Keras - Run the training script.
$ python train_fraud_classifier.py backend: jax train rows: 4800 validation rows: 1200 training fraud rate: 0.115 class weights: legit=0.56, fraud=4.35 epochs completed: 8 val pr_auc: 0.425 val precision@0.5: 0.301 val recall@0.5: 0.743 threshold 0.35 counts: tp=118, fp=349, fn=22, tn=711 threshold 0.35 metrics: precision=0.253, recall=0.843 saved model: fraud_classifier.keras sample fraud scores: routine=0.050, suspicious=1.000
- Compare the default metric threshold with the lower review threshold.
val precision@0.5: 0.301 val recall@0.5: 0.743 threshold 0.35 counts: tp=118, fp=349, fn=22, tn=711 threshold 0.35 metrics: precision=0.253, recall=0.843
The Precision and Recall metric objects report at the default 0.5 threshold. The separate threshold block shows the effect of a lower cutoff for manual review.
- Confirm that the saved model and scored transactions appear in the output.
saved model: fraud_classifier.keras sample fraud scores: routine=0.050, suspicious=1.000
The Normalization layer is stored inside the .keras model, but the scoring code must keep the same feature order used during training.
Related: How to save and load a Keras model
Related: How to run batch prediction in Keras
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.