视觉编码器:patch


文档摘要

视觉编码器:patch 本节摘要:读像素的视觉模型需要像素的「分词器」。patch 嵌入就是那个分词器。把图像切成方块网格、每块展平、过一个线性层投影,再加 2D 位置信号让 transformer 知道每块在原图何处。transformer 吃向量序列;图像是三通道网格。每像素当 token 会炸序列长:224x224 RGB 是 150,528 token,12 层 transformer 注意力付不起;整图当一个大平向量丢掉局部性,注意力层无法恢复。前端编码器的工是把像素网格压成几百 token,每个概括一块方形区域。

视觉编码器:patch

本节摘要:读像素的视觉模型需要像素的「分词器」。patch 嵌入就是那个分词器。把图像切成方块网格、每块展平、过一个线性层投影,再加 2D 位置信号让 transformer 知道每块在原图何处。transformer 吃向量序列;图像是三通道网格。每像素当 token 会炸序列长:224x224 RGB 是 150,528 token,12 层 transformer 注意力付不起;整图当一个大平向量丢掉局部性,注意力层无法恢复。前端编码器的工是把像素网格压成几百 token,每个概括一块方形区域。224x224 切成 16x16 patch 产 14x14 共 196 patch,每 patch 展平成 (3,16,16)=768 像素值再经线性层映到模型隐维——transformer 看到 196 个隐维 token 加一个 CLS token。

对应原课程:Phase 19 · Lesson 58 · vision-encoder-patches(原英文 phases/19-capstone-projects/58-vision-encoder-patches/docs/en.md)。本节属「多模态/VLM」赛道第一节。

学习目标

阅读完本节,你应当能够:

  1. 把图像 token 化成定长 patch 嵌入序列。
  2. 实现基于 Conv2d 的 patch 投影,数学上等同 unfold 后 linear。
  3. 构建确定性 2D 正弦位置嵌入,使 token 序编码空间位置。
  4. 在合成夹具上验证 patch 数、嵌入形状、Conv2d/unfold 等价性。

一、问题与直觉

transformer 吃向量序列。图像是三通道网格。每像素当 token 炸序列长:224x224 RGB 是 150,528 token,12 层 transformer 付不起注意力。整图当一个大平向量丢局部性,注意力层无法恢复。前端编码器的工是把像素网格压成几百 token,每个概括一块方形区域。

patch 嵌入用一个线性投影解决。224x224 切 16x16 patch 产 14x14 共 196 patch。每 patch 从 (3,16,16)=768 像素值展平成一个向量,再线性层映到模型隐维。transformer 看到 196 个隐维(常 768)token 加 CLS token——这是网络其余部分能嚼的序列。

为什么 patch 而非像素

注意力对序列长是二次。196 token 序列每头每层花 196*196=38,416 分数;150,528 token 序列花 150,528*150,528=226 亿。patch 买 590,000 倍注意力算力缩减,单个 16x16 区域带够高层视觉任务的信号。代价是丢失单 patch 内的细粒度空间细节,故下游多模态栈常在需精确定位时跑第二高分辨率分支。

为什么线性投影就够

每 patch 当独立向量。投影学一组基:边缘检测器、颜色滤波器、简单纹理。单线性层小(768*768=589,824 参数,ViT-Base)且训得快。更深卷积 stem 存在(「混合」ViT),但平线性投影是标准,多数现代开源编码器用这精确形状发。

Conv2d 技巧

Conv2d(in_channels=3, out_channels=hidden, kernel_size=patch_size, stride=patch_size) 无 padding 给与 unfold 后 linear 相同数值,因每输出位把 patch 像素与一滤波器点积。卷积就是 patch 投影,多数生产代码库这样发,因 GPU 上更快、少一次 reshape。

位置嵌入

token 出投影不带序。2D 正弦嵌入给每 token 一个编码其 (row, col) 位置的固定信号。一半嵌入维用多频 sin/cos 编行位置,另一半编列位置。编码确定,可换分辨率不重训,且干净插值到训练未见过的网格。

组件 形状 参数
patch 投影(Conv2d) (hidden, 3, patch, patch) 3*P*P*hidden + hidden
位置嵌入(固定) (num_patches, hidden) 0(算的,非学的)
CLS token(学的) (1, hidden) hidden

ViT-Base/16 在 224 分辨率:投影 590,592 参数,CLS 768,正弦位置零。下一节在其上叠 12 层 transformer。

等价性作健全性检查

patch 步有两种拼法:Conv2d 投影与显式 unfold 后 linear。它们须对同权重产同输出。若不,unfold 数学错了,编码器其余建在沙上。本节测试练此等价。

二、从零实现

