Skip to content

词袋模型

词袋模型(Bag-of-Words)是一种简单的文本表示方法,也是自然语言处理的一个 经典模型。它将文本中的词看作一个个独立的个体,不考虑它们在句子中的顺序,只关心每个词出现的频次

工作原理

词袋模型的核心思想是把:文本表示成一个“词频向量”,不看词的顺序、句法结构,只关注词是否出现出现的次数

可以看作是一个装词的口袋,里面有大量的词,零零散散的装在里面的。

假设有三句话作为语料库(Corpus)

句子1:我喜欢吃苹果
句子2:我不喜欢吃香蕉
句子3:她喜欢吃葡萄

第 1 步分词,也就是将每个句子切分成词:

["我", "喜欢", "吃", "苹果"]
["我", "不", "喜欢", "吃", "香蕉"]
["她", "喜欢", "吃", "葡萄"]

第 2 步构建词表,提取所有出现过的唯一词汇,按顺序编号:

可以理解为针对上面的多个词表进行去重,构建一个词表

词表 = ["我", "喜欢", "吃", "苹果", "不", "香蕉", "她", "葡萄"]

这个词表确定了向量的“维度”,共 8 个词,所以向量长度就是 8

第 3 步构建词频向量,也就是每句话对应一个向量

词汇位置喜欢苹果香蕉葡萄
句子1 向量11110000
句子2 向量11101100
句子3 向量01100011

这个表就是 BoW 特征向量化后的结果,每句话变成一个“向量”,可以用于计算机处理。

这一点非常重要,也是一个里程碑式的进步,将句子进行向量化之后,意味着可以进行数学计算了。

BoW 相对于 N-Gram 的改进:

问题N-Gram 的缺陷BoW 的解决
维度膨胀严重N-Gram 组合数非常多(特别是 Trigram 以上)BoW 只统计词,不考虑组合,向量维度更小
数据稀疏严重N-Gram 中很多词组组合没出现BoW 用词频统计,不会因未出现的组合而归 0,更稳定
上下文建模复杂N-Gram 有上下文,但建模代价大BoW 完全不建上下文,适合计算效率优先的场景
擅长的领域N-Gram 通常用于语言建模、生成BoW 更适合分类、聚类、检索等任务,不是用来“生成文本”的模型

由此可以看出,词袋模型更擅长的任务是:

;[1, 1, 1, 1, 0, 0, 0, 0][(1, 1, 1, 0, 0, 0, 0, 0)][(0, 0, 0, 0, 0, 0, 0, 1)]

代码实践

# 导入必要的库
from sklearn.feature_extraction.text import CountVectorizer # 用于构建词袋模型
import jieba # 中文分词库
# 1. 定义原始的中文语料(每个元素是一句话)
corpus = [
"我喜欢吃苹果",
"我不喜欢吃香蕉",
"她喜欢吃葡萄"
]
# 2. 对每个句子进行 jieba 分词,并用空格连接(因为 CountVectorizer 默认按空格分词)
corpus_cut = [" ".join(jieba.cut(sentence)) for sentence in corpus]
# 输出分词后的语料,看一下 jieba 是如何切词的
print("【分词结果】:")
for i, sent in enumerate(corpus_cut):
print(f"句子{i + 1}{sent}")
# 3. 创建 CountVectorizer 实例(词袋模型工具)
vectorizer = CountVectorizer()
# 4. 训练词袋模型,并将文本转换为词频向量
X = vectorizer.fit_transform(corpus_cut)
# 5. 输出词表(即 BoW 模型所构建的所有词汇)
print("\n【词表】:")
print(vectorizer.get_feature_names_out())
# 6. 输出词频向量(稀疏矩阵 → 转换成数组形式展示)
print("\n【向量表示】:")
print(X.toarray())

🤔 将句子转为了向量表示,有什么用呢?

根据向量就可以去评判文本的相似度,以及对文本进行分类。

# 1. 导入必要库
from sklearn.feature_extraction.text import CountVectorizer # 用于构建词袋模型
from sklearn.naive_bayes import MultinomialNB # 朴素贝叶斯分类器
from sklearn.pipeline import make_pipeline # 构建训练流程
import jieba # 中文分词库
import numpy as np
# 2. 准备语料和标签(1 表示正面,0 表示负面)
texts = [
"这家店的菜太难吃了", # 0
"服务态度很差,再也不来了", # 0
"菜品很丰富,味道不错", # 1
"真是一次愉快的用餐体验", # 1
"价格太贵,吃不饱", # 0
"环境很好,服务也周到" # 1
]
labels = [0, 0, 1, 1, 0, 1]
# 3. 使用 jieba 进行分词,并以空格连接适配 CountVectorizer
texts_cut = [" ".join(jieba.cut(text)) for text in texts]
# 输出分词结果
print("【分词结果】:")
for i, text in enumerate(texts_cut):
print(f"句子{i+1}{text}")
# 4. 初始化 CountVectorizer,并拟合语料库
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts_cut)
# 输出词表(词袋)
print("\n【词表(Vocabulary)】:")
print(vectorizer.get_feature_names_out())
# 输出向量表示(词频矩阵)
print("\n【词频向量表示】:")
print(X.toarray())
# 5. 使用朴素贝叶斯分类器进行训练
model = MultinomialNB()
model.fit(X, labels)
# 6. 测试新样本
test_comments = [
"服务不好,东西也难吃", # 0
"饭很好吃,环境也不错", # 1
"太贵了,不推荐", # 0
"下次还会来,真的很满意" # 1
]
test_cut = [" ".join(jieba.cut(comment)) for comment in test_comments]
# 输出测试分词
print("\n【测试集分词】:")
for i, sent in enumerate(test_cut):
print(f"测试{i+1}{sent}")
# 将测试语句转换为词袋向量
X_test = vectorizer.transform(test_cut)
# 输出测试向量
print("\n【测试集向量】:")
print(X_test.toarray())
# 7. 执行预测
predictions = model.predict(X_test)
# 输出最终预测结果
print("\n【情感预测结果】:")
for comment, label in zip(test_comments, predictions):
print(f"'{comment}' → {'正面' if label == 1 else '负面'}")

词袋模型的缺陷

  1. 只看词有没有,不看上下文
  2. 词序被忽略:在词袋模型里面,“我打了他”和“他打了我”,两句话会被看作是一个意思。
  3. 没语义泛化能力:模型不知道“满意”和“不错”意思相近,“愉快”和“开心”是同义词。

-EOF-