视觉 Transformer 与多模态模型

本文系统讲解视觉 Transformer 与多模态模型,回答 ViT 为什么需要大数据预训练、Swin 的窗口注意力解决什么、CLIP 的零样本分类怎么用等实战问题。覆盖自注意力复杂度、patch embedding、位置编码、DeiT、Swin、ConvNeXt 与图文多模态,并给出 timm 推理与 CLIP 零样本分类两段可运行代码,附模型对比表、权衡与常见坑。

引言

Transformer 从 NLP 走进视觉,带来两件事:一是 ViT 证明「纯注意力也能做视觉」,二是 CLIP 这类多模态模型让「零样本分类」与「图文检索」成为工程标配。理解 ViT 与 CLIP,是理解当下视觉系统的基础。

本文按「注意力 → ViT → Swin 与 ConvNeXt → 多模态」的顺序拆解,重点讲清 patch embedding、位置编码、窗口注意力、对比学习这些机制,并给出可直接运行的代码。

目录

  1. 自注意力回顾
  2. ViT
  3. DeiT 与训练技巧
  4. Swin
  5. ConvNeXt
  6. 多模态 CLIP
  7. BLIP-2 与 LLaVA
  8. 代码:timm 加载 ViT 与 Swin
  9. 代码:CLIP 零样本分类
  10. 模型对比表
  11. 自监督与大规模预训练
  12. 视觉基础模型与提示工程
  13. 多模态检索与向量数据库
  14. 端侧与部署
  15. 权衡取舍
  16. 常见坑清单
  17. 小结

1. 自注意力回顾

自注意力把输入序列的每个元素映射为查询 Q、键 K、值 V,用 Q 与 K 的相似度做加权,再对 V 加权求和:

Attention(Q, K, V) = softmax(Q K^T / sqrt(d)) V

多头注意力并行多组 QKV,捕捉不同子空间的关系。关键性质是「全局感受野」:任意两个位置都能直接交互,这是它与卷积最大的区别。代价是复杂度对序列长度 N 是 O(N²),序列越长越贵,这也是 ViT 处理高分辨率图像时的主要瓶颈。

2. ViT

2.1 patch embedding

ViT 把图像切成固定大小的 patch(如 16×16),每个 patch 展平后线性投影成一个向量,再当作序列输入 Transformer。224×224 的图切成 16×16 的 patch,得到 196 个 token,加上 class token 共 197 个。

2.2 class token 与位置编码

ViT 借鉴 BERT,在序列最前面加一个可学习的 class token,用它的输出做分类。由于注意力本身无序,必须加位置编码(position embedding)提供位置信息。ViT 用可学习的一维位置编码。

2.3 数据需求

ViT 缺少卷积的归纳偏置(局部性、平移不变性),因此在小数据集上表现不如 CNN,必须在超大规模数据(如 JFT-300M、ImageNet-21K)上预训练才能超越 CNN。ViT-B/16 约 8600 万参数,ImageNet-21K 预训练后在 ImageNet-1K 上约 84% Top-1。

3. DeiT 与训练技巧

DeiT(Data-efficient Image Transformer)证明「不用超大私有数据也能训好 ViT」,靠的是强数据增强与蒸馏。它引入蒸馏 token,让 ViT 向一个强 CNN 教师学习。DeiT-B 在 ImageNet-1K 上约 81.8% Top-1,把 ViT 的训练门槛大幅降低。

训练 ViT 的常见技巧:

  • 强增强:RandAugment、Mixup、Cutmix,弥补归纳偏置的缺失。
  • 随机深度(stochastic depth):训练时随机丢弃整个残差分支,缓解过拟合。
  • 层学习率衰减:浅层用更小学习率,保护预训练特征。
  • 优化器:AdamW 配合较长的 warmup,避免早期训练不稳。
  • 权重衰减与标签平滑:抑制过拟合,提升泛化。

4. Swin

Swin Transformer 用两个设计降低计算并引入层次:

  • 窗口注意力:把注意力限制在局部窗口内,复杂度从 O(N²) 降到线性,同时保持局部建模。
  • 移位窗口:相邻层之间移动窗口位置,让窗口间能跨窗交互,弥补局部性限制。
  • 层次结构:像 CNN 一样逐层下采样,输出多尺度特征,天然适配检测与分割。

