词语的文本相似度

一.基于词典

人为构建,比较主观,不利于维护

1.1 基于词林

1.1.1 结构

扩展版同义词词林分为5层结构,如图,随着级别的递增,词义刻画越来越细,到了第五层,每个分类里词语数量已经不大,很多只有一个词语,已经不可再分,可以称为原子词群、原子类或原子节点。不同级别的分类结果可以为自然语言处理提供不同的服务,例如第四层的分类和第五层的分类在信息检索、文本分类、自动问答等研究领域得到应用。有研究证明,对词义进行有效扩展,或者对关键词做同义词替换可以明显改善信息检索、文本分类和自动问答系统的性能。

下载后的词典文件如下所示:

1
2
3
4
5
Aa01A01= 人 士 人物 人士 人氏 人选
Aa01A02= 人类 生人 全人类
Aa01A03= 人手 人员 人口 人丁 口 食指
Aa01A04= 劳力 劳动力 工作者
Aa01A05= 匹夫 个人

表中的编码位是按照从左到右的顺序排列。第八位的标记有3 种,分别是“=”、“#”、“@”, “=”代表“相等”、“同义”。末尾的“#”代表“不等”、“同类”,属于相关词语。末尾的“@”代表“自我封闭”、“独立”,它在词典中既没有同义词,也没有相关词。

源码如下

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
class WordSimilarity2010(SimilarBase):
'''
本类根据下面的论文方法:
基于同义词词林的词语相似度计算方法,田久乐, 赵 蔚(东北师范大学 计算机科学与信息技术学院, 长春 130117 )
计算两个单词所有编码组合的相似度,取最大的一个
'''

def __init__(self):
super(WordSimilarity2010, self).__init__()
self.a = 0.65
self.b = 0.8
self.c = 0.9
self.d = 0.96
self.e = 0.5
self.f = 0.1
self.degree = 180
self.PI = math.pi

def similarity(self, w1, w2):
'''
判断两个词的相似性。
:param w1: [string]
:param w2: [string]
:return: [float]0~1之间。
'''

code1 = self._data.get(w1, None)
code2 = self._data.get(w2, None)

if not code1 or not code2:
return 0 # 只要有一个不在库里则代表没有相似性。

# 最终返回的最大相似度
sim_max = 0

# 两个词可能对应多个编码
for c1 in code1:
for c2 in code2:
cur_sim = self.sim_by_code(c1, c2)
# print(c1, c2, '的相似度为:', cur_sim)
if cur_sim > sim_max:
sim_max = cur_sim

return sim_max

def sim_by_code(self, c1, c2):
"""
根据编码计算相似度
"""

# 先把code的层级信息提取出来
clayer1 = self._parse_code(c1)
clayer2 = self._parse_code(c2)

common_layer = self.get_common_layer(clayer1,clayer2)
length = len(common_layer)

# 如果有一个编码以'@'结尾,那么表示自我封闭,这个编码中只有一个词,直接返回f
if c1.endswith('@') or c2.endswith('@') or 0 == length:
return self.f

cur_sim = 0
if 6 <= length:
# 如果前面七个字符相同,则第八个字符也相同,要么同为'=',要么同为'#''
if c1.endswith('=') and c2.endswith('='):
cur_sim = 1
elif c1.endswith('#') and c2.endswith('#'):
cur_sim = self.e
else:
k = self.get_k(clayer1, clayer2)
n = self.get_n(common_layer)
if 1 == length:
cur_sim = self.sim_formula(self.a, n, k)
elif 2 == length:
cur_sim = self.sim_formula(self.b, n, k)
elif 3 == length:
cur_sim = self.sim_formula(self.c, n, k)
elif 4 == length:
cur_sim = self.sim_formula(self.d, n, k)

return cur_sim

def sim_formula(self, coeff, n, k):
"""
计算相似度的公式,不同的层系数不同
"""
return coeff * math.cos(n * self.PI / self.degree) * ((n - k + 1) / n)

def get_common_layer(self, ca, cb):
'''
返回相应的layer层
:param ca: [list(str)] 分解后的编码。
:param cb: [list(str)] 分解后的编码。
:return: [list(str)]列表代表相应的根编码。
'''
common_layer = []

for i, j in zip(ca, cb):
if i == j:
common_layer.append(i)
else:
break
return common_layer

