Appearance
章节2:词向量技术
学习目标
- 理解词袋模型(BoW)与 TF‑IDF 向量化的数学原理与局限
- 掌握 Word2Vec 的 CBOW 和 Skip‑Gram 两种架构及其训练逻辑
- 了解 GloVe 与 FastText 词向量的核心改进点
- 掌握 BPE 子词编码的算法流程
- 能够用 t‑SNE 对词向量进行可视化并分析语义关系
2.1 词袋模型与 TF‑IDF 向量化
2.1.1 词袋模型(Bag of Words)
将文本表示为一个固定长度的向量,每个维度对应一个词,值为该词在文档中出现的次数。
python
from sklearn.feature_extraction.text import CountVectorizer
import jieba
corpus = [
"我爱学习自然语言处理",
"自然语言处理很有趣",
"我爱编程"
]
# 使用 jieba 作为分词器
vectorizer = CountVectorizer(tokenizer=jieba.lcut)
bow = vectorizer.fit_transform(corpus)
print("词汇表:", vectorizer.get_feature_names_out())
print("BoW 矩阵:\n", bow.toarray())BoW 的缺陷:
- 稀疏性 — 维度 = 词表大小,大量零值
- 忽略词序 — "猫追狗" 与 "狗追猫" 向量相同
- 不区分重要程度 — 高频无意义词("的")与重要词权重相同
2.1.2 TF‑IDF 向量化
TF‑IDF 对 BoW 进行加权改进:
TF(t,d) = 词 t 在文档 d 中出现的次数 / 文档 d 总词数
IDF(t) = log(总文档数 / 包含词 t 的文档数)
TF-IDF = TF × IDFpython
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf_vec = TfidfVectorizer(tokenizer=jieba.lcut)
tfidf_matrix = tfidf_vec.fit_transform(corpus)
print("TF-IDF 矩阵:\n", tfidf_matrix.toarray())局限性: BoW 和 TF‑IDF 都无法捕捉语义相似性——"北京"和"首都"在向量空间中距离很远,但人类知道它们相关。
2.2 Word2Vec
2.2.1 核心思想
"You shall know a word by the company it keeps." — J.R. Firth
Word2Vec(Google, 2013)基于分布假设:上下文相似的词,语义也相似。
2.2.2 CBOW(Continuous Bag of Words)
目标: 给定上下文词,预测中心词。
输入: ["我", "___", "自然语言处理"] → 预测: "学习"2.2.3 Skip‑Gram(跳字模型)
目标: 给定中心词,预测上下文词。
输入: "学习" → 预测: ["我", "自然语言处理"]架构对比:
| 模型 | 输入→输出 | 速度 | 对高频词 | 适合场景 |
|---|---|---|---|---|
| CBOW | 上下文→中心词 | 快 | 平滑 | 大规模语料 |
| Skip‑Gram | 中心词→上下文 | 慢 | 可学习罕见词 | 小语料或罕见词重要 |
2.2.4 Gensim 实现示例
python
from gensim.models import Word2Vec
import jieba
# 准备语料(已分词)
sentences = [
"我 爱 学习 自然语言处理",
"自然语言处理 是 人工智能 的 重要 分支",
"我 喜欢 编程 和 人工智能",
"深度学习 是 机器 学习 的 子集",
"Python 是 常用 的 编程语言"
]
# 分词
corpus = [s.split() for s in sentences]
# 训练 Word2Vec
model = Word2Vec(
sentences=corpus,
vector_size=100, # 词向量维度
window=5, # 上下文窗口大小
min_count=1, # 最小词频
sg=0, # 0=CBOW, 1=Skip-Gram
workers=4,
epochs=50
)
# 查看词向量
print("词向量维度:", model.wv["人工智能"].shape)
# 找相似词
print("与'人工智能'最相似的词:")
print(model.wv.most_similar("人工智能", topn=5))
# 词类比: 北京 - 中国 + 美国 = ?
try:
result = model.wv.most_similar(positive=["美国", "北京"], negative=["中国"], topn=3)
print("词类比结果:", result)
except KeyError:
print("语料太小无法演示类比,大语料下有效果")2.3 GloVe 与 FastText
2.3.1 GloVe(Global Vectors, Stanford 2014)
核心改进: 不仅利用局部上下文窗口,还利用全局共现矩阵的统计信息。
损失函数: J = Σ f(X_ij)(w_i·w̃_j + b_i + b̃_j - log X_ij)²
其中 X_ij 是词 i 和词 j 的共现次数python
# GloVe 通常预训练后加载(而非从零训练)
# 下载预训练向量: https://nlp.stanford.edu/projects/glove/
from gensim.models import KeyedVectors
# 加载 GloVe 格式(需先下载 glove.6B.100d.txt)
# glove_vec = KeyedVectors.load_word2vec_format("glove.6B.100d.txt", binary=False, no_header=True)
# print(glove_vec.most_similar("beijing"))2.3.2 FastText(Facebook, 2016)
核心改进: 将每个词拆分为字符级 n-gram 子词,解决**未登录词(OOV)**问题。
python
from gensim.models import FastText
# FastText 可以处理未见过的词
ft_model = FastText(
sentences=corpus,
vector_size=100,
window=5,
min_count=1,
min_n=2, # 最小 n-gram 长度
max_n=5, # 最大 n-gram 长度
epochs=50
)
# "编程语言"在语料中,但"编程语言学习"未出现
vector_oov = ft_model.wv["编程语言学习"] # FastText 也能生成向量!
print("OOV 词向量:", vector_oov[:5]) # 前5维示意三种方法对比:
| 模型 | 上下文方式 | 处理 OOV | 训练速度 | 核心优势 |
|---|---|---|---|---|
| Word2Vec | 局部窗口 | ❌ | 快 | 语义类比能力强 |
| GloVe | 全局共现 | ❌ | 中 | 利用全局统计 |
| FastText | 局部窗口 + 子词 | ✅ | 中 | 处理生僻词 |
2.4 BPE 子词编码(Byte Pair Encoding)
2.4.1 为什么需要子词
- 英语中 "unbelievable" = "un" + "believe" + "able"
- BPE 将词拆解为更小的子词单元,平衡词表大小与 OOV 覆盖
2.4.2 BPE 算法流程
1. 初始化字符词表(所有独立字符)
2. 统计所有相邻字符对的频次
3. 合并频次最高的字符对为新符号
4. 重复步骤2-3,直到词表达到目标大小2.4.3 使用 HuggingFace Tokenizers 实现 BPE
python
from tokenizers import Tokenizer, models, trainers
# 准备语料
corpus = [
"unbelievable",
"believable",
"believe",
"able",
"unable"
]
# 初始化 BPE tokenizer
tokenizer = Tokenizer(models.BPE())
trainer = trainers.BpeTrainer(vocab_size=50, special_tokens=["[UNK]"])
# 训练
tokenizer.train_from_iterator(corpus, trainer)
# 测试
encoded = tokenizer.encode("unbelievable")
print("子词拆分:", encoded.tokens)
# 输出示例: ['un', 'belie', 'vable']应用: BPE 是 GPT、BERT、RoBERTa 等主流预训练模型的标准分词方法。
2.5 词向量可视化(t‑SNE)与下游应用
2.5.1 使用 t‑SNE 可视化词向量
python
import numpy as np
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
# 提取词向量
words = ["自然语言处理", "人工智能", "深度学习", "机器学习",
"Python", "编程", "计算机", "数据", "算法", "模型"]
word_vectors = np.array([model.wv[w] for w in words])
# t-SNE 降维到2D
tsne = TSNE(n_components=2, random_state=42, perplexity=5)
embeddings_2d = tsne.fit_transform(word_vectors)
# 可视化
plt.figure(figsize=(10, 8))
for i, word in enumerate(words):
x, y = embeddings_2d[i]
plt.scatter(x, y, c="blue", s=100)
plt.annotate(word, (x, y), fontsize=12, ha="center")
plt.title("词向量 t-SNE 可视化")
plt.grid(True, alpha=0.3)
plt.show()t‑SNE 调参要点:
| 参数 | 作用 | 推荐值 |
|---|---|---|
| perplexity | 控制局部/全局平衡 | 5-50(小数据集用小值) |
| learning_rate | 收敛速度 | 200(默认) |
| n_iter | 迭代次数 | 1000+ |
2.5.2 词向量的下游应用
- 作为特征输入 — 将词向量拼接作为分类器输入
- 语义搜索 — 将查询转为向量,用余弦相似度检索
- 文本聚类 — 文档词向量的均值作为文档向量,进行 K‑Means
- 迁移学习 — 在大型语料上预训练的向量直接用于下游任务
python
# 文档向量 = 词向量均值(简单文档嵌入)
def doc_vector(model, words):
vectors = [model.wv[w] for w in words if w in model.wv]
if not vectors:
return np.zeros(model.vector_size)
return np.mean(vectors, axis=0)
doc1_vec = doc_vector(model, ["自然语言处理", "人工智能"])
doc2_vec = doc_vector(model, ["深度学习", "机器学习"])
from sklearn.metrics.pairwise import cosine_similarity
sim = cosine_similarity([doc1_vec], [doc2_vec])
print(f"文档语义相似度: {sim[0][0]:.4f}")小结
- BoW / TF‑IDF 是早期的向量化方法,简单但无法捕捉语义。
- Word2Vec(CBOW / Skip‑Gram)让向量具有语义——相似的词距离近。
- GloVe 融合全局共现统计,FastText 引入子词解决 OOV 问题。
- BPE 是现代化的子词编码方案,已广泛应用于预训练模型。
- t‑SNE 能将高维词向量降至2D便于可视化分析。
- 词向量作为迁移学习的基石,为后续预训练语言模型铺平了道路。
练习
- 用 Gensim 在20篇新闻上训练 Word2Vec(CBOW vs Skip‑Gram),比较两种模式对"中国"的相似词列表差异。
- 用 FastText 重新训练,观察它对未登录词(如"元宇宙Z世代")能否生成合理向量。
- 实现一个简单的 BPE 训练器:给定词表 ["low", "lower", "new", "newer", "widest"],手动执行3轮合并。
- 从预训练的 Word2Vec 中文词向量(如搜狗新闻词向量)中抽取"北京-首都+东京"的类比结果。
- 挑战题: 在 t‑SNE 可视化中,用不同颜色标注不同语义类别的词(如:城市、技术、人物),观察聚类效果。