查看全课大纲(第 45 / 69 节)
  1. 1.NLP 的研究领域、应用与学习路径
  2. 2.自然语言的基本术语
  3. 3.信息论基础:信息熵与交叉熵
  4. 4.语言的进化与语义变迁
  5. 5.主观概率、客观概率与条件概率
  6. 6.贝叶斯原理与推理
  7. 7.随机变量:二项式、期望方差与联合概率
  8. 8.常见概率分布
  9. 9.辛普森悖论与贝叶斯解题实例
  10. 10.语言模型:语言概率与词袋模型
  11. 11.n-gram 语言模型(二元 / 三元 / N-Gram)
  12. 12.语言模型评价:交叉熵、困惑度与插值
  13. 13.朴素贝叶斯文本分类:垃圾邮件
  14. 14.拼写检查
  15. 15.中文分词:构词法、最大匹配、最优路径与 Jieba
  16. 16.词表示问题与语义相似度
  17. 17.TF-IDF 权重处理
  18. 18.One-Hot 表达与词表示的局限
  19. 19.Word2Vec 核心思想
  20. 20.Word2Vec 训练过程
  21. 21.Skip-gram 模型详解
  22. 22.词性标注任务与标注方法(规则 + N-Gram)
  23. 23.EM 算法与混合模型
  24. 24.高斯混合模型 GMM
  25. 25.从混合模型到隐马尔可夫模型
  26. 26.隐马尔可夫模型(HMM)推导与维特比解码
  27. 27.线性链条件随机场 CRF
  28. 28.贝叶斯网络与图模型
  29. 29.隐马尔可夫链与 HMM 推导
  30. 30.PLSA 主题模型
  31. 31.LSI 与 SVD 向量表达
  32. 32.LDA 与狄利克雷分布
  33. 33.主题模型实验复现(困惑度 / 分类 / 相似度 / 延展)
  34. 34.上下文无关文法(CFG)与句法分析
  35. 35.概率上下文无关文法 PCFG
  36. 36.统计机器翻译:词对齐与翻译模型
  37. 37.统计机器翻译中的对齐模型与参数估计
  38. 38.从统计机器翻译到神经机器翻译
  39. 39.神经元与反向传播速览(指向深度学习课)
  40. 40.CNN 原理及其在 NLP 中的应用
  41. 41.循环神经网络的基本原理与 Elman / Jordan 网络
  42. 42.LSTM:长短期记忆网络的核心思想与分步实现
  43. 43.Encoder-Decoder 框架
  44. 44.Seq2Seq 与早期注意力机制
  45. 45.注意力机制的背景与动机
  46. 46.注意力模型实现原理与匹配函数
  47. 47.记忆网络:组成与计算
  48. 48.注意力机制的应用与历史延展
  49. 49.Transformer 结构:Encoder-Decoder 栈与 Self-Attention
  50. 50.Multi-Head Attention、FFNN 与位置编码
  51. 51.层归一化、损失函数与 Universal Transformer 延展
  52. 52.预训练范式:从 ELMo 到 BERT
  53. 53.BERT 与掩码语言模型
  54. 54.BERT 下游任务微调与 GLUE 口径
  55. 55.GPT 与自回归语言建模
  56. 56.GPT-1 到 GPT-4 的演进
  57. 57.指令微调与 RLHF
  58. 58.分词器:BPE、WordPiece 与中文词表
  59. 59.上下文窗口与长文本处理
  60. 60.能力边界、成本与适用场景
  61. 61.文本分类实战:从朴素贝叶斯到逻辑回归
  62. 62.命名实体识别实战
  63. 63.文本生成与摘要实战
  64. 64.语义检索与向量库实战
  65. 65.RAG 知识库问答
  66. 66.NLP 评估指标:BLEU / ROUGE / BERTScore 与人工评估
  67. 67.幻觉与事实性问题
  68. 68.文本与知识结合(知识图谱压缩)
  69. 69.跨媒体信息检索与未来展望

注意力机制的背景与动机

小象实战讲义 · 自然语言处理

在深度学习席卷自然语言处理领域之后,我们拥有了强大的序列建模工具——循环神经网络(RNN)及其变体LSTM。然而,在处理复杂的序列到序列任务,如机器翻译、对话生成时,传统的编码器-解码器架构遇到了瓶颈:它需要将整个输入序列的信息压缩成一个固定长度的上下文向量,这往往导致信息丢失,特别是对于长序列。本节我们将回顾这一背景,并引出注意力机制的核心动机:让模型在生成输出的每一步,都能动态地“注意”输入序列中最相关的部分,从而实现对信息的精细化利用。

