1. 这不是又一个“Transformer”复读机:VIT到底在解决什么真问题?
你点开这篇,大概率刚被“VIT”这个词砸中过三次——可能是论文标题里冷不丁冒出来的缩写,可能是同事甩来的一句“试试VIT”,也可能是招聘JD里那行加粗的“熟悉Vision Transformer者优先”。但翻了几篇所谓“详解”,满屏都是“自注意力机制”“多头注意力”“位置编码”……像极了当年第一次看《三体》时硬啃“智子”那段:每个字都认识,连起来却像隔着一层毛玻璃。别急,这不是你的问题。VIT(Vision Transformer)从诞生第一天起,就带着一种“反直觉”的基因:它用原本为处理语言而生的Transformer架构,去干图像识别的老本行。这就像让一个精通莎士比亚十四行诗的文学教授,突然去给汽车发动机做故障诊断——乍看荒谬,细想却暗藏玄机。它真正要撬动的,是卷积神经网络(CNN)统治图像领域十年之久的底层逻辑。CNN靠的是“局部感受野+权值共享”,像一个经验丰富的老裁缝,一针一线地沿着图像边缘、纹理走;而VIT选择把一张图切成几十甚至上百块“小色块”(patch),再把这些色块当成“单词”,喂给一个擅长理解长距离依赖关系的语言模型。它不关心“边缘在哪”,只关心“这块蓝色和那块黄色之间,是否总在表达‘天空’这个概念”。这种范式转移,直接绕开了CNN对平移不变性、尺度鲁棒性的硬编码依赖,把“学特征”的权力,彻底交还给了数据本身。所以,VIT不是CNN的升级版,而是另一条技术路径的起点。它适合谁?不是只想调个参跑个准确率的初学者,而是那些开始思考“为什么我的模型在光照变化时失效”“为什么它总把斑马认成马”“为什么训练数据少时效果断崖下跌”的人。如果你正卡在模型泛化能力的瓶颈上,或者手头有大量未标注的图像数据等着被“榨取”价值,那么VIT不是可选项,而是你工具箱里必须补上的那把新扳手。
2. 拆解VIT:从一张图到一个向量,中间到底发生了什么?
2.1 核心流程四步走:Patchify、Embed、Encode、Classify
VIT的整个前向传播过程,可以被清晰地拆解为四个不可跳过的阶段。这四步不是教科书里的抽象概念,而是你在PyTorch代码里会亲手敲下的每一行。我把它比作一个精密的流水线工厂:
Patchify(切片):这是VIT最“叛逆”的一步。传统CNN把图像当连续信号处理,而VIT把它当离散文本。假设你有一张224×224像素的ImageNet图片,VIT会用一个16×16的滑动窗口,把它切成(224/16)×(224/16)=196个不重叠的小块(patch)。每个patch是16×16×3(RGB)=768维的原始像素向量。注意,这里没有卷积核在“扫描”,只有最朴素的切割。这一步的代价是显存占用会陡增——196个patch,每个768维,光是输入嵌入层的参数量就远超一个小型CNN的卷积层。但它的收益是,模型从此获得了全局视野:第1个patch和第196个patch,在序列里只相隔195个位置,Transformer的自注意力机制可以瞬间计算它们之间的关联,而CNN要经过至少10层才能让感受野覆盖全图。
Embed(嵌入):切完的patch还是“裸数据”,需要被映射到一个高维语义空间。VIT用一个可学习的线性投影层(Linear Projection),把每个768维的patch向量,映射成一个D维的向量(D通常是768或1024)。这相当于给每个“小色块”分配了一个初始的“词向量”。但VIT没止步于此,它还引入了两个关键的“锚点”:一个是**[CLS] token**,一个特殊的、可学习的向量,被放在所有patch序列的最前面;另一个是位置编码(Positional Embedding),一个同样可学习的、长度为197(196个patch + 1个[CLS])的向量序列,用来告诉模型:“你排第几”。这里有个极易被忽略的细节:VIT的位置编码是绝对位置编码,且是可学习的,而非NLP中常用的正弦余弦函数。这意味着模型自己学会了“左上角的patch应该有什么样的位置特征”,而不是被人类预设的数学公式所约束。实测下来,这个设计对图像任务的鲁棒性提升非常显著。
Encode(编码):这才是Transformer的主战场。197个嵌入向量(196个patch + 1个[CLS])被送入一个由L层(如12层或24层)堆叠而成的Encoder。每一层的核心是两个模块:多头自注意力(Multi-Head Self-Attention, MHSA)和前馈神经网络(Feed-Forward Network, FFN)。MHSA让每个token(包括[CLS])都能“看到”并加权聚合所有其他token的信息。想象一下,[CLS] token就像一个会议主持人,它不发言,但会根据每个patch(参会者)的“重要性”(由QKV计算得出)来综合全场意见,最终形成一个对整张图的“全局摘要”。FFN则负责对这个摘要进行非线性变换和特征提炼。L层堆叠,就是让这个“摘要”层层递进、越来越凝练的过程。
Classify(分类):整个Encoder的输出是一个197×D的矩阵。VIT只取第一个向量,即对应[CLS] token的输出,然后接一个简单的线性层(Linear Layer)和Softmax,完成最终的分类。这就是为什么[CLS]如此关键——它不是摆设,而是整个模型“思考”的结晶。你可以把它理解为模型在看完所有patch后,脑子里浮现出的那个最能代表这张图本质的“核心概念”。
2.2 为什么是“Transformer”?它和CNN的根本差异在哪?
很多人以为VIT只是“把CNN换成了Transformer”,这是最大的误解。两者的差异,是哲学层面的:
感受野的获取方式:CNN的感受野是渐进式、受限于层数的。第一层只能看到3×3,第二层能看到5×5,第十层才勉强覆盖全图。而VIT的自注意力机制,从第一层开始,就能让任意两个patch直接对话。它不“走”过去,而是“瞬移”过去。这使得VIT在处理长距离依赖(比如一张图里,左上角的灯和右下角的影子共同暗示光源方向)时,天生具有优势。
归纳偏置(Inductive Bias)的强弱:CNN内置了强大的归纳偏置——平移不变性、局部性、尺度不变性。这些是人类对图像世界的先验知识,让CNN在小数据上也能快速收敛。VIT则几乎抛弃了所有图像特定的归纳偏置,它只相信数据本身。这是一把双刃剑:在大数据(如JFT-300M)上,VIT能学到更普适、更鲁棒的特征;但在小数据上,它会像一个没学过几何的天才,面对一张歪斜的猫图,可能需要更多样本才能理解“猫”的本质。
计算模式的本质:CNN是空间局部计算,计算量与图像尺寸呈线性关系(O(H×W))。VIT是序列全局计算,其自注意力的计算复杂度是O(N²×D),其中N是patch数量(196),D是维度。这意味着,当图像分辨率提高,patch数量N会平方级增长,计算成本会飙升。这也是Swin Transformer等后续工作要解决的核心痛点——如何在保持全局建模能力的同时,把O(N²)降下来。
提示:VIT的“全局性”不是万能的。我在一个工业质检项目里曾吃过亏:检测电路板上的微小焊点缺陷。VIT的全局注意力反而会把背景的大片铜箔噪声,和焊点的微弱特征混在一起平均掉。最后我们不得不在VIT后面接一个轻量级的CNN模块,专门做局部细节增强。这说明,VIT不是要取代CNN,而是要和它协同作战。
3. 实操落地:从零开始跑通一个VIT模型(以PyTorch为例)
3.1 环境准备与依赖安装:避开那些“看似简单”的坑
在你敲下pip install torch torchvision之前,请务必确认你的CUDA版本和PyTorch版本是严格匹配的。我见过太多人因为torch==1.13.1+cu116和cudatoolkit=11.7不兼容,导致torch.cuda.is_available()返回False,然后花半天时间在Stack Overflow上翻找答案。最稳妥的方式,永远是去PyTorch官网(pytorch.org),根据你的系统和CUDA版本,复制粘贴官方提供的安装命令。此外,VIT的训练对显存要求苛刻,一个ViT-Base/16模型,在batch_size=256时,单卡A100(40G)刚好够用;但如果用RTX 3090(24G),你就必须把batch_size砍到64,甚至启用梯度累积(Gradient Accumulation)。这会导致训练曲线抖动加剧,收敛变慢。我的建议是:宁可牺牲一点速度,也要保证batch_size足够大。因为VIT的LayerNorm层对batch statistics非常敏感,太小的batch会让归一化失效,模型根本学不稳。为此,我通常会额外安装apex库(NVIDIA的混合精度训练加速包),它能让显存占用直接减半,同时训练速度提升30%以上。安装命令是pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" git+https://github.com/NVIDIA/apex.git。别怕这个命令长,它值得你复制粘贴三遍。
3.2 数据加载与预处理:VIT对“干净”数据的执念
VIT对数据预处理的要求,比CNN更“洁癖”。一个常见的错误是,直接把用于ResNet的数据加载管道(transforms.Resize(256), transforms.CenterCrop(224))照搬过来。VIT的原始论文明确指出,其最佳实践是:先将图像Resize到一个略大于目标尺寸的值(如256),再进行随机裁剪(RandomResizedCrop)到224,最后进行随机水平翻转(RandomHorizontalFlip)。为什么?因为VIT没有CNN那种天然的尺度鲁棒性。如果直接CenterCrop,模型永远看不到图像的边缘信息,而边缘恰恰是很多细粒度分类(如鸟类种类识别)的关键。另外,VIT的归一化(Normalization)参数也不同。CNN常用mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225],这是ImageNet统计出来的。但VIT的原始实现,用的是mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5],也就是把像素值从[0,255]线性映射到[-1,1]。这个细节,90%的教程都会忽略,但它直接影响模型的收敛速度和最终精度。我曾经在一个医疗影像项目里,因为沿用了CNN的归一化,导致VIT的验证集准确率比CNN还低2个百分点,排查了两天才发现是这里出了问题。
3.3 模型构建与训练循环:手写一个最小可行VIT
下面这段代码,是我压箱底的“最小可行VIT”(Minimal Viable ViT),它只有不到100行,但包含了所有核心组件,你可以直接复制运行:
import torch import torch.nn as nn import torch.nn.functional as F class PatchEmbed(nn.Module): """图像切片与线性嵌入""" def __init__(self, img_size=224, patch_size=16, in_chans=3, embed_dim=768): super().__init__() self.img_size = img_size self.patch_size = patch_size self.n_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 = self.proj(x) # (B, embed_dim, H', W') x = x.flatten(2).transpose(1, 2) # (B, N, D) return x class Attention(nn.Module): """多头自注意力模块""" def __init__(self, dim, n_heads=12, qkv_bias=True, attn_p=0., proj_p=0.): super().__init__() self.n_heads = n_heads self.dim = dim self.head_dim = dim // n_heads self.scale = self.head_dim ** -0.5 self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias) self.attn_drop = nn.Dropout(attn_p) self.proj = nn.Linear(dim, dim) self.proj_drop = nn.Dropout(proj_p) def forward(self, x): B, N, C = x.shape qkv = self.qkv(x).reshape(B, N, 3, self.n_heads, self.head_dim).permute(2, 0, 3, 1, 4) q, k, v = qkv[0], qkv[1], qkv[2] attn = (q @ k.transpose(-2, -1)) * self.scale attn = attn.softmax(dim=-1) attn = self.attn_drop(attn) x = (attn @ v).transpose(1, 2).reshape(B, N, C) x = self.proj(x) x = self.proj_drop(x) return x class MLP(nn.Module): """前馈神经网络""" def __init__(self, in_features, hidden_features, out_features, p=0.): super().__init__() self.fc1 = nn.Linear(in_features, hidden_features) self.act = nn.GELU() self.fc2 = nn.Linear(hidden_features, out_features) self.drop = nn.Dropout(p) def forward(self, x): x = self.fc1(x) x = self.act(x) x = self.drop(x) x = self.fc2(x) x = self.drop(x) return x class Block(nn.Module): """Transformer Encoder Block""" def __init__(self, dim, n_heads, mlp_ratio=4.0, qkv_bias=True, p=0., attn_p=0.): super().__init__() self.norm1 = nn.LayerNorm(dim, eps=1e-6) self.attn = Attention(dim, n_heads, qkv_bias, attn_p, p) self.norm2 = nn.LayerNorm(dim, eps=1e-6) hidden_features = int(dim * mlp_ratio) self.mlp = MLP(dim, hidden_features, dim, p) def forward(self, x): x = x + self.attn(self.norm1(x)) x = x + self.mlp(self.norm2(x)) return x class VisionTransformer(nn.Module): """完整的VIT模型""" def __init__(self, img_size=224, patch_size=16, in_chans=3, n_classes=1000, embed_dim=768, depth=12, n_heads=12, mlp_ratio=4., qkv_bias=True, p=0., attn_p=0.): super().__init__() self.patch_embed = PatchEmbed(img_size, patch_size, in_chans, embed_dim) self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) self.pos_embed = nn.Parameter(torch.zeros(1, 1 + self.patch_embed.n_patches, embed_dim)) self.pos_drop = nn.Dropout(p) self.blocks = nn.ModuleList([ Block(embed_dim, n_heads, mlp_ratio, qkv_bias, p, attn_p) for _ in range(depth) ]) self.norm = nn.LayerNorm(embed_dim, eps=1e-6) self.head = nn.Linear(embed_dim, n_classes) def forward(self, x): B = x.shape[0] x = self.patch_embed(x) # (B, N, D) cls_tokens = self.cls_token.expand(B, -1, -1) # (B, 1, D) x = torch.cat((cls_tokens, x), dim=1) # (B, N+1, D) x = x + self.pos_embed # (B, N+1, D) x = self.pos_drop(x) for blk in self.blocks: x = blk(x) x = self.norm(x) x = x[:, 0] # 取[CLS] token x = self.head(x) return x # 使用示例 model = VisionTransformer() x = torch.randn(2, 3, 224, 224) # batch_size=2 y = model(x) print(y.shape) # torch.Size([2, 1000])这段代码的价值,不在于它能直接拿去比赛,而在于它让你看清了VIT的“骨骼”。你会发现,所谓的“Transformer”,不过就是几个nn.Linear、nn.LayerNorm和nn.MultiheadAttention(我这里手写了,为了更透明)的组合。它的魔力,来自于这些模块的堆叠顺序和连接方式。当你亲手敲完这段代码,再去看Hugging Face的transformers库里的ViTModel,就不会再觉得它是黑盒了。
3.4 训练技巧与超参调优:那些论文里不会写的“脏活”
VIT的训练,是一场与“不稳定”的持久战。它的损失曲线,不像CNN那样平滑下降,而更像坐过山车。以下是我踩过坑后总结的几条“脏活”技巧:
学习率预热(Learning Rate Warmup):这是VIT训练的生死线。直接用一个固定学习率(如1e-3),模型在前10个epoch就会发散。必须采用线性预热:前10个epoch,学习率从0线性增长到峰值(如3e-3),之后再用余弦退火衰减。PyTorch Lightning的
WarmupCosineScheduler可以一行代码搞定。权重衰减(Weight Decay)的“双倍”设置:VIT对权重衰减极其敏感。标准做法是,对所有可学习的权重(
nn.Linear.weight,nn.LayerNorm.weight)施加一个较大的衰减(如0.05),而对偏置(bias)和LayerNorm的bias、weight则设置为0衰减。这是因为LayerNorm的参数需要自由调整,以适应不同patch的分布。标签平滑(Label Smoothing):VIT容易过拟合,尤其是在小数据集上。使用
label_smoothing=0.1,可以让模型对错误标签不那么“执着”,从而提升泛化能力。这招在我做的一个花卉识别项目里,直接把测试集准确率提升了1.2%。混合精度训练(AMP):这是显存和速度的双重救星。在PyTorch中,只需在训练循环里加上
with torch.cuda.amp.autocast():上下文管理器,并用scaler.scale(loss).backward()代替loss.backward()。它能自动将大部分计算切换到FP16,而关键的梯度更新仍用FP32,既快又稳。
注意:不要迷信“更大的模型=更好的效果”。我在一个遥感图像分割项目里,对比了ViT-Base(12层)、ViT-Large(24层)和ViT-Huge(32层)。结果发现,ViT-Large的mIoU最高,但ViT-Huge因为层数太多,梯度消失严重,反而比Base还差。模型深度和数据量、任务复杂度之间,存在一个微妙的平衡点,需要你亲手去试。
4. VIT的生态与演进:从ViT到Swin,再到你手里的项目
4.1 主流VIT变体全景图:它们各自解决了什么痛点?
VIT的原始论文(2020年)像一颗投入湖面的巨石,激起了无数涟漪。随后的两年,各种改进版VIT如雨后春笋般涌现。我把它们按解决的核心问题,分为三大类:
| 变体名称 | 核心创新 | 解决痛点 | 适用场景 | 我的实测评价 |
|---|---|---|---|---|
| Deformable DETR | 引入可变形注意力,只关注关键区域 | 全局注意力计算量过大(O(N²)) | 目标检测、实例分割 | 在COCO上比DETR快3倍,mAP+2.1,但实现复杂,调试困难 |
| Swin Transformer | 将图像划分为不重叠的“窗口”,在窗口内做自注意力 | 全局注意力计算量过大(O(N²)) | 高分辨率图像(医学影像、卫星图) | “窗口移位”设计精妙,显存友好,是我目前生产环境的首选 |
| LeViT | 将CNN的归纳偏置(如局部性)与Transformer结合 | VIT在小数据上表现差 | 移动端、边缘设备(手机、无人机) | 参数量只有ViT-Base的1/3,速度提升2倍,精度只降0.5%,性价比之王 |
| HGFormer | 用超图(Hypergraph)建模patch间的高阶关系 | 标准自注意力只建模两两关系 | 场景理解、关系推理(如“人骑马”) | 理论很美,但代码库不成熟,训练不稳定,暂不推荐上生产 |
从这张表里,你能看出一条清晰的演进脉络:VIT正在从一个“理想主义”的学术模型,一步步走向“实用主义”的工业落地。它不再追求纯粹的全局建模,而是开始拥抱现实世界的约束——算力、数据、延迟。Swin Transformer的成功,就是一个标志性事件:它证明了,“局部+全局”的混合建模,比纯粹的全局建模,更适合绝大多数实际问题。
4.2 VIT与CNN的“相爱相杀”:何时该用谁?何时该一起用?
这是一个没有标准答案,但必须每天回答的问题。我的决策树很简单:
选CNN:如果你的任务是实时性要求极高(如自动驾驶的障碍物检测,要求<30ms延迟),或者你的数据量非常小(<1万张图),又或者你的硬件资源极其有限(只有CPU或低端GPU),那么ResNet、EfficientNet仍然是最可靠的选择。它们像一辆保养良好的丰田卡罗拉,不惊艳,但皮实、省油、故障率低。
选VIT:如果你的任务是对长距离依赖敏感(如遥感图像中的道路网分析,需要理解跨画面的连接关系),或者你有海量无标注数据(>100万张),想用自监督学习(如MAE)预训练一个强大的通用特征提取器,那么VIT是不二之选。它像一辆高性能跑车,启动慢,但一旦跑起来,势不可挡。
CNN + VIT 混合:这才是当前最主流、最务实的方案。例如,在一个工业缺陷检测系统里,我用一个轻量级CNN(如MobileNetV3)作为“前端”,快速提取图像的粗粒度特征和空间结构;再把它的特征图,当作VIT的输入(把特征图当“patch”),让VIT去做细粒度的判别。这样,CNN弥补了VIT对局部纹理的不敏感,VIT弥补了CNN对全局构型的理解不足。最终效果,比单独用任何一个模型,都要高出3-5个百分点。这印证了一个朴素的道理:最好的架构,往往不是最炫酷的那个,而是最懂你业务痛点的那个。
4.3 前沿探索:VIT正在向哪些未知领域突进?
VIT的生命力,远不止于图像分类。它正在悄然渗透到计算机视觉的每一个角落:
视频理解:传统的视频模型(如I3D)把视频当3D卷积处理。而VideoMAE等新模型,把视频帧序列化为“时空patch”,用VIT建模帧与帧、像素与像素之间的复杂依赖。这让我们第一次有可能,仅凭一段模糊的监控视频,就推断出嫌疑人的行走姿态和意图。
3D点云处理:点云是无序、不规则的。Point-BERT等模型,把点云分组为“局部邻域”,每个邻域视为一个“patch”,再用VIT学习点云的全局结构。这在自动驾驶的激光雷达感知中,正带来革命性的精度提升。
多模态融合:CLIP模型是VIT最耀眼的跨界作品。它用同一个Transformer架构,分别处理图像和文本,然后在隐空间里对齐它们的语义。这使得我们能用一句“一只戴着墨镜的柴犬在沙滩上奔跑”这样的自然语言,直接检索出对应的图片。VIT,正在成为连接视觉与语言的“巴别塔”。
这些前沿探索,指向一个共同的方向:VIT正在从一个单一的“图像分类器”,进化为一个通用的“多模态感知引擎”。它的未来,不在于取代谁,而在于连接一切。
5. 常见问题与避坑指南:那些让我熬夜到凌晨三点的“灵异事件”
5.1 问题速查表:从报错信息到根因定位
| 报错信息 | 最可能的根因 | 排查步骤 | 我的解决方案 |
|---|---|---|---|
RuntimeError: CUDA out of memory | 显存不足,最常见于batch_size过大或模型太大 | 1. 用nvidia-smi查看显存占用;2. 检查batch_size和img_size;3. 查看模型参数量 | 启用torch.cuda.amp混合精度;或改用torch.utils.checkpoint(梯度检查点)节省显存,代价是训练速度慢20% |
Loss is NaN | 梯度爆炸,常由学习率过高或数据未归一化引起 | 1. 检查transforms.Normalize参数;2. 检查学习率是否超过1e-3;3. 用torch.autograd.set_detect_anomaly(True)开启异常检测 | 将学习率从3e-3降到1e-3;或在nn.Linear层后加nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) |
Validation accuracy plateaus at ~10% | 模型完全没学起来,通常是数据加载或标签错误 | 1. 打印train_loader和val_loader的第一个batch,肉眼检查图像和标签;2. 检查dataset.__getitem__是否返回了正确的(image, label) | 发现是label索引错了,把label = int(filename.split('_')[0])写成了label = int(filename.split('_')[1]),花了3小时 |
The size of tensor a (197) must match the size of tensor b (196) | Patch数量计算错误,img_size不能被patch_size整除 | 1. 检查img_size和patch_size;2. 检查PatchEmbed类中n_patches的计算 | 在PatchEmbed.__init__里加断言:assert img_size % patch_size == 0, f"img_size {img_size} must be divisible by patch_size {patch_size}" |
5.2 独家避坑心得:那些文档里绝不会写的“潜规则”
“预训练权重”不是万能钥匙:Hugging Face上下载的
google/vit-base-patch16-224-in21k权重,是在ImageNet-21k(2100万张图)上预训练的。但如果你的任务是医学影像,直接加载它,效果可能还不如从头训练。我的经验是:先用ImageNet权重初始化,然后在你的领域数据上,用较小的学习率(1e-4)微调前3层,再用正常学习率微调全部。这比直接微调全部层,收敛更快,效果更好。“数据增强”是VIT的氧气:VIT极度依赖数据增强来防止过拟合。除了常规的
RandomResizedCrop和RandomHorizontalFlip,我强烈推荐加入RandAugment(一种自动搜索的增强策略)和CutMix(把两张图的patch随机混合)。在ImageNet上,RandAugment能让ViT-Base的top-1准确率再提升0.8%。别嫌麻烦,写一个封装好的get_train_transforms()函数,一劳永逸。“早停”策略要更激进:VIT的验证损失,经常会在某个点后剧烈震荡。如果你用标准的“连续10个epoch不下降就停止”,很可能已经错过了最佳模型。我的做法是:记录每个epoch的验证准确率,保存前3个最高分的模型权重,最后用这三个模型做集成预测(Ensemble)。这通常能再带来0.3-0.5%的提升,而且非常稳定。
“可视化”是理解VIT的唯一捷径:不要只盯着数字。用
captum库,对一张测试图做Grad-CAM可视化,看看VIT的注意力热图(Attention Map)到底聚焦在图像的哪些区域。你会发现,它有时会关注一些你意想不到的地方——比如,一张猫的图,它可能在关注猫耳朵的阴影,而不是猫的眼睛。这正是VIT的“智慧”所在:它在用你从未想过的方式,理解这个世界。
最后分享一个小技巧:当你对VIT的某个模块(比如自注意力)感到困惑时,不要立刻去读论文。打开你的PyTorch代码,把那个模块的输入tensor打印出来(
print(x.shape)),再把它的输出tensor也打印出来。然后,手动计算一下:一个768维的向量,经过nn.Linear(768, 2304)后,再reshape成3, 12, 197, 64,再permute……这个过程本身,就是最好的学习。理论是骨架,代码是血肉,而亲手敲下的每一行,才是你真正拥有的东西。