VisionTransformer(二)—— 从Word Embedding到Patch Embedding:跨模态的向量化统一
1. Word Embedding与Patch Embedding的跨模态统一
第一次看到VisionTransformer(ViT)论文时,最让我惊讶的是作者竟然用处理文本的方式处理图像。这就像用菜刀切面包——工具不对口,但效果出奇地好。要理解这种跨模态的统一,我们得先拆解两个核心概念:NLP中的Word Embedding和CV中的Patch Embedding。
在NLP领域,Word Embedding就像给词语制作身份证。比如"猫"这个词,通过Embedding会变成[0.2, -0.5, 0.7]这样的向量。神奇的是,相似的词在向量空间里会靠得很近——"猫"和"狗"的距离,会比"猫"和"汽车"近得多。这种特性让模型能理解语义关系,比如"国王-男人+女人≈女王"这样的向量运算。
而在CV领域,Patch Embedding做的是类似的事情。假设我们有一张224x224的图片,把它切成16x16的小块(每个patch是14x14像素),然后把这些图像块拉平成向量。这就相当于把图像"分词"了,每个patch对应NLP中的一个word token。通过可学习的线性投影,这些图像patch被映射到与word embedding相同的向量空间。
2. 文本与图像的"分词"艺术
2.1 NLP的分词策略
在自然语言处理中,分词就像切香肠。英文相对简单,按空格和标点就能切分。比如句子"I love cats"可以分成["I", "love", "cats"]三个token。中文更复杂,"我喜欢猫"可能需要分成["我","喜欢","猫"]。
但简单的分词会遇到问题:
- 新词难以识别(比如网络用语"绝绝子")
- 歧义切分("南京市长江大桥"可以怎么切?)
- 稀有词处理
现代NLP系统通常采用Byte Pair Encoding(BPE)等子词切分方法,把单词拆成更小的语义单元。比如"unhappiness"可能被分成"un", "happy", "ness"。
2.2 CV的"分词"创新
图像没有显式的分隔符,所以ViT采用了简单粗暴的均匀网格划分。比如把224x224图像分成16x16的patch,每个patch就是14x14像素。这种划分方式有几个关键考虑:
- 计算效率:直接处理整张图像的计算量太大(224x224=50,176像素),分成196个patch(16x16)后,每个patch只有14x14=196像素
- 局部性保留:每个patch保留了局部空间信息
- 可扩展性:patch大小可以调整(如32x32或8x8)
我在实际项目中测试过不同patch size的影响。对于ImageNet分类,16x16是个不错的平衡点。但做细粒度分类(比如鸟类识别)时,用8x8的patch能保留更多细节信息。
3. 从离散符号到连续向量
3.1 Word Embedding的向量化
传统NLP使用one-hot编码,比如:
- "猫" = [1,0,0]
- "狗" = [0,1,0]
- "汽车" = [0,0,1]
这种表示有两个致命缺陷:
- 维度灾难(词汇表多大,向量就有多长)
- 无法表达词与词之间的关系
Word Embedding通过低维稠密向量解决了这两个问题。以Word2Vec为例,它通过预测上下文来学习词向量,使得语义相似的词在向量空间中距离相近。比如:
- "猫" ≈ [0.5, -0.2, 0.3]
- "狗" ≈ [0.6, -0.1, 0.2]
- "汽车" ≈ [-0.3, 0.8, 0.1]
3.2 Patch Embedding的实现技巧
ViT处理图像patch的方式与Word Embedding惊人地相似:
线性投影:用一个可学习的矩阵E将patch投影到D维空间
# 伪代码示例 patch_embedding = nn.Linear(patch_dim, embed_dim)位置编码:因为Transformer本身没有位置概念,需要额外添加位置信息
# 正弦位置编码 position = torch.arange(num_patches).unsqueeze(1) div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(position * div_term) pe[:, 1::2] = torch.cos(position * div_term)CLS Token:借鉴BERT的做法,添加一个特殊的分类token,用于最终分类
我在实现时发现,位置编码对模型性能影响很大。使用可学习的位置嵌入(learnable positional embedding)通常比固定正弦编码效果更好,尤其是在处理高分辨率图像时。
4. 语义保留的对比分析
4.1 NLP中的语义保留
好的Word Embedding应该能捕捉:
- 同义词相似性("快乐"和"愉快")
- 反义关系("大"和"小")
- 词性变化("run"和"running")
- 上下文相关含义("苹果"作为水果vs公司)
例如,通过GloVe或BERT学到的embedding,可以在向量空间中进行类比推理:
vec("国王") - vec("男人") + vec("女人") ≈ vec("女王")4.2 CV中的特征保留
Patch Embedding需要保留的是视觉特征:
- 局部纹理和边缘
- 颜色分布
- 几何形状
- 空间关系
实验表明,ViT的浅层embedding会学习到类似CNN的低级特征检测器(边缘、颜色斑点等),而深层embedding则能捕捉更高级的语义信息。
一个有趣的发现是:当把ViT的patch embedding可视化时,早期的层会显示出类似Gabor滤波器的模式,这与CNN的早期卷积核非常相似。这说明尽管架构不同,但模型都会首先学习检测低级视觉特征。
5. 统一范式的优势与挑战
5.1 跨模态统一的优势
- 架构简化:同一套Transformer架构可以处理文本和图像
- 多模态融合:更容易实现图文联合建模(如CLIP模型)
- 知识迁移:NLP领域的预训练技术可以迁移到CV
- 长程依赖:Self-attention能直接建模全局关系,不像CNN需要堆叠多层
在实际项目中,这种统一性带来了很大便利。比如我们可以用几乎相同的代码库处理文本分类和图像分类任务,只需要调整输入embedding部分。
5.2 面临的挑战
- 计算复杂度:Self-attention的O(n²)复杂度对高分辨率图像不友好
- 数据需求:ViT需要大量数据才能达到CNN的性能
- 局部性缺失:均匀划分patch可能破坏自然图像的局部结构
- 位置敏感:需要精心设计位置编码方案
在资源受限的场景下,我通常会采用混合架构——浅层用CNN提取局部特征,深层用Transformer建模全局关系。这种设计在计算效率和模型性能之间取得了不错的平衡。
6. 实现细节与优化技巧
6.1 高效实现Patch Embedding
原始ViT论文使用线性投影实现patch embedding,但在实际应用中,用卷积层实现更高效:
class PatchEmbed(nn.Module): def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768): super().__init__() self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size) def forward(self, x): x = self.proj(x) # [B, C, H, W] -> [B, D, H/P, W/P] x = x.flatten(2) # [B, D, H/P, W/P] -> [B, D, N] x = x.transpose(1, 2) # [B, D, N] -> [B, N, D] return x这种实现有几点优势:
- 直接利用卷积的优化实现
- 支持非方形patch(如16x32)
- 可以轻松扩展到重叠patch(通过调整stride)
6.2 位置编码的变体
除了标准的位置编码,还有几种值得尝试的方案:
相对位置编码:考虑patch之间的相对距离而非绝对位置
# 计算相对位置偏置 relative_position_bias = nn.Parameter( torch.zeros((2*window_size-1)*(2*window_size-1), num_heads))条件位置编码:根据图像内容动态生成位置信息
# 使用轻量网络生成位置编码 self.pos_embed_net = nn.Sequential( nn.Conv2d(in_chans, embed_dim//4, 3, padding=1), nn.GELU(), nn.Conv2d(embed_dim//4, embed_dim, 3, padding=1))层次化位置编码:为不同分辨率特征图设计不同的位置编码
在图像超分辨率任务中,我发现条件位置编码能显著提升模型性能,因为它能更好地适应不同图像内容的空间关系。
7. 前沿发展与实际应用
最近的研究在ViT的embedding方面有几个有趣的方向:
- 动态patch大小:根据图像内容自适应调整patch划分
- 非均匀采样:在重要区域使用更密集的patch
- 多尺度embedding:同时处理不同尺度的patch
- 混合模态embedding:统一文本、图像、音频的embedding空间
在实际部署ViT模型时,有几个实用建议:
- 对小数据集,先用CNN backbone提取特征再输入Transformer
- 对高分辨率图像,考虑分阶段下采样(如先2x2平均池化)
- 使用知识蒸馏(用CNN教师模型指导ViT训练)
- 对移动端部署,可以采用蒸馏后的紧凑ViT变体(如DeiT-Tiny)
我在一个医学图像分析项目中对比了不同embedding策略。发现对于X光片这类高分辨率图像,采用渐进式patch embedding(浅层用小patch,深层用大patch)能在保持精度的同时减少30%计算量。
