Özer Cem KELAHMETKELAHMET
Tools · Self-supervised methods

MoCo

MoCo explained: how momentum contrast uses a slowly updated encoder and a queue of negatives for self-supervised learning, with code and tabular links.

Overview

MoCo, short for momentum contrast, is a self-supervised method that learns visual features from unlabeled images. It treats learning as a dictionary look-up problem. A query view of an image must find its matching key view in a large dictionary of keys that also holds many wrong answers. The dictionary is a queue of recent encoded samples, and the keys are produced by a second encoder that is updated slowly, as a moving average of the main one.

The two design choices, the queue and the momentum encoder, let MoCo use a very large and consistent set of negative examples without needing a huge batch. That made strong contrastive learning possible on modest hardware. This article explains the idea in plain terms, walks through the update rule with numbers, compares MoCo with SimCLR, BYOL and DINO, and shows how the same momentum-and-queue pattern helps models that learn from raw tables.

What it is

MoCo is a framework for contrastive learning, which in turn is a branch of self-supervised learning. The training signal is made from the data itself: two augmented views of the same image should have similar features, and views of different images should not.

The core idea: a dictionary

Imagine a library with a card catalog. A query comes in. Your job is to find the one card that matches. The card catalog holds many cards, and only one is right. The better your catalog, the better you learn to tell matches from non-matches.

In MoCo:

  • The query is the feature of one view, made by the query encoder.
  • The keys are features of other views, made by the key encoder.
  • The positive key is the other view of the same image.
  • All other keys in the dictionary are negatives.
  • The loss asks the query to pick the positive key from the crowd.

The two ingredients

Ingredient What it is Why it helps
Queue A first-in, first-out list of recent key vectors Gives thousands of negatives, decoupled from batch size
Momentum encoder A key encoder updated as a slow average of the query encoder Keeps the keys in the queue consistent with each other

Where it came from

Before MoCo, contrastive methods faced a trade-off about their dictionaries. One option was to compute negatives from the current batch with an end-to-end encoder, as SimCLR later did. This keeps keys fresh but ties the number of negatives to batch size, which is limited by memory. Another option was a memory bank that stored one stored vector per training image. This gives many negatives, but the stored vectors are old, produced by earlier versions of the encoder, so they become stale and inconsistent.

The MoCo paper, "Momentum Contrast for Unsupervised Visual Representation Learning," appeared in 2019 and was presented in 2020. Its contribution was to decouple dictionary size from batch size and to keep it consistent. A queue lets you hold a large dictionary. A momentum encoder makes the queue's contents change smoothly, so keys encoded many steps apart still agree with each other.

Several follow-ups shaped the family:

  • MoCo v2 (2020) borrowed ideas from SimCLR: an MLP projection head and stronger augmentation. It showed that these ideas were compatible with the queue and improved results without large batches.
  • MoCo v3 (2021) adapted the method for vision transformers. It used in-batch negatives, a predictor like the one in BYOL, and fixes to make transformer training stable.

The overall lesson was that the contrastive recipe has several interchangeable parts, and the queue plus momentum encoder is a good way to get many negatives cheaply.

How it works

Think of a school photo project. Each day you take a new class photo and pin it to the back of a long corkboard, removing the oldest. To keep photos comparable, the same camera settings are used every day, and the settings are changed only a tiny bit each week. Now you can compare any new photo to the whole board and trust that the differences are real, not caused by camera changes.

Step 1: Two views

An image is augmented twice. One view goes to the query encoder, and the other goes to the key encoder. Typical augmentations are random crop and resize, color jitter, gray conversion, blur and flip.

Step 2: Encode query and key

  • The query encoder f_q maps the first view to a query vector q.
  • The key encoder f_k maps the second view to a key vector k+.
  • Both vectors are normalized to unit length.

Step 3: Compare with the dictionary

The dictionary holds K stored keys from earlier batches. The query is compared with the positive key and all queued keys. The scores are cosine similarities divided by a temperature.

Step 4: Compute the InfoNCE loss

