03 文本向量化:词袋模型与 TF-IDF
系列文章目录
前言
上一篇文章我们把文本清洗成了干净的词语序列,但计算机不认识"词",只认识"数字"。所以接下来要解决一个核心问题:
如何把一段文本变成一个机器可以计算的向量?
这个把文本转成数值向量的过程,就叫文本向量化(Text Vectorization),也叫文本表示(Text Representation)。它是连接"文本世界"和"数字世界"的桥梁,是整个 NLP 的基础设施。
这篇文章我们聚焦两种最经典、也是最常用的向量化方法:词袋模型(Bag of Words, BoW) 和 TF-IDF。

1. 为什么需要文本向量化
先想一个问题:我们要判断"这部电影太棒了"是好评还是差评。计算机怎么做?
-
计算机不能直接处理"电影""太棒了"这些汉字
-
但如果我们把每个词映射成一个数字或向量,就能用数学工具(距离、相似度、分类器)来处理了
向量化的核心价值在于:把非结构化的文本,转化为结构化、可计算的数值形式。
2. 词袋模型(Bag of Words, BoW)
2.1 核心思想
词袋模型的想法非常朴素:
把文本拆成一个个词,统计每个词在文档中出现的次数,形成一个"词 → 次数"的向量。至于词的先后顺序,完全不考虑。
因为忽略了顺序,就像"把词装进一个麻袋"一样,所以叫"词袋"。
2.2 举个栗子
假设有三篇文档:
文档1:我喜欢学习自然语言处理
文档2:我喜欢编程
文档3:自然语言处理很有趣
第一步:统计所有文档中出现过的词,构建词表(Vocabulary):
词表 = [我, 喜欢, 学习, 自然语言, 处理, 编程, 很, 有趣]
第二步:统计每个文档中各词的词频,得到一个向量:
| 文档 | 我 | 喜欢 | 学习 | 自然语言 | 处理 | 编程 | 很 | 有趣 |
|---|---|---|---|---|---|---|---|---|
| 文档1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 |
| 文档2 | 1 | 1 | 0 | 0 | 0 | 1 | 0 | 0 |
| 文档3 | 0 | 0 | 0 | 1 | 1 | 0 | 1 | 1 |
这样,每个文档都变成了一个 8 维的向量,计算机就可以直接处理了。
2.3 用 sklearn 实现
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
"我喜欢学习自然语言处理",
"我喜欢编程",
"自然语言处理很有趣",
]
# 注意:CountVectorizer 默认按空格分词,中文需要自定义 tokenizer(这里简化为字符/词级示意)
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(corpus)
print(vectorizer.get_feature_names_out()) # 词表
print(X.toarray()) # 词频矩阵
2.4 词袋模型的缺点
词袋模型虽然简单,但有两个致命缺点:
-
忽略语义和顺序:"我喜欢你"和"你喜欢我"会得到完全一样的向量,但意思相反
-
高频词权重过高:一篇文档里"的、了"出现 100 次,"NLP"出现 1 次,向量几乎被虚词主导
-
高维稀疏:词表可能有几万词,但每个文档只用到其中一小部分,向量大部分是 0
为了缓解第 2 个缺点,就有了 TF-IDF。
3. TF-IDF
3.1 核心思想
TF-IDF 的核心想法是:
一个词对文档的重要性,不仅取决于它在当前文档中出现得多不多(TF),还取决于它在整个语料中稀不稀罕(IDF)。
如果一个词在很多文档里都出现(比如"的"),那它对区分文档没什么帮助,权重应该降低;如果一个词只在少数文档里出现(比如"深度学习"),那它很可能承载了关键信息,权重应该提高。
3.2 公式
TF-IDF 由两部分相乘:
TF-IDF = TF × IDF
- TF(词频,Term Frequency):词在文档中出现的次数 / 文档总词数
TF(t, d) = 词 t 在文档 d 中出现的次数 / 文档 d 的总词数
- IDF(逆文档频率,Inverse Document Frequency):衡量词的稀有程度
IDF(t) = log( 总文档数 N / (包含词 t 的文档数 + 1) )
分母加 1 是为了防止除零(避免某个词在所有文档中都没出现过的情况)。
3.3 直觉理解
-
如果"深度学习"在 1000 篇文档中只出现在 10 篇里,它的 IDF 很高 → 权重高
-
如果"的"在 1000 篇文档中出现在 999 篇里,它的 IDF 接近 log(1) = 0 → 权重低
所以 TF-IDF 能自动识别并压低"的、了、是"这类高频无意义词,同时突出真正有区分度的关键词。
3.4 用 sklearn 实现
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
"我喜欢学习自然语言处理",
"我喜欢编程",
"自然语言处理很有趣",
]
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(corpus)
print(X.toarray()) # 每个文档的 TF-IDF 向量
print(vectorizer.idf_) # 每个词的 IDF 值
4. BoW 与 TF-IDF 对比
| 对比维度 | 词袋模型 BoW | TF-IDF |
|---|---|---|
| 核心思想 | 统计词频 | 词频 × 稀有度 |
| 能否区分高频虚词 | 不能,虚词权重很高 | 能,自动压低虚词权重 |
| 是否体现语义 | 否 | 否(仍是词袋,无顺序) |
| 计算复杂度 | 低 | 略高 |
| 适用场景 | 简单的基线模型 | 文本分类、关键词提取、检索 |
| 是否稀疏 | 高维稀疏 | 高维稀疏 |
💡 小贴士:在实际项目中,TF-IDF 通常是比 BoW 更好的默认选择,尤其在做文本分类和关键词提取时。
5. 常见问题与建议
-
中文需要先分词:词袋/TF-IDF 基于"词"统计,中文必须先分词(见第 2 篇)
-
词表爆炸问题 :语料规模大时词表可达几十万维,可设置
max_features限制维度 -
丢失顺序信息:如果任务对语序敏感(如判断"猫咬狗"和"狗咬猫"的区别),需要更高级的表示
-
适用场景:词袋/TF-IDF 更适合"理解性"任务(分类、聚类、检索),对"生成性"任务力不从心
总结
这一篇我们掌握了两种最基础的文本向量化方法:
-
词袋模型(BoW):统计词频,简单直观,但忽略顺序和语义、虚词权重过高
-
TF-IDF:在词频基础上加入稀有度惩罚,自动突出关键词,是更实用的改进版
但无论是 BoW 还是 TF-IDF,都有一个共同的局限:它们都是"稀疏的、离散的"表示,无法捕捉词语之间的语义相似度。"苹果"和"香蕉"在向量空间里距离为 0(因为它们从不同时出现)。
要解决这个问题,我们需要一种能表示语义的稠密向量 ------这就是下一篇的主角 Word2Vec。
📌 下篇预告 :04 词向量 Word2Vec:让词变成向量
参考资料
-
sklearn 官方文档:8.2. Feature extraction --- scikit-learn 1.9.0 documentation
-
TF-IDF 维基百科:https://en.wikipedia.org/wiki/Tf%E2%80%93idf