阶段2
|
NLP传统 · 第10课/共30课
📖 第10课:GloVe
🎯 课程目标
GloVe(Global Vectors)融合了全局矩阵分解和局部上下文窗口的优势,是Word2Vec的重要替代方案。
- 理解GloVe的核心思想:利用全局共现统计
- 从零实现简化版GloVe
- 理解加权函数的设计意图
- 对比GloVe与Word2Vec的异同
📐 核心原理
GloVe的目标函数
J = Σ f(X_ij) (w_i^T · w̃_j + b_i + b̃_j - log X_ij)²
💡 核心洞察:Word2Vec只看窗口内的局部共现,GloVe利用整个语料库的全局共现矩阵。
🔬 手动实现GloVe
简化版GloVe训练
import numpy as np
from collections import defaultdict
class SimpleGloVe:
def __init__(self, vector_size=20, alpha=0.75, x_max=10):
self.vector_size = vector_size
self.alpha = alpha
self.x_max = x_max
def build_cooccurrence(self, corpus, window=2):
cooccur = defaultdict(float)
for tokens in corpus:
for i, center in enumerate(tokens):
for j in range(max(0, i-window), min(len(tokens), i+window+1)):
if i != j:
weight = 1.0 / abs(i - j)
cooccur[(center, tokens[j])] += weight
return cooccur
def weighting_func(self, x):
if x < self.x_max:
return (x / self.x_max) ** self.alpha
return 1.0
def fit(self, corpus, window=2, epochs=100, lr=0.01):
cooccur = self.build_cooccurrence(corpus, window)
vocab = sorted(set(w for pair in cooccur for w in pair))
word2idx = {w: i for i, w in enumerate(vocab)}
n = len(vocab)
W = np.random.randn(n, self.vector_size) * 0.1
W_ctx = np.random.randn(n, self.vector_size) * 0.1
b = np.zeros(n)
b_ctx = np.zeros(n)
pairs = [(word2idx[w1], word2idx[w2], count) for (w1, w2), count in cooccur.items()]
for epoch in range(epochs):
total_loss = 0
for i, j, x_ij in pairs:
diff = np.dot(W[i], W_ctx[j]) + b[i] + b_ctx[j] - np.log(x_ij)
f_x = self.weighting_func(x_ij)
total_loss += f_x * diff ** 2
grad_main = f_x * diff * W_ctx[j]
grad_ctx = f_x * diff * W[i]
W[i] -= lr * grad_main
W_ctx[j] -= lr * grad_ctx
b[i] -= lr * f_x * diff
b_ctx[j] -= lr * f_x * diff
if (epoch + 1) % 20 == 0:
print(f"Epoch {epoch+1}: loss = {total_loss:.4f}")
self.W = W + W_ctx
self.word2idx = word2idx
self.vocab = vocab
return self
def most_similar(self, word, topn=5):
if word not in self.word2idx:
return []
vec = self.W[self.word2idx[word]]
norms = np.linalg.norm(self.W, axis=1, keepdims=True)
norms = np.where(norms == 0, 1, norms)
normed = self.W / norms
vec_normed = vec / (np.linalg.norm(vec) + 1e-8)
sims = normed @ vec_normed
sims[self.word2idx[word]] = -1
top_idx = np.argsort(sims)[-topn:][::-1]
return [(self.vocab[i], sims[i]) for i in top_idx]
corpus = [
['自然', '语言', '处理', '人工智能'],
['深度', '学习', '自然', '语言', '处理'],
['机器', '学习', '人工智能'],
['词', '向量', '语言', '表示'],
['文本', '分类', '自然', '语言'],
]
model = SimpleGloVe(vector_size=20, alpha=0.75)
model.fit(corpus, window=2, epochs=100, lr=0.05)
print("\n=== 相似词 ===")
for word in ['语言', '学习', '自然']:
similar = model.most_similar(word, topn=3)
print(f" '{word}': {[(w, f'{s:.3f}') for w, s in similar]}")
Epoch 20: loss = 1.2989
Epoch 40: loss = 0.9057
Epoch 60: loss = 0.5976
Epoch 80: loss = 0.3649
Epoch 100: loss = 0.2146
=== 相似词 ===
'语言': [('自然', '0.881'), ('处理', '0.130'), ('表示', '0.031')]
'学习': [('人工智能', '0.508'), ('深度', '0.499'), ('文本', '0.293')]
'自然': [('语言', '0.881'), ('表示', '-0.091'), ('机器', '-0.096')]
✅ 验证通过:代码实机运行结果如上
📊 GloVe vs Word2Vec对比
| 方面 | Word2Vec | GloVe |
| 训练数据 | 局部上下文窗口 | 全局共现矩阵 |
| 优化目标 | 预测上下文词 | 最小化共现概率的加权平方误差 |
| 全局信息 | ❌ 只看局部窗口 | ✅ 利用全局统计 |
| 小语料 | 一般 | 更好 |
| 大语料 | 更好(增量训练) | 需要重建共现矩阵 |
📝 练习
动手练习
- 修改加权函数参数(α, x_max),观察对训练的影响
- 用更大语料训练GloVe,与Word2Vec在类比任务上对比
- 实现GloVe的增量训练
🌐
🏆 成就解锁:全局视角者
掌握了GloVe全局词向量的训练方法。
🧪 扩展:GloVe的实现细节
共现矩阵的构建
GloVe需要预先构建完整的共现矩阵,这是与Word2Vec的根本区别:
- 对称窗口:左右各window个词
- 距离加权:距离越近权重越大(1/d)
- 内存消耗:|V|×|V|的矩阵可能很大,需要稀疏存储
Stanford GloVe预训练向量
Stanford发布的预训练GloVe向量:
- 6B tokens: 50/100/200/300维,400K词表
- 42B tokens: 300维,1.9M词表
- 840B tokens: 300维,2.2M词表
直接加载预训练向量通常比从零训练效果好得多——除非你有领域特定的大语料。
🔬 扩展实践:GloVe工程实战
使用预训练GloVe向量
Stanford提供了多个规模的预训练GloVe向量:
- glove.6B:60亿token,400K词表,50/100/200/300维
- glove.42B.300d:420亿token,1.9M词表,300维
- glove.840B.300d:8400亿token,2.2M词表,300维
加载方式:解析文本文件,每行格式为"word v1 v2 ... v300"
词向量的局限性
- 静态表示:"苹果"始终是同一个向量,无法区分公司/水果
- OOV问题:新词没有向量,只能随机初始化或用子词
- 无句子级语义:词向量平均丢失词序信息
- 偏见:训练语料中的性别/种族偏见会被词向量捕获
🧪 扩展:GloVe预训练向量
Stanford GloVe预训练向量
- 6B tokens:50/100/200/300维,400K词表
- 42B tokens:300维,1.9M词表
- 840B tokens:300维,2.2M词表
直接加载预训练向量通常比从零训练效果好——除非你有领域特定的大语料。
词向量的局限性
- 静态表示:"苹果"始终是同一个向量,无法区分公司/水果
- OOV问题:新词没有向量,只能随机初始化或用子词
- 无句子级语义:词向量平均丢失词序信息
- 偏见:训练语料中的性别/种族偏见会被词向量捕获
这些局限正是ELMo、BERT等上下文表示方法要解决的核心问题。
📚 参考文献
推荐阅读
- Jurafsky & Martin. Speech and Language Processing (3rd ed). 第10课相关章节
- Manning, Raghavan & Schutze. Introduction to Information Retrieval
- Goodfellow, Bengio & Courville. Deep Learning. 自然语言处理章节
在线资源
- NLTK文档: https://www.nltk.org/
- spaCy文档: https://spacy.io/
- gensim文档: https://radimrehurek.com/gensim/
- sklearn文本处理: https://scikit-learn.org/stable/modules/feature_extraction.html
GloVe - 关键概念速查
| 概念 | 定义 | 应用 |
| Pipeline | 处理流水线,各步骤串行执行 | 文本处理系统 |
| Tokenization | 将文本切分为词/子词单元 | 分词、子词分割 |
| Embedding | 将离散符号映射到连续向量空间 | 词向量、句向量 |
| Feature Engineering | 从原始数据提取有效数值特征 | 传统ML模型输入 |
| Fine-tuning | 在预训练模型基础上微调 | BERT等模型适配 |
本课要点回顾
- GloVe是NLP技术栈的重要组件
- 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
- 代码验证是学习的好方法——动手跑一遍胜过读十遍
- 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
- 实践出真知——用自己的数据跑一遍,发现课本上没写的问题
❓ 常见问题FAQ
- Q: 这个方法的数据需求量是多少?
- A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
- Q: 如何处理中文的特殊情况?
- A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
- Q: 传统方法和深度学习如何选择?
- A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
- Q: 如何评估和调优?
- A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
- Q: 生产环境部署要注意什么?
- A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。
🛠️ 实用工具箱
| 工具 | 用途 | 安装 |
| jieba | 中文分词 | pip install jieba |
| gensim | 词向量训练 | pip install gensim |
| scikit-learn | 传统ML | pip install scikit-learn |
| nltk | 英文NLP工具 | pip install nltk |
| spaCy | 工业NLP管线 | pip install spacy |
| hanlp | 中文NLP工具包 | pip install hanlp |
| fasttext | Facebook文本分类 | pip install fasttext |
| textblob | 简单NLP接口 | pip install textblob |