前言

自然语言处理面临的文本数据往往是非结构化杂乱无章的文本数据,而机器学习算法处理的数据往往是固定长度的输入和输出,因而机器学习并不能直接处理原始的文本数据,必须把文本数据转换成数字,比如向量,这个过程就叫做特征提取或者特征编码。一种流行并且简单的特征提取方法就是词袋模型。

词袋模型(BOW)

介绍

词袋模型(Bag of Words)不考虑文本中词语的顺序,只考虑词表(vocabulary)或者语料库中的词语在这个文本中的出现次数,进而把一个文本转化为向量表示,是比较简单直白的一种特征提取方法。

模型构建过程

1. 获取文本数据(这里以两个文本句子为例)

1
2
# 目的:利用词袋模型得出下列句子的向量表示
txts = ['朋友们喜欢吃饭、睡觉、打豆豆。', '我喜欢吃饭、睡觉。']

2. 文本分词并构建语料库

NLP无法处理一整个句子,因此第一步工作往往是分词。中文文本的分词常用的是jieba分词,英文文本的分词可以使用NLTK中的word_tokenize函数

分词后进行语料库的构建,对于语料库的构建要保证语料库里面没有重复的词语或者标点,因而可以使用set方法。另外构建语料库后要进行数字映射便于后续的向量表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 构建语料库并进行数字映射
def construct_word_dict(texts):
"""
:param texts: 文本列表
:return: 字典(词语及对应的数字映射)
"""
words = []
for text in texts:
# jieba分词
words += jieba.lcut(text)
# 去除重复的元素
words = set(words)
# 建立词与数字的映射
words = dict(zip(words, range(len(words))))
return words

示例文本生成的语料库如下:

1
{'我': 0, '朋友': 1, '打': 2, '豆豆': 3, '吃饭': 4, '。': 5, '们': 6, '、': 7, '喜欢': 8, '睡觉': 9}

3. 建立文本的向量表示

在这里词袋模型的向量表示是把语料库中单词或标点的出现频数作为其对应的数字表示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 建立文本的向量表示
def vector_rep(text, corpus_dict):
"""
:param text: 文本
:param corpus_dict: 语料库
:return: 对应文本的向量表示
"""
vec = []
for key in corpus_dict.keys():
if key in text:
vec.append((corpus_dict[key], text.count(key)))
else:
vec.append((corpus_dict[key], 0))

vec = sorted(vec, key=lambda x: x[0])

return vec

示例文本对应的向量表示如下:(每一个元组里面第一个值是语料库某个词的数字映射,第二个值是其出现的频数)

1
2
3
4
# '朋友们喜欢吃饭、睡觉、打豆豆。'的向量表示
[(0, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 2), (9, 1)]
# '我喜欢吃饭、睡觉。'的向量表示
[(0, 1), (1, 0), (2, 1), (3, 1), (4, 1), (5, 0), (6, 1), (7, 0), (8, 1), (9, 0)]

模型应用与缺点

应用

通过词袋模型得到的文本的向量表示可以利用余弦相似度的方法来判断句子相似度

缺点

  1. 没有考虑词序以及词之间的联系,容易丢失了重要信息;
  2. 随着语料库的增大,一个文本的向量表示可能是一个非常稀疏的高维向量,严重影响内存和计算资源;
  3. 词袋模型严重缺乏相似词之间的表达,比如对于 ‘ 我喜欢北京 ’ 和 ‘ 我不喜欢北京 ‘这两个句子的是不相似的,但依据词袋模型得到的向量表示利用余弦相似度的方法所求的相似度较高。

余弦相似度

余弦相似度,也称为余弦距离,是用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小的度量,其计算公式及对应的python实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 余弦相似度
def similarity_with_2_txts(vec1, vec2):
"""
:param vec1: 文本1的向量表示(这里的向量形式是词袋模型所得到的向量表示形式)
:param vec2: 文本2的向量表示
:return: 两个文本的余弦相似度
"""
inner_product = 0
square_length_vec1 = 0
square_length_vec2 = 0
for tup1, tup2 in zip(vec1, vec2):
inner_product += tup1[1] * tup2[1]
square_length_vec1 += tup1[1] ** 2
square_length_vec2 += tup2[1] ** 2

return inner_product / sqrt(square_length_vec1 * square_length_vec2)

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# -*- coding: utf-8 -*-
# @Time : 2023/5/7 20:23
# @Author : aurora
# @FileName: BOW_Model.py
# @Software: PyCharm
# @Blog :https://2aurora2.github.io/

import jieba
import warnings
from math import sqrt

warnings.filterwarnings('ignore')


# 构建语料库并进行数字映射
def construct_word_dict(texts):
"""
:param texts: 文本列表
:return: 字典(词语及对应的数字映射)
"""
words = []
for text in texts:
# jieba分词
words += jieba.lcut(text)
# 去除重复的元素
words = set(words)
# 建立词与数字的映射
words = dict(zip(words, range(len(words))))
return words


# 建立文本的向量表示
def vector_rep(text, corpus_dict):
"""
:param text: 文本
:param corpus_dict: 语料库
:return: 对应文本的向量表示
"""
vec = []
for key in corpus_dict.keys():
if key in text:
vec.append((corpus_dict[key], text.count(key)))
else:
vec.append((corpus_dict[key], 0))

vec = sorted(vec, key=lambda x: x[0])

return vec


# 余弦相似度
def similarity_with_2_txts(vec1, vec2):
"""
:param vec1: 文本1的向量表示
:param vec2: 文本2的向量表示
:return: 两个文本的余弦相似度
"""
inner_product = 0
square_length_vec1 = 0
square_length_vec2 = 0
for tup1, tup2 in zip(vec1, vec2):
inner_product += tup1[1] * tup2[1]
square_length_vec1 += tup1[1] ** 2
square_length_vec2 += tup2[1] ** 2

return inner_product / sqrt(square_length_vec1 * square_length_vec2)


txts = ['我喜欢北京', '我不喜欢北京']

word_dict = {}
text_vec = []

if __name__ == '__main__':
# 构建语料库
word_dict = construct_word_dict(txts)
# 求出文本的向量表示
for txt in txts:
text_vec.append(vector_rep(txt, word_dict))
# 利用余弦相似度判断文本相似度
print(similarity_with_2_txts(text_vec[0], text_vec[1]))

参考文章

  1. NLP入门(一)词袋模型及句子相似度 - 山阴少年 - 博客园 (cnblogs.com)

  2. 词向量之词袋模型(BOW)详解_bow词袋模型_Elenstone的博客-CSDN博客