☰
DINO无标签蒸馏实践:从原理到复现的关键细节
2026/10/21 3:02:38 网站建设 项目流程

做视觉项目的人,大概都经历过这种时刻:手里攒了几万张图,还没人标注,分类、检测、分割全都卡在第一步。我也一样,第一次接触 DINO 的时候正被无标签数据折磨,看到全称 Distillation with NO labels 反而更困惑——知识蒸馏不是要教师网络先在有标签数据上训练吗?没有标签,蒸馏什么?怎么蒸?

后来把论文和开源实现完整吃了一遍,才发现 DINO 的"蒸馏"和我们熟悉的 teacher-student 蒸馏根本不是一回事。它不蒸馏某个任务的答案,而是蒸馏"同一张图片在不同视角下应该长成什么特征"。这恰好是无标签视觉表征学习的核心命题。这篇笔记我会从传统蒸馏的前提假设讲起,逐步拆解 DINO 的三个核心机制、数据增强策略,再把我复现和调参时踩过的坑全部摆出来,给同样在无标签数据里挣扎的朋友一条能直接照做的路径。

1. 没有标签也能蒸馏?先看传统知识蒸馏的"前提假设"

1.1 传统蒸馏为什么必须"有标签"

很多人第一次看到"无标签知识蒸馏"会觉得矛盾,根源在于我们把蒸馏机制理解成了一个固定范式:教师网络在标注数据上训练,输出 logits 或概率分布;学生网络去拟合这个分布,把教师的"软知识"迁移过来。这里的知识其实包含两类东西——类别间的相似性(比如猫和豹子的特征距离比猫和桌子近)和样本自身的特征结构。但不管哪一类,传统蒸馏的起点都建立在"教师知道正确答案"的基础上。

如果一个网络从没在标签上学过,它的输出层没有类别维度,那它拿什么给学生当目标?这就好比你让一位从来没看过菜谱的厨师当导师,教徒弟做一道他没见过的菜,两个人只能大眼瞪小眼。这也是为什么传统蒸馏无法直接移植到无标签场景。

1.2 DINO 对"知识"的重新定义:学身份,而不是学类别

DINO 的思路是跳开"类别"这个维度,把教师的知识定义成"网络对同一物体不同视角的响应一致性"。具体做法非常简洁:用同一个网络的两个副本,分别叫做 teacher 和 student。给它们喂同一张图片的不同裁剪视角,要求 student 输出的特征分布去匹配 teacher 输出的特征分布。

teacher 的目标不是某个标签,而是它自己对这个输入的表征。这意味着我们不再要求网络说出来"这是什么",而是要求网络形成一种内部的一致判断——"这两个裁剪来自同一个东西"。这种判断建立在图像内容本身的结构上,而不是人工标注上,所以叫自蒸馏(self-distillation)。DINO 在这里做的最重要的一步,就是把"蒸馏一个任务的答案"改成了"蒸馏一个视角变换不变的函数"。网络只要学会这个函数,不管图像怎么裁剪、变形、变色,它都能给出稳定的特征,而这些特征天然就包含了物体的语义信息。

这套逻辑真正跑通之后,我愈发觉得它像一种"对比学习的精神,但用了蒸馏的形式":不和负样本比,只和教师的视角比。少了构造负样本的麻烦,也少了 batch size 不够导致负样本匮乏的问题,这让它在小 batch 场景下比对比学习更皮实。

2. 三个关键咬合点:EMA、centering、sharpening

2.1 教师网络是怎么"慢吞吞"地进化的

DINO 里的教师网络并不是单独训练出来的,而是学生参数的指数移动平均(EMA)。每个训练步,学生网络正常通过梯度更新,教师网络则按照下面这个公式被"拖"着走:

θ_t = λ * θ_t + (1 - λ) * θ_s