Swin-B 约 8800 万参数,ImageNet-1K 上约 83.5% Top-1,是检测分割里最常用的 Transformer 骨干。

5. ConvNeXt

ConvNeXt 反过来把 Transformer 的设计经验搬回 CNN:大核 7×7 深度卷积、LayerNorm、GELU、更少的归一化与激活、倒瓶颈结构。在同等训练配方下,ConvNeXt 与 Swin 精度相当,但纯卷积结构在部署上更成熟、算子支持更好。ConvNeXt-B 约 8900 万参数,Top-1 约 83.8%。

结论:CNN 与 Transformer 之争在工程上已趋于融合,选型更多看部署生态与具体任务。

6. 多模态 CLIP

CLIP(Contrastive Language-Image Pre-training)用 4 亿图文对做对比学习:把图像编码器与文本编码器映射到同一空间,让匹配的图文对相似度更高、不匹配的更低。

它的工程价值在于零样本分类:把类别名写成 prompt(如 “a photo of a cat”),编码成文本向量,与图像向量比相似度,取最高者作为预测。无需训练即可分类新类别。CLIP 的 ViT-L/14 图像编码器约 3 亿参数,在 ImageNet 零样本上约 76% Top-1。

CLIP 也支撑图文检索、图像相似度、内容审核等任务。

7. BLIP-2 与 LLaVA

多模态大模型把视觉编码器与大语言模型连接:

  • BLIP-2:用 Q-Former 把图像特征压缩成少量 query token,喂给冻结的 LLM,实现图像问答与描述。
  • LLaVA:用简单的投影层把视觉特征映射到 LLM 的词嵌入空间,再用指令数据微调,实现对话式视觉理解。
  • SAM:用 prompt(点、框)驱动分割,是「视觉基础模型」的另一代表。

这类模型的工程形态是「视觉编码器加投影层加 LLM」,理解这个三段式结构,就理解了大部分多模态系统的骨架。

8. 代码:timm 加载 ViT 与 Swin

timm 提供统一接口加载各种预训练视觉模型,是工程上最省心的选择。

import torch
import timm
from PIL import Image
from timm.data import resolve_data_config, create_transform

def load_and_predict(model_name, image_path, topk=5):
    model = timm.create_model(model_name, pretrained=True).eval()
    config = resolve_data_config({}, model=model)
    transform = create_transform(**config)
    x = transform(Image.open(image_path).convert("RGB")).unsqueeze(0)
    with torch.inference_mode():
        probs = model(x).softmax(dim=1)[0]
    p, idx = probs.topk(topk)
    return model_name, [(int(i), round(float(v), 4)) for v, i in zip(p, idx)]

for name in ["vit_base_patch16_224", "swin_tiny_patch4_window7_224", "convnext_tiny"]:
    print(load_and_predict(name, "dog.jpg"))

注意 resolve_data_config 会自动读取模型配套的输入尺寸与归一化参数,避免手动配置出错。ViT 与 Swin 的输入尺寸和归一化常常不同,手动写很容易踩坑。

9. 代码:CLIP 零样本分类

open_clip 或 transformers 都能加载 CLIP,下面演示零样本分类。

import torch
import open_clip
from PIL import Image

model, _, preprocess = open_clip.create_model_and_transforms(
    "ViT-B-32", pretrained="laion2b_s34b_b79k"
)
model.eval()
tokenizer = open_clip.get_tokenizer("ViT-B-32")

labels = ["a photo of a cat", "a photo of a dog", "a photo of a car"]
text = tokenizer(labels)

image = preprocess(Image.open("unknown.jpg")).unsqueeze(0)
with torch.inference_mode():
    image_feat = model.encode_image(image)
    text_feat = model.encode_text(text)
    image_feat /= image_feat.norm(dim=-1, keepdim=True)
    text_feat /= text_feat.norm(dim=-1, keepdim=True)
    probs = (100.0 * image_feat @ text_feat.T).softmax(dim=-1)[0]

for label, p in zip(labels, probs.tolist()):
    print(f"{label:<25s} {p:.4f}")