💡 核心导读

  • 背景回顾:从语音助手到对话代理,构建一个优秀的智能系统需要结合长期知识(知识库)和短期交互(对话历史),这超出了传统机器学习模型的范畴。
  • 模型演进:从词向量(Word2Vec)到RNN/LSTM,再到Seq2Seq模型,我们回顾了神经网络处理序列数据的关键步骤及其局限性。
  • 核心问题:传统Seq2Seq模型将整个输入序列编码为单一固定向量,导致信息瓶颈和长距离依赖丢失。
  • 解决思路:引入注意力机制,允许解码器在生成每个词时,动态地“回顾”并加权关注编码器的所有隐藏状态,从而突破信息瓶颈。
  • 前瞻:注意力机制是通往更强大模型(如Transformer)的基石,它启发了对“记忆”和“知识”如何融入神经网络的思考。

从智能助手到记忆需求

自然语言处理的终极目标之一是构建能够与人自然交互的智能助手,例如各类语音助手、对话助手。这类系统远不止是语音识别或简单的模式匹配。

一个理想的对话代理(Dialogue Agent)需要满足几个关键条件:

  1. 整合知识(Combine Knowledge):能够结合外部知识库(如维基百科、电影数据库)来完成复杂任务,而不仅仅是依赖训练数据中的统计模式。
  2. 开放式对话(Open-ended Conversation):能够进行多轮、无固定终点的长对话,而非简单的单轮问答。
  3. 学习新任务(Learn New Tasks):能够从对话交互中学习并适应新的任务。

传统的机器学习模型,尤其是那些将数据抽象为参数模型的(如线性回归、神经网络),在训练完成后就“遗忘”了原始数据,只保留了参数。这不利于整合大量外部知识。相反,像K近邻(KNN)这样的算法需要存储所有数据。构建智能系统,需要一种能够有效存储和访问“记忆”(知识)的机制。

序列建模的演进与瓶颈

词向量:离散符号的连续化

一切始于如何表示文字。早期使用独热编码(One-Hot Representation),每个词是词典长度的一个稀疏向量。通过神经网络语言模型等工作,我们可以将词嵌入到低维连续空间,得到词向量(Word Embedding),如Word2Vec(如 Skip-gram)。这使得神经网络可以处理连续的数值向量,而非离散的符号。

# 示例:一个简单的词向量查找表(Lookup Table)
import numpy as np

# 小词典与预先给定的固定词向量(这里用手工设定的小数演示查找表结构;真实词向量由训练得到)
vocab = ['cat', 'dog', 'apple', 'banana']
embedding_matrix = np.array([
    [ 0.21, -0.35,  0.10],
    [ 0.18, -0.30,  0.05],
    [-0.12,  0.40,  0.22],
    [-0.10,  0.36,  0.18],
])

# 构建词到索引的映射
word_to_idx = {word: i for i, word in enumerate(vocab)}

def get_embedding(word):
    idx = word_to_idx.get(word)
    if idx is not None:
        return embedding_matrix[idx]
    else:
        return None

# 获取'dog'的词向量
dog_embedding = get_embedding('dog')
print(f"词向量 'dog': {dog_embedding}")
词向量 'dog': [ 0.18 -0.3   0.05]

RNN与LSTM:捕捉时序依赖

语言具有时序特征。循环神经网络(RNN) 专为处理序列数据设计,其隐藏状态理论上可以捕捉之前所有时间步的信息。然而,标准RNN存在梯度消失/爆炸问题,难以学习长距离依赖。

长短期记忆网络(LSTM) 通过引入门控机制(输入门、遗忘门、输出门)缓解了梯度消失、改善长距离依赖学习;需要注意它并不能完全消除梯度问题,梯度爆炸仍可能通过梯度裁剪等手段处理。LSTM 的门控与更新权重由训练得到;细胞状态(Cell State)是随当前序列逐步更新的内部激活,可视为一种内部“记忆”,但承载的仍是参数化模型内部的信息,而非对外部知识的显式存储。

Seq2Seq模型:编码器-解码器架构

