- Classify anomalies as point, contextual or collective, and distinguish outlier detection from novelty detection.
- Apply robust z-scores, the IQR rule, isolation forests, one-class SVMs and autoencoder reconstruction error, and explain when each fails.
- Evaluate detectors under extreme class imbalance using precision–recall rather than accuracy or ROC alone.
- Derive a cost-optimal decision threshold from a cost matrix and calibrated probabilities.
- Design a fraud system with graph features, drift monitoring, adversarial robustness and a human review loop.
Before this: Model Evaluation and Metrics, Decision Trees and Ensembles, Unsupervised Learning: Clustering and Dimensionality Reduction, Classic Classifiers: kNN, Naive Bayes and SVMs
Anomaly detection finds observations that do not fit the pattern of the rest: a failing bearing in a turbine, an intrusion in network logs, a spike in cloud spend, a stolen card being used. Fraud detection is its most commercially important use, and the hardest, because the "anomalies" are people who adapt to whatever you deploy. This lesson covers the unsupervised toolbox (statistics, isolation forests, one-class SVMs, autoencoders), the supervised reality of fraud (extreme imbalance and cost-sensitive thresholds), and the operational parts that decide whether a system works: graphs, drift, adversaries and human review.
Intuition: the odd one out in a crowd
Picture a stadium crowd seen from above. Most people sit in dense clusters. Someone standing alone in an empty aisle is easy to spot, because few people are near them. A pickpocket moving through the crowd is harder: locally they look like everyone else, and they only stand out by their path over time or by who they keep bumping into. These are the three ideas behind most detectors: density (how many neighbours do you have?), context (is this normal here and now?) and relationships (who are you connected to?).
Types of anomalies
| Type | Definition | Example |
|---|---|---|
| Point | A single observation far from the rest | A $9,000 purchase on a card that usually spends $40 |
| Contextual | Normal in general, abnormal in its context (time, place, user) | 30 °C is normal in July, anomalous in January; a login at 3 a.m. for a 9-to-5 user |
| Collective | A group of observations that is anomalous together, though each looks normal | Twenty small $1 card tests in two minutes; a slow, steady data exfiltration |
A second distinction matters for method choice. Outlier detection looks for anomalies inside contaminated training data. Novelty detection trains on clean, normal data and asks whether new points differ from it.
Statistical methods: z-score and IQR
For a roughly Gaussian feature with mean and standard deviation , the z-score is , and is a common flag (about 0.27% of a true normal distribution lies outside ). The weakness is that outliers inflate and hide themselves. The robust z-score replaces mean and standard deviation with the median and the median absolute deviation :
where the constant 0.6745 makes the scale comparable to for normal data; is a standard cut-off.
The interquartile range rule (Tukey's fences) uses the first and third quartiles and , flagging points outside .
Worked example. Daily logins: $12, 14, 15, 15, 16, 17, 18, 60Q_1 = 14.5$ and , so and the fences are and . The value 60 is flagged. Now the plain z-score: the mean is and the population standard deviation is about , so , which is below 3. The outlier inflated the standard deviation enough to hide itself. That is why robust statistics are the default.
These methods are univariate. They miss a transaction whose amount and location are each normal but whose combination is not. Multivariate statistics (Mahalanobis distance under a fitted covariance) handle linear correlations; the methods below handle more.
Isolation forest
Liu, Ting and Zhou (2008) turned the density idea upside down: rather than modelling normal data, isolate points. Build many random trees; each node picks a random feature and a random split value between that feature's min and max. Anomalies are few and different, so they get separated from everyone else in very few splits, while points inside dense clusters need many. The anomaly score for a point with average path length over trees, each built on a subsample of points, is
where is the harmonic number and is the average path length of an unsuccessful search in a binary search tree, used to normalise. Scores near 1 indicate anomalies; scores well below 0.5 indicate normal points. Isolation forests are fast (linear time, small subsamples such as 256), need no distance metric, and work well on tabular data with tens of features. They struggle when anomalies are only visible in feature interactions that axis-aligned random splits rarely cut, and with many irrelevant features.
One-class SVM
The one-class SVM (Schölkopf et al., 2001) maps data through a kernel, typically the RBF kernel, and finds a boundary that encloses most of the training data with maximum margin from the origin in feature space. The hyperparameter is an upper bound on the fraction of training points treated as outliers and a lower bound on the fraction of support vectors. It is suited to novelty detection on clean, moderate-sized data with good feature scaling; it scales roughly quadratically or worse with sample size and is sensitive to the kernel width .
Autoencoders and reconstruction error
An autoencoder is a neural network trained to reproduce its input through a narrow bottleneck: an encoder compresses to a code and a decoder reconstructs . Trained only (or mostly) on normal data with loss , it learns to reconstruct normal patterns well and unusual ones poorly. The reconstruction error becomes the anomaly score, thresholded at, say, the 99.5th percentile of errors on validation data. Autoencoders shine on high-dimensional or structured data: sensor windows, images of manufactured parts, log sequences (with recurrent or Transformer encoders). Failure modes: an overly powerful autoencoder learns to reconstruct anomalies too, and contamination in training data teaches it fraud as "normal".
Comparing the detectors in code
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.metrics import average_precision_score, roc_auc_score
rng = np.random.default_rng(42)
normal = rng.normal(0, 1, size=(5000, 4))
anomalies = rng.uniform(-6, 6, size=(25, 4)) # 0.5% contamination
X = np.vstack([normal, anomalies])
y = np.r_[np.zeros(len(normal)), np.ones(len(anomalies))]
med = np.median(X, axis=0)
mad = np.median(np.abs(X - med), axis=0)
z_score = np.abs(0.6745 * (X - med) / mad).max(axis=1) # worst feature per row
iso = IsolationForest(n_estimators=200, random_state=0).fit(X)
iso_score = -iso.score_samples(X) # higher = more anomalous
for name, s in [("robust z", z_score), ("isolation forest", iso_score)]:
print(f"{name:17s} ROC-AUC={roc_auc_score(y, s):.3f} "
f"PR-AUC={average_precision_score(y, s):.3f}")
Both detectors reach ROC-AUC near 0.99 here, but PR-AUC (average precision) separates them: about 0.95 for the robust z-score and 0.85 for the isolation forest. With such extreme anomalies, simple statistics are competitive, which is a useful reminder to always run the simple baseline. Notice also how different ROC-AUC and PR-AUC look at 0.5% prevalence; the next section explains why.
Fraud: extreme class imbalance
In fraud you usually do have labels (chargebacks, confirmed cases), so supervised models such as gradient-boosted trees on engineered features are the workhorse, often combined with unsupervised scores as extra features. The difficulty is imbalance. The widely used public credit-card dataset from the Université Libre de Bruxelles machine learning group contains 284,807 transactions of which 492 are fraud, about 0.17%. At that prevalence:
- Accuracy is useless. Predicting "legitimate" for everything scores 99.83%.
- ROC-AUC flatters. The false-positive rate divides by hundreds of thousands of negatives, so thousands of false alarms look like a tiny rate.
- Precision–recall is what operations feel. Precision is the fraction of alerts that are fraud; recall is the fraction of fraud caught. The PR curve and its area (average precision) are the right summary (Saito and Rehmsmeier, 2015, make this case in detail).
Handling imbalance: use class weights or a focal loss; resample with care (undersampling the majority class is common and cheap, but it distorts predicted probabilities, so recalibrate afterwards); and, above all, choose the decision threshold from costs, not from 0.5.
Worked example: picking a threshold with a cost matrix
A card issuer processes 100,000 transactions a day, of which 200 are fraud (0.2%). Assume:
| Flagged (alert) | Not flagged | |
|---|---|---|
| Fraud | Review cost $10, loss prevented | Loss $250 (average fraud amount) |
| Legitimate | Review and friction cost $10 | $0 |
Three candidate thresholds from the model's validation PR curve:
| Threshold | Fraud caught (recall) | False alerts | Precision | Missed-fraud cost | Alert cost | Total |
|---|---|---|---|---|---|---|
| High | 100 (50%) | 300 | 100/400 = 25% | 100 × 250 = 25,000 | 400 × 10 = 4,000 | 29,000 |
| Medium | 150 (75%) | 1,350 | 150/1,500 = 10% | 50 × 250 = 12,500 | 1,500 × 10 = 15,000 | 27,500 |
| Low | 180 (90%) | 5,820 | 180/6,000 = 3% | 20 × 250 = 5,000 | 6,000 × 10 = 60,000 | 65,000 |
Doing nothing costs . The medium threshold minimises cost even though its precision is only 10%. Chasing 90% recall more than doubles cost because of the alert volume.
The decision rule behind this: if the model outputs a calibrated fraud probability , flagging costs and not flagging has expected cost . Flag when
So the right threshold is 4%, not 50%. In reality varies per transaction (it is roughly the amount at risk), so the threshold should too: flag when . Capacity is the other constraint: if analysts can review only 1,000 alerts a day, rank by expected loss and take the top 1,000.
import numpy as np
from sklearn.datasets import make_classification
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
X, y = make_classification(n_samples=200_000, n_features=20, n_informative=6,
weights=[0.998], flip_y=0, random_state=0)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.5, stratify=y, random_state=0)
p = LogisticRegression(max_iter=1000).fit(X_tr, y_tr).predict_proba(X_te)[:, 1]
C_FN, C_ALERT = 250.0, 10.0
def total_cost(thr):
flag = p >= thr
missed = np.sum(~flag & (y_te == 1))
return missed * C_FN + flag.sum() * C_ALERT
grid = np.linspace(0.001, 0.5, 500)
best = grid[np.argmin([total_cost(t) for t in grid])]
print("do nothing:", y_te.sum() * C_FN)
print(f"best threshold {best:.3f}: cost {total_cost(best):,.0f}")
print(f"theory threshold 0.040: cost {total_cost(C_ALERT / C_FN):,.0f}")
On this synthetic data, the do-nothing cost is $50,000. The empirically best threshold (about 0.05) costs about $30,700, and the theoretical 0.04 costs about $31,400. They are close because logistic regression is reasonably calibrated. In practice, pick the threshold on a validation set and confirm it on a later, held-out time period.
Graph-based fraud detection
Fraudsters reuse resources: devices, IP addresses, shipping addresses, phone numbers, merchant accounts. Model the data as a graph, with accounts, cards, devices and addresses as nodes and edges for "used", "paid" or "shares". Patterns invisible row by row become obvious:
- Rings: dozens of new accounts sharing two devices and one address.
- Mule chains: money moving through a path of accounts in quick succession.
- Bipartite cores: a set of fake reviewers who all review the same set of products.
Techniques range from simple graph features fed to a tabular model (degree, number of distinct cards per device, connected-component size, the share of known-fraud neighbours, PageRank) to community detection and graph neural networks that learn node embeddings by aggregating neighbour features. Label propagation is powerful but leaky: when you compute "fraud neighbours" features for a backtest, use only labels known as of the event time.
Concept drift and adversarial behaviour
Concept drift means the relationship between features and labels changes over time. Seasonality (holiday shopping), new products and economic shifts cause natural drift. Fraud adds adversarial drift: when you block one pattern, attackers probe the threshold and move to the next. Consequences and responses:
- Label delay. Chargebacks arrive weeks later, so today's performance is unknown. Monitor proxy signals (score distributions, alert rates, feature drift with population stability index) and evaluate on matured labels.
- Retraining cadence. Retrain frequently on recent data, validate out-of-time, and keep a champion–challenger setup.
- Robust features. Velocity counts (transactions per card per hour), deviations from the customer's own history and graph features are harder to fake than raw amounts.
- Feedback loops. Blocked transactions never get labels, so the model never learns whether they were fraud. Hold out a small random sample from intervention or use reject-inference techniques to keep training data unbiased.
- Probing. Rate-limit, avoid exposing exact scores or reasons to attackers, and add some randomisation in step-up challenges.
Human review loops
Production fraud systems are layered: hard rules (sanctioned countries, impossible travel), a model score, automated actions by score band (approve, step-up authentication such as a one-time passcode, decline), and a case management queue for human analysts. Design the loop deliberately:
- Give analysts the score and the reasons: top feature contributions, linked entities on the graph, the customer's history.
- Capture analyst decisions as labels, with reason codes; these are the freshest labels you have.
- Measure queue precision, time to decision and analyst agreement; the alert budget should match staffing.
- Use active learning: route a share of uncertain cases to review, because they teach the model most.
- Keep an audit trail and an appeal path; in lending, payments and insurance, decisions about people can carry regulatory duties for explanation and non-discrimination.
Common pitfalls
- Reporting accuracy or ROC-AUC alone on data with 0.1% positives.
- Random train/test splits that put the same fraud ring, device or customer in both sets; use time-based and group-aware splits.
- Using the default 0.5 threshold instead of a cost- and capacity-based one.
- Resampling the training data and then reading the model's probabilities as real-world fraud rates without recalibration.
- Training an unsupervised detector on data already contaminated with the fraud you want to find, then setting
contaminationto a guess and treating it as truth. - Ignoring label delay, so last week looks fraud-free.
- Leaky features such as "chargeback flag" or graph features built with future labels.
- Treating the model as static in an adversarial setting.
In practice
Start by defining the cost of each outcome with the business, the review capacity, and the latency budget (card authorisations need a decision in tens of milliseconds). Build robust statistical baselines and an isolation forest for unlabelled problems; for labelled fraud, train gradient-boosted trees on velocity, behavioural-deviation and graph features, with unsupervised scores as inputs. Evaluate with PR curves and expected cost on an out-of-time test set. Calibrate probabilities, set thresholds by expected loss, and serve decisions in bands. Wrap it in monitoring for drift and label maturity, retrain on a cadence, and treat analyst feedback as the most valuable data stream you have.
Key takeaways
- Anomalies can be single points, context-dependent values or suspicious groups, and each calls for different features and methods.
- Robust statistics (median and MAD, IQR) are strong baselines because ordinary means and standard deviations are pulled by the outliers they are trying to find.
- Isolation forests score points by how quickly random splits isolate them; autoencoders score by reconstruction error; one-class SVMs learn a boundary around normal data.
- At fraud-level imbalance, use precision–recall and expected cost; the right threshold comes from costs and review capacity, not 0.5.
- Fraud is adversarial and labels arrive late, so graph features, out-of-time validation, drift monitoring and frequent retraining are essential.
- Human analysts are part of the system: give them reasons, capture their decisions as labels and size the alert volume to their capacity.
Terms in this lesson
- Contextual anomaly
- An observation that is unusual only given its context, such as time, location or user.
- Collective anomaly
- A set of observations that is anomalous as a group even if each member looks normal alone.
- Median absolute deviation (MAD)
- The median of absolute deviations from the median, a robust measure of spread.
- Isolation forest
- An ensemble of random trees that scores points as anomalous when they are isolated in few random splits.
- One-class SVM
- A kernel method that learns a boundary enclosing most training data, flagging points outside it.
- Reconstruction error
- The difference between an input and an autoencoder's reconstruction of it, used as an anomaly score.
- Average precision
- A summary of the precision–recall curve equal to the precision averaged over recall levels at each threshold.
- Cost matrix
- A table giving the cost of each combination of true class and decision, used to set decision thresholds.
- Concept drift
- A change over time in the relationship between input features and the target.
- Label delay
- The lag between an event and the arrival of its ground-truth label, such as a chargeback weeks later.
Check your understanding
- 1.
A household's electricity use of 40 kWh in a day is normal in winter but unusual for this household in mild spring weather. What kind of anomaly is it?
- 2.
A fraud model has ROC-AUC 0.97 on data with 0.1% fraud. Why might operations still complain?
- 3.
Missing a fraud costs 400 dollars on average, and reviewing any alert costs 8 dollars. With calibrated probabilities, above what fraud probability should you flag?
- 4.
You have 50,000 sensor windows from a machine known to be healthy and want to flag future windows that look different. Which approach fits best?
- 5.
Why can isolation forests detect anomalies without any distance or density calculation?
Exercises
Work each problem before opening the solution.
- Exercise 1Core
Response times (ms):
110, 120, 118, 125, 122, 119, 900, 121, 117, 123. Compute the robust z-score of 900 using median and MAD, and say whether it is flagged at 3.5. Then compute the IQR fences (use median-of-halves quartiles).Show solution
Sorted: 110, 117, 118, 119, 120, 121, 122, 123, 125, 900. Median = (120 + 121)/2 = 120.5.
Absolute deviations from 120.5: 10.5, 3.5, 2.5, 1.5, 0.5, 0.5, 1.5, 2.5, 4.5, 779.5. Sorted: 0.5, 0.5, 1.5, 1.5, 2.5, 2.5, 3.5, 4.5, 10.5, 779.5. MAD = (2.5 + 2.5)/2 = 2.5.
Robust z(900) = 0.6745 x (900 - 120.5)/2.5 = 0.6745 x 311.8 = 210.3. That is far above 3.5, so it is flagged.
IQR: lower half 110, 117, 118, 119, 120, so Q1 = 118. Upper half 121, 122, 123, 125, 900, so Q3 = 123. IQR = 5. Fences: 118 - 7.5 = 110.5 and 123 + 7.5 = 130.5. Both 900 and 110 fall outside the fences. 110 is a mild low outlier under Tukey's rule, but its robust z-score is 0.6745 x (-10.5)/2.5 = -2.83, so it is not flagged at 3.5. Different rules give different answers near the boundary.
- Exercise 2Core
Using the lesson's cost matrix (missed fraud $250, any alert $10), a new model at some threshold catches 170 of 200 frauds with 2,330 false alerts per day. Compute precision, recall and total daily cost. Is it better than the medium threshold (cost $27,500)?
Show solution
Alerts = 170 + 2,330 = 2,500. Precision = 170/2,500 = 6.8%. Recall = 170/200 = 85%.
Cost = missed 30 x 250 = 7,500 plus alerts 2,500 x 10 = 25,000, so $32,500. That is worse than $27,500. The extra 20 frauds caught save $5,000, but the extra 1,000 alerts cost $10,000.
- Exercise 3Challenge
Implement an autoencoder-style novelty detector with scikit-learn only: train an
MLPRegressorwith a 2-unit bottleneck to reconstruct 8-dimensional normal data that lies near a 2-D subspace, set the threshold at the 99th percentile of validation reconstruction error, and report the detection rate on anomalies that leave the subspace.Show solution
import numpy as np from sklearn.neural_network import MLPRegressor from sklearn.preprocessing import StandardScaler rng = np.random.default_rng(0) W = rng.normal(size=(2, 8)) def normal_data(n): # points near a 2-D subspace of R^8 return rng.normal(size=(n, 2)) @ W + 0.05 * rng.normal(size=(n, 8)) X_tr, X_val = normal_data(4000), normal_data(1000) X_anom = rng.normal(size=(200, 8)) * 2.0 # off the subspace sc = StandardScaler().fit(X_tr) ae = MLPRegressor(hidden_layer_sizes=(16, 2, 16), max_iter=2000, random_state=0) ae.fit(sc.transform(X_tr), sc.transform(X_tr)) # target = input def err(X): Z = sc.transform(X) return np.mean((Z - ae.predict(Z)) ** 2, axis=1) thr = np.percentile(err(X_val), 99) print('false-alarm rate on validation: %.3f' % np.mean(err(X_val) > thr)) print('detection rate on anomalies: %.3f' % np.mean(err(X_anom) > thr))The network is forced through a 2-unit bottleneck, so it can only reconstruct points that lie near the 2-D structure it learned. Validation false alarms are about 1% by construction. Most off-subspace anomalies have far larger reconstruction error and are detected; expect a detection rate close to 1. Points to discuss: scale inputs; set the threshold on held-out normal data, not on training data; and a larger bottleneck (for example 8 units) would let the model reconstruct everything, destroying detection.
- Exercise 4Challenge
You are designing features for card-fraud detection on a transaction graph (cards, devices, IPs, merchants). Propose five graph-derived features, explain the fraud pattern each captures, and describe how to compute them without temporal leakage in a backtest.
Show solution
- Distinct cards per device in the last 24 h: card-testing and stolen-card rings use one device with many cards.
- Accounts sharing this shipping address or phone number: synthetic-identity rings reuse contact details.
- Share of known-fraud neighbours within 2 hops: guilt by association with confirmed fraud.
- Connected-component size and age: large, newly formed components of linked accounts suggest coordinated account creation.
- Merchant-side velocity: new cards seen per hour at this merchant relative to its baseline: compromised or collusive merchants.
Avoiding leakage: build the graph as an event stream. For a transaction at time t, compute every feature using only edges with timestamps before t, inside the stated window. For feature 3, count a neighbour as fraud only if its fraud label was confirmed before t, not in hindsight. Use point-in-time snapshots in the feature store and an out-of-time test period. Split train and test by time, and check that the same ring's members do not appear in both sets (use group-aware splits by component, or accept the overlap and report it).
Further reading
- paperFei Tony Liu, Kai Ming Ting, Zhi-Hua Zhou, Isolation Forest (2008)
- paperVarun Chandola, Arindam Banerjee, Vipin Kumar, Anomaly Detection: A Survey (2009)
- paperBernhard Schölkopf et al., Estimating the Support of a High-Dimensional Distribution (2001)
- paperTakaya Saito and Marc Rehmsmeier, The Precision-Recall Plot Is More Informative than the ROC Plot When Evaluating Binary Classifiers on Imbalanced Datasets (2015)
- paperJoão Gama et al., A Survey on Concept Drift Adaptation (2014)
- docsscikit-learn documentation, Novelty and Outlier Detection
Go further
Sources
Fact-checked and last reviewed 2026-11-05