1. 什么是词袋模型? #
1.1 核心概念 #
词袋模型(Bag of Words,简称BoW)是一种将文本转换为数字向量的方法。想象一下,你把一篇文章的所有词都扔进一个袋子里,然后只统计每个词出现了多少次,完全不管这些词原来的顺序和位置。这就是"词袋"这个名字的由来。
1.2 为什么叫"词袋"? #
- "词":指的是文本中的词汇
- "袋":表示我们把这些词都装进一个袋子里,打乱顺序
- "模型":这是一种将文本转换为计算机可以处理的数字的方法
1.3 简单类比 #
假设你有两个购物清单:
- 清单1:苹果、香蕉、苹果、橙子
- 清单2:香蕉、橙子、葡萄
用词袋模型表示,我们只关心:
- 清单1:苹果(2次)、香蕉(1次)、橙子(1次)
- 清单2:香蕉(1次)、橙子(1次)、葡萄(1次)
我们不在乎这些水果在清单上的顺序,只关心每种水果出现了几次。
2. 为什么需要词袋模型? #
2.1 计算机无法直接理解文字 #
计算机只能处理数字,不能直接理解文字。如果我们想让计算机分析文本(比如判断一篇文章是正面还是负面情感),就需要把文字转换成数字。
2.2 词袋模型的作用 #
词袋模型将文本转换成固定长度的数字向量,这样:
- 计算机可以处理:向量是数字,计算机可以计算
- 可以比较文本:通过比较向量,可以判断两段文本是否相似
- 可以用于机器学习:大多数机器学习算法需要数字输入
2.3 实际应用场景 #
- 文本分类:判断邮件是垃圾邮件还是正常邮件
- 情感分析:判断评论是正面还是负面
- 文档相似度:找出相似的文档
- 搜索引擎:匹配搜索关键词和网页内容
3. 词袋模型的工作原理 #
3.1 三个核心步骤 #
词袋模型的工作过程可以分为三个步骤:
3.1.1 步骤1:构建词汇表 #
收集所有文档中出现过的所有不重复的词,形成一个词汇表(也叫词典)。
例子: 假设我们有两句话:
- 句子1:"我喜欢机器学习"
- 句子2:"机器学习喜欢数据"
分词后:
- 句子1:
["我", "喜欢", "机器学习"] - 句子2:
["机器学习", "喜欢", "数据"]
词汇表就是所有不重复的词:["我", "喜欢", "机器学习", "数据"]
3.1.2 步骤2:创建词到索引的映射 #
为了快速查找每个词在词汇表中的位置,我们创建一个字典,将每个词映射到一个数字索引。
例子:
"我" → 0
"喜欢" → 1
"机器学习" → 2
"数据" → 33.1.3 步骤3:向量化 #
对于每个文档,我们创建一个向量(一个数字列表)。向量的长度等于词汇表的长度,每个位置对应词汇表中的一个词,数值表示该词在文档中出现的次数。
例子:
句子1:"我喜欢机器学习"
- "我"出现1次 → 向量位置0的值是1
- "喜欢"出现1次 → 向量位置1的值是1
- "机器学习"出现1次 → 向量位置2的值是1
- "数据"出现0次 → 向量位置3的值是0
- 最终向量:
[1, 1, 1, 0]
句子2:"机器学习喜欢数据"
- "我"出现0次 → 向量位置0的值是0
- "喜欢"出现1次 → 向量位置1的值是1
- "机器学习"出现1次 → 向量位置2的值是1
- "数据"出现1次 → 向量位置3的值是1
- 最终向量:
[0, 1, 1, 1]
3.2 可视化理解 #
原始文本:
文档1: "我喜欢机器学习"
文档2: "机器学习喜欢数据"
↓ 分词
分词结果:
文档1: ["我", "喜欢", "机器学习"]
文档2: ["机器学习", "喜欢", "数据"]
↓ 构建词汇表
词汇表: ["我", "喜欢", "机器学习", "数据"]
索引: [ 0, 1, 2, 3 ]
↓ 向量化
文档1向量: [1, 1, 1, 0] ← "我"1次,"喜欢"1次,"机器学习"1次,"数据"0次
文档2向量: [0, 1, 1, 1] ← "我"0次,"喜欢"1次,"机器学习"1次,"数据"1次4. 实现词袋模型 #
4.1 代码示例 #
# 第一步:准备数据
# 我们准备三个已经分好词的文档作为示例
# 在实际应用中,这些文档可能是文章、评论、邮件等
documents = [
["我", "喜欢", "机器学习"],
["机器学习", "喜欢", "数据"],
["我", "爱", "编程"]
]
# 打印原始文档,方便查看
print("=" * 50)
print("原始文档:")
print("=" * 50)
for i, doc in enumerate(documents, 1):
print(f"文档 {i}: {doc}")
# 第二步:构建词汇表
# 词汇表包含所有文档中出现过的所有不重复的词
print("\n" + "=" * 50)
print("第二步:构建词汇表")
print("=" * 50)
# 创建一个空集合,用来收集所有不重复的词
# 集合(set)的特点是自动去除重复元素
vocab_set = set()
# 遍历每个文档,将文档中的所有词添加到集合中
for doc in documents:
# update()方法可以将列表中的所有元素添加到集合中
vocab_set.update(doc)
# 将集合转换为排序后的列表
# sorted()确保词汇表的顺序是固定的,这样每次运行结果一致
vocabulary = sorted(list(vocab_set))
# 打印词汇表
print(f"词汇表(共{len(vocabulary)}个词): {vocabulary}")
# 第三步:创建词到索引的映射
# 这个字典帮助我们快速找到每个词在词汇表中的位置
print("\n" + "=" * 50)
print("第三步:创建词到索引的映射")
print("=" * 50)
# 使用字典推导式创建映射
# enumerate()返回索引和对应的值
# 例如:enumerate(['a', 'b']) → [(0, 'a'), (1, 'b')]
word_to_index = {word: idx for idx, word in enumerate(vocabulary)}
# 打印映射关系,方便理解
print("词 → 索引映射:")
for word, index in word_to_index.items():
print(f" '{word}' → {index}")
# 第四步:定义向量化函数
# 这个函数将一段分词后的文本转换为词袋向量
print("\n" + "=" * 50)
print("第四步:定义向量化函数")
print("=" * 50)
def bag_of_words(text, word_to_index):
"""
将分词后的文本转换为词袋模型向量
参数说明:
- text: 分词后的文本,是一个词列表,例如 ["我", "喜欢", "编程"]
- word_to_index: 词到索引的映射字典,例如 {"我": 0, "喜欢": 1, "编程": 2}
返回值:
- vector: 词袋向量,是一个整数列表,长度等于词汇表长度
"""
# 创建一个全0向量,长度等于词汇表的长度
# [0] * n 表示创建一个包含n个0的列表
vector = [0] * len(word_to_index)
# 遍历输入文本中的每个词
for word in text:
# 检查该词是否在词汇表中
if word in word_to_index:
# 获取该词在词汇表中的索引位置
index = word_to_index[word]
# 在对应位置计数加1
vector[index] += 1
# 返回生成的向量
return vector
# 测试向量化函数
# 用一个简单的例子测试函数是否正常工作
test_text = ["我", "喜欢", "机器学习"]
test_vector = bag_of_words(test_text, word_to_index)
print(f"测试文本: {test_text}")
print(f"测试向量: {test_vector}")
print("(这个向量表示:'我'出现1次,'喜欢'出现1次,'机器学习'出现1次)")
# 第五步:将所有文档转换为向量
print("\n" + "=" * 50)
print("第五步:将所有文档转换为向量")
print("=" * 50)
# 创建一个列表来存储所有文档的向量
all_vectors = []
# 遍历每个文档
for i, doc in enumerate(documents, 1):
# 将文档转换为向量
vector = bag_of_words(doc, word_to_index)
# 将向量添加到列表中
all_vectors.append(vector)
# 打印结果,方便查看
print(f"文档 {i}: {doc}")
print(f" 向量: {vector}")
# 详细解释向量的含义
print(" 向量含义:")
for j, count in enumerate(vector):
if count > 0: # 只显示出现次数大于0的词
word = vocabulary[j]
print(f" '{word}' 出现 {count} 次")
# 打印最终结果汇总
print("\n" + "=" * 50)
print("最终结果汇总")
print("=" * 50)
print(f"词汇表: {vocabulary}")
print("\n所有文档的向量:")
for i, vector in enumerate(all_vectors, 1):
print(f"文档 {i}: {vector}")
# 额外说明:向量可以用于计算文档相似度
print("\n" + "=" * 50)
print("应用示例:计算文档相似度")
print("=" * 50)
print("通过比较向量,我们可以判断文档的相似程度")
print("例如,文档1和文档2的向量:")
print(f" 文档1: {all_vectors[0]}")
print(f" 文档2: {all_vectors[1]}")
print("它们都有'喜欢'和'机器学习',所以比较相似")4.2 运行结果 #
运行上述代码后,你会看到类似以下的输出:
==================================================
原始文档:
==================================================
文档 1: ['我', '喜欢', '机器学习']
文档 2: ['机器学习', '喜欢', '数据']
文档 3: ['我', '爱', '编程']
==================================================
第二步:构建词汇表
==================================================
词汇表(共6个词): ['数据', '我', '机器学习', '爱', '编程', '喜欢']
==================================================
第三步:创建词到索引的映射
==================================================
词 → 索引映射:
'数据' → 0
'我' → 1
'机器学习' → 2
'爱' → 3
'编程' → 4
'喜欢' → 5
==================================================
第四步:定义向量化函数
==================================================
测试文本: ['我', '喜欢', '机器学习']
测试向量: [0, 1, 1, 0, 0, 1]
(这个向量表示:'我'出现1次,'喜欢'出现1次,'机器学习'出现1次)
==================================================
第五步:将所有文档转换为向量
==================================================
文档 1: ['我', '喜欢', '机器学习']
向量: [0, 1, 1, 0, 0, 1]
向量含义:
'我' 出现 1 次
'机器学习' 出现 1 次
'喜欢' 出现 1 次
文档 2: ['机器学习', '喜欢', '数据']
向量: [1, 0, 1, 0, 0, 1]
向量含义:
'数据' 出现 1 次
'机器学习' 出现 1 次
'喜欢' 出现 1 次
文档 3: ['我', '爱', '编程']
向量: [0, 1, 0, 1, 1, 0]
向量含义:
'我' 出现 1 次
'爱' 出现 1 次
'编程' 出现 1 次
==================================================
最终结果汇总
==================================================
词汇表: ['数据', '我', '机器学习', '爱', '编程', '喜欢']
所有文档的向量:
文档 1: [0, 1, 1, 0, 0, 1]
文档 2: [1, 0, 1, 0, 0, 1]
文档 3: [0, 1, 0, 1, 1, 0]
==================================================
应用示例:计算文档相似度
==================================================
通过比较向量,我们可以判断文档的相似程度
例如,文档1和文档2的向量:
文档1: [0, 1, 1, 0, 0, 1]
文档2: [1, 0, 1, 0, 0, 1]
它们都有'喜欢'和'机器学习',所以比较相似5. 词袋模型的优缺点 #
5.1 优点 #
5.1.1 简单直观 #
词袋模型的原理非常简单,容易理解和实现。即使没有深厚的数学背景,也能快速掌握。
5.1.2 计算高效 #
将文本转换为向量后,计算机可以快速处理。向量之间的计算(如计算相似度)非常高效。
5.1.3 适用范围广 #
虽然简单,但在很多实际任务中效果不错,特别是:
- 文本分类(如垃圾邮件检测)
- 情感分析(判断评论是正面还是负面)
- 文档检索(找到相似的文档)
5.1.4 易于扩展 #
可以在此基础上添加更多功能,比如:
- 考虑词的重要性(TF-IDF)
- 过滤停用词(如"的"、"了"等常见但无意义的词)
- 处理N-gram(考虑词的组合)
5.2 缺点 #
5.2.1 丢失词序信息 #
词袋模型完全忽略了词的顺序,这会导致一些问题:
例子:
- "狗咬人" 和 "人咬狗" 在词袋模型中是完全相同的向量
- 但它们的含义完全不同!
这是因为词袋模型只关心词是否出现,不关心出现的顺序。
5.2.2 丢失语义信息 #
词袋模型将每个词视为完全独立的,无法理解词与词之间的关系:
例子:
- "喜欢" 和 "爱" 是近义词,但词袋模型认为它们是两个完全不同的词
- "好" 和 "坏" 是反义词,但词袋模型无法理解它们的对立关系
5.2.3 维度灾难 #
当词汇表很大时,向量会变得非常长,而且大部分位置都是0(稀疏向量):
例子:
- 如果有10,000个不同的词,每个文档的向量就有10,000维
- 一篇短文可能只包含几十个词,所以向量中大部分都是0
- 这会占用大量内存,计算也会变慢
5.2.4 无法处理新词 #
如果遇到词汇表中没有的新词,词袋模型会直接忽略它,无法处理。
5.3 适用场景 #
适合使用词袋模型的场景:
- 文本分类任务(如垃圾邮件检测、新闻分类)
- 文档相似度计算(粗略比较)
- 作为更复杂模型的基线(baseline)
- 数据量较小,对效果要求不高的场景
不适合使用词袋模型的场景:
- 需要理解语义的任务(如机器翻译、问答系统)
- 需要考虑词序的任务(如文本生成)
- 需要处理同义词、反义词的任务
- 对效果要求很高的生产环境