对于机器翻译、摘要生成等任务,序列到序列(Seq2Seq)模型成为标准架构。它包含两部分:

  • 编码器(Encoder):一个RNN/LSTM,将输入序列(如源语言句子)编码成一个上下文向量(Context Vector),通常是编码器最后一个隐藏状态。
  • 解码器(Decoder):另一个RNN/LSTM,以上下文向量为初始状态,逐步生成输出序列(如目标语言句子)。
编码过程(Encoder):
输入: [A, B, C, D] -> RNN/LSTM -> 上下文向量 c

解码过程(Decoder):
初始状态: c
生成第一步: 输入 <START>,输出 W,状态更新
生成第二步: 输入 W,输出 X,状态更新
生成第三步: 输入 X,输出 Y,状态更新
生成第四步: 输入 Y,输出 Z,状态更新
输出序列: [W, X, Y, Z]

然而,这种架构存在一个根本性缺陷:信息瓶颈。无论输入序列多长,所有信息都必须压缩进一个固定维度的上下文向量中。对于长句子,靠前的信息很容易在压缩过程中丢失或衰减,导致翻译质量下降,特别是细节的遗漏。

注意力机制的引入

为了解决信息瓶颈问题,注意力机制应运而生。其核心思想是:在解码器生成每一个词的时候,不去仅仅依赖那个单一的、压缩过的上下文向量,而是让解码器能够“访问”编码器在所有时间步产生的全部隐藏状态,并动态地决定当前应该更“注意”哪些隐藏状态。

具体来说:

  1. 解码器在生成第 t 个目标词时,会计算一个注意力权重分布。
  2. 这个权重分布基于解码器当前的隐藏状态与编码器所有隐藏状态的相关性。
  3. 用这个权重对编码器的隐藏状态进行加权求和,得到一个动态的上下文向量,这个向量聚焦于当前生成步骤最相关的输入部分。
  4. 解码器结合这个动态上下文向量和自身的隐藏状态,来预测当前输出的词。
# 注意力机制核心计算步骤的简化模拟(使用纯NumPy)
import numpy as np

# 模拟编码器的隐藏状态(假设有3个时间步,每个隐藏状态维度为4)
encoder_hidden_states = np.array([
    [0.1, 0.2, 0.3, 0.4],
    [0.5, 0.6, 0.7, 0.8],
    [0.9, 1.0, 1.1, 1.2]
])

# 模拟解码器在某个时间步的隐藏状态(维度需与编码器隐藏状态匹配或可投影)
decoder_hidden_state = np.array([0.3, 0.4, 0.5, 0.6])

# 1. 计算相关性得分(这里使用点积注意力)
scores = np.dot(encoder_hidden_states, decoder_hidden_state)
print(f"相关性得分: {scores.round(2)}")

# 2. 将得分转换为注意力权重(使用softmax)
def softmax(x):
    exp_x = np.exp(x - np.max(x)) # 数值稳定性处理
    return exp_x / exp_x.sum()

attention_weights = softmax(scores)
print(f"注意力权重: {attention_weights.round(4)}")

# 3. 加权求和得到动态上下文向量
context_vector = np.dot(attention_weights, encoder_hidden_states)
print(f"读取的向量(动态上下文向量): {context_vector.round(4)}")
相关性得分: [0.5  1.22 1.94]
注意力权重: [0.1375 0.2824 0.5802]
读取的向量(动态上下文向量): [0.6771 0.7771 0.8771 0.9771]

通过这种方式,模型在生成“apple”时,可以高度关注输入序列中的“苹果”;在生成“red”时,则可以关注“红”。这极大地提升了模型处理长序列和复杂对齐关系的能力。

从注意力到记忆网络

注意力机制的本质是一种软寻址(Soft Addressing) 的读取操作:给定一个查询(解码器状态),从一组键值对(编码器状态)中检索出最相关的信息。这启发了研究者对神经网络与记忆系统结合的探索。

记忆网络(Memory Network) 将这种思想推广:显式地引入一个外部记忆模块(Memory Module),其中存储着知识(如事实三元组、故事句子)。一个控制器模块(Controller Module)(通常是一个神经网络)通过注意力机制(寻址)从记忆模块中读取相关信息,并结合当前输入(问题)来生成输出(答案)。

记忆网络基本结构:
输入(问题) -> 控制器 -> 生成查询向量
                                     |
                                     v
