4、Word2Vec入门:CBOW与Skip-gram架构、负采样与层次Softmax、Gensim库快速上手
说实话,Word2Vec 是我入行 NLP 时第一个让我觉得「哇,原来词还能这么玩」的技术。2013 年 Google 放出这篇的时候,整个圈子都震了一下——一个两层的神经网络,居然能把「国王 - 男人 + 女人 ≈ 女王」这种语义关系给学出来。
这一章,我们就把它拆开看看。
4.1 核心思想:用上下文学词义
Word2Vec 的直觉其实很简单:一个词的意思,由它周围的词决定。你想想看,如果我说「我今天去____买了杯咖啡」,你大概率会填「星巴克」或者「瑞幸」,而不是「冰箱」或「轮胎」。这就是分布语义学的核心假设。
Word2Vec 有两种训练模式,它们正好是互逆的:
- CBOW(Continuous Bag of Words):用上下文词预测中心词
- Skip-gram:用中心词预测上下文词
我个人习惯这样记:CBOW 适合高频词,训练快;Skip-gram 对低频词更友好,但慢一些。选哪个?看你的数据量和任务场景。
4.2 CBOW 与 Skip-gram 架构详解
4.2.1 CBOW 架构
CBOW 的输入是上下文词的 one-hot 向量,输出是中心词的概率分布。中间只有一层隐藏层——嗯,就是那个我们最终要拿来做词向量的矩阵。
流程是这样的:
- 取窗口大小(比如 2),拿到中心词前后各 2 个词
- 把这些词的 one-hot 向量分别乘以输入权重矩阵 W(形状:V×N)
- 得到 N 维向量后取平均
- 再乘以输出权重矩阵 W'(形状:N×V)
- 经过 softmax 得到 V 个词的概率分布
- 用交叉熵损失更新 W 和 W'
我在项目中遇到过一个问题:窗口大小怎么选?太小了学不到全局语义,太大了又引入噪声。一般经验是 5-10,但如果你做的是专业领域(比如医疗、法律),窗口可以适当缩小,因为专业术语的上下文通常更紧凑。
4.2.2 Skip-gram 架构
Skip-gram 正好反过来。输入是中心词,输出是它周围每个词的概率。注意,这里每个上下文词是独立预测的,所以损失是多个交叉熵的和。
Skip-gram 的优点是:对低频词更友好。为什么?因为一个低频词只要出现一次,它就会强迫模型去预测它周围的多个词,相当于一次训练产生了多个样本。CBOW 则相反,多个上下文词取平均后,低频词的信号容易被稀释。
| 对比维度 | CBOW | Skip-gram |
|---|---|---|
| 输入 → 输出 | 上下文 → 中心词 | 中心词 → 上下文 |
| 训练速度 | 快 | 慢(约 2-3 倍) |
| 低频词效果 | 一般 | 好 |
| 高频词效果 | 好 | 一般 |
| 适用场景 | 大规模语料、高频词为主 | 小规模语料、低频词重要 |
4.3 负采样与层次 Softmax
好,现在问题来了。上面说的 softmax 要对 V 个词(通常是几万到几十万)做归一化,每次更新都要算一遍,太慢了。怎么办?
Word2Vec 给了两个优化方案:负采样和层次 Softmax。
4.3.1 负采样(Negative Sampling)
负采样的思路很巧妙:我不去算所有词的概率,而是只挑几个「负样本」来算。
具体来说:
- 正样本:中心词和它真实的上下文词
- 负样本:中心词和随机挑的几个「非上下文词」
- 目标:让正样本的得分高,负样本的得分低
负样本怎么挑?不是均匀采样,而是按词频的 3/4 次方加权。为什么是 3/4?实验出来的,能让低频词被采到的概率稍微高一点。
我曾经踩过一个坑:负样本数量设太大。默认是 5-10,我试过 20,结果训练慢了一倍,效果反而没提升。后来我养成了习惯:小数据集用 5,大数据集用 10,再多就没必要了。
4.3.2 层次 Softmax(Hierarchical Softmax)
层次 Softmax 是另一种思路。它用 Huffman 树把 V 分类问题变成了 log₂(V) 次二分类问题。
怎么做的?
- 根据词频构建 Huffman 树,高频词路径短
- 每个叶子节点对应一个词
- 每个内部节点是一个二分类器(sigmoid)
- 预测时,从根节点走到目标词,路径上的概率相乘
这样,计算复杂度从 O(V) 降到了 O(log V)。对于 10 万词的词表,从 10 万次计算降到约 17 次,效果非常明显。
不过层次 Softmax 有个限制:它假设词之间是互斥的(一个位置只能有一个词),这在某些场景下不太自然。负采样就没有这个假设。
| 优化方法 | 核心思想 | 复杂度 | 适用场景 |
|---|---|---|---|
| 负采样 | 只更新少量负样本 | O(k),k 为负样本数 | 通用,低频词友好 |
| 层次 Softmax | 用 Huffman 树做二分类 | O(log V) | 高频词多、词表大 |
4.4 架构对比图
下面这张图把 CBOW 和 Skip-gram 的流程放在一起对比,方便你理解它们的差异。
4.5 Gensim 库快速上手
理论说完了,来点实际的。Gensim 是 Python 里最成熟的 Word2Vec 实现,API 设计得很干净。
4.5.1 安装与基本使用
pip install gensim
训练一个最简单的模型:
from gensim.models import Word2Vec
from gensim.utils import simple_preprocess
# 假设你有一些句子
sentences = [
"我 今天 去 星巴克 买 了 一杯 拿铁",
"咖啡 的 味道 很 香",
"我 喜欢 喝 美式 咖啡"
]
# 分词(这里用空格预分好,实际要用 jieba 等工具)
tokenized = [s.split() for s in sentences]
# 训练模型
model = Word2Vec(
sentences=tokenized,
vector_size=100, # 词向量维度
window=5, # 窗口大小
min_count=1, # 忽略出现次数少于该值的词
sg=0, # 0=CBOW, 1=Skip-gram
hs=0, # 0=负采样, 1=层次Softmax
negative=5, # 负样本数
epochs=5 # 迭代次数
)
# 查看词向量
print(model.wv['咖啡']) # 输出 100 维向量
# 找相似词
print(model.wv.most_similar('咖啡', topn=3))
simple_preprocess 函数可以帮你做基础的分词和清洗,但中文场景下效果一般。建议用 jieba 或 pkuseg 先分好词,再喂给 Gensim。
4.5.2 关键参数详解
| 参数 | 默认值 | 说明 | 我的建议 |
|---|---|---|---|
vector_size |
100 | 词向量维度 | 小数据用 100,大数据用 300 |
window |
5 | 上下文窗口大小 | 5-10,专业领域可缩小 |
min_count |
5 | 最低词频过滤 | 数据量大时设 5-10,小数据设 1 |
sg |
0 | 0=CBOW, 1=Skip-gram | 默认 CBOW,低频词多时用 Skip-gram |
hs |
0 | 0=负采样, 1=层次Softmax | 保持默认 0 |
negative |
5 | 负样本数量 | 5-10,不要超过 20 |
epochs |
5 | 训练轮数 | 5-20,看 loss 收敛情况 |
4.5.3 模型保存与加载
# 保存
model.save("word2vec.model")
# 加载
model = Word2Vec.load("word2vec.model")
# 只保存词向量(轻量版)
model.wv.save_word2vec_format("vectors.txt", binary=False)
save_word2vec_format,结果加载后没法继续训练了。如果你还要继续训练,一定要用 model.save() 保存完整模型。
4.5.4 常用操作
# 找相似词
model.wv.most_similar("咖啡", topn=10)
# 找不匹配的词
model.wv.doesnt_match(["星巴克", "瑞幸", "咖啡", "冰箱"])
# 词向量运算(经典例子)
result = model.wv.most_similar(positive=["国王", "女人"], negative=["男人"])
print(result) # 应该接近「女王」
# 获取词向量
vec = model.wv["咖啡"]
# 计算两个词的相似度
sim = model.wv.similarity("咖啡", "拿铁")
4.6 本章小结
这一章我们走通了 Word2Vec 的核心脉络:
- CBOW 用上下文预测中心词,适合高频词和大数据
- Skip-gram 用中心词预测上下文,对低频词更友好
- 负采样 通过只更新少量负样本大幅加速训练
- 层次 Softmax 用 Huffman 树把复杂度从 O(V) 降到 O(log V)
- Gensim 提供了开箱即用的实现,参数调优是关键