之前在做视觉分类任务时,第一次看到 ViT(Vision Transformer)的完整实现,最大的感受是:Transformer 本身并不复杂,但代码里混杂了 Patch Embedding、Positional Encoding、Multi-Head Attention、LayerNorm、Dropout 这些概念之后,读起来就变得很吃力。尤其是从 CNN 转向 Transformer 的时候,思维上会出现几个坎:图像怎么变成序列?Patch 是什么?Forward 过程到底怎么流动?为什么最后接的是一个 Classification Head?
本篇就用一套完整可运行的 PyTorch 代码,把 ViT 从输入图像到最终分类输出逐步拆开。所有概念都结合代码片段讲解,尽量不做纯理论堆砌。适用人群包括:刚入门 Transformer 的算法工程师、准备复现 ViT 论文的在校学生、以及想把 Vision Transformer 用到自己项目里但还没完全搞清楚细节的开发者。
通过本文,你会掌握以下几个关键点:
- Transformer 的核心模块:Embedding、Multi-Head Attention、FFN、LayerNorm。
- Patch Embedding 如何把图像变为序列。
- ViT 的完整 Forward 流程。
- 用 PyTorch 从零实现一个小型 ViT,并跑通一次训练和推理。
1. 背景与核心概念:为什么视觉任务也用 Transformer
1.1 从 CNN 到 Transformer:视觉模型发生了什么变化
在 Transformer 出现之前,图像分类任务基本由 CNN 主导。CNN 的思想是局部感受野和滑动窗口,通过卷积核在图像上滑动提取局部特征,再经过池化逐步扩大感受野。对于图像这种具有局部相关性的数据,CNN 的自然归纳偏置非常有效。
但 CNN 也有局限:它更擅长捕捉局部信息,要建模长距离依赖关系,需要堆叠很多层卷积,或者引入注意力机制,而且卷积操作对平移、缩放等具有一定不变性,但整体架构设计的自由度反而受限。
Transformer 一开始是为 NLP 设计的。它的核心是 Self-Attention,能直接建模序列中任意两个位置之间的依赖关系,距离不再是问题。于是研究者开始思考一个问题:图像能不能也看成序列?
这就是 ViT(Vision Transformer)的核心思路:把图像切分成一个个 Patch,每个 Patch 拉平成一个向量,再把这些向量当作 Token 输入 Transformer。这样图像就被转换成了“句子”,Transformer 可以像处理词向量一样处理图像块。
1.2 ViT 的基本流程概览
ViT 的完整流程可以拆成如下几步:
- 输入一张图像,例如 224×224×3。
- 把图像切分成固定大小的 Patch,例如每个 Patch 是 16×16。
- 每个 Patch 拉平成向量,经过线性映射得到 Patch Embedding。
- 在序列最前面加上一个可学习的 [CLS] Token,用于最后分类。
- 给每个 Token 加上位置编码(Positional Encoding)。
- 输入 Transformer Encoder,经过多层 Self-Attention 和 MLP。
- 取出 [CLS] Token 对应的输出,经过 Classification Head 得到分类结果。
后面所有代码都是围绕这条主流程展开的。
2. 环境准备与依赖说明
本文代码基于 PyTorch,示例环境如下:
- Python 3.8+
- PyTorch 1.13 或 2.x
- torchvision(用于数据集加载)
- matplotlib(用于可视化,非必须)
- 操作系统:Windows / Linux / macOS 均可
版本不需要完全一致,PyTorch 2.x 在 API 层面与 1.x 差异不大,文章代码以常用 API 为准。如果你的环境是 CPU,也可以运行,只是训练速度会慢。
安装依赖的命令:
pip install torch torchvision matplotlib如果你使用 GPU 版本,请根据自己的 CUDA 版本到 PyTorch 官网选择对应安装命令。