Anomaly Detection Cheat Sheet
Explains point, contextual, and collective anomalies, key detection algorithms like One-Class SVM and autoencoders, and how to evaluate results on imbalanced data.
Types of Anomalies
Categorize the anomaly before picking a technique.
- Point anomaly- A single data instance that is far from the rest of the data (e.g., one huge transaction)
- Contextual anomaly- Normal in general but abnormal given a specific context (e.g., high heating usage in summer)
- Collective anomaly- A group of related instances that is anomalous together, even if individual points look normal
- Global vs. local- Global anomalies deviate from the whole dataset; local anomalies deviate only from their neighborhood
One-Class SVM
Learn a boundary around normal data using only non-anomalous training examples.
from sklearn.svm import OneClassSVM# Train only on data assumed to be normalclf = OneClassSVM(kernel='rbf', nu=0.05, gamma='scale')clf.fit(X_train_normal)preds = clf.predict(X_test) # 1 = normal, -1 = anomaly
Autoencoder Reconstruction Error
Flag inputs the network reconstructs poorly as anomalies.
import torch, torch.nn as nnclass Autoencoder(nn.Module): def __init__(self, in_dim, latent_dim=8): super().__init__() self.encoder = nn.Sequential(nn.Linear(in_dim, 32), nn.ReLU(), nn.Linear(32, latent_dim)) self.decoder = nn.Sequential(nn.Linear(latent_dim, 32), nn.ReLU(), nn.Linear(32, in_dim)) def forward(self, x): return self.decoder(self.encoder(x))# After training on normal data only:recon = model(x_batch)error = torch.mean((recon - x_batch) ** 2, dim=1)anomalies = error > threshold # threshold set from validation error distribution
Evaluating Anomaly Detectors
Anomaly datasets are almost always heavily imbalanced.
- Precision@k- Fraction of the top-k flagged points that are true anomalies; useful when review capacity is limited
- Recall- Fraction of true anomalies that were caught; often prioritized when missed anomalies are costly
- PR-AUC- Area under the precision-recall curve; more informative than ROC-AUC on rare-class problems
- F1 / F-beta score- Harmonic mean of precision and recall; use F-beta to weight recall higher when misses are costly
LSTM Autoencoder for Time-Series Anomalies
Reconstruct sliding windows of a sequence and flag windows with high reconstruction error as collective/contextual anomalies.
import torch, torch.nn as nnclass LSTMAutoencoder(nn.Module): def __init__(self, n_features, seq_len, latent_dim=16): super().__init__() self.encoder = nn.LSTM(n_features, latent_dim, batch_first=True) self.decoder = nn.LSTM(latent_dim, n_features, batch_first=True) self.seq_len = seq_len def forward(self, x): _, (h, _) = self.encoder(x) h_rep = h[-1].unsqueeze(1).repeat(1, self.seq_len, 1) out, _ = self.decoder(h_rep) return out# windows: (batch, seq_len, n_features) built from a rolling window over normal datarecon = model(windows)error = torch.mean((recon - windows) ** 2, dim=(1, 2))anomalous_windows = error > threshold # threshold from validation error quantile
Change Point Detection for Collective Anomalies
Detect shifts in a signal's statistical properties (mean, variance) rather than individual outlying points, using the ruptures library.
import ruptures as rptimport numpy as npsignal = df['metric'].values# PELT with an L2 cost is fast and doesn't require specifying the number of change pointsalgo = rpt.Pelt(model='l2', min_size=10, jump=1).fit(signal)change_points = algo.predict(pen=10)print(f"Detected {len(change_points) - 1} segments, breakpoints at {change_points[:-1]}")# Segments with unusually short duration or extreme mean shift = collective anomalies
STL Decomposition Residual Thresholding
Separate trend and seasonality before flagging anomalies, so a normal seasonal peak isn't mistaken for a contextual anomaly.
from statsmodels.tsa.seasonal import STLimport numpy as npstl = STL(series, period=24, robust=True).fit() # robust=True downweights outliers while fittingresidual = stl.residmad = np.median(np.abs(residual - np.median(residual)))modified_z = 0.6745 * (residual - np.median(residual)) / madanomalies = series[np.abs(modified_z) > 3.5]# stl.trend and stl.seasonal are available to visualize why a point is/isn't anomalous
Online Anomaly Detection with River
Score and adapt to each new event one at a time for streaming data, instead of retraining a batch model on a fixed window.
from river import anomalymodel = anomaly.HalfSpaceTrees(n_trees=25, height=8, window_size=250, seed=42)for event in event_stream: # event: dict of feature -> value score = model.score_one(event) # higher = more anomalous, before update model = model.learn_one(event) if score > 0.8: alert(event, score)
Production Anomaly Detection Concerns
Operational issues that matter more than algorithm choice once a detector ships.
- Concept drift- 'Normal' shifts over time (seasonality, growth); retrain or use online/adaptive models instead of a static threshold
- Alert fatigue- Too many false positives cause operators to ignore alerts; tune thresholds against a labeled precision target, not recall alone
- Delayed ground truth- True labels (fraud confirmed, incident resolved) often arrive days later, so online evaluation must track predictions against delayed feedback
- Cold start- New entities (users, sensors, servers) have no history to establish a baseline; fall back to population-level or cohort-level models
- Explainability- Autoencoder/One-Class SVM scores are hard to justify to operators; pair with per-feature reconstruction error or SHAP to explain each flag
Never tune the anomaly threshold on the test set -- pick it from a held-out validation set's score distribution, then evaluate once on test to get an honest estimate of production performance.