Winning Kaggle the Reproducible Way

Interactive chapter

This page shows the chapter notebook with its saved results. Constructed examples do not measure competition gains.

Chapter 45: Metric Learning and ArcFace

Try the decision first

How much does the angular margin improve retrieval of unseen identities over a cross-entropy descriptor, and where does a larger margin stop helping?

Write your expectation before opening the worked results. Use the chapter activity to compare supported inputs. To change the calculation itself, run the notebook on GitHub.

Your answers stay on this page until you download them. They are not sent to a server.

Open the chapter activity

Worked notebook and saved results

How much does the angular margin improve retrieval of unseen identities over a cross-entropy descriptor, and where does a larger margin stop helping?

The margin m is one of the two head settings the chapter calls starting candidates rather than a universal winner. Sixteen paired trainings, each judged on query identities absent from training, show how the gain moves with m and when training becomes unreliable.

Constructed data and a linear embedding; the sizes of these effects are properties of this generator, not a competition result.

The experiment

Constructed image features: 40 training identities with 6 images each, 32 features of which 8 carry identity and 24 are larger nuisance noise (pose and light), mixed by a random rotation. A linear embedding to 8 dimensions is trained with Adam in two ways from the same start: a plain linear cross-entropy head (the reference descriptor), and the chapter's ArcMarginHead with scale s = 16 and the control's margin m, whose gradients are written out in numpy. Retrieval is scored as MAP@5 on 40 further identities (3 gallery and 3 query images each), with every ranked identity listed once, as in the chapter's retrieval rule, averaged over 16 draws.

Calculation code
import numpy as np

from kaggle_companion.activities._common import clean

SEED = 45
DRAWS = 16                 # independent worlds; every estimate is a mean over draws and every gain is paired
D_SIG, D_NUIS = 8, 24      # identity-bearing dimensions and nuisance (pose, light) dimensions of the 32 image features
TRAIN_IDS, TRAIN_IMAGES = 40, 6
TEST_IDS = 40              # unseen identities, three gallery images and three query images each
SCALE = 16.0               # the chapter's s (feature scale); 16 is enough for 40 classes
EPOCHS = 300


def images(rotation, centers, ids, rng):
    """Image features: identity signal plus larger nuisance noise, mixed by a fixed rotation (a stand-in for backbone features)."""
    signal = centers[ids] + rng.normal(size=(len(ids), D_SIG))
    nuisance = 2.0 * rng.normal(size=(len(ids), D_NUIS))
    return np.column_stack([signal, nuisance]) @ rotation.T


def arc_logits(cos, labels, margin):
    """The chapter's ArcMarginHead: add the margin to the target angle, cos(theta + m), with the stability fix near pi - m."""
    sin = np.sqrt(1 - cos ** 2)
    shifted = np.where(cos > np.cos(np.pi - margin), cos * np.cos(margin) - sin * np.sin(margin),
                       cos - np.sin(np.pi - margin) * margin)
    out = cos.copy()
    out[np.arange(len(labels)), labels] = shifted[np.arange(len(labels)), labels]
    return SCALE * out


def loss_and_grads(params, X, labels, margin, head):
    """Softmax cross-entropy loss and its gradients. head 'ce': plain linear head. head 'arc': normalised features, prototypes and margin."""
    W, P = params
    n = len(X)
    E = X @ W
    onehot = np.eye(len(P))[labels]
    if head == "ce":
        logits = E @ P.T
    else:
        norm_e = np.linalg.norm(E, axis=1, keepdims=True)
        norm_p = np.linalg.norm(P, axis=1, keepdims=True)
        U, V = E / norm_e, P / norm_p
        cos = np.clip(U @ V.T, -1 + 1e-7, 1 - 1e-7)
        logits = arc_logits(cos, labels, margin)
    z = logits - logits.max(1, keepdims=True)
    prob = np.exp(z) / np.exp(z).sum(1, keepdims=True)
    loss = -np.mean(np.log(prob[np.arange(n), labels]))
    dlogits = (prob - onehot) / n
    if head == "ce":
        return loss, [X.T @ (dlogits @ P), dlogits.T @ E]
    sin = np.sqrt(1 - cos ** 2)
    stable = cos > np.cos(np.pi - margin)
    slope = np.where(stable, np.cos(margin) + cos * np.sin(margin) / sin, 1.0)
    dcos = SCALE * dlogits * np.where(onehot == 1, slope, 1.0)
    dU, dV = dcos @ V, dcos.T @ U
    dE = (dU - (dU * U).sum(1, keepdims=True) * U) / norm_e      # back through the length normalisation
    dP = (dV - (dV * V).sum(1, keepdims=True) * V) / norm_p
    return loss, [X.T @ dE, dP]