记忆模块 [记忆向量1, 记忆向量2, ...] <- 注意力寻址(点积+softmax)
                                     |
                                     v
                             加权求和 -> 读取的向量
                                     |
                                     v
控制器结合读取的向量和内部状态 -> 输出(答案)

这种架构使得模型能够显式地存储和访问大量知识,更接近我们期望的智能助手所需的长短期记忆结合的能力。

📝 动手练一练

  1. 概念辨析:传统Seq2Seq模型中的上下文向量与引入注意力机制后的动态上下文向量有何本质区别?注意力机制如何缓解了“信息瓶颈”问题?
👉 点击查看参考答案

本质区别:

  • 传统上下文向量:是编码器处理整个输入序列后产生的单一、固定的向量(通常是最后一个隐藏状态)。它承载了所有输入信息的“平均”或“总结”。
  • 动态上下文向量:是解码器在每个输出时间步,根据当前状态,通过注意力权重对编码器所有隐藏状态进行加权求和得到的向量。它是动态变化、与当前输出步骤相关的。

如何缓解信息瓶颈: 注意力机制允许解码器在生成每个词时,直接“看到”并聚焦于输入序列中最相关的部分,而不是被迫从那个可能已经丢失细节的单一固定向量中提取所有信息。这相当于将解码器与编码器之间的连接从“单线宽带”变成了“可调节的多路访问”,极大地提升了信息传递的容量和精度,特别是对于长序列。

  1. 手算练习:假设编码器有三个隐藏状态:h1 = [1, 0], h2 = [0, 1], h3 = [1, 1]。解码器当前隐藏状态 s = [0.5, 0.5]。使用点积注意力,计算解码器对这三个编码器状态的注意力权重,并计算得到的动态上下文向量。
👉 点击查看参考答案

计算步骤:

  1. 计算点积得分:

    • score1 = [1,0]·[0.5,0.5] = 1*0.5 + 0*0.5 = 0.5
    • score2 = [0,1]·[0.5,0.5] = 0*0.5 + 1*0.5 = 0.5
    • score3 = [1,1]·[0.5,0.5] = 1*0.5 + 1*0.5 = 1.0
    • scores = [0.5, 0.5, 1.0]
  2. 计算softmax权重(exp(x) 后归一化):

    • exp_scores = [exp(0.5), exp(0.5), exp(1.0)] ≈ [1.6487, 1.6487, 2.7183]
    • sum_exp = 1.6487 + 1.6487 + 2.7183 ≈ 6.0157
    • weights = exp_scores / sum_exp ≈ [0.274, 0.274, 0.452] (四舍五入)
  3. 计算动态上下文向量(加权和):

    • context = 0.274*[1,0] + 0.274*[0,1] + 0.452*[1,1]
    • context = [0.274*1 + 0.274*0 + 0.452*1, 0.274*0 + 0.274*1 + 0.452*1]
    • context = [0.274 + 0 + 0.452, 0 + 0.274 + 0.452]
    • context = [0.726, 0.726]

因此,注意力权重约为 [0.274, 0.274, 0.452],动态上下文向量约为 [0.726, 0.726]。可以看到,模型更关注 h3(得分最高),因为它与解码器状态 s 最“相关”。

本章小结

本节我们回顾了注意力机制产生的背景。从构建需要结合长短期记忆的智能助手这一目标出发,我们梳理了神经网络处理序列数据的演进路径:从词向量到RNN/LSTM,再到Seq2Seq模型。我们明确指出传统Seq2Seq架构的信息瓶颈问题是引入注意力机制的直接动因。注意力机制通过让解码器动态地、有选择地关注编码器的全部隐藏状态,实现了信息的精细化利用,显著提升了长序列任务的处理能力。这一机制不仅是改进Seq2Seq模型的关键,其“查询-键-值”的软寻址思想,更是后续记忆网络、Transformer等革命性架构的核心基础。

📋 行动清单

  • 理解传统Seq2Seq模型(编码器-解码器)的工作流程及其信息瓶颈问题。
  • 掌握注意力机制的核心计算步骤:相关性得分计算、softmax权重归一化、加权求和得到动态上下文向量。
  • 能够通过简单的向量示例,手动计算注意力权重和上下文向量,体会其“聚焦”作用。
  • 思考注意力机制与记忆网络在“外部知识访问”这一理念上的联系。

—— 小象教研组