GraphDINO¶
Adapted from Caron et al. (DINO), ICCV 2021. Brings ViT-style self-distillation with no labels to graphs: a student network learns to match a teacher's soft cluster assignments (prototypes) across multiple augmented views, with the teacher itself just an EMA of the student.
How it works¶
- Student encoder + student head; teacher encoder + teacher head (EMA, gradient-free).
n_viewstotal augmented views; the firstn_global_viewsgo to the teacher, all views go to the student.DINOLoss: cross-entropy over every pair(teacher_i, student_j)withi ≠ j, averaged over the number of terms.- Student output:
log_softmax(logits / student_temp), computed insideDINOHead. - Teacher output:
softmax((logits − center) / teacher_temp_eff), computed insideDINOHead.
- Student output:
- Teacher temperature warmup:
teacher_temp_effgrows linearly fromwarmup_teacher_temptoteacher_tempoverwarmup_teacher_temp_epochsepochs (DINOHead.set_epoch(epoch)). - Center update (EMA):
center = c_mom * center + (1 − c_mom) * mean(teacher_logits), over the raw teacher logits (DINOHead.prototype_logits, before centering and softmax), as in the reference DINO implementation. Handled byDINOHead.update_center(), called frompost_step()— this is what keeps the teacher's output distribution from collapsing to a single prototype. - EMA teacher: momentum grows on a cosine schedule from
ema_tau_basetoema_tauovertotal_steps. freeze_last_layer_epochs: during the first N epochs, gradients of the prototype layer (student_head.proto) are zeroed out after backward, inpost_backward()— a stabilization trick carried over from the original DINO recipe.
Hyperparameters¶
| Field | Default | Notes |
|---|---|---|
student_temp |
0.1 |
|
teacher_temp |
0.07 |
final teacher temperature (reference DINO: 0.04) |
warmup_teacher_temp |
0.04 |
starting teacher temperature (must be ≤ teacher_temp) |
warmup_teacher_temp_epochs |
30 |
epochs of linear warmup from warmup_teacher_temp to teacher_temp; 0 uses teacher_temp from the start |
center_momentum |
0.9 |
|
norm_last_layer |
False |
fix the prototypes' weight-norm scale at 1 (logits become cosines in [-1, 1]), as DINO's norm_last_layer; off = trainable scale |
ema_tau |
0.996 |
final teacher EMA momentum |
ema_tau_base |
0.9 |
starting teacher EMA momentum, annealed to ema_tau on a cosine schedule over total_steps (reference DINO starts at 0.996); never above ema_tau |
total_steps |
0 |
length of the EMA schedule in optimizer steps — set it to your number of training steps; 0 keeps the momentum fixed at ema_tau_base |
freeze_last_layer_epochs |
1 |
|
n_views / n_global_views |
2 / 2 |
Why the defaults differ from reference DINO
With reference DINO's values (EMA momentum 0.996 from the first step, teacher
temperature 0.04) GraphDINO's accuracy drops as full-batch training continues: in the
citation-network ablations (GIN, 5 seeds, linear probe) Cora goes 63.5 → 50.7 → 39.6 at
300 → 1000 → 3000 steps, ending below the untrained encoder (41.5). Diagnostics tie it to the
teacher's output sharpening until every node gets a hard prototype assignment that no
longer tracks the classes.
The defaults are the setting that held up best there: a faster teacher
(ema_tau_base=0.9, annealed to ema_tau=0.996) with a softer one (teacher_temp=0.07,
warmed up from 0.04). Cora: 72.9 → 68.0 → 67.8. norm_last_layer=True, gradient
clipping and more prototypes did not help. With a GCN encoder the defaults help in long
runs (Cora 77.3 against 71.9 at 3000 steps) and make little difference at 300 steps. Tables
and caveats:
Benchmarks → Sensitivity.
- The ablations always set
total_stepsto the length of the run. Without it the momentum stays at0.9, which is only spot-checked at 300 steps. - To get reference DINO back:
ema_tau_base=0.996,head.teacher_temp=0.04.
Operation order¶
forward → loss → backward → clip_grad → post_backward (freeze last layer)
→ optimizer.step → post_step (EMA teacher + center update)
Config example¶
config = {
"encoder": {"name": "gin", "hidden_dim": 128, "num_layers": 2},
"head": {
"name": "dino", "proj_hidden": 128, "bottleneck_dim": 32,
"n_prototypes": 128,
},
"augment_teacher": [{"name": "edge_drop", "p": 0.2}, {"name": "feat_mask", "p": 0.1}],
"augment_student": [{"name": "edge_drop", "p": 0.3}, {"name": "feat_mask", "p": 0.2}],
"total_steps": num_epochs * steps_per_epoch, # length of the EMA schedule
"freeze_last_layer_epochs": 1,
}
model = GraphDINO(config, in_channels=dataset.num_features)
Reference¶
GraphDINOConfig — see Config API.