其中 λ 通常取 0.996 到 0.9995 之间,非常接近 1。这意味着教师的参数每步只向学生移动一点点,变化非常缓慢。为什么要这样设计?我个人的理解是:如果教师直接复制学生,那训练目标就变成了"学生自己匹配自己",网络很快就会找到一个退化解——对所有输入都输出同样分布,这样损失是 0,但什么特征都没学到。EMA 让教师始终保持一个"历史集成"状态,目标函数相对稳定,不会和学生一起陷入塌缩。这个机制有点像有个经验丰富的老师傅在旁边,基于多年的经验给意见,而学生每次只被允许小幅影响老师傅的判断,所以老师的意见整体稳定、靠谱,网络才能一点一点被带向有意义的表征空间。

2.2 防坍塌的双保险:centering 和 sharpening 的博弈

自监督方法最怕的"表征坍塌"——网络对所有输入都输出同一个常数分布,训练损失虽然最低,但特征毫无信息。DINO 用两种操作从两个方向夹击这个风险。

第一个是 centering(集中化)。在教师分支输出 logits 之后,先减去一个平均向量 c,这个 c 也是通过 EMA 方式更新的,统计的是教师最近输出的均值。减去 c 的作用是防止教师把所有样本都预测到同一个类别方向,让它的输出分布尽量平坦。第二个是 sharpening(锐化),也就是让学生在低温下做 softmax。学生输出的温度参数 τ_s 通常取 0.1,远低于教师侧的温度(0.04 到 0.07)。低温 Softmax 会放大 logits 之间的差异,让概率分布更尖锐,相当于逼学生对特征之间的细微差别更敏感。

这两个操作一个在"压平",一个在"锐化",方向相反却形成均衡。只做 centering,教师输出容易被抹平,学生学不到区分性;只做 sharpening,学生容易过度自信,训练不稳定。论文里也说得很明确:二者必须同时用,才能有效避免坍塌。我在实验里试过把学生的温度从 0.1 调大到 0.3,训练到 200 个 epoch 之后特征明显退化,KNN 精度掉了好几个点,可见这个平衡有多脆弱。

2.3 为什么 DINO 选择 Vision Transformer 而不是纯 CNN

DINO 在论文中实际验证了 CNN 和 Vision Transformer 都能工作,但视觉效果和最终精度上 ViT 明显更出色。原因是多方面的。第一,ViT 的 attention 机制天然把全局信息显式建模出来,self-attention 在多视角匹配上比卷积的局部感受野更有效率。第二,DINO 训练出的 ViT attention 图极其清晰,能够不经过任何标注就实现类似分割的效果,这是 CNN 难以做到的。第三,从训练稳定性来看,ViT 配合 EMA teacher 的收敛过程在 DINO 框架下比 CNN 平稳,尤其在 batch size 不算特别大的时候。

有一段时间业界很多人说"ViT 需要海量数据,小数据跑不动",但 DINO 这个工作反而是把它从小数据里拉出来的关键步骤之一。这也提醒我们,不要简单重复"自监督适合 CNN"这种过时结论,模型架构和训练范式必须放在一起考虑。

3. multi-crop 策略:一图多看,多尺度对比

3.1 全局图加局部图的哲理:教师只看大图,学生全看

DINO 训练时用的数据增强策略叫 multi-crop。每张图会生成多个裁剪视图,其中两个是全局视图(global view),分辨率通常为 224x224,覆盖原图大约 50% 的区域;另外六个或八个是局部视图(local view),分辨率 96x96,覆盖原图大约 10% 的区域。关键的不对称设计在于:teacher 分支只看两个全局视图,student 分支则同时看全局视图和全部局部视图,并且每个 view 都会和教师的全局视图输出做交叉熵匹配。

这个设计的内涵是:学生被迫做到"惊鸿一瞥就能认出全貌"。局部视图只包含物体的一小部分,但学生必须输出和教师看完整画面时一致的特征分布。逼迫网络学会从细节推理整体,而不是简单记住某个固定构图。这种多尺度的目标比传统的"两张图对比"更能让特征具备位置不变性和尺度鲁棒性。实际效果上也验证了这一点:去掉局部视图,DINO 在 ImageNet 线性评估上会掉不少精度,说明局部视图并不是锦上添花,而是核心燃料。

3.2 工程实现时要小心 batch 设计的细节

