HardLoss Functions

Implement InfoNCE Loss

Loss Functions

Hard

Problem

Compute one-directional InfoNCE loss for two aligned embedding batches. Build the similarity logits:

S = \frac{Z_1Z_2^{\mathsf T}}{\tau}

Treat entries S_{ii} as positive pairs and every entry in row i as a candidate:

L = -\frac{1}{N}\sum_{i=1}^{N}\log\left(\frac{e^{S_{ii}}}{\sum_{j=1}^{N}e^{S_{ij}}}\right)

Here, N is batch size, \tau is temperature, and row i of Z_1 is paired with row i of Z_2. Subtract each row maximum before exponentiation and return the mean loss as a Python float.

Theory

InfoNCE (Information Noise-Contrastive Estimation) is the loss function behind many modern self-supervised learning methods like SimCLR, MoCo, and CLIP. It learns representations by contrasting positive pairs against negative pairs.

The setup:

The goal: make the anchor close to its positive and far from all negatives.


The InfoNCE Formula

For an anchor with embedding z, positive with embedding z^+, and N negatives with embeddings z^-_1, ..., z^-_N:

L = -\log \frac{\exp(\text{sim}(z, z^+) / \tau)}{\exp(\text{sim}(z, z^+) / \tau) + \sum_{i=1}^{N} \exp(\text{sim}(z, z^-_i) / \tau)}

Where:


Breaking Down the Formula

The structure is similar to softmax cross-entropy:

Numerator: similarity between anchor and positive, exponentiated Denominator: sum of similarities between anchor and ALL samples (positive + negatives), exponentiated

This can be rewritten as:

L = -\text{sim}(z, z^+)/\tau + \log\left(\sum_{k} \exp(\text{sim}(z, z_k)/\tau)\right)

Where k ranges over positive and all negatives.

The loss is minimized when:


The Temperature Parameter

The temperature \tau controls the "hardness" of the contrastive task:

Low temperature (e.g., 0.07):

High temperature (e.g., 1.0):

Common values: 0.07 to 0.5

The optimal temperature depends on:


Why It Works: Information Theory Perspective

InfoNCE has a deep connection to mutual information. The loss is a lower bound on:

I(X; Y) \geq \log(N) - L_{\text{InfoNCE}}

Where:

Minimizing InfoNCE maximizes a lower bound on mutual information. This means the learned representations capture information shared between different views of the same data.


Numerical Example

Consider an anchor with 1 positive and 3 negatives:

Similarities (before temperature scaling):

With temperature = 0.5:

Scaled similarities: 1.8, 0.6, 0.2, -0.4

Exponentials: exp(1.8) = 6.05, exp(0.6) = 1.82, exp(0.2) = 1.22, exp(-0.4) = 0.67

Sum of denominator: 6.05 + 1.82 + 1.22 + 0.67 = 9.76

Loss = -log(6.05 / 9.76) = -log(0.62) = 0.48

If the positive were more similar (say 0.99 instead of 0.9), the loss would be lower.


The Importance of Negatives

InfoNCE requires many negatives to work well:

Few negatives (e.g., 10):

Many negatives (e.g., 65,536 in MoCo):

The batch size dilemma:


Symmetric InfoNCE

In many implementations (like SimCLR), the loss is computed symmetrically:

For a pair (i, j) of augmented views:

This ensures both views learn equally good representations.


The Gradient

The gradient with respect to the anchor embedding z:

\frac{\partial L}{\partial z} = -\frac{1}{\tau}\left(z^+ - \sum_k p_k \cdot z_k\right)

Where p_k is the softmax probability for sample k.

Interpretation:


InfoNCE vs. Triplet Loss

Triplet loss:

InfoNCE:

InfoNCE generally outperforms triplet loss when you have access to many negatives.


Common Implementations

SimCLR:

MoCo:

CLIP:


Where InfoNCE Is Used

Examples

Example 1

Input
Z1 = [[1, 0], [0, 1]], Z2 = [[1, 0], [0, 1]], temperature = 0.1
Output
0.000045
Explanation
Each diagonal similarity is much larger than the competing similarity in its row.

Example 2

Input
Z1 = [[1, 0], [0, 1]], Z2 = [[0, 1], [1, 0]], temperature = 0.1
Output
10.000045

Example 3

Input
Z1 = [[1, 0], [0, 1]], Z2 = [[1, 0], [0, 1]], temperature = 1.0
Output
0.313262

Hints

  1. Use Z1 @ Z2.T / temperature to build the logits.
  2. Subtract np.max(logits, axis=1, keepdims=True) before exponentiation.
  3. Use np.diag(shifted) for the positive-pair logits.

Requirements

Constraints

Starter Code

import numpy as np

def info_nce_loss(Z1: list, Z2: list, temperature: float = 0.1) -> float:
    """
    Returns the loss as a float.
    """
    # Write code here
    pass

Test Cases

CaseMatches
Identity embeddingspublic
Misaligned pairspublic
High temperaturepublic