On this page
As in Contrastive Learning, training pulls the embeddings of two random edits of a photo, its views, together, but here with no negatives (other images pushed away). Every training step, one network learns while its target, the other view’s embedding it must match, comes from a momentum encoder (a copy whose weights follow a moving average of the network’s) or from the same network with no gradient flowing through it (a stop-gradient). Each method adds one part that stops every image from getting the same output.
If the only signal is make the two views match, one vector for every image matches every pair and says nothing; this is collapse. Negatives forbid it; without them, nothing in the loss does.
In a subtler dimensional collapse, the embeddings spread along only a few directions of the space and stay flat along the rest; Failure Modes and Checks plots it.
BYOL (Bootstrap Your Own Latent, Grill et al. 2020; latent means the embedding space) trains one network to predict a momentum encoder’s embedding of the other view, with no negatives, and still does not collapse. Its trained online network turns view 1 into a prediction p1 through an encoder, a projector (a small head, as in SimCLR) and a predictor (a second small head that guesses the target). The target network, an encoder and projector with no predictor, turns view 2 into the target z2. It gets no gradients and is an exponential moving average (EMA) of the online weights.
The target follows MoCo’s momentum rule. Each step it keeps a share m of its old weights, and m rises from 0.996 to 1.
The snippet computes eq line 2 for both view orders on toy vectors; cosine_similarity scales both to unit length itself.
import torch
import torch.nn.functional as F
def byol_loss(p, z):
cos = F.cosine_similarity(p, z, dim=-1)
return (2 - 2 * cos).mean() # batch mean
B, D = 8, 256
p1, p2 = torch.randn(B, D), torch.randn(B, D) # online predictions
z1, z2 = torch.randn(B, D), torch.randn(B, D) # targets
L = byol_loss(p1, z2) + byol_loss(p2, z1)
With no negatives to collect, BYOL needs no big batch.
Removing any one of three parts collapses BYOL. BYOL’s authors removed the predictor and changed how the target updates; a later method, SimSiam (Section 4), removed the stop-gradient.
Figure 6. Only the naive run collapses to one point (hand-scripted, not a trained run).
SimSiam (Simple Siamese networks, Chen and He 2021; Siamese means two branches with the same weights) uses one network for both views, with no momentum encoder and no negatives. As in BYOL, each view gives an output z and a prediction p, and each prediction is compared with the other view’s output behind a stop-gradient, written sg.
Removing the predictor collapses it too (0.1%). The SimSiam paper compares training to k-means clustering, which holds cluster centers fixed while fitting points, then updates the centers; the stop-gradient does the job of the fixed-centers step.
DINO (self-distillation with no labels, Caron et al. 2021; the student learns from a teacher built from itself) trains a student network to copy a teacher, a momentum encoder of the student (MoCo’s momentum rule). Two edits to the teacher’s output, centering and sharpening, stop collapse. Each network has K outputs, 65,536 in DINO. A softmax turns them into shares that sum to 1, and dividing by a temperature first sets how peaked the shares are, as in Knowledge Distillation but set low for the teacher. The student matches the teacher’s shares by cross entropy.
Figure 8. Three toy images, K = 8 outputs; the center is their mean.
This snippet runs one BYOL step, then moves the target toward the online network by EMA.
import copy, math
import torch
import torch.nn as nn
import torch.nn.functional as F
def byol_loss(p, z):
return (2 - 2 * F.cosine_similarity(p, z, dim=-1)).mean()
def mlp(d_in, d_out=256):
return nn.Sequential(nn.Linear(d_in, 4096), nn.BatchNorm1d(4096), nn.ReLU(), nn.Linear(4096, d_out))
encoder = nn.Sequential(nn.Flatten(), nn.Linear(3 * 32 * 32, 512))
online = nn.Sequential(encoder, mlp(512)) # encoder + projector
predictor = mlp(256)
target = copy.deepcopy(online).requires_grad_(False)
params = [*online.parameters(), *predictor.parameters()]
opt = torch.optim.SGD(params, lr=0.2, momentum=0.9)
k, K_STEPS, M_BASE = 0, 1000, 0.996 # step k
x1, x2 = torch.rand(64, 3, 32, 32), torch.rand(64, 3, 32, 32)
p1, p2 = predictor(online(x1)), predictor(online(x2))
with torch.no_grad():
z1, z2 = target(x1), target(x2)
L = byol_loss(p1, z2) + byol_loss(p2, z1)
opt.zero_grad()
L.backward()
opt.step()
m = 1 - (1 - M_BASE) * (math.cos(math.pi * k / K_STEPS) + 1) / 2
with torch.no_grad():
for p_t, p_o in zip(target.parameters(), online.parameters()):
p_t.mul_(m).add_(p_o, alpha=1 - m)
A SimSiam step has no target network; z.detach() is the stop-gradient sg of Section 4 eq lines 2 and 3.
import torch
import torch.nn as nn
import torch.nn.functional as F
def D(p, z):
return -F.cosine_similarity(p, z.detach(), dim=-1).mean()
online = nn.Sequential(nn.Flatten(), nn.Linear(3 * 32 * 32, 256)) # stand-in
predictor = nn.Linear(256, 256)
x1, x2 = torch.rand(64, 3, 32, 32), torch.rand(64, 3, 32, 32)
z1, z2 = online(x1), online(x2)
p1, p2 = predictor(z1), predictor(z2)
L = D(p1, z2) / 2 + D(p2, z1) / 2
detach() as sg.A DINO step trains the student, then moves the teacher toward it by EMA and the center c toward the batch mean.
import copy
import torch
import torch.nn as nn
import torch.nn.functional as F
def dino_loss(s_out, t_out, c, tau_s=0.1, tau_t=0.04):
P_t = F.softmax((t_out - c) / tau_t, dim=-1) # center, sharpen
log_P_s = F.log_softmax(s_out / tau_s, dim=-1)
return -(P_t * log_P_s).sum(dim=-1).mean()
K_OUT, LAM, M_C, WD = 1024, 0.996, 0.9, 0.04 # toy K; DINO uses 65,536
student = nn.Sequential(nn.Flatten(), nn.Linear(3 * 32 * 32, K_OUT))
teacher = copy.deepcopy(student).requires_grad_(False)
c = torch.zeros(K_OUT)
opt = torch.optim.AdamW(student.parameters(), lr=5e-4, weight_decay=WD)
x1, x2 = torch.rand(64, 3, 32, 32), torch.rand(64, 3, 32, 32)
s1, s2 = student(x1), student(x2)
with torch.no_grad():
t1, t2 = teacher(x1), teacher(x2)
L = (dino_loss(s1, t2, c) + dino_loss(s2, t1, c)) / 2
opt.zero_grad()
L.backward()
opt.step()
with torch.no_grad():
for p_t, p_s in zip(teacher.parameters(), student.parameters()):
p_t.mul_(LAM).add_(p_s, alpha=1 - LAM)
c = M_C * c + (1 - M_C) * torch.cat([t1, t2]).mean(dim=0)
WD from 0.04 to 0.4 on a cosine schedule.| BYOL | SimSiam | DINO | |
|---|---|---|---|
| Acts | every step; target EMA after it | every step; no copy | every step; teacher EMA and center after it |
| Keeps | target network | only the weights | teacher and center c |
| Settings | batch 4096, LARS, 1000 epochs | batch 512, SGD base lr 0.05, 100-epoch ablations | batch 1024, AdamW; 2 global 224 px views to both networks, plus local 96 px views to the student only (multi-crop); K = 65,536 |
| Cost | about 8 hours on 512 TPU v3 cores | one network; fits a typical 8-GPU machine | ViT-S/16, 300 epochs on two 8-GPU machines, about 46 hours with the 2 global views only and about 73 hours with 10 extra local views (Table 8) |
| Code | deepmind-research/byol | facebookresearch/simsiam | facebookresearch/dino |
| Gotchas | no predictor or m = 0 collapses | no stop-gradient or no predictor collapses; watch the output spread | a center rate of 0.999 collapses; drop centering or sharpening and it collapses |
| Knob, default | Raise it | Lower it |
|---|---|---|
| BYOL’s m, the target blend; 0.996 rising to 1 | a slower target; at 1 a frozen random target gives 18.8% | a faster target; 0.9 still gives 68.4%, and 0 collapses to 0.3% (Table 5) |
| DINO’s λ, the teacher blend; 0.996 rising to 1 | the same trade-off as BYOL’s m: slower is steadier; copying the student each step fails to converge (sec. 5.2) | |
| Center rate for c; 0.9 in the official code | the center lags; 0.999 collapses (k-NN 0.1%) | the center follows each batch; 0 gives 69.1% against 69.7% |
| τt divides gt(x) − c; 0.04 raised to 0.07 over the first 30 epochs (warm-up) | flatter teacher shares; above 0.06 without warm-up it collapses | sharper shares, a peak per image; at 0, k-NN falls to 43.9% |
| τs divides the student’s outputs; 0.1 | flatter student shares; very high, they go flat and can no longer match a peaked teacher, so the loss stays high | sharper student shares that commit to one output; near 0, the student puts all its share on its top output |
BYOL’s projector output size, the length of the prediction and target vectors (d_out in the snippet’s mlp); 256 (sec. 3.3) | no gain; 512 gives 72.6% against 72.5% at 256 | accuracy falls slowly; 16 still gives 69.9% (Table 14b) |
From the BYOL, SimSiam and DINO papers and main_dino.py.
Masked Image Modeling comes next, which hides most of an image and rebuilds it.