text
loss = -log( exp(q . k+ / T) /
             ( exp(q . k+ / T) + sum over queue keys k- of exp(q . k- / T) ) )

This is a classification loss with K + 1 classes where the correct class is the positive key. Only the query encoder gets gradients from this loss.

Step 5: Update the query encoder

Back-propagation updates the query encoder f_q. The key encoder does not get gradients. This is important because the queue holds many keys, and back-propagating through all of them would be expensive and would also not make sense, since they came from earlier steps.

Step 6: Momentum update of the key encoder

The key encoder's parameters are updated as an exponential moving average of the query encoder's parameters:

text
theta_k  <-  m * theta_k  +  (1 - m) * theta_q

Here m is a momentum coefficient very close to 1, such as 0.999. With such a value, the key encoder changes very slowly. Keys produced at different times are therefore close to one another in how they were computed.

Step 7: Update the queue

The new batch of keys is added to the end of the queue. The oldest batch is removed. The dictionary is always the most recent K keys.

Step 8: Avoid a cheating shortcut

In a naive setup, the positive pair is computed inside the same batch, and batch normalization statistics are shared across both views. The network can then leak information through those shared statistics and find a trivial solution. MoCo avoids this with shuffling batch normalization. On multiple devices, the keys are shuffled across devices before encoding so that the query and its positive key are normalized with statistics from different subsets. This stops the leak.

Why momentum matters

Suppose the key encoder were simply a copy of the query encoder, updated at every step. The keys in the queue would then come from many different versions of the encoder, and the model would struggle to compare them. If the key encoder were frozen, it would not improve. A slow moving average strikes the balance: it improves, but slowly enough that keys remain comparable.

Everyday analogies

  • The queue is a conveyor belt that always carries the most recent items for comparison.
  • The momentum encoder is a judge who changes their standards only a little each day, so earlier rulings still make sense.
  • Shuffling batch norm is like making sure the two people comparing notes did not see the same private answer sheet.
  • Negatives are a lineup of look-alikes you must tell apart.

MoCo versions at a glance

Version Main change Why it mattered
MoCo v1 Queue plus momentum encoder Large, consistent dictionary without large batches
MoCo v2 MLP head and stronger augmentation Matched or beat large-batch methods on modest hardware
MoCo v3 Built for vision transformers, in-batch negatives Stable self-supervised training for transformers

Why a consistent dictionary matters

Contrastive learning compares a query with many keys. If keys come from very different encoders, a high or low score may reflect the encoder gap, not the image. The model then learns to exploit noise. A consistent dictionary means that score differences mostly reflect real differences in the images.

There are three ways to build a dictionary, and each has a weak point:

  • End-to-end in-batch: consistent, but small and memory heavy.
  • Memory bank: large, but stale and inconsistent.
  • Queue with momentum encoder: large and mostly consistent, at the price of a second network.

A mental model of the training loop

  • Pull a batch of images.
  • Make two views of each.
  • Encode the first view with the fast encoder and the second with the slow encoder.
  • Score the query against its key and the whole queue.
  • Update the fast encoder from the loss.
  • Nudge the slow encoder toward the fast one.
  • Push the new keys into the queue and drop the oldest.
  • Repeat for many epochs.

Key concepts and terms

Term Plain meaning
Query The feature of one view, which looks for its match
Key The feature of a view stored in the dictionary
Positive key The key from the same image as the query
Negative keys Keys from other images
Dictionary The set of keys available for comparison
Queue A first-in, first-out buffer holding the dictionary
Momentum encoder A key encoder that follows the query encoder via a moving average
Momentum coefficient The weight on the old key parameters, close to one
InfoNCE A contrastive loss that treats matching as classification
Temperature A scale that sharpens or softens similarity scores
Shuffling BN A trick that prevents information leaks through batch statistics
Consistency Keys in the dictionary being produced by similar encoders
Projection head A small MLP on top of the encoder for the loss
Linear evaluation Training a linear classifier on frozen features to test them
Stop-gradient Blocking gradient flow into a branch

