查看全课大纲(第 46 / 69 节)
- 1.NLP 的研究领域、应用与学习路径
- 2.自然语言的基本术语
- 3.信息论基础:信息熵与交叉熵
- 4.语言的进化与语义变迁
- 5.主观概率、客观概率与条件概率
- 6.贝叶斯原理与推理
- 7.随机变量:二项式、期望方差与联合概率
- 8.常见概率分布
- 9.辛普森悖论与贝叶斯解题实例
- 10.语言模型:语言概率与词袋模型
- 11.n-gram 语言模型(二元 / 三元 / N-Gram)
- 12.语言模型评价:交叉熵、困惑度与插值
- 13.朴素贝叶斯文本分类:垃圾邮件
- 14.拼写检查
- 15.中文分词:构词法、最大匹配、最优路径与 Jieba
- 16.词表示问题与语义相似度
- 17.TF-IDF 权重处理
- 18.One-Hot 表达与词表示的局限
- 19.Word2Vec 核心思想
- 20.Word2Vec 训练过程
- 21.Skip-gram 模型详解
- 22.词性标注任务与标注方法(规则 + N-Gram)
- 23.EM 算法与混合模型
- 24.高斯混合模型 GMM
- 25.从混合模型到隐马尔可夫模型
- 26.隐马尔可夫模型(HMM)推导与维特比解码
- 27.线性链条件随机场 CRF
- 28.贝叶斯网络与图模型
- 29.隐马尔可夫链与 HMM 推导
- 30.PLSA 主题模型
- 31.LSI 与 SVD 向量表达
- 32.LDA 与狄利克雷分布
- 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
- 34.上下文无关文法(CFG)与句法分析
- 35.概率上下文无关文法 PCFG
- 36.统计机器翻译:词对齐与翻译模型
- 37.统计机器翻译中的对齐模型与参数估计
- 38.从统计机器翻译到神经机器翻译
- 39.神经元与反向传播速览(指向深度学习课)
- 40.CNN 原理及其在 NLP 中的应用
- 41.循环神经网络的基本原理与 Elman / Jordan 网络
- 42.LSTM:长短期记忆网络的核心思想与分步实现
- 43.Encoder-Decoder 框架
- 44.Seq2Seq 与早期注意力机制
- 45.注意力机制的背景与动机
- 46.注意力模型实现原理与匹配函数
- 47.记忆网络:组成与计算
- 48.注意力机制的应用与历史延展
- 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
- 50.Multi-Head Attention、FFNN 与位置编码
- 51.层归一化、损失函数与 Universal Transformer 延展
- 52.预训练范式:从 ELMo 到 BERT
- 53.BERT 与掩码语言模型
- 54.BERT 下游任务微调与 GLUE 口径
- 55.GPT 与自回归语言建模
- 56.GPT-1 到 GPT-4 的演进
- 57.指令微调与 RLHF
- 58.分词器:BPE、WordPiece 与中文词表
- 59.上下文窗口与长文本处理
- 60.能力边界、成本与适用场景
- 61.文本分类实战:从朴素贝叶斯到逻辑回归
- 62.命名实体识别实战
- 63.文本生成与摘要实战
- 64.语义检索与向量库实战
- 65.RAG 知识库问答
- 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
- 67.幻觉与事实性问题
- 68.文本与知识结合(知识图谱压缩)
- 69.跨媒体信息检索与未来展望
注意力模型实现原理与匹配函数
小象实战讲义 · 自然语言处理
在上一节我们了解了注意力机制的基本思想和宏观架构。本节课,我们将深入其内部,详细拆解注意力模型的具体实现原理,并介绍一个关键组件——匹配函数(Match Function)。理解这些细节,是掌握从传统Seq2Seq+Attention到现代Transformer自注意力机制的关键一步。
💡 核心导读
- 注意力计算流程:掌握从查询(Query)与键(Key)的相似度计算,到权重归一化(Softmax),再到加权求和得到上下文向量(Context Vector)的完整三步流程。
- 点积作为相似度度量:理解为何在向量化表示下,点积(Dot Product)能有效衡量语义相关性,并作为注意力分数的基础。
- 匹配函数的作用:了解匹配函数在更复杂的注意力架构(如记忆网络)中,如何用于计算查询与外部记忆(Memory)之间的关联强度。
- 从原理到应用:通过机器翻译的实例,直观感受注意力权重如何动态分配,并理解其相对于传统编码器-解码器模型的优势。
注意力机制的计算原理
让我们从一个具体的场景出发:基于注意力机制的序列到序列(Seq2Seq)模型,常用于机器翻译。假设我们要将中文“我爱中国”翻译成英文“I love China”。
在传统的编码器-解码器模型中,编码器将整个源语句压缩成一个固定长度的上下文向量,解码器仅基于这个单一的向量生成目标语句。这就像要求你在只记住一句话的“中心思想”后,逐词复述出另一种语言的版本,对于长句子来说信息损失严重。
注意力机制的引入,让解码器在生成每一个目标词时,都能“回顾”编码器对所有源词的处理结果,并动态决定当前应该“关注”源语句的哪些部分。
1. 核心计算步骤
注意力计算可以分解为三个清晰的步骤:评分(Score)、归一化(Softmax)、加权求和(Weighted Sum)。
我们定义一些符号:
- 编码器隐藏状态:源语句经过编码器(如双向RNN/LSTM)处理后,每个输入词
i都对应一个隐藏状态向量h_i。这些向量共同构成了注意力可查询的“记忆库”。 - 解码器上一时刻隐藏状态:在生成目标词
t的时刻,解码器上一时刻(t-1)的隐藏状态s_{t-1}被用作查询(Query),因为它包含了到目前生成为止的上下文信息。 - 目标:计算一个上下文向量(Context Vector)
c_t,它是对所有编码器隐藏状态h_i的加权和,权重反映了s_{t-1}与每个h_i的相关性。
步骤一:计算注意力分数(Score) 对于每一个编码器隐藏状态 h_i,我们计算其与查询 s_{t-1} 的相似度得分 e_{t,i}。 一种最直接且常用的方法是点积(Dot Product): e_{t,i} = s_{t-1}^T \cdot h_i 为什么用点积?因为 h_i 和 s_{t-1} 都是经过网络学习得到的向量,其维度相同。点积同时包含方向与模长两方面的信息:。在模长大致相当时,点积越大通常表示方向越接近、夹角越小,语义上可能越相关;但模长较大的向量即使方向并非更接近,也可能得到更大的点积,因此点积大小不能直接等同于夹角大小。结果越小(甚至为负),则表示方向越偏离,相关性越低。这为我们提供了一种衡量“关注度”的原始分数。
步骤二:权重归一化(Softmax) 原始分数 e_{t,i} 的大小范围不确定,我们需要将其转换为一个概率分布,即所有权重之和为1。这通过Softmax函数实现: \alpha_{t,i} = \frac{\exp(e_{t,i})}{\sum_{j=1}^{N} \exp(e_{t,j})} 这里 N 是源语句长度。\alpha_{t,i} 就是最终的注意力权重,它表示在生成第 t 个目标词时,模型对第 i 个源词的关注程度。例如,\alpha_{t,2}=0.7 意味着模型认为当前目标词70%的信息应来源于第二个源词。
步骤三:计算上下文向量(Weighted Sum) 最后,我们将所有编码器隐藏状态 h_i 按其对应的注意力权重 \alpha_{t,i} 进行加权求和,得到上下文向量 c_t: c_t = \sum_{i=1}^{N} \alpha_{t,i} h_i 这个 c_t 不再是整个句子的笼统概括,而是针对当前生成步骤动态聚焦后的源端信息摘要。解码器将 c_t 与自身的当前状态结合,来预测下一个目标词。
2. 一个简化的数值示例
让我们通过一个极度简化的例子来直观感受这个过程。假设向量维度为3,我们有一个查询向量和两个“记忆”向量。
import numpy as np
# 固定随机种子以确保输出可复现
np.random.seed(42)
# 定义向量
# s_{t-1}: 解码器上一时刻的隐藏状态 (Query)
s_prev = np.array([0.8, -0.2, 0.5])
# h1, h2: 编码器对两个源词的隐藏状态 (Keys/Values)
h1 = np.array([0.1, 0.9, 0.3])
h2 = np.array([0.7, 0.2, -0.5])
print("查询向量 s_prev:", s_prev)
print("编码器状态 h1:", h1)
print("编码器状态 h2:", h2)
print()
# 步骤1: 计算点积分数
score1 = np.dot(s_prev, h1)
score2 = np.dot(s_prev, h2)
print("原始注意力分数 e_t1 (s·h1):", round(score1, 4))
print("原始注意力分数 e_t2 (s·h2):", round(score2, 4))
print()
# 步骤2: Softmax 归一化得到权重
e_scores = np.array([score1, score2])
attention_weights = np.exp(e_scores) / np.sum(np.exp(e_scores))
print("Softmax 后的注意力权重 alpha:")
print(" alpha_t1 (对h1):", round(attention_weights[0], 4))
print(" alpha_t2 (对h2):", round(attention_weights[1], 4))
print(" 权重和:", round(np.sum(attention_weights), 4))
print()
# 步骤3: 计算上下文向量 (加权和)
context_vector = attention_weights[0] * h1 + attention_weights[1] * h2
print("上下文向量 c_t (alpha1*h1 + alpha2*h2):")
print(" ", np.round(context_vector, 4))查询向量 s_prev: [ 0.8 -0.2 0.5]
编码器状态 h1: [0.1 0.9 0.3]
编码器状态 h2: [ 0.7 0.2 -0.5]
原始注意力分数 e_t1 (s·h1): 0.05
原始注意力分数 e_t2 (s·h2): 0.27
Softmax 后的注意力权重 alpha:
alpha_t1 (对h1): 0.4452
alpha_t2 (对h2): 0.5548
权重和: 1.0
上下文向量 c_t (alpha1*h1 + alpha2*h2):
[ 0.4329 0.5117 -0.1438]从结果可以看出,查询向量 s_prev 与 h2 的点积分数(0.27)高于与 h1 的分数(0.05),因此经过Softmax后,h2 获得了更高的注意力权重(0.5548),h1 为 0.4452,两者差距并不大。最终的上下文向量 c_t 是 h1 和 h2 的加权混合,但更偏向于 h2。
匹配函数(Match Function)的作用
在上述标准注意力中,匹配函数就是点积操作。它定义了如何计算查询(Query)与键(Key)之间的相似度。然而,在更复杂的架构中,例如记忆网络(Memory Networks),匹配函数的设计可以更加灵活和强大。
记忆网络显式地维护一个外部记忆矩阵,用于存储事实、知识或上下文信息。当有一个查询(如一个问题)输入时,模型需要在这个记忆库中找到最相关的信息片段来生成答案。这个过程通常涉及多跳(Multi-hop)推理。
匹配函数的设计
在记忆网络中,匹配函数 M(q, m) 负责计算查询向量 q 与记忆槽向量 m 之间的匹配分数。这个函数不限于点积,可以是:
- 点积(Dot Product):
M(q, m) = q^T m。简单高效,假设向量在同一空间。 - 双线性变换(Bilinear):
M(q, m) = q^T W m。其中W是一个可学习的权重矩阵。这允许模型学习查询空间和记忆空间之间更复杂的交互关系。 - 多层感知机(MLP):
M(q, m) = MLP(concat(q, m))。将查询和记忆向量拼接后送入一个神经网络,输出一个标量分数。表达能力最强,但参数更多。
匹配函数的输出分数会经过Softmax,转化为从记忆库中读取不同记忆槽的概率权重,类似于标准注意力中的权重 \alpha。
多跳推理示例
考虑一个简单的阅读理解任务,知识(记忆)如下:
- John在操场。
- John捡起了足球。
- Bob去了厨房。
问题:足球在哪里?
单跳匹配(仅用问题匹配记忆):
- 匹配函数计算“Where is the football?”与每个记忆的分数。
- 记忆2(“John picked up the football”)可能得分最高,因为它直接包含“football”。
- 但记忆2并没有直接给出位置。
多跳推理(两跳):
- 第一跳:问题匹配记忆库,找到最相关的记忆(记忆2)。
- 第二跳:将第一跳的输出(一个关于记忆2的向量表示)与原始问题结合,形成一个新的、更丰富的查询向量。
- 用这个新查询再次匹配记忆库。此时,由于新查询包含了“John”和“football”的联合信息,它可能更强烈地匹配记忆1(“John在操场”),从而得出答案“操场”。
# 伪代码示意多跳记忆网络的匹配过程(非可执行)
# 记忆库 M = [m1, m2, m3] # 每个mi是一个向量
# 初始查询 q0 = embed("Where is the football?")
# 可学习的匹配函数 match(q, m) -> score
# 第一跳
scores_hop1 = [match(q0, mi) for mi in M]
weights_hop1 = softmax(scores_hop1)
o1 = sum(weights_hop1[i] * M[i] for i in range(len(M))) # 读取的向量
# 更新查询:q1 = R(q0 + o1),其中R是一个可学习的变换(如全连接层)
q1 = transform(q0 + o1)
# 第二跳
scores_hop2 = [match(q1, mi) for mi in M]
weights_hop2 = softmax(scores_hop2)
o2 = sum(weights_hop2[i] * M[i] for i in range(len(M)))
# 最终答案基于 o2 预测
answer = predict_answer(o2)匹配函数在这里是核心,它决定了模型如何评估查询与每一条记忆的相关性,从而引导多跳推理的路径。
注意力权重的可视化意义
注意力机制不仅有效,而且具有较好的可解释性。通过将注意力权重 \alpha_{t,i} 可视化,我们可以直观地看到在翻译过程中,目标词与源词之间的“对齐”关系。
例如,在翻译“I love China”时,生成“love”的时刻,注意力权重可能会在“爱”和“热爱”等词上形成高峰。而生成“China”时,高峰则集中在“中国”上。对于更复杂的句子,如将“我热爱这个国家”翻译成“I love this country”,生成“love”时的注意力可能会同时分布在“热”和“爱”上,体现了翻译的非一一对应性。
这种动态的、软性的对齐,是统计机器翻译中硬对齐的巨大进步,也是神经机器翻译成功的关键因素之一。
📝 动手练一练
- 概念辨析:在标准的Seq2Seq注意力模型中,计算注意力权重时,为什么要在点积分数上使用Softmax函数,而不是直接使用归一化后的点积值(如除以最大值)?
👉 点击查看参考答案
Softmax函数能够将一组实数(点积分数)转换为一个合法的概率分布,即所有输出值在(0,1)区间且和为1。这具有两个关键优势:
- 概率解释:权重可以被解释为“概率”,表示模型将多少“注意力”分配给对应的源词,非常直观。
- 突出主要项:Softmax的指数运算会按相对分差放大高分值与低分值之间的差距。分布是否尖锐取决于分差相对于温度(缩放系数)的大小:分差较大时最大项概率被明显抬高;分差较小时输出仍可接近均匀,如本节 0.05 与 0.27 的示例,Softmax 后也只得到 0.4452 与 0.5548,并非“一个赢家全拿”。而简单的线性归一化(如除以和或最大值)不具备这种非线性放大效果,可能导致注意力过于分散。
- 手算练习:假设在某个解码时刻,查询向量
q = [1, 0, -1],三个键向量分别为k1 = [0, 1, 0],k2 = [1, 0, 1],k3 = [-1, 0, -1]。请手动计算(或编写一小段Python代码验证)使用点积注意力时,每个键的注意力权重\alpha_i。
👉 点击查看参考答案
首先计算点积分数:
e1 = q·k1 = 1*0 + 0*1 + (-1)*0 = 0e2 = q·k2 = 1*1 + 0*0 + (-1)*1 = 0e3 = q·k3 = 1*(-1) + 0*0 + (-1)*(-1) = 0
三个分数都是0。应用Softmax:exp(0)=1,分母为 1+1+1=3。 因此,每个键的注意力权重均为 \alpha1 = \alpha2 = \alpha3 = 1/3 ≈ 0.3333。 这里点积为 0 只说明查询向量与这些键向量在当前表示和这种打分方式下正交(夹角为 90°),并不等同于语义上完全无关;而注意力被均匀分配,直接原因是三个分数恰好相等(都为 0),Softmax 对相等分数给出均匀权重。这也说明了为什么需要让模型学习到有意义的向量表示,使得相关的查询和键能有更高的点积分数。
import numpy as np
q = np.array([1, 0, -1])
keys = np.array([[0,1,0], [1,0,1], [-1,0,-1]])
scores = np.dot(keys, q) # 计算点积
weights = np.exp(scores) / np.sum(np.exp(scores))
print("点积分数:", scores)
print("注意力权重:", np.round(weights, 4))点积分数: [0 0 0]
注意力权重: [0.3333 0.3333 0.3333]本章小结
本节我们深入剖析了注意力模型的核心实现原理。关键在于理解其三步计算流程:评分-归一化-加权求和。点积作为最基础的匹配函数,因其计算简单且能有效反映向量相似度而被广泛使用。我们还探讨了在记忆网络等复杂架构中,匹配函数的扩展形式及其在多跳推理中的作用。
注意力机制的本质是让模型学会动态的、有选择性的信息检索。它解决了传统Seq2Seq模型信息瓶颈的问题,为处理长序列、复杂对齐任务提供了强大工具,并直接启发了后续Transformer模型中自注意力机制的诞生。
📋 行动清单
- 在白板或纸上手动推导一遍注意力计算的三步流程,确保理解每个步骤的输入、输出和数学形式。
- 尝试修改上一节中的注意力计算示例,将点积匹配函数替换为其他形式(如计算余弦相似度),并观察权重分布的变化。
- 思考:在聊天机器人或文本摘要任务中,注意力机制可以帮助模型关注用户输入或原文的哪些关键部分?
—— 小象教研组