NLP从入门到实战(三)

词袋模型与句子相似度计算

本文将会介绍NLP中常见的词袋模型(Bag of Words)以及如何利用词袋模型来计算句子间的相似度(余弦相似度,cosine similarity)。
 首先,让我们来看一下,什么是词袋模型。

将所有词语装进一个袋子里,不考虑其词法和语序的问题,即每个词语都是独立的。例如下面个例句,就可以构成一个词袋,袋子里包括所有词语。假设建立一个数组(或词典)用于映射匹配

我们以下面两个简单句子为例:

sent1 = "Word bag model,Put all the words in a bag, regardless of their morphology and word order, that is, each word is independent. For example, the above two examples can form a word bag, which includes Jane, wants, to, go, Shenzhen, Bob and Shanghai. Suppose you build an array (or dictionary) for mapping matches."
sent2 = "Words bags model,Put all the words in a bag, regardless of their morphology and word order, this is, each word is independent. For example, the above two examples can form a word bag, which includes Jane, wants, to, go, Shenzhen, Bob and Shanghai. Suppose you build an array (or dictionary) for mapping matches."

通常,NLP无法一下子处理完整的段落或句子,因此,第一步往往是分句和分词。这里只有句子,因此我们只需要分词即可。对于英语句子,可以使用NLTK中的word_tokenize函数,对于中文句子,则可使用jieba模块。故第一步为分词,代码如下:

from nltk import word_tokenize
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]

输出的结果如下:

[['Word', 'bag', 'model', ',', 'Put', 'all', 'the', 'words', 'in', 'a', 'bag', ',', 'regardless', 'of', 'their', 'morphology', 'and', 'word', 'order', ',', 'that', 'is', ',', 'each', 'word', 'is', 'independent', '.', 'For', 'example', ',', 'the', 'above', 'two', 'examples', 'can', 'form', 'a', 'word', 'bag', ',', 'which', 'includes', 'Jane', ',', 'wants', ',', 'to', ',', 'go', ',', 'Shenzhen', ',', 'Bob', 'and', 'Shanghai', '.', 'Suppose', 'you', 'build', 'an', 'array', '(', 'or', 'dictionary', ')', 'for', 'mapping', 'matches', '.'],
['Words', 'bags', 'model', ',', 'Put', 'all', 'the', 'words', 'in', 'a', 'bag', ',', 'regardless', 'of', 'their', 'morphology', 'and', 'word', 'order', ',', 'this', 'is', ',', 'each', 'word', 'is', 'independent', '.', 'For', 'example', ',', 'the', 'above', 'two', 'examples', 'can', 'form', 'a', 'word', 'bag', ',', 'which', 'includes', 'Jane', ',', 'wants', ',', 'to', ',', 'go', ',', 'Shenzhen', ',', 'Bob', 'and', 'Shanghai', '.', 'Suppose', 'you', 'build', 'an', 'array', '(', 'or', 'dictionary', ')', 'for', 'mapping', 'matches', '.']]

分词完毕。下一步是构建语料库,即所有句子中出现的单词及标点。代码如下:

all_list = []
for text in texts:
    all_list += text
corpus = set(all_list)
print(corpus)

输出如下:

{'wants', 'to', 'each', 'mapping', 'Words', 'morphology', 'array', '.', 'for', 'you', 'and', 'is', 'matches', 'Word', 'word', ',', 'Bob', 'can', 'which', 'a', 'that', 'an', 'Put', 'includes', 'bag', 'this', 'the', 'bags', 'words', 'two', 'in', 'Suppose', 'build', 'dictionary', 'examples', 'Shanghai', 'all', 'For', 'Jane', 'of', 'or', 'form', 'go', 'their', 'model', 'regardless', 'order', 'independent', 'example', 'above', 'Shenzhen', '(', ')'}

可以看到,语料库中一共是8个单词及标点。接下来,对语料库中的单词及标点建立数字映射,便于后续的句子的向量表示。代码如下:

corpus_dict = dict(zip(corpus, range(len(corpus))))
print(corpus_dict)

输出如下:

