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

📖 第08课:词袋模型

🎯 课程目标

词袋模型(Bag of Words)是最基础的文本表示方法,将文本转化为固定维度向量。

📐 核心原理

词袋模型的"三不"

💡 N-gram改进:Bigram捕获相邻词关系,部分弥补顺序丢失。代价是特征空间爆炸。

🔬 手动实现词袋模型

手动词袋向量化器

from collections import Counter import numpy as np class BoWVectorizer: def __init__(self): self.vocab = {} def fit(self, documents): word_set = set() for doc in documents: word_set.update(doc.split()) self.vocab = {word: idx for idx, word in enumerate(sorted(word_set))} return self def transform(self, documents): matrix = np.zeros((len(documents), len(self.vocab))) for i, doc in enumerate(documents): counts = Counter(doc.split()) for word, count in counts.items(): if word in self.vocab: matrix[i, self.vocab[word]] = count return matrix def get_feature_names(self): return sorted(self.vocab.keys(), key=lambda x: self.vocab[x]) docs = [ "我 喜欢 自然语言 处理", "自然语言 处理 很 有趣", "我 喜欢 机器 学习", "深度 学习 是 机器 学习 的 子领域" ] bow = BoWVectorizer() bow.fit(docs) matrix = bow.transform(docs) print("=== 词袋模型 ===") print(f"词表: {bow.get_feature_names()}") print(f"矩阵形状: {matrix.shape}") print("\n文档向量:") for i, doc in enumerate(docs): print(f" 文档{i+1}: {matrix[i].astype(int)}") def cosine_sim(v1, v2): dot = np.dot(v1, v2) norm = np.linalg.norm(v1) * np.linalg.norm(v2) return dot / norm if norm > 0 else 0 print("\n=== 文档相似度 ===") for i in range(len(docs)): for j in range(i+1, len(docs)): sim = cosine_sim(matrix[i], matrix[j]) print(f" 文档{i+1}↔文档{j+1}: {sim:.4f}")
=== 词袋模型 === 词表: ['喜欢', '处理', '子领域', '学习', '很', '我', '是', '有趣', '机器', '深度', '的', '自然语言'] 矩阵形状: (4, 12) 文档向量: 文档1: [1 1 0 0 0 1 0 0 0 0 0 1] 文档2: [0 1 0 0 1 0 0 1 0 0 0 1] 文档3: [1 0 0 1 0 1 0 0 1 0 0 0] 文档4: [0 0 1 2 0 0 1 0 1 1 1 0] === 文档相似度 === 文档1↔文档2: 0.5000 文档1↔文档3: 0.5000 文档1↔文档4: 0.0000 文档2↔文档3: 0.0000 文档2↔文档4: 0.0000 文档3↔文档4: 0.5000

✅ 验证通过:代码实机运行结果如上

🔧 N-gram词袋与稀疏性

N-gram扩展与稀疏性分析

from sklearn.feature_extraction.text import CountVectorizer import jieba import numpy as np corpus = [ "自然语言处理很有趣", "语言处理是重要技术", "深度学习推动自然语言处理", "计算机视觉也很重要" ] corpus_seg = [' '.join(jieba.cut(text)) for text in corpus] # Unigram cv1 = CountVectorizer(token_pattern=r'(?u)\b\w+\b') X1 = cv1.fit_transform(corpus_seg) print("=== Unigram词袋 ===") print(f"特征: {cv1.get_feature_names_out()}") print(f"矩阵:\n{X1.toarray()}") # Bigram cv2 = CountVectorizer(ngram_range=(2,2), token_pattern=r'(?u)\b\w+\b') X2 = cv2.fit_transform(corpus_seg) print(f"\n=== Bigram词袋 ===") print(f"特征: {cv2.get_feature_names_out()}") # 稀疏性分析 total = X1.shape[0] * X1.shape[1] nonzero = X1.count_nonzero() print(f"\n=== 稀疏性 ===") print(f"矩阵大小: {X1.shape}, 非零元素: {nonzero}/{total} ({nonzero/total*100:.1f}%)")
=== Unigram词袋 === 特征: ['也' '处理' '学习' '很' '技术' '推动' '是' '有趣' '深度' '自然语言' '视觉' '计算机' '语言' '重要'] 矩阵: [[0 1 0 1 0 0 0 1 0 1 0 0 0 0] [0 1 0 0 1 0 1 0 0 0 0 0 1 1] [0 1 1 0 0 1 0 0 1 1 0 0 0 0] [1 0 0 1 0 0 0 0 0 0 1 1 0 1]] === Bigram词袋 === 特征: ['也 很' '处理 很' '处理 是' '学习 推动' '很 有趣' '很 重要' '推动 自然语言' '是 重要' '深度 学习' '自然语言 处理' '视觉 也' '计算机 视觉' '语言 处理' '重要 技术'] === 稀疏性 === 矩阵大小: (4, 14), 非零元素: 19/56 (33.9%)