在工程上实现 multi-crop 需要注意 batch 的组织方式。最自然的方式是一个 batch 里同时包含多张 global 和多张 local 图,由数据加载器一次性返回。但在多卡分布式训练里,每个卡拿到的样本数量、view 数量必须完全一致,否则 EMA 更新的 center 统计就不稳定。我自己实现时踩过这个坑:第一版为了让显存更省,把 local view 拆分到了两个半 batch 里过,结果 teacher center 的统计口径不一致,训练过程震荡很厉害。后来完全按照官方做法,把 teacher 的 input 固定为 global crops,student 的 input 为 global 加上 local crops,一起 forward,问题就消失了。

还有一些细节值得注意:局部视图的裁剪面积不能设得太小,尤其是数据集中目标物体普遍偏小时。如果裁剪面积太小,局部视图可能只包含一片背景纹理,学生强行匹配教师的全貌输出,反而把语义信息带偏。

4. 从论文到工程:复现 DINO 的配置细节与避坑经验

4.1 论文和代码里最关键的几个超参数

光看论文会觉得 DINO 的公式不难,但真到复现阶段,超参数比公式更容易让你翻车。我把自己梳理的关键配置整理成了表格,方便对照参考。

配置项论文推荐值备注
教师温度 τ_t0.04 - 0.07多数实验用 0.04
学生温度 τ_s0.1过大容易退化
teacher EMA 系数 λ0.996 - 0.9995训练长则偏大
centering 动量0.9统计教师输出的均值中心
全局视图2 个(224x224)约覆盖原图 50%
局部视图6 - 8 个(96x96)约覆盖原图 10%
batch size512 - 2048实际实验可降低
优化器AdamW权重大约 lr 对应
学习率线性缩放,需 warmup小 batch 时尤其重要

这些配置一环扣一环。教师温度决定目标分布的锐利程度,EMA 系数决定教师更新的速度,centering 动量影响目标中心的稳定性。如果你在某一个数据集上训练效果不理想,优先检查组合而不是单个参数。我试过单独把 teacher 温度从 0.04 改成 0.07,短期看不出问题,但 100 个 epoch 之后特征质量明显下降,原因是目标分布过平滑、学生收到的梯度太温和。

4.2 显存与 batch size 的折衷:实际训练怎么落地

DINO 论文里推荐的 batch size 动辄 512 甚至 2048,对个人开发者来说相当不友好。但好消息是,DINO 对 batch size 的敏感程度比对比学习低不少,因为它的目标不依赖负样本。如果你只有 1-2 张消费级显卡,我实践下来的建议是先把 batch size 设为 128,配合梯度累积把有效 batch 撑到 256 或 512,但要注意开启 grad checkpointing 节省显存,同时把 local view 数量降到 4 个左右。

在训练初期,模型还没进入稳定状态,较小的 batch 会让 center 的 EMA 统计噪声偏大。解决办法是适当增大 centering 的动量到 0.95,让中心统计更平滑;同时把学习率的 warmup 从默认的 10 个 epoch 拉长到 20 到 30 个 epoch。说到底,DINO 在小资源下能不能跑通,主要考验你对 EMA、centering 和温度这几个参数的耦合理解。一个参数不够调就调另一个,但别一次动太多。

4.3 训练中几个容易被忽略的坑

我复现过程中踩过的坑,每一个都值得单独说。

第一,teacher 分支的前向传播必须包在torch.no_grad()里,否则它会参与梯度图构建,显存直接翻倍,训练速度急剧下降。看起来理所当然,但很多人忽略。

第二,teacher 参数更新用的是 EMA,而不是直接复制 student。有些早期实现把 teacher 也当成可训练参数来更新,那个叫双分支训练,不叫 DINO。

第三,center 向量的更新不能脱离 teacher 的输出。我见过有人把 center 更新放在no_grad外面,导致 center 参与了梯度计算,目标函数变成一个移动靶,损失曲线永远压不平。

第四,局部视图的持有顺序要随机打散,不要简单地跟在全局视图后面塞进 batch。Transformer 对输入顺序不敏感,但 batch 的组织会影响 BN 层的统计效果,如果用了 BatchNorm,这个问题会更明显。DINO 的实现默认用 LayerNorm,等你改成 ResNet backbone 的时候就要格外小心。