prompt 的措辞会显著影响零样本精度,工程上常做 prompt ensemble:用多个模板(“a photo of a {}"、“a picture of a {}")取平均,能提升一两个点。

10. 模型对比表

模型类型参数量ImageNet Top-1特点
ResNet-50CNN25.6M76.1%部署成熟
ConvNeXt-BCNN89M83.8%现代化 CNN
ViT-B/16Transformer86M84.0%(21K 预训练)需大数据
DeiT-BTransformer86M81.8%仅 ImageNet 训练
Swin-BTransformer88M83.5%层次结构,检测分割友好
CLIP ViT-L/14多模态约 428M76.0% 零样本开放词汇

指标口径不同:ViT-B/16 的 84% 需要 ImageNet-21K 预训练,DeiT-B 只在 ImageNet-1K 训练,直接比较时要看清训练数据。

11. 自监督与大规模预训练

ViT 的崛起离不开自监督预训练,它让「无标注数据」也能训出强视觉表征。

  • MAE(Masked Autoencoder):随机遮住 75% 的 patch,只让编码器看剩下的,再让解码器重建被遮住的部分。重建任务迫使模型学习语义,MAE 预训练的 ViT 微调后精度很高。
  • DINO / DINOv2:自蒸馏,学生网络预测教师网络的输出,无需负样本,产出的特征在下游任务上泛化好。DINOv2 的 ViT-g 特征被广泛用于检索与分割。
  • SimCLR / MoCo:对比学习,同一图的不同增强视为正样本,其他图视为负样本。MoCo 用队列与动量编码器扩大负样本量。
import torch

def random_masking(x, mask_ratio=0.75):
    # x: (B, N, D) patch 序列,返回保留的 token 与索引
    B, N, D = x.shape
    len_keep = int(N * (1 - mask_ratio))
    noise = torch.rand(B, N)
    ids_shuffle = noise.argsort(dim=1)
    ids_keep = ids_shuffle[:, :len_keep]
    x_kept = torch.gather(x, 1, ids_keep.unsqueeze(-1).expand(-1, -1, D))
    return x_kept, ids_keep

tokens = torch.randn(2, 196, 768)
kept, idx = random_masking(tokens, 0.75)
print(kept.shape, idx.shape)   # (2, 49, 768) (2, 49)

自监督预训练的价值在于:工业场景往往有海量无标注图像,用 MAE 或 DINOv2 预训练再加少量标注微调,效果常优于直接 ImageNet 迁移。

12. 视觉基础模型与提示工程

「视觉基础模型」指那些在超大规模数据上训练、可零样本或少量样本迁移到新任务的模型,代表有 CLIP、DINOv2、SAM。它们的工程用法是「组合」:

  • CLIP 提供开放词汇的语义理解与图文对齐。
  • DINOv2 提供通用的稠密视觉特征,适合检索与匹配。
  • SAM 提供类别无关的精细分割。

提示工程对 CLIP 尤其重要。把类别名放进自然语言模板,比直接编码单词更准:

templates = [
    "a photo of a {}",
    "a picture of a {}",
    "a close-up photo of a {}",
    "a bad photo of a {}",
]

def build_prompts(class_names, templates):
    # 每个类别生成多个 prompt,编码后取平均
    prompts = []
    for name in class_names:
        prompts.append([t.format(name) for t in templates])
    return prompts

print(build_prompts(["cat", "dog"], templates)[0])

prompt ensemble 通常能带来一两个点的零样本提升,代价是文本编码次数乘以模板数。工程上可预先离线编码所有类别的文本向量并缓存。

13. 多模态检索与向量数据库

CLIP 的另一大用途是跨模态检索:用文本搜图,或用图搜图。做法是把图像与文本都编码成同维度向量,再在向量库里做近邻搜索。

import numpy as np

def cosine_topk(query, gallery, k=3):
    # query: (D,), gallery: (N, D),均已 L2 归一化
    sims = gallery @ query
    idx = np.argsort(-sims)[:k]
    return idx, sims[idx]

D, N = 512, 100
gallery = np.random.rand(N, D).astype(np.float32)
gallery /= np.linalg.norm(gallery, axis=1, keepdims=True)
query = np.random.rand(D).astype(np.float32)
query /= np.linalg.norm(query)

