“深度学习的发展史,其实就是模型越来越‘懂得注意’的过程。”
在你还没接触 Transformer 之前,RNN 是自然语言处理的主力军。它们一次处理一个词,就像一位有点健忘的老教授,一边听你说话一边试图记住上下文,却常常在长段落中忘了开头。
直到 Attention 机制的诞生,彻底改变了这一局面。
想象你在浏览一篇论文,标题、图表和某些粗体关键字可能立刻抓住你的注意力。你不会平均看待每一个字,而是有意识地“聚焦”于重要部分。
同样地,Attention 让神经网络在处理语言时,也能自己判断“谁重要”,并优先考虑那些部分。
在技术上,它的定义可以简单归结为:
“给定一个查询向量(Query),根据键向量(Key)计算相关性分数,然后用这些分数对值向量(Value)进行加权平均,得到输出。”
这段话初看可能有点抽象,但它其实就是:“我问一个问题,然后根据别人的回答做出决定”。
Attention 最早被广泛应用在机器翻译中。假设我们翻译一句话:
英文:The cat sat on the mat
中文:那只猫坐在垫子上
如果只用一个固定向量来表示整个英文句子(像早期的 Seq2Seq 模型那样),模型可能记不住前面的主语或后面的宾语。而 Attention 则允许模型在翻译“垫子”这个词时,重点“关注”英文句中的“mat”,而不是平均考虑整句。
这就是 Attention:它让模型的输出可以有选择性地参考输入的每一部分。
Attention 的这种特性,彻底改变了 NLP 模型的设计理念。从最初作为“翻译增强器”的小组件,到如今成为大语言模型的骨干,它完成了一次史诗级的逆袭。
“语言的意义,不只在每个词本身,而在它与其它词之间的关系。”
如果你问我:Transformer 为什么能在 NLP 中打败 RNN、CNN、LSTM?
我会毫不犹豫地说:Self-Attention 给了它全局的上下文感知能力。
Self-Attention(自注意力机制)就是让序列中的每个词,在理解自身意义的同时,考虑整个句子中其它词的影响。
想象你在读这句话:
“自然语言处理是人工智能中最活跃的研究方向之一。”
当你理解“研究方向”这几个字时,你可能会联想到“自然语言处理”这个主语。这种从句中其它部分借助信息的行为,就是 Self-Attention 在模型中的体现。
我们以一句话为例:“我 爱 自然语言处理”
模型在处理这个序列时,会对每个词进行如下操作:
每个词都变成了 Q、K、V 三元组(通过不同的权重矩阵乘法得到)。
对当前词的 Q,分别与所有词的 K 做点积,然后除以 √d 归一化,再通过 Softmax 变成权重概率。
这就得到了一个“我该注意谁”的分数分布。
将每个 V 乘上对应权重,再求和,形成这个词新的语义向量。
词:“语言”
Q:“语言”发出一个“我是谁”的问题
K/V:句中其它词的身份与信息
Attention Score 可能是这样:
词 | 相关性分数(score) | 注意力权重(softmax 后) |
---|---|---|
我 | 0.1 | 0.05 |
爱 | 0.3 | 0.10 |
自然 | 0.8 | 0.35 |
语言 | 1.0 | 0.40 |
处理 | 0.6 | 0.10 |
可以看到,“语言”最关注的是“自然”和自己,这恰好反映了“自然语言”这个词组的内部结构。模型自己学会了理解短语结构,而不是靠我们手动设计规则。
我们甚至可以可视化这些权重,看到每一层 Transformer 在“盯”哪些词。这让 Self-Attention 成为极少数既强大又可解释的机制之一。
“人脑有多个神经通路同时处理视觉、语言和记忆,大模型也一样:Attention 不只一头,它有很多个。”
如果说 Self-Attention 是模型的“自我关注能力”,那么 Multi-Head Attention(多头注意力) 则是让它同时从多个角度看待自己。
这是 Transformer 中非常精妙的设计,它不是简单的增强性能,而是一种真正模仿人类“多线程思维”的策略。
想象你要理解这句话:
“苹果公司昨天发布了最新款的智能眼镜。”
一个注意力头可能关注“发布 → 苹果公司”的动作主语关系;
另一个注意力头可能关注“发布 → 智能眼镜”的宾语结构;
还有一个可能识别“昨天”作为时间状语。
如果你只给模型一个头,它只能从一个方向思考。而多个头可以并行思考多个依赖关系,帮助模型更全面地理解语义。
其实它是一个“套路重复”的过程:
我们将输入的 Q、K、V 拆分成多个子空间,例如 8 个头,每个子空间大小是总维度的一部分(比如 512 → 8×64)。
每个头使用独立的权重矩阵,对自己的 Q/K/V 进行变换,并单独执行一次完整的 Self-Attention 流程。
将所有头的输出拼接在一起,再通过一个线性层统一映射,恢复为原始维度。
伪代码层面看就是:
for head in heads:
head_output = attention(Q_i, K_i, V_i)
final_output = concat(head_outputs) → linear transform
特性 | 描述 |
---|---|
并行语义捕捉 | 每个头可以学习不同的语言结构或语义模式 |
降低信息丢失 | 相比单头,多头能更稳定地提取全局特征 |
更丰富的表示能力 | 同一个词在多个头中可以有不同的语义解释 |
支持更深层次的组合语义建模 | 比如一个头关注实体,一个头关注动词组合 |
多头 Attention 就像多个专家组成的团队,各自从不同角度对同一个输入提出看法,最后综合成一个结论。
在 Transformer 可视化中,有些 Attention Head 会自动学会:
没人教过它们这么做。它们“自学成才”。
“如果说 Self-Attention 是模型的内省,Cross-Attention 就是它与外部世界的连接。”
在 Self-Attention 中,Query、Key、Value 都来自同一个地方,模型只关心“自己和自己怎么交流”;
而在 Cross-Attention 中,Query 与 Key/Value 来自不同的来源,也就是说,模型在向另一个输入提问。
这听起来有点抽象?别急,我们马上举例。
假设你正在翻译英文句子:
英文原文:The cat sat on the mat.
中文翻译:那只猫坐在垫子上。
在 Transformer 的 Encoder-Decoder 架构中:
比如 Decoder 当前正在尝试生成“垫子”这个词,它需要在 Encoder 的表示中找到“mat”这个词,并根据其语义来做出决策。
这时的 Attention 流程是这样的:
Query(来自 Decoder 当前步)
Key / Value(来自 Encoder 所有步)
→ 得出注意力分布 → 聚合 Value → 得到上下文信息
Decoder 一边生成目标语言,一边“查字典”。
因为 信息源不同,必须有一种机制能把 Encoder 看到的“外部世界”引入到 Decoder 中。
Self-Attention 只能让 Decoder 看自己的历史生成;
Cross-Attention 则是让它“看到源语言的信息”,是翻译的关键所在。
你见过 ChatGPT 看图回答问题的场景吗?比如:
用户上传一张图问:“这是什么动物?”
这时候,模型的文字输入是问题,而图片是通过视觉编码器生成的特征。
谁是 Query?谁是 Key/Value?
通过 Cross-Attention,语言模型可以“询问”图像编码,“你那边有没有和‘动物’相关的线索?”于是实现了多模态对齐。
其实和 Self-Attention 几乎一样,只不过 Q 来自一个序列,K/V 来自另一个。
def cross_attention(q_from_decoder, kv_from_encoder):
K = linear_k(kv_from_encoder)
V = linear_v(kv_from_encoder)
Q = linear_q(q_from_decoder)
...
return attention(Q, K, V)
它只是把“注意力目标”从自己换成了别人。
Attention 类型 | Q 来自 | K/V 来自 | 功能简述 |
---|---|---|---|
Self-Attention | 当前序列 | 当前序列 | 了解内部结构 |
Cross-Attention | 目标序列 | 外部序列 | 获取外部上下文,进行信息交互 |
Cross-Attention 是我们构建“理解与交流”能力的桥梁。没有它,就没有 GPT-4V 这样的“看图对话”;没有它,翻译模型就只会“自说自话”。
“一个懂得自省,一个善于交流,Transformer 正是靠这两个家伙打天下。”
很多人初看 Attention 的时候,总会问一句:
“Self-Attention 和 Cross-Attention,究竟区别在哪?”
“是不是只是换了个数据源?”
说对了一半。两者的数学形式基本一样,但应用语境、信息流方向、目标完全不同。
对比维度 | Self-Attention | Cross-Attention |
---|---|---|
Query 来源 | 当前序列(自己) | 目标序列(例如 Decoder) |
Key/Value 来源 | 当前序列(自己) | 外部序列(例如 Encoder 输出、图像编码) |
使用位置 | Encoder/Decoder 内部 | Decoder 外部与 Encoder 交互,多模态任务中 |
功能 | 理解内部结构,自我感知 | 从外部引入信息,构建联系 |
示例 | GPT、BERT、ViT | Transformer Decoder、CLIP、GPT-4V |
这俩就像一个人类的“内心戏” vs “对外交流”:
完整翻译流程中,两种 Attention 缺一不可。
这就像人类在说话时,会一边回忆脑中画面(Self),一边接收眼前图片信息(Cross)。
在很多大模型架构中,Cross-Attention 和 Self-Attention 是层层交替使用的:
这种“内省—交流—再思考”的迭代方式,是语言模型理解复杂任务的根本手段。
没有 Self-Attention,模型没法理解语义结构;
没有 Cross-Attention,模型就变成“闭门造车”。
正是这两者的协同,使得 Transformer 不再是一个堆线性层的拼图,而是一台能处理语言、图像、音频,甚至世界知识的推理引擎。
好,我们继续进入第六章,这一章更偏“动手实践”风格,给你一个可跑、可理解、可扩展的 Attention 最小实现版本。哪怕你没读过原论文,也能动手理解其机制。
“别光看原理,撸一段代码你会顿悟。”
虽然 Transformer 听起来像个庞然大物,但它的 Attention 核心,其实只需要几行代码就能实现。只要你理解了它的计算本质,其实非常优雅、简单。
一个最小版的 Attention 函数,功能包括:
import torch
import torch.nn.functional as F
def simple_attention(q, k, v, mask=None):
"""
q, k, v: [batch_size, seq_len, d_k]
mask: [batch_size, seq_len, seq_len] or None
"""
d_k = q.size(-1)
# Q x K^T
scores = torch.matmul(q, k.transpose(-2, -1)) / d_k**0.5
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
# softmax 得到权重分布
weights = F.softmax(scores, dim=-1)
# 加权 Value 得到输出
output = torch.matmul(weights, v)
return output, weights
这就是 Attention 的本质。你可能没想到,GPT 的核心竟然能简化为 10 行代码。
# 模拟一个 batch 中两个句子,每个有4个词,维度为8
q = torch.rand(2, 4, 8)
k = torch.rand(2, 4, 8)
v = torch.rand(2, 4, 8)
output, attn_weights = simple_attention(q, k, v)
print("输出形状:", output.shape)
print("注意力权重:", attn_weights[0])
输出:
输出形状: torch.Size([2, 4, 8])
注意力权重:
tensor([[0.22, 0.24, 0.30, 0.24],
[0.25, 0.26, 0.27, 0.22],
...
])
你可以直观地看到,每一行表示当前词对其它词的“关注程度”。
你只需换输入来源,就能复用这个函数。模型就是这么“魔改”的:改的不是函数,而是数据流。
很多人听了几遍原理仍然晕,是因为少了手感。而当你亲手写出 Attention 后,你会发现:
“原来它并不复杂,复杂的是它应用的方式。”
今天先写到这里,明天出差,得准备行李了