ciLin.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. #!/usr/bin/env python3
  2. #*- coding: utf-8*-
  3. #
  4. # Copyright (C) 2016 ashengtx <linls@whu.edu.cn>
  5. # under MIT License
  6. import math
  7. class CilinSimilarity(object):
  8. """
  9. 基于哈工大同义词词林扩展版计算语义相似度
  10. """
  11. def __init__(self):
  12. """
  13. 'code_word' 以编码为key,单词list为value的dict,一个编码有多个单词
  14. 'word_code' 以单词为key,编码为value的dict,一个单词可能有多个编码
  15. 'vocab' 所有的单词
  16. 'N' N为单词总数,包括重复的词
  17. """
  18. self.a = 0.65
  19. self.b = 0.8
  20. self.c = 0.9
  21. self.d = 0.96
  22. self.e = 0.5
  23. self.f = 0.1
  24. self.degree = 180
  25. self.code_word = {}
  26. self.word_code = {}
  27. self.vocab = set()
  28. self.N = 0
  29. self.read_cilin()
  30. def read_cilin(self):
  31. """
  32. 读入同义词词林,编码为key,词群为value,保存在self.code_word
  33. 单词为key,编码为value,保存在self.word_code
  34. 所有单词保存在self.vocab
  35. """
  36. with open('./cilin.txt', 'r', encoding='gbk') as f:
  37. for line in f.readlines():
  38. res = line.split()
  39. code = res[0]
  40. words = res[1:]
  41. self.vocab.update(words)
  42. self.code_word[code] = words
  43. self.N += len(words)
  44. for w in words:
  45. if w in self.word_code.keys():
  46. self.word_code[w].append(code)
  47. else:
  48. self.word_code[w] = [code]
  49. def get_common_str(self, c1, c2):
  50. """
  51. 获取两个字符的公共部分
  52. """
  53. res = ''
  54. for i, j in zip(c1, c2):
  55. if i == j:
  56. res += i
  57. else:
  58. break
  59. if 3 == len(res) or 6 == len(res):
  60. res = res[0:-1]
  61. return res
  62. def get_layer(self, common_str):
  63. """
  64. 根据common_str返回两个编码所在的层数。
  65. 如果没有共同的str,则位于第一层,用0表示;
  66. 如果第1个字符相同,则位于第二层,用1表示;
  67. 这里第一层用0表示。
  68. """
  69. length = len(common_str)
  70. table = {1: 1, 2: 2, 4: 3, 5: 4, 7: 5}
  71. if length in table.keys():
  72. return table[length]
  73. return 0
  74. def code_layer(sefl, c):
  75. """
  76. 将编码按层次结构化
  77. Aa01A01=
  78. 第三层和第五层是两个数字表示;
  79. 第一、二、四层分别是一个字母;
  80. 最后一个字符用来区分所有字符相同的情况。
  81. """
  82. return [c[0], c[1], c[2:4], c[4], c[5:7], c[7]]
  83. def get_k(self, c1, c2):
  84. """
  85. 返回两个编码对应分支的距离,相邻距离为1
  86. """
  87. if c1[0] != c2[0]:
  88. return abs(ord(c1[0]) - ord(c2[0]))
  89. elif c1[1] != c2[1]:
  90. return abs(ord(c1[1]) - ord(c2[1]))
  91. elif c1[2] != c2[2]:
  92. return abs(int(c1[2]) - int(c2[2]))
  93. elif c1[3] != c2[3]:
  94. return abs(ord(c1[3]) - ord(c2[3]))
  95. else:
  96. return abs(int(c1[4]) - int(c2[4]))
  97. def get_n(self, common_str):
  98. """
  99. 计算所在分支层的分支数
  100. 即计算分支的父节点总共有多少个子节点
  101. 两个编码的common_str决定了它们共同处于哪一层
  102. 例如,它们的common_str为前两层,则它们共同处于第三层,则我们统计前两层为common_str的第三层编码个数就好了
  103. """
  104. if 0 == len(common_str):
  105. return 0
  106. siblings = set()
  107. layer = self.get_layer(common_str)
  108. for c in self.code_word.keys():
  109. if c.startswith(common_str):
  110. clayer = self.code_layer(c)
  111. siblings.add(clayer[layer])
  112. return len(siblings)
  113. # sim2016 begin ====================================
  114. def sim2016(self, w1, w2):
  115. """
  116. 根据以下论文提出的改进方法计算:
  117. 《基于知网与词林的词语语义相似度计算》,朱新华,马润聪, 孙柳,陈宏朝( 广西师范大学 计算机科学与信息工程学院,广西 桂林541004)
  118. """
  119. # 如果有一个词不在词林中,则相似度为0
  120. if w1 not in self.vocab or w2 not in self.vocab:
  121. return 0
  122. sim_max = 0
  123. # 获取两个词的编码
  124. code1 = self.word_code[w1]
  125. code2 = self.word_code[w2]
  126. for c1 in code1:
  127. for c2 in code2:
  128. cur_sim = self.sim2016_by_code(c1, c2)
  129. # print(c1, c2, cur_sim)
  130. if cur_sim > sim_max:
  131. sim_max = cur_sim
  132. return sim_max
  133. def sim2016_by_code(self, c1, c2):
  134. """
  135. 根据编码计算相似度
  136. """
  137. # 先把code的层级信息提取出来
  138. clayer1 = self.code_layer(c1)
  139. clayer2 = self.code_layer(c2)
  140. common_str = self.get_common_str(c1, c2)
  141. # print('common_str: ', common_str)
  142. length = len(common_str)
  143. # 如果有一个编码以'@'结尾,那么表示自我封闭,这个编码中只有一个词,直接返回f
  144. if c1.endswith('@') or c2.endswith('@') or 0 == length:
  145. return self.f
  146. cur_sim = 0
  147. if 7 <= length:
  148. # 如果前面七个字符相同,则第八个字符也相同,要么同为'=',要么同为'#''
  149. if c1.endswith('=') and c2.endswith('='):
  150. cur_sim = 1
  151. elif c1.endswith('#') and c2.endswith('#'):
  152. cur_sim = self.e
  153. else:
  154. # 从这里开始要改,这之前都一样
  155. k = self.get_k(clayer1, clayer2)
  156. n = self.get_n(common_str)
  157. d = self.dist2016(common_str)
  158. e = math.sqrt(self.epow(-1 * k / (2 * n)))
  159. cur_sim = (1.05 - 0.05 * d) * e
  160. return cur_sim
  161. def dist2016(self, common_str):
  162. """
  163. 计算两个编码的距离
  164. """
  165. w1, w2, w3, w4, = 0.5, 1, 2.5, 2.5
  166. weights = [w1, w2, w3, w4]
  167. layer = self.get_layer(common_str)
  168. try:
  169. if 0 == layer:
  170. return 18
  171. else:
  172. return 2 * sum(weights[0:4 - layer + 1])
  173. except Exception as e:
  174. print('dist2016 errer, 共有的层数不能大于5')
  175. def epow(self, x):
  176. """ e^x """
  177. return pow(math.e, x)
  178. # sim2016 end ====================================