自监督视觉:SimCLR、DINO、MAE 本节摘要:标签是监督视觉的瓶颈。自监督预训练把它拿掉——从一亿张无标签图里学视觉特征,再在一万张带标签图上微调。本节追踪三大家族:对比学习(SimCLR,拉近正对、推开负样本,需大批)、师生蒸馏(DINO,学生预测教师输出、教师是学生的 EMA、靠居中+锐化防塌缩)、掩码重建(MAE,掩 75% patch 重建像素)。从零实现 InfoNCE 损失,讲清为什么批 512 行而批 32 不行、为什么 MAE 的 75% 掩码率不是随便定的(图像 patch 熵低,BERT 的 15% 不够)。读完本节,你能用 DINOv2/MAE 检查点做线性探测与零样本检索。 对应原课程:Phase 4 · Lesson 17 · (原英文 )。
本节摘要:标签是监督视觉的瓶颈。自监督预训练把它拿掉——从一亿张无标签图里学视觉特征,再在一万张带标签图上微调。本节追踪三大家族:对比学习(SimCLR,拉近正对、推开负样本,需大批)、师生蒸馏(DINO,学生预测教师输出、教师是学生的 EMA、靠居中+锐化防塌缩)、掩码重建(MAE,掩 75% patch 重建像素)。从零实现 InfoNCE 损失,讲清为什么批 512 行而批 32 不行、为什么 MAE 的 75% 掩码率不是随便定的(图像 patch 熵低,BERT 的 15% 不够)。读完本节,你能用 DINOv2/MAE 检查点做线性探测与零样本检索。
对应原课程:Phase 4 · Lesson 17 ·
self-supervised-vision(原英文phases/04-computer-vision/17-self-supervised-vision/docs/en.md)。
阅读完本节,你应当能够:
监督式 ImageNet 有 130 万张标注图,标注成本估计 1000 万美元。医疗和工业数据集更小、更贵。每个视觉团队都在问:能否在廉价无标签数据(YouTube 帧、网页爬取、监控、卫星扫掠)上预训练,再在小的带标签集上微调?
自监督学习就是答案。在 LAION 或 JFT 上训的现代自监督 ViT,微调后达到或超过监督式 ImageNet 精度,且比监督预训练迁移到下游任务(检测、分割、深度)更好。DINOv2(Meta,2023)和 MAE(Meta,2022)是当前可迁移视觉特征的生产默认。
概念上的转变是:前置任务(模型被训去做的事)不必是下游任务。要紧的是它逼模型学到有用特征。预测灰度图的颜色、旋转图片再让模型分类旋转角度、掩 patch 重建——都奏效过。能 scale 的三种是对比学习、师生蒸馏、掩码重建。
取一张图,套两次随机增强,得到两个视图。都过同一个编码器加一个投影头。最小化这样一个损失:「这两个嵌入应该接近」「这个嵌入应该远离批里所有其他图的嵌入」。
每批 2N 个视图里正对 (z_i, z_j) 的损失: L_ij = -log( exp(sim(z_i, z_j) / tau) / sum_{k in batch \ {i}} exp(sim(z_i, z_k) / tau) ) sim = 余弦相似度 tau = 温度(0.1 是标准)
这就是 InfoNCE 损失。它要求每个正对有许多负样本,所以批大小要紧——SimCLR 要 512~8192。MoCo 引入过去批的动量队列,把负样本数与批大小解耦。
两个同架构网络:学生和教师。教师是学生权重的指数移动平均(EMA)。两者都看图像的增强视图。学生的输出被训去匹配教师的——没有显式负样本。
loss = CE( student_output(view_1), teacher_output(view_2) ) + CE( student_output(view_2), teacher_output(view_1) ) teacher_weights = m * teacher_weights + (1 - m) * student_weights (m ≈ 0.996)
为什么不塌缩成「预测常数」:教师输出被居中(减去每维均值)和锐化(除以小温度)。居中防止某一维主导;锐化防止输出塌缩成均匀分布。
DINOv2 把 DINO 在 1.42 亿张精选图上 scale 上去,得到的特征是当前零样本视觉检索和密集预测的 SOTA。
掩掉 ViT 输入 75% 的 patch,只把可见的 25% 过编码器。一个小解码器接收编码器输出加上被掩位置上的掩码 token,被训去重建被掩 patch 的像素。
编码器: 可见 25% patch -> 特征 解码器: 特征 + 被掩位置的掩码 token -> 重建像素 损失: 仅在被掩 patch 上的重建像素与原像素 MSE
让 MAE 跑通的关键设计:
预训练后丢掉解码器,编码器就是特征提取器。
BERT 掩 15% token,MAE 掩 75%。差别在信息密度。
75% 高到简单空间外推解不了,编码器必须表示图像内容。
自监督预训练后,标准评估是线性探测:冻结编码器,在上面训一个单层线性分类器,用 ImageNet 标签,报 top-1 准确率。
线性探测是特征质量的纯度量;微调通常再加 2~5 个点,但混入了头重训的效应。
import torch import torchvision.transforms as T two_view_train = lambda: T.Compose([ T.RandomResizedCrop(96, scale=(0.2, 1.0)), T.RandomHorizontalFlip(), T.ColorJitter(0.4, 0.4, 0.4, 0.1), T.RandomGrayscale(p=0.2), T.ToTensor(), ]) class TwoViewDataset(torch.utils.data.Dataset): def __init__(self, base): self.base = base self.aug = two_view_train() def __len__(self): return len(self.base) def __getitem__(self, i): img, _ = self.base[i] v1 = self.aug(img) v2 = self.aug(img) return v1, v2
每个 __getitem__ 返回同一张图的两个增强视图,不需要标签。
import torch.nn.functional as F def info_nce(z1, z2, tau=0.1): """ z1, z2: (N, D) L2 归一化的配对视图嵌入 """ N, D = z1.shape z = torch.cat([z1, z2], dim=0) # (2N, D) sim = z @ z.T / tau # (2N, 2N) mask = torch.eye(2 * N, dtype=torch.bool, device=z.device) sim = sim.masked_fill(mask, float("-inf")) targets = torch.cat([torch.arange(N, 2 * N), torch.arange(0, N)]).to(z.device) return F.cross_entropy(sim, targets)
调用前先 L2 归一化嵌入。tau=0.1 是 SimCLR 默认;更低让损失更尖锐,需要更多负样本。
z1 = F.normalize(torch.randn(16, 32), dim=-1) z2 = z1.clone() loss_same = info_nce(z1, z2, tau=0.1).item() z2_random = F.normalize(torch.randn(16, 32), dim=-1) loss_random = info_nce(z1, z2_random, tau=0.1).item() print(f"配对相同时的 InfoNCE: {loss_same:.3f}") print(f"随机配对时的 InfoNCE: {loss_random:.3f}")
相同配对应给低损失(大批低温下接近 0)。随机配对应给 log(2N−1),16 对时约 log(31) ≈ 3.4。
def random_mask_indices(num_patches, mask_ratio=0.75, seed=0): g = torch.Generator().manual_seed(seed) n_keep = int(num_patches * (1 - mask_ratio)) perm = torch.randperm(num_patches, generator=g) visible = perm[:n_keep] masked = perm[n_keep:] return visible.sort().values, masked.sort().values num_patches = 196 visible, masked = random_mask_indices(num_patches, mask_ratio=0.75) print(f"可见: {len(visible)} / {num_patches}") print(f"掩码: {len(masked)} / {num_patches}")
简单、快、对给定种子确定性。真实 MAE 实现会批处理并保留每样本掩码。
DINOv2 是 2026 年的生产标准:
import torch from transformers import AutoImageProcessor, AutoModel processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base") model = AutoModel.from_pretrained("facebook/dinov2-base") model.eval() # 零样本检索的每图嵌入 with torch.no_grad(): inputs = processor(images=[pil_image], return_tensors="pt") outputs = model(**inputs) embedding = outputs.last_hidden_state[:, 0] # CLS token
得到的 768 维嵌入是现代图像检索、密集对应、零样本迁移流水线的主干。下游任务微调往往只需一个线性头。
图文嵌入用 SigLIP 或 OpenCLIP;MAE 风格微调用 timm 仓库的各种 MAE 检查点。
本节产出两个可复用文件(位于原课程 outputs/):
prompt-ssl-pretraining-picker.md:一个提示词——给定数据集大小、算力、下游任务,在 SimCLR / MAE / DINOv2 之间挑。skill-linear-probe-runner.md:一个技能——为任意冻结编码器 + 带标签数据集写出线性探测评估。tau in [0.05, 0.1, 0.2, 0.5] 对损失的图。下一节进入 CLIP 与开放词表——用图文对比学习让模型零样本认出训练时没见过的类别。