def get_k(self, c1, c2):
"""
返回两个编码对应分支的距离,相邻距离为1
"""
if c1[0] != c2[0]:
return abs(ord(c1[0]) - ord(c2[0]))
elif c1[1] != c2[1]:
return abs(ord(c1[1]) - ord(c2[1]))
elif c1[2] != c2[2]:
return abs(int(c1[2]) - int(c2[2]))
elif c1[3] != c2[3]:
return abs(ord(c1[3]) - ord(c2[3]))
else:
return abs(int(c1[4]) - int(c2[4]))

def get_n(self, common_layer):
'''
返回相应结点下有多少个同级子结点。
:param common_layer: [listr(str)]相同的结点。
:return: int
'''

end_node = self._code_tree
for t_node_name in common_layer:
end_node = end_node[t_node_name]

if not isinstance(end_node, dict):
return end_node
return len(end_node.keys())

1.1.2 使用

环境准备:pip install WordSimilarity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from word_similarity import WordSimilarity2010
import time

ws_tool = WordSimilarity2010()
start = time.time()
b_a = "联系方式"
b_b = "电话"
sim_b = ws_tool.similarity(b_a, b_b)
print(b_a, b_b, '相似度为', sim_b)
end = time.time()
print("运行时间:"+str(end-start))
b_a = "手机"
b_b = "电话"
sim_b = ws_tool.similarity(b_a, b_b)
print(b_a, b_b, '相似度为', sim_b)
end = time.time()
print("运行时间:"+str(end-start))
1
2
3
4
联系方式 电话 相似度为 0
运行时间:5.793571472167969e-05
手机 电话 相似度为 0.30484094213212237
运行时间:0.0001442432403564453

1.2 基于知网与词林的词语语义相似度计算

1.2.1 原理

综合了词林cilin与知网hownet的相似度计算方法,采用混合策略,混合策略具体可以参考源码,如下

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
82
83
84
85
from hownet.howNet import How_Similarity
from cilin.V3.ciLin import CilinSimilarity

class HybridSim():
'''
混合相似度计算策略。使用了词林与知网词汇量的并集。扩大了词汇覆盖范围。
'''
ci_lin = CilinSimilarity() # 实例化词林相似度计算对象
how_net = How_Similarity() # 实例化知网相似度计算对象
Common = ci_lin.vocab & how_net.vocab
A = how_net.vocab - ci_lin.vocab
B = ci_lin.vocab - how_net.vocab

@classmethod
def get_Final_sim(cls, w1, w2):
lin = cls.ci_lin.sim2018(w1, w2) if w1 in cls.ci_lin.vocab and w2 in cls.ci_lin.vocab else 0
how = cls.how_net.calc(w1, w2) if w1 in cls.how_net.vocab and w2 in cls.how_net.vocab else 0

if w1 in cls.Common and w2 in cls.Common: # 两个词都被词林和知网共同收录。
# print('两个词都被词林和知网共同收录。', end='\t')
# print(w1, w2, '词林改进版相似度:', lin, end='\t')
# print('知网相似度结果为:', how, end='\t')
return lin * 1 + how * 0 # 可以调节两者的权重,以获取更优结果!!

if w1 in cls.A and w2 in cls.A: # 两个词都只被知网收录。
return how
if w1 in cls.B and w2 in cls.B: # 两个词都只被词林收录。
return lin

if w1 in cls.A and w2 in cls.B: # 一个只被词林收录,另一个只被知网收录。
print('触发策略三,左词为知网,右词为词林')
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w2][0]]
if not same_words:
return 0.2
all_sims = [cls.how_net.calc(word, w1) for word in same_words]
print(same_words, all_sims, end='\t')
return max(all_sims)

if w2 in cls.A and w1 in cls.B:
print('触发策略三,左词为词林,右词为知网')
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w1][0]]
if not same_words:
return 0.2
all_sims = [cls.how_net.calc(word, w2) for word in same_words]
print(w1, '词林同义词有:', same_words, all_sims, end='\t')
return max(all_sims)

if w1 in cls.A and w2 in cls.Common:
print('策略四(左知网):知网相似度结果为:', how)
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w2][0]]
if not same_words:
return how
all_sims = [cls.how_net.calc(word, w1) for word in same_words]
print(w2, '词林同义词有:', same_words, all_sims, end='\t')
return 0.6 * how + 0.4 * max(all_sims)

if w2 in cls.A and w1 in cls.Common:
print('策略四(右知网):知网相似度结果为:', how)
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w1][0]]
if not same_words:
return how
all_sims = [cls.how_net.calc(word, w2) for word in same_words]
print(same_words, all_sims, end='\t')
return 0.6 * how + 0.4 * max(all_sims)

