阶段2 | NLP传统 · 第10课/共30课

📖 第10课:GloVe

🎯 课程目标

GloVe(Global Vectors)融合了全局矩阵分解和局部上下文窗口的优势,是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对比

方面Word2VecGloVe
训练数据局部上下文窗口全局共现矩阵
优化目标预测上下文词最小化共现概率的加权平方误差
全局信息❌ 只看局部窗口✅ 利用全局统计
小语料一般更好
大语料更好(增量训练)需要重建共现矩阵

📝 练习

动手练习

  1. 修改加权函数参数(α, x_max),观察对训练的影响
  2. 用更大语料训练GloVe,与Word2Vec在类比任务上对比
  3. 实现GloVe的增量训练
🌐

🏆 成就解锁:全局视角者

掌握了GloVe全局词向量的训练方法。

🧪 扩展:GloVe的实现细节

共现矩阵的构建

GloVe需要预先构建完整的共现矩阵,这是与Word2Vec的根本区别:

Stanford GloVe预训练向量

Stanford发布的预训练GloVe向量:

直接加载预训练向量通常比从零训练效果好得多——除非你有领域特定的大语料。

🔬 扩展实践:GloVe工程实战

使用预训练GloVe向量

Stanford提供了多个规模的预训练GloVe向量:

加载方式:解析文本文件,每行格式为"word v1 v2 ... v300"

词向量的局限性

  1. 静态表示:"苹果"始终是同一个向量,无法区分公司/水果
  2. OOV问题:新词没有向量,只能随机初始化或用子词
  3. 无句子级语义:词向量平均丢失词序信息
  4. 偏见:训练语料中的性别/种族偏见会被词向量捕获

🧪 扩展:GloVe预训练向量

Stanford GloVe预训练向量

直接加载预训练向量通常比从零训练效果好——除非你有领域特定的大语料。

词向量的局限性

  1. 静态表示:"苹果"始终是同一个向量,无法区分公司/水果
  2. OOV问题:新词没有向量,只能随机初始化或用子词
  3. 无句子级语义:词向量平均丢失词序信息
  4. 偏见:训练语料中的性别/种族偏见会被词向量捕获

这些局限正是ELMo、BERT等上下文表示方法要解决的核心问题。

📚 参考文献

推荐阅读

在线资源

GloVe - 关键概念速查

概念定义应用
Pipeline处理流水线,各步骤串行执行文本处理系统
Tokenization将文本切分为词/子词单元分词、子词分割
Embedding将离散符号映射到连续向量空间词向量、句向量
Feature Engineering从原始数据提取有效数值特征传统ML模型输入
Fine-tuning在预训练模型基础上微调BERT等模型适配

本课要点回顾

  1. GloVe是NLP技术栈的重要组件
  2. 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
  3. 代码验证是学习的好方法——动手跑一遍胜过读十遍
  4. 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
  5. 实践出真知——用自己的数据跑一遍,发现课本上没写的问题

❓ 常见问题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传统MLpip install scikit-learn
nltk英文NLP工具pip install nltk
spaCy工业NLP管线pip install spacy
hanlp中文NLP工具包pip install hanlp
fasttextFacebook文本分类pip install fasttext
textblob简单NLP接口pip install textblob