词袋模型 (BOW) 解析及代码实战
词袋模型 (BOW) 解析及代码实战
引言:文本表示的核心挑战
在自然语言处理(NLP)领域,文本数据的数学化表示是算法理解语义的基础。词袋模型(Bag of Words, BoW) 作为文本表示领域的里程碑方法,通过将文本转化为数值向量,成功解决了早期NLP任务的特征工程难题。尽管存在忽略词序的局限性,词袋模型在文本分类、情感分析等场景中仍展现出惊人的实用性。
词袋模型深度解析
模型定义与数学表达
词袋模型将文本抽象为无序词集合(“bag”),通过向量化表示实现文本数字化。给定文档集D包含m个文档,经处理后得到n维词汇表V,每个文档可表示为:
Document i = [ w i 1 , w i 2 , . . . , w i n ] \text{Document}_i = [w_{i1}, w_{i2}, ..., w_{in}] Documenti=[wi1,wi2,...,win]
其中 w i j w_{ij} wij 表示词汇表第j个词在文档i中的出现频次或存在性(0/1)。这种表示方式使文本数据可直接输入机器学习模型。
完整处理流程
-
文本预处理(核心步骤常被忽视)
- 分词处理(中文需专门分词器)
- 去除停用词(的、是、了等无实义词)
- 词干提取(英文场景常见)
-
词汇表构建
- 收集所有文档的唯一词项
- 建立词项到索引的映射
-
向量化表示
- 频次统计(TF表示)
- 二值化表示(存在性判断)
- TF-IDF加权(进阶改进)
模型特性分析
核心优势:
- 复杂度可控(时间复杂度O(n))
- 可解释性强(特征对应具体词语)
- 基线模型基准(任何NLP系统都应对比BoW效果)
关键局限:
- 语义信息丢失("猫追狗"与"狗追猫"无法区分)
- 维度灾难(词汇表可达 1 0 5 10^5 105量级)
- 同义忽略("电脑"与"计算机"视为不同特征)
中文词袋模型完整实现
实验环境搭建
!pip install jieba matplotlib scikit-learn # 中文分词与可视化支持
定制化中文语料库
corpus = [
"我特别特别喜欢看电影", # 文档1:电影偏好表达
"这部电影真的是很好看的电影", # 文档2:影片评价
"今天天气真好是难得的好天气", # 文档3:天气描述
"我今天去看了一部电影", # 文档4:观影记录
"电影院的电影都很好看" # 文档5:影院评价
]
中文分词实战
import jieba
# 精准模式分词(相比全模式更准确)
corpus_tokenized = [list(jieba.cut(sentence, cut_all=False))
for sentence in corpus]
print("分词结果:")
for i, tokens in enumerate(corpus_tokenized):
print(f"文档{i+1}: {'/'.join(tokens)}")
输出示例:
分词结果:
文档1: 我/特别/特别/喜欢/看/电影
文档2: 这部/电影/真的/是/很/好看/的/电影
文档3: 今天天气/真好/是/难得/的/好/天气
文档4: 我/今天/去/看/了/一部/电影
文档5: 电影院/的/电影/都/很/好看
词汇表构建优化
from collections import defaultdict
# 自动排序词汇表
word_index = defaultdict(lambda: len(word_index))
for tokens in corpus_tokenized:
for word in tokens:
word_index[word] # 自动分配递增索引
# 转换为标准字典
vocab = dict(word_index)
print("有序词汇表:", vocab)
输出示例:
有序词汇表: {'我': 0, '特别': 1, '喜欢': 2, '看': 3, '电影': 4, '这部': 5, '真的': 6, '是': 7, '很': 8, '好看': 9, '的': 10, '今天天气': 11, '真好': 12, '难得': 13, '好': 14, '天气': 15, '今天': 16, '去': 17, '了': 18, '一部': 19, '电影院': 20, '都': 21}
词频向量生成
import numpy as np
# 初始化矩阵 (文档数 × 词汇量)
bow_matrix = np.zeros((len(corpus), len(vocab)), dtype=np.int16)
for doc_idx, tokens in enumerate(corpus_tokenized):
for word in tokens:
bow_matrix[doc_idx, vocab[word]] += 1
print("词频矩阵:")
print(bow_matrix)
输出结果:
词频矩阵:
[[1 2 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 2 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 1 0 0 1 1 1 1 1 1 0 0 0 0 0 0]
[1 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0]
[0 0 0 0 1 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 1 1]]
输出解析:
文档1向量: [1,2,1,1,1,0,...] # "特别"出现2次
文档2向量: [0,0,0,2,0,...] # "电影"出现2次
相似度计算与可视化
余弦相似度数学原理
余弦相似度衡量向量空间中的方向相似性:
similarity = cos ( θ ) = A ⋅ B ∥ A ∥ ∥ B ∥ \text{similarity} = \cos(\theta) = \frac{A \cdot B}{\|A\| \|B\|} similarity=cos(θ)=∥A∥∥B∥A⋅B
- 值域范围:[-1,1],文本分析中通常为[0,1]
- 对绝对频次不敏感,关注分布模式
代码实现
def enhanced_cosine_sim(vec_a, vec_b):
"""添加零向量保护机制的余弦相似度"""
dot = np.dot(vec_a, vec_b)
norm_a = np.linalg.norm(vec_a)
norm_b = np.linalg.norm(vec_b)
# 处理零向量特殊情况
if norm_a == 0 or norm_b == 0:
return 0.0 # 定义零向量与任何向量相似度为0
return dot / (norm_a * norm_b)
# 构建相似度矩阵
sim_matrix = np.array([
[enhanced_cosine_sim(bow_matrix[i], bow_matrix[j])
for j in range(len(corpus))]
for i in range(len(corpus))
])
热力图可视化
import matplotlib.pyplot as plt
import seaborn as sns
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei'] # 使用黑体作为默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决负号 '-' 显示为方块的问题
plt.figure(figsize=(10,8))
sns.heatmap(
sim_matrix,
annot=True,
xticklabels=[f"Doc{i+1}" for i in range(5)],
yticklabels=[f"Doc{i+1}" for i in range(5)],
cmap="Blues",
vmin=0,
vmax=1,
)
plt.title("文档间余弦相似度热力图", fontsize=14)
plt.show()
结果分析:
- 文档2与文档5相似度高
- 文档3与其他文档相似度低
- 文档4与文档1、2存在一定相似性
工业级实现:Scikit-learn实战
中文CountVectorizer适配
from sklearn.feature_extraction.text import CountVectorizer
# 示例文本数据
documents = [
"This is the first document.",
"This document is the second document.",
"And this is the third one.",
"Is this the first document?"
]
# 使用CountVectorizer构建词袋模型
vectorizer = CountVectorizer()
# 对文本进行拟合和转换
X = vectorizer.fit_transform(documents)
# 获取词袋模型的词汇表
vocab = vectorizer.get_feature_names_out()
# 将稀疏矩阵转换为密集矩阵
dense_matrix = X.toarray()
# 显示词袋模型的结果
print("词汇表:", vocab)
print("词袋向量:")
print(dense_matrix)
解析
dense_matrix = X.toarray()
在上述代码示例中,X
是通过CountVectorizer
对文本数据进行拟合和转换后得到的结果。X
实际上是一个稀疏矩阵(scipy.sparse matrix),它用来存储词袋模型(Bag of Words model)的计数结果。稀疏矩阵是一种专门用于存储大多数元素为零的矩阵的数据结构,这样可以有效地节省内存空间和提高计算效率。
- 稀疏矩阵 vs 密集矩阵:在自然语言处理(NLP)和其他应用中,当我们使用诸如词袋模型、TF-IDF等方法将文本转化为数值向量时,通常会得到一个包含大量零值的矩阵。这是因为每篇文档只包含词汇表中的少部分单词。为了高效地存储和处理这种数据,我们使用稀疏矩阵。然而,有时我们需要将这个稀疏矩阵转换成密集矩阵(dense matrix),即普通二维数组,其中所有元素都以显式形式存储,包括零值。
toarray()
方法:X.toarray()
就是用来完成稀疏矩阵到密集矩阵转换的方法。它将稀疏矩阵X
转换成一个标准的 NumPy 数组。这意味着,即使矩阵中有很多零值,它们也会被显式地表示出来,从而形成一个完整的二维数组。需要注意的是,如果文档数量很大或者词汇表非常丰富,
toarray()
可能会导致大量的内存消耗,因为它将所有的零也显式存储起来。在这种情况下,可能更合适直接使用稀疏矩阵的格式进行后续操作,除非确实需要密集矩阵的形式。
模型演进与拓展应用
词袋模型的现代变体
模型变种 | 核心改进 | 典型应用场景 |
---|---|---|
TF-IDF | 词频-逆文档频率加权 | 信息检索 |
n-gram模型 | 保留局部词序(2-3词组合) | 短文本分类 |
哈希技巧 | 降维处理 | 大规模文本处理 |
加权词袋 | 融入词性、位置等信息 | 法律文本分析 |
参考文献
【AIGC篇】文本表示方法比较:词袋模型 vs. 词向量 - 知了知了__ - 稀土掘金
NLP_Bag-Of-Words(词袋模型) - you_are_my_sunshine* - CSDN