if w1 in cls.B and w2 in cls.Common:
print(w1, w2, '策略五(左词林):词林改进版相似度:', lin)
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w1][0]]
if not same_words:
return lin
all_sims = [cls.how_net.calc(word, w2) for word in same_words]
print(w1, '词林同义词有:', same_words, all_sims, end='\t')
return 0.6 * lin + 0.4 * max(all_sims)

if w2 in cls.B and w1 in cls.Common:
print(w1, w2, '策略五(右词林):词林改进版相似度:', lin)
same_words = cls.ci_lin.code_word[cls.ci_lin.word_code[w2][0]]
if not same_words:
return lin
all_sims = [cls.how_net.calc(word, w1) for word in same_words]
print(w2, '词林同义词有:', same_words, all_sims, end='\t')
return 0.6 * lin + 0.4 * max(all_sims)

print('对不起,词语可能未收录,无法计算相似度!')
return -1

1.2.2 使用

参考https://github.com/yaleimeng/Final_word_Similarity

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
from Hybrid_Sim import HybridSim
from Pearson import *

import time


if __name__ == '__main__':

print('词林词汇量', len(HybridSim.ci_lin.vocab ),'\t知网词汇量', len(HybridSim.how_net.vocab))
print('两者总词汇量',len(HybridSim.ci_lin.vocab | HybridSim.how_net.vocab),'\t重叠词汇量', len(HybridSim.Common))
b_a = "联系方式"
b_b = "电话"
start = time.time()
hybrid = HybridSim.get_Final_sim(b_a, b_a)
end = time.time()
print(b_a+" "+b_b+"相似度为:", hybrid)
print("运行时间:"+str(end-start))
b_a = "手机"
b_b = "电话"
start = time.time()
hybrid = HybridSim.get_Final_sim(b_a, b_a)
end = time.time()
print(b_a+" "+b_b+"相似度为:", hybrid)
print("运行时间:"+str(end-start))

1
2
3
4
5
6
7
词林词汇量 77498 	知网词汇量 53336
两者总词汇量 85817 重叠词汇量 45017
对不起,词语可能未收录,无法计算相似度!
联系方式 电话相似度为: -1
运行时间:3.504753112792969e-05
手机 电话相似度为: 1.0
运行时间:0.019332408905029297

二.基于词向量

基于样本构建,利于维护

2.1 基于word2vec

2.2.1 原理

word2vec的原理和词向量获取过程不在此赘述,在本部分主要讲解基于word2vec的词向量如何计算词语相似度。源码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def similarity(self, w1, w2):
"""Compute cosine similarity between two keys.

Parameters
----------
w1 : str
Input key.
w2 : str
Input key.

Returns
-------
float
Cosine similarity between `w1` and `w2`.

"""
return dot(matutils.unitvec(self[w1]), matutils.unitvec(self[w2]))

2.2.2 使用

训练

1
2
3
4
5
6
7
8
9
10
11
from gensim.models.word2vec import Word2Vec
import pandas as pd
from gensim import models
import jieba
###train
data=pd.read_csv(data_path)
sentences=data.tolist()
model= Word2Vec()
model.build_vocab(sentences)
model.train(sentences,total_examples = model.corpus_count,epochs = 5)
model.save(model_path)

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from gensim import models
import time

if __name__ == '__main__':
model=models.Word2Vec.load(model_path)
start = time.time()
b_a = "联系方式"
b_b = "电话"
sim_b = model.wv.n_similarity(b_a, b_b)
end = time.time()
start = time.time()
print(b_a, b_b, '相似度为', sim_b)
print("运行时间:" + str(end - start))
b_a = "手机"
b_b = "电话"
sim_b = model.wv.n_similarity(b_a, b_b)
end = time.time()
print(b_a, b_b, '相似度为', sim_b)
print("运行时间:" + str(end - start))
1
2
3
4
联系方式 电话 相似度为 -0.014857853
运行时间:-4.76837158203125e-07
手机 电话 相似度为 0.1771852
运行时间:0.0004227161407470703

参考文献

https://blog.csdn.net/sinat_33741547/article/details/80016713

https://github.com/yaleimeng/Final_word_Similarity

Author

Lavine Hu

Posted on

2021-07-21

Updated on

2021-11-26

Licensed under

Comments

:D 一言句子获取中...