A worked example

A numeric walk-through of the momentum update

Suppose one weight in the query encoder is 1.00 and the same weight in the key encoder is 0.90. With m = 0.99:

  • New key weight = 0.99 * 0.90 + 0.01 * 1.00 = 0.891 + 0.010 = 0.901

The key weight moved from 0.900 to 0.901. It barely changes. Now suppose the query encoder weight moves to 1.20 after a few steps and stays there. The key weight chases it slowly:

Step Query weight Key weight
0 1.20 0.900
1 1.20 0.903
10 1.20 about 0.929
100 1.20 about 1.10
300 1.20 about 1.19

After about 100 steps the key has covered roughly 63 percent of the gap, since the remaining gap shrinks by a factor of 0.99 each step. This smooth drift is what keeps queued keys comparable.

A numeric walk-through of the loss

Take a queue of 3 negatives and 1 positive, with temperature T = 0.2. Suppose the cosine similarities of the query are:

Key Similarity Scaled (divide by T)
Positive 0.70 3.5
Negative 1 0.20 1.0
Negative 2 0.10 0.5
Negative 3 0.30 1.5
  • exp(3.5) is about 33.1
  • exp(1.0) is about 2.72
  • exp(0.5) is about 1.65
  • exp(1.5) is about 4.48
  • Sum is about 41.95
  • Probability for the positive is about 0.789
  • Loss is about 0.237

If the model were confused and gave the positive a similarity of 0.30, the loss would rise. Learning pushes this number down.

Code sketch

python
import copy
import torch
import torch.nn as nn
import torch.nn.functional as F

class MoCo(nn.Module):
    def __init__(self, encoder, dim=128, queue_size=4096, m=0.999, T=0.2):
        super().__init__()
        self.m, self.T = m, T
        self.enc_q = encoder
        self.enc_k = copy.deepcopy(encoder)
        for p in self.enc_k.parameters():
            p.requires_grad = False           # key encoder gets no gradients
        self.register_buffer("queue", F.normalize(torch.randn(queue_size, dim), dim=1))
        self.register_buffer("ptr", torch.zeros(1, dtype=torch.long))

    @torch.no_grad()
    def _momentum_update(self):
        for pq, pk in zip(self.enc_q.parameters(), self.enc_k.parameters()):
            pk.data = self.m * pk.data + (1 - self.m) * pq.data

    @torch.no_grad()
    def _enqueue(self, keys):
        n = keys.size(0)
        i = int(self.ptr)
        self.queue[i:i + n] = keys           # assumes queue_size % n == 0
        self.ptr[0] = (i + n) % self.queue.size(0)

    def forward(self, view_q, view_k):
        q = F.normalize(self.enc_q(view_q), dim=1)
        with torch.no_grad():
            self._momentum_update()
            k = F.normalize(self.enc_k(view_k), dim=1)
        l_pos = (q * k).sum(dim=1, keepdim=True)         # [N, 1]
        l_neg = q @ self.queue.t()                        # [N, K]
        logits = torch.cat([l_pos, l_neg], dim=1) / self.T
        labels = torch.zeros(q.size(0), dtype=torch.long, device=q.device)
        loss = F.cross_entropy(logits, labels)           # positive is index 0
        self._enqueue(k)
        return loss

This outline omits shuffling batch normalization and multi-device handling, which are needed in a faithful reproduction.

What to look at while it trains

  • The loss should fall, then flatten.
  • A linear probe on frozen encoder features should improve over time.
  • The similarity of positives should rise and the similarity of negatives should stay lower.
  • If all outputs collapse to one point, the loss would look odd and the probe would be poor.

A tabular analog in code

This sketch shows how to apply the same loop to table rows. The row encoder here is a stand-in for any network that maps a numeric row vector to an embedding.

python
import torch
import torch.nn as nn

def mask_cells(x, rate=0.25):
    """Zero out a random subset of cells to mimic missing values.
    x: [N, D] tensor with numeric columns already scaled."""
    keep = (torch.rand_like(x) > rate).float()
    return x * keep