def train(X, labels, head, margin, seed):
    """Full-batch Adam on a linear 32 -> 8 embedding and the class prototypes. Returns the embedding matrix."""
    rng = np.random.default_rng(seed)
    params = [rng.normal(0, 1 / np.sqrt(X.shape[1]), (X.shape[1], D_SIG)), rng.normal(0, 1, (TRAIN_IDS, D_SIG))]
    m1 = [np.zeros_like(p) for p in params]
    m2 = [np.zeros_like(p) for p in params]
    for t in range(1, EPOCHS + 1):
        _, grads = loss_and_grads(params, X, labels, margin, head)
        for i, g in enumerate(grads):
            m1[i] = 0.9 * m1[i] + 0.1 * g
            m2[i] = 0.999 * m2[i] + 0.001 * g * g
            params[i] -= 0.02 * (m1[i] / (1 - 0.9 ** t)) / (np.sqrt(m2[i] / (1 - 0.999 ** t)) + 1e-8)
    return params[0]


def unit(a):
    return a / np.linalg.norm(a, axis=1, keepdims=True)


def map_at_5(query, query_ids, gallery, gallery_ids):
    """MAP@5 with one true identity per query. Ranked identities are listed once each, as in the chapter's retrieval rule."""
    similarity = unit(query) @ unit(gallery).T
    scores = []
    for sim, truth in zip(similarity, query_ids):
        ranked = []
        for j in np.argsort(-sim):
            if gallery_ids[j] not in ranked:
                ranked.append(gallery_ids[j])
            if len(ranked) == 5:
                break
        scores.append(1.0 / (ranked.index(truth) + 1) if truth in ranked else 0.0)
    return float(np.mean(scores))


def separation(query, query_ids, gallery, gallery_ids):
    """Mean cosine between same-identity pairs and between different-identity pairs."""
    sim = unit(query) @ unit(gallery).T
    same = query_ids[:, None] == gallery_ids[None, :]
    return float(sim[same].mean()), float(sim[~same].mean())


def run(margin):
    rng = np.random.default_rng(SEED)
    rows = {"ce": [], "arc": []}
    for draw in range(DRAWS):
        rotation, _ = np.linalg.qr(rng.normal(size=(D_SIG + D_NUIS, D_SIG + D_NUIS)))
        train_centers, test_centers = rng.normal(size=(TRAIN_IDS, D_SIG)) * 2, rng.normal(size=(TEST_IDS, D_SIG)) * 2
        y = np.repeat(np.arange(TRAIN_IDS), TRAIN_IMAGES)
        X = images(rotation, train_centers, y, rng)
        ids_seen, ids_unseen = np.repeat(np.arange(TRAIN_IDS), 3), np.repeat(np.arange(TEST_IDS), 3)
        sets = {"seen": (images(rotation, train_centers, ids_seen, rng), images(rotation, train_centers, ids_seen, rng), ids_seen),
                "unseen": (images(rotation, test_centers, ids_unseen, rng), images(rotation, test_centers, ids_unseen, rng), ids_unseen)}
        for name, head, m in (("ce", "ce", 0.0), ("arc", "arc", float(margin))):
            W = train(X, y, head, m, seed=draw)
            row = {}
            for split, (gallery, query, ids) in sets.items():
                row["map_" + split] = map_at_5(query @ W, ids, gallery @ W, ids)
            same, diff = separation(sets["unseen"][1] @ W, ids_unseen, sets["unseen"][0] @ W, ids_unseen)
            row.update({"same_cos": same, "diff_cos": diff})
            rows[name].append(row)
    mean = {n: {k: float(np.mean([r[k] for r in rs])) for k in rs[0]} for n, rs in rows.items()}
    gain = np.array([a["map_unseen"] - c["map_unseen"] for a, c in zip(rows["arc"], rows["ce"])])
    return clean({
        "margin": margin, "ce": mean["ce"], "arc": mean["arc"],
        "gain_unseen": gain.mean(), "gain_se": gain.std(ddof=1) / np.sqrt(DRAWS), "win_rate": float((gain > 0).mean()),
        "per_draw": {"ce": [r["map_unseen"] for r in rows["ce"]], "arc": [r["map_unseen"] for r in rows["arc"]]},
        "draws": DRAWS, "train_identities": TRAIN_IDS, "unseen_identities": TEST_IDS,
    })

Measure it across the control

The control is angular margin m (radians). Each value below runs the whole experiment from the generator up.

Calculation code
import pandas as pd
from kaggle_companion.activities.ch45 import explain

