Online Metric Learning: OASIS, LEGO and POLA¶
A k-nearest-neighbours classifier is only as good as its distance. Euclidean distance treats
every feature the same; metric learning learns a matrix M so that
d(u, v) = sqrt((u - v)^T M (u - v)) pulls same-class points together and pushes
different-class points apart. This page implements three online metric learners from their
papers, one sample (pair or triplet) at a time, and compares them as kNN metrics.
Step 1. Three algorithms, three kinds of supervision¶
| Algorithm | Paper | Learns from | Model | Update |
|---|---|---|---|---|
| OASIS | Chechik, Sharma, Shalit, Bengio, NIPS 2009 | triplets (query, positive, negative) | bilinear similarity x^T W y, no PSD or symmetry constraint |
passive-aggressive: W += tau * x (x+ - x-)^T, tau = min(C, loss / ‖V‖²) |
| LEGO | Jain, Kulis, Dhillon, Grauman, NIPS 2008 | pairs with a target squared distance | Mahalanobis A |
LogDet-regularized closed form: A -= eta (y_bar - y) A z z^T A / (1 + eta (y_bar - y) y_hat) |
| POLA | Shalev-Shwartz, Singer, Ng, ICML 2004 | pairs labelled similar (+1) / dissimilar (-1) | pseudo-metric (A, b) |
A -= y alpha v v^T, alpha = loss / (1 + ‖v‖⁴), then projection onto the PSD cone and b >= 1 |
All three are in scripts/river_metric_learning/metric_learning.py.
Step 2. Train one and use it as a kNN metric¶
import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from metric_learning import mahalanobis, train_pola
X, y = load_wine(return_X_y=True)
X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, test_size=0.7, random_state=42)
mu, sd = X_tr.mean(0), X_tr.std(0) + 1e-9
X_tr, X_te = (X_tr - mu) / sd, (X_te - mu) / sd
A = train_pola(X_tr, y_tr, n_pairs=30000)
KNeighborsClassifier(n_neighbors=3, metric=mahalanobis(A)).fit(X_tr, y_tr).score(X_te, y_te)
LEGO needs a target distance for each pair, not a similar/dissimilar flag: same-class pairs
get the 5th percentile of same-class squared distances, different-class pairs the 95th
percentile of different-class ones (Section 4 of the paper). OASIS learns a similarity, not a
distance; to use it in kNN this page takes M = W^T W, which is always positive semi-definite
(see Step 4).
Step 3. Dense data: same split for every method¶
Setup: 30% train / 70% test (stratified, random_state=42), features standardized on the
training set, k = 3. OASIS with C = 0.01; triplets/pairs: 20,000 (Iris), 30,000 (Wine),
50,000 (Digits; POLA 20,000 because of its eigendecomposition per similar pair).
| Dataset | Features | Euclidean | OASIS (W^T W) |
LEGO | POLA |
|---|---|---|---|---|---|
| Iris | 4 | 0.9333 | 0.9524 (+1.9) | 0.9619 (+2.9) | 0.9524 (+1.9) |
| Wine | 13 | 0.9200 | 0.9520 (+3.2) | 0.9360 (+1.6) | 0.9520 (+3.2) |
| Digits | 64 | 0.9459 | 0.9603 (+1.4) | 0.9372 (−0.9) | 0.9658 (+2.0) |
POLA is best or tied-best on Wine and Digits and never below the baseline; LEGO wins on Iris but loses on Digits. Differences are of a few test points on Iris and Wine (105 and 125 test samples), so they are indicative, not decisive.
Step 4. Sparse text: how OASIS's W becomes a distance¶
On 20 newsgroups (500 binary word features, L2-normalized, 70/30 split, k = 5, 20,000
triplets), the way W is turned into a kNN distance matters more than C:
| Pair | C | Euclidean | (W + W^T)/2 |
W^T W |
|---|---|---|---|---|
| sci.space vs comp.graphics | 0.01 | 0.5255 | 0.6071 | 0.6820 |
| sci.space vs comp.graphics | 0.1 | 0.5255 | 0.6922 | 0.8588 |
| comp.sys.ibm.pc.hardware vs sci.electronics | 0.01 | 0.5051 | 0.5356 | 0.5593 |
| comp.sys.ibm.pc.hardware vs sci.electronics | 0.1 | 0.5051 | 0.6085 | 0.7831 |
W^T W beats the symmetrized W by 17 points on both pairs at C = 0.1. This is our own
finding, not the paper's: Section 4.3.1 of the OASIS paper compares symmetric variants
(sym(W) and a dissimilarity form) against the asymmetric score in a ranking task and finds
them "slightly worse, or equal"; W^T W does not appear there. A plausible reason it works
here: (W + W^T)/2 can have negative eigenvalues, so (u - v)^T M (u - v) stops being a
distance, while W^T W is always positive semi-definite.
The euclidean baseline on text is close to chance (0.51–0.53 for two classes): with 500 binary features and headers removed, raw word overlap is a weak signal, which is exactly where a learned metric has the most room.
What this shows¶
- On small dense data all three improve kNN on Iris and Wine; POLA is the most reliable (never below the baseline here), at the cost of an eigendecomposition per similar pair.
- On sparse high-dimensional text, OASIS (no PSD constraint, cheap updates) gives large gains,
provided
Wis turned into a distance throughW^T W. - A metric learner does not fit river's existing estimator types: it learns from pairs or
triplets and outputs a distance, not a prediction. Plugging one into river would need its
own base class (for example
learn_triplet/learn_pairand adistancemethod) thatneighborsestimators can take as their metric.
Reproduce¶
pip install scikit-learn
python scripts/river_metric_learning/benchmark.py
pytest tests/dense_armor/test_river_metric_learning.py
benchmark.py prints both tables and writes data/river_metric_learning.csv (about 11
minutes on a laptop CPU, mostly POLA's eigendecompositions and the 20 newsgroups download).
Details¶
- LEGO input: the original notebook passed a boolean (same class or not) as LEGO's target; the paper's target is a squared distance. With the boolean, same-class pairs are pushed toward distance 1. The script uses the 5th/95th-percentile targets of Section 4.
- An earlier version of the dense table mixed two splits (OASIS on 70/30, LEGO and POLA on 30/70), so the deltas did not match the baseline column; Step 3 uses one split for all.
- The different-class percentile excludes the zero self-distances on the diagonal.
- kNN distances are computed as a precomputed matrix (
d² = uMu + vMv − 2uMv, clipped at1e-12), identical to a per-pair callable and much faster.