共计 1905 个字符,预计需要花费 5 分钟才能阅读完成。
CBOW 模型基础与痛点分析
CBOW(Continuous Bag-of-Words)是 Word2Vec 的经典实现之一,通过上下文词预测中心词来学习词向量。其核心优势在于能高效捕获词语的分布式表示,但初学者常因以下问题卡壳:

- 维度混乱:输入层、隐藏层、输出层的矩阵形状变化不清晰
- 梯度困惑:softmax 的导数计算与链式法则结合时容易迷失方向
- 实践断层:理论公式与代码实现难以对应
手算梯度全流程拆解
1. 模型结构说明
假设词表大小 V =5,词向量维度 d =3,上下文窗口为 2:
# 参数初始化(关键变量注释)W_in = np.random.randn(5, 3) # 输入层到隐藏层的权重矩阵(V,d)
W_out = np.random.randn(3, 5) # 隐藏层到输出层的权重矩阵(d,V)
2. 前向传播步骤
- 输入层→隐藏层:
- 上下文词索引转为 one-hot 向量(如
[0,1,0,0,0]) - 计算隐藏层向量:$h = \frac{1}{C}\sum_{c=1}^C W_{in}^T x_c$(C= 窗口大小)
-
维度变化:(d,V) × (V,1) → (d,1)
-
隐藏层→输出层:
- 计算未归一化得分:$u = W_{out}^T h$
- softmax 概率:$p_i = \frac{e^{u_i}}{\sum_j e^{u_j}}$
3. 损失函数与梯度推导
交叉熵损失:$L = -\sum_{i=1}^V y_i \log p_i$
关键梯度计算(建议手推验证):
- $\frac{\partial L}{\partial u} = p – y$(y 是中心词的 one-hot 标签)
- $\frac{\partial L}{\partial W_{out}} = h (p – y)^T$
- $\frac{\partial L}{\partial h} = W_{out}(p – y)$
- $\frac{\partial L}{\partial W_{in}} = \frac{1}{C}\sum_c x_c (\frac{\partial L}{\partial h})^T$
Python 实现关键代码
def forward(context_words, W_in, W_out):
"""context_words: 上下文词索引列表"""
h = np.mean([W_in[word] for word in context_words], axis=0)
u = W_out.T @ h
p = np.exp(u) / np.sum(np.exp(u))
return h, u, p
def backward(y_true, p, h, W_out, context_words):
"""y_true: 中心词的 one-hot 编码"""
grad_u = p - y_true
grad_W_out = np.outer(h, grad_u)
grad_h = W_out @ grad_u
grad_W_in = np.zeros_like(W_in)
for word in context_words:
grad_W_in[word] += grad_h / len(context_words)
return grad_W_in, grad_W_out
避坑实践指南
- 初始化技巧 :词向量初始值建议在[-0.5/d, 0.5/d] 均匀分布
- 梯度爆炸:当词表较大时,对梯度进行裁剪(
grad = np.clip(grad, -5, 5)) - 低频词处理:采用 subsampling 技术,概率丢弃高频词
延伸思考
- 与 Skip-gram 对比:
- CBOW 梯度来自多个上下文词的平均,训练更稳定
-
Skip-gram 对低频词表现更好,但计算量更大
-
PyTorch 实现建议:
- 利用
nn.Embedding层代替手动矩阵操作 - 自动微分简化梯度计算(示例代码片段):
# PyTorch 自动微分版
embedding = nn.Embedding(vocab_size, dim)
loss_func = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(embedding.parameters(), lr=0.01)
# 训练循环中...
optimizer.zero_grad()
h = torch.mean(embedding(context_words), dim=0)
loss = loss_func(h @ W_out, target_word)
loss.backward()
optimizer.step()
总结
通过这次手算实践,我们拆解了 CBOW 模型的黑箱操作。建议读者:
1. 在小词表(如 V =5)上手工验算前 3 次迭代
2. 尝试添加负采样加速训练
3. 可视化训练前后的词向量(TSNE 降维)观察变化
这种从零实现的经历,能帮助建立对词嵌入技术更本质的理解。当后续使用 gensim 等工具时,你会更清楚参数调整对结果的影响。
正文完
