Most anomaly detectors describe the normal data and flag whatever doesn't fit the description. The Elliptic Envelope fits one Gaussian ellipse; the Local Outlier Factor compares each point's local density to its neighbours'; the Isolation Forest counts how few random cuts isolate a point. The One-Class SVM does something different: it takes the max-margin idea from a regular SVM and turns it inward, learning a curved frontier that wraps the support of the data as tightly as it can — then calls anything outside a novelty. That boundary can be a ring, two islands, a crescent — shapes a single ellipse can't represent.
The decision function
With only "normal" examples and no anomalies to learn from, the model maps points through an RBF kernel K(x, y) = exp(−γ‖x − y‖²) — the kernel trick, so the infinite-dimensional feature map is never built — and produces a decision function of the form:
f(x) = Σᵢ αᵢ K(x, xᵢ) − ρ
f(x) ≥ 0 is inside (normal), f(x) < 0 is outside (anomaly), and the boundary is the contour f = 0. The points that end up with αᵢ > 0 are the support vectors — the only ones that shape the frontier.
A didactic realization worth being honest about
The real one-class SVM (Schölkopf's formulation) finds a sparse set of weights αᵢ by solving a dual quadratic program with SMO: min ½ αᵀKα subject to Σα = 1 and the box 0 ≤ αᵢ ≤ 1/(νn). The demo behind this walkthrough does not run SMO. It keeps the identical decision-function form but takes the transparent SVDD / Parzen choice αᵢ = 1/n, so the raw score of each point is simply its RBF kernel density — high in the crowded core, low out on the fringe. This is a faithful didactic approximation, tuned so ν and γ behave exactly as the textbook says and every kernel contribution stays visible — not the exact sparse solve.
def rbf(a, b, g):
dx, dy = a.x - b.x, a.y - b.y
return math.exp(-g * (dx * dx + dy * dy)) # 1 when identical -> 0 when far
# SVDD / Parzen score of each point = its RBF kernel density
# sᵢ = (1/n) Σⱼ K(xᵢ, xⱼ) (uniform weight αⱼ = 1/n)
def density_scores(pts, gamma):
return [sum(rbf(p, q, gamma) for q in pts) / len(pts) for p in pts]
The ν-quantile offset, and the ν guarantee
Where you place ρ decides how many points fall outside. The beautiful property of ν is that it is both an upper bound on the fraction of outliers and a lower bound on the fraction of support vectors. The demo realizes that directly by setting ρ to the ν-quantile of the scores, so exactly ⌊ν·n⌋ points score below it and are flagged:
def choose_rho(scores, nu):
s = sorted(scores) # ascending
k_out = min(len(s) - 1, int(nu * len(s)))
return s[k_out] # exactly k_out points below rho
def decision(x, pts, gamma, rho): # signed score at ANY point
return sum(rbf(x, p, gamma) for p in pts) / len(pts) - rho
Two knobs govern everything. ν sets the flagged fraction. γ is the RBF width: raise it and each point's influence decays fast, tightening the frontier and making it wigglier; lower it and the influence is broad, smoothing toward one round dome. The boundary itself is drawn by evaluating f on a grid and running marching squares on the f = 0 sign changes.
Where it sits
In practice you reach for scikit-learn, which wraps libsvm's SMO:
from sklearn.svm import OneClassSVM
oc = OneClassSVM(kernel="rbf", nu=0.1, gamma="scale")
oc.fit(X_train_normal) # trained on inliers only
labels = oc.predict(X_new) # +1 inlier / -1 novelty
scores = oc.decision_function(X_new) # signed distance to the boundary (f)
Because it fits a boundary rather than a density or a distance, it wraps non-convex shapes the Elliptic Envelope cannot, and unlike the transductive LOF it is inductive — once fit, it scores brand-new points, which makes it a go-to for genuine novelty detection. The costs are the flip side of that power: two coupled knobs with no label to cross-validate against, real sensitivity to feature scaling and to contamination in the training set, and an O(n²)–O(n³) solve that keeps it a moderate-n tool.
Scatter your own data and watch the frontier breathe as you slide ν and γ: https://dev48v.infy.uk/ml/day58-one-class-svm.html
Top comments (0)