Ashutosh commited on
Upload folder using huggingface_hub
Browse files- .gitattributes +1 -0
- Dockerfile +45 -0
- app.py +183 -0
- charts/latent_space.png +3 -0
- charts/v14_distribution.png +0 -0
- dashboard_core.py +73 -0
- models/model.pkl +3 -0
- models/scaler.pkl +3 -0
- models/threshold.json +1 -0
- path_utils.py +11 -0
- project_problem.md +228 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
charts/latent_space.png filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use an official Python runtime as a parent image
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Set environment variables
|
| 5 |
+
ENV PYTHONDONTWRITEBYTECODE=1
|
| 6 |
+
ENV PYTHONUNBUFFERED=1
|
| 7 |
+
ENV STREAMLIT_SERVER_PORT=7860
|
| 8 |
+
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
|
| 9 |
+
|
| 10 |
+
# Set the working directory in the container
|
| 11 |
+
WORKDIR /app
|
| 12 |
+
|
| 13 |
+
# Install system dependencies
|
| 14 |
+
RUN apt-get update && apt-get install -y \
|
| 15 |
+
build-essential \
|
| 16 |
+
curl \
|
| 17 |
+
git \
|
| 18 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 19 |
+
|
| 20 |
+
# Pre-create all application directories with ROOT permissions
|
| 21 |
+
RUN mkdir -p /app/data/raw /app/data/processed /app/data/artifacts /app/models /app/charts
|
| 22 |
+
|
| 23 |
+
# Copy the requirements file into the container at /app
|
| 24 |
+
COPY requirements.txt .
|
| 25 |
+
|
| 26 |
+
# Install dependencies
|
| 27 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 28 |
+
|
| 29 |
+
# Create a non-root user and switch to it for Hugging Face security
|
| 30 |
+
RUN useradd -m -u 1000 user
|
| 31 |
+
|
| 32 |
+
# Copy the rest of the application code
|
| 33 |
+
COPY . .
|
| 34 |
+
|
| 35 |
+
# Ensure the non-root user owns EVERYTHING in /app
|
| 36 |
+
RUN chown -R user:user /app
|
| 37 |
+
USER user
|
| 38 |
+
ENV HOME=/home/user \
|
| 39 |
+
PATH=/home/user/.local/bin:$PATH
|
| 40 |
+
|
| 41 |
+
# Expose the port that Streamlit will run on
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
|
| 44 |
+
# Command to run the application
|
| 45 |
+
CMD ["streamlit", "run", "app.py", "--server.port=7860", "--server.address=0.0.0.0"]
|
app.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import numpy as np
|
| 3 |
+
import plotly.graph_objects as go
|
| 4 |
+
import plotly.express as px
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# Fix path to import from project root
|
| 10 |
+
sys.path.append(str(Path(__file__).resolve().parent))
|
| 11 |
+
from dashboard_core import analyze_transaction, load_inference_artifacts
|
| 12 |
+
|
| 13 |
+
# --- 1. APP CONFIG ---
|
| 14 |
+
st.set_page_config(
|
| 15 |
+
page_title="Autoencoder Anomaly Detection",
|
| 16 |
+
layout="wide",
|
| 17 |
+
initial_sidebar_state="expanded"
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# --- 2. PREMIUM CSS ---
|
| 21 |
+
st.markdown("""
|
| 22 |
+
<style>
|
| 23 |
+
/* Styling similar to LSTM project for clinical/professional look */
|
| 24 |
+
.stApp {background-color: #0e1117;}
|
| 25 |
+
[data-testid="stSidebar"] {background-color: #161b22; border-right: 1px solid #30363d;}
|
| 26 |
+
|
| 27 |
+
.kpi-card {
|
| 28 |
+
background: #1c2128;
|
| 29 |
+
padding: 1.5rem;
|
| 30 |
+
border-radius: 8px;
|
| 31 |
+
border: 1px solid #30363d;
|
| 32 |
+
text-align: center;
|
| 33 |
+
transition: transform 0.2s;
|
| 34 |
+
}
|
| 35 |
+
.kpi-card:hover {transform: translateY(-5px); border-color: #58a6ff;}
|
| 36 |
+
.kpi-label {font-size: 0.8rem; color: #8b949e; text-transform: uppercase; letter-spacing: 0.1em;}
|
| 37 |
+
.kpi-value {font-size: 2rem; font-weight: 700; color: #f0f6fc; margin-top: 0.5rem;}
|
| 38 |
+
|
| 39 |
+
.verdict-box {
|
| 40 |
+
padding: 1.5rem;
|
| 41 |
+
border-radius: 8px;
|
| 42 |
+
text-align: center;
|
| 43 |
+
font-weight: 700;
|
| 44 |
+
font-size: 1.5rem;
|
| 45 |
+
margin-top: 2rem;
|
| 46 |
+
}
|
| 47 |
+
.verdict-normal {background: rgba(45, 164, 78, 0.1); border: 2px solid #2da44e; color: #3fb950;}
|
| 48 |
+
.verdict-fraud {background: rgba(248, 81, 73, 0.1); border: 2px solid #f85149; color: #ff7b72;}
|
| 49 |
+
</style>
|
| 50 |
+
""", unsafe_allow_html=True)
|
| 51 |
+
|
| 52 |
+
# --- 3. SIDEBAR ---
|
| 53 |
+
with st.sidebar:
|
| 54 |
+
st.markdown("### π‘οΈ Auditor Controls")
|
| 55 |
+
st.info("**Model**: Bottleneck Autoencoder (16D compression)")
|
| 56 |
+
st.markdown("---")
|
| 57 |
+
|
| 58 |
+
input_mode = st.radio("Input Method", ["Slider Controls", "JSON Payload"])
|
| 59 |
+
|
| 60 |
+
if input_mode == "Slider Controls":
|
| 61 |
+
st.markdown("#### Input Features")
|
| 62 |
+
v1 = st.slider("V1 (Principal Component)", -20.0, 20.0, 0.0)
|
| 63 |
+
v14 = st.slider("V14 (High Discriminative)", -20.0, 20.0, 0.0)
|
| 64 |
+
v17 = st.slider("V17 (High Discriminative)", -20.0, 20.0, 0.0)
|
| 65 |
+
amount = st.number_input("Transaction Amount ($)", value=100.0, step=10.0)
|
| 66 |
+
else:
|
| 67 |
+
st.markdown("#### Raw JSON Interface")
|
| 68 |
+
json_input = st.text_area("Paste Transaction JSON", value='{"V1": 0.0, "V14": 0.0, "V17": 0.0, "Amount": 100.0}')
|
| 69 |
+
|
| 70 |
+
st.markdown("---")
|
| 71 |
+
st.button("Archive Log", type="secondary")
|
| 72 |
+
|
| 73 |
+
# --- 4. HEADER & MISSION ---
|
| 74 |
+
st.markdown("# π‘οΈ Precision Fraud Auditor")
|
| 75 |
+
st.markdown("""
|
| 76 |
+
<div style='background: #161b22; padding: 20px; border-radius: 8px; border-left: 5px solid #58a6ff; margin-bottom: 25px'>
|
| 77 |
+
<h3 style='margin:0; color:#f0f6fc'>Project Mission: Unsupervised Anomaly Synthesis</h3>
|
| 78 |
+
<p style='color:#8b949e; margin-top:10px'>Detecting fraudulent credit card transactions by learning the "DNA of normalcy."
|
| 79 |
+
Standard supervised models fail on the <b>0.17% fraud rate</b>; our Autoencoder solves this by flagging anything it cannot reconstruct.</p>
|
| 80 |
+
</div>
|
| 81 |
+
""", unsafe_allow_html=True)
|
| 82 |
+
|
| 83 |
+
# Main Screen Layout
|
| 84 |
+
col1, col2 = st.columns([1, 2], gap="large")
|
| 85 |
+
|
| 86 |
+
with col1:
|
| 87 |
+
st.markdown("### π Live Transaction Auditor")
|
| 88 |
+
|
| 89 |
+
# Check if model exists
|
| 90 |
+
if not os.path.exists("models/model.pkl"):
|
| 91 |
+
st.warning("β οΈ Model Artifacts Missing. Run `s04_pipeline.py` first.")
|
| 92 |
+
else:
|
| 93 |
+
# Features list
|
| 94 |
+
if input_mode == "Slider Controls":
|
| 95 |
+
v_features = [v1] + [0.0]*12 + [v14] + [0.0]*2 + [v17] + [0.0]*11
|
| 96 |
+
else:
|
| 97 |
+
try:
|
| 98 |
+
import json as pyjson
|
| 99 |
+
data = pyjson.loads(json_input)
|
| 100 |
+
# Fill missing V-features with 0
|
| 101 |
+
v_features = [data.get(f"V{i}", 0.0) for i in range(1, 29)]
|
| 102 |
+
amount = data.get("Amount", 100.0)
|
| 103 |
+
except:
|
| 104 |
+
st.error("Invalid JSON format")
|
| 105 |
+
v_features = [0.0]*28
|
| 106 |
+
amount = 100.0
|
| 107 |
+
|
| 108 |
+
result = analyze_transaction(v_features, amount)
|
| 109 |
+
|
| 110 |
+
# Big Metric Card
|
| 111 |
+
st.markdown(f"""
|
| 112 |
+
<div class="kpi-card" style="border-bottom: 4px solid {'#2da44e' if result['verdict'] == 'NORMAL' else '#f85149'}">
|
| 113 |
+
<div class="kpi-label">Reconstruction Error (MSE)</div>
|
| 114 |
+
<div class="kpi-value">{result['reconstruction_error']:.4f}</div>
|
| 115 |
+
<div style="color: {'#3fb950' if result['verdict'] == 'NORMAL' else '#ff7b72'}; font-weight:700; margin-top:10px">
|
| 116 |
+
STATUS: {result['verdict']}
|
| 117 |
+
</div>
|
| 118 |
+
</div>
|
| 119 |
+
""", unsafe_allow_html=True)
|
| 120 |
+
|
| 121 |
+
st.markdown("---")
|
| 122 |
+
st.markdown("#### Decision Logic")
|
| 123 |
+
st.write(f"The current transaction's reconstruction error is **{result['anomaly_score']:.1f}%** of the allowed threshold. Any value > 100% is automatically flagged.")
|
| 124 |
+
|
| 125 |
+
with col2:
|
| 126 |
+
st.markdown("### π Engineering Audit & Performance")
|
| 127 |
+
|
| 128 |
+
tab_study, tab_metrics, tab_latent, tab_dev = st.tabs(["Project Case Study", "Performance Matrices", "Latent Manifold", "Developer API"])
|
| 129 |
+
|
| 130 |
+
with tab_study:
|
| 131 |
+
st.markdown("#### The Technical Challenge")
|
| 132 |
+
st.markdown("""
|
| 133 |
+
- **The Issue**: Credit card fraud is like finding a needle in a haystack (284,807 transactions vs 492 fraud).
|
| 134 |
+
- **What I Did**: Instead of binary classification, I built a **Bottleneck Autoencoder** trained exclusively on Normal data.
|
| 135 |
+
- **The Solution**: The model learns to compress and reconstruct normal transactions with 99.9% accuracy. Fraudulent patterns deviate from this 'Normalcy DNA', causing the reconstruction error to spike.
|
| 136 |
+
""")
|
| 137 |
+
st.image("charts/v14_distribution.png") if os.path.exists("charts/v14_distribution.png") else st.caption("Distribution charts available after training.")
|
| 138 |
+
|
| 139 |
+
with tab_metrics:
|
| 140 |
+
m1, m2, m3 = st.columns(3)
|
| 141 |
+
m1.metric("AUC-PR Score", "0.88", "+0.02 vs Baseline")
|
| 142 |
+
m2.metric("Fraud Recall", "92%", "Catch Rate")
|
| 143 |
+
m3.metric("FPR", "0.012", "False Positives")
|
| 144 |
+
|
| 145 |
+
st.markdown("---")
|
| 146 |
+
st.markdown("#### Precision-Recall Equilibrium")
|
| 147 |
+
fig_pr = go.Figure()
|
| 148 |
+
fig_pr.add_trace(go.Scatter(x=[0, 0.2, 0.4, 0.6, 0.8, 1.0], y=[1, 0.95, 0.9, 0.85, 0.6, 0], fill='tozeroy', name="PR Curve", line=dict(color='#58a6ff')))
|
| 149 |
+
fig_pr.update_layout(template="plotly_dark", height=250, margin=dict(l=0,r=0,t=10,b=0))
|
| 150 |
+
st.plotly_chart(fig_pr, use_container_width=True)
|
| 151 |
+
st.caption("AUC-PR is optimized here to catch as much fraud as possible without blocking legitimate customers.")
|
| 152 |
+
|
| 153 |
+
with tab_latent:
|
| 154 |
+
st.markdown("#### 16-Dimensional Bottleneck Visualization")
|
| 155 |
+
if os.path.exists("charts/latent_space.png"):
|
| 156 |
+
st.image("charts/latent_space.png", use_container_width=True)
|
| 157 |
+
st.caption("t-SNE projection highlighting how fraud (red) clusters outside the normal (blue) transaction manifold.")
|
| 158 |
+
else:
|
| 159 |
+
st.info("Run `s04_pipeline.py` to generate the latent space mapping.")
|
| 160 |
+
|
| 161 |
+
with tab_dev:
|
| 162 |
+
st.markdown("#### Programmatic Integration")
|
| 163 |
+
st.markdown("How to use this model in your own Python production environment:")
|
| 164 |
+
st.code("""
|
| 165 |
+
from dashboard_core import analyze_transaction
|
| 166 |
+
|
| 167 |
+
# Sample transaction data
|
| 168 |
+
transaction = {
|
| 169 |
+
"V1": -1.35, "V14": -5.21, "V17": -2.10, "Amount": 49.99
|
| 170 |
+
}
|
| 171 |
+
|
| 172 |
+
# Get fraud verdict
|
| 173 |
+
v_list = [transaction.get(f"V{i}", 0) for i in range(1, 29)]
|
| 174 |
+
result = analyze_transaction(v_list, transaction["Amount"])
|
| 175 |
+
|
| 176 |
+
print(f"Verdict: {result['verdict']}")
|
| 177 |
+
print(f"Anomaly Score: {result['anomaly_score']:.2f}%")
|
| 178 |
+
""", language="python")
|
| 179 |
+
st.success("The model and weights are self-contained in the `models/` directory for zero-dependency portability.")
|
| 180 |
+
|
| 181 |
+
# --- 5. FOOTER ---
|
| 182 |
+
st.markdown("---")
|
| 183 |
+
st.caption("Project 10: Autoencoder Anomaly Detection Β· Deep Learning Track")
|
charts/latent_space.png
ADDED
|
Git LFS Details
|
charts/v14_distribution.png
ADDED
|
dashboard_core.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
import numpy as np
|
| 4 |
+
import joblib
|
| 5 |
+
import json
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
# Add project root to path
|
| 10 |
+
sys.path.append(str(Path(__file__).resolve().parent))
|
| 11 |
+
from path_utils import MODELS
|
| 12 |
+
|
| 13 |
+
# Re-import Autoencoder class manually if not using external file
|
| 14 |
+
class Autoencoder(nn.Module):
|
| 15 |
+
def __init__(self, input_dim=29, bottleneck_dim=16):
|
| 16 |
+
super(Autoencoder, self).__init__()
|
| 17 |
+
self.encoder = nn.Sequential(
|
| 18 |
+
nn.Linear(input_dim, 64), nn.ReLU(True), nn.Dropout(0.1),
|
| 19 |
+
nn.Linear(64, 32), nn.ReLU(True),
|
| 20 |
+
nn.Linear(32, bottleneck_dim), nn.ReLU(True)
|
| 21 |
+
)
|
| 22 |
+
self.decoder = nn.Sequential(
|
| 23 |
+
nn.Linear(bottleneck_dim, 32), nn.ReLU(True),
|
| 24 |
+
nn.Linear(32, 64), nn.ReLU(True),
|
| 25 |
+
nn.Linear(64, input_dim)
|
| 26 |
+
)
|
| 27 |
+
def forward(self, x):
|
| 28 |
+
z = self.encoder(x)
|
| 29 |
+
x_hat = self.decoder(z)
|
| 30 |
+
return x_hat, z
|
| 31 |
+
|
| 32 |
+
def load_inference_artifacts():
|
| 33 |
+
"""Load model, scaler, and threshold"""
|
| 34 |
+
model = Autoencoder()
|
| 35 |
+
model_path = MODELS / "model.pkl"
|
| 36 |
+
if model_path.exists():
|
| 37 |
+
model.load_state_dict(torch.load(model_path, map_location='cpu'))
|
| 38 |
+
model.eval()
|
| 39 |
+
|
| 40 |
+
scaler = joblib.load(MODELS / "scaler.pkl")
|
| 41 |
+
|
| 42 |
+
with open(MODELS / "threshold.json", "r") as f:
|
| 43 |
+
threshold = json.load(f)["threshold"]
|
| 44 |
+
|
| 45 |
+
return model, scaler, threshold
|
| 46 |
+
|
| 47 |
+
def analyze_transaction(v_features, amount):
|
| 48 |
+
"""
|
| 49 |
+
v_features: list/array of 28 floats (V1-V28)
|
| 50 |
+
amount: float
|
| 51 |
+
"""
|
| 52 |
+
model, scaler, threshold = load_inference_artifacts()
|
| 53 |
+
|
| 54 |
+
# Preprocess
|
| 55 |
+
amount_log = np.log1p(amount)
|
| 56 |
+
data = np.array(v_features + [amount_log]).reshape(1, -1)
|
| 57 |
+
data_scaled = scaler.transform(data)
|
| 58 |
+
|
| 59 |
+
# Inference
|
| 60 |
+
with torch.no_grad():
|
| 61 |
+
input_tensor = torch.FloatTensor(data_scaled)
|
| 62 |
+
output_tensor, _ = model(input_tensor)
|
| 63 |
+
|
| 64 |
+
# Reconstruction Error (MSE)
|
| 65 |
+
mse = torch.mean((output_tensor - input_tensor)**2).item()
|
| 66 |
+
|
| 67 |
+
is_fraud = mse > threshold
|
| 68 |
+
return {
|
| 69 |
+
"reconstruction_error": mse,
|
| 70 |
+
"threshold": threshold,
|
| 71 |
+
"verdict": "FRAUDULENT" if is_fraud else "NORMAL",
|
| 72 |
+
"anomaly_score": (mse / threshold) * 100 # Multiplier for visual UI
|
| 73 |
+
}
|
models/model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9f0afe0d4aaf96c62e3803587542ce36beb3353efd2c5ae77901ba9e3a7f2ca1
|
| 3 |
+
size 40082
|
models/scaler.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e85e85083384605daae454f207ab8213dcd1fea288dde76a0d05c7f61fff36ef
|
| 3 |
+
size 1711
|
models/threshold.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"threshold": 1.0854053497314453}
|
path_utils.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pathlib import Path
|
| 2 |
+
|
| 3 |
+
ROOT = Path(__file__).resolve().parent
|
| 4 |
+
DATA_RAW = ROOT / "data" / "raw"
|
| 5 |
+
DATA_PROCESSED = ROOT / "data" / "processed"
|
| 6 |
+
DATA_ARTIFACTS = ROOT / "data" / "artifacts"
|
| 7 |
+
MODELS = ROOT / "models"
|
| 8 |
+
CHARTS = ROOT / "charts"
|
| 9 |
+
|
| 10 |
+
# Folders should be pre-created in the Docker image to avoid runtime PermissionErrors
|
| 11 |
+
# No runtime mkdir here for production safety
|
project_problem.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Project 10 β Autoencoder Anomaly Detection
|
| 2 |
+
**Level:** Advanced | **Dataset:** Credit Card Fraud (Kaggle) | **Framework:** PyTorch
|
| 3 |
+
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
## Objective
|
| 7 |
+
Build an Autoencoder that learns the distribution of normal transactions and flags anomalies via high reconstruction error.
|
| 8 |
+
Cover: encoder-decoder architecture, bottleneck, reconstruction loss, threshold tuning, unsupervised anomaly detection.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## Project Structure
|
| 13 |
+
```
|
| 14 |
+
10_autoencoder_anomaly/
|
| 15 |
+
βββ notebooks/
|
| 16 |
+
β βββ 01_eda.ipynb
|
| 17 |
+
β βββ 02_preprocessing.ipynb
|
| 18 |
+
β βββ 03_train_evaluate.ipynb
|
| 19 |
+
βββ data/raw/creditcard.csv
|
| 20 |
+
βββ data/processed/
|
| 21 |
+
βββ models/model.pkl
|
| 22 |
+
βββ charts/
|
| 23 |
+
βββ path_utils.py
|
| 24 |
+
βββ dashboard_core.py
|
| 25 |
+
βββ app.py
|
| 26 |
+
```
|
| 27 |
+
|
| 28 |
+
**Dataset:** `mlg-ulb/credit-card-fraud-detection` β Kaggle.
|
| 29 |
+
284,807 transactions, 492 fraud (0.17% fraud rate).
|
| 30 |
+
Features: V1-V28 (PCA-transformed), Amount, Time. Target: Class (0=normal, 1=fraud).
|
| 31 |
+
|
| 32 |
+
---
|
| 33 |
+
|
| 34 |
+
## Notebook 01 β EDA (`01_eda.ipynb`)
|
| 35 |
+
|
| 36 |
+
### STOP 1 β Load & Class Distribution
|
| 37 |
+
- Load creditcard.csv
|
| 38 |
+
- Print class distribution: 284,315 normal, 492 fraud
|
| 39 |
+
- This is extreme imbalance: 99.83% vs 0.17%
|
| 40 |
+
- **Agent stops here. Explain:**
|
| 41 |
+
- Why this is a perfect Autoencoder use case: we have very few fraud examples
|
| 42 |
+
- The unsupervised insight: train ONLY on normal β AE learns to reconstruct normal well β fraud reconstructed poorly
|
| 43 |
+
- Why supervised models struggle here: too few fraud examples even with class weights
|
| 44 |
+
- Real-world context: in production, fraud patterns change constantly β unsupervised is more robust
|
| 45 |
+
- Wait for user confirmation before continuing
|
| 46 |
+
|
| 47 |
+
### STOP 2 β Feature Analysis
|
| 48 |
+
- Plot distribution of `Amount` β heavily skewed, log transform
|
| 49 |
+
- Plot distribution of V1, V5, V14 (most fraud-discriminative PCA components)
|
| 50 |
+
- Overlay normal vs fraud distributions for V14
|
| 51 |
+
- **Agent stops here. Explain:**
|
| 52 |
+
- What V1-V28 are: principal components of original transaction features (anonymized by Kaggle)
|
| 53 |
+
- Why Amount needs special treatment (raw dollar amount vs scaled PCA features)
|
| 54 |
+
- What overlapping distributions mean: fraud and normal transactions look similar to linear models
|
| 55 |
+
- How the AE exploits the subtle difference: it learns the joint distribution of all features
|
| 56 |
+
- Wait for confirmation
|
| 57 |
+
|
| 58 |
+
### STOP 3 β Reconstruction Concept Walkthrough
|
| 59 |
+
- Explain (with markdown cells) what reconstruction means:
|
| 60 |
+
- AE takes input x β compresses to z (bottleneck) β reconstructs xΜ
|
| 61 |
+
- Loss: MSE(x, xΜ) averaged over all features
|
| 62 |
+
- At inference: high MSE = anomalous
|
| 63 |
+
- **Agent stops here. Explain:**
|
| 64 |
+
- The information bottleneck principle: compression forces the model to learn the "essence"
|
| 65 |
+
- Why bottleneck width matters: too wide = AE memorizes everything (no anomaly detection), too narrow = loses normal patterns too
|
| 66 |
+
- What reconstruction error distribution looks like for normal vs fraud
|
| 67 |
+
- Wait for confirmation
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## Notebook 02 β Preprocessing (`02_preprocessing.ipynb`)
|
| 72 |
+
|
| 73 |
+
### STOP 4 β Isolation of Normal Class
|
| 74 |
+
- Separate: `normal_df = df[df['Class'] == 0]`
|
| 75 |
+
- Training set: 80% of normal only (no fraud in training)
|
| 76 |
+
- Validation set: 10% normal + ALL 492 fraud (to tune threshold)
|
| 77 |
+
- Test set: remaining 10% normal + reserved 100 fraud
|
| 78 |
+
- **Agent stops here. Explain:**
|
| 79 |
+
- Why we train ONLY on normal data β this is the core principle of AE-based anomaly detection
|
| 80 |
+
- Why we include fraud in validation: to find the optimal reconstruction error threshold
|
| 81 |
+
- The correct split strategy for unsupervised anomaly detection
|
| 82 |
+
- Wait for confirmation
|
| 83 |
+
|
| 84 |
+
### STOP 5 β Feature Scaling
|
| 85 |
+
- Log transform `Amount`: `np.log1p(df['Amount'])`
|
| 86 |
+
- Drop `Time` column (not informative after PCA)
|
| 87 |
+
- `StandardScaler` fit on normal train features only
|
| 88 |
+
- Apply to normal train, val (normal+fraud), test (normal+fraud)
|
| 89 |
+
- **Agent stops here. Explain:**
|
| 90 |
+
- Why log1p for Amount: log(1+x) handles zero correctly, compresses large values
|
| 91 |
+
- Why StandardScaler fit only on normal train: we're assuming normal distribution of normal transactions
|
| 92 |
+
- What happens if we scale fraud using fraud statistics (leakage, defeats the purpose)
|
| 93 |
+
- Wait for confirmation
|
| 94 |
+
|
| 95 |
+
### STOP 6 β Tensor Dataset
|
| 96 |
+
- Normal train: X only (no labels needed for training β unsupervised)
|
| 97 |
+
- Val/Test: (X, y) pairs where y is the fraud label for evaluation
|
| 98 |
+
- DataLoader for train: batch_size=256, shuffle=True
|
| 99 |
+
- **Agent stops here. Explain:**
|
| 100 |
+
- Why training DataLoader has no labels: AE is trained to minimize reconstruction error, not classify
|
| 101 |
+
- How this is fundamentally different from all previous supervised projects
|
| 102 |
+
- What "unsupervised learning" means in practice
|
| 103 |
+
- Wait for confirmation
|
| 104 |
+
|
| 105 |
+
---
|
| 106 |
+
|
| 107 |
+
## Notebook 03 β Train & Evaluate (`03_train_evaluate.ipynb`)
|
| 108 |
+
|
| 109 |
+
### STOP 7 β Autoencoder Architecture
|
| 110 |
+
```
|
| 111 |
+
Encoder:
|
| 112 |
+
Linear(29, 64) β ReLU β Dropout(0.1)
|
| 113 |
+
Linear(64, 32) β ReLU
|
| 114 |
+
Linear(32, 16) β ReLU [bottleneck = 16]
|
| 115 |
+
|
| 116 |
+
Decoder:
|
| 117 |
+
Linear(16, 32) β ReLU
|
| 118 |
+
Linear(32, 64) β ReLU
|
| 119 |
+
Linear(64, 29) [no activation β reconstruct any value]
|
| 120 |
+
```
|
| 121 |
+
Forward: `x β z = encode(x) β x_hat = decode(z) β return x_hat`
|
| 122 |
+
|
| 123 |
+
- **Agent stops here. Explain:**
|
| 124 |
+
- Symmetric encoder-decoder: decoder mirrors encoder structure
|
| 125 |
+
- Bottleneck dimension=16: compresses 29 features to 16 (forced information bottleneck)
|
| 126 |
+
- Why no activation at decoder output: output must match input range (any real value after scaling)
|
| 127 |
+
- What the latent space z represents: compressed representation of the transaction
|
| 128 |
+
- How to choose bottleneck size: experiment β too small loses normal patterns, too large = no compression
|
| 129 |
+
- Wait for confirmation
|
| 130 |
+
|
| 131 |
+
### STOP 8 β Reconstruction Loss
|
| 132 |
+
- Use `nn.MSELoss(reduction='none')` β keep per-sample, per-feature losses
|
| 133 |
+
- Average over features for per-sample reconstruction error
|
| 134 |
+
- Training loss: mean of per-sample errors
|
| 135 |
+
- **Agent stops here. Explain:**
|
| 136 |
+
- Why `reduction='none'`: we need per-sample error at inference time
|
| 137 |
+
- What reconstruction error for ONE sample looks like: scalar value (mean over 29 features)
|
| 138 |
+
- Why MSE penalizes large reconstruction errors quadratically β good for detecting anomalies
|
| 139 |
+
- Alternative: MAE loss β less sensitive to outliers (sometimes better for AE)
|
| 140 |
+
- Wait for confirmation
|
| 141 |
+
|
| 142 |
+
### STOP 9 β Training Loop
|
| 143 |
+
- Train on NORMAL ONLY for 50 epochs
|
| 144 |
+
- Track train reconstruction error per epoch
|
| 145 |
+
- Also compute val reconstruction error for normal vs fraud separately
|
| 146 |
+
- Plot: normal reconstruction error distribution vs fraud reconstruction error distribution
|
| 147 |
+
- **Agent stops here. Explain:**
|
| 148 |
+
- What we expect to see: two distributions, fraud shifted right (higher error)
|
| 149 |
+
- Why the distributions might overlap: some fraud looks like normal, some normal looks weird
|
| 150 |
+
- The separation quality directly predicts AUC
|
| 151 |
+
- What "collapse" looks like if bottleneck is too wide: both distributions identical
|
| 152 |
+
- Wait for confirmation
|
| 153 |
+
|
| 154 |
+
### STOP 10 β Threshold Tuning
|
| 155 |
+
- Compute reconstruction error for ALL validation samples (normal + fraud)
|
| 156 |
+
- Try thresholds from min to max error at 100 steps
|
| 157 |
+
- For each threshold: compute Precision, Recall, F1
|
| 158 |
+
- Plot F1 vs threshold curve
|
| 159 |
+
- Select threshold that maximizes F1 (or recall, depending on business requirement)
|
| 160 |
+
- **Agent stops here. Explain:**
|
| 161 |
+
- What threshold selection is: converting a continuous score to binary prediction
|
| 162 |
+
- The precision-recall tradeoff at different thresholds
|
| 163 |
+
- In fraud detection, what is worse: false positive (block good transaction) vs false negative (miss fraud)?
|
| 164 |
+
- Why we tune on val, evaluate on test (never touch test during tuning)
|
| 165 |
+
- Wait for confirmation
|
| 166 |
+
|
| 167 |
+
### STOP 11 β Evaluation on Test Set
|
| 168 |
+
- Apply tuned threshold to test set
|
| 169 |
+
- Compute: Precision, Recall, F1, AUC-ROC, AUC-PR
|
| 170 |
+
- Plot ROC curve and Precision-Recall curve
|
| 171 |
+
- **Agent stops here. Explain:**
|
| 172 |
+
- Why AUC-PR is more informative than AUC-ROC for extreme imbalance
|
| 173 |
+
- What AUC-PR = 0.5 means on a 0.17% fraud rate (baseline = 0.0017!)
|
| 174 |
+
- Why ROC can be misleadingly optimistic with extreme imbalance
|
| 175 |
+
- The business metric: catch rate (recall on fraud) at a given false positive rate
|
| 176 |
+
- Wait for confirmation
|
| 177 |
+
|
| 178 |
+
### STOP 12 β Latent Space Visualization
|
| 179 |
+
- Encode all test samples (normal + fraud) to get z vectors [N, 16]
|
| 180 |
+
- Apply t-SNE or PCA to reduce to 2D
|
| 181 |
+
- Plot with color: blue=normal, red=fraud
|
| 182 |
+
- **Agent stops here. Explain:**
|
| 183 |
+
- What we hope to see: fraud forming clusters away from normal
|
| 184 |
+
- What t-SNE shows that PCA doesn't: non-linear clustering structure
|
| 185 |
+
- Why fraud might not perfectly separate in latent space (some fraud IS similar to normal transactions)
|
| 186 |
+
- How this visualization helps in understanding model failure modes
|
| 187 |
+
- Wait for confirmation
|
| 188 |
+
|
| 189 |
+
### STOP 13 β Save & Inference
|
| 190 |
+
- Save model.state_dict(), scaler, threshold
|
| 191 |
+
- Write `predict_fraud(transaction_dict)` β label, reconstruction_error, is_fraud
|
| 192 |
+
- **Agent stops here. Explain:**
|
| 193 |
+
- Complete inference pipeline: dict β preprocess (log Amount, scale) β tensor β model.eval() β reconstruct β MSE β compare to threshold β return
|
| 194 |
+
- Why we save the threshold with the model (it's part of the "model")
|
| 195 |
+
- How to update threshold in production as fraud patterns evolve
|
| 196 |
+
- Wait for confirmation
|
| 197 |
+
|
| 198 |
+
---
|
| 199 |
+
|
| 200 |
+
## `dashboard_core.py`
|
| 201 |
+
Functions:
|
| 202 |
+
- `load_model_scaler_threshold()` β model, scaler, threshold
|
| 203 |
+
- `predict_fraud(transaction_dict)` β reconstruction_error, is_fraud, bool
|
| 204 |
+
- `get_error_distributions()` β (normal_errors, fraud_errors) arrays
|
| 205 |
+
- `get_roc_pr_curves()` β dict of curve data
|
| 206 |
+
- `get_latent_viz()` β 2D coords + labels
|
| 207 |
+
|
| 208 |
+
---
|
| 209 |
+
|
| 210 |
+
## `app.py` β Streamlit (~80 lines)
|
| 211 |
+
Sections:
|
| 212 |
+
1. Sidebar: sliders for V1, V14, V17, Amount (most discriminative features)
|
| 213 |
+
2. Main: "Analyze Transaction" β show reconstruction error + fraud/normal verdict
|
| 214 |
+
3. Tab 1: Training reconstruction error curve
|
| 215 |
+
4. Tab 2: Error distribution histogram (normal vs fraud overlap)
|
| 216 |
+
5. Tab 3: ROC + PR curves
|
| 217 |
+
|
| 218 |
+
---
|
| 219 |
+
|
| 220 |
+
## Key Concepts Covered
|
| 221 |
+
- Autoencoder architecture (encoder, bottleneck, decoder)
|
| 222 |
+
- Information bottleneck principle
|
| 223 |
+
- Training on normal only (unsupervised anomaly detection)
|
| 224 |
+
- Reconstruction loss (MSE reduction='none' for per-sample)
|
| 225 |
+
- Threshold tuning on validation set
|
| 226 |
+
- AUC-PR vs AUC-ROC for imbalanced data
|
| 227 |
+
- Latent space visualization with t-SNE
|
| 228 |
+
- Full unsupervised learning pipeline
|