Artha_ai / scripts /train_fraud_model.py
Gaurav711's picture
feat(prod-hardening): production hardening, SHA-256 model verification gate, hybrid ChromaDB store, decoupled credentials, k8s scaffolding, and MODEL_CARD.md
00a11a2
Raw
History Blame Contribute Delete
6.16 kB
import os
import joblib
import hashlib
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier, IsolationForest
from sklearn.metrics import classification_report, f1_score, precision_score, recall_score
from faker import Faker
import logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
# Initialize Faker and seed for reproducibility
fake = Faker()
Faker.seed(2026)
np.random.seed(2026)
def generate_synthetic_data(num_records: int = 5000) -> pd.DataFrame:
"""Generates synthetic Indian transaction data with embedded fraud patterns."""
logger.info(f"Generating {num_records} synthetic transaction records...")
records = []
for i in range(num_records):
# Determine if this record will be fraudulent (approx 5% fraud rate)
is_fraud = 1 if np.random.rand() < 0.05 else 0
if is_fraud:
# Fraud patterns
fraud_type = np.random.choice(["high_amount", "velocity", "location_anomaly", "time_anomaly"])
if fraud_type == "high_amount":
amount = float(np.random.uniform(50000, 1000000)) # ₹50K to ₹10L
hour = int(np.random.randint(0, 24))
velocity_1h = int(np.random.randint(1, 4))
distance_from_home = float(np.random.uniform(0, 50))
merchant_risk = float(np.random.uniform(0.1, 0.4))
elif fraud_type == "velocity":
amount = float(np.random.uniform(100, 2000))
hour = int(np.random.randint(0, 24))
velocity_1h = int(np.random.randint(8, 25)) # high velocity
distance_from_home = float(np.random.uniform(0, 10))
merchant_risk = float(np.random.uniform(0.2, 0.7))
elif fraud_type == "location_anomaly":
amount = float(np.random.uniform(1000, 20000))
hour = int(np.random.randint(0, 24))
velocity_1h = int(np.random.randint(1, 3))
distance_from_home = float(np.random.uniform(200, 5000)) # very far
merchant_risk = float(np.random.uniform(0.3, 0.8))
else: # time anomaly
amount = float(np.random.uniform(5000, 50000))
hour = int(np.random.choice([1, 2, 3, 4])) # 1AM - 4AM
velocity_1h = int(np.random.randint(2, 5))
distance_from_home = float(np.random.uniform(10, 150))
merchant_risk = float(np.random.uniform(0.5, 0.9))
else:
# Legitimate transactions
amount = float(np.random.uniform(10, 10000)) # ₹10 to ₹10K
hour = int(np.random.choice([7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23])) # daylight/evening hours
velocity_1h = int(np.random.choice([1, 2, 3])) # low velocity
distance_from_home = float(np.random.uniform(0, 30)) # close to home coordinates
merchant_risk = float(np.random.uniform(0.01, 0.15)) # standard merchants
records.append({
"amount": amount,
"hour": hour,
"velocity_1h": velocity_1h,
"distance_from_home": distance_from_home,
"merchant_risk": merchant_risk,
"is_fraud": is_fraud
})
df = pd.DataFrame(records)
logger.info("Dataset generated successfully.")
return df
def train_and_save_ensemble():
# 1. Generate Dataset
df = generate_synthetic_data(10000)
# 2. Features and Target split
features = ["amount", "hour", "velocity_1h", "distance_from_home", "merchant_risk"]
X = df[features]
y = df["is_fraud"]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2026, stratify=y)
# 3. Train RandomForest Classifier
logger.info("Training RandomForest Classifier...")
rf_model = RandomForestClassifier(
n_estimators=100,
max_depth=6,
random_state=2026,
n_jobs=-1
)
rf_model.fit(X_train, y_train)
# 4. Train Isolation Forest Anomaly Detector
logger.info("Training Isolation Forest Anomaly Detector...")
# Contamination matches the ground truth fraud rate approx
iforest = IsolationForest(
n_estimators=100,
contamination=0.05,
random_state=2026
)
iforest.fit(X_train)
# 5. Evaluate RandomForest alone
y_pred_rf = rf_model.predict(X_test)
logger.info("Evaluation Metrics for RandomForest:")
logger.info("\n" + classification_report(y_test, y_pred_rf))
f1 = f1_score(y_test, y_pred_rf)
precision = precision_score(y_test, y_pred_rf)
recall = recall_score(y_test, y_pred_rf)
# 6. Serializing models
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
model_dir = os.path.join(base_dir, "app", "models")
os.makedirs(model_dir, exist_ok=True)
model_path = os.path.join(model_dir, "fraud_model.joblib")
logger.info(f"Saving models package to {model_path}...")
# Save ensemble structure
ensemble = {
"rf": rf_model,
"iforest": iforest,
"features": features,
"metrics": {
"f1": float(f1),
"precision": float(precision),
"recall": float(recall)
}
}
# Secure serialization using joblib
joblib.dump(ensemble, model_path)
# Compute SHA-256 integrity hash of the generated joblib file
sha256_hash = hashlib.sha256()
with open(model_path, "rb") as f:
for byte_block in iter(lambda: f.read(4096), b""):
sha256_hash.update(byte_block)
hash_hex = sha256_hash.hexdigest()
hash_path = model_path + ".sha256"
with open(hash_path, "w") as hf:
hf.write(hash_hex)
logger.info(f"SHA-256 model checksum computed and saved: {hash_hex}")
logger.info("Models saved successfully. Training completed successfully.")
if __name__ == "__main__":
train_and_save_ensemble()