视觉transformer 部分知识梳理
视觉 Transformer(Vision Transformer, ViT)中完全保留了 Q、K、V 这套机制,而且用法和原始 Transformer 在 NLP 中几乎一模一样。
下面我用最直白的方式解释:ViT 里也有 Q、K、V,它们干的事情也是一样的——让图像的不同区域“互相看、互相参考”。
🖼️ 第一步:图像怎么变成“词”?
在 NLP 中,输入是一串词(如 “cat sat on mat”)。
在 ViT 中,图像被切成小块(patches),比如一张 224×224 的图,切成 16×16 的小块,得到 14×14 = 196 个图像块。
每个图像块被展平 + 线性变换 → 变成一个向量,就像一个“视觉词”(visual token)。
所以输入变成了:
[patch1, patch2, ..., patch196] → 形状 (196, d)
这就和 NLP 的词序列形式一样了!
🔍 第二步:Q、K、V 怎么来的?
对这 196 个 patch 向量,ViT 做的事情和 Transformer 完全相同:
- 每个 patch 向量 xix_ixi 通过三个可学习矩阵生成:
- qi=xiWQq_i = x_i W_Qqi=xiWQ → Query:这个区域“想关注什么特征”
- ki=xiWKk_i = x_i W_Kki=xiWK → Key:这个区域“能被如何匹配”
- vi=xiWVv_i = x_i W_Vvi=xiWV → Value:这个区域“实际包含什么视觉信息”
✅ 所以,Q、K、V 在 ViT 中依然存在,而且意义类似:
- Q:我在找什么样的视觉模式?(比如边缘、纹理、物体部件)
- K:我这个区域看起来像什么?(供别人来匹配)
- V:我这里真正有什么内容?(用于最终融合)
👀 第三步:注意力在图像中起什么作用?
举个例子:
假设图像中有 一只狗,它的头在 patch A,尾巴在 patch Z。
当模型处理 patch A(狗头)时:
- 它的 Query(q_A)会去和所有 patch 的 Key(k_1 到 k_196)算相似度
- 发现 k_Z(狗尾巴)和 q_A 有一定相关性(因为都是“狗”的一部分)
- 于是注意力权重会给 patch Z 一些分数
- 最终输出 = 所有 patch 的 Value 加权和 → 狗头的表示里就融入了尾巴的信息!
👉 这就是 全局上下文建模:哪怕两个区域离得很远,也能建立联系!
❓那和 CNN 有什么区别?
- CNN:只能通过滑动窗口看局部(比如 3×3 邻域),要看到全局得堆很多层。
- ViT(带 QKV 注意力):第一层就能让任意两个 patch 互相通信,天生具有全局感受野。
✅ 总结
| 问题 | 回答 |
|---|---|
| ViT 有没有 Q、K、V? | 有!完全保留 |
| Q、K、V 在图像中是什么意思? | Q=“我想看啥”,K=“我能被谁看”,V=“我有啥内容” |
| 为什么需要 Q 和 K 算相关性? | 为了知道图像中哪个区域该关注哪个区域(比如眼睛关注鼻子,车轮关注车身) |
| 是不是只关注自己? | 不是!恰恰是要跨区域关注,这才是注意力的价值 |
所以,不管是文字还是图像,Transformer 的核心思想不变:
通过 Q-K 匹配,动态决定从哪些地方(V)提取信息。
在视觉任务中,这就变成了:让图像的不同部分学会“互相理解”。
如果你看 ViT 的代码(比如 PyTorch 实现),你会发现它的 Attention 模块和 NLP 里的几乎一模一样 😊
Q、K、V 是通过输入乘以三个“全局共享”的可学习矩阵 WQW_QWQ、WKW_KWK、WVW_VWV 得到的;所有位置的 q 都共用同一个 WQW_QWQ,k 共用 WKW_KWK,v 共用 WVW_VWV。
🧩 举个具体例子(以 ViT 为例)
假设一张图像被切成 4 个 patch(为了简单):
- 输入:X=[x1,x2,x3,x4]X = [x_1, x_2, x_3, x_4]X=[x1,x2,x3,x4],每个 xi∈Rdx_i \in \mathbb{R}^{d}xi∈Rd(比如 d=768)
- 所以 X∈R4×dX \in \mathbb{R}^{4 \times d}X∈R4×d
模型内部有三个固定的、可训练的权重矩阵(对整个模型来说是全局的):
- WQ∈Rd×dkW_Q \in \mathbb{R}^{d \times d_k}WQ∈Rd×dk
- WK∈Rd×dkW_K \in \mathbb{R}^{d \times d_k}WK∈Rd×dk
- WV∈Rd×dvW_V \in \mathbb{R}^{d \times d_v}WV∈Rd×dv
💡 注意:这三个矩阵不是每个位置单独学的,而是整个模型只有一套,所有 patch 共用!
然后计算:
- Q=XWQ=[x1WQ, x2WQ, x3WQ, x4WQ]=[q1,q2,q3,q4]Q = X W_Q = [x_1 W_Q,\; x_2 W_Q,\; x_3 W_Q,\; x_4 W_Q] = [q_1, q_2, q_3, q_4]Q=XWQ=[x1WQ,x2WQ,x3WQ,x4WQ]=[q1,q2,q3,q4]
- K=XWK=[k1,k2,k3,k4]K = X W_K = [k_1, k_2, k_3, k_4]K=XWK=[k1,k2,k3,k4]
- V=XWV=[v1,v2,v3,v4]V = X W_V = [v_1, v_2, v_3, v_4]V=XWV=[v1,v2,v3,v4]
✅ 所以:
- 所有 q 都用同一个 WQW_QWQ → 这叫参数共享
- 但因为输入 xix_ixi 不同,所以 q1,q2,...q_1, q_2, ...q1,q2,... 是不同的向量
🔍 怎么理解 WQW_QWQ、WKW_KWK、WVW_VWV 的作用?
可以把它们看作三个“翻译器”,把原始输入“翻译”成三种不同角色的语言:
| 矩阵 | 角色 | 比喻 |
|---|---|---|
| WQW_QWQ | 查询翻译器 | 把“我是什么”翻译成“我想找什么” |
| WKW_KWK | 关键词翻译器 | 把“我是什么”翻译成“我能被怎么找到” |
| WVW_VWV | 内容翻译器 | 把“我是什么”翻译成“我真正要传递的信息” |
🌰 举个图像例子:
- 一个 patch 是“狗的眼睛”
- WQW_QWQ 把它变成:“我在找和眼睛相关的区域(比如鼻子、耳朵)”
- WKW_KWK 把它变成:“我是面部特征,可以被‘找脸’的 query 匹配”
- WVW_VWV 把它变成:“我包含高亮、圆形、深色等视觉细节”
这些“翻译规则”(即 WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV)是通过大量数据训练出来的。模型会自动学会:
- 什么时候该让 Q 关注局部纹理?
- 什么时候该让 K 响应“物体整体”类的查询?
- V 应该保留哪些信息用于后续融合?
❓为什么可以共用同一个 WQW_QWQ?
因为 Transformer 假设:不管输入出现在序列的哪个位置,它的“角色转换规则”是一样的。
- 在 NLP 中:“猫”不管是句首还是句尾,它作为“名词”的性质不变
- 在 CV 中:一个“边缘 patch”不管在图像左上角还是右下角,它作为“边缘”的语义也不变
所以用同一套 WQW_QWQ 是合理且高效的(减少参数,提升泛化)。
⚠️ 补充:有些改进模型(如 Deformable DETR、CoaT)会引入位置相关的投影,但标准 Transformer/ViT 是位置无关的线性投影。
🧠 再打个比方(工厂流水线)
想象一个工厂:
- 原料:4 个零件 x1x_1x1 到 x4x_4x4
- 三条生产线:
- 生产线 Q:用同一套模具 WQW_QWQ,把每个零件压成“需求卡”
- 生产线 K:用同一套模具 WKW_KWK,把每个零件压成“标签卡”
- 生产线 V:用同一套模具 WVW_VWV,把每个零件压成“内容包”
然后系统拿所有“需求卡”去匹配所有“标签卡”,决定怎么组合“内容包”。
✅ 模具(WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV)是固定的,但原料不同 → 产出就不同。
✅ 最终总结
| 问题 | 回答 |
|---|---|
| QKV 是怎么来的? | Q=XWQQ = XW_QQ=XWQ, K=XWKK = XW_KK=XWK, V=XWVV = XW_VV=XWV |
| 所有 q 共用 WQW_QWQ 吗? | 是的!全局共享,参数不随位置变化 |
| WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV 是什么? | 可学习的线性变换矩阵,相当于三个“角色翻译器” |
| 它们是怎么训练的? | 和整个网络一起,通过反向传播自动优化(目标:让注意力机制有效) |
| 为什么能共用? | 因为模型假设:语义角色与位置无关,同一类输入应被同样“翻译” |
我们用 Vision Transformer (ViT) 的一个具体、真实、带数字的例子,一步一步说明 Q、K、V 以及对应的权重矩阵 WQW_QWQ、WKW_KWK、WVW_VWV 的维度、大小和计算过程。
🖼️ 场景设定(以 ViT-Base 为例)
这是 Google 提出的标准 ViT 模型中最常用的配置:
| 参数 | 值 |
|---|---|
| 输入图像尺寸 | 224 × 224 × 3 |
| Patch 大小 | 16 × 16 |
| Patch 数量 | (224/16)2=14×14=196(224/16)^2 = 14 \times 14 = 196(224/16)2=14×14=196 |
| 每个 patch 展平后维度 | 16×16×3=76816 \times 16 \times 3 = 76816×16×3=768 |
| Embedding 维度(即 d) | 768 |
| 注意力头数(多头) | 12(但我们先看单头简化版) |
| dk=dv=dd_k = d_v = ddk=dv=d(标准 ViT 中通常设为相等) | 768 |
💡 为了简单,我们先看单头注意力(single-head),后面再说多头。
第一步:输入 → Patch Embeddings
原始图像 → 切成 196 个 patch → 每个 patch 展平成 768 维向量 → 加上位置编码。
所以输入矩阵:
X∈R196×768
X \in \mathbb{R}^{196 \times 768}
X∈R196×768
- 196 行:每个 patch(token)
- 768 列:每个 token 的特征维度
第二步:定义 WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV
在标准 ViT 中,这三个矩阵的形状是:
WQ∈R768×768WK∈R768×768WV∈R768×768\\ W_Q \in \mathbb{R}^{768 \times 768} \\ W_K \in \mathbb{R}^{768 \times 768} \\ W_V \in \mathbb{R}^{768 \times 768} WQ∈R768×768WK∈R768×768WV∈R768×768
✅ 因为输入维度是 768,输出 Q/K/V 的维度也设为 768(也可以不同,但 ViT 默认相同)。
这些是可训练参数,模型一开始随机初始化,训练后学会“如何翻译”。
第三步:计算 Q, K, V
进行矩阵乘法:
Q=XWQ⇒(196×768)×(768×768)=(196×768)K=XWK⇒(196×768)V=XWV⇒(196×768) Q = X W_Q \quad \Rightarrow \quad (196 \times 768) \times (768 \times 768) = (196 \times 768) \\ K = X W_K \quad \Rightarrow \quad (196 \times 768) \\ V = X W_V \quad \Rightarrow \quad (196 \times 768) Q=XWQ⇒(196×768)×(768×768)=(196×768)K=XWK⇒(196×768)V=XWV⇒(196×768)
所以:
- Q: 196 个 query 向量,每个 768 维 → 形状
(196, 768) - K: 196 个 key 向量,每个 768 维 → 形状
(196, 768) - V: 196 个 value 向量,每个 768 维 → 形状
(196, 768)
🔍 注意:虽然 Q 和 K 都是 768 维,但它们是通过不同的线性变换得到的,所以内容不同!
第四步:计算注意力(核心!)
现在我们要算:每个 patch 应该关注哪些其他 patch?
1. 计算相似度(Q 和 K 的点积)
Scores=QKT⇒(196×768)×(768×196)=(196×196) \text{Scores} = Q K^T \quad \Rightarrow \quad (196 \times 768) \times (768 \times 196) = (196 \times 196) Scores=QKT⇒(196×768)×(768×196)=(196×196)
结果是一个 196×196 的矩阵,其中:
- 第 i 行第 j 列 = qi⋅kjq_i \cdot k_jqi⋅kj → 表示 patch i 的 query 与 patch j 的 key 的相关性
✅ 举例:
Scores[0, 50]很大 → 说明第 0 个 patch(比如左上角天空)在关注第 50 个 patch(可能是云朵)Scores[100, 100]可能不大 → 自己不一定最相关!
2. Softmax 归一化
对每一行做 softmax,得到注意力权重:
A=softmax(QKT768)∈R196×196
A = \text{softmax}\left( \frac{QK^T}{\sqrt{768}} \right) \in \mathbb{R}^{196 \times 196}
A=softmax(768QKT)∈R196×196
每行和为 1,表示“当处理 patch i 时,给其他所有 patch 分配的注意力比例”。
3. 加权求和 V
Output=AV⇒(196×196)×(196×768)=(196×768) \text{Output} = A V \quad \Rightarrow \quad (196 \times 196) \times (196 \times 768) = (196 \times 768) Output=AV⇒(196×196)×(196×768)=(196×768)
最终输出:每个 patch 都融合了全局信息的新表示。
📊 总结维度表(单头 ViT)
| 名称 | 符号 | 形状 | 说明 |
|---|---|---|---|
| 输入 | XXX | (196, 768) | 196 个图像块,每个 768 维 |
| 查询权重 | WQW_QWQ | (768, 768) | 可学习,所有 patch 共用 |
| 键权重 | WKW_KWK | (768, 768) | 可学习,共用 |
| 值权重 | WVW_VWV | (768, 768) | 可学习,共用 |
| 查询 | Q=XWQQ = X W_QQ=XWQ | (196, 768) | 每个 patch 的“我想关注什么” |
| 键 | K=XWKK = X W_KK=XWK | (196, 768) | 每个 patch 的“我能被怎么匹配” |
| 值 | V=XWVV = X W_VV=XWV | (196, 768) | 每个 patch 的“我有什么内容” |
| 注意力分数 | QKTQK^TQKT | (196, 196) | 所有 patch 两两之间的相关性 |
| 输出 | AVA VAV | (196, 768) | 融合全局信息后的新特征 |
🔁 补充:多头注意力(Multi-Head)怎么办?
ViT-Base 实际用的是 12 个头。做法是:
- 把 WQW_QWQ 拆成 12 个小矩阵,每个头有自己的 WQh∈R768×64W_Q^h \in \mathbb{R}^{768 \times 64}WQh∈R768×64(因为 768 ÷ 12 = 64)
- 同理 WKh,WVh∈R768×64W_K^h, W_V^h \in \mathbb{R}^{768 \times 64}WKh,WVh∈R768×64
- 每个头算自己的 Q_h, K_h, V_h ∈ (196, 64)
- 每个头输出 (196, 64),12 个头拼起来 → (196, 768)
- 最后再过一个线性层(WO∈R768×768W_O \in \mathbb{R}^{768 \times 768}WO∈R768×768)整合
但核心思想不变:还是 Q-K 匹配,V 融合。
✅ 最后用一句话帮你记住:
在 ViT 中,196 个图像块各自通过同一套三个“翻译器”(WQ,WK,WVW_Q, W_K, W_VWQ,WK,WV)变成 Q、K、V;然后每个块用自己的 Q 去扫描所有块的 K,决定从谁的 V 里拿信息,最终得到更聪明的表示。
如果你用 PyTorch 写 ViT,这部分代码大概长这样(简化):
# X: [196, 768]
Q = X @ W_Q # [196, 768]
K = X @ W_K # [196, 768]
V = X @ W_V # [196, 768]
attn = (Q @ K.T) / math.sqrt(768) # [196, 196]
attn = attn.softmax(dim=-1)
output = attn @ V # [196, 768]







更多推荐



所有评论(0)