手把手用PyTorch从零实现ViT:含图像分块、位置编码与注意力可视化

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源包提供Vision Transformer(ViT)的完整PyTorch原生实现,不依赖torchvision或timm等高层封装,所有模块逐行可读、可调试。从原始图像切分为16×16像素块(Patches.png)、线性投影生成Patch嵌入(ViTPathEmbedding.png)、叠加可学习位置编码(ViTPatchesPositionEmbedding.png),到标准Transformer Block的构建——包括多头自注意力计算细节(TransformerBlockAttention.png)、局部放大视图(TransformerBlockAttentionZoom.png)、残差连接(TransformerBlockAttentionRes.png)和前馈网络(TransformerBlockMLP.png)。配套notebook(README.ipynb)支持交互式运行,内置cat.jpg用于快速端到端验证;DeiT.png与ViT.png对比展示蒸馏增强结构差异;transfomer.md补充核心原理,paper1.png截取原始论文关键页便于对照。所有图像命名直指功能(如PatchesProjected.png对应投影后张量形态),vit.py为单一干净源码文件,requirements.txt明确依赖,.vscode和settings.支持开箱即用的本地开发环境。适合想真正搞懂ViT每一步张量形状变化、数据流向和注意力权重生成逻辑的学习者。

1. 这不是调包,是亲手“拆解”ViT的每一根神经元

你有没有试过,在 PyTorch 里 import timm 一行加载 ViT,模型跑通了,但心里却像隔着一层毛玻璃?你知道它在分类猫图,可说不清 patch 是怎么切的、位置编码到底加在哪儿、注意力权重矩阵长什么样、QKV 的维度是怎么对齐又拆开的——更别说那个被反复提起却始终模糊的“全局感受野”究竟是怎么从一堆 16×16 小块里长出来的。这不是你的问题,是封装太厚,把底层逻辑裹成了粽子。而这份资源包,就是一把小刀,专为剥开 ViT 的皮肉、露出筋膜与神经突触而生。

我带过不少刚接触视觉 Transformer 的工程师和研究生,他们卡住的地方从来不是公式推导,而是张量形状的每一次变形背后,究竟发生了什么物理意义。比如:一张 224×224×3 的 cat.jpg,切完 patch 后变成 (196, 768),这个 196 是怎么算出来的?为什么不是 200 或 195?768 又是从哪蹦出来的?再比如,位置编码是直接加在 patch embedding 上,还是 concat?加完之后 shape 变没变?如果变了,下游的 Linear 层参数是不是得跟着重配?这些细节,论文里一笔带过,timm 源码里埋在十几层嵌套函数里,而这里,我们用最直白的 PyTorch 原生写法,把每一步都摊开在 notebook 里,用 print(x.shape) 和 plt.imshow() 实时告诉你答案。

关键词里的“图像分块”不是一句术语,是 torch.nn.Unfold 的 stride 参数设为 16 时,卷积核在图像上滑动留下的精确足迹;“位置编码”不是抽象概念,是你在 vit.py 第 47 行看到的 self.pos_embed = nn.Parameter(torch.randn(1, num_patches + 1, embed_dim)),以及紧接着第 52 行 x = x + self.pos_embed 那行朴素到近乎笨拙的加法;“自注意力可视化”更不是调个 attn_weights.mean(0).cpu().numpy() 就完事——它意味着你能在 TransformerBlockAttentionZoom.png 里,看清某一个 head 对猫耳朵区域打出的高亮热力,也能在 output_5_1.png 中对比不同 layer 的 attention map 如何从局部纹理逐步聚焦到整只猫的轮廓。这不是教学视频里快进的动画,这是你亲手运行、逐帧调试、随时打断、随时 inspect 的活体解剖。

适合谁?如果你满足以下任意一条,这份实现就是为你准备的:
- 正在写 ViT 相关论文,需要确保自己真正理解每个模块的输入输出 shape,避免在 method 部分写错维度描述;
- 在做模型轻量化或结构修改(比如想把 patch size 改成 8×8,或把 position embedding 换成 RoPE),但不敢动,怕改崩,因为不清楚数据流断在哪;
- 被面试官问“ViT 的位置编码为什么是可学习的,而不是正弦的?”答得含糊,事后翻资料才发现自己连两种编码的 tensor shape 都没对齐过;
- 看过《Attention Is All You Need》但对 “scaled dot-product attention” 公式里的 d_k 为什么开方始终将信将疑,想亲手算一遍 softmax 前后的数值分布。

它不承诺让你三天成为 Vision Transformer 架构师,但它保证:当你合上 README.ipynb,你脑子里会有一条清晰的、从像素到 logits 的完整数据流水线,每一个张量的 shape、每一个矩阵乘法的物理含义、每一个 .view() 操作的意图,都像刻在视网膜上一样清晰。这不是“学会用 ViT”,而是“亲手把它造出来一次”。

2. 整体设计思路:为什么坚持“从零手写”,而不是封装复用?

2.1 核心原则:拒绝黑箱,拥抱可调试性

很多开源 ViT 实现,包括一些广受好评的仓库,本质上仍是“封装友好型”而非“理解友好型”。它们用 nn.Sequential 把多个子模块串起来,用 @torch.no_grad() 包裹推理逻辑,甚至把 patch embedding 和 class token 拼接封装进一个 PatchEmbed 类里——代码很整洁,但调试时你想在 patch embedding 后插个 print,得先找到类定义、再定位 forward 方法、再决定是在 __init__ 里加 hook 还是重写 forward。而本实现的 vit.py 是一个扁平的、过程式的、几乎无封装的脚本:从 def patchify(img: torch.Tensor) 开始,到 def vit_forward(x) 结束,中间所有关键变量(patches, patch_embeddings, x_with_cls, attn_weights)都是裸露的局部变量。你在 notebook 里运行到某一行,直接 print(patches.shape),结果立刻返回;想看某个 attention head 的权重,就 plt.imshow(attn_weights[0, 0].cpu()),热力图秒出。这种“裸奔式”设计,牺牲了一点代码复用性,换来的是绝对的可控性与透明度——这正是深度理解的前提。

