supervised contrastive learning class-level positive pairs

# Supervised Contrastive Learning: Class-Level Positive Pairs

## Introduction & Motivation

Supervised contrastive learning extends contrastive methods to labeled data. Positive pairs: same class (multiple samples). Learns discriminative representations; outperforms standard cross-entropy baseline. Stronger inductive bias; class separability explicit in loss. Applications: classification, metric learning, open-set recognition.

Motivation: Leverage labels to define positive pairs explicitly. Cross-entropy only constrains decision boundary; contrastive enforces inter-class separation throughout embedding space.

Applications: Metric learning, image retrieval, face recognition, open-set classification.

---

## Core Concepts & Theory

### Positive Set

All samples of same class treated as positives; enable large positive sets. Batch must have multiple samples per class for effectiveness.

### Inter-Class Separation

Negative pairs span different classes; distance in embedding space reflects class distance.

### Temperature & Concentration

Lower temperature concentrates mass on hard negatives; affects convergence.

---

## Mathematical Formulation

Supervised contrastive loss:
$$\ell_i = -\frac{1}{|P(i)|} \sum_{p \in P(i)} \log \frac{\exp( ext{sim}(z_i, z_p) / au)}{\sum_{a=1}^{N} \exp( ext{sim}(z_i, z_a) / au)}$$

Relation to cross-entropy:
Supervised contrastive generalizes cross-entropy; at τ→0, becomes nearest-class-mean classifier.

---

## Advanced Theory & Extensions

### Hard Negative Mining

Focus on hard negatives (closest different-class); online hard negative queue.

### Multi-Prototype

Class represented by multiple prototypes; handles class imbalance.

### Focal Contrastive

Weight negatives by margin; hard negatives weighted higher.

---

## Computational Considerations

Batch composition: Require multiple samples per class; affects batch construction.

Positive set size: Larger → more stable, but requires batch strategy.

Computational cost: O(batch_size × |P(i)|) per sample.

---

## Practical Implementation Strategies

### Batch Construction

Ensure multiple samples per class (e.g., 4 samples × 8 classes = batch 32).

### Class-Aware Sampling

Sample classes uniformly; within-class random sampling.

### Mixup with Contrastive

Combine mixup augmentation with supervised contrastive.

---

## Benchmark Datasets & Evaluation

CIFAR-10/100: Standard classification; supervised contrastive +2-3% vs cross-entropy.

ImageNet: Large-scale; supervised contrastive strong baseline.

Metrics: Top-1/5 accuracy, embedding quality (clustering, purity).

---

## Key Challenges & Limitations

### Batch Dependency

Performance sensitive to batch composition; require class balance.

### Scalability

Large number of classes challenges; positives may be sparse.

### Hyperparameter Interaction

Temperature, learning rate, batch size tightly coupled.

---

## Hyperparameter Tuning

Temperature τ: 0.05-0.5; class-specific tuning possible.

Positive weight: 1.0 (equal to negatives); alternative: weight by |P(i)|.

Learning rate: 0.5-2.0 (higher than cross-entropy).

---

## Real-World Applications & Case Studies

CIFAR Classification: Supervised contrastive achieves 96.1% on CIFAR-10 (vs. 95.5% cross-entropy).

Face Recognition: Inter-person separation via contrastive loss.

Medical Diagnosis: Disease class separation in embedding space.

---

## Integration with Other Methods

Supervised contrastive + Self-supervised → pre-train self-supervised, fine-tune supervised contrastive.

Supervised contrastive + Metric learning → siamese networks with contrastive loss.

---

## Summary & Key Takeaways

Supervised contrastive learning leverages class labels to define positive pairs, achieving superior generalization via explicit inter-class separation in embedding space.

Principles:
1. Positive pairs: same-class samples; multiple per batch.
2. NT-Xent loss adapted for class-level positives.
3. Batch construction critical; require class-balanced sampling.
4. Lower temperature encourages hard negative mining.
5. Strong transfer learning baseline for downstream tasks.

---

---

## Appendix: Practical Labs

### Lab 1: Class-Stratified Batch

import torch
import numpy as np

def stratified_batch_sampler(labels, batch_size=32, samples_per_class=4):
 """Sample batch with equal samples per class"""
 unique_classes = torch.unique(labels)
 n_classes = len(unique_classes)
 
 if batch_size != n_classes * samples_per_class:
 raise ValueError(f"Batch size {batch_size} != n_classes {n_classes} * samples_per_class {samples_per_class}")
 
 batch_indices = []
 for c in unique_classes:
 class_indices = torch.where(labels == c)[0]
 sampled = np.random.choice(class_indices.cpu().numpy(), size=samples_per_class, replace=False)
 batch_indices.extend(sampled)
 
 return torch.tensor(batch_indices)