row_encoder = nn.Sequential(nn.Linear(32, 128), nn.ReLU(), nn.Linear(128, 64))
moco_rows = MoCo(row_encoder, dim=64, queue_size=8192, m=0.99, T=0.2)
opt = torch.optim.AdamW(moco_rows.enc_q.parameters(), lr=1e-3)

for batch in loader:                      # batch: [N, 32] scaled numeric rows
    loss = moco_rows(mask_cells(batch), mask_cells(batch))
    opt.zero_grad()
    loss.backward()
    opt.step()

Categorical columns would first be embedded or one-hot encoded. Each training step uses two independent masks of the same rows, so the positive pair is a row seen through two different sets of gaps.

Strengths and limitations

Strengths

  • Large dictionary of negatives without a large batch.
  • Consistent keys thanks to the slow momentum encoder.
  • Runs on a modest number of GPUs compared with large-batch approaches.
  • Strong transfer to detection and segmentation in the original reports.
  • Flexible: the encoder can be a convolutional network or a vision transformer.
  • Works well with the same augmentations and heads as SimCLR.

Limitations

  • More moving parts than a plain in-batch method: a second encoder, a queue and shuffling.
  • Momentum and queue size are extra hyperparameters.
  • Stale or too large a queue can hurt, because very old keys come from an older state of the model.
  • False negatives remain: other images of the same class are treated as wrong answers.
  • Sensitive to augmentation choices.
  • Transformer versions need extra care for stable training.

How it compares with alternatives

Method Negatives Second network Where negatives come from Notes
MoCo Yes Momentum encoder Queue of recent keys Decouples dictionary size from batch size
SimCLR Yes No Current batch Simple, needs large batches
BYOL No Target network (momentum) None Predictor and stop-gradient avoid collapse
DINO No Teacher (momentum) None Self-distillation with centering and sharpening
MAE No No None Reconstruct masked parts
Memory bank methods Yes No Stored per-sample vectors Stale vectors
Supervised pre-training No No None Needs labels

Notice that the momentum encoder idea reappears in BYOL and DINO. MoCo helped popularize it. In those later methods the slow-moving network serves as a target or teacher, not as a source of keys for a dictionary.

Role in tabular foundation models

Tabular foundation models learn from raw tables. Each row is an example, columns can be numbers, categories, dates or text, cells may be missing, and headers may be absent or uninformative. See /model/research for the wider picture. MoCo was made for images, but its two building blocks are useful for tables.

Queue of negatives for row embeddings

A contrastive tabular model builds two corrupted views of a row, for example by masking some cells or swapping in values from the same column of other rows. It must then tell the matching view apart from views of other rows. Tables can have millions of rows, and a large batch of wide rows may not fit in memory. A MoCo-style queue holds many encoded rows cheaply. This makes it practical to pre-train a row encoder on a big table with modest hardware.

Momentum encoder for stable targets in continual learning

Operational tables grow and drift. A momentum encoder acts like a slow memory. When a model is updated on new rows, the slowly moving copy changes less, so earlier row embeddings stay comparable with new ones. This is a useful pattern for continual learning, where a model must adopt new data without forgetting the old, and where stored embeddings should stay valid. The same idea is used with an exponential moving average of weights in many training setups.

Mixed types and missing values

The augmentation step is where tabular work differs most. Masking a cell is a natural way to simulate a missing value. Replacing a cell with a value from the same column keeps the type valid. A model trained to match masked views of a row learns to be robust to gaps, which links to masked cell modeling and imputation networks.

Tables without headers

Column names are often missing or cryptic. A row encoder trained contrastively learns from value patterns and co-occurrence, not from header text. That helps with sector or semantic classification of tables and columns, where the question is "what kind of data is this?" and the names cannot be trusted.

Anomaly detection and retrieval

The queue is also a ready-made nearest-neighbor index of recent rows. A new row whose embedding has low similarity to every entry can be flagged for review, which connects to anomaly detection. Rows with high similarity to a query row support retrieval, deduplication and clustering.