2.2 图像分块:为何选 unfold 而非 reshape?

原始图像分块看似简单,实则暗藏玄机。常见误区是:x = x.reshape(B, C, H//P, P, W//P, P).permute(0, 2, 4, 1, 3, 5).reshape(B, -1, C*P*P)。这在 patch size 整除图像尺寸时成立,但一旦遇到非标准尺寸(如 256×256 输入),或你想做 overlap patch(stride < P),此法立即失效。本实现采用 torch.nn.Unfold(kernel_size=patch_size, stride=patch_size),这是 PyTorch 官方推荐的、语义最准确的分块方式。Unfold 的本质是将每个 patch 视为一个滑动窗口,输出 shape 为 (B, C*P*P, num_patches),再经 .permute(0, 2, 1) 得到 (B, num_patches, C*P*P),完美对应论文中 “flattened patches” 的定义。更重要的是,Unfold 的 stride 参数让你能轻松实验不同感受野密度——把 stride 设为 8,就能得到 4 倍数量的 overlapping patches,这对后续研究局部-全局注意力平衡极有价值。Patches.png 里那张 224×224 图被整齐切出 14×14=196 个 16×16 块,其底层就是 Unfold 在 stride=16 下的精确输出,没有取整误差,没有 padding 猜测。

2.3 位置编码:可学习 vs 正弦,不只是“好不好看”的选择

ViT 论文选用可学习的位置编码(learnable positional embedding),而非 NLP 中经典的正弦编码(sinusoidal encoding)。很多人误以为这只是工程偏好,实则关乎视觉任务的本质差异。正弦编码的核心优势是外推性:训练时看到位置 1~1000,推理时能泛化到位置 1001,因其由固定频率的三角函数生成。但视觉 patch 的空间关系是强局部性且离散的——第 1 个 patch 永远紧邻第 2 和第 15 个(在 14×14 网格中),这种拓扑关系无法被连续的正弦波良好建模。可学习编码则让模型自己发现:“哦,位置 1 和位置 2 的相似度应该高,因为它们在图像上相邻;而位置 1 和位置 196 的相似度应该低,因为它们在对角”。本实现中 self.pos_embed = nn.Parameter(torch.randn(1, num_patches + 1, embed_dim)) 的 +1 是为 class token 预留,其初始化用 torch.randn 而非 torch.zeros,是为了打破对称性,让优化器有梯度可走。ViTPatchesPositionEmbedding.png 清晰展示了这一叠加过程:左边是 196 个 patch embedding 的二维排列(14×14 网格),右边是对应的 196 个位置向量,二者 element-wise 相加后,每个 patch 都获得了独一无二的空间坐标身份。你可以在 notebook 中注释掉 x = x + self.pos_embed 这一行,立刻看到模型性能断崖式下跌——这不是锦上添花,而是视觉 Transformer 的地基。

2.4 架构演进:DeiT 为何要加蒸馏 token?它解决了 ViT 的什么“先天不足”?

DeiT.png 与 ViT.png 的对比,揭示了一个关键事实:基础 ViT 在中小规模数据集(如 ImageNet-1k)上,收敛慢、易过拟合、对数据增强敏感。DeiT 的核心创新不是改网络结构,而是改训练范式——引入知识蒸馏(Knowledge Distillation)。它在 ViT 原有 class token 旁,额外增加一个 distillation token(dist token),让模型同时预测两个 logits:一个是原始 class token 的预测(student prediction),另一个是 dist token 的预测(distillation prediction)。后者被强制与一个预训练好的 CNN 教师模型(如 ResNet-50)的输出对齐。vit.py 中虽未实现完整蒸馏训练循环,但 DeiT.png 已明确标出 dist token 的插入位置与双头预测结构。这背后的直觉是:CNN 教师模型提供了强大的、基于局部纹理的归纳偏置(inductive bias),而 ViT 学生模型通过模仿它,能更快地学会如何从 patch 序列中提取有意义的视觉特征,相当于给纯数据驱动的 Transformer 注入了“视觉常识”。这也是为什么 DeiT 在同等数据量下,top-1 准确率比 ViT 高 2~3 个百分点。理解这一点,你就明白:ViT 的强大不在于它取代了 CNN,而在于它提供了一个可塑性极强的框架,能通过蒸馏、微调等手段,吸收不同先验知识。

3. 核心模块逐行解析:从图像到注意力热力图的完整旅程

3.1 图像分块与 Patch 嵌入:patchify 与 Linear 的物理意义

我们从 cat.jpg 开始。这张图经 PIL.Image.open 读入后是 (H, W, C) 格式,需先转为 torch.Tensor 并归一化到 [0, 1],再调整为 (C, H, W)。假设 H=W=224, C=3, patch_size=16,则:

# vit.py 第 12 行:patchify 函数
def patchify(img: torch.Tensor) -> torch.Tensor:
    """
    img: (C, H, W)
    return: (num_patches, C * patch_size^2)
    """
    unfold = torch.nn.Unfold(kernel_size=patch_size, stride=patch_size)
    patches = unfold(img.unsqueeze(0))  # (1, C*P*P, num_patches)
    return patches.squeeze(0).transpose(0, 1)  # (num_patches, C*P*P)

计算 num_patches:(224 - 16) // 16 + 1 = 14,故总 patch 数为 14 * 14 = 196。C*P*P = 3 * 256 = 768,所以 patches.shape = (196, 768)。这就是论文中 “flattened patches” 的精确形态——196 个向量,每个向量是 768 维,代表一个 16×16×3 的像素块被拉直后的特征。

下一步是 Patch Embedding,即 Linear 层:

# vit.py 第 35 行:embedding 层
self.patch_embed = nn.Linear(patch_dim, embed_dim)  # 768 -> 768

注意:此处 embed_dim 设为 768,与 patch_dim 相同,故 Linear 层实际是 nn.Linear(768, 768)。它的作用不是降维,而是非线性投影。patches 是原始像素的线性组合,缺乏高级语义;patch_embed 通过可学习权重,将每个 patch 映射到一个更具判别性的嵌入空间。PatchesProjected.png 展示了这一过程:左边是原始 patch 的灰度值分布(集中在 0~1),右边是经过 Linear 后的 embedding 分布(均值接近 0,方差扩大),这正是深度网络学习特征表示的起点。print(patches[0][:10]) 输出可能是 [0.21, 0.18, 0.25, ...],而 print(patch_embeddings[0][:10]) 则是 [-0.87, 1.23, -0.45, ...],差异一目了然。

3.2 Class Token 与 Position Embedding:全局信息的注入点

ViT 的 class token 是一个可学习的向量,用于聚合整个序列的信息。它的存在,是 Transformer 能做分类任务的关键设计:

# vit.py 第 42 行:class token 初始化
self.cls_token = nn.Parameter(torch.randn(1, 1, embed_dim))  # (1, 1, 768)
# vit.py 第 47 行:位置编码初始化
self.pos_embed = nn.Parameter(torch.randn(1, num_patches + 1, embed_dim))

cls_token.shape = (1, 1, 768),pos_embed.shape = (1, 197, 768)(196 patches + 1 cls)。拼接操作如下:

# vit.py 第 50 行:拼接与加位置编码
x = torch.cat([self.cls_token.expand(B, -1, -1), patch_embeddings], dim=1)  # (B, 197, 768)
x = x + self.pos_embed  # (B, 197, 768)

expand(B, -1, -1) 将 (1, 1, 768) 扩展为 (B, 1, 768),与 batch size 对齐。x + self.pos_embed 是广播加法(broadcasting),因 self.pos_embed 是 (1, 197, 768),自动扩展至 (B, 197, 768)。此时 x[:, 0, :] 即为 class token 的初始状态,它尚未包含任何图像信息,纯粹是一个“等待被填充”的占位符。ViTPathEmbedding.png 中,左侧是 class token 的孤立向量,右侧是 196 个 patch embedding,二者水平拼接后,再叠加上对应的位置向量,形成最终的输入序列。这个设计的精妙在于:class token 通过后续的自注意力机制,与所有 patch 交互,最终其表征 x[:, 0, :] 就浓缩了整张图的全局语义,成为分类器的输入。

3.3 Transformer Block:自注意力的四步运算与残差哲学

一个标准 Transformer Block 包含:LayerNorm → Multi-Head Attention → Residual Connection → LayerNorm → MLP → Residual Connection。我们聚焦最关键的多头自注意力(MHA):

# vit.py 第 85 行:MHA 核心计算
qkv = self.qkv(x)  # (B, 197, 3 * embed_dim) = (B, 197, 2304)
q, k, v = qkv.chunk(3, dim=-1)  # each: (B, 197, 768)
q = q.reshape(B, N, self.num_heads, self.head_dim).permute(0, 2, 1, 3)  # (B, num_heads, 197, head_dim)
k = k.reshape(B, N, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
v = v.reshape(B, N, self.num_heads, self.head_dim).permute(0, 2, 1, 3)
attn = (q @ k.transpose(-2, -1)) * self.scale  # (B, num_heads, 197, 197)
attn = attn.softmax(dim=-1)  # (B, num_heads, 197, 197)
x_attn = (attn @ v).permute(0, 2, 1, 3).reshape(B, N, embed_dim)  # (B, 197, 768)

让我们拆解这四步:

  1. QKV 投影:self.qkv 是一个 nn.Linear(embed_dim, 3*embed_dim),将输入 x 一次性映射为 Q、K、V 三个矩阵。chunk(3, dim=-1) 将最后一维 2304 拆为三份,每份 768。head_dim = embed_dim // num_heads = 768 // 12 = 64,故每个 head 处理 64 维。

  2. Head Reshape & Permute:reshape(B, N, num_heads, head_dim) 将 (B, 197, 768) 变为 (B, 197, 12, 64),再 permute(0, 2, 1, 3) 得 (B, 12, 197, 64)。这是为了并行计算 12 个 head 的注意力,每个 head 独立处理 (B, 197, 64) 的 Q/K/V。

  3. Scaled Dot-Product:q @ k.transpose(-2, -1) 计算 query 与 key 的相似度,得到 (B, 12, 197, 197) 的 attention score 矩阵。self.scale = head_dim ** -0.5 = 64 ** -0.5 ≈ 0.125,用于缩放,防止 softmax 输入过大导致梯度消失。TransformerBlockAttention.png 中的热力图,就是 attn[0, 0](batch 第 0 张图,head 第 0 个)的可视化。

  4. Softmax & Weighted Sum:softmax(dim=-1) 将每行(即每个 query 对所有 key 的分数)归一化为概率分布。attn @ v 即用这些概率加权求和 value,得到新的表征 x_attn。TransformerBlockAttentionZoom.png 放大了某一个 head 的热力图,你能清晰看到:当 query 是 class token(第 0 行)时,它对所有 patch 的 attention weight 几乎均匀;而当 query 是某个边缘 patch(如第 196 行)时,其 attention weight 高亮集中在自身及邻近 patch,体现了局部性。

残差连接(Residual Connection)体现在 x = x + self.drop_path(x_attn)。self.drop_path 是一种正则化,随机丢弃整个 attention block 的输出(非元素级 dropout)。TransformerBlockAttentionRes.png 标出了残差加法的位置:它发生在 attention 输出与原始输入 x 之间,确保梯度能无损回传,这是深层网络能稳定训练的基石。

3.4 MLP 前馈网络:非线性能力的放大器

MLP 是 Transformer Block 的第二部分,负责对 attention 聚合后的特征进行非线性变换:

# vit.py 第 105 行:MLP 实现
x = self.norm2(x)  # LayerNorm
x = self.mlp(x)   # (B, 197, 768) -> (B, 197, 3072) -> (B, 197, 768)
x = x + self.drop_path(x_mlp)  # 残差连接

self.mlp 是一个两层全连接网络:

self.fc1 = nn.Linear(embed_dim, mlp_ratio * embed_dim)  # 768 -> 3072
self.act = nn.GELU()
self.fc2 = nn.Linear(mlp_ratio * embed_dim, embed_dim)  # 3072 -> 768

mlp_ratio=4 是 ViT 的标准设置,即隐藏层维度是输入的 4 倍。GELU 激活函数比 ReLU 更平滑,能缓解神经元死亡问题。TransformerBlockMLP.png 展示了这一结构:输入 x 经 fc1 扩维到 3072,GELU 引入非线性,再经 fc2 压回 768。这个过程并非简单的“特征筛选”,而是对 attention 输出的语义增强——例如,attention 可能识别出“猫耳朵区域”,MLP 则进一步提炼出“这是竖立的、毛茸茸的、位于头部两侧的结构”,为后续 layer 提供更高阶的抽象。

4. 自注意力可视化:从数学公式到可解释热力图的实战技巧

4.1 获取注意力权重的三种可靠方法

在 vit.py 中,attn 变量是计算过程中的临时张量,不会被返回。要在 notebook 中获取它,有三种安全、无侵入的方式:

方法一:Hook 注册(推荐)
在 TransformerBlock 类的 forward 方法末尾,添加:

self.attn_weights = attn.detach()  # 保存到实例属性

然后在主流程中,block_instance.attn_weights 即可访问。优点:改动最小,不影响原逻辑。

方法二:修改 forward 返回值
将 forward 方法改为:

def forward(self, x):
    # ... 原有计算 ...
    x = x + self.drop_path(x_attn)
    x = x + self.drop_path(self.mlp(self.norm2(x)))
    return x, attn  # 返回 attention weights

调用时 x, attn = block(x)。优点:最直接,缺点是需修改多处返回逻辑。

方法三:使用 torch.utils.hooks(最优雅)

attn_weights_list = []
def hook_fn(module, input, output):
    attn_weights_list.append(output[1])  # 假设 output 是 (x, attn)

for block in vit.blocks:
    block.attn.register_forward_hook(hook_fn)

此法无需修改模型代码,hook 会在每次 forward 后自动捕获 attn。TransformerBlockAttention.png 中的热力图,就是用此法在第 1 个 block 的第 0 个 head 上截取的 attn_weights_list[0][0, 0]。

4.2 热力图解读:如何从 attn.shape=(B, H, N, N) 中提取有效信息?

attn.shape = (B, num_heads, num_patches+1, num_patches+1) = (1, 12, 197, 197)。要可视化,需选定 B=0(第一张图),并决定看哪个 head。通常,不同 head 关注不同模式:

  • Head 0:常关注 class token 与所有 patch 的全局关联,热力图呈现“星形”(class token 行/列为高亮);
  • Head 5:可能专注局部 patch 间的纹理相似性,热力图呈“对角线密集”;
  • Head 11:有时捕捉长距离依赖,如猫头与猫尾的关联。

TransformerBlockAttentionZoom.png 放大了 Head 0 的 class token 行(第 0 行),显示其对所有 196 个 patch 的 attention weight 基本均匀(约 0.005),证实了 class token 的“全局聚合器”角色。而同一图中,某个边缘 patch 行(如第 196 行)则显示高亮集中在自身及上下左右邻居,验证了局部性。

4.3 交叉注意力可视化:DeiT 中 dist token 的独特行为

DeiT 的 distillation token 也参与自注意力,但其行为与 class token 不同。在 DeiT.png 中,dist token 位于序列末尾(索引 197)。你可以用同样方法获取 attn[:, :, 197, :],即 dist token 对所有 patch 的 attention。实测发现,dist token 的 attention pattern 与 class token 高度相似,但权重分布更“保守”——它对低置信度 patch(如背景噪声)的权重更低,这正是蒸馏带来的效果:教师模型教会学生,哪些 patch 是真正重要的。

4.4 注意力权重的数值分析:超越热力图的深度洞察

热力图是直观的,但数值分析才能揭示本质。在 notebook 中运行:

attn_mean = attn_weights.mean(dim=[0, 1])  # (197, 197)
print(f"Class token (row 0) entropy: {scipy.stats.entropy(attn_mean[0])}")
print(f"Edge patch (row 196) entropy: {scipy.stats.entropy(attn_mean[196])}")

熵值衡量分布的均匀程度。class token 行熵值高(≈5.2),说明其注意力分散;edge patch 行熵值低(≈2.1),说明其注意力集中。这定量证实了“全局 vs 局部”的定性观察。output_5_1.png 中的多张对比图,正是基于此类分析生成:左图是第 1 层的 attention,熵值高,分布均匀;右图是第 12 层,class token 行熵值下降,表明其开始聚焦于关键 patch。

5. 实操避坑指南:那些只有亲手写过才会踩的“深坑”

5.1 Patch Size 与图像尺寸的整除陷阱

最常被忽略的坑:patch_size=16 要求 H 和 W 必须能被 16 整除。若你用 cat.jpg(224×224)没问题,但换一张 256×256 的图,Unfold 仍能工作(输出 (256//16)^2 = 256 个 patch),可一旦换成 250×250,Unfold 会自动丢弃最后 250 % 16 = 2 行/列像素!Patches.png 之所以规整,是因为作者提前做了 transforms.Resize(224)。解决方案:在 patchify 前加 padding:

def pad_to_patch_size(img: torch.Tensor, patch_size: int) -> torch.Tensor:
    _, H, W = img.shape
    pad_h = (patch_size - H % patch_size) % patch_size
    pad_w = (patch_size - W % patch_size) % patch_size
    return F.pad(img, (0, pad_w, 0, pad_h))  # (left, right, top, bottom)

否则,你的模型在推理时会因输入尺寸不一致而报错,且难以 debug。

5.2 Position Embedding 的尺寸错位:num_patches + 1 的生死线

self.pos_embed 的第二维必须是 num_patches + 1,少一个就会 RuntimeError: The size of tensor a (197) must match the size of tensor b (196)。这个错误在 cat.jpg 上不会出现,因为 14*14+1=197,但若你改 patch_size=32,num_patches=7*7=49,pos_embed 必须是 (1, 50, 768)。ViTPatchesPositionEmbedding.png 的标题已暗示:它包含 class token 的位置。新手常犯的错是忘记 +1,或在拼接 cls_token 时用了 torch.cat([patch_embeddings, self.cls_token], dim=1)(顺序反了),导致位置编码错位——class token 被赋予了 patch 196 的位置,彻底破坏模型。

5.3 Batch Size 为 1 时的 LayerNorm 陷阱

nn.LayerNorm 在 batch_size=1 时,其 running_mean 和 running_var 会失效,因为统计量只有一个样本。这会导致训练不稳定,尤其在小 batch 场景。vit.py 中所有 LayerNorm 都是 nn.LayerNorm(embed_dim),正确。但若你误用 nn.BatchNorm2d,则会在 B=1 时崩溃。requirements.txt 中指定 torch>=1.9.0,正是因为它修复了早期版本中 LayerNorm 在极端 batch 下的数值问题。

5.4 GELU 激活函数的版本兼容性

ViT 论文使用 GELU,但 PyTorch 1.0 之前只有 nn.ReLU。vit.py 中 self.act = nn.GELU() 要求 PyTorch ≥ 1.2.0。若你环境是旧版,需手动实现:

class GELU(nn.Module):
    def forward(self, x):
        return 0.5 * x * (1 + torch.tanh(math.sqrt(2 / math.pi) * (x + 0.044715 * torch.pow(x, 3))))

transfomer.md 中专门有一节解释 GELU 为何比 ReLU 更适合 Transformer:它在负值区有非零梯度,能缓解“死区”问题,让模型在训练初期更鲁棒。

5.5 可视化时的设备同步:.cpu() 不是万能的

在 GPU 上运行时,attn_weights 是 cuda tensor。直接 plt.imshow(attn_weights[0, 0].cpu().numpy()) 是正确的,但若你忘了 .cpu(),会报错 Can't convert CUDA tensor to numpy。更隐蔽的坑是:.numpy() 会触发同步(synchronize),拖慢速度。对于大批量可视化,应先 attn_weights = attn_weights.cpu() 再 .numpy(),而非每次 .cpu().numpy()。README.ipynb 中所有可视化 cell 都加了 %%time magic,就是为了让你感知到这个差异。

6. 从单图验证到完整训练:如何用此代码包搭建你的第一个 ViT 训练 pipeline

6.1 快速端到端验证:cat.jpg 的五步走通

README.ipynb 的核心价值,在于它把 vit.py 的每个模块都封装成可独立运行的 cell。按顺序执行:

  1. 加载与预处理:PIL.Image.open('cat.jpg') → transforms.Compose([...]) → tensor.shape = (3, 224, 224)。
  2. Patchify:调用 patchify(img),print(patches.shape) 确认 (196, 768)。
  3. Embedding & Position:patch_embeddings = model.patch_embed(patches) → x = torch.cat([...]) + model.pos_embed,print(x.shape) 确认 (1, 197, 768)。
  4. Forward Pass:logits, attn_weights = model(x),print(logits.shape) 应为 (1, 1000)(ImageNet 类数)。
  5. 可视化:plt.imshow(attn_weights[0, 0]),看到热力图即成功。

这五步,就是 ViT 的心脏起搏器。只要其中一步 shape 对不上,就说明你对数据流的理解有偏差。cat.jpg 的存在,不是为了“好看”,而是作为一个确定性的、可复现的锚点,帮你校准整个认知坐标系。

6.2 扩展为完整训练:只需三处关键修改

vit.py 是 inference-focused,要训练,需补充:

① 数据加载器:替换 cat.jpg 为 torchvision.datasets.ImageFolder,添加 RandomHorizontalFlip, ColorJitter 等增强。settings. 中的 data_dir 配置指向你的 ImageNet 路径。

② 损失函数与优化器:在 notebook 中添加:

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.05)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)

AdamW 是 ViT 训练标配,weight_decay=0.05 比传统 CNN 的 1e-4 更大,因 Transformer 参数更多。

③ 训练循环:核心是 loss = criterion(logits, targets),但 ViT 训练的关键技巧是 Label Smoothing(criterion = LabelSmoothingCrossEntropy(smoothing=0.1)),它能显著提升泛化性,这也是 transfomer.md 中强调的实践要点。

6.3 性能调优:为什么你的 ViT 训练慢、显存爆?

  • 显存爆炸:attn_weights 的 shape 是 (B, H, N, N),当 B=64, H=12, N=197 时,仅存储就需要 64*12*197*197*4 ≈ 115MB(float32)。解决:用 torch.cuda.amp.autocast() 混合精度,或启用 torch.compile(model)(PyTorch 2.0+)。
  • 训练慢:ViT 的瓶颈在 q @ k.transpose 的 O(N^2) 复杂度。patch_size=16 时 N=197,尚可;若你尝试 patch_size=8,N=784,计算量暴增 16 倍。Patches.png 的 16×16 不是随意选的,是精度与效率的黄金分割点。

6.4 模型诊断:如何判断你的 ViT 是否“学坏了”?

  • Attention Entropy 过低:所有 head 的 class token 行熵值 < 3.0,说明模型过早收敛,只关注少数 patch,可能过拟合。
  • Gradient Norm 为 0:torch.norm(model.patch_embed.weight.grad) 为 0,说明 patch embedding 层没更新,检查 requires_grad=True 是否被意外关闭。
  • Logits 方差过小:logits.std() < 0.1,说明模型输出趋同,丧失判别力,检查 LayerNorm 是否放在了错误位置(应在 qkv 前,而非后)。

这些诊断指标,README.ipynb 的最后一个 cell 都已预置好,你只需运行,就能获得一份“ViT 健康报告”。

我在实际项目中部署 ViT 时,曾因忽略 pad_to_patch_size 导致线上服务在处理用户上传的非标准尺寸图片时批量报错,排查了两天才定位到 Unfold 的隐式裁剪行为。后来我把这个检查点写进了团队的 code review checklist。真正的工程落地,从来不是照着论文抄公式,而是在每一个 print(shape) 和 plt.imshow() 的确认中,把理论变成肌肉记忆。这份资源包的价值,不在于它多完美,而在于它足够“粗糙”——粗糙到你能看见每一行代码的呼吸,听见每一个张量变形时的咔哒声。当你亲手把 cat.jpg 切成 196 块,再看着 class token 的 attention 权重从均匀分布慢慢聚焦到猫眼区域,那一刻,ViT 就不再是论文里的符号,而是你指尖流淌的、有温度的视觉理解。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:这个资源包提供Vision Transformer(ViT)的完整PyTorch原生实现,不依赖torchvision或timm等高层封装,所有模块逐行可读、可调试。从原始图像切分为16×16像素块(Patches.png)、线性投影生成Patch嵌入(ViTPathEmbedding.png)、叠加可学习位置编码(ViTPatchesPositionEmbedding.png),到标准Transformer Block的构建——包括多头自注意力计算细节(TransformerBlockAttention.png)、局部放大视图(TransformerBlockAttentionZoom.png)、残差连接(TransformerBlockAttentionRes.png)和前馈网络(TransformerBlockMLP.png)。配套notebook(README.ipynb)支持交互式运行,内置cat.jpg用于快速端到端验证;DeiT.png与ViT.png对比展示蒸馏增强结构差异;transfomer.md补充核心原理,paper1.png截取原始论文关键页便于对照。所有图像命名直指功能(如PatchesProjected.png对应投影后张量形态),vit.py为单一干净源码文件,requirements.txt明确依赖,.vscode和settings.支持开箱即用的本地开发环境。适合想真正搞懂ViT每一步张量形状变化、数据流向和注意力权重生成逻辑的学习者。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

从CNN到ViT:视觉Transformer原理与PyTorch实战 Transformer架构正在重塑计算机视觉的技术范式。作为处理序列数据的革命性模型,其核心的自注意力机制通过计算全局关系权重实现特征交互,相比传统CNN的局部感受野具有更强的长程建模能力。在工程实践中,这种特性使ViT(Vision Transformer)在医疗影像分析、遥感检测等需要跨尺度理解的任务中表现突出。通过PyTorch实现可见,ViT将图像拆分为视觉token后,配合可学习的位置编码和多头注意力层,能够自然地建立像素级全局关联。虽然计算复杂度较高,但采用混合架构设计、窗口注意力等优化策略后, 阅读详情

相关推荐

ViT实战:手把手教你用Visual Transformer进行图像分类(附代码)

本文提供了一份从零构建视觉Transformer(ViT)进行图像分类的实战指南。文章详细解析了ViT的核心组件,包括多头自注意力机制(Multi-head Attention)和Transformer编码器,并附有完整的PyTorch代码实现,涵盖环境搭建、数据预处理、模型构建、训练策略及可视化调试全流程,帮助开发者快速掌握这一前沿技术。

ooo22的博客 648

从零构建ViT:揭秘图像分块与位置编码的魔法

本文深入解析了Vision Transformer(ViT)的核心技术,重点探讨图像分块与位置编码的实现原理。通过PyTorch代码示例展示如何从零构建ViT模型,并分析其在图像分类任务中的性能表现,为开发者提供实用的训练技巧和优化策略。

hhh00的博客 692

Transformer核心模块实战:用PyTorch复现ViT中的Multi-Head Attention并可视化注意力图

本文详细介绍了如何使用PyTorch复现Vision Transformer(ViT)中的多头注意力机制(Multi-Head Attention),并通过可视化注意力图展示模型如何关注图像的不同区域。从环境配置、数据预处理到完整ViT模块的实现,逐步解析了多头注意力的原理与代码实现,帮助开发者深入理解Transformer在计算机视觉中的应用。

weixin_30275415的博客 418

从零到一:手把手教你用PyTorch实现ViT图像分类(附完整代码)

本文详细介绍了如何使用PyTorch从零开始实现视觉Transformer(ViT)进行图像分类,涵盖数据预处理、模型构建、训练评估等全流程。通过实战代码演示ViT的核心组件如Patch Embedding、位置编码和Transformer Encoder的实现,并提供了高级优化技巧如混合精度训练和注意力可视化,帮助开发者快速掌握这一前沿技术。

xray4的博客 787

视觉Transformer(ViT)架构解析与PyTorch实现

Transformer架构在自然语言处理领域取得巨大成功后,其自注意力机制被引入计算机视觉领域,形成了视觉Transformer(ViT)这一创新架构。与传统的卷积神经网络(CNN)不同,ViT通过将图像分割为块序列并应用自注意力机制,能够直接建模全局像素关系。这种架构在大规模视觉任务中展现出强大潜力,特别是在需要长程依赖建模的场景。本文以CIFAR-10数据集为例,详细解析ViT的核心组件包括图像分块嵌入、位置编码和Transformer编码器,并给出完整的PyTorch实现代码,帮助开发者理解如何将Tr

weixin_34353714的博客 333

从零实现图像Transformer:可调试的ViT教学版

图像Transformer是一种将二维图像转化为序列化token并利用自注意力机制建模全局关系的深度学习架构,其核心原理在于脱离CNN的局部归纳偏置,通过patch embedding、位置编码与多头注意力实现端到端的空间语义建模。相比传统卷积网络,它具备更强的长程依赖建模能力与结构透明性,技术价值体现在可解释性、梯度直通性及模块级可控性,广泛应用于教学演示、模型诊断与轻量部署场景。本文聚焦‘从零构建’与‘逐层可调试’两大特性,以PyTorch原生实现CIFAR级轻量ViT,覆盖patchify策略、2D位

weixin_29057163的博客 316

ViT详解:图像序列化、位置编码与注意力可视化

Vision Transformer通过将图像切分为Patch并组织成序列,借助Transformer的自注意力机制对全局依赖进行建模。与卷积网络不同,ViT不依赖局部性和平移等变性先验,而需要额外引入位置编码来保留空间信息。理解位置编码如何作为“空间记忆”注入序列,以及如何通过注意力可视化观察模型在不同层关注区域的变化,是掌握ViT的关键。这类模型在数据充足时具有更强的表达上限,广泛应用于图像分类、目标识别等视觉任务。本文结合工程实践,详细拆解Patch嵌入、位置编码、注意力可视化的原理与实现,并分享实际

weixin_34253126的博客 374

ViT实战:用PyTorch从零搭建Vision Transformer图像分类模型(附完整代码)

本文详细介绍了如何使用PyTorch从零开始搭建Vision Transformer(ViT)图像分类模型。通过分步解析Patch Embedding、可学习位置编码、多头自注意力机制及Transformer编码器等核心模块的代码实现,并分享数据增强、优化器配置等关键训练技巧,帮助开发者深入理解并实践这一基于Transformer的图像分类新范式。

weixin_29098391的博客 220

揭秘Vision Transformer黑盒:用vit-pytorch实现特征重要性可视化

你是否曾困惑于AI模型如何"思考"图像分类?当深度学习模型给出"猫"或"狗"的判断时,它究竟关注了图像中的哪些区域?传统卷积神经网络(CNN)的可视化方法已无法满足Transformer架构的解析需求。本文将带你使用vit-pytorch库,一步步揭开Vision Transformer(ViT)的决策过程,通过代码实例掌握特征重要性分析技术,让AI的"注意力"变得可见。 读完本文你将学会: -...

gitblog_01145的博客 920

别再只盯着CNN了!手把手带你用PyTorch从零搭建ViT模型(附完整代码)

本文详细介绍了如何使用PyTorch从零搭建Visual Transformer(ViT)模型,涵盖环境配置、数据预处理、核心模块实现及训练技巧。通过实战案例演示ViT在图像分类任务中的应用,特别适合希望突破CNN局限的开发者。文章还提供了完整的代码示例和优化建议,帮助读者快速掌握这一革命性技术。

weixin_42639633的博客 372

UFS逻辑单元管理

介绍UFS 逻辑单元管理和配置

m0_51720747的博客 1942

从零实现Transformer:深入理解自注意力、位置编码与工程实践

Transformer架构作为现代深度学习的核心基础,其核心在于自注意力机制,它通过计算序列内部元素间的动态关联,有效解决了长距离依赖问题。这一原理不仅推动了自然语言处理领域的革命,更成为计算机视觉、时间序列预测等多模态任务的通用骨干网络。其技术价值在于通过并行化计算和模块化设计,实现了高效的特征提取和强大的表征能力。在实际应用中,从文本分类、序列标注到视觉Transformer(ViT),都需要对位置编码、残差连接等组件进行针对性适配。本文聚焦于自注意力机制和位置编码这两个关键模块,通过PyTorch代码

weixin_33816821的博客 1563

MaxEnt物种分布预测中的常见误区与优化技巧:如何提高模型准确率

本文深入探讨了MaxEnt物种分布预测模型从基础应用到精准预测的进阶实践。针对模型准确率提升,文章系统分析了环境变量筛选、模型参数调优、结果批判性解读等核心环节的常见误区,并提供了基于生态位模型原理的深度优化技巧,帮助研究者产出更可靠、可解释的预测结果。

weixin_29219539的博客 593

别再死磕CNN了!用PyTorch从零搭建Vision Transformer(ViT)图像分类模型

本文详细介绍了如何使用PyTorch从零搭建Vision Transformer(ViT)图像分类模型,深入解析ViT的设计哲学和核心组件,包括图像分块嵌入、位置编码和Transformer编码器。通过对比实验和实战案例,展示了ViT在图像分类任务中的优势,并提供了训练技巧与优化建议,帮助开发者掌握这一前沿技术。

weixin_26968079的博客 246

从零开始:用摩尔线程S4000运行开源AI项目的完整流程

本文详细介绍了如何使用摩尔线程S4000 GPU从零开始运行开源AI项目的完整流程,包括硬件选择、驱动安装、容器化环境配置以及典型AI项目的实战部署。通过性能优化技巧,显著提升推理速度,适合开发者快速上手国产GPU的AI应用开发。

weixin_29231725的博客 578

Silvaco Atlas入门指南——从零开始掌握半导体器件仿真

本文为Silvaco Atlas半导体器件仿真软件的入门指南,详细介绍了从零开始掌握该工具的核心流程。内容涵盖Atlas的基本概念、仿真流程、核心文件(输入、结构、输出文件)、交互式与批处理两种操作模式,并逐步演示了如何从定义网格、划分区域、设置电极到进行掺杂、选择物理模型并求解,最终获取和分析器件特性的完整步骤。旨在帮助微电子相关专业人员和工程师快速上手这一强大的“数字实验室”,提升半导体器件设计与研发效率。

weixin_29306317的博客 398

从NLP到CV:手把手教你用Transformers实现图像分类(附ViT实战代码)

本文详细介绍了如何将Transformer模型从自然语言处理领域迁移到计算机视觉任务,特别是图像分类。通过手把手实战教程,深入解析Vision Transformer(ViT)的核心原理,包括图像块嵌入、位置编码和自注意力机制,并提供完整的代码实现。文章还分享了处理高分辨率图像、注意力可视化及使用预训练模型进行迁移学习的实用技巧,帮助开发者快速掌握这一前沿技术。

php55的博客 645

oracle p6 破解版下载,Primavera P6项目管理软件 16~17 图文详细安装教程(附下载)

Primavera P6 Professional项目管理软件结合了Oracle和Microsoft SQL Server关系数据库,是一款非常好用且功能强大的综合的项目管理软件(PPM) 解决方案,主要用于土建等方面的施工计划分析,具有进度时间安排与资源控制功能的多用户、多项目系统,支持多层项目分层结构、角色与技能导向的资源安排、记录实际数据、自定义视图以及自定义数据,本文主要为大家图文详细介绍...

weixin_33978451的博客 4903

别再死磕CNN了!手把手教你用PyTorch复现ViT(Vision Transformer)图像分类

本文详细介绍了如何使用PyTorch复现Vision Transformer(ViT)进行图像分类,揭示了ViT如何通过图像分块和Transformer架构超越传统CNN。文章包含代码实现、核心架构解析及与CNN的实战对比,帮助开发者掌握这一图像识别新范式。

weixin_30655569的博客 574

AURIX-TC3xx-GTM详解六-DPLL(Digital PLL Module)-寄存器1-DPLL寄存器

主要有下图这些寄存器2、

m0_69929716的博客 438

UMI——斯坦福刷盘机器人:通过手持夹爪革新数据收集方式,且使用视觉SLAM和Diffusion Policy预测动作

没想到今年年初的斯坦福mobilealoha的热度刚过,而到今年2月的下旬,斯坦福另一个团队又推出了UMI刷盘机器人,且这两个团队还互相认识、还在一块共同切磋(顺带小小感叹一下,斯坦福的氛围是真好而且真高产)斯坦福UMI刷盘机器人其与mobile aloha(以及AirExo: Low-Cost Exoskeletons for Learning Whole-Arm Manipulation in the Wild)最大的不同在于其收集数据处理的非真实的机器人,而是一个手持夹持器(从而大幅降低成本)

u013250861的博客 1354

从零手写ViT:PyTorch实现Vision Transformer的核心细节与完整代码

Vision Transformer(ViT)是Transformer架构在图像领域的开创性应用,其核心思想是将图像分割为固定大小的Patch,并通过Patch Embedding将每个图像块转换为Token序列,再利用自注意力机制捕捉全局依赖关系。与卷积神经网络相比,ViT摆脱了局部归纳偏置,在超大规模数据集上展现出更强的特征表达能力,已成为图像分类、目标检测等任务的重要技术路线。使用PyTorch逐行实现ViT,不仅能深入理解注意力机制、位置编码、分类头等组件的协作原理,还能掌握从环境配置、数据集选型到

weixin_34380948的博客 283

为什么 ADT 在 SAP BTP ABAP environment 里认证成功却无法登录 Service Instance:根因拆解与一站式修复路线

摘要 ADT报错"认证成功但Service Instance登录失败"通常源于ABAP租户的权限或用户映射问题,而非账号密码错误。常见原因包括:1) ABAP租户内未创建对应业务用户;2) 业务用户缺少开发角色(如SAP_BR_DEVELOPER);3) BTP角色集合未分配或IdP组映射缺失;4) 浏览器会话混淆身份;5) 连接了非开发租户(如Customizing Tenant);6) 实例休眠导致误判。 解决方法:检查ABAP业务用户及角色分配,确认BTP角色集合映射,使用隐身窗口

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 252