# Test
labels = torch.tensor([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3])
batch_idx = stratified_batch_sampler(labels, batch_size=12, samples_per_class=3)

print(f"Batch indices: {batch_idx}")
assert len(batch_idx) == 12, "Should have batch_size samples"
print("✓ Stratified batch sampler working")

if __name__ == "__main__":
 print("Lab 1: Stratified Batch - PASSED")

### Lab 2: Positive Set Construction

import torch
import numpy as np

def construct_positive_set(labels, exclude_idx):
 """Construct positive set for sample at exclude_idx"""
 target_label = labels[exclude_idx]
 positive_indices = torch.where(labels == target_label)[0]
 
 # Remove self
 positive_indices = positive_indices[positive_indices != exclude_idx]
 
 return positive_indices

# Test
labels = torch.tensor([0, 0, 1, 1, 2, 2, 0])
pos_set = construct_positive_set(labels, exclude_idx=0)

print(f"Positive set for index 0 (label {labels[0]}): {pos_set}")
assert len(pos_set) >= 1, "Should have at least one positive"
assert labels[pos_set[0]] == labels[0], "All positives should match class"
print("✓ Positive set construction working")

if __name__ == "__main__":
 print("Lab 2: Positive Set - PASSED")

### Lab 3: Supervised Contrastive Loss

import torch
import torch.nn.functional as F
import numpy as np

def supervised_contrastive_loss(z, labels, tau=0.1):
 """Supervised contrastive loss with class-level positives"""
 batch_size = z.size(0)
 z_norm = F.normalize(z, dim=1)
 sim = torch.mm(z_norm, z_norm.T) / tau
 
 # Create positive mask (same class, excluding self)
 mask = (labels.unsqueeze(1) == labels.unsqueeze(0)).float()
 mask = mask - torch.eye(batch_size)
 
 # Numerator: positives
 exp_sim = torch.exp(sim)
 pos_sum = (mask * exp_sim).sum(dim=1, keepdim=True)
 
 # Denominator: all except self
 total_sum = exp_sim.sum(dim=1, keepdim=True) - torch.diag_embed(torch.diag(exp_sim))
 
 loss = -torch.log(pos_sum / total_sum + 1e-8)
 
 # Average over samples with positives
 valid = (mask.sum(dim=1) > 0)
 return loss[valid].mean() if valid.sum() > 0 else loss.mean()

# Test
z = torch.randn(16, 128)
labels = torch.tensor([0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 0, 1, 2, 3])

loss = supervised_contrastive_loss(z, labels, tau=0.1)

print(f"Supervised contrastive loss: {loss:.4f}")
assert 0 < loss < 10, "Loss should be in reasonable range"
assert np.isfinite(loss.item()), "Loss should be finite"
print("✓ Supervised contrastive loss working")

if __name__ == "__main__":
 print("Lab 3: Supervised Contrastive - PASSED")

### Lab 4: Class Separation Metric

import torch
import torch.nn.functional as F
import numpy as np

def compute_class_separation(z, labels):
 """Measure inter-class vs intra-class distance"""
 unique_labels = torch.unique(labels)
 z_norm = F.normalize(z, dim=1)
 
 intra_distances = []
 inter_distances = []
 
 for c1 in unique_labels:
 mask_c1 = labels == c1
 z_c1 = z_norm[mask_c1]
 
 # Intra-class: pairwise within class
 if len(z_c1) > 1:
 pairwise = torch.mm(z_c1, z_c1.T)
 intra_distances.append((1 - pairwise[~torch.eye(len(z_c1), dtype=torch.bool)]).mean().item())
 
 # Inter-class: distance to other classes
 for c2 in unique_labels:
 if c1 < c2:
 z_c2 = z_norm[labels == c2]
 pairwise = torch.mm(z_c1, z_c2.T)
 inter_distances.append((1 - pairwise).mean().item())
 
 intra_mean = np.mean(intra_distances) if intra_distances else 0
 inter_mean = np.mean(inter_distances) if inter_distances else 1
 
 return intra_mean, inter_mean

# Test
z = torch.randn(20, 64)
labels = torch.tensor([0]*5 + [1]*5 + [2]*5 + [3]*5)

intra, inter = compute_class_separation(z, labels)

print(f"Intra-class distance: {intra:.4f}, Inter-class: {inter:.4f}")
assert 0 <= intra <= 2, "Intra-class distance should be valid"
assert 0 <= inter <= 2, "Inter-class distance should be valid"
assert inter > intra, "Inter-class should be larger than intra-class"
print("✓ Class separation metric working")

if __name__ == "__main__":
 print("Lab 4: Class Separation - PASSED")

Go deeper with CFSGPT

Get AI-powered deep-dives, save terms, and run advanced simulations — free account.

Create Free Account