Relation to in-context learning

Some tabular models use in-context learning: they read labeled rows as a prompt and predict labels for new rows in a single forward pass, as in TabPFN. A contrastively trained row encoder can supply embeddings as the prompt rows, which may help when tables are wide or noisy. This pairing is a design idea, not a claim about any specific model.

Honest caveats

Contrastive pre-training for tables is still a research area. Gains over strong tree-based baselines are not guaranteed and depend on the corruption design. Pick evaluation sets carefully, using the guidance in /model/benchmarks, and see /model/architecture for model design context.

Practical guidance and best practices

Queue size

  • Start with a queue of a few thousand to tens of thousands of keys.
  • Bigger is better up to a point, then returns diminish.
  • Make the queue size a multiple of the batch size so that the pointer wraps cleanly.

Momentum coefficient

  • Use a value near 0.99 to 0.999.
  • A smaller value makes the key encoder track faster but less consistently.
  • With a short training run, a slightly smaller value can be better, since the model has less time to move.

Temperature

  • Values around 0.07 to 0.2 are common.
  • Tune together with the learning rate.

Augmentations

  • Use the strong combination of crop, color jitter, gray and blur for images.
  • For tables, tune the corruption rate and verify rows remain plausible.

Projection head

  • Use an MLP head, as in MoCo v2.
  • Evaluate on the encoder output, not the head output.

Batch normalization

  • Use shuffling BN or a synchronized variant across devices.
  • If you use a single device, think about how to avoid a leak, for example by using group or layer normalization.

Evaluation

  • Use a linear probe as the main yardstick.
  • Add a few-label fine-tuning test.
  • Report spread over several seeds.

Operations

  • Save the key encoder and the query encoder, and also the queue if you plan to resume.
  • Log similarity statistics for positives and negatives.
  • Keep a record of augmentations and seeds so runs can be repeated.

Debugging guide

Symptom Likely cause What to try
Loss drops suddenly to near zero Batch norm leak Use shuffling BN or another normalization
Features collapse Learning rate too high, bad temperature Lower the rate, retune the temperature
Probe stalls early Momentum too high for the schedule Lower the momentum coefficient
Probe fluctuates Queue too small or too stale Increase queue size or lower the momentum
Memory problems Large encoder and large batch Reduce batch, rely on the queue for negatives
Poor results after resume Queue was reset Restore the queue or warm it up for a few steps

Questions to ask before you adopt it

  • Do I need many negatives but cannot afford a very large batch?
  • Can I handle a second network and a queue in my training code?
  • Do I have clean augmentations that keep the meaning of an example?
  • Would a negative-free method be simpler for my data?
  • How will I know the features are useful for my real task?

Choosing MoCo or another method

  • Choose MoCo when memory limits batch size but you still want many negatives.
  • Choose SimCLR when you have the hardware and want the simplest code.
  • Choose BYOL when picking negatives is awkward, for example when many items share a class.
  • Choose DINO when you train a vision transformer and want strong label-free features.
  • Choose a reconstruction method such as MAE when you want features that keep fine detail.
  • Whatever you pick, compare on the same linear probe and the same data.

Reporting and reproducibility

  • Record the queue size, momentum, temperature and augmentation list.
  • Record the random seeds and the number of devices.
  • Share the exact evaluation recipe, including how features were extracted.
  • Keep a short log of failed runs, since they teach as much as the successes.

Common mistakes

  • Letting gradients flow into the key encoder.
  • Setting the momentum too low, which makes the queue inconsistent.
  • Setting it too high with a short schedule, so the key encoder barely learns.
  • Forgetting to normalize query and key vectors.
  • Skipping shuffling BN and then seeing a suspiciously fast loss drop with poor features.
  • Using a queue whose size is not a multiple of the batch size, which breaks the pointer logic.
  • Filling the queue with stale keys after resuming from a checkpoint.
  • Using duplicate images, which create false negatives.
  • Reading the projection head output as the feature.
  • Judging success by the loss alone.

Frequently asked questions

