Anomaly Detection with Isolation Forests
What anomaly detection is
Anomaly detection (also called outlier detection) finds instances that deviate strongly from the norm. The odd-ones-out are called anomalies or outliers; the regular instances are called inliers.
It shows up everywhere:
- fraud detection on credit card transactions
- spotting defective products on a manufacturing line
- catching sensor failures or sudden usage spikes
- cleaning a dataset of bad instances before training another model on it
Géron draws a useful distinction:
- anomaly detection — the training set may already contain some outliers
- novelty detection — the algorithm is trained only on “clean” data, so anything unlike the training set is flagged as novel
Why Isolation Forest works
Isolation Forest isolates points using random splits, the same way a Decision Tree grows: at every node it picks a random feature and a random threshold between that feature’s min and max, chopping the data in two. Keep doing this and every instance eventually ends up alone in its own region.
The trick is that anomalies are easier to isolate:
- they sit far from other points, so a handful of random cuts is enough to wall them off
- normal points are surrounded by neighbors, so it takes many more cuts to isolate them
Average the number of splits needed across a whole forest of these random trees, and you get an anomaly score for free — no distance metric, no density estimate.
flowchart LR A["Random split trees"] --> I["Isolate points"] I --> S["Short path length -> anomaly"] I --> L["Long path length -> normal"]
Scikit-learn example
Ten obvious outliers scattered around a tight blob of normal points — Isolation Forest should catch most of them:
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
rng = np.random.RandomState(42)
X_inliers, _ = make_blobs(n_samples=120, centers=1, cluster_std=0.5, random_state=42)
X_outliers = rng.uniform(low=-6, high=6, size=(12, 2))
X = np.vstack([X_inliers, X_outliers])
iso = IsolationForest(n_estimators=200, contamination=0.1, random_state=42)
labels = iso.fit_predict(X) # 1 = normal, -1 = anomaly
print("Total points:", len(X))
print("Detected anomalies:", int((labels == -1).sum()))import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
rng = np.random.RandomState(42)
X_inliers, _ = make_blobs(n_samples=120, centers=1, cluster_std=0.5, random_state=42)
X_outliers = rng.uniform(low=-6, high=6, size=(12, 2))
X = np.vstack([X_inliers, X_outliers])
iso = IsolationForest(n_estimators=200, contamination=0.1, random_state=42)
labels = iso.fit_predict(X) # 1 = normal, -1 = anomaly
print("Total points:", len(X))
print("Detected anomalies:", int((labels == -1).sum()))Total points: 132
Detected anomalies: 14Total points: 132
Detected anomalies: 14Scoring anomalies
fit_predict()fit_predict() only gives you a hard yes/no. score_samples()score_samples() gives a continuous
score instead — the lower the score, the more anomalous the point — so you can rank
instances or pick your own cutoff:
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
rng = np.random.RandomState(42)
X_inliers, _ = make_blobs(n_samples=120, centers=1, cluster_std=0.5, random_state=42)
X_outliers = rng.uniform(low=-6, high=6, size=(12, 2))
X = np.vstack([X_inliers, X_outliers])
iso = IsolationForest(n_estimators=200, contamination=0.1, random_state=42)
iso.fit(X)
scores = iso.score_samples(X)
most_anomalous = np.argsort(scores)[0]
print("Lowest anomaly score index:", most_anomalous)
print("Is it one of the injected outliers?", most_anomalous >= 120)import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.datasets import make_blobs
rng = np.random.RandomState(42)
X_inliers, _ = make_blobs(n_samples=120, centers=1, cluster_std=0.5, random_state=42)
X_outliers = rng.uniform(low=-6, high=6, size=(12, 2))
X = np.vstack([X_inliers, X_outliers])
iso = IsolationForest(n_estimators=200, contamination=0.1, random_state=42)
iso.fit(X)
scores = iso.score_samples(X)
most_anomalous = np.argsort(scores)[0]
print("Lowest anomaly score index:", most_anomalous)
print("Is it one of the injected outliers?", most_anomalous >= 120)Lowest anomaly score index: 126
Is it one of the injected outliers? TrueLowest anomaly score index: 126
Is it one of the injected outliers? TrueTips
contaminationcontaminationshould be your best guess of the anomaly rate — raise it and you’ll flag more points as anomalies (more false positives); lower it and you’ll miss some real ones (more false negatives). It’s the usual precision/recall trade-off.- scale features first if they differ dramatically in range — a feature measured in the thousands can dominate the random splits
decision_function()decision_function()also works and is centered so 0 is roughly the cutoff between normal and anomalous
Pros and cons
Pros:
- efficient even in high-dimensional data
- no distance or density computation required
- scales well — building random trees is cheap
Cons:
contaminationcontaminationneeds a reasonable guess; get it very wrong and the labels are noisy- like any random-forest-style method, individual runs vary slightly unless you fix
random_staterandom_state
Mini-checkpoint
If contamination is too high, what happens?
- you’ll label too many normal points as anomalies (more false positives).
Visualize it
Watch random axis-aligned splits carve up the plane. Each split cuts one region in two; a point turns red the moment it ends up alone in its own region — that’s “isolated.” The four corner outliers get isolated in just a couple of splits, while the crowded cluster in the middle takes many more:
🧪 Try It Yourself
Exercise 1 – Fit an Isolation Forest
Exercise 2 – Rank Points by Anomaly Score
Exercise 3 – Raising Contamination Flags More Points
Next
Continue to Association Rule Learning (Apriori Algorithm) — a very different unsupervised technique for finding “if A then B” patterns instead of scoring outliers.
If this helped you, consider buying me a coffee ☕
Buy me a coffeeWas this page helpful?
Let us know how we did
