3、视觉Transformer (ViT) 基础:图像分块(Patch Embedding),Class Token,ViT在自动驾驶中的定位
好,咱们进入正题。这一章聊的是视觉Transformer,也就是ViT。
说实话,我第一次接触ViT的时候,心里是有点抵触的。你想啊,CNN那套东西我们用了这么多年,各种trick玩得飞起,突然来个纯Transformer做视觉,靠谱吗?
后来我在一个车道线检测项目里试了一下,嗯,真香。今天我就把ViT最核心的三个东西掰开揉碎讲清楚:图像分块、Class Token,以及它在自动驾驶里到底能干啥。
3.1 图像分块(Patch Embedding)—— 把图片变成序列
Transformer本来是为处理序列数据设计的,比如一句话。但图片是二维的,怎么办?
ViT的做法很粗暴:把图片切成一块一块的小方块。
举个例子,一张224x224的图片,切成16x16的patch。那总共就有(224/16)² = 196个patch。每个patch拉平成一个向量,再经过一个线性映射,就得到了我们想要的token。
核心公式(理解即可):
假设输入图片尺寸为 H×W×C,patch尺寸为 P×P。
那么 patch 数量 N = (H/P) × (W/P)
每个 patch 拉平后维度为 P²×C,经过线性映射得到 D 维向量。
最终输入序列形状为 N × D。
我在实际项目中踩过一个坑:patch size的选择。太小了,序列太长,计算量爆炸;太大了,细节丢失严重。我个人习惯在自动驾驶场景下用16x16或14x14,兼顾效率和精度。
# 伪代码,帮你理解
import torch
import torch.nn as nn
class PatchEmbed(nn.Module):
def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768):
super().__init__()
self.num_patches = (img_size // patch_size) ** 2
self.proj = nn.Conv2d(in_chans, embed_dim,
kernel_size=patch_size, stride=patch_size)
def forward(self, x):
# x: [B, 3, 224, 224]
x = self.proj(x) # [B, 768, 14, 14]
x = x.flatten(2) # [B, 768, 196]
x = x.transpose(1, 2) # [B, 196, 768]
return x
避坑指南:
我曾经直接用全连接层做patch embedding,结果显存直接爆了。后来发现用卷积实现更高效——卷积核大小等于patch size,步长也等于patch size,一步到位。
3.2 Class Token —— 那个特殊的“占位符”
ViT里有个挺有意思的设计:Class Token。
你想想看,CNN做分类时,最后用全局平均池化得到一个特征向量。但Transformer不一样,它输出的是序列。那怎么拿来做分类呢?
ViT的做法是:在输入序列前面额外加一个可学习的向量,叫Class Token。这个token不对应任何图像patch,它的任务就是“吸收”全局信息,最后用它来做分类。
为什么不用全局平均池化?
我个人的理解是:Class Token通过自注意力机制,能动态地关注到图像中最关键的区域。而全局平均池化是均匀地对待每个patch,不够灵活。在自动驾驶中,有时候车只占画面的一小部分,Class Token这种“注意力聚焦”的能力就很关键。
# Class Token 的初始化
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
# 在forward中拼接到patch tokens前面
cls_tokens = self.cls_token.expand(B, -1, -1) # [B, 1, 768]
x = torch.cat((cls_tokens, x), dim=1) # [B, 197, 768]
注意:
Class Token的位置编码也要单独处理。我见过有人忘了给Class Token加位置编码,结果模型怎么训都不收敛。其实ViT原论文里,Class Token的位置编码是跟其他patch一起学的,但实践中我建议显式地处理一下,避免踩坑。
3.3 ViT在自动驾驶中的定位
聊完基础,咱们说说ViT在自动驾驶里到底能干啥。
说实话,ViT不是来取代CNN的。它更像是一个“补充者”,专门解决CNN搞不定的问题。
| 任务 | CNN表现 | ViT优势 |
|---|---|---|
| 目标检测 | 小目标容易漏检 | 全局注意力,小目标也能抓住 |
| 语义分割 | 边界细节不够好 | 长距离依赖,分割更连贯 |
| BEV感知 | 需要复杂后处理 | 天然适合做视角转换 |
| 多模态融合 | 融合方式有限 | 注意力机制,融合更灵活 |
我去年做过一个项目,用ViT做多相机BEV融合。说实话,效果确实比CNN好。尤其是在遮挡场景下,ViT能通过注意力机制“看到”被遮挡的车辆,这是CNN很难做到的。
我的建议:
- 小模型用CNN,大模型用ViT。 如果你算力有限,老老实实用ResNet。
- ViT适合做骨干网络。 比如作为BEV感知的编码器,效果拔群。
- 混合架构是趋势。 比如CNN+Transformer,前面用CNN提取底层特征,后面用Transformer做全局推理。
3.4 小结
这一章我们聊了ViT的三个核心点:
- 图像分块:把图片切成patch,变成序列。注意patch size的选择。
- Class Token:一个特殊的可学习向量,用来做分类。别忘了处理它的位置编码。
- ViT的定位:不是取代CNN,而是补充。在自动驾驶中,ViT在全局感知、多模态融合方面有天然优势。
下一章我们会深入ViT在BEV感知中的具体应用,包括如何把2D特征映射到3D空间。到时候我会分享一个我实际跑过的代码框架,保证干货满满。
课后思考:
如果让你用ViT替换YOLO的骨干网络,你觉得哪些地方需要调整?欢迎在评论区留言讨论。