{'wants': 0, 'to': 1, 'each': 2, 'mapping': 3, 'Words': 4, 'morphology': 5, 'array': 6, '.': 7, 'for': 8, 'you': 9, 'and': 10, 'is': 11, 'matches': 12, 'Word': 13, 'word': 14, ',': 15, 'Bob': 16, 'can': 17, 'which': 18, 'a': 19, 'that': 20, 'an': 21, 'Put': 22, 'includes': 23, 'bag': 24, 'this': 25, 'the': 26, 'bags': 27, 'words': 28, 'two': 29, 'in': 30, 'Suppose': 31, 'build': 32, 'dictionary': 33, 'examples': 34, 'Shanghai': 35, 'all': 36, 'For': 37, 'Jane': 38, 'of': 39, 'or': 40, 'form': 41, 'go': 42, 'their': 43, 'model': 44, 'regardless': 45, 'order': 46, 'independent': 47, 'example': 48, 'above': 49, 'Shenzhen': 50, '(': 51, ')': 52}

虽然单词及标点并没有按照它们出现的顺序来建立数字映射,不过这并不会影响句子的向量表示及后续的句子间的相似度。
 下一步,也就是词袋模型的关键一步,就是建立句子的向量表示。这个表示向量并不是简单地以单词或标点出现与否来选择0,1数字,而是把单词或标点的出现频数作为其对应的数字表示,结合刚才的语料库字典,句子的向量表示的代码如下:

# 建立句子的向量表示
def vector_rep(text, corpus_dict):
    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

vec1 = vector_rep(texts[0], corpus_dict)
vec2 = vector_rep(texts[1], corpus_dict)
print(vec1)
print(vec2)

输出如下:

[(0, 1), (1, 1), (2, 1), (3, 1), (4, 0), (5, 1), (6, 1), (7, 3), (8, 1), (9, 1), (10, 2), (11, 2), (12, 1), (13, 1), (14, 3), (15, 11), (16, 1), (17, 1), (18, 1), (19, 2), (20, 1), (21, 1), (22, 1), (23, 1), (24, 3), (25, 0), (26, 2), (27, 0), (28, 1), (29, 1), (30, 1), (31, 1), (32, 1), (33, 1), (34, 1), (35, 1), (36, 1), (37, 1), (38, 1), (39, 1), (40, 1), (41, 1), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 1), (51, 1), (52, 1)]
[(0, 1), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 3), (8, 1), (9, 1), (10, 2), (11, 2), (12, 1), (13, 0), (14, 3), (15, 11), (16, 1), (17, 1), (18, 1), (19, 2), (20, 0), (21, 1), (22, 1), (23, 1), (24, 2), (25, 1), (26, 2), (27, 1), (28, 1), (29, 1), (30, 1), (31, 1), (32, 1), (33, 1), (34, 1), (35, 1), (36, 1), (37, 1), (38, 1), (39, 1), (40, 1), (41, 1), (42, 1), (43, 1), (44, 1), (45, 1), (46, 1), (47, 1), (48, 1), (49, 1), (50, 1), (51, 1), (52, 1)]

让我们稍微逗留一会儿,来看看这个向量。在第一句中I出现了两次,在预料库字典中,I对应的数字为5,因此在第一句中5出现2次,在列表中的元组即为(5,2),代表单词I在第一句中出现了2次。以上的输出可能并不那么直观

OK,词袋模型到此结束。接下来,我们会利用刚才得到的词袋模型,即两个句子的向量表示,来计算相似度。
 在NLP中,如果得到了两个句子的向量表示,那么,一般会选择用余弦相似度作为它们的相似度,而向量的余弦相似度即为两个向量的夹角的余弦值。其计算的Python代码如下:

from math import sqrt
def similarity_with_2_sents(vec1, vec2):
    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))


cosine_sim = similarity_with_2_sents(vec1, vec2)
print('两个句子的余弦相似度为: %.4f。'%cosine_sim)

输出结果如下:

两个句子的余弦相似度为: 0.9853。

这样,我们就通过句子的词袋模型,得到了它们间的句子相似度。
 当然,在实际的NLP项目中,如果需要计算两个句子的相似度,我们只需调用gensim模块即可,它是NLP的利器,能够帮助我们处理很多NLP任务。下面为用gensim计算两个句子的相似度的代码:

