句子的相似性可以使用下面的一些方法来计算:
- 杰卡德相似度
- 余弦相似度
- TextRank 相似度
1. 杰卡德相似度
杰卡德相似度需要计算两个句子的交并集,然而这里没有说明使用什么粒度来计算。所以我们可以基于词粒度来计算,也可以基于字的粒度来计算。
如果以词的粒度来计算的话,我们可以先将句子A和句子B分词,计算两个句子词的并集减去两个句子词的交集,并除以两个句子词的并集,该值的取值范围为 [0, 1], 值越接近于 1,则说明两个句子越相似。
如果以字的粒度的话,就不需要分词了,也可以以字的粒度来计算两个句子的叫并集,从而计算出杰卡德相似度。
import jieba import numpy as np def jaccard_similarity(sentence1, sentence2): # 计算句子并集 sentence_union = np.union1d(sentence1, sentence2) # 计算句子交集 sentence_inter = np.intersect1d(sentence1, sentence2) print('交集:', sentence_union) print('并集:', sentence_inter) # 计算并集和交集的差集 sentence_diff = np.setdiff1d(sentence_union, sentence_inter) print('差集:', sentence_diff) # 计算杰卡德系数 jaccard_similarity = 1 - len(sentence_diff) / len(sentence_union) print('杰卡德相似度: %.2f' % jaccard_similarity) # 1. 字的粒度 def test01(): sentence1 = '我爱北京天安门' sentence2 = '我爱北京大前门' sentence1_words = list(sentence1) sentence2_words = list(sentence2) print('句子1:', sentence1_words) print('句子2:', sentence2_words) jaccard_similarity(sentence1_words, sentence2_words) # 2. 词的粒度 def test02(): # 添加自定义词 # jieba.load_userdict() jieba.add_word('大前门') sentence1 = '我爱北京天安门' sentence2 = '我爱北京大前门' sentence1_words = jieba.lcut(sentence1) sentence2_words = jieba.lcut(sentence2) print('句子1:', sentence1_words) print('句子2:', sentence2_words) jaccard_similarity(sentence1_words, sentence2_words) if __name__ == '__main__': test01() print('-' * 30) test02()
程序运行结果:
句子1: ['我', '爱', '北', '京', '天', '安', '门'] 句子2: ['我', '爱', '北', '京', '大', '前', '门'] 交集: ['京' '前' '北' '大' '天' '安' '我' '爱' '门'] 并集: ['京' '北' '我' '爱' '门'] 差集: ['前' '大' '天' '安'] 杰卡德相似度: 0.56 ------------------------------ 句子1: ['我', '爱', '北京', '天安门'] 句子2: ['我', '爱', '北京', '大前门'] 交集: ['北京' '大前门' '天安门' '我' '爱'] 并集: ['北京' '我' '爱'] 差集: ['大前门' '天安门'] 杰卡德相似度: 0.60
2. 余弦相似度
通过余弦相似度计算两个句子的相似性,其关键是将句子转换为向量表示。我们可以将其转换为词频向量,也可以通过 word2vec 将每个词的词向量相加取均值作为句子向量,也可以用其他的方法。余弦相似度取值范围为[-1, 1],越接近 1 说明两个句子越相似。
import jieba import numpy as np import torch from sklearn.feature_extraction.text import CountVectorizer if __name__ == '__main__': jieba.add_word('大前门') s1 = '我爱北京天安门' s2 = '我爱北京大前门' # 句子分词 s1 = ' '.join(jieba.lcut(s1)) s2 = ' '.join(jieba.lcut(s2)) # 词频向量 transfer = CountVectorizer() sentence_vector = transfer.fit_transform([s1, s2]).toarray() s1, s2 = sentence_vector # 余弦相似度 cosine_similarity = (s1 @ s2) / (np.sqrt(s1 @ s1) * np.sqrt(s2 @ s2)) print('cosine_similarity:', cosine_similarity) s1, s2 = torch.tensor([s1]).float(), torch.tensor([s2]).float() # 参数为两个二维向量 [batch_size, dim] cosine_similarity = torch.cosine_similarity(s1, s2) print('cosine_similarity:', cosine_similarity.item())
程序输出结果:
cosine_similarity: 0.4999999999999999 cosine_similarity: 0.5
3. TextRank 句子相似性
公式可理解为句子A和句子B交集词的数量除以句子A的长度对数和句子B的长度对数。
import jieba import numpy as np import torch from sklearn.feature_extraction.text import CountVectorizer if __name__ == '__main__': jieba.add_word('大前门') s1 = jieba.lcut('我爱北京天安门') s2 = jieba.lcut('我爱北京大前门') # 句子交集 intersect = np.intersect1d(s1, s2) # TextRank 相似度 similarity = len(intersect) / (np.log(len(s1)) + np.log(len(s2))) print('similarity:', similarity)
程序输出结果:
similarity: 1.0820212806667227
其他相似度计算方法后面再进行补充.