本文作为笔者NLP入门系列文章第一篇,以后我们就要步入NLP时代。
本文将会介绍NLP中常见的词袋模型(Bag of
Words)以及如何利用词袋模型来计算句子间的相似度(余弦相似度,cosine
similarity)。
首先,让我们来看一下,什么是词袋模型。我们以下面两个简单句子为例:
1 2 sent1 = "I love sky, I love sea." sent2 = "I like running, I love reading."
通常,NLP无法一下子处理完整的段落或句子,因此,第一步往往是分句和分词。这里只有句子,因此我们只需要分词即可。对于英语句子,可以使用NLTK中的word_tokenize函数,对于中文句子,则可使用jieba模块。故第一步为分词,代码如下:
1 2 3 from nltk import word_tokenize sents = [sent1, sent2] texts = [[word for word in word_tokenize(sent)] for sent in sents]
输出的结果如下:
1 [['I ', 'love ', 'sky ', ',', 'I ', 'love ', 'sea ', '. '], ['I ', 'like ', 'running ', ',', 'I ', 'love ', 'reading ', '. ']]
分词完毕。下一步是构建语料库,即所有句子中出现的单词及标点。代码如下:
1 2 3 4 5 all_list = []for text in texts: all_list += text corpus = set (all_list)print (corpus)
输出如下:
1 {'love', 'running', 'reading', 'sky', '.', 'I', 'like', 'sea', ','}
可以看到,语料库中一共是8个单词及标点。接下来,对语料库中的单词及标点建立数字映射,便于后续的句子的向量表示。代码如下:
1 2 corpus_dict = dict (zip (corpus, range (len (corpus))))print (corpus_dict)
输出如下:
1 {'running': 1 , 'reading': 2 , 'love': 0 , 'sky': 3 , '.': 4 , 'I': 5 , 'like': 6 , 'sea': 7 , ',': 8 }
虽然单词及标点并没有按照它们出现的顺序来建立数字映射,不过这并不会影响句子的向量表示及后续的句子间的相似度。
下一步,也就是词袋模型的关键一步,就是建立句子的向量表示。这个表示向量并不是简单地以单词或标点出现与否来选择0,1数字,而是把单词或标点的出现频数作为其对应的数字表示,结合刚才的语料库字典,句子的向量表示的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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)
输出如下:
1 2 [(0 , 2 ), (1 , 0 ), (2 , 0 ), (3 , 1 ), (4 , 1 ), (5 , 2 ), (6 , 0 ), (7 , 1 ), (8 , 1 )] [(0 , 1 ), (1 , 1 ), (2 , 1 ), (3 , 0 ), (4 , 1 ), (5 , 2 ), (6 , 1 ), (7 , 0 ), (8 , 1 )]
让我们稍微逗留一会儿,来看看这个向量。在第一句中I出现了两次,在预料库字典中,I对应的数字为5,因此在第一句中5出现2次,在列表中的元组即为(5,2),代表单词I在第一句中出现了2次。以上的输出可能并不那么直观,真实的两个句子的代表向量应为:
1 2 [2, 0, 0, 1, 1, 2, 0, 1, 1] [1, 1, 1, 0, 1, 2, 1, 0, 1]
OK,词袋模型到此结束。接下来,我们会利用刚才得到的词袋模型,即两个句子的向量表示,来计算相似度。
在NLP中,如果得到了两个句子的向量表示,那么,一般会选择用余弦相似度作为它们的相似度,而向量的余弦相似度即为两个向量的夹角的余弦值。其计算的Python代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from math import sqrtdef 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)
输出结果如下:
这样,我们就通过句子的词袋模型,得到了它们间的句子相似度。
当然,在实际的NLP项目中,如果需要计算两个句子的相似度,我们只需调用gensim模块即可,它是NLP的利器,能够帮助我们处理很多NLP任务。下面为用gensim计算两个句子的相似度的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 corporafrom gensim.similarities import Similarity dictionary = corpora.Dictionary(texts) 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)
输出结果如下:
1 2 3 [['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:
1 2 3 4 5 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模块的代码前添加以下代码即可:
1 2 3 import warnings warnings.filterwarnings(action='ignore' ,category=UserWarning,module='gensim' ) warnings.filterwarnings(action='ignore' ,category=FutureWarning,module='gensim' )
本文到此结束,感谢阅读!如果不当之处,请速联系笔者,欢迎大家交流!祝您好运~
欢迎关注我的公众号
NLP奇幻之旅 ,原创技术文章第一时间推送。
欢迎关注我的知识星球“自然语言处理奇幻之旅 ”,笔者正在努力构建自己的技术社区。