VALUES = [0, 0.5, 0.8, 1.2]
results = {value: run(value) for value in VALUES}
table = pd.DataFrame({str(v): explain(results[v], v)['metrics'] for v in VALUES})
print(table.to_string())
                                           0             0.5             0.8             1.2
Reference MAP@5, unseen                0.640           0.640           0.640           0.640
ArcFace MAP@5, unseen                  0.685           0.778           0.781           0.686
Paired gain (standard error)  +0.046 (0.012)  +0.139 (0.012)  +0.142 (0.014)  +0.047 (0.032)
ArcFace wins in draws                    81%            100%            100%             69%
ArcFace worst draw                     0.617           0.703           0.725           0.452
ArcFace MAP@5, seen                    0.676           0.771           0.778           0.679

Look at it

The figure shows the default setting, angular margin m (radians) = 0.5.

Calculation code
import matplotlib.pyplot as plt
from kaggle_companion.activities.ch45 import draw, NCOLS, HEIGHT

DEFAULT = 0.5
fig, axes = plt.subplots(1, NCOLS, figsize=(6.6 if NCOLS == 1 else 10.4, HEIGHT), layout='constrained')
draw(axes, results[DEFAULT], DEFAULT)
plt.show()
Saved figure from this chapter calculation

What the result says

With margin 0.5, retrieval of unseen identities reaches MAP@5 0.778 against 0.640 for the cross-entropy descriptor: 0.778 - 0.640 = 0.138 (standard error 0.012, better in 100% of the 16 draws). The margin head is clearly better than the reference. On identities seen in training the margin head scores 0.771, close to its unseen score. The reference scores 0.634 on seen identities.

Apply this to your competition

Book location: Chapter 45, Two Head Settings: Scale and Margin.

Technical corrections

Apply a lesson from this chapter

Select a lesson whose conditions match your project. State how you would test it and what would make you reject it.

Merge pair-dependent keypoints before coarse reconstruction

Conditions

  • dense or cropped pairwise matchers produce inconsistent feature coordinates across views

Action

  • Proposed adaptation of source observation: Aggregate image matches, merge nearby points by confidence and reconstruct a coarse model before refining.

Check

  • Proposed verification: Measure track lengths, point counts, runtime and camera accuracy before/after merging.

Limits

  • Merging sacrifices initial matching precision; thresholds can collapse distinct nearby structures.

Find source links and value grades in the lesson atlas

Add detector keypoints to a semi-dense geometric matcher

Conditions

  • semi-dense sampling misses useful local geometric points

Action

  • Proposed adaptation of source observation: Compare detector-seeded matching with default semi-dense points and their union.

Check

  • Proposed verification: Evaluate geometric precision, registration quality and compute on held-out scenes.

Limits

  • Overlapping points may be redundant and the chosen detector pair was selected using LB.

Find source links and value grades in the lesson atlas

Benchmark dimensionality reduction on the actual pretrained embedding

Conditions

  • a fixed-dimensional embedding submission needs compression

Action

  • Proposed conditional application: Compare pooling or projection choices per backbone rather than transferring results from weak to strong embeddings.

Check

  • Proposed check: Compare compression methods at equal output dimension on the actual backbone and held-out retrieval pairs.

Limits

  • Projection distortion and pooling vary with learned geometry; t-SNE out-of-sample handling unspecified.

Find source links and value grades in the lesson atlas

Preserve failed matchers and runtime-excluded recovery methods

Conditions

  • registration recovery or model substitutions compete with a fixed inference budget

Action

  • Proposed adaptation of source observation: Compare quality and runtime before adopting substitutes; reserve a budget for unregistered-image recovery only if feasible.

Check

  • Proposed verification: Evaluate complete scenes and report whether recovery was actually used in submitted inference.

Limits

  • Unselected localization is not evidence of a final gain; some failures were suspected tuning limitations.

Find source links and value grades in the lesson atlas

Preserve embedding-space compatibility in retrieval ensembles

Conditions

  • retrieval embeddings from separate models may occupy incompatible coordinate systems

Action

  • Proposed adaptation of source observation: Compare related fine-tuned checkpoints before combining embedding vectors; measure compatibility rather than assuming alignment.

Check

  • Proposed verification: Evaluate retrieval metric against individual embeddings and inspect cross-model similarity structure.

Limits

  • A trained alignment matrix is only a proposed future method in this source; no use or gain is established.

Find source links and value grades in the lesson atlas

Union complementary retrieval shortlists before expensive matching

Conditions

  • expensive geometric matching needs pair coverage under limited time

Action

  • Proposed adaptation of source observation: Compare the union of modest complementary shortlists with a deeper single-retrieval shortlist.

Check

  • Proposed verification: Measure candidate overlap coverage, final reconstruction score and runtime, including any preclustering cost.