What does MoCo stand for?

MoCo stands for momentum contrast. The name refers to the momentum-updated key encoder and the contrastive loss used to learn from pairs of views. It was introduced as a way to build large, consistent dictionaries for unsupervised visual representation learning.

Why does MoCo use a queue?

The queue stores recent key vectors, which serve as negatives. It lets the number of negatives be much larger than the batch size, because keys from earlier batches remain available. The oldest keys are removed as new ones are added, so the dictionary stays fresh.

What is the momentum encoder?

It is a second copy of the encoder whose weights are an exponential moving average of the main encoder's weights. Because it changes slowly, keys it produced over many recent steps remain similar in how they were computed, which makes comparisons against the queue meaningful.

How is MoCo different from SimCLR?

SimCLR takes its negatives from the current batch and therefore needs very large batches. MoCo keeps negatives in a queue and uses a momentum encoder, so it can work with smaller batches. Later MoCo versions borrowed the projection head and stronger augmentations from SimCLR.

Does MoCo need negative examples?

Yes. MoCo is a contrastive method and learns by telling the matching key apart from the negatives. If you want to avoid negatives, look at BYOL or DINO, which learn from a slowly updated target or teacher without them.

What is shuffling batch normalization?

It is a trick that prevents the model from cheating through batch statistics. If a query and its positive key were normalized with the same batch statistics, the network could leak information between them. Shuffling keys across devices makes their statistics come from different subsets.

Can MoCo be used with vision transformers?

Yes. A later version adapted the approach for vision transformers, with changes to keep training stable. The core idea of a momentum encoder is reused, and in-batch negatives can replace the queue.

Is MoCo useful for tabular data?

Its ideas are. A queue of encoded rows gives many negatives cheaply, and a momentum encoder gives stable targets across updates. With corrupted rows as views, the same loss can train a row encoder that handles mixed types and missing cells.

How large should the queue be?

There is no single answer. Many setups use a few thousand to tens of thousands of keys. A larger queue gives more negatives but holds older keys. Tune it together with the momentum coefficient and check results with a linear probe.

What can go wrong during MoCo training?

Common issues are information leaks through batch normalization, an inconsistent queue from a low momentum value, collapse, and false negatives from duplicates. Watching the linear probe score and the similarity of positives and negatives helps spot these early.

Glossary

  • Batch normalization: a layer that normalizes activations using batch statistics.
  • Contrastive loss: a loss that pulls positives together and pushes negatives apart.
  • Dictionary: the set of keys used for comparison.
  • Exponential moving average: a running average that weights recent values more.
  • InfoNCE: a loss that frames matching as classification among many candidates.
  • Key: an encoded sample stored for comparison.
  • Key encoder: the network that produces keys.
  • Momentum coefficient: the weight kept on the old parameters in the average.
  • Negative: a sample that should not match the query.
  • Positive: the sample that should match the query.
  • Query: the encoded sample that looks for its match.
  • Queue: a first-in, first-out buffer of keys.
  • Stale key: a stored key made by an older version of the encoder.
  • Stop-gradient: preventing gradient flow into part of a model.
  • Temperature: a scale applied to similarities before the softmax.

Summary

MoCo reframes contrastive learning as a dictionary look-up. A query encoder is trained by gradient descent. A key encoder follows it as a slow moving average. A queue of recent keys supplies a large and consistent set of negatives that does not depend on batch size. Shuffling batch normalization closes a subtle leak. Together these choices made strong self-supervised pre-training possible without huge batches.

For tabular work, the queue and the momentum encoder transfer well. Corrupted rows play the role of views. The queue makes row-level contrast affordable. The momentum encoder keeps embeddings stable as new data arrives. These help with imputation, anomaly detection, clustering and semantic classification of tables. Compare with SimCLR, BYOL and DINO, and read /model/research, /model/architecture and /model/benchmarks for how such models are built and tested.

It belongs to Self-supervised methods in my stack, under AI and machine learning. Others in the same group:

Type to search.
    ↑↓ navigate↵ open