You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

conlleval.py 10 kB

4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. # Python version of the evaluation script from CoNLL'00-
  2. # Originates from: https://github.com/spyysalo/conlleval.py
  3. # Intentional differences:
  4. # - accept any space as delimiter by default
  5. # - optional file argument (default STDIN)
  6. # - option to set boundary (-b argument)
  7. # - LaTeX output (-l argument) not supported
  8. # - raw tags (-r argument) not supported
  9. import sys
  10. import re
  11. import codecs
  12. from collections import defaultdict, namedtuple
  13. ANY_SPACE = '<SPACE>'
  14. class FormatError(Exception):
  15. pass
  16. Metrics = namedtuple('Metrics', 'tp fp fn prec rec fscore')
  17. class EvalCounts(object):
  18. def __init__(self):
  19. self.correct_chunk = 0 # number of correctly identified chunks
  20. self.correct_tags = 0 # number of correct chunk tags
  21. self.found_correct = 0 # number of chunks in corpus
  22. self.found_guessed = 0 # number of identified chunks
  23. self.token_counter = 0 # token counter (ignores sentence breaks)
  24. # counts by type
  25. self.t_correct_chunk = defaultdict(int)
  26. self.t_found_correct = defaultdict(int)
  27. self.t_found_guessed = defaultdict(int)
  28. def parse_args(argv):
  29. import argparse
  30. parser = argparse.ArgumentParser(
  31. description='evaluate tagging results using CoNLL criteria',
  32. formatter_class=argparse.ArgumentDefaultsHelpFormatter
  33. )
  34. arg = parser.add_argument
  35. arg('-b', '--boundary', metavar='STR', default='-X-',
  36. help='sentence boundary')
  37. arg('-d', '--delimiter', metavar='CHAR', default=ANY_SPACE,
  38. help='character delimiting items in input')
  39. arg('-o', '--otag', metavar='CHAR', default='O',
  40. help='alternative outside tag')
  41. arg('file', nargs='?', default=None)
  42. return parser.parse_args(argv)
  43. def parse_tag(t):
  44. m = re.match(r'^([^-]*)-(.*)$', t)
  45. return m.groups() if m else (t, '')
  46. def evaluate(iterable, options=None):
  47. if options is None:
  48. options = parse_args([]) # use defaults
  49. counts = EvalCounts()
  50. num_features = None # number of features per line
  51. in_correct = False # currently processed chunks is correct until now
  52. last_correct = 'O' # previous chunk tag in corpus
  53. last_correct_type = '' # type of previously identified chunk tag
  54. last_guessed = 'O' # previously identified chunk tag
  55. last_guessed_type = '' # type of previous chunk tag in corpus
  56. for line in iterable:
  57. line = line.rstrip('\r\n')
  58. if options.delimiter == ANY_SPACE:
  59. features = line.split()
  60. else:
  61. features = line.split(options.delimiter)
  62. if num_features is None:
  63. num_features = len(features)
  64. elif num_features != len(features) and len(features) != 0:
  65. raise FormatError('unexpected number of features: %d (%d)' %
  66. (len(features), num_features))
  67. if len(features) == 0 or features[0] == options.boundary:
  68. features = [options.boundary, 'O', 'O']
  69. if len(features) < 3:
  70. raise FormatError('unexpected number of features in line %s' % line)
  71. guessed, guessed_type = parse_tag(features.pop())
  72. correct, correct_type = parse_tag(features.pop())
  73. first_item = features.pop(0)
  74. if first_item == options.boundary:
  75. guessed = 'O'
  76. end_correct = end_of_chunk(last_correct, correct,
  77. last_correct_type, correct_type)
  78. end_guessed = end_of_chunk(last_guessed, guessed,
  79. last_guessed_type, guessed_type)
  80. start_correct = start_of_chunk(last_correct, correct,
  81. last_correct_type, correct_type)
  82. start_guessed = start_of_chunk(last_guessed, guessed,
  83. last_guessed_type, guessed_type)
  84. if in_correct:
  85. if (end_correct and end_guessed and
  86. last_guessed_type == last_correct_type):
  87. in_correct = False
  88. counts.correct_chunk += 1
  89. counts.t_correct_chunk[last_correct_type] += 1
  90. elif (end_correct != end_guessed or guessed_type != correct_type):
  91. in_correct = False
  92. if start_correct and start_guessed and guessed_type == correct_type:
  93. in_correct = True
  94. if start_correct:
  95. counts.found_correct += 1
  96. counts.t_found_correct[correct_type] += 1
  97. if start_guessed:
  98. counts.found_guessed += 1
  99. counts.t_found_guessed[guessed_type] += 1
  100. if first_item != options.boundary:
  101. if correct == guessed and guessed_type == correct_type:
  102. counts.correct_tags += 1
  103. counts.token_counter += 1
  104. last_guessed = guessed
  105. last_correct = correct
  106. last_guessed_type = guessed_type
  107. last_correct_type = correct_type
  108. if in_correct:
  109. counts.correct_chunk += 1
  110. counts.t_correct_chunk[last_correct_type] += 1
  111. return counts
  112. def uniq(iterable):
  113. seen = set()
  114. return [i for i in iterable if not (i in seen or seen.add(i))]
  115. def calculate_metrics(correct, guessed, total):
  116. tp, fp, fn = correct, guessed-correct, total-correct
  117. p = 0 if tp + fp == 0 else 1.*tp / (tp + fp)
  118. r = 0 if tp + fn == 0 else 1.*tp / (tp + fn)
  119. f = 0 if p + r == 0 else 2 * p * r / (p + r)
  120. return Metrics(tp, fp, fn, p, r, f)
  121. def metrics(counts):
  122. c = counts
  123. overall = calculate_metrics(
  124. c.correct_chunk, c.found_guessed, c.found_correct
  125. )
  126. by_type = {}
  127. for t in uniq(list(c.t_found_correct) + list(c.t_found_guessed)):
  128. by_type[t] = calculate_metrics(
  129. c.t_correct_chunk[t], c.t_found_guessed[t], c.t_found_correct[t]
  130. )
  131. return overall, by_type
  132. def report(counts, out=None):
  133. if out is None:
  134. out = sys.stdout
  135. overall, by_type = metrics(counts)
  136. c = counts
  137. out.write('processed %d tokens with %d phrases; ' %
  138. (c.token_counter, c.found_correct))
  139. out.write('found: %d phrases; correct: %d.\n' %
  140. (c.found_guessed, c.correct_chunk))
  141. if c.token_counter > 0:
  142. out.write('accuracy: %6.2f%%; ' %
  143. (100.*c.correct_tags/c.token_counter))
  144. out.write('precision: %6.2f%%; ' % (100.*overall.prec))
  145. out.write('recall: %6.2f%%; ' % (100.*overall.rec))
  146. out.write('FB1: %6.2f\n' % (100.*overall.fscore))
  147. for i, m in sorted(by_type.items()):
  148. out.write('%17s: ' % i)
  149. out.write('precision: %6.2f%%; ' % (100.*m.prec))
  150. out.write('recall: %6.2f%%; ' % (100.*m.rec))
  151. out.write('FB1: %6.2f %d\n' % (100.*m.fscore, c.t_found_guessed[i]))
  152. def report_notprint(counts, out=None):
  153. if out is None:
  154. out = sys.stdout
  155. overall, by_type = metrics(counts)
  156. c = counts
  157. final_report = []
  158. line = []
  159. line.append('processed %d tokens with %d phrases; ' %
  160. (c.token_counter, c.found_correct))
  161. line.append('found: %d phrases; correct: %d.\n' %
  162. (c.found_guessed, c.correct_chunk))
  163. final_report.append("".join(line))
  164. if c.token_counter > 0:
  165. line = []
  166. line.append('accuracy: %6.2f%%; ' %
  167. (100.*c.correct_tags/c.token_counter))
  168. line.append('precision: %6.2f%%; ' % (100.*overall.prec))
  169. line.append('recall: %6.2f%%; ' % (100.*overall.rec))
  170. line.append('FB1: %6.2f\n' % (100.*overall.fscore))
  171. final_report.append("".join(line))
  172. for i, m in sorted(by_type.items()):
  173. line = []
  174. line.append('%17s: ' % i)
  175. line.append('precision: %6.2f%%; ' % (100.*m.prec))
  176. line.append('recall: %6.2f%%; ' % (100.*m.rec))
  177. line.append('FB1: %6.2f %d\n' % (100.*m.fscore, c.t_found_guessed[i]))
  178. final_report.append("".join(line))
  179. return final_report
  180. def end_of_chunk(prev_tag, tag, prev_type, type_):
  181. # check if a chunk ended between the previous and current word
  182. # arguments: previous and current chunk tags, previous and current types
  183. chunk_end = False
  184. if prev_tag == 'E': chunk_end = True
  185. if prev_tag == 'S': chunk_end = True
  186. if prev_tag == 'B' and tag == 'B': chunk_end = True
  187. if prev_tag == 'B' and tag == 'S': chunk_end = True
  188. if prev_tag == 'B' and tag == 'O': chunk_end = True
  189. if prev_tag == 'I' and tag == 'B': chunk_end = True
  190. if prev_tag == 'I' and tag == 'S': chunk_end = True
  191. if prev_tag == 'I' and tag == 'O': chunk_end = True
  192. if prev_tag != 'O' and prev_tag != '.' and prev_type != type_:
  193. chunk_end = True
  194. # these chunks are assumed to have length 1
  195. if prev_tag == ']': chunk_end = True
  196. if prev_tag == '[': chunk_end = True
  197. return chunk_end
  198. def start_of_chunk(prev_tag, tag, prev_type, type_):
  199. # check if a chunk started between the previous and current word
  200. # arguments: previous and current chunk tags, previous and current types
  201. chunk_start = False
  202. if tag == 'B': chunk_start = True
  203. if tag == 'S': chunk_start = True
  204. if prev_tag == 'E' and tag == 'E': chunk_start = True
  205. if prev_tag == 'E' and tag == 'I': chunk_start = True
  206. if prev_tag == 'S' and tag == 'E': chunk_start = True
  207. if prev_tag == 'S' and tag == 'I': chunk_start = True
  208. if prev_tag == 'O' and tag == 'E': chunk_start = True
  209. if prev_tag == 'O' and tag == 'I': chunk_start = True
  210. if tag != 'O' and tag != '.' and prev_type != type_:
  211. chunk_start = True
  212. # these chunks are assumed to have length 1
  213. if tag == '[': chunk_start = True
  214. if tag == ']': chunk_start = True
  215. return chunk_start
  216. def return_report(input_file):
  217. with codecs.open(input_file, "r", "utf8") as f:
  218. counts = evaluate(f)
  219. return report_notprint(counts)
  220. def main(argv):
  221. args = parse_args(argv[1:])
  222. if args.file is None:
  223. counts = evaluate(sys.stdin, args)
  224. else:
  225. with open(args.file) as f:
  226. counts = evaluate(f, args)
  227. report(counts)
  228. if __name__ == '__main__':
  229. sys.exit(main(sys.argv))

通过对新冠疫情相关信息收集,进行分类、归纳,取得事件之间的联系,可以构成一个丰富的新冠信息知识图谱。新冠信息知识图谱的构建能够充分挖掘信息价值,为人们提供直观的参考依据。本项目基于NEO4J图数据库,来进行COVID-19病例活动行径信息的知识图谱构建与应用,达到追溯传播途径、疫情防控的目的·。