Limits

  • No universal top-k follows; preclustering was an explicitly unselected alternative.

Find source links and value grades in the lesson atlas

Center visual-style descriptors to remove common content

Conditions

  • writer identification needs deviations from typical character appearance

Action

  • Proposed conditional application: Compute multiscale rooted-normalized oBIF histograms, subtract training mean histograms and classify deviations with Euclidean nearest neighbors.

Check

  • Proposed check: Fit means and descriptor tuning only on training folds, compare centered versus raw histograms, and test unseen-writer rejection.

Limits

  • Specialist oBIF descriptor and four parameters require vision expertise; source232formatting is ambiguous.

Find source links and value grades in the lesson atlas

Compare taxonomy auxiliary losses and metric heads in fine-grained recognition

Conditions

  • fine-grained image labels include a meaningful taxonomy

Action

  • Proposed adaptation of source observation: Evaluate auxiliary taxonomy heads and combined margin/CE outputs with separate controlled ablations.

Check

  • Proposed verification: Use the same data split and report top-level and leaf-level errors plus cost.

Limits

  • The table is sequential, so rows are conditional changes rather than independent additive gains; metric name is not supplied here.

Find source links and value grades in the lesson atlas

Stage embedding fine-tuning to protect class geometry

Conditions

  • a pretrained image embedding overfits during full fine-tuning

Action

  • Proposed conditional application: First train the dimensionality-reduction head, then unfreeze the backbone at a lower learning rate while freezing the final classifier and adding projection dropout.

Check

  • Proposed check: Ablate staged training, classifier freezing and dropout separately using unseen-category retrieval validation.

Limits

  • Multi-change gain is not isolated; frozen classifier may constrain adaptation.

Find source links and value grades in the lesson atlas

Optimize matching implementation before adding heavier models

Conditions

  • matching pipeline is near submission runtime limits

Action

  • Proposed adaptation of source observation: Profile attention/position modules, verify AMP is actually enabled and distribute independent scenes where supported.

Check

  • Proposed verification: Check numerical output parity, complete scene results and wall time with runtime margin.

Limits

  • Failed TensorRT/VGGT paths were not final methods; custom accelerations can change precision or platform requirements.

Find source links and value grades in the lesson atlas

Separate known-identity and unseen-identity verification performance

Conditions

  • a verifier derives similarities from known-class probability vectors

Action

  • Proposed conditional application: Report seen-artist and unseen-artist AUC separately before trusting open-set generalization.

Check

  • Proposed check: Hold out identities when fitting a verifier and report seen/unseen performance separately; compare proposed metric learning only as a new experiment.

Limits

  • Siamese metric learning is future work in this source; cohort AUC disparity is not an isolated method gain.

Find source links and value grades in the lesson atlas

Reuse class-distribution predictions as a pair-similarity baseline

Conditions

  • pairwise identity tasks have labeled training classes

Action

  • Proposed adaptation of source observation: Train class recognition and compute pair scores from cached probability-vector inner products before expensive pairwise training.

Check

  • Proposed verification: Compare pair AUC and memory/runtime with a dedicated metric-learning model.

Limits

  • The source calls this unsupervised verification, but the descriptor classifier is supervised; unseen identities may generalize poorly.

Find source links and value grades in the lesson atlas

Progress image resolution and dataset breadth in stages

Conditions

  • large retrieval training is costly and noisy

Action

  • Proposed conditional application: Train first on a clean low-resolution subset, then longer on broader medium-resolution data, and finish with a short high-resolution phase.

Check

  • Proposed check: Compare staged and fixed-resolution/data schedules at equal compute on held-out landmark metrics.

Limits

  • Changes data and resolution together; millions of images and long training are substantial.

Find source links and value grades in the lesson atlas

Use sparse matches to choose rotations and overlap crops

Conditions

  • image pairs include rotated views and partial overlaps

Action

  • Proposed adaptation of source observation: Use a cheap sparse pass to choose orientation and overlap, then run expensive matching on aligned crops.

Check

  • Proposed verification: Check rotation correctness, overlap recall and final geometric registration.

Limits

  • Crop redetection creates multi-view inconsistency that needs separate handling.

Find source links and value grades in the lesson atlas

Delay approximate bundle adjustment and alternate track refinement

Conditions

  • large SfM scenes need speed without unstable initialization

Action

  • Proposed adaptation of source observation: Use accurate early geometry solving, then benchmark approximate parallel BA and iterative feature/geometry refinement.

Check

  • Proposed verification: Compare registered cameras, reprojection errors, pose metric and runtime across iterations.

Limits

  • The initialization rationale is author intuition; approximate solvers can converge differently.

Find source links and value grades in the lesson atlas