想象一位经验丰富的鸟类学家,带着年轻的学徒去野外观察。学徒一开始什么都不懂,只能跟在师傅身后,看师傅如何观察、如何辨认。师傅从不给学徒发一本“鸟儿图鉴答案册”——因为大自然本身没有标好名字的标签。学徒要做的,是看大量的鸟,听师傅的判断,自己慢慢琢磨出“什么样算一只鸟”“不同的鸟有什么区别”。这个过程中,师傅的眼光也在不断精进,但变化很缓慢,而学徒则大步快跑地追赶。最终,学徒练就了一双火眼金睛,哪怕遇到从没见过的鸟,也能一眼认出它的种类。 这就是 DINO 和 DINOv2 讲的故事:让 AI 像学徒一样,通过“自己看、自己比、跟着老师傅学”的方式,从海量普通图片中学习视觉世界的规律,而完全不需要人工标注。这个故事里有“老师傅”(教师网络)、有“学徒”(学生网络)、有“观察笔记”(ViT 特征)、也有“厚厚一叠观察记录”(DINOv2 的超大数据集)。读完这篇文章,你会明白这场“师徒传承”的每一步是怎么走的,以及为什么这套方法能让 AI 拥有一双通用而敏锐的“眼睛”。

岗位关联

对 AI 从业者而言,DINO/DINOv2 是自监督视觉领域绕不开的地基:理解它,你就理解了“无标注”时代视觉模型如何自学成才,也能更清楚多模态大模型的视觉底座是怎么炼成的。

目录
  1. 可视化演示
  2. 自监督学习目标
  3. 自蒸馏机制
  4. ViT 特征学习
  5. DINOv2 数据工程
  6. 零样本能力
  7. 与 MAE 对比
  8. 通用视觉表示
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

动态演示DINO/DINOv2的核心机制,通过输入/处理/存储/输出四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

DINO/DINOv2:DINO DINO自蒸馏方法核心原理 ViT特征学习与中心化锐化 核心机制可视化 DINO/DINOv2整体架构与关键组件

自监督学习目标

在野外观察的第一天,师傅告诉学徒:“我们不带图鉴,也不给鸟贴标签。你要做的,是观察。”这就是自监督学习的核心哲学——不依赖人工标注,直接从数据本身找规律。传统监督学习像是老师拿着标准答案册子考学生:“这是麻雀,记住;这是乌鸦,记住。”而自监督学习让模型自己“出题”、自己“解题”:既然同一只鸟无论从哪个角度看都是同一只鸟,那就让 AI 学会辨认“不同角度的同一只鸟”——这就是一个天然的“代理任务”。

DINO 的代理任务可以这样理解:把一张图片做两种不同的“花样处理”——比如裁剪出不同区域、改变颜色、调转方向——得到它的两个“视角”。虽然两个视角长得不完全一样,但它们描述的仍是同一只鸟。DINO 要求模型对这两个视角给出尽量一致的“身份判断”。这就像你从正面和侧面看一个人,虽然看到的样子不同,但你知道那是同一个人。早期对比学习也是这个思路,用“拉近正样本、推远负样本”的方式让模型学会这种一致性:

不过,DINO 并没有走这条“对比”的路,而是选择了一条更温和的“师徒相传”之路——下一节我们将看到,DINO 真正巧妙的地方在于用“师傅教徒弟”取代了“正面硬碰硬”的对比。那天在野外,师傅对学徒说:“你看得多了,自然就认得了。”而 DINO 会把这句话变成一套精密的数学机制。

Lcontrast=−log⁡exp⁡(sim⁡(zi,zj)/τ)∑k=1Nexp⁡(sim⁡(zi,zk)/τ)\mathcal{L}_{\text{contrast}} = -\log \frac{\exp(\operatorname{sim}(z_i, z_j)/\tau)}{\sum_{k=1}^{N} \exp(\operatorname{sim}(z_i, z_k)/\tau)}
这个公式的意思是:对某个样本 $z_i$,让它与自己的“另一个视角” $z_j$ 的相似度尽量大,而与其他所有样本 $z_k$ 的相似度尽量小。$\tau$ 是温度参数,控制比较的“严格程度”,温度越低,模型越“苛刻”。
  1. 分子部分(拉近正样本)$z_i$ 与它的同源视角 $z_j$ 做相似度计算后取指数,越相似这个值越大,模型就越“开心”。
    exp⁡(sim⁡(zi,zj)/τ)\exp(\operatorname{sim}(z_i, z_j)/\tau)
  2. 分母部分(推远负样本)把 $z_i$ 与所有样本 $z_k$ 的相似度都加起来作为“参照系”,让 $z_i$ 在众多候选中“只认得”自己的同伴 $z_j$。
    ∑k=1Nexp⁡(sim⁡(zi,zk)/τ)\sum_{k=1}^{N} \exp(\operatorname{sim}(z_i, z_k)/\tau)
  3. 负对数(变成损失)把比值取负对数,比值越大损失越小,模型通过最小化损失来实现“认出同伴”这个目标。
    −log⁡(⋅)-\log(\cdot)