code/main.py 实现:

  • PatchEmbed:nn.ModuleConv2d 做 patch 投影。
  • sinusoidal_2d(grid_h, grid_w, dim):无状态函数建 2D 位置表。
  • VisionFrontEnd:组 patch 嵌入、CLS 前置、位置相加进一前向。
  • synthesize_image(seed):从 numpy.random 建确定性 224x224x3 夹具。
  • demo:跑一夹具图过前端,打输出形状、CLS token 范数、位置嵌入一行。
class PatchEmbed(nn.Module): def __init__(self, img_size=224, patch=16, in_ch=3, hidden=768): self.num_patches = (img_size // patch) ** 2 # 196 self.proj = nn.Conv2d(in_ch, hidden, kernel_size=patch, stride=patch) def forward(self, x): # x: (B,3,224,224) return self.proj(x).flatten(2).transpose(1, 2) # (B, 196, 768) class VisionFrontEnd(nn.Module): def __init__(self, img_size=224, patch=16, hidden=768): self.patch_embed = PatchEmbed(img_size, patch, 3, hidden) self.cls_token = nn.Parameter(torch.zeros(1, 1, hidden)) gh = gw = img_size // patch self.pos_embed = sinusoidal_2d(gh, gw, hidden) # 固定, 不学 def forward(self, x): tokens = self.patch_embed(x) # (B, 196, 768) cls = self.cls_token.expand(x.size(0), -1, -1) tokens = torch.cat([cls, tokens], dim=1) # (B, 197, 768) return tokens + self.pos_embed # 加 2D 正弦位置

code/test_main.py 覆盖:patch 数匹配 (image_size/patch_size)**2;输出形状匹配 (batch, num_patches+1, hidden);Conv2d 投影在小夹具上等同手动 unfold 后 linear;正弦位置表跨调用确定;CLS token 跨批维广播无泄漏。

设计要点:Conv2d 投影等价 unfold 后 linear,但 GPU 更快少一次 reshape,故生产用 Conv2d。正弦位置是固定算的非学的——这使换分辨率不重训(学习位置在固定分辨率赢,正弦在训练后换分辨率赢)。CLS token 是学前缀,无 patch 内容,通过跨每块注意力累积信息,末层成整图摘要供下游头投影。patch 投影罕是瓶颈,下游注意力层主导算力。

三、框架对比

同 patch 前端出现在每个现代视觉语言模型:CLIP ViT-L/14、SigLIP、DINOv2、Qwen-VL 族、InternVL 栈都从 Conv2d patch 投影 + 位置信号起。差异在下游(CLS vs 无 CLS 池化、register token、patch 大小 14 vs 16、经插值位置的动态分辨率)。本节前端是这些模型站的基底。timm 库(HuggingFace)提供 PatchEmbed 同实现,可加载预训权重。ConvNeXt 用 4 层卷积 stem 替单 Conv2d,但多数 ViT 保单投影。本节手写让你看清 patch 切、线性投影、2D 正弦位置——这些在用 timm 时是黑盒。

四、可复用产物

code/main.py + code/test_main.py。demo 在 CPU 上几秒跑完:224x224 夹具被 token 化成 (1, 197, 768),首 token 是 CLS,后 196 是 patch token,位置嵌入范数行内均匀(正弦签名)。PatchEmbedVisionFrontEnd 是第 57 节 ViT 的前端底座,可独立复用——任何「图像 token 化」场景。

五、练习

  1. 学习位置:把正弦位置换学习的 nn.Parameter,在微型合成分类任务上比首 epoch 损失(固定分辨率学习位置赢)。
  2. unfold 等价:把 Conv2d 换显式 nn.Unfold + nn.Linear,断言输出在浮点容差内匹配。
  3. 非方 patch:支持非方 patch(如 32x16 宽输入),验证位置表处理非方网格。
  4. 批画像:在批 1/8/64 画像 patch 步,确认它罕是瓶颈。
  5. 冻结特征器:把前端当冻结特征器在 4 类合成形状数据集上训,CLS 输出应线性可分。

本节要点回顾

  1. patch 是像素分词器:切方块网格、展平、线性投影,压几百 token。
  2. patch 非像素:注意力二次,196 token 比 150,528 像素省 59 万倍算力。
  3. Conv2d 等价 unfold+linear:GPU 更快少一次 reshape,生产用 Conv2d。
  4. 线性投影就够:学边缘/颜色/纹理基,小且训得快。
  5. 2D 正弦位置:固定算的,可换分辨率不重训,一半维编行一半编列。
  6. CLS token 是学前缀:无 patch 内容,跨块注意力累积成整图摘要。

下一节,我们做「ViT Transformer 编码器」——12 层 pre-LN transformer 块把 patch token 序列变成上下文 token 序列,CLS token 池整图特征。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U