sent1 = "I love sky, I love sea."
sent2 = "I like running, I love reading."

from nltk import word_tokenize
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]
print(texts)

from gensim import corpora
from gensim.similarities import Similarity

#  语料库
dictionary = corpora.Dictionary(texts)

# 利用doc2bow作为词袋模型
corpus = [dictionary.doc2bow(text) for text in texts]
similarity = Similarity('-Similarity-index', corpus, num_features=len(dictionary))
print(similarity)
# 获取句子的相似度
new_sensence = sent1
test_corpus_1 = dictionary.doc2bow(word_tokenize(new_sensence))

cosine_sim = similarity[test_corpus_1][1]
print("利用gensim计算得到两个句子的相似度: %.4f。"%cosine_sim)

输出结果如下:

[['I', 'love', 'sky', ',', 'I', 'love', 'sea', '.'], ['I', 'like', 'running', ',', 'I', 'love', 'reading', '.']]
Similarity index with 2 documents in 0 shards (stored under -Similarity-index)
利用gensim计算得到两个句子的相似度: 0.7303。

注意,如果在运行代码时出现以下warning:

gensim\utils.py:1209: UserWarning: detected Windows; aliasing chunkize to chunkize_serial
  warnings.warn("detected Windows; aliasing chunkize to chunkize_serial")

gensim\matutils.py:737: FutureWarning: Conversion of the second argument of issubdtype from `int` to `np.signedinteger` is deprecated. In future, it will be treated as `np.int32 == np.dtype(int).type`.
  if np.issubdtype(vec.dtype, np.int):

如果想要去掉这些warning,则在导入gensim模块的代码前添加以下代码即可:

import warnings
warnings.filterwarnings(action='ignore',category=UserWarning,module='gensim')
warnings.filterwarnings(action='ignore',category=FutureWarning,module='gensim')

附上源码

from nltk import word_tokenize

sent1 = "Word bag model,Put all the words in a bag, regardless of their morphology and word order, that is, each word is independent. For example, the above two examples can form a word bag, which includes Jane, wants, to, go, Shenzhen, Bob and Shanghai. Suppose you build an array (or dictionary) for mapping matches."
sent2 = "Words bags model,Put all the words in a bag, regardless of their morphology and word order, this is, each word is independent. For example, the above two examples can form a word bag, which includes Jane, wants, to, go, Shenzhen, Bob and Shanghai. Suppose you build an array (or dictionary) for mapping matches."
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]
print(texts)
all_list = []
for text in texts:
    all_list += text
corpus = set(all_list)
print(corpus)
corpus_dict = dict(zip(corpus, range(len(corpus))))
print(corpus_dict)

def create_vector(text, corpus_dict):
    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))
    print(vec)
    vec = sorted(vec, key=lambda x: x[0])
    return vec

vec1 = create_vector(texts[0], corpus_dict)
vec2 = create_vector(texts[1], corpus_dict)
print(vec1, vec2)

from math import sqrt
def similarity_with_2_sents(vec1, vec2):
    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))


cosine_sim = similarity_with_2_sents(vec1, vec2)
print('两个句子的余弦相似度为: %.4f。'%cosine_sim)



sent1 = "I love sky, I love sea."
sent2 = "I like running, I love reading."

from nltk import word_tokenize
sents = [sent1, sent2]
texts = [[word for word in word_tokenize(sent)] for sent in sents]
print(texts)

from gensim import corpora
from gensim.similarities import Similarity

#  语料库
dictionary = corpora.Dictionary(texts)

# 利用doc2bow作为词袋模型
corpus = [dictionary.doc2bow(text) for text in texts]
similarity = Similarity('-Similarity-index', corpus, num_features=len(dictionary))
print(similarity)
# 获取句子的相似度
new_sensence = sent1
test_corpus_1 = dictionary.doc2bow(word_tokenize(new_sensence))

cosine_sim = similarity[test_corpus_1][1]
print("利用gensim计算得到两个句子的相似度: %.4f。"%cosine_sim)

本文到此结束,感谢阅读!如果不当之处,请速联系笔者,欢迎大家交流!祝您好运~

猜你喜欢

转载自blog.csdn.net/qq_46098574/article/details/106588026