我在自己的数据集上跑 DINO 时,核心训练循环可以简化成下面这段伪代码,结构上足够清晰:

for x_global, x_local in loader: # 教师只看全局视图,且不参与梯度 with torch.no_grad(): teacher_logits = teacher(x_global) teacher_logits = teacher_logits - center teacher_prob = softmax(teacher_logits / teacher_temp) # 学生看到所有视图 student_logits = student(torch.cat([x_global, x_local])) student_prob = softmax(student_logits / student_temp) # 每个局部视图都要匹配教师的全局输出 loss = cross_entropy(student_prob, teacher_prob.expand(...)) loss.backward() optimizer.step() student_ema_update(teacher, student, m=ema_factor) center = momentum * center + (1 - momentum) * teacher_logits.mean(dim=0)

有一点要提醒:这里每个局部视图都要和教师的全局视图输出计算损失,并不是只取一张局部图做匹配。要把广播逻辑写对,否则损失只覆盖了一个 view,信息量少了一大截。

5. 训练完成之后:如何评估和可视化 DINO 的特征

5.1 评估方式:KNN 和线性探测

自监督模型没有标签,训练完怎么知道特征质量好不好?最常用的两套评估方法:线性探测(linear probing)和 KNN 评估。

线性探测的思路是冻结特征提取器,只在一个线性分类层上训练,看 Top-1 精度能到多少。这个精度反映的是特征本身的可分性,如果特征是一堆没有区分度的向量,线性层怎么学都上不去。KNN 评估更简单,不需要任何训练:提取所有样本的特征,对某个查询样本找它在特征空间里的最近邻,用邻居对应的标签投票。KNN 精度能非常直观地反映特征聚类效果。DINO 在具有一定规模的数据集上训练出的特征,KNN 精度和线性探测精度都很可观。实际业务中如果只是做相似度检索,KNN 这个指标比线性探测更有参考价值。

5.2 看 attention:DINO 到底在"看哪里"

DINO 训练出的 ViT 有一个非常惊艳的特性:attention 可视化结果往往接近无监督语义分割。给模型看一张图,它的 attention 图会集中在物体轮廓上,背景区域注意力很低;换一张图,注意力会平滑地跟着目标位置走,不需要任何标签。这个特性对业务场景有很直接的帮助,比如弱监督检测、无标注视频帧的筛选、OCR 区域的先验定位,甚至可以用 DINO 的特征来生成初始伪标签,再配上简单的聚类算法凑出训练集。

在我自己的项目里,DINO 特征在图像去重和相似检索上的表现尤其稳。以前我用过其他预训练模型的特征做检索,对拍照角度变化非常敏感;换用 DINO 特征之后,同一物体不同角度的检索一致性明显改善。这个特点说白了就是它训练目标(对视角变化鲁棒)的直接延伸。

最后再说点实际操作的体会

DINO 的实验看起来公式简单,真正跑下来才发现对细节要求极高,属于"越平常越致命"的方法。它不需要负样本,看起来"便宜",但调参的耦合度比对比学习更高,一个温度、一个动量、一个裁剪尺寸,都可能让特征质量天差地别。如果你的目标是自己的业务数据而不是刷公开榜单,我建议先花一周时间跑一个小规模实验,把上面表格里的配置在缩小版数据上验证一遍,再上全量资源。自监督训练最忌讳一上来就拉满所有算力,等两三百个 epoch 后才发现方向不对,那成本就太高了。

最后分享一个小技巧:如果你手里的数据类别极不均衡,或图片中小目标特别多,可以把 global view 的裁剪面积从默认的 0.5 调大到 0.7,把 local view 的裁剪面积下限从 0.1 调高到 0.2。这样做的目的是保证小目标在局部视图中不会碎成一堆背景,训练出来的特征在小目标场景下会好不少。我在这类数据集上试过,调整前后 KNN 精度能差近三个点,值得你测试时多加一组对比。希望这份从原理到工程的拆解,能让你少走一些我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询