Retrieval evaluation¶
Use this executable recipe from the repository root with the frozen development
environment. It extracts embeddings with an ordinary PyTorch loop, then passes
explicit query and reference tensors to
AccuracyCalculator. It uses synthetic tensors,
completes in under a second on CPU, and performs no downloads. The Python file
in docs/recipes is the canonical executable source; it is not an installed
library API.
| Task | Executable source | Command |
|---|---|---|
| Extract embeddings and evaluate split, two-stream, centroid, or hierarchical retrieval | View evaluation.py | uv run --frozen --no-sync python -m docs.recipes.evaluation |
The command imports the installed package, so install the checkout as
described in the development guide,
or prefix it with PYTHONPATH=src. Verify the contracts:
uv run --frozen --no-sync --env-file tests/cpu.env python -m pytest tests/recipes/test_evaluation.py
The command prints precision_at_1 and r_precision for one query split against
itself and against a separate gallery. The tests assert exact metric values for
each composition below.
Extract embeddings¶
extract_embeddings(model, loader) iterates a DataLoader yielding
(data, labels), concatenates model(data) in loader order, and L2-normalizes
each row. It runs under torch.inference_mode(), so outputs carry no autograd
graph. The caller chooses model.eval() or training mode, the device, the
dtype, any trunk/embedder composition, and any dimensionality reduction. The
function changes none of them. Drop the normalization when the selected
distance should see raw embeddings.
Labels are used as returned by the loader. Map strings or sparse identifiers to
integers before evaluation, for example with
torch.unique(labels, return_inverse=True) over the full label set.
Compose query and reference splits¶
split_accuracy(calculator, query, references, query_in_reference) takes
(embeddings, labels) pairs. When query_in_reference is true, it places the
query first in the reference tensor and passes ref_includes_query=True, so
each query's own row is excluded from its neighbors and from its relevant
count. Additional reference splits follow in the given order. When it is false,
the reference is only the given splits and no row is treated as a self-match.
| Query | References | Query in reference | Meaning |
|---|---|---|---|
val |
none | yes | Leave-one-out retrieval within val |
val |
train |
no | Retrieve val queries from a train gallery |
val |
train |
yes | Retrieve from val ∪ train, excluding each query |
Equal shapes or equal labels never imply self-matches. Pass the flag that describes how the reference was assembled.
Nearest-centroid accuracy¶
nearest_centroid_accuracy(calculator, query, reference) takes
(embeddings, labels) pairs. It replaces the reference with one mean embedding
per class and passes the centroids and their classes to get_accuracy. With one
reference per class, precision_at_1 is nearest-centroid accuracy: 1 when the
nearest centroid has the query's class, 0 otherwise. Use a calculator that
selects precision_at_1:
import torch
from docs.recipes.evaluation import nearest_centroid_accuracy
from pytorch_metric_learning.evaluation import AccuracyCalculator
reference = torch.tensor([[0.0, 0.0], [2.0, 0.0], [6.0, 0.0], [8.0, 0.0]])
reference_labels = torch.tensor([0, 0, 1, 1])
query = torch.tensor([[0.5, 0.0], [3.0, 0.0], [5.0, 0.0]])
query_labels = torch.tensor([0, 1, 1])
calculator = AccuracyCalculator(include=("precision_at_1",))
accuracy = nearest_centroid_accuracy(
calculator, (query, query_labels), (reference, reference_labels)
)
assert accuracy["precision_at_1"] == 2 / 3
The calculator's knn_func sets the geometry, avg_of_avgs sets the weighting
of classes, and a query whose class has no reference is a
lone query, excluded from the result.
The centroids are arithmetic means with the reference's dtype and device. Class
sums accumulate in float64, or float32 on MPS, so a large low-precision gallery
does not overflow before the division. For cosine geometry, normalize embeddings
before calling the function, and use a knn_func with CosineSimilarity, which
normalizes each centroid before comparing. Other metrics in the calculator would
rank against one reference per class, so they are not meaningful here.
Two-stream evaluation¶
two_stream_accuracy(calculator, anchors, references, labels) evaluates
anchors against a separate reference stream whose row i shares label i with
anchor i. The reference never includes the anchors, so no row is removed.
Parent-conditioned hierarchical retrieval¶
parent_conditioned_accuracy takes labels shaped (N, L), ordered from the
finest level to the coarsest. For each level l < L - 1, it evaluates level-l
labels within each parent group defined by column l + 1, considering only
references with that parent. The result for a level is the unweighted mean over
parent groups present in the query, under the key <metric>_level<l>. With more
than one evaluated level, AVERAGE_<metric> is the unweighted mean over levels.
For example, a cat query is ranked only against other animals, and a truck query
only against other vehicles.
For global, unconditioned evaluation of one label level, pass that column to
split_accuracy directly.
Published benchmark splits¶
The library does not download or parse datasets. The splits below are the conventional metric-learning protocols for common benchmarks. Applications load images with their own data pipeline and must respect each dataset's license and terms.
| Dataset | Training split | Test split |
|---|---|---|
| CUB-200-2011, Wah et al. | Classes 1–100, 5,864 images | Classes 101–200, 5,924 images |
| Cars196, Krause et al., 3D Object Representations for Fine-Grained Categorization | Classes 1–98, 8,054 images | Classes 99–196, 8,131 images |
| Stanford Online Products, Oh Song et al. | Ebay_train.txt, 59,551 images |
Ebay_test.txt, 60,502 images |
| iNaturalist 2018, Van Horn et al. | 325,846 images | 136,093 images |
The iNaturalist retrieval split is the Inaturalist_{train,test}_set1.txt
protocol published with Smooth-AP
by Brown et al. Train and test classes are disjoint in every row, so evaluate the
test split against itself with query_in_reference=True.