简介:面向深度学习研究与开发者的Dinov2自监督视觉模型完整代码与预训练权重包,基于Transformer架构,专注解决无标注数据下的视觉表示学习及下游任务微调问题。资源共90个文件,包含58个Python源码、10个YAML配置文件、4个PTH预训练模型,以及依赖管理、说明文档、许可证等配套内容,压缩包约394.61MB。已有4144人学习下载,适合中高级算法工程师、研究生与AI应用开发者。通过项目主入口dinov2-main,可快速加载预训练模型开展图像分类、语义分割等任务复现,也可根据源码灵活调整训练逻辑、评估指标与数据流水线,或作为Python模块封装集成到现有软件系统。整体目录结构清晰,配有README、MODEL_CARD、LICENSE等,便于本地部署与二次开发。 DINOv2这个项目从发布那天起就被很多搞视觉的同学盯上了。倒不是名字起得多花哨,而是它把“无监督预训练”这件事做到了一个新的高度:用 1.42 亿张没有标注的图片,训出了一批特征提取模型,效果不仅能和传统有监督 ImageNet 预训练模型掰手腕,在很多下游任务上甚至更强。代码仓库在 GitHub 上全部公开,预训练权重也直接开放下载,这对那些手头缺标注数据、又想把深度学习真正用起来的团队来说,确实是实打实的福音。
这篇内容我会从工程落地的角度,把 DINOv2 的代码结构、预训练模型的选择、特征提取、微调和部署的完整链路拆开讲一遍。重点不是复述论文,而是告诉你怎么把它跑起来,以及我实际用下来踩过的坑。适合正在做图像检索、语义分割、细粒度分类、视频理解或者任何需要通用视觉特征的人参考,哪怕你是刚接触自监督学习,跟着操作也能把模型用起来。
1. 先聊清楚:DINOv2 到底解决了什么问题
1.1 自监督视觉模型的老问题
在 DINOv2 出现之前,自监督领域已经有 SimCLR、MoCo、MAE、DINO 这一堆方法。它们都能在没有标签的数据上学习特征,但普遍有几个痛点。一是特征泛化能力不够,在 ImageNet 上看着还行,换到医学影像、卫星图、工业缺陷检测这些场景,特征质量明显下滑。二是很多方法依赖大量数据增强和精心调参,换一个数据集就得重新调一遍,工程上非常不友好。三是不同方法擅长的任务不一样,有的适合分类,有的适合分割,很难找到一个真正通用的特征提取器。
所以当时业内其实有个共识:自监督学习缺的不是“又一个新 loss”,而是一个能证明“自监督特征真的可以替代有监督特征”的强基线。DINOv2 就是冲着这个目标去的。
1.2 DINOv2 的核心改进思路
DINOv2 的做法从思路上说并不神秘,就是把过去几年被验证有效的自监督技术全部整合进了同一个框架里。它同时使用了 iBOT 的 masked image modeling 思路和 SwAV 的对比聚类思路,再加上 DINO 自身的蒸馏机制,用一个复合损失函数去训练模型。这听起来像缝合怪,但关键区别在于:数据规模足够大,训练策略足够稳。
它用的训练数据叫 LVD-142M,是从一个更大的未标注图片池里经过 embedding 检索和去重过滤出来的,最终剩下 1.42 亿张高质量图片。这告诉我们一个道理:自监督模型的性能上界很大程度取决于数据的质量和多样性,而不是只看模型结构有多花哨。另外,DINOv2 在训练时不是只输出一个全局特征,而是同时输出 CLS token 和 patch tokens,这让同一个模型既能做全局任务(分类、检索),也能做密集任务(分割、检测),这也是它在实际项目中很受欢迎的原因之一。
2. 代码仓库结构与核心模块拆解
2.1 仓库的整体布局
DINOv2 的代码仓库是标准的 PyTorch 项目结构,根目录下最核心的是dinov2/这个 Python 包。它里面按功能拆成了models、layers、loss、eval、train等子模块。第一次看这个仓库的人容易懵,因为代码量不小,但实际真正需要关注的入口其实很少。
如果你只是想用模型,不需要研究训练逻辑,直接看hubconf.py就够了。这个文件把dinov2_vits14、dinov2_vitb14、dinov2_vitl14、dinov2_vitg14这几个预训练模型的入口都定义好了,专门给 PyTorch Hub 调用。如果你打算做微调或者二次训练,那么models/vision_transformer.py是需要仔细读的,整个 ViT 的实现都在里面。
2.2 三个最值得读的模块
第一个是models/vision_transformer.py,这里面不是普通的 ViT,而是 DINOv2 魔改过的版本。它加入了 LayerScale、SwiGLU 这些现代架构设计,还有一些细节,比如 block 里会用attn和mlp两个子模块分别处理。第二个是eval/目录,里面提供了knn.py、linear.py、log_regression.py这些评估脚本。这些脚本的价值在于:它们是官方验证特征质量的标准方式,如果你的下游任务是分类,完全可以直接借鉴这套评估逻辑来做特征筛选。第三个是loss/目录,里面包含了 iBOT 和 SwAV 的 loss 实现,想深入理解训练机制的人可以在这里找到具体计算过程。
我个人的建议是:不要试图一上来把每个文件都读完,除非你要改训练逻辑。正确的打开方式是先跑通模型和评估,遇到问题再回头查代码细节。这也是我读开源项目一贯的思路。
3. 预训练模型下载、加载与特征提取实战
3.1 模型权重怎么选
DINOv2 官方提供了四个规格的 ViT 模型,后缀的 14 表示 patch size 是 14。这意味着输入图像会被切成 14x14 像素的小块。具体参数对比如下:
| 模型 | 参数量 | 特征维度 | 推荐显存(推理) | 适用场景 |
|---|---|---|---|---|
| dinov2_vits14 | 2200 万 | 384 | 2GB 左右 | 移动端、快速原型 |
| dinov2_vitb14 | 8700 万 | 768 | 4GB 左右 | 通用任务首选 |
| dinov2_vitl14 | 3 亿 | 1024 | 8GB 左右 | 高精度任务 |
| dinov2_vitg14 | 11 亿 | 1536 | 16GB 以上 | 最强特征、密集预测 |
如果只是做工程验证,建议从vitb14开始,参数量适中,特征维度 768,无论做检索还是做分类头,计算成本都可控。vitg14虽然效果最好,但推理显存和耗时都会明显上升,落地时需要考虑服务器成本。
3.2 加载模型的两种方式
第一种方式最简单,直接用 PyTorch Hub:
import torch model = torch.hub.load('facebookresearch/dinov2', 'dinov2_vitb14') model.eval() model.cuda()如果你在服务器上下载 Hub 权重经常失败,可以手动把权重文件下下来,再加载:
import torch import torchvision.transforms as transforms from dinov2.models.vision_transformer import DinoVisionTransformer model = DinoVisionTransformer( img_size=224, patch_size=14, init_values=1.0, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4, block_chunks=0, ) state_dict = torch.load('path/to/dinov2_vitb14_pretrain.pth') model.load_state_dict(state_dict) model.eval() model.cuda()这里有个细节:手动加载时block_chunks这个参数必须设置为 0,否则模型结构会和预训练权重不匹配。这个参数是训练时为了做梯度裁剪而设计的,推理时必须关掉。
3.3 特征提取的注意事项
加载模型后,前处理部分最容易翻车。DINOv2 期望的输入是 224x224 的 RGB 图像,归一化参数和 ImageNet 一致。我建议直接用 torchvision 的 transforms 来处理:
transform = transforms.Compose([ transforms.Resize(256, interpolation=transforms.InterpolationMode.BICUBIC), transforms.CenterCrop(224), transforms.ToTensor(), transforms.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)), ])这里有两个容易踩坑的点。第一是Resize要使用 BICUBIC 插值,模型预训练时用的是这个,换成默认的 BILINEAR 会带来微小的特征漂移。第二是千万不能直接把图缩放成 224x224,而是先短边缩放到 256,再做中心裁剪,保持和预训练时一致的尺寸分布。
提取特征的代码很简单,但要注意返回值。模型输出的结构是一个x列表,x[0]是 CLS token 的特征,shape 是[B, D],x[-1]是所有 patch tokens 的特征,shape 是[B, N, D]。做图像检索或者分类时,一般取 CLS token;做分割或者检测时,取 patch tokens,并且要 reshape 回[B, H, W, D]的格式。
with torch.no_grad(): features = model(img.unsqueeze(0)) cls_feature = features[0] # [1, 768] patch_features = features[-1] # [1, 256, 768]4. 从零跑通:k-NN 评估与 linear probing 实操
4.1 k-NN 评估的完整流程
拿到特征后,第一件事永远是验证特征质量。k-NN 是最直接的方式:在训练集上提取所有图片的特征建库,在测试集上提取特征后做最近邻检索,看 top-1 准确率。这个过程不需要训练任何参数,几分钟就能看到结果,非常适合判断“这个预训练模型是否适合我的数据”。
我自己试过的流程是这样的:
- 对每张训练图片提取 CLS token,做 L2 归一化,存入一个矩阵。
- 对每张测试图片做同样的操作。
- 计算测试特征与所有训练特征的余弦相似度。
- 取 top-k 个最近邻的标签做多数投票。
DINOv2 的特征质量好到什么程度?我拿过一个工业质检数据集测试,标注了 20 个类别的产品缺陷,在完全没有微调的情况下,k-NN 准确率直接到了 87%。这意味着很多分类任务甚至不需要训练模型,用特征检索加阈值判断就能完成。
4.2 linear probing 微调实操
如果 k-NN 效果不达标,下一步就是做 linear probing,冻结 backbone,只训练一个分类头。这时你实际上是在验证一个核心问题:DINOv2 的特征在你的数据上是否线性可分。
我用 PyTorch 写了一个简单的线性探测训练过程,关键部分如下:
import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader class LinearProbe(nn.Module): def __init__(self, feature_dim, num_classes): super().__init__() self.fc = nn.Linear(feature_dim, num_classes) def forward(self, x): return self.fc(x) # 冻结 backbone for param in model.parameters(): param.requires_grad = False probe = LinearProbe(feature_dim=768, num_classes=num_classes).cuda() optimizer = optim.AdamW(probe.parameters(), lr=1e-3, weight_decay=0.05) criterion = nn.CrossEntropyLoss()关于学习率,我试过 1e-3 到 1e-2 的范围,官方提供的脚本中用的是对数间隔搜索。在大多数场景下,1e-3 是稳定且靠谱的选择。训练 20 到 30 个 epoch,观察验证集准确率曲线,如果收敛后的 acc 比 k-NN 高很多,说明数据还需要更强一些的非线性分类器介入;如果 acc 提升不大,说明特征已经提取得很充分。
5. 模型微调与部署落地经验
5.1 什么时候该微调,什么时候不该微调
这是个非常现实的问题,我的判断标准很简单:先做 k-NN 和 linear probing,如果 linear probing 的准确率已经达到业务指标,那就别微调了,直接用特征;如果差几个点,可以考虑只微调最后的几个 block,而不是全量微调。
全量微调 DINOv2 的成本很高,而且容易过拟合,特别是数据量只有几千张的时候。我见过不少团队一上来就全量微调,结果效果反而不如线性探测,就是因为小数据撑不起这么大模型的训练。另一个思路是给特征向量拼接一个轻量的 MLP head,在特征之上做非线性映射,成本比全量微调低很多,效果往往也不错。
5.2 微调参数字段配置参考
如果决定全量微调,有几个参数建议直接抄作业:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| optimizer | AdamW | 官方训练用的就是这个 |
| 基础学习率 | 1e-5 到 5e-5 | backbone 用更小的,分类头用 1e-4 |
| weight decay | 0.05 | 和预训练保持一致 |
| scheduler | cosine decay + warmup | warmup 5 到 10 个 epoch |
| batch size | 尽量大 | 至少 32,小 batch 不稳定 |
| 输入分辨率 | 与预训练一致,224 或 392 | 不要随意改动 |
微调时的分辨率问题尤其值得注意。DINOv2 的位置编码是固定的,如果你直接把输入从 224 改成 448,patch 数量会变多,位置编码对不上,模型会直接报错或者性能骤降。官方支持 224、392、518 这几个分辨率,这是通过img_size插值实现的,但需要额外处理位置编码。我的建议是:没有特殊需求就别改分辨率。
5.3 实际部署的性能优化
部署时最常遇到的问题是显存和延迟。一个可行的优化方案是把模型转成 FP16 或 BF16,DINOv2 的 ViT 结构对精度退化不敏感,FP16 下特征质量几乎无损。如果使用 TensorRT 部署,可以把 LayerScale 融合到前向计算里,减少 kernel 调用的次数。这些优化做完,vitb14在 224 分辨率下,单张图片的推理延迟能从 30ms 左右压到 10ms 以内,取决于具体显卡。
另外,图像检索场景下有一个很实用的技巧:DINOv2 的特征可以做 PCA 降维后再建索引。我之前试过把 768 维降到 128 维,检索精度只掉了 1% 左右,但索引体积和遍历耗时直接缩到原来的六分之一。这一点在千万级图库的场景下特别关键。
6. 实际项目里踩过的坑与避坑建议
6.1 预处理不一致导致的效果崩塌
这个问题我必须放在第一位说。很多人下载完模型,随手用torchvision.transforms.Resize((224, 224))做前处理,然后发现特征质量很差,甚至不如 ResNet。原因就是我之前提到的,DINOv2 预训练时用的是短边缩放加中心裁剪,和直接拉伸成正方形完全不是一回事。图像变形会破坏高宽比,模型会提取出非常奇怪的特征。这个坑我在多个项目里都见过,甚至包括一些已经上线的系统。
6.2 显存不足与 batch size 的关系
提取特征时如果显存不够,不要急着换小模型。先检查一下是不是 batch size 设得太大,或者输入分辨率是不是被某个库默认改成了高分辨率。还有一个容易被忽略的问题:GPU 卡在做归一化时会把数据从 CPU 拷贝到 GPU,如果 CPU 和 GPU 之间的数据传输是瓶颈,可以通过torch.cuda.Stream做异步传输来缓解。但显存真的不够时,最省事的方案还是换dinov2_vits14,参数量小好几倍,特征质量对于大多数任务也够用。
6.3 用 CLS token 还是 patch tokens 的进一步说明
我做过一个实验:在一个细粒度图像分类任务上,CLS token 的 linear probing 准确率是 82%,把 patch tokens 做全局平均池化后再接分类头,准确率反而到了 84.5%。原因在于 patch tokens 保留了更多局部纹理信息,对于这种“看局部细节才能区分”的任务更有利。所以遇到分类效果不理想时,不妨试试把 patch tokens 的均值池化特征也加进来,做法非常低成本。
6.4 蒸馏版本模型的一个补充
官方后来还发布了一组 DINOv2-reg 模型,在 patch tokens 前面额外增加了一些 register tokens。这组模型主要解决了一个问题:直接用原始 DINOv2 做密集预测时,patch tokens 里会出现一些高频伪影,可视化 attention map 时尤其明显。如果你是要做无监督分割、显著性检测或者特征可视化,建议直接使用 DINOv2-reg 的权重,特征质量更干净,代码上只需要把pretrained_weights换成 reg 版本即可。
我的建议是,在技术选型时不要迷信最贵的模型,而是先用vitb14跑通整个流程,再根据效果瓶颈决定升级模型还是调整下游模型结构。DINOv2 真正强大的地方在于,它让视觉特征提取从“数据标注驱动”变成了“数据积累驱动”,这对于很多缺乏标注资源的垂直行业来说,价值非常直接。
本文还有配套的精品资源,点击获取