自蒸馏机制

自蒸馏是 DINO 最核心的“师徒机制”。想象一下:师傅(教师网络)看到一只鸟,心里会做一个“判断分布”——七分像麻雀、两分像柳莺、一分像山雀。徒弟(学生网络)看到同一只鸟,也说出自己的判断分布。徒弟的目标很简单:让自己的判断尽量接近师傅的判断。但这里有个微妙之处:为什么学生要向教师学,而不是两个网络互相学?如果没有一个“稳定的标杆”,两个网络就会互相迁就,最后变成“你说什么都对”,大家一起退化成瞎子——这在深度学习里叫“表示坍塌”。

DINO 的解决方案很优雅:教师网络不直接训练,而是用“指数移动平均”(EMA)的方式,从学生网络的参数缓慢“沉淀”出来。师傅不是一步登天的圣人,而是几十年如一日地积累经验——每带一天学徒,师傅的眼光只精进一点点。数学上,教师参数 $\theta_t$ 在第 $k$ 步更新为:

其中 $\theta_s$ 是学生参数,$\lambda$ 通常取 0.996 甚至 0.9995。学生大步快跑(正常梯度更新),教师慢步沉淀(EMA 更新),徒弟再怎么折腾,师傅始终是那个“稳如泰山”的标杆。

而学生“向师傅学习”这件事本身,用的损失是“软标签交叉熵”:师傅输出的概率分布 $P_t(x)$ 作为“标准答案”,学生输出的分布 $P_s(x)$ 要尽量贴近它。这不是生硬的“对或错”,而是“师傅说七分像麻雀,我也该说七分像麻雀”:

师傅的判断分布给学生提供了比“硬标签”丰富得多的信息。到了傍晚,徒弟的笔记已经和师傅的观察方式越来越像——而且因为师傅稳,徒弟永远不会被自己的噪音带偏。这正是 DINO 自蒸馏的精妙之处。

θt←λθt+(1−λ)θs\theta_t \leftarrow \lambda \theta_t + (1-\lambda) \theta_s
  1. 保留师傅的旧经验$\lambda \theta_t$ 表示教师网络保留了自己以前 99.6% 的“旧眼光”,不轻易被学徒带偏。
    λθt\lambda \theta_t
  2. 吸收学生的一点新悟$(1-\lambda)\theta_s$ 表示学生网络最近的学习成果中,只有极小一部分(比如 0.4%)被师傅采纳。
    (1−λ)θs(1-\lambda) \theta_s
  3. 合二为一两项相加,得到教师网络的新参数。每一步都是“旧经验为主、新见识为辅”的稳健更新。
    Ldistill=−∑cPt(c)(x)log⁡Ps(c)(x)\mathcal{L}_{\text{distill}} = -\sum_{c} P_t^{(c)}(x) \log P_s^{(c)}(x)

ViT 特征学习

有了师徒机制,还需要一双好眼睛——DINO 选用了 ViT(Vision Transformer)作为骨干。之前我们看的 CNN 就像“从上到下、从左到右扫视全图”,而 ViT 则是把一张图切成一个个小方块(patch),像把一句话拆成一个个单词,然后像阅读理解一样阅读整张图片。在自然界,师傅告诉学徒:“别急着看整只鸟,先看局部——喙的形状、羽毛的颜色、翅膀的斑纹,把这些局部信息串起来。”

ViT 在处理图像时,会往序列最前面放一个特殊的“CLS token“,它就像这篇文章的”总结摘要”。图像的所有 patch 信息通过注意力机制(Attention)汇集到 CLS token 上,CLS token 最终携带了整张图的全局语义。DINO 发现,ViT 训练完成后的注意力图极其漂亮——模型的目光会自动聚焦在鸟的头、身体、翅膀这些有语义的部位,而自动忽略背景。这个注意力公式是 Transformer 家族的基石:

其中 $Q$、$K$、$V$ 分别代表“查询”“关键词”“取值”。可以这样理解:$Q$ 是我在问“图像里哪里最重要”,$K$ 是每个位置说“我是什么”,$V$ 是每个位置真正贡献的内容。模型先算出“哪里值得看”(注意力权重),再按权重去“取内容”(加权求和)。

