You rarely have labels for tomorrow’s failures. So learn what normal looks like — with a z-score, an isolation forest, a shell, or a reconstruction error — and flag what doesn’t fit.
“Anomalous” only means “far from the baseline you chose”. The baseline can be a mean and std, a quartile range, a tilted cloud's covariance, a forest of random splits, or a network's reconstruction — change the model and the anomaly list changes. That is why contaminated training data is the first failure mode: the outliers help define normal.
model normal → flag deviations02 / THREE FRAMINGS
Labels change what you can judge, not just train.
Fully unsupervised: no labels, fit on everything and hope anomalies are rare. Semi-supervised: a clean normal set — the strongest setup, fit on it and score the world. Weakly supervised: a few labeled anomalies, used for evaluation only. Tomorrow's anomaly is a type you have never seen, so train to recognize normal, not the last attack.
no labels needed · labels for judging03 / RANK BEFORE YOU CUT
A score is continuous; the threshold is a decision.
Detectors emit a number per point, so the useful question is not “is this an anomaly?” but “of the top k flagged, how many are real?”. Precision@k, recall and AUPRC survive extreme imbalance where accuracy is meaningless. Set the cut from the cost of a false alarm versus a miss — and raise it when operator attention runs out.
precision@k = what review wastes
MENTAL MODEL IN ONE SENTENCE
Anomaly detection is a model of normal plus a threshold: the model decides what shape “normal” has — and whether a point is unusual because of its distance, its direction, its isolation, or its reconstruction cost — while the threshold decides how many false alarms you can afford. Both must be monitored, because the world drifts.
By the end you will be able to pick a detector for a dataset shape, say out loud what each one assumes, compute precision@k on a ranked queue, explain why contamination only moves the threshold, and name the operational failure modes — drift, alert fatigue and feedback loops — before they hit production.
01
WHAT COUNTS AS WEIRD
“Normal” is a model you fit to the baseline.
An anomaly is not a property of a point — it is a statement about how far that point is from your model of normal. Change the model and the list changes.
A credit card in New York at 2 pm and in Tokyo at 2:05 pm. A factory sensor reading 150° when its normal band is 80–120. A server answering in 205 ms when it usually takes about 120. These are anomalies — but notice that each sentence contains a hidden model: a usual travel pattern, a normal operating band, a service-level baseline. Anomaly detection is the business of fitting that model of normal and scoring how far new data departs from it.
Labels are the reason the framing flips. Fraud is roughly 0.1% of transactions; equipment failures happen a few times a year. One million transactions contain about 1,000 frauds — but the fraud you will see next month is a scheme nobody labeled yet. A classifier trained only on yesterday’s fraud types learns exactly those; a model of normal flags whatever does not fit, including the new scheme. And a classifier that answers “normal” to everything is 99.9% accurate and catches nothing, which is why accuracy never appears in this lesson’s metrics.
Point anomalies are unusual on their own; contextual anomalies are only unusual given time or place; collective anomalies are unusual as a sequence even when each value is fine. Most detectors in this lesson find point anomalies; context is a feature-engineering problem.
Quick check
A server normally serves 200 requests per second. At 3 am it logs 200 requests per second — normal load, wrong hour. What kind of anomaly is that, and what does it need?
02
WHO GETS A LABEL
Labels decide what you can learn — and prove.
There are four practical setups, and they differ in one question: what data do you trust enough to learn “normal” from, and what do you keep aside to judge the detector?
The source lesson calls anomaly detection “fundamentally different from classification”, and the difference is the training distribution. A classifier needs examples of both classes; a detector needs a trustworthy picture of normal and a way to measure deviations. When a clean baseline exists, use it: fitting on normal-only data is the strongest setup, because anomalies can never teach the model that they are ordinary. When labels exist, resist the urge to just train a classifier — keep them for evaluation and let the detector stay broad. In production the two coexist: unsupervised detection for coverage, a supervised model for the known high-priority types, and humans for the ambiguous middle.
Same new point, two philosophies. The supervised classifier is precise about what it was shown and blind to everything else; the unsupervised detector is broad by construction and pays for it with false alarms.
Setup
What you fit on
What it buys you
The catch
Fully unsupervised
Everything you have, anomalies included
No labels required at all; works immediately
Anomalies must be rare, or they become part of “normal”
Semi-supervised
A clean baseline of normal data only
The strongest setup: the normal model is not polluted
You need a window of history you trust — and it drifts
Weakly supervised
Unsupervised training; labeled anomalies only for judging
Honest precision/recall on the few cases you know
The labeled cases cover only anomaly types you have seen
Supervised
A classifier trained on labeled normals and anomalies
High precision on known anomaly types
Misses novel types entirely and needs enough positives
03
STATISTICAL BASELINES
One ruler per feature, or one ruler that knows the shape.
The oldest detectors ask a simple question: how far from normal is this point, in units of the data’s own spread? The answers differ by how much of the data’s shape the ruler remembers.
Z-score. Standardize every feature: subtract its mean, divide by its standard deviation, and flag readings beyond a threshold. The default k = 3 comes from the bell curve (99.7% of normal data falls inside 3σ). It is fast, explainable — “this reading is 3.7σ above normal” — and only faithful when the baseline is one roughly-Gaussian cluster. On the lesson’s 55 response times the contaminated statistics are mean 121.16 ms and std 22.66 ms, so the reading 205.0 scores z = 3.70, 180.0 scores 2.60, and 152.0 scores 1.36. Notice what happened: the planted faults inflated the std. Fit only on the 50 ordinary readings and the std is 9.56 ms — then 180.0 scores 6.19σ and 152.0 scores 3.26σ. Contamination hides the very points that caused it.
IQR. Take the middle half of the data instead: Q1 (25th percentile) and Q3 (75th), then flag anything below Q1 − 1.5·IQR or above Q3 + 1.5·IQR, where IQR = Q3 − Q1. Percentiles barely move when one point goes wild, so this rule is robust to contamination and says nothing about bell curves. On the same 55 readings Q1 = 114.05, Q3 = 128.50 and IQR = 14.45, giving bounds [92.38, 150.18]: all five planted points are outside, with no false alarms. (Tighten to factor 1.0 and the bounds [99.60, 142.95] flag six points — five true, one false.) Its blind spot is the joint space: each feature is checked alone, so a point can be normal in every feature and still be impossible in combination.
z-score: z = (x − mean) / std flag |z| > k
IQR: IQR = Q3 − Q1 flag x < Q1 − f·IQR or x > Q3 + f·IQR
Mahalanobis: d² = (x − μ)ᵀ Σ⁻¹ (x − μ) flag d > t
sensor 55: mean 121.16, std 22.66 (clean 50: mean 120.80, std 9.56)
205.0 → z 3.70 180.0 → z 2.60 152.0 → z 1.36
One ruler, 55 readings, five planted faults
Move the threshold and watch precision, recall and precision@k recompute from the displayed values. The hollow rings are the ground truth the detector never sees.
mean 121.16 std 22.66
clean-baseline std (50 normal) 9.56
Q1 114.05 Q3 128.50 IQR 14.45
bounds [87.18, 155.14]
flagged 4 tp 4 fp 0 fn 1
precision 1.000 recall 0.800 f1 0.889
top 5 by |score| → precision@5 = 1.000
At k = 1.5–2.5 the z-score gets 4 of 5 with no false alarms. The fifth (180.0 ms) sits at z = 2.60 because the anomalies themselves inflated the std — against a clean baseline it would be 6.19σ.
Mahalanobis distance keeps the shape. Instead of measuring distance feature by feature, it first rotates the axes until the correlations vanish and rescales each axis to unit spread — the mathematical name is whitening — and then measures ordinary Euclidean distance in that cleaned-up space. In one dimension it is the z-score; in a tilted two-dimensional cloud it is the only one of the three that notices a point sitting off the diagonal. The lesson’s correlated baseline has mean (−0.148, −0.123), σx = 0.919, σy = 0.813 and correlation ρ = 0.862. The planted point (2.6, −2.5) scores 2.99 on x and −2.92 on y — each barely under the 3σ line — but its Mahalanobis distance is 11.27, while the largest baseline point scores 2.74. At the same threshold of 3.0, per-feature z-score misses two of the six planted points; Mahalanobis misses none.
The cloud has a shape, not just a center
The normal points run along a diagonal. Switch between a per-feature z-score ruler and a Mahalanobis ellipse, then contaminate the fit to watch the boundary lose its shape.
fit mean (-0.148, -0.123)
std (0.919, 0.813)
corr ρ 0.862
flagged 6 tp 6 fp 0 fn 0
precision 1.000 recall 1.000
planted point (2.6, −2.5): score 11.266
it scored z = 2.99 per feature — under 3.0
With the clean fit, the ellipse flags all six planted points at d > 3 and no baseline point (largest baseline d = 2.74). Contaminate the fit and the correlation and scale collapse — the same points score much lower.
Derivation: what whitening does, with the numbers
Standardize. Subtract the mean from each feature and divide by its std. A correlated cloud becomes a tilted ellipse, not a circle: the correlation survives because standardizing touches each axis separately.
Naive joint distance. In standardized coordinates, ordinary Euclidean distance would score the baseline point (2.11, 1.63) as √(2.46² + 2.16²) = 3.27 — a false alarm at t = 3 even though it sits comfortably inside the cloud. The diagonal spread is being credited as if it were deviation.
Whiten. Multiply by Σ⁻¹ (equivalently, rotate to the cloud’s principal axes and divide each by its own spread). The tilted ellipse becomes a unit circle: on-axis scatter and cross-feature correlation are both removed.
Measure. Now Euclidean distance is the anomaly score. The same baseline point scores Mahalanobis 2.46 — normal — and the off-diagonal planted point (2.6, −2.5) scores 11.27, even though each of its coordinates is under 3σ.
Read the formula. d² = (x − μ)ᵀ Σ⁻¹ (x − μ). The vector (x − μ) is the displacement from the centre; Σ⁻¹ rescales and de-correlates it; the dot product with the original displacement squares the result into a distance. Dividing by the covariance matrix is the entire trick.
whitened coordinates of (2.6, −2.5):
z per feature x: 2.99 y: −2.92 (max < 3.0 → missed)
Euclidean √(2.99² + 2.92²) = 4.18
Mahalanobis d = 11.27 (detected)
baseline point (2.11, 1.63), also under 3σ per feature:
Euclidean 3.27 → would be a false alarm
Mahalanobis 2.46 → correctly normal
(max baseline d = 2.74, so t = 3.0 is clean)
This is also why Mahalanobis needs more data than a z-score: a d-feature covariance matrix has d(d+1)/2 numbers to estimate, and it becomes unreliable when features outnumber rows. With few samples, prefer per-feature IQR; with many correlated sensors, Mahalanobis or a forest is worth the extra data.
Quick check
The baseline is two office locations with different average temperatures. Which baseline detector is most likely to fail, and why?
04
ISOLATION FOREST
Don’t measure distance. Count the questions it takes to isolate.
Anomalies are few and different, so in a random partition of the data they fall into their own corner quickly. The number of splits needed becomes the score — no distances, no distribution assumptions.
How it works. Build many trees. At each node, pick a random feature, pick a random split value between that feature’s minimum and maximum, and send every point left or right. Keep splitting until each point is alone (or a depth limit is reached). A normal point lives in a dense region, so many random questions are needed before it is separated from its neighbors. A far-out point is separated almost immediately. That is the whole trick: anomalies have short average path lengths across the forest.
The raw path length depends on the dataset size, so the score is normalized by c(n), the expected path length of an unsuccessful search in a random binary search tree. A score near 0.5 means ordinary; near 1 means isolated fast; near 0 means buried deep in a dense cluster. In the lab the six planted points have average paths 3.46–4.62 and the deepest normal point 10.49, and a threshold near 0.635 separates them exactly — with no labels used to build the forest.
Each line is one random split. A point that lands alone is isolated. The tree on screen is one of 24; the scores use the average path length across all of them.
The six planted points have average paths 3.46–4.62; the normal points run up to 10.49. Threshold 0.635 separates them exactly — no labels were used to build the forest.
Derivation: why random splits isolate the few and different, and what c(n) corrects
One split is a question. Pick a feature; the split value is uniform between the min and max of the points currently in the node. A point sitting alone at the edge of the range gets cut off by the first question with high probability; a point in the middle survives the first cut with probability roughly its distance from the nearer edge.
Path length is a depth in a random tree. Splitting continues independently at every level, so the expected number of splits needed to isolate a point grows like the logarithm of the size of the dense region it lives in. In the lab’s 24 trees the normal points average 9.24 splits and run up to 10.49; the planted points take 3.46–4.62.
Growing n stretches every path. Add ordinary points and the tree gets deeper even for a fixed anomaly, because there are more chances for a split to miss it. Without normalization, the same point would look increasingly normal on larger datasets.
c(n) is the fair baseline. For a random binary search tree with n keys, the expected path length of a miss is 2·H(n−1) − 2·(n−1)/n. Using the harmonic approximation H(m) ≈ ln m + 0.5772, c(61) = 2·(ln 60 + 0.5772) − 2·60/61 = 7.376. The score 2^(−path/c) is 1 when the path is 0 (isolated at the root), 0.5 when the path equals c(n), and approaches 0 for very deep points.
Why subsample. Real forests cap each tree at max_samples = 256 points. Path lengths for normal points saturate as n grows while c(n) creeps up only logarithmically, so a single anomaly competes against enormous leaf sizes; subsampling keeps trees short, makes them diverse, and keeps the whole forest cheap. 256 is the paper’s default: dense enough to represent normal, small enough to stay fast.
isolation needs no distances and no Gaussian assumption:
dense region → many splits survive → long path → small score
lone point → first split usually cuts it off → short path → big score
lab, 24 trees, n = 61: planted paths 3.46 … 4.62
deepest normal path 10.49
≥ 0.635 threshold: 6 of 6 planted, 0 false alarms
Quick check
An isolation forest is built on a dataset where 40 of the 100 features are pure noise. What happens to the ability to isolate a real anomaly quickly?
05
ONE-CLASS BOUNDARIES
Draw a shell around the only class you have.
One-class SVM and SVDD learn a fence that contains normal data and keeps everything else out — with a knob that says how much of the training set may fall outside.
One-class SVM treats the normal points as a single class and fits a boundary that encloses them in a high-dimensional feature space. Because it uses a kernel, the boundary can bend around clusters that a circle cannot wrap: the RBF kernel lets the fence curve in whatever shape the data needs. SVDD (support vector data description) is the sphere-shaped cousin: it looks for the smallest sphere that contains the normal data, allowing a few points to sit outside through slack variables.
Both share the parameter ν (nu). It sets the fraction of training points allowed outside the boundary — the same role contamination plays for the threshold of a score-based detector. A larger ν buys a tighter fence and catches more deviations, at the cost of ringing the baseline’s own edge. The lab below is the simplest possible SVDD: a circle fitted so that ν of the baseline lies on or outside it. Real one-class SVM solves a quadratic program, but the tradeoff you feel on the slider is exactly the one production uses.
Wrap a shell around normal
A simplified SVDD boundary: a circle fitted so that ν of the baseline is allowed outside. Slide ν and watch precision fall as the shell tightens.
centre (0.131, 0.155)
radius 2.7176 (ν = 0.05)
flagged 8 tp 5 fp 3 fn 0
precision 0.625 recall 1.000
support points 3 of 60
planted distances 3.30 – 3.77
ν = 0.05 → radius 2.718, all five planted points caught, plus three baseline points (precision 0.625). ν = 0.30 → radius 1.633 and 23 flags: recall stays 1.0, precision collapses to 0.217. This is the contamination dial again, wearing a different name.
The simplest SVDD, with the numbers
Fit a centre. The lab uses the mean of the 60 baseline points, (0.131, 0.155).
Choose a radius. Sort every baseline distance from the centre and take the (1 − ν) quantile — the circle that leaves exactly ν of the training data on the outside edge.
Score. Any new point is flagged when its distance exceeds the radius. There is no per-feature logic and no probability — just inside or outside.
Move ν. At ν = 0.05 the radius is 2.718 and the boundary leaves 3 of the 60 baseline points outside; all five planted points (distances 3.30–3.77) are caught, precision 0.625. At ν = 0.30 the radius shrinks to 1.633, 23 points are flagged, recall stays 1.000 and precision falls to 0.217. Recall is flat because the anomalies were always outside; every extra flag is a baseline point.
What the real algorithm adds. SVDD minimizes R² + (1/νn)·Σξᵢ over a centre, a radius and slack ξᵢ — the QP version of the same tradeoff. Kernels replace the circle with a flexible shell, and the points that touch the boundary are the support vectors. This lab is a spherical teaching model: no kernel, no slack optimization, just the quantile.
ν = 0.05 → radius 2.718 → 8 flags: 5 true + 3 baseline precision 0.625
ν = 0.30 → radius 1.633 → 23 flags: 5 true + 18 baseline precision 0.217
nu is to a boundary what contamination is to a score:
it decides how much of the baseline you are willing to call suspicious.
06
RECONSTRUCTION ERROR
Compress until only normal survives the squeeze.
An autoencoder is trained to copy its input through a narrow bottleneck. It gets good at copying normal data — and that is exactly why anomalous data comes back wrong.
An autoencoder has two halves: an encoder that compresses the input into a small code, and a decoder that rebuilds the input from the code. Train it on normal data with a reconstruction loss, and it learns the regular structure well enough to copy it. At test time, feed it a new point, measure the reconstruction error, and treat a large error as the anomaly score: the network never learned how to compress that kind of point, so the round trip loses information.
The linear version is a picture you can compute by hand. A 2-D cloud with a one-number bottleneck must project every point onto a line; the best line is the direction along which the data varies most (the first principal component), and the reconstruction error is the distance from the point to that line. On the lesson’s correlated cloud the axis is u = (0.7554, 0.6553) and it holds 93.2% of the variance. The ordinary point (1.12, 0.48) projects to (0.874, 0.763) and reconstructs with error 0.375; the planted point (2.6, −2.5) projects to (0.244, 0.217) and reconstructs with error 3.596. All six planted points land between 2.01 and 4.23; every baseline point stays under 0.87. A threshold of 1.0 separates the two groups perfectly.
reconstruction error = ‖ x − decoder(encoder(x)) ‖
linear 2 → 1 → 2: the bottleneck learns the principal axis u
x = (1.12, 0.48) projection (0.874, 0.763) error 0.375 normal
x = (2.6, −2.5) projection (0.244, 0.217) error 3.596 anomaly
baseline max 0.87 · planted min 2.01 · threshold 1.0 works
Learn normal, then measure surprise
A linear autoencoder with a one-number bottleneck learns the diagonal axis of the normal cloud. Reconstruction error is the distance from that axis — the anomalies are far off it.
anomaly
4.23
anomaly
3.79
anomaly
3.60
anomaly
3.23
anomaly
3.17
anomaly
2.01
normal
0.87
normal
0.71
normal
0.63
normal
0.58
Top ten reconstruction errors out of 61 points. All six planted points rank above every normal point — the smallest planted error (2.01) is more than twice the largest normal one (0.87).
axis u (0.7554, 0.6553)
λ1 1.403 λ2 0.102
flagged 6 tp 6 fp 0 fn 0
precision 1.000 recall 1.000
normal max error 0.869
planted min error 2.007
worked point (2.6, −2.5): 3.596
Threshold 1.0 flags all six planted points with zero false alarms on this set (normal max 0.87). The gap between 0.87 and 2.01 is why reconstruction error is so popular for sensor and log monitoring.
Derivation: why the bottleneck must find the principal axis
The bottleneck is a lossy channel. A 2-D input encoded to one number can only be rebuilt as a point on a one-dimensional curve. For a linear autoencoder that curve is a line through the data’s mean: x̂ = μ + t·u, where t is the code.
The error is a distance to the line. For any x, the best code is the projection t = (x − μ)·u, and the reconstruction error is the part of x the line cannot represent: ‖x − μ − t·u‖.
Squared error decomposes along the axes. Rotate the cloud so one axis is u and the other is perpendicular; the total variance is λ1 + λ2. A bottleneck along u discards exactly the perpendicular variance λ2. The lesson cloud has λ1 = 1.403 and λ2 = 0.102, so the kept axis holds 1.403/1.505 = 93.2% of the variance and the discarded direction contributes only 6.8%.
Minimizing squared error maximizes kept variance. Because the discarded error equals the perpendicular variance, the optimal line is the one with the largest λ — the principal component. This is why a linear bottleneck is PCA in neural-network clothing, and why the reconstruction error is literally the distance from the learned subspace.
Nonlinear bottlenecks go further. Replace the line with a curved manifold learned by an MLP and the same logic holds: high error means the point is far from the manifold of normal data. That generality is the reason autoencoders work on images, audio and sequences where a straight axis would be useless.
variance kept λ1 / (λ1 + λ2) = 1.403 / 1.505 = 0.932
error = distance from the point to the learned axis
normal max 0.87 (largest reconstruction cost in the baseline)
planted min 2.01 (smallest cost among the six planted points)
07
EVALUATE & OPERATE
Judge the ranking. Then keep it alive.
With 0.1% anomalies, accuracy is a lie and thresholds are decisions. The metrics that survive are ranking metrics — and the system only keeps working if someone watches the scores as carefully as the flags.
Predict “normal” for every credit-card transaction and you are right 99.9% of the time while catching 0% of the fraud. That is why this lesson never uses accuracy. The practical alternatives are precision@k — of the k most suspicious items, how many are real — recall at a fixed review budget, and AUPRC, the area under the precision–recall curve. AUROC is seductive but misleading under heavy imbalance: the false-positive rate stays tiny while precision collapses, so a detector can score 0.95 AUROC and still waste most of an analyst’s queue.
contamination deserves its own sentence. It does not change the anomaly scores, and it is not used during training: it only converts continuous scores into flags by cutting at the corresponding quantile. Setting contamination = 0.05 means “flag the top 5%”. If the true rate is 0.5%, four out of five alerts are false by construction. When you do not know the rate — almost always — work with raw scores and choose the cut from costs and review capacity.
The ranking console: judge, don’t just threshold
Twelve new readings, four detectors fit on the clean baseline. Choose a detector and a review budget; the table re-ranks and the metrics recompute from the rows shown.
#
reading (x, y)
score
agreement
flag
truth
1
(0.00, 4.20)
0.964
4 / 4
FLAG
◎ anomaly
2
(-2.80, 2.60)
0.665
4 / 4
FLAG
◎ anomaly
3
(2.60, -2.50)
0.596
3 / 4
FLAG
◎ anomaly
4
(-2.40, 2.20)
0.543
0 / 4
—
◎ anomaly
5
(2.11, 1.63)
0.334
0 / 4
—
normal
6
(1.12, 0.48)
0.121
1 / 4
—
normal
7
(0.91, 0.03)
0.096
0 / 4
—
normal
8
(0.59, 0.87)
0.090
0 / 4
—
normal
9
(-1.30, -1.06)
0.071
0 / 4
—
normal
10
(-0.27, 0.46)
0.046
0 / 4
—
normal
11
(0.58, 0.40)
0.021
0 / 4
—
normal
12
(0.32, 0.27)
0.006
0 / 4
—
normal
Rows are ranked by the selected score. “Agreement” counts how many of the four detectors place this reading in their own top 3.
fit on 55 baseline: mean (-0.148, -0.123)
std (0.919, 0.813) ρ 0.862
Q1/Q3 of x -0.950 / 0.560
Q1/Q3 of y -0.750 / 0.450
isolation: 24 trees, max depth 6
review top 3 tp 3 fp 0 fn 1
precision 1.000 recall 0.750 f1 0.857
precision@3 by method
z 1.000
IQR 0.667
Mahalanobis 1.000
isolation 1.000
consensus 1.000
IQR is the weak detector here: the planted points are off-diagonal, so no single feature is extreme. At the console’s 25% budget the consensus catches three anomalies in three reviews (precision 1.000), and at 30% it is still four-for-four. Push the budget to the top and switch on the two-detector rule: it prunes the row only one method put in its top-k.
Derivation: precision@k on the ranking console
The console streams twelve new readings: eight ordinary, four planted. All four detectors are fit on the clean 55-point baseline, so the only thing that changes between methods is how they rank the twelve.
k = 3 (contamination 25%, the console default):
consensus top-3 = (0,4.2), (−2.8,2.6), (2.6,−2.5) → all planted
precision@3: z 1.000 · IQR 0.667 · Mahalanobis 1.000 · isolation 1.000 · consensus 1.000
k = 4: the 4th-ranked row is the 4th planted point → precision@4 = 1.000
k = 6: 4 planted + 2 ordinary → precision@6 = 0.667
precision@6 by method:
z-score 4/6 = 0.667
IQR 2/6 = 0.333 ← off-diagonal points look normal per feature
Mahalanobis 4/6 = 0.667
isolation 4/6 = 0.667
consensus 4/6 = 0.667
recall at k: k=3 → 0.750 k=4 → 1.000 k=6 → 1.000 (with 2 false alarms)
The numbers to internalize: precision@k answers “how much review time is wasted?”, but says nothing about the anomalies below the cut — always report recall or total alerts alongside it. And the IQR row is the lesson of chapter 3 in one line: a detector that looks at one feature at a time cannot rank points whose anomaly lives in the combination.
Keeping it alive in production
Threshold drift. A fixed cutoff assumes a fixed world. When traffic, seasonality or a product change moves the score distribution, yesterday's threshold starts flagging everything or nothing. Monitor the score distribution itself — median and 95th percentile — not just the flags.
Alert fatigue. Operators who see too many false alarms stop reading them, and the detector dies socially before it dies technically. Start conservative (fewer, more reliable alerts), lower the threshold only as trust and review capacity grow, and reserve instant pages for the highest-cost classes.
Ensembles by agreement. Different detectors fail differently. Normalize each score to [0, 1], average them, or flag only when two or more methods agree. A point caught by every method is almost certainly anomalous; a point caught by one may be that method's quirk.
Feature engineering beats detector choice. Rolling means, ratios, time-since-last-event, cycle position, error rates per endpoint — the features decide what “normal” even means. A mediocre detector on good features routinely beats a fancy one on raw columns, and contextual anomalies are impossible without context features.
Close the loop — carefully. Record how humans resolve each alert (true, false, unclear) and use it to re-evaluate and re-tune. But beware the feedback loop: you only ever learn about points you flagged. Keep a small random audit sample that is reviewed regardless of score, or your false-negative rate is invisible.
Explain every flag. “Feature CPU is 4.2 standard deviations above normal”, “isolated in 3.1 splits versus 8.5 for ordinary points”, “reconstruction error 0.83 versus a baseline of 0.05”. An operator who understands the reason can act; an unexplained score decays into ignored noise.
Quick check
Your detector's contamination is 0.10 and the daily queue is 2,000 flagged items. Investigators can review 200 per day. What actually changed when someone set contamination to 0.10?
08
CHECK YOURSELF
Six questions. Then the terms worth keeping.
Answer before you look. The contamination question and the z-score-failure question are the ones that separate a memorized definition from a working instinct.
0 / 6 answered · 0 correct
01Why is anomaly detection typically framed as an unsupervised problem rather than classification?
02A temperature of 90°F is normal in summer but anomalous in winter. What type of anomaly is this?
03The z-score method flags points more than 3 standard deviations from the mean. When does this approach fail?
04How does Isolation Forest detect anomalies differently from distance-based methods?
05You build both an unsupervised anomaly detector and a supervised fraud classifier. When should you prefer the unsupervised approach?
06You set contamination = 0.05 on a detector while the true anomaly rate is 0.5%. What does contamination change?
Key terms, demystified
Click a card to swap the lazy description for what it actually means.
Exercises from the lesson
Four problems with exact numbers — a threshold sweep, a multivariate anomaly, a streaming mean and an isolation score. Try first; a worked answer is one click away.
Run the z-score detector on the lesson's 55 response times with thresholds 1.0 → 5.0 in steps of 0.5. Tabulate flagged count, precision, recall and F1. Where is the sweet spot — and why does k = 4.0 flag nothing?Show one worked answer
The displayed data has mean 121.16 ms and std 22.66 ms; the five planted points are 35.0, 52.0, 152.0, 180.0 and 205.0. k = 1.0 → 6 flags, 5 TP, 1 FP (95.7 at z = −1.13): precision 0.833, recall 1.000, F1 0.833. k = 1.5, 2.0, 2.5 → 4 flags, all true: precision 1.000, recall 0.800, F1 0.889. k = 3.0 → 3 flags, precision 1.000, recall 0.600. k = 3.5 → 2 flags, recall 0.400. k = 4.0 and above → 0 flags: precision and recall both 0. The sweet spot by F1 is k ∈ [1.5, 2.5]; ranking by |z| still finds all five anomalies in the top five (precision@5 = 1.000), so if analysts review a ranked queue, the exact k matters less. k = 4 flags nothing because the planted points inflated the std from the clean-baseline 9.56 ms to 22.66 ms: 180.0 sits at z = 2.60 after contamination, but would be 6.19σ against the clean baseline. Contaminated statistics hide the very points that caused them.
Build 2-D data where each feature looks normal on its own but the combination is anomalous. Show that per-feature z-score misses it while the Mahalanobis distance catches it.Show one worked answer
The lesson's correlated cloud has baseline mean (−0.148, −0.123), σx = 0.919, σy = 0.813 and correlation ρ = 0.862. Take the planted point (2.6, −2.5): its per-feature z-scores are (2.6 + 0.148)/0.919 = 2.99 and (−2.5 + 0.123)/0.813 = −2.92, so the max rule at k = 3.0 misses it — each coordinate is “only” about 3σ out. But the cloud runs along a diagonal, and (2.6, −2.5) sits far off that diagonal. The Mahalanobis distance, using Σ⁻¹, is 11.27; the largest baseline point scores 2.74. At the same 3.0 threshold, per-feature z flags 4 of the 6 planted points and Mahalanobis flags all 6 with no false alarms. Distance from the center is not enough — direction matters.
Modify the z-score detector to stream: update the running mean and variance with Welford's algorithm as points arrive. Show on [10, 12, 14, 100] that after the last point the online mean and variance equal the batch values. What does the anomalous point do to the detector?Show one worked answer
Welford per point: n += 1; delta = x − mean; mean += delta/n; M2 += delta·(x − mean_new). Starting from mean 0, M2 0: after 10 → mean 10, M2 0; after 12 → mean 11, M2 2; after 14 → mean 12, M2 8; after 100 → delta 88, mean 34, M2 = 8 + 88·(100 − 34) = 5,816. Variance = 5,816/4 = 1,454 and std = 38.13. Batch check: deviations from 34 are −24, −22, −20, 66; squares 576 + 484 + 400 + 4,356 = 5,816. Identical. The catch: the streaming detector learns from every point it scores, so one wild value moves the mean to 34 and the std to 38 — the next anomaly has to be even wilder to stand out. Real monitors freeze the baseline (train on normal history), score new data against it, and use a rolling window or exponential decay when the distribution genuinely drifts.
The isolation lab reports c(61) = 7.376. Compute the score for the most isolated planted point (average path 3.458, displayed as 3.46) and for the deepest normal point (10.493). Why does the original method use max_samples = 256?Show one worked answer
score = 2^(−E[h]/c(n)). For 3.458: 2^(−3.458/7.376) = 2^(−0.469) = 0.723. For 10.493: 2^(−10.493/7.376) = 2^(−1.423) = 0.373. In the lab the six planted points score 0.648–0.723 and the normals top out at 0.623, so a threshold near 0.635 separates them with no false positives. max_samples = 256: each tree sees a random subsample instead of all n points. Path lengths of normal points saturate as n grows (c(n) grows only logarithmically), so a single anomaly competes against enormous leaf sizes; subsampling keeps the trees short, decorrelates them from each other, and makes each tree cheap. 256 is the paper's default: large enough that normal regions still look dense, small enough that every tree is fast.
Terms this lesson borrows from later lessons (or outside)
You do not need to master these here. Each one gets a proper treatment in its own lesson; the one-line meaning is enough to keep reading. Orange dotted underlines in the prose point back to this list.
normal distribution — The bell curve, described by a mean and a standard deviation. The z-score rule comes from it: about 99.7% of draws land within 3σ. (Phase 1, Lesson 06)
percentile / quantile — The value below which a given fraction of the data falls. Q1 and Q3 are the 25th and 75th percentiles; a percentile is not affected by how extreme the top values are. (Phase 1, Lesson 15)
covariance matrix Σ — A square table recording how each pair of features moves together. Σ⁻¹ in the Mahalanobis formula removes both the scale of every feature and the correlations between them. (Phase 1, Lesson 15)
principal component / PCA — The direction along which the data varies most. A linear autoencoder with a one-number bottleneck learns exactly this axis. (Phase 1, Lesson 10)
precision and recall — Precision: of the flagged items, the fraction that are real. Recall: of the real anomalies, the fraction that were flagged. They trade off against each other at every threshold. (Phase 2, Lesson 09)
AUPRC / AUROC — Area under the precision–recall (or ROC) curve, summarizing every threshold. With severe imbalance, AUROC can look great while precision at a practical threshold is terrible; AUPRC is the more honest summary. (Phase 2, Lesson 09)
kernel trick — Replacing dot products with a similarity function so a linear boundary in a high-dimensional space becomes a curved boundary in the input space. One-class SVM uses it to wrap a flexible shell around normal data. (Phase 2, Lesson 05)
autoencoder / bottleneck — A network trained to copy its input through a narrow layer. The narrow layer must discard what it cannot afford to keep, so only the regular structure of normal data survives. (Phase 3, Deep Learning)
decision tree — A model that repeatedly asks yes/no questions about features. An isolation tree is a decision tree built with random questions and no labels. (Phase 2, Lesson 04)
KEEP GOING
A picture is a start. Practice is the rest.
This lesson is a port of an open course. Everything here traces back to it — and the next step is running the code yourself.
Lesson text adapted from AI Engineering from Scratch (Phase 02, Lesson 16) and the Math Foundations Notebook reference build. The six labs, the deterministic datasets shared by the hero and labs, the Mahalanobis worked check, the contaminated-statistics arithmetic, the c(61) isolation numbers, the simplified one-class boundary and the ranking console are original to this page. Every score, threshold and precision@k shown is computed live from the displayed data.