idx, scores = cosine_topk(query, gallery, k=3)
print("top3:", idx, np.round(scores, 4))

规模小时用暴力检索即可;上百万级用 FAISS 建索引(IVF、HNSW)。大规模检索可用 FAISS 建索引(IVF、HNSW),与检索增强系统的向量检索高度相通。

14. 端侧与部署

ViT 的注意力在端侧部署上有两个难点:算子在部分芯片上支持不完整、高分辨率下注意力显存大。工程手段有:

  • 用专为端侧设计的模型:MobileViT、EfficientFormer、MobileSAM,把注意力量化到 int8。
  • 用窗口注意力或线性注意力降低复杂度,避免全图 O(N²)。
  • 导出 ONNX 时确认注意力算子被正确转换,必要时改写为等价算子组合。
  • 用 TensorRT 或 NCNN 做图优化与算子融合,实测端侧延迟。
模型类型参数量端侧友好度
MobileViT-S混合约 5.6M高
EfficientFormer-L纯 Transformer约 31M中
MobileSAM分割约 10M高
ViT-B/16纯 Transformer86M低

端侧部署的通用手段与 CNN 一致,量化、剪枝、蒸馏的细节见模型压缩与端侧部署。

15. 权衡取舍

  • 数据需求:ViT 依赖大规模预训练,小数据集用 CNN 或 DeiT 更稳。
  • 计算复杂度:自注意力 O(N²),高分辨率图像代价大,Swin 的窗口注意力缓解了这点。
  • 部署生态:CNN 与 Swin 的算子支持更好,纯 ViT 的注意力算子在某些端侧芯片上支持有限。
  • 零样本与微调:CLIP 零样本方便但精度有限,有标注数据时微调仍更优。
  • 精度与显存:多模态大模型显存需求高,服务化要算好批大小。
  • 预训练成本与收益:自监督预训练成本高,只有数据规模足够大时才划算。
  • 窗口大小与感受野:Swin 窗口越小越快,但跨窗建模依赖移位,窗口过小损失全局信息。
  • 零样本与开放词汇:CLIP 零样本省标注,但细粒度分类仍不如有监督微调。

16. 常见坑清单

  • 位置编码尺寸不匹配:换输入分辨率时位置编码需要插值,直接加载会报错。
  • 小数据集直接训 ViT:缺少归纳偏置,过拟合严重,先用预训练权重。
  • 归一化参数不一致:ViT 与 CNN 的均值和方差常常不同,混用会掉点。
  • patch size 与分辨率不匹配:patch 16 需要分辨率为 16 的倍数,否则要 padding。
  • 注意力显存爆炸:高分辨率下注意力矩阵巨大,需用窗口注意力或梯度检查点。
  • CLIP 类别名写得太随意:prompt 措辞影响精度,用 ensemble 提升稳定性。
  • 忽略训练数据口径:拿 21K 预训练的 ViT 与 1K 训练的 CNN 比不公平。
  • 盲目替换骨干:检测任务换 Swin 后 FPN 配置也要相应调整。
  • 自监督预训练数据不匹配:用自然图像预训练的权重迁到医学图像,收益有限。
  • 向量未归一化:检索时忘记 L2 归一化,余弦相似度退化成点积,排序错乱。
  • 忽略文本编码缓存:每次请求都重编码类别文本,浪费算力。
  • 随机深度设为 0:深层 ViT 不设随机深度容易过拟合。
  • 学习率过大:ViT 对学习率敏感,过大会破坏预训练权重。

17. 小结

视觉 Transformer 的工程主线是:小数据用 CNN 或 DeiT,大数据用 ViT,检测分割用 Swin,开放词汇用 CLIP。理解四个关键机制:patch embedding 把图像变序列、位置编码补位置信息、窗口注意力降复杂度、对比学习连接图文。多模态大模型的结构可概括为「视觉编码器加投影层加大语言模型」,抓住这个骨架就能快速上手。选型时别忘了部署生态与训练数据口径这两个常被忽视的维度。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「计算机视觉」更多文章

  1. 检测与分割的评估指标
  2. 多模态视觉语言模型
  3. 3D 视觉:点云与深度估计