✅ 验证通过:代码实机运行结果如上

📝 练习

动手练习

  1. 实现一个带TF-IDF加权的词袋模型
  2. 对比Unigram、Bigram、Trigram在情感分类任务上的效果
  3. 实现特征选择(按词频阈值过滤低频/高频词)
👜

🏆 成就解锁:词袋匠人

掌握了最经典的文本向量化方法。

🧪 扩展:从词袋到神经方法

词袋模型的数学视角

词袋模型将文档映射到|V|维空间中的一个点。这个空间的维度等于词表大小,通常上万到几十万。

高维稀疏向量的问题:

词袋的进化路径

词袋(BoW) → TF-IDF加权 → LSA降维 → Word2Vec稠密 → BERT上下文
  稀疏高维    权重优化      矩阵分解    局部语义     全局语义

每一步都在解决上一步的核心问题,但也在引入新的挑战。没有银弹——选择取决于任务和数据。

📚 扩展阅读:从稀疏到稠密

文本表示的演进

文本表示方法的本质区别在于如何编码"词的语义":

  1. 词袋/TF-IDF:语义=词面匹配,"开心"≠"快乐"
  2. LSA:通过SVD分解发现潜在语义维度,"开心"≈"快乐"
  3. Word2Vec/GloVe:通过上下文学习词向量,语义相似=向量相近
  4. ELMo/BERT:上下文化表示,"苹果"在水果和公司语境下有不同向量

每次演进都解决了上一步的核心问题,但引入了新的复杂性。词袋的简单性在很多场景下仍然是优势。

🔬 扩展实践:从词袋到句向量

词袋 → TF-IDF → LSA → 句向量

词袋模型的关键升级路径:

  1. 词袋 → TF-IDF:加权,降低常见词影响
  2. TF-IDF → LSA:SVD降维,发现潜在语义,解决稀疏性
  3. LSA → Word2Vec平均:用预训练词向量的平均值作为句向量
  4. W2V平均 → SIF:加权平均(SIF算法),效果显著提升

SIF(Smooth Inverse Frequency)的核心思想:常用词的向量贡献应该被降低权重,类似于TF-IDF对IDF的利用。

🧪 扩展:从词袋到句向量

文本表示的演进路径

  1. 词袋 → TF-IDF:加权,降低常见词影响
  2. TF-IDF → LSA:SVD降维,发现潜在语义,解决稀疏性
  3. LSA → Word2Vec平均:用预训练词向量的平均值作为句向量
  4. W2V平均 → SIF:加权平均(SIF算法),效果显著提升

SIF(Smooth Inverse Frequency)的核心思想:常用词的向量贡献应该被降低权重。

词袋模型的数学视角

词袋模型将文档映射到|V|维空间中的一个点。高维稀疏向量的问题:

📚 参考文献

推荐阅读

在线资源

词袋模型 - 关键概念速查

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

本课要点回顾

  1. 词袋模型是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