1 """Tagger evaluation routines.
2
3 @note: This was originally C{jazzparser.utils.stats}, but only ended up having
4 tagger evaluation stuff in it.
5
6 """
7 """
8 ============================== License ========================================
9 Copyright (C) 2008, 2010-12 University of Edinburgh, Mark Granroth-Wilding
10
11 This file is part of The Jazz Parser.
12
13 The Jazz Parser is free software: you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation, either version 3 of the License, or
16 (at your option) any later version.
17
18 The Jazz Parser is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License
24 along with The Jazz Parser. If not, see <http://www.gnu.org/licenses/>.
25
26 ============================ End license ======================================
27
28 """
29 __author__ = "Mark Granroth-Wilding <mark.granroth-wilding@ed.ac.uk>"
30
31 from math import log
32 import logging
33
34
35 logger = logging.getLogger("main_logger")
36
37 -def tagger_entropy(input, grammar, tagger_class, correct_tags, options={}):
38 """
39 Given some input, a tagger and a list of correct tags,
40 computes the entropy of the tagger over the input, returning a tuple
41 of the total entropy and the number of chords that were involved
42 in the computation.
43
44 The option dictionary options will be passed to the tagger.
45 """
46
47 tagger = tagger_class(grammar, input, options=options)
48 def _get_word(i):
49 if i >= len(correct_tags):
50 return None
51 else:
52 return tagger.get_word(i)
53 def _get_context(i):
54 if i == 0:
55 return "START *%s* %s" % (_get_word(0), _get_word(1))
56 elif i == 1:
57 return "START %s *%s* %s" % (_get_word(0), _get_word(1), _get_word(2))
58 else:
59 return "%s %s *%s* %s" % (_get_word(i-2), _get_word(i-1), _get_word(i), _get_word(i+1))
60
61 total_entropy = 0.0
62 total_chords = 0
63 for i in range(tagger.input_length):
64
65 if correct_tags[i] not in ["", "?"]:
66
67
68 correct = (_get_word(i).root, correct_tags[i])
69 probability = tagger.get_tag_probability(i, correct)
70 if probability == 0.0:
71
72
73
74
75 logger.error("The correct tag (%s) was given 0 probability by the tagger. Context: %s. Assigning a very small probability instead." % (correct_tags[i], _get_context(i)))
76 probability = float('1e-50')
77 entropy = log(probability, 2)
78 total_entropy += entropy
79 total_chords += 1
80 total_entropy = -1 * total_entropy
81 return (total_entropy, total_chords)
82
83 -def tagger_agreement(input, grammar, tagger_class, correct_tags, options={}, confusion_matrix=None, topn=1):
84 """
85 Like tagger_entropy, but instead of computing the entropy computes
86 the proportion of top tags assigned that agree with the gold
87 standard.
88
89 The option dictionary options will be passed to the tagger.
90
91 Optionally puts a confusion matrix in the dictionary given in
92 confusion_matrix. This is a dictionary keyed by correct tags whose
93 values are dictionaries keyed by the incorrect tags that were
94 confused for them, whose values are a count of the confusions.
95
96 Returns a tuple (number_agreeing, number_compared).
97
98 """
99
100 tagger = tagger_class(grammar, input, options=options)
101
102 total_agreeing = 0
103 total_chords = 0
104
105
106 spans = tagger.get_signs(offset=0)
107 added = len(spans)
108 offset = 1
109 while added > 0:
110 new_spans = tagger.get_signs(offset=offset)
111 added = len(new_spans)
112 spans.extend(new_spans)
113 offset += 1
114
115 word_signs = {}
116 for (start,end,sign) in spans:
117 if end == start + 1:
118 word_signs.setdefault(start, []).append(sign)
119
120 for i in range(tagger.input_length):
121
122 if correct_tags[i] not in ["", "?"]:
123 tags = word_signs.get(i, [])
124 if len(tags) == 0:
125
126 assigned_tag = None
127 else:
128 tags = [schema for __,(root,schema),__ in tags]
129 assigned_tag = tags[0]
130
131 tags = tags[:topn]
132 if correct_tags[i] in tags:
133
134 total_agreeing += 1
135
136
137 if confusion_matrix is not None and correct_tags[i] != assigned_tag:
138 conf = confusion_matrix.setdefault(correct_tags[i], {})
139 conf.setdefault(assigned_tag, 0)
140
141 conf[assigned_tag] += 1
142 total_chords += 1
143 return (total_agreeing, total_chords)
144