在 DINO 中,注意力图不只是可视化工具,它本身就蕴含了模型对图像结构的理解——哪里是物体、哪里是背景、哪些像素属于同一个目标,全都刻在注意力矩阵里。学徒在师傅的指导下,不是记住某一只鸟的样子,而是学会了“先把目光落到喙上、再落到翅膀上”这种结构化的观察方式。这正是 ViT 特征学习的奇妙之处:模型没有被告知“鸟有眼睛”,却自己涌现出了对动物身体的局部感知。

Attention⁡(Q,K,V)=softmax⁡(QK⊤dk)V\operatorname{Attention}(Q,K,V) = \operatorname{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V
  1. 查询与关键词的匹配$QK^\top$ 计算每个位置之间的“相关性分数”,类似学徒问“这个部位和鸟的整体有什么关系”。
    QK⊤QK^\top
  2. 缩放保持稳定除以 $\sqrt{d_k}$($d_k$ 是特征维度),防止分数过大导致 softmax 饱和——就像做菜时盐放多了会盖住所有味道,要控制用量。
    QK⊤dk\frac{QK^\top}{\sqrt{d_k}}
  3. 归一化成权重softmax 把相关性分数变成一组加起来等于 1 的权重,表示“目光应该分配多少比例到每个位置”。
    softmax⁡(⋅)\operatorname{softmax}(\cdot)
  4. 加权汇总最后乘以 $V$,把内容按照权重取回来,得到这个位置的输出特征。
    ⋯V\cdots V

DINOv2 数据工程

DINOv2 的故事可以概括为一句朴素的话:把观察的规模和品质做到极致。如果说 DINO 是师傅带着学徒在一小片林子里认鸟,那 DINOv2 就是带着学徒走遍全球,看过几十亿张自然图像。DINOv2 的数据集 LVD-142M 有 1.42 亿张图像,涵盖从卫星照片到博物馆藏品的各种场景。这就好比学徒见过太多鸟之后,不仅认得麻雀乌鸦,连从没见过的新物种也能一眼判断出它的类别。

但光有“多”还不够,数据工程讲究“质”——DINOv2 对数据做了严格的清洗和去重。设想一下:如果学徒每天看到的都是同一只鸟的照片,他的眼光就会变得狭窄。DINOv2 用一个预训练好的模型对图像做 embedding,然后通过聚类去重,把几乎重复的图像剔除掉,保证每一张图都带来新信息。同时,DINOv2 还做了“课程式”的训练——先在小数据集上训练一个“种子模型”,再用这个种子模型去海量数据里做“伪标注”,筛选出最有价值的数据,最后训练最终模型。这就像师傅先带徒弟在自家后院认识基本鸟类,再带他去全世界见识稀罕品种。

为了避免重复造轮子,DINOv2 还引入了一个关键技术:用一个大模型作为“老师”,把知识蒸馏给一个小模型。这样一来,不必每个模型都在 1.4 亿张图上从头训练。DINOv2 的总训练目标融合了多种自监督信号,同时优化全局特征和局部 patch 特征:

$\mathcal{L}_{\text{CLS}}$ 是让整张图的全局特征向教师“看齐”的蒸馏损失,$\mathcal{L}_{\text{patch}}$ 则是在更细的 patch 级别上做类似的事,让每个局部小方块也知道自己“属于哪个物体”。

有了这套“大规模 + 严清洗 + 蒸馏提效”的数据工程,DINOv2 训练出的视觉特征在 ImageNet 线性分类、语义分割、深度估计等任务上全面碾压了之前的自监督方法,甚至逼近甚至超过了许多使用标注数据训练的监督模型。学徒看够了世界,眼力自然就不一样了。

LDINOv2=LCLS+Lpatch\mathcal{L}_{\text{DINOv2}} = \mathcal{L}_{\text{CLS}} + \mathcal{L}_{\text{patch}}
  1. 全局蒸馏损失$\mathcal{L}_{\text{CLS}}$ 作用于 CLS token 上,确保模型对整张图的“总结判断”和师傅一致——学徒看完整只鸟后总体判断要对。
    LCLS\mathcal{L}_{\text{CLS}}
  2. 局部对齐损失$\mathcal{L}_{\text{patch}}$ 作用于每个图像块上,让局部特征也承载语义——学徒不光认得出整只鸟,还要指得出“这块是鸟嘴”。
    Lpatch\mathcal{L}_{\text{patch}}
  3. 两者相加全局与局部同时约束,模型既有整体观又不失细节。
    LDINOv2=LCLS+Lpatch\mathcal{L}_{\text{DINOv2}} = \mathcal{L}_{\text{CLS}} + \mathcal{L}_{\text{patch}}

零样本能力

“零样本”这个词听起来玄乎,其实就是指“出师之后,遇到从没直接训练过的任务也能上手”。想象一下,学徒跟在师傅身边三年,从没见过“极乐鸟”这种稀罕货,但有一天他在异国森林里第一次看到极乐鸟,愣了几秒后说:“这是一种鸟,像是雀类的远亲。”为什么他能做到?因为他通过数万小时的观察,已经掌握了“鸟之所以是鸟”的深层规律——喙的形态、羽毛纹理、身体结构、飞行姿态。这些规律不需要每个物种单独教。

DINO/DINOv2 的零样本能力在执行层面非常简单粗暴:无需训练任何分类头,直接用“最近邻”(KNN)方法。具体来说,把训练集所有图像通过模型提取成特征向量,存成一个“记忆库”;来了新图像,也提取特征,然后在记忆库里找最相似的 $k$ 张图,让它们投票决定新图像属于哪一类。这就像学徒翻看自己的记忆相册,找“最像的几位老朋友”来参考:

其中 $f(x)$ 是输入图像 $x$ 通过 DINO 提取的特征向量,$y_i$ 是记忆库中第 $i$ 张图的类别标签,kNN 选出最相似的 $k$ 个样本。

在 ImageNet 上,DINOv2 仅靠 KNN 分类就能达到 83.5% 左右的 top-1 准确率,几乎不逊色于一些训练了完整分类头的监督模型!更惊艳的是,这个特征不仅适用于图像分类,还能直接用于深度估计、语义分割、实例检索等任务,完全不需要重新训练。这就是“学会了观察世界”本身带来的力量——真正通用的能力不是死记硬背每种题目,而是理解事物背后的规律。

y^(x)=majority_vote⁡{yi∣i∈kNN⁡(f(x))}\hat{y}(x) = \operatorname{majority\_vote}\{ y_i \mid i \in \operatorname{kNN}(f(x)) \}
  1. 提取特征$f(x)$ 表示把图像 $x$ 送入 DINO 网络,得到特征向量——好比学徒看到新鸟后,心里形成的“整体印象”。
    f(x)f(x)
  2. 找出最近邻$\operatorname{kNN}(f(x))$ 在记忆库中检索与 $f(x)$ 最相似的 $k$ 个特征对应的样本——好比翻相册找最相像的 $k$ 位老朋友。
    kNN⁡(f(x))\operatorname{kNN}(f(x))
  3. 多数投票$\operatorname{majority\_vote}\{\cdots\}$ 让这 $k$ 个“老朋友”的类别标签投票表决,票数最高的类别即为预测结果。
    majority_vote⁡(⋅)\operatorname{majority\_vote}(\cdot)

与 MAE 对比

在自监督视觉领域,MAE(Masked Autoencoder)是另一条引人注目的路线。如果 DINO 是“观察派”,那 MAE 就是“重建派”。MAE 的思路像玩拼图游戏:把图片随机遮掉大部分区域(比如 75% 的小方块),然后让模型根据剩下的 25% 去猜被遮住的内容,目标是尽量还原原始像素。重建派认为:“只要你把图片补全得天衣无缝,你必然理解了图片的结构。”

DINO 的观察派则认为:“我不需要知道每一根羽毛怎么画,我只需要知道这是一只鸟、它和哪类鸟更接近。”DINO 的目标不是还原像素,而是学习语义身份。这两者的核心差异体现在学到的特征上:MAE 的特征非常擅长“判断两幅图是否相似”这种像素级任务,但它的注意力图是弥散的、模糊的——被遮住的 patch 会让注意力涣散;而 DINO 的特征天然具备清晰的语义边界,注意力图能精准地勾勒出物体的轮廓。实验也证实了这一点:在 KNN 分类上,DINO 大幅领先 MAE;但在像素重建任务上,MAE 则是当之无愧的王者。MAE 的重建目标可以写成:

这场派别之争没有绝对的胜负:MAE 更擅长“精细还原”的世界,DINO 更懂“语义理解”的世界。但在构建通用视觉表示、服务下游语义任务这件事上,DINO 的观察派思路明显更胜一筹。师傅的总结一针见血:“画出一只完美无缺的假鸟,不代表你认识真鸟;但如果你能一眼认出它,你根本不需要把它画出来。”

LMAE=1N∑i=1N∥xi−x^i∥2\mathcal{L}_{\text{MAE}} = \frac{1}{N}\sum_{i=1}^{N} \| x_i - \hat{x}_i \|^2
  1. 逐像素误差$x_i - \hat{x}_i$ 是原图第 $i$ 个像素与重建结果的差值,差得越多损失越大。
    ∥xi−x^i∥2\lVert x_i - \hat{x}_i \rVert^2
  2. 平均所有像素$\frac{1}{N}\sum_{i=1}^{N}$ 对所有被遮住位置的像素误差求平均,得到一个整体的重建质量分数。
    1N∑i=1N(⋅)\frac{1}{N}\sum_{i=1}^{N}(\cdot)

通用视觉表示

DINOv2 的终极目标是成为“视觉领域的通用底座”——就像学会了“如何观察”本身,而不是学会某一项具体任务。这双“火眼金睛”练成之后,各行各业都可以直接来“借用”。下游任务使用 DINOv2 特征的方式非常简单:固定住预训练好的骨干网络,只训练一个轻量级的“任务适配头”。比如做图像分类就加一个线性分类层,做分割就加一个分割头,几乎不需要调整骨干网络的参数。

这带来了巨大的实际价值。在传统的监督学习范式下,每换一个新任务,就要重新收集标注数据、重新训练整个模型;而有了 DINOv2,很多任务只需要很少的标注样本就能取得好效果——医学影像、卫星遥感、工业质检这些标注稀缺的领域尤其受益。更深远的意义在于多模态:DINOv2 这类通用视觉表示可以直接充当“视觉编码器”,与文本编码器对齐后构建图文多模态模型。它为整个 AI 系统提供了一双“标准化的眼睛”,让模型团队不必从零开始训练视觉理解能力。

这背后有一点值得深思:为什么 DINOv2 的特征如此“百搭”?因为自监督训练目标迫使模型捕捉数据中最本质、最稳定的结构规律,而不是表面上与某个任务相关的偶然特征。学徒在野外数年的观察中,学会的不是“如何在考试中拿高分”,而是“鸟类的本质是什么样子的”——这种知识可以迁移到任何需要识别鸟类、甚至识别有生命的物体的场景中。当天傍晚,师傅看着已经独当一面的学徒,欣慰地说:“你已经不需要我了。你练出来的这双眼睛,够你走遍天下了。”

AI 应用场景

场景一:电商平台的“以图搜图”零样本检索

阶段:特征提取与检索(推理阶段无需微调)

解决的问题:传统电商以图搜图依赖人工标注的商品类目,新上架商品往往找不到对应标签。DINOv2 提取的视觉特征天然包含“同类物品相近”的语义结构,直接用特征向量做最近邻检索,无需为每类商品训练分类器。

具体案例:某二手交易平台接入 DINOv2 后,用户上传一件二手相机外套,系统在千万级商品库中通过特征检索,几毫秒内返回视觉风格相近的相机包、摄影配件等商品。即使商品类目从未出现在训练数据中,检索依然准确。平台无需维护庞大的标签体系,冷启动成本大幅降低。

场景二:医学影像的小样本辅助诊断

阶段:预训练 + 少量标注微调

解决的问题:医疗影像标注依赖专业医生,成本极高且样本稀少。DINOv2 自监督预训练的大模型已在海量自然图像上学会了通用结构感知能力,医学影像的形态结构同样受益于这套特征。只需几百张标注影像微调一个轻量分类头,就能达到接近全监督的效果。

具体案例:某研究团队用 DINOv2 特征 + 500 张眼底照片训练糖尿病视网膜病变分级模型,仅用全监督训练十分之一的标注量,就取得了 AUC 0.94 的成绩。医生将注意力图作为可视化解释,能直观看到模型聚焦在血管异常区域,增强了临床可信度。

场景三:多模态大模型的视觉骨干

阶段:多模态对齐(预训练基础底座)

解决的问题:训练图文多模态模型时,视觉编码器需要“看懂”图像的各种细粒度语义。从零开始训练视觉编码器需要海量图文对数据;直接使用 DINOv2 作为视觉骨干,可以让多模态模型将精力集中在“文本与图像的跨模态映射”而非“从零理解图像”。

具体案例:某团队构建中文视觉问答系统时,将 DINOv2 作为视觉编码器,冻结其参数后只训练“图像特征到文本语义空间”的映射层。实验表明,该方案相比随机初始化的视觉编码器,在 VQA 准确率上提升了 15 个百分点,同时训练成本下降了约 40%。DINOv2 提供了“先理解、再对齐”的稳健起点。

Python 演示

python118 行
import numpy as np # 迷你自蒸馏演示:用 numpy 实现"DINO 式"师徒学习# 模拟三类"鸟"的二维特征观测,学生向 EMA 教师学习 class MiniNet:    """极简两层 MLP,类比 ViT 的特征提取器"""     def __init__(self, in_dim=2, hidden=16, out=8, seed=0):        r = np.random.default_rng(seed)        self.W1 = r.normal(0, 0.3, (in_dim, hidden))        self.b1 = np.zeros(hidden)        self.W2 = r.normal(0, 0.3, (hidden, out))        self.b2 = np.zeros(out)     def forward(self, X):        h = np.maximum(X @ self.W1 + self.b1, 0.0)  # ReLU        return h @ self.W2 + self.b2     def assign(self, other):        """复制另一网络的参数"""        self.W1 = other.W1.copy()        self.b1 = other.b1.copy()        self.W2 = other.W2.copy()        self.b2 = other.b2.copy()     def ema_update(self, other, alpha):        """向另一网络做指数移动平均"""        self.W1 = alpha * self.W1 + (1 - alpha) * other.W1        self.b1 = alpha * self.b1 + (1 - alpha) * other.b1        self.W2 = alpha * self.W2 + (1 - alpha) * other.W2        self.b2 = alpha * self.b2 + (1 - alpha) * other.b2     def soft_feat(self, X, temp=0.07):        """L2 归一化后做 softmax,输出'身份判断分布'"""        z = self.forward(X)        z = z / np.linalg.norm(z, axis=1, keepdims=True) / temp        e = np.exp(z)        return e / e.sum(axis=1, keepdims=True)  def augment(X, noise=0.2):    """模拟图像增强:加高斯扰动,类比 DINO 的多视图裁剪"""    return X + np.random.default_rng().normal(0, noise, size=X.shape)  def distill_loss(s_out, t_out):    """教师分布作为软标签,学生分布贴近它(交叉熵)"""    return -(t_out * np.log(s_out + 1e-9)).sum(axis=1).mean()  def build_data():    """生成三类鸟的二维观察数据(类比图像特征)"""    r = np.random.default_rng(42)    centers = np.array([[2.0, 2.0], [6.0, 5.0], [1.0, 6.0]])    xs, ys = [], []    for c, center in enumerate(centers):        xs.append(r.normal(loc=center, scale=0.6, size=(50, 2)))        ys.append(np.full(50, c))    return np.vstack(xs), np.concatenate(ys)  def knn_accuracy(student, X_train, y_train, X_test, y_test, k=5):    """零样本能力评估:直接用特征做最近邻投票"""    feat_train = student.forward(X_train)    feat_test = student.forward(X_test)    correct = 0    for i, feat in enumerate(feat_test):        dist = np.linalg.norm(feat_train - feat, axis=1)        knn_idx = np.argsort(dist)[:k]        pred = np.bincount(y_train[knn_idx]).argmax()        if pred == y_test[i]:            correct += 1    return correct / len(y_test)  def main():    X_train, y_train = build_data()    X_test, y_test = build_data()     student = MiniNet(seed=0)   # 学徒:大步学习    teacher = MiniNet(seed=1)   # 师傅:稳定标杆    teacher.assign(student)     ema = 0.9    lr = 0.05    epochs = 60    for epoch in range(epochs):        for i in range(len(X_train)):            # 同一只鸟的两个增强视图            v1 = augment(X_train[i])            v2 = augment(X_train[i])            # 师傅和徒弟分别对两个视图做判断            with_teacher = teacher.soft_feat(v2[None, :])[0]            s_out = student.soft_feat(v1[None, :])[0]            # 蒸馏损失 + 简单梯度步            s_feat = student.forward(v1[None, :])            grad_out = (s_out - with_teacher)            grad_h = grad_out @ student.W2.T            grad_h[s_feat <= 0] = 0            grad_w2 = np.outer(s_feat, grad_out)            grad_w1 = np.outer(v1, grad_h)            student.W2 -= lr * grad_w2            student.b2 -= lr * grad_out            student.W1 -= lr * grad_w1            student.b1 -= lr * grad_h        # 师傅用 EMA 慢慢沉淀徒弟的经验        teacher.ema_update(student, ema)        if epoch % 10 == 0:            acc = knn_accuracy(teacher, X_train, y_train, X_test, y_test)            print(f"epoch {epoch:3d} | acc {acc:.3f}")     final_acc = knn_accuracy(teacher, X_train, y_train, X_test, y_test)    print(f"final KNN acc: {final_acc:.3f}")  if __name__ == "__main__":    main()
目的:用纯 NumPy 实现 DINO 的核心机制——学生网络从教师网络的软输出中学习、教师网络通过 EMA 指数移动平均稳定更新,并最终用教师特征做 KNN 零样本分类,直观展示自监督蒸馏训练的完整流程。
  1. student = MiniNet(seed=0)创建“学徒”网络,类比 DINO 中的学生分支,参数通过梯度下降快速更新。
  2. teacher.assign(student)用学生参数初始化“师傅”网络,类比 DINO 中教师网络以学生为起点。
  3. teacher.soft_feat(v2[None, :])[0]教师对第二个增强视图输出“软判断分布”,作为训练的软标签。
  4. s_out = student.soft_feat(v1[None, :])[0]学生对第一个视图输出判断,目标是逼近教师分布。
  5. grad_out = (s_out - with_teacher)简化的梯度信号:学生输出与教师输出的差异,差异越大更新越猛。
  6. teacher.ema_update(student, ema)教师参数向学生做指数移动平均,对应公式 $\theta_t \leftarrow \lambda \theta_t + (1-\lambda)\theta_s$。
  7. knn_accuracy(...)用 KNN 最近邻机制评估“零样本”识别能力:只靠特征相似度投票,不做任何额外训练。

常见误区

误区自监督学习既然“不需要标注”,那是不是随便给几张图就能训练出好模型?我看 DINOv2 效果不错,应该对数据要求不高吧。

正解自监督省掉的是“人工标注”,而不是“数据量”。DINOv2 用了 1.42 亿张图像做预训练,数据规模是它成功的第一要素。就像学徒认鸟,看三只鸟和看三万只鸟练出来的眼力天差地别。自监督只是解决了“谁来讲课”的问题,但“听课的量”一点也少不了。

为何常见:人们容易把“无需标注”误解为“无需数据”,实际上无标注数据的收集、清洗、去重同样需要大量工程投入。

误区教师网络既然叫“教师”,应该就是训练好后固定下来,只指导学生、自己不动。

正解DINO 的教师网络每时每刻都在更新,只是更新方式非常“稳”——它通过 EMA 从学生参数中缓慢吸收经验,每一步只移动一点点($\lambda$ 常取 0.996,即只采纳 0.4% 的学生新知识)。师傅不是不变的,而是“越老越精”。

为何常见:“教师”这个词让人联想到固定不变的名师,忽略了 DINO 中教师是随学生共同演化的动态标杆。

误区MAE 和 DINO 都是自监督预训练,一个学重建一个学对比,训练出来应该区别不大,随便挑一个用就行。

正解两者学到的特征性质截然不同。DINO 学的是语义身份(“这是什么鸟”),注意力图清晰聚焦物体部位;MAE 学的是像素结构(“这块缺了什么”),注意力图模糊弥散。在 KNN 分类、语义分割、深度估计等语义任务上,DINO 全面领先;但在像素级重建任务上 MAE 更强。选型必须看下游任务需求。

为何常见:初学者容易把“自监督”当成一个统一的标签,忽略了不同代理任务目标导致的特征语义差异。

误区DINOv2 能零样本分类,是不是意味着它不需要在目标数据集上训练,甚至不需要任何数据?

正解零样本指的是“不需要在目标任务上微调”,但模型在预训练阶段已经看过了海量数据,只不过这些数据没有标注。KNN 分类也依赖一个“记忆库”来提供参考样本。真正的零样本能力来自大规模预训练中沉淀的通用规律,而不是凭空来的。

为何常见:“零样本”这个词具有误导性,让人以为模型什么都没见过就能做分类,忽略了预训练数据规模的作用。

误区CLS token 是 ViT 用来做分类的,所以只有在分类任务中才有用,其他任务用不到。

正解CLS token 的本质是所有 patch 信息的“全局汇聚点”,它承载的是整张图的语义摘要,远不止分类头那么简单。DINO 的注意力图分析、DINOv2 的全局特征蒸馏、以及检索和分割任务的全局表示都用到了 CLS token 的特征。分类只是它的“顺便用途”。

为何常见:因为 ViT 通常接一个分类头来输出类别,初学者就把 CLS token 和分类任务绑定,忽略了它作为全局特征聚合器的本质作用。

面试题

Q1. DINO 中教师网络的参数是如何更新的?

单选题 · easy

  • A.直接通过梯度下降训练
  • B.用学生网络的 EMA 指数移动平均更新← 正确答案
  • C.用标注数据微调得到
  • D.固定不变从不更新

答案:B。DINO 的教师网络不直接参与梯度训练,而是通过对学生网络的参数做 EMA 缓慢更新($\theta_t \leftarrow \lambda\theta_t + (1-\lambda)\theta_s$)。选项 A 是普通学生网络的更新方式;C 与自监督前提矛盾;D 忽略了教师也在动态演化。

Q2. DINO 中“自蒸馏”的软标签来源于哪里?

单选题 · easy

  • A.人工标注的类别标签
  • B.教师网络对增强视图的输出分布← 正确答案
  • C.图像像素的重建结果
  • D.聚类算法生成的伪标签

答案:B。DINO 的蒸馏损失是让学生的输出逼近教师对同一图像另一增强视图的输出分布,教师输出即软标签。A 是监督学习;C 是 MAE 的目标;D 是某些聚类类自监督方法用到的策略,并非 DINO 的机制。

Q3. ViT 中的 CLS token 在 DINO 中承担什么角色?

单选题 · easy

  • A.只负责输出分类概率
  • B.汇总全局信息作为图像表征← 正确答案
  • C.存储图像的边缘像素值
  • D.替代所有 patch token 的位置编码

答案:B。CLS token 通过注意力机制聚合所有 patch 的信息,输出作为整张图的全局特征表示。它虽然可以接分类头,但本质是特征聚合器,选项 A 过于片面,C 和 D 描述的功能与 CLS token 无关。

Q4. DINO 中学生和教师分别处理什么样的输入?

单选题 · mid

  • A.相同图像的不同增强视图← 正确答案
  • B.完全相同的原图
  • C.教师看大图,学生看小图
  • D.教师看文本,学生看图像

答案:A。DINO 对同一张图做两个不同强度的增强,形成“师生两路输入”。学生通常处理更强的裁剪和扰动(更难的视图),教师处理相对完整的视图。B 缺少增强差异,C 的“大图小图”只是增强的一种形式而非全部,D 混入了多模态概念。

Q5. DINOv2 相较于 DINO 最大的改进在哪方面?

单选题 · mid

  • A.换用了 CNN 骨干网络
  • B.大规模数据 + 多目标蒸馏训练← 正确答案
  • C.引入了人工标注数据
  • D.放弃了自蒸馏机制

答案:B。DINOv2 的核心改进是构建了 1.42 亿张图的数据集并采用多目标蒸馏训练(全局 + patch 级)。A 错误,DINOv2 仍基于 ViT;C 违背自监督前提;D 错误,自蒸馏机制被保留并强化。

Q6. DINO 的蒸馏损失函数属于哪种类型?

单选题 · mid

  • A.均方误差损失
  • B.二值交叉熵损失
  • C.软标签交叉熵损失← 正确答案
  • D.三重损失

答案:C。DINO 让学生输出的概率分布去拟合教师输出的概率分布(软标签),使用交叉熵衡量两个分布的差异。A 是 MAE 的重建损失;B 用于二分类;D 是三元组对比损失,都不匹配 DINO 的蒸馏机制。

Q7. EMA 系数 $\lambda=0.996$ 的含义是什么?

单选题 · hard

  • A.教师参数每一步 99.6% 来自学生新参数
  • B.教师参数每一步保留 99.6% 的旧状态← 正确答案
  • C.学生每步更新 0.4% 的参数
  • D.教师每 1000 步才更新一次

答案:B。$\theta_t \leftarrow \lambda\theta_t + (1-\lambda)\theta_s$ 中 $\lambda=0.996$ 表示教师保留其旧参数的 99.6%,只吸收学生新参数的 0.4%。这使得教师演化极其缓慢、稳定。A 把新旧比例弄反了,C 描述的是学生更新的幅度而非 EMA 系数,D 混淆了“步数”与“比例”的概念。

Q8. 下列关于 DINO 与 MAE 的说法,正确的有哪些?

多选题 · mid

  • A.MAE 的目标是重建被遮住的像素← 正确答案
  • B.DINO 的目标是学习图像的语义身份← 正确答案
  • C.MAE 的注意力图通常比 DINO 更清晰
  • D.DINO 特征在语义任务上通常优于 MAE← 正确答案

答案:ABD。A 和 B 正确描述了两种方法的代理任务目标;D 正确,实验表明 DINO 在 KNN 分类、分割等语义任务上优于 MAE;C 错误,MAE 的注意力图因遮挡重建任务而弥散模糊,DINO 的注意力图反而更清晰聚焦。

Q9. DINO 如何防止“表示坍塌”(所有样本输出相同特征)?

多选题 · mid

  • A.教师网络使用 EMA 稳定更新← 正确答案
  • B.对教师输出做 centering 中心化← 正确答案
  • C.对教师输出做 softmax 锐化← 正确答案
  • D.使用更大的学习率

答案:ABC。DINO 防止坍塌的三板斧是:EMA 教师提供稳定标杆(A)、centering 防止某个类别主导(B)、温度锐化让分布更尖锐(C)。D 错误,加大学习率反而可能加剧训练不稳定和坍塌风险。

Q10. DINOv2 的通用视觉表示体现在哪些方面?

多选题 · hard

  • A.直接通过 KNN 分类做零样本预测← 正确答案
  • B.固定骨干只微调轻量任务头← 正确答案
  • C.可以作为多模态模型视觉编码器← 正确答案
  • D.只适用于 ImageNet 单一数据集

答案:ABC。DINOv2 的特征通用性体现在多个任务和多个数据集的复用能力:KNN 零样本(A)、少量微调适配下游(B)、作为多模态视觉骨干(C)。D 错误,通用表示恰恰意味着不限于单一数据集。