3.3 图像 Patch 化与位置编码 本节钻进 ViT 内部最关键的两个设计决策:patch 怎么切 与 位置怎么编。这两个看似细节的选择,决定了模型的能力上限、计算成本与泛化能力。 一、Patch 化的三个工程参数 把图像切成 patch 时,有三个核心参数: 参数一:Patch 大小 $P$ 每个 patch 的边长。常见选择: $P=32$:token 少(一张 224 图变成 49 个 patch),训练快,但精度上限低 $P=16$:标准选择(196 个 patch),精度与开销平衡 $P=14$:CLIP 等模型用,略多于 196(实际 256 个),精度更高 $P=8$ 或更小:高分辨率场景使用,token 数激增 直觉上,patch 越小,每个 patch
本节钻进 ViT 内部最关键的两个设计决策:patch 怎么切 与 位置怎么编。这两个看似细节的选择,决定了模型的能力上限、计算成本与泛化能力。
把图像切成 patch 时,有三个核心参数:
每个 patch 的边长。常见选择:
直觉上,patch 越小,每个 patch 携带的信息越细,但 token 数量平方级增长,开销爆炸。
标准 ViT 用 stride = patch 大小(不重叠)。但有些变体用更小 stride 让 patch 重叠,相当于在 patch 化阶段就引入了「平滑过渡」:
后续如 NaViT、SigLIP-2 等模型探索了变长 patch——同一张图的不同区域用不同 patch 大小,重要区域用小 patch、背景用大 patch,平衡精度与开销。
每个 P \times P \times C 的 patch 可以展平成 P^2 C 维向量。例如 16 \times 16 \times 3 = 768 维。这与隐藏维度 D 不一定相同,所以需要一个线性投影 P^2 C \to D。
工程上把这个投影与 patch 切分合并成一个卷积(如 3.2 节所述),是最高效的实现。
ViT 的 token 数 N 与图像面积成正比:
而 Self-Attention 的复杂度是 O(N^2),这意味着:
这就是为什么高分辨率 ViT 极其昂贵——把分辨率从 224 提到 1024,注意力开销变 421 倍。
多模态大模型在处理文档、图表、医学影像等需要细粒度的场景时,会面临这个矛盾。常见的解决方案:
| 方案 | 思路 | 代表 |
|---|---|---|
| 动态分辨率 | 把大图切成多个 224 子图分别编码 | Qwen-VL、LLaVA-NeXT |
| Token 压缩 | 用 Q-Former 等模块把 N 个 patch 压成 K 个 token | BLIP-2 |
| 局部注意力 | 限制注意力在局部窗口 | Swin Transformer |
| 金字塔结构 | 多尺度 patch | NaViT、SigLIP-2 |
回顾第2章:Self-Attention 对输入是置换不变的。如果不加位置编码,ViT 把 patch 顺序打乱后输出完全一样——这显然不对,因为图像的「左上」与「右下」是有语义差异的(天空通常在上方,地面在下方)。
位置编码的任务是给每个 patch 注入它在图像中的位置信息,让模型能区分「这块纹理在猫脸上」和「同样纹理在背景里」。
最朴素的方案——把 patch 按行扫描成一维序列(0, 1, 2, ..., 195),每个位置分配一个可学习向量:
听起来反直觉:图像是二维的,为什么一维编码够用?
实证答案是:Transformer 自己能从数据中学到二维结构。一维位置编码提供了「这是第几个 patch」的标识,至于「第几个 patch 对应图像哪个位置」,模型在训练中自动建立映射。
但一维编码有一个明显短板——分辨率外推性差。训练时见过 14×14 网格(196 个位置),推理时给 28×28(784 个位置)就用不了。
把行索引与列索引分别编码:
其中 E_\text{row} \in \mathbb{R}^{H/P \times D/2}、E_\text{col} \in \mathbb{R}^{W/P \times D/2}。
这种方式更契合图像的二维结构,但实验显示提升有限,所以标准 ViT 没有采用。
把文本 Transformer 的正弦编码推广到二维:
不增加可学习参数,但分辨率外推性比学习式略好。
不编码「patch 在第几行第几列」,而编码「patch A 相对 patch B 偏移多少」。这种编码天然支持变分辨率,是 Swin Transformer 的特色。
近期的视觉模型(如 ViT-RoPE)也尝试把 RoPE 引入视觉。把 patch 的二维坐标(行、列)映射到一个复合的旋转频率,让注意力分数只依赖 patch 之间的相对距离。
ViT 在序列最前面拼接一个特殊的可学习 token——[CLS]。它的工作方式是:
CLS 的物理意义是「专门用来做全局聚合的可学习槽位」。它不像 patch token 那样有具体内容,而是通过注意力机制主动收集它认为重要的 patch 信息。
不使用 CLS 也能聚合全局——把所有 patch token 的输出取平均(Global Average Pooling, GAP):
两种方式的取舍:
| 方式 | 优点 | 缺点 |
|---|---|---|
| CLS token | 可学习的注意力权重,能动态聚焦关键区域 | 多一个 token,需要训练学怎么聚合 |
| GAP | 无参数,简单 | 等权平均,无法突出关键 patch |
实证上两者差距不大,但 CLS 在多模态任务里更常用——因为它学到的聚合方式更适合「找出图中最重要的区域」这种语义任务。
在多模态大模型场景里,CLS token 通常被丢弃,只使用 N 个 patch token。因为 LLM 需要的是细粒度的局部信息(每个 patch 都对应图像的一小块),而不是一个压缩后的全局向量。
下面这张 SVG 把 patch 化与位置编码的可视化合到一起:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 360" font-family="sans-serif" font-size="12"> <!-- 原图 --> <rect x="20" y="40" width="160" height="160" fill="#e1f5ff" stroke="#333"/> <text x="100" y="30" text-anchor="middle" font-weight="bold">224×224 输入</text> <!-- patch 网格 --> <g transform="translate(220,40)"> <rect width="160" height="160" fill="#fff4e1" stroke="#333"/> <g stroke="#999" stroke-width="0.5"> <line x1="0" y1="40" x2="160" y2="40"/> <line x1="0" y1="80" x2="160" y2="80"/> <line x1="0" y1="120" x2="160" y2="120"/> <line x1="40" y1="0" x2="40" y2="160"/> <line x1="80" y1="0" x2="80" y2="160"/> <line x1="120" y1="0" x2="120" y2="160"/> </g> <text x="80" y="180" text-anchor="middle">4×4 patch 网格(示意)</text> </g> <!-- 展平后的序列 --> <g transform="translate(420,40)"> <text x="80" y="0" text-anchor="middle" font-weight="bold">展平为 token 序列</text> <rect x="0" y="20" width="30" height="120" fill="#ffe1e1" stroke="#333"/> <text x="15" y="85" text-anchor="middle" font-size="10">CLS</text> <g font-size="9"> <rect x="35" y="20" width="20" height="120" fill="#e8f5e1" stroke="#333"/><text x="45" y="85" text-anchor="middle">p1</text> <rect x="58" y="20" width="20" height="120" fill="#e8f5e1" stroke="#333"/><text x="68" y="85" text-anchor="middle">p2</text> <rect x="81" y="20" width="20" height="120" fill="#e8f5e1" stroke="#333"/><text x="91" y="85" text-anchor="middle">p3</text> <rect x="104" y="20" width="20" height="120" fill="#e8f5e1" stroke="#333"/><text x="114" y="85" text-anchor="middle">p4</text> <rect x="127" y="20" width="20" height="120" fill="#e8f5e1" stroke="#333"/><text x="137" y="85" text-anchor="middle">...</text> </g> <text x="80" y="160" text-anchor="middle">[CLS, p1, p2, ..., pN]</text> </g> <!-- 加位置编码 --> <g transform="translate(640,40)"> <text x="80" y="0" text-anchor="middle" font-weight="bold">+ 二维位置编码</text> <g font-size="9"> <rect x="20" y="20" width="120" height="20" fill="#f5e1ff" stroke="#333"/> <text x="80" y="35" text-anchor="middle">行 0: pos(0,0), pos(0,1), ...</text> <rect x="20" y="50" width="120" height="20" fill="#f5e1ff" stroke="#333"/> <text x="80" y="65" text-anchor="middle">行 1: pos(1,0), pos(1,1), ...</text> <rect x="20" y="80" width="120" height="20" fill="#f5e1ff" stroke="#333"/> <text x="80" y="95" text-anchor="middle">行 2: ...</text> <rect x="20" y="110" width="120" height="20" fill="#f5e1ff" stroke="#333"/> <text x="80" y="125" text-anchor="middle">行 3: ...</text> </g> <text x="80" y="160" text-anchor="middle">每个 patch 携带 (row, col) 信息</text> </g> <!-- 箭头 --> <line x1="180" y1="120" x2="215" y2="120" stroke="#333" marker-end="url(#arr2)"/> <line x1="380" y1="120" x2="415" y2="120" stroke="#333" marker-end="url(#arr2)"/> <line x1="600" y1="120" x2="635" y2="120" stroke="#333" marker-end="url(#arr2)"/> <defs> <marker id="arr2" markerWidth="8" markerHeight="8" refX="6" refY="4" orient="auto"> <path d="M0,0 L8,4 L0,8 z" fill="#333"/> </marker> </defs> </svg>
总结 ViT 在实际工程中几个被反复验证的经验:
Patch 化把图像变成 token 序列,位置编码注入二维空间信息。三个核心参数(patch 大小、步长、特征维度)决定了 token 数量与精度上限。CLS token 是可学习的全局聚合槽位,但多模态场景通常只用 patch token。高分辨率场景下 token 数量爆炸是核心痛点,催生了动态分辨率、token 压缩等多种工程方案。
下一节(3.4)我们看 CNN 与 Transformer 如何融合——这是另一条试图兼顾两者优势的技术路线。