查看全课大纲(第 19 / 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.跨媒体信息检索与未来展望
Word2Vec 核心思想
小象实战讲义 · 自然语言处理
在上一节,我们探讨了基于统计的共现矩阵方法(如 TF-IDF、PMI)来获取词向量。这些方法直观,但面临稀疏性和计算效率的挑战。本节我们将进入词向量学习的经典里程碑——Word2Vec。它通过一个精巧的神经网络模型,从大规模文本中高效学习出高质量的稠密词向量,其核心思想“通过上下文预测中心词”或“通过中心词预测上下文”深刻影响了后续的表示学习。理解 Word2Vec 不仅是掌握词嵌入的关键,也是通往现代预训练模型的重要基石。
💡 核心导读
- 从统计到预测:Word2Vec 将词向量学习从一个统计计数问题,转变为一个基于神经网络的预测问题,通过优化一个预测任务来间接学习词向量。
- 两种经典架构:掌握连续词袋模型(CBOW)和跳字模型(Skip-gram)的核心区别:CBOW 用上下文预测中心词,适合高频词;Skip-gram 用中心词预测上下文,对低频词更友好。
- 嵌入矩阵的妙用:理解 Word2Vec 模型中“输入嵌入矩阵”和“输出嵌入矩阵”的双重角色,以及它们如何将高维的 One-Hot 向量映射到低维的稠密空间。
- 效率是关键:初步了解 Word2Vec 为了提升训练效率而引入的层次 Softmax 和负采样技术,它们避免了在全词表上进行昂贵的概率计算。
神经语言模型:Word2Vec 的思想源头
在深入 Word2Vec 之前,我们需要回顾其思想源头——神经语言模型(Neural Language Model)。传统的 n-gram 语言模型通过统计历史词序列的出现频率来预测下一个词,例如:
- 二元(Bigram)模型:
- 三元(Trigram)模型:
但这种模型面临“维度灾难”:随着历史长度 增加,可能的词序列组合呈指数级增长,导致数据极其稀疏。Bengio 等人在 2003 年提出了一种创新的思路:用一个神经网络函数 来逼近整个历史序列对当前词的联合影响。
其核心公式可以抽象为:
这里, 是词 的稠密向量表示(即词向量)。模型的工作流程如下:
- 将固定窗口内的 个历史词的 One-Hot 向量,分别乘以一个共享的嵌入矩阵 ,得到它们的稠密向量表示。
- 将这些稠密向量拼接或平均后,输入到一个多层前馈神经网络中。
- 网络输出层是一个 Softmax,产生在整个词表 上的概率分布,目标是使真实下一个词 的概率最大。
通过反向传播算法,同时优化神经网络的权重参数 和嵌入矩阵 。训练完成后,矩阵 的每一行就对应一个词的词向量。
这个模型的重大意义在于:
- 分布式表示:词的含义被分布在一个低维连续向量的各个维度上,语义相似的词会有相似的向量。
- 自动特征学习:神经网络自动从数据中学习出有效的特征表示,而非人工设计。
然而,原始的神经语言模型计算成本很高,因为输出层的 Softmax 需要在整个词表(可能数万甚至数十万)上进行归一化。Word2Vec 的贡献,正是对上述思想进行了大幅简化,提出了更高效、更专注的词向量学习模型。
Word2Vec 模型架构:CBOW 与 Skip-gram
Word2Vec 摒弃了“预测下一个词”的严格语言模型目标,转而采用更直接的任务来学习词向量:学习词与其上下文之间的关系。它提出了两种具体的模型架构:
连续词袋模型(Continuous Bag-of-Words, CBOW)
CBOW 模型的目标是:给定一个中心词周围的上下文词,预测这个中心词本身。
假设我们有一个句子:... the quick brown fox jumps ...,设定窗口大小为 2。当中心词是 brown 时,其上下文词为 [the, quick, fox, jumps]。
CBOW 模型的处理步骤如下:
- 输入层:将上下文窗口中所有 个词的 One-Hot 向量作为输入( 为单边窗口大小)。
- 投影层:每个输入词的 One-Hot 向量与输入嵌入矩阵 相乘,得到其对应的输入词向量 。然后,将所有上下文词的输入向量进行平均,得到一个 维的上下文综合向量。
- 输出层:将平均后的上下文向量与另一个输出嵌入矩阵 相乘,得到一个 维的分数向量。
- Softmax:对分数向量应用 Softmax 函数,得到在词表 上每个词作为中心词的概率分布。
- 目标:最大化真实中心词
brown的概率。
其条件概率公式为: 其中, 是上下文词向量的平均, 是输出矩阵 中对应中心词 的列向量。
跳字模型(Skip-gram)
Skip-gram 模型的目标与 CBOW 相反:给定一个中心词,预测其周围窗口内的每一个上下文词。
同样以句子 ... the quick brown fox jumps ... 为例,中心词为 brown,窗口大小为 2。模型需要分别预测 the, quick, fox, jumps 出现的概率,并假设这些预测是相互独立的。
Skip-gram 模型的处理步骤如下:
- 输入层:将中心词的 One-Hot 向量作为输入。
- 投影层:输入向量与输入嵌入矩阵 相乘,得到中心词的输入向量 。
- 输出层:对于窗口内的每一个目标上下文位置,将中心词向量 与输出嵌入矩阵 相乘,得到一个 维的分数向量。
- Softmax:对每个分数向量独立应用 Softmax,得到该位置上下文词的概率分布。
- 目标:最大化所有上下文词(如
the,quick,fox,jumps)的联合概率(或对数概率之和)。
其条件概率公式为: 模型目标是最大化平均对数概率:
CBOW vs. Skip-gram:对比与应用
- CBOW:使用上下文(多个词)的平滑平均来预测中心词。训练速度通常更快,对高频词的表示学习效果更好。
- Skip-gram:使用一个中心词来预测多个上下文词。它将每个中心词-上下文词对都视为一个训练样本,因此能产生更多的训练样本,对低频词的表示学习效果更优,在大语料上表现通常更好。
在实际应用中,Skip-gram 更为常用。
从原理到实现:理解嵌入矩阵与计算流程
为了更具体地理解 Word2Vec 的计算过程,我们用一个极简的例子来演示 CBOW 和 Skip-gram 的前向传播步骤。请注意,以下代码仅为教学演示,不涉及实际的训练和优化。
import numpy as np
# ===== 第一部分:定义极简词汇表和参数 =====
# 假设我们的微型词汇表只有4个词
vocab = ["the", "quick", "brown", "fox"]
V = len(vocab) # 词表大小 = 4
d = 3 # 词向量维度 = 3 (为了演示方便,实际中常为100-300)
# 创建词到索引的映射
word_to_idx = {word: i for i, word in enumerate(vocab)}
# 随机初始化输入嵌入矩阵 V_in (|V| x d) 和输出嵌入矩阵 U_out (d x |V|)
# 注意:在实际Word2Vec中,这两个矩阵都是需要学习的参数。
np.random.seed(42) # 固定随机种子,确保结果可复现
V_in = np.random.randn(V, d).round(4) # 输入矩阵
U_out = np.random.randn(d, V).round(4) # 输出矩阵
print("词汇表索引映射:", word_to_idx)
print("\n随机初始化的输入嵌入矩阵 V_in (|V| x d):")
print(V_in)
print("\n随机初始化的输出嵌入矩阵 U_out (d x |V|):")
print(U_out)词汇表索引映射: {'the': 0, 'quick': 1, 'brown': 2, 'fox': 3}
随机初始化的输入嵌入矩阵 V_in (|V| x d):
[[ 0.4967 -0.1383 0.6477]
[ 1.523 -0.2342 -0.2341]
[ 1.5792 0.7674 -0.4695]
[ 0.5426 -0.4634 -0.4657]]
随机初始化的输出嵌入矩阵 U_out (d x |V|):
[[ 0.242 -1.9133 -1.7249 -0.5623]
[-1.0128 0.3142 -0.908 -1.4123]
[ 1.4656 -0.2258 0.0675 -1.4247]]现在,我们分别模拟 CBOW 和 Skip-gram 的前向传播。
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import numpy as np
np.random.seed(42) # 固定随机种子,确保输出可复现
vocab = ["the", "quick", "brown", "fox"]
V = len(vocab) # 词表大小 = 4
d = 3 # 词向量维度 = 3 (为了演示方便,实际中常为100-300)
word_to_idx = {word: i for i, word in enumerate(vocab)}
V_in = np.random.randn(V, d).round(4) # 输入矩阵
U_out = np.random.randn(d, V).round(4) # 输出矩阵
# ===== 第二部分:模拟 CBOW 前向传播 =====
# 假设句子片段 "... quick brown fox ...",窗口大小 m=1
# 中心词是 "brown" (索引2),上下文词是 ["quick", "fox"] (索引1和3)
context_words = ["quick", "fox"]
center_word = "brown"
# 步骤1&2: 获取上下文词的输入向量并平均
context_vec_sum = np.zeros(d)
for word in context_words:
one_hot = np.zeros(V)
one_hot[word_to_idx[word]] = 1
# 通过矩阵乘法获取输入向量: v_w = one_hot^T * V_in
# 等价于直接取 V_in 的对应行
word_vec = np.dot(one_hot, V_in)
context_vec_sum += word_vec
context_avg = context_vec_sum / len(context_words)
print(f"上下文词 '{context_words}' 的平均向量 context_avg:")
print(context_avg.round(4))
# 步骤3: 计算输出分数 (logits)
logits = np.dot(context_avg, U_out)
print(f"\n输出分数向量 logits:")
print(logits.round(4))
# 步骤4: 应用Softmax得到概率分布 (仅为演示,未考虑数值稳定性)
exp_logits = np.exp(logits)
probs = exp_logits / np.sum(exp_logits)
print(f"\n预测的概率分布 (Softmax输出):")
for i, word in enumerate(vocab):
print(f" P({word} | context) = {probs[i]:.4f}")
# 步骤5: 计算损失 (交叉熵)
true_center_idx = word_to_idx[center_word]
loss = -np.log(probs[true_center_idx])
print(f"\n对于中心词 '{center_word}' 的交叉熵损失: {loss:.4f}")上下文词 '['quick', 'fox']' 的平均向量 context_avg:
[ 1.0328 -0.3488 -0.3499]
输出分数向量 logits:
[ 0.0904 -2.0066 -1.4884 0.4104]
预测的概率分布 (Softmax输出):
P(the | context) = 0.3695
P(quick | context) = 0.0454
P(brown | context) = 0.0762
P(fox | context) = 0.5089
对于中心词 'brown' 的交叉熵损失: 2.5743
# —— 重新构建上文用到的微型数据(本节各代码块相互独立,可单独运行)
import numpy as np
np.random.seed(42) # 固定随机种子,确保输出可复现
vocab = ["the", "quick", "brown", "fox"]
V = len(vocab) # 词表大小 = 4
d = 3 # 词向量维度 = 3 (为了演示方便,实际中常为100-300)
word_to_idx = {word: i for i, word in enumerate(vocab)}
V_in = np.random.randn(V, d).round(4) # 输入矩阵
U_out = np.random.randn(d, V).round(4) # 输出矩阵
context_words = ["quick", "fox"]
center_word = "brown"
context_vec_sum = np.zeros(d)
for word in context_words:
one_hot = np.zeros(V)
one_hot[word_to_idx[word]] = 1
# 通过矩阵乘法获取输入向量: v_w = one_hot^T * V_in
# 等价于直接取 V_in 的对应行
word_vec = np.dot(one_hot, V_in)
context_vec_sum += word_vec
context_avg = context_vec_sum / len(context_words)
# ===== 第三部分:模拟 Skip-gram 前向传播 =====
# 使用相同的词汇表和随机矩阵
# 中心词为 "brown" (索引2),我们希望预测其上下文词,例如 "quick" (索引1) 和 "fox" (索引3)
center_word = "brown"
target_context_words = ["quick", "fox"] # 假设我们只考虑这两个位置
# 步骤1&2: 获取中心词向量
center_one_hot = np.zeros(V)
center_one_hot[word_to_idx[center_word]] = 1
v_c = np.dot(center_one_hot, V_in) # 中心词向量
print(f"中心词 '{center_word}' 的向量 v_c:")
print(v_c.round(4))
total_log_prob = 0
print("\n对每个目标上下文词进行预测:")
for target_word in target_context_words:
# 步骤3&4: 计算该目标词的概率
logits_sg = np.dot(v_c, U_out)
exp_logits_sg = np.exp(logits_sg)
probs_sg = exp_logits_sg / np.sum(exp_logits_sg)
target_idx = word_to_idx[target_word]
target_prob = probs_sg[target_idx]
target_log_prob = np.log(target_prob)
total_log_prob += target_log_prob
print(f" 预测 P({target_word} | {center_word}) = {target_prob:.4f}, log prob = {target_log_prob:.4f}")
# 步骤5: 计算平均对数概率 (Skip-gram的目标是最大化这个值)
avg_log_prob = total_log_prob / len(target_context_words)
print(f"\n对上下文词 '{target_context_words}' 的平均对数概率: {avg_log_prob:.4f}")
# 损失通常是负的平均对数概率
loss_sg = -avg_log_prob
print(f"对应的损失 (负的平均对数概率): {loss_sg:.4f}")中心词 'brown' 的向量 v_c:
[ 1.5792 0.7674 -0.4695]
对每个目标上下文词进行预测:
预测 P(quick | brown) = 0.0970, log prob = -2.3331
预测 P(fox | brown) = 0.3823, log prob = -0.9617
对上下文词 '['quick', 'fox']' 的平均对数概率: -1.6474
对应的损失 (负的平均对数概率): 1.6474通过以上演示,我们可以看到:
- 嵌入矩阵是核心:
V_in将 One-Hot 词映射为稠密向量,U_out用于计算预测分数。训练后,V_in的行向量通常作为最终的词向量使用(有时也会使用U_out的列向量或两者的组合)。 - 计算流程清晰:CBOW 先聚合上下文再预测;Skip-gram 直接用中心词进行多次预测。
- 效率瓶颈:计算
probs时需要对整个词表 进行 和求和,当 很大时(例如 10 万),这一步的计算成本极高。这正是 Word2Vec 提出负采样(Negative Sampling) 和层次 Softmax(Hierarchical Softmax) 等优化技术的根本原因,它们将在下一节详细讨论。
📝 动手练一练
- 概念辨析:假设我们有一个训练好的 Word2Vec 模型。现在给定句子“猫在沙发上睡觉”,窗口大小为 2。请分别说明,在 CBOW 和 Skip-gram 模型中,当中心词为“在”时,模型的输入和预测目标分别是什么?
👉 点击查看参考答案
CBOW 模型:
- 输入:中心词“在”的上下文词,即窗口内的词:
["猫", "沙发", "上"](注意:“睡觉”超出了窗口)。 - 预测目标:中心词“在”本身。模型的任务是根据输入的三个上下文词,预测出最可能出现在中间的那个词是“在”。
- 输入:中心词“在”的上下文词,即窗口内的词:
Skip-gram 模型:
- 输入:中心词“在”本身。
- 预测目标:中心词“在”的每一个上下文词,即分别预测
P("猫"|"在"),P("沙发"|"在"),P("上"|"在")。模型的任务是,给定“在”,使其周围出现“猫”、“沙发”、“上”这些词的概率尽可能大。
- 手算理解:沿用上面代码示例中的微型词汇表
["the", "quick", "brown", "fox"]、随机初始化的V_in和U_out矩阵。请手动计算(或写一小段代码验证),在 Skip-gram 模型中,当中心词为"the"(索引0) 时,模型预测上下文词"quick"(索引1) 的未归一化的分数(logit)是多少?即计算 。提示: 是V_in的第0行, 是U_out的第1列。
👉 点击查看参考答案
根据上文代码输出的矩阵(同一组随机初始化参数):
- = [0] =
[0.4967, -0.1383, 0.6477] - = [:, 1] =
[-1.9133, 0.3142, -0.2258]( 的列对应词)
计算点积:
所以,未归一化的分数约为 -1.1400。你可以运行以下代码进行验证:
import numpy as np
# 复用上文同一组 V_in、U_out(seed 42,保留4位小数)
V_in = np.array([[ 0.4967, -0.1383, 0.6477],
[ 1.523, -0.2342, -0.2341],
[ 1.5792, 0.7674, -0.4695],
[ 0.5426, -0.4634, -0.4657]])
U_out = np.array([[ 0.242, -1.9133, -1.7249, -0.5623],
[-1.0128, 0.3142, -0.908, -1.4123],
[ 1.4656, -0.2258, 0.0675, -1.4247]])
v_the = V_in[0]
u_quick = U_out[:, 1]
score = np.dot(v_the, u_quick)
print(f"分数 logit('quick' | 'the') = {score:.4f}")分数 logit('quick' | 'the') = -1.1400输出:分数 logit('quick' | 'the') = -1.1400
📝 动手练一练
概念辨析:假设我们有一个训练好的 Word2Vec 模型。对于同一个词,其“输入嵌入矩阵”中的向量和“输出嵌入矩阵”中的向量,含义和用途有何不同?在 CBOW 和 Skip-gram 模型中,哪个矩阵在预测时被用作最终的词向量更常见?
👉 点击查看参考答案
在 Word2Vec 模型中:
- 输入嵌入矩阵:负责将输入的 One-Hot 向量映射为稠密向量。在 CBOW 中,它映射上下文词;在 Skip-gram 中,它映射中心词。
- 输出嵌入矩阵:负责将隐藏层向量映射到输出层,用于计算与词表中每个词的“得分”,进而通过 Softmax 或负采样得到概率。
两者的含义不同:输入向量更侧重于词在作为“输入特征”时的表示,而输出向量更侧重于词在作为“预测目标”时的表示。在实际应用中,通常将输入嵌入矩阵中的向量作为该词的最终词向量(即
word2vec.wv.vectors),因为它在上下文中更稳定。也有做法是将两个矩阵的向量平均或拼接,但单独使用输入向量是最常见的做法。手算理解:假设我们有一个极简的词表
[‘自然’, ‘语言’, ‘处理’],其 One-Hot 编码顺序对应为[1,0,0],[0,1,0],[0,0,1]。假设 CBOW 模型的输入嵌入矩阵 (维度 3x2)和输出嵌入矩阵 (维度 2x3)的初始值如下(仅为演示,非实际训练结果): 对于一个上下文窗口为 2(即前后各 1 个词)的 CBOW 模型,给定上下文词为[‘自然’, ‘处理’],请手动计算: a) 上下文词的平均隐藏层向量 。 b) 在未使用 Softmax 的情况下,模型对中心词的原始得分(logits)向量 (即 )。 c) 根据得分向量 ,模型预测的中心词最可能是词表中的哪一个?👉 点击查看参考答案
import numpy as np # 定义词表及One-Hot vocab = ['自然', '语言', '处理'] # 定义嵌入矩阵 E_input = np.array([[0.1, 0.2], # ‘自然’ [0.3, 0.4], # ‘语言’ [0.5, 0.6]]) # ‘处理’ E_output = np.array([[0.7, 0.8, 0.9], [1.0, 1.1, 1.2]]) # a) 计算上下文词‘自然’(索引0)和‘处理’(索引2)的平均隐藏向量 h v_context1 = E_input[0] # [0.1, 0.2] v_context2 = E_input[2] # [0.5, 0.6] h = (v_context1 + v_context2) / 2 print(f"平均隐藏层向量 h: {h}") # b) 计算得分向量 s = h^T * E_output s = np.dot(h, E_output) # h 是 (2,), E_output 是 (2,3), 结果 s 是 (3,) print(f"原始得分向量 s: {s}") # c) 找出得分最高的索引,即为预测的中心词索引 predicted_idx = np.argmax(s) predicted_word = vocab[predicted_idx] print(f"预测的中心词索引: {predicted_idx}, 对应词: '{predicted_word}'")
平均隐藏层向量 h: [0.3 0.4] 原始得分向量 s: [0.61 0.68 0.75] 预测的中心词索引: 2, 对应词: ‘处理’ ```
运行上述代码,可以得到结果:
- 平均隐藏层向量
- 原始得分向量
- 得分最高的索引是 2,对应词 `‘处理’`。
因此,在此特定参数和上下文下,模型预测的中心词最可能是 **`‘处理’`**。请注意,这只是初始随机参数下的一个前向传播示例,并非有意义的训练结果。
</details>本章小结
本节我们深入探讨了 Word2Vec 的核心思想与模型架构。我们从神经语言模型的背景出发,理解了 Word2Vec 如何将复杂的语言概率预测问题,简化为更高效的“词-上下文”关系建模任务。重点掌握了 CBOW 和 Skip-gram 两种经典模型的目标、架构与计算流程,并通过代码演示了其前向传播步骤。关键点在于理解双嵌入矩阵(输入矩阵 和输出矩阵 )的作用,以及它们如何通过一个预测任务被共同优化,从而产生高质量的稠密词向量。
📋 行动清单
- 理清模型目标:能够清晰说出 CBOW 和 Skip-gram 在输入、输出和目标上的本质区别。
- 理解计算图:对照讲义中的公式和代码,在心中勾勒出 CBOW/Skip-gram 从前向传播到计算损失的数据流动图。
- 预见效率问题:思考当词表规模 达到 10 万量级时,输出层 Softmax 的计算会成为怎样的瓶颈?这为学习下一节的优化技术(负采样、层次 Softmax)做好了准备。
—— 小象教研组