共计 3347 个字符,预计需要花费 9 分钟才能阅读完成。
背景介绍
在自然语言处理(NLP)中,将词语转换为计算机可以理解的数值形式是一个基础且关键的问题。词嵌入(Word Embedding)技术就是解决这一问题的有效方法之一。词嵌入不仅能保留词语的语义信息,还能捕捉词语之间的语法和上下文关系。

CBOW(Continuous Bag of Words)模型是词嵌入技术中的一种经典算法,它通过预测中心词来学习词向量。CBOW 模型特别适合处理大规模语料库,并且在很多 NLP 任务中表现出色,比如文本分类、情感分析等。
算法原理
CBOW 模型的核心思想是利用上下文词语预测中心词。具体来说,给定一个窗口大小内的上下文词语,CBOW 模型通过计算这些词语的词向量平均值,然后通过一个 softmax 层预测中心词的概率分布。
-
模型结构 :CBOW 模型通常包含输入层、投影层和输出层。输入层接收上下文词语的 one-hot 编码,投影层将这些编码转换为词向量,输出层通过 softmax 函数计算中心词的概率。
-
训练目标 :CBOW 模型的训练目标是最大化中心词的对数似然函数,即:
$$
\mathcal{L} = \sum_{t=1}^{T} \log p(w_t | w_{t-k}, \dots, w_{t-1}, w_{t+1}, \dots, w_{t+k})
$$
其中,$w_t$ 是中心词,$k$ 是窗口大小。 -
数学公式推导 :假设词嵌入矩阵为 $W \in \mathbb{R}^{V \times d}$,其中 $V$ 是词汇表大小,$d$ 是词向量维度。上下文词语的词向量平均值为:
$$
\mathbf{v} = \frac{1}{2k} \sum_{i=-k, i \neq 0}^{k} W^T \mathbf{x}{t+i}
$$
然后通过 softmax 函数计算中心词的概率:
$$
p(w_t | \text{context}) = \frac{\exp(\mathbf{v}^T \mathbf{x}_t)}{\sum
$$}^{V} \exp(\mathbf{v}^T \mathbf{x}_j)
手算实例
为了更好地理解 CBOW 模型的计算过程,我们来看一个简单的例子。假设我们有一个包含 3 个词语的语料库:”I love NLP”。
- 词汇表 :首先,我们构建词汇表 $V = {“I”, “love”, “NLP”}$,并为每个词语分配一个 one-hot 编码:
- “I”: [1, 0, 0]
- “love”: [0, 1, 0]
-
“NLP”: [0, 0, 1]
-
词嵌入矩阵 :假设词向量维度 $d=2$,随机初始化词嵌入矩阵 $W$:
$$
W = \begin{bmatrix}
0.1 & 0.2 \
0.3 & 0.4 \
0.5 & 0.6
\end{bmatrix}
$$ -
计算上下文向量 :假设窗口大小 $k=1$,中心词是 ”love”,上下文词语是 ”I” 和 ”NLP”。计算上下文词向量的平均值:
$$
\mathbf{v} = \frac{1}{2} (W^T \mathbf{x}I + W^T \mathbf{x}
$$}) = \frac{1}{2} \left(\begin{bmatrix} 0.1 \ 0.2 \end{bmatrix} + \begin{bmatrix} 0.5 \ 0.6 \end{bmatrix} \right) = \begin{bmatrix} 0.3 \ 0.4 \end{bmatrix -
预测中心词 :通过 softmax 函数计算中心词的概率分布:
$$
p(\text{love} | \text{context}) = \frac{\exp(\mathbf{v}^T \mathbf{x}{\text{love}})}{\sum
$$
计算后可以得到每个词语的概率值。}^{3} \exp(\mathbf{v}^T \mathbf{x}_j)} = \frac{\exp(0.3 \times 0.3 + 0.4 \times 0.4)}{\exp(0.3 \times 0.1 + 0.4 \times 0.2) + \exp(0.3 \times 0.3 + 0.4 \times 0.4) + \exp(0.3 \times 0.5 + 0.4 \times 0.6)
代码实现
下面是用 Python 和 NumPy 实现 CBOW 模型的完整代码:
import numpy as np
# 词汇表和 one-hot 编码
vocab = ["I", "love", "NLP"]
word_to_idx = {word: idx for idx, word in enumerate(vocab)}
idx_to_word = {idx: word for idx, word in enumerate(vocab)}
# 超参数
window_size = 1
embedding_dim = 2
learning_rate = 0.01
epochs = 100
# 初始化词嵌入矩阵
W = np.random.rand(len(vocab), embedding_dim)
# 训练数据
sentences = [["I", "love", "NLP"]]
# 训练过程
for epoch in range(epochs):
for sentence in sentences:
for i in range(window_size, len(sentence) - window_size):
# 获取上下文和中心词
context = [sentence[i - window_size], sentence[i + window_size]]
center_word = sentence[i]
# 转换为 one-hot 编码
context_indices = [word_to_idx[word] for word in context]
center_idx = word_to_idx[center_word]
# 计算上下文向量
context_vectors = W[context_indices]
v = np.mean(context_vectors, axis=0)
# 计算 softmax 概率
scores = np.dot(W, v)
exp_scores = np.exp(scores)
probs = exp_scores / np.sum(exp_scores)
# 计算梯度
grad = np.copy(probs)
grad[center_idx] -= 1
# 更新词嵌入矩阵
W -= learning_rate * np.outer(grad, v)
print("训练后的词嵌入矩阵:")
print(W)
性能考量
在训练 CBOW 模型时,有几个关键的超参数需要调整:
-
窗口大小 :窗口大小决定了模型在预测中心词时考虑的上下文范围。较大的窗口可以捕捉更多的上下文信息,但可能导致模型过于关注远距离的词语。
-
词向量维度 :词向量的维度决定了模型的表达能力。维度越高,模型可以捕捉更复杂的语义关系,但也需要更多的训练数据和计算资源。
-
学习率 :学习率影响模型收敛的速度和稳定性。过大的学习率可能导致模型无法收敛,而过小的学习率则会使训练过程非常缓慢。
避坑指南
-
初始化问题 :词嵌入矩阵的初始化非常重要。如果初始化值过大或过小,可能导致梯度消失或爆炸。建议使用较小的随机值进行初始化。
-
数据预处理 :在训练之前,需要对文本数据进行预处理,比如去除停用词、标点符号等。否则,噪声数据会影响模型的性能。
-
计算效率 :CBOW 模型的 softmax 计算复杂度与词汇表大小成正比,对于大规模词汇表,可以使用负采样(Negative Sampling)或层次 softmax(Hierarchical Softmax)来加速训练。
延伸思考
-
CBOW 与 Skip-gram 的对比 :CBOW 模型通过上下文预测中心词,适合处理高频词;而 Skip-gram 模型通过中心词预测上下文,适合处理低频词。在实际应用中,可以根据任务需求选择合适的模型。
-
实际应用建议 :对于小型数据集,可以使用预训练的词向量(如 Word2Vec、GloVe)作为初始值进行微调;对于大型数据集,可以从头开始训练 CBOW 模型。
-
未来的研究方向 :如何结合 CBOW 模型与深度学习模型(如 LSTM、Transformer)来进一步提升 NLP 任务的性能?
结尾
通过本文的详细讲解和手算实例,相信大家对 CBOW 模型和词嵌入矩阵有了更深入的理解。CBOW 模型虽然简单,但在实际应用中表现出了强大的能力。希望这篇文章能帮助你在 NLP 项目中更好地使用词嵌入技术。
最后,留给大家两个思考问题:
1. 如何评估词向量的质量?
2. 在不同语言中,CBOW 模型的表现是否会有所不同?为什么?
