词袋模型(Bag-of-Words)是一种简单的文本表示方法,也是自然语言处理的一个 经典模型。它将文本中的词看作一个个独立的个体,不考虑它们在句子中的顺序,只关心每个词出现的频次。
工作原理
词袋模型的核心思想是把:文本表示成一个“词频向量”,不看词的顺序、句法结构,只关注词是否出现和出现的次数。
可以看作是一个装词的口袋,里面有大量的词,零零散散的装在里面的。
假设有三句话作为语料库(Corpus)
句子1:我喜欢吃苹果句子2:我不喜欢吃香蕉句子3:她喜欢吃葡萄第 1 步分词,也就是将每个句子切分成词:
["我", "喜欢", "吃", "苹果"]["我", "不", "喜欢", "吃", "香蕉"]["她", "喜欢", "吃", "葡萄"]第 2 步构建词表,提取所有出现过的唯一词汇,按顺序编号:
可以理解为针对上面的多个词表进行去重,构建一个词表
词表 = ["我", "喜欢", "吃", "苹果", "不", "香蕉", "她", "葡萄"]这个词表确定了向量的“维度”,共 8 个词,所以向量长度就是 8。
第 3 步构建词频向量,也就是每句话对应一个向量
| 词汇位置 | 我 | 喜欢 | 吃 | 苹果 | 不 | 香蕉 | 她 | 葡萄 |
|---|---|---|---|---|---|---|---|---|
| 句子1 向量 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| 句子2 向量 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 0 |
| 句子3 向量 | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
这个表就是 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 进行分词,并以空格连接适配 CountVectorizertexts_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 '负面'}")词袋模型的缺陷
- 只看词有没有,不看上下文
- 词序被忽略:在词袋模型里面,“我打了他”和“他打了我”,两句话会被看作是一个意思。
- 没语义泛化能力:模型不知道“满意”和“不错”意思相近,“愉快”和“开心”是同义词。
-EOF-