Vision Transformer (ViT) 完全解析:从注意力机制到 PyTorch 实战

本文系统介绍了 Vision Transformer (ViT) 的核心原理与实现。首先分析了将 Transformer 引入视觉领域的动机,对比了 CNN 的局部归纳偏置与 Transformer 的全局建模能力。接着详细解析了 ViT 的整体架构,包括图像分块、Patch Embedding、位置编码、CLS Token 以及 Transformer Encoder 中的自注意力、多头注意力和 MLP 模块。

starmain246的博客 465

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

如何理解ViT自注意力机制:PyTorch Image Models深度解析

PyTorch Image Models (timm) 库中的Vision Transformer (ViT) 自注意力机制彻底改变了计算机视觉领域,将原本用于自然语言处理的Transformer架构成功应用于图像分类任务。这种创新的自注意力机制让模型能够全局关注图像的所有部分,而不仅仅是局部特征,为深度学习带来了革命性的突破。 ## 什么是Vision Transformer (ViT) V

gitblog_00951的博客 361

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

从NLP到CV:手把手教你用PyTorch实现Vision Transformer图像分类(附完整代码)

本文详细介绍了如何使用PyTorch实现Vision Transformer(ViT)进行图像分类,包括ViT的核心架构、Patch Embedding、位置编码和Transformer Encoder的实现。通过完整的代码示例和训练技巧,帮助开发者快速掌握这一革命性的计算机视觉技术,适用于医学影像、卫星图片等需要全局建模能力的场景。

m5n6o7的博客 725

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

上一篇: MATLAB视频运动估计三算法工具包:ES/TSS/DS实现+GUI交互+YUV序列处理
下一篇: VC6.0下可直接运行的二叉树遍历与普通树转二叉树可视化代码包
人间清醒863
博客等级 码龄3年 33粉丝 1544原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值