Skip to content

章节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 的缺陷:

  1. 稀疏性 — 维度 = 词表大小,大量零值
  2. 忽略词序 — "猫追狗" 与 "狗追猫" 向量相同
  3. 不区分重要程度 — 高频无意义词("的")与重要词权重相同

2.1.2 TF‑IDF 向量化

TF‑IDF 对 BoW 进行加权改进:

TF(t,d)  = 词 t 在文档 d 中出现的次数 / 文档 d 总词数
IDF(t)   = log(总文档数 / 包含词 t 的文档数)
TF-IDF   = TF × IDF
python
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 词向量的下游应用

  1. 作为特征输入 — 将词向量拼接作为分类器输入
  2. 语义搜索 — 将查询转为向量,用余弦相似度检索
  3. 文本聚类 — 文档词向量的均值作为文档向量,进行 K‑Means
  4. 迁移学习 — 在大型语料上预训练的向量直接用于下游任务
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}")

小结

  1. BoW / TF‑IDF 是早期的向量化方法,简单但无法捕捉语义。
  2. Word2Vec(CBOW / Skip‑Gram)让向量具有语义——相似的词距离近。
  3. GloVe 融合全局共现统计,FastText 引入子词解决 OOV 问题。
  4. BPE 是现代化的子词编码方案,已广泛应用于预训练模型。
  5. t‑SNE 能将高维词向量降至2D便于可视化分析。
  6. 词向量作为迁移学习的基石,为后续预训练语言模型铺平了道路。

练习

  1. 用 Gensim 在20篇新闻上训练 Word2Vec(CBOW vs Skip‑Gram),比较两种模式对"中国"的相似词列表差异。
  2. 用 FastText 重新训练,观察它对未登录词(如"元宇宙Z世代")能否生成合理向量。
  3. 实现一个简单的 BPE 训练器:给定词表 ["low", "lower", "new", "newer", "widest"],手动执行3轮合并。
  4. 从预训练的 Word2Vec 中文词向量(如搜狗新闻词向量)中抽取"北京-首都+东京"的类比结果。
  5. 挑战题: 在 t‑SNE 可视化中,用不同颜色标注不同语义类别的词(如:城市、技术、人物),观察聚类效果。

Python 学习资料