shalmaneser 0.0.1.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +8 -0
- data/CHANGELOG.rdoc +0 -0
- data/LICENSE.rdoc +0 -0
- data/README.rdoc +0 -0
- data/lib/common/AbstractSynInterface.rb +1227 -0
- data/lib/common/BerkeleyInterface.rb +375 -0
- data/lib/common/CollinsInterface.rb +1165 -0
- data/lib/common/ConfigData.rb +694 -0
- data/lib/common/Counter.rb +18 -0
- data/lib/common/DBInterface.rb +48 -0
- data/lib/common/EnduserMode.rb +27 -0
- data/lib/common/Eval.rb +480 -0
- data/lib/common/FixSynSemMapping.rb +196 -0
- data/lib/common/FrPrepConfigData.rb +66 -0
- data/lib/common/FrprepHelper.rb +1324 -0
- data/lib/common/Graph.rb +345 -0
- data/lib/common/ISO-8859-1.rb +24 -0
- data/lib/common/ML.rb +186 -0
- data/lib/common/Maxent.rb +215 -0
- data/lib/common/MiniparInterface.rb +1388 -0
- data/lib/common/Optimise.rb +195 -0
- data/lib/common/Parser.rb +213 -0
- data/lib/common/RegXML.rb +269 -0
- data/lib/common/RosyConventions.rb +171 -0
- data/lib/common/SQLQuery.rb +243 -0
- data/lib/common/STXmlTerminalOrder.rb +194 -0
- data/lib/common/SalsaTigerRegXML.rb +2347 -0
- data/lib/common/SalsaTigerXMLHelper.rb +99 -0
- data/lib/common/SleepyInterface.rb +384 -0
- data/lib/common/SynInterfaces.rb +275 -0
- data/lib/common/TabFormat.rb +720 -0
- data/lib/common/Tiger.rb +1448 -0
- data/lib/common/TntInterface.rb +44 -0
- data/lib/common/Tree.rb +61 -0
- data/lib/common/TreetaggerInterface.rb +303 -0
- data/lib/common/headz.rb +338 -0
- data/lib/common/option_parser.rb +13 -0
- data/lib/common/ruby_class_extensions.rb +310 -0
- data/lib/fred/Baseline.rb +150 -0
- data/lib/fred/FileZipped.rb +31 -0
- data/lib/fred/FredBOWContext.rb +863 -0
- data/lib/fred/FredConfigData.rb +182 -0
- data/lib/fred/FredConventions.rb +232 -0
- data/lib/fred/FredDetermineTargets.rb +324 -0
- data/lib/fred/FredEval.rb +312 -0
- data/lib/fred/FredFeatureExtractors.rb +321 -0
- data/lib/fred/FredFeatures.rb +1061 -0
- data/lib/fred/FredFeaturize.rb +596 -0
- data/lib/fred/FredNumTrainingSenses.rb +27 -0
- data/lib/fred/FredParameters.rb +402 -0
- data/lib/fred/FredSplit.rb +84 -0
- data/lib/fred/FredSplitPkg.rb +180 -0
- data/lib/fred/FredTest.rb +607 -0
- data/lib/fred/FredTrain.rb +144 -0
- data/lib/fred/PlotAndREval.rb +480 -0
- data/lib/fred/fred.rb +45 -0
- data/lib/fred/md5.rb +23 -0
- data/lib/fred/opt_parser.rb +250 -0
- data/lib/frprep/AbstractSynInterface.rb +1227 -0
- data/lib/frprep/Ampersand.rb +37 -0
- data/lib/frprep/BerkeleyInterface.rb +375 -0
- data/lib/frprep/CollinsInterface.rb +1165 -0
- data/lib/frprep/ConfigData.rb +694 -0
- data/lib/frprep/Counter.rb +18 -0
- data/lib/frprep/FNCorpusXML.rb +643 -0
- data/lib/frprep/FNDatabase.rb +144 -0
- data/lib/frprep/FixSynSemMapping.rb +196 -0
- data/lib/frprep/FrPrepConfigData.rb +66 -0
- data/lib/frprep/FrameXML.rb +513 -0
- data/lib/frprep/FrprepHelper.rb +1324 -0
- data/lib/frprep/Graph.rb +345 -0
- data/lib/frprep/ISO-8859-1.rb +24 -0
- data/lib/frprep/MiniparInterface.rb +1388 -0
- data/lib/frprep/Parser.rb +213 -0
- data/lib/frprep/RegXML.rb +269 -0
- data/lib/frprep/STXmlTerminalOrder.rb +194 -0
- data/lib/frprep/SalsaTigerRegXML.rb +2347 -0
- data/lib/frprep/SalsaTigerXMLHelper.rb +99 -0
- data/lib/frprep/SleepyInterface.rb +384 -0
- data/lib/frprep/SynInterfaces.rb +275 -0
- data/lib/frprep/TabFormat.rb +720 -0
- data/lib/frprep/Tiger.rb +1448 -0
- data/lib/frprep/TntInterface.rb +44 -0
- data/lib/frprep/Tree.rb +61 -0
- data/lib/frprep/TreetaggerInterface.rb +303 -0
- data/lib/frprep/do_parses.rb +142 -0
- data/lib/frprep/frprep.rb +686 -0
- data/lib/frprep/headz.rb +338 -0
- data/lib/frprep/one_parsed_file.rb +28 -0
- data/lib/frprep/opt_parser.rb +94 -0
- data/lib/frprep/ruby_class_extensions.rb +310 -0
- data/lib/rosy/AbstractFeatureAndExternal.rb +240 -0
- data/lib/rosy/DBMySQL.rb +146 -0
- data/lib/rosy/DBSQLite.rb +280 -0
- data/lib/rosy/DBTable.rb +239 -0
- data/lib/rosy/DBWrapper.rb +176 -0
- data/lib/rosy/ExternalConfigData.rb +58 -0
- data/lib/rosy/FailedParses.rb +130 -0
- data/lib/rosy/FeatureInfo.rb +242 -0
- data/lib/rosy/GfInduce.rb +1115 -0
- data/lib/rosy/GfInduceFeature.rb +148 -0
- data/lib/rosy/InputData.rb +294 -0
- data/lib/rosy/RosyConfigData.rb +115 -0
- data/lib/rosy/RosyConfusability.rb +338 -0
- data/lib/rosy/RosyEval.rb +465 -0
- data/lib/rosy/RosyFeatureExtractors.rb +1609 -0
- data/lib/rosy/RosyFeaturize.rb +280 -0
- data/lib/rosy/RosyInspect.rb +336 -0
- data/lib/rosy/RosyIterator.rb +477 -0
- data/lib/rosy/RosyPhase2FeatureExtractors.rb +230 -0
- data/lib/rosy/RosyPruning.rb +165 -0
- data/lib/rosy/RosyServices.rb +744 -0
- data/lib/rosy/RosySplit.rb +232 -0
- data/lib/rosy/RosyTask.rb +19 -0
- data/lib/rosy/RosyTest.rb +826 -0
- data/lib/rosy/RosyTrain.rb +232 -0
- data/lib/rosy/RosyTrainingTestTable.rb +786 -0
- data/lib/rosy/TargetsMostFrequentFrame.rb +60 -0
- data/lib/rosy/View.rb +418 -0
- data/lib/rosy/opt_parser.rb +379 -0
- data/lib/rosy/rosy.rb +77 -0
- data/lib/shalmaneser/version.rb +3 -0
- data/test/frprep/test_opt_parser.rb +94 -0
- data/test/functional/functional_test_helper.rb +40 -0
- data/test/functional/sample_experiment_files/fred_test.salsa.erb +122 -0
- data/test/functional/sample_experiment_files/fred_train.salsa.erb +135 -0
- data/test/functional/sample_experiment_files/prp_test.salsa.erb +138 -0
- data/test/functional/sample_experiment_files/prp_test.salsa.fred.standalone.erb +120 -0
- data/test/functional/sample_experiment_files/prp_test.salsa.rosy.standalone.erb +120 -0
- data/test/functional/sample_experiment_files/prp_train.salsa.erb +138 -0
- data/test/functional/sample_experiment_files/prp_train.salsa.fred.standalone.erb +138 -0
- data/test/functional/sample_experiment_files/prp_train.salsa.rosy.standalone.erb +138 -0
- data/test/functional/sample_experiment_files/rosy_test.salsa.erb +257 -0
- data/test/functional/sample_experiment_files/rosy_train.salsa.erb +259 -0
- data/test/functional/test_fred.rb +47 -0
- data/test/functional/test_frprep.rb +52 -0
- data/test/functional/test_rosy.rb +20 -0
- metadata +284 -0
@@ -0,0 +1,338 @@
|
|
1
|
+
# RosyConfusability
|
2
|
+
# KE May 05
|
3
|
+
#
|
4
|
+
# Access instance database created by the Rosy role assignment system
|
5
|
+
# and compute the confusability of target categories
|
6
|
+
# for the data in the (training) database there.
|
7
|
+
#
|
8
|
+
# We define confusability as follows:
|
9
|
+
# Given a frame fr, let
|
10
|
+
# - fes(fr) the FEs of fr (a set)
|
11
|
+
# - gfs(fe) the grammatical functions realizing the FE fe in the data
|
12
|
+
# - gfs(fr) = U_{fe \in fes(fr)} gfs(fe) the grammatical functions realizing roles of fr
|
13
|
+
#
|
14
|
+
# Then the entropy of a grammatical function gf within fr is
|
15
|
+
#
|
16
|
+
# gfe_{fr}(gf) = \sum_{fe \in fes(fr)} -p(fe|gf) log p(fe|gf)
|
17
|
+
#
|
18
|
+
# where p(fe|gf) = f(gf, fe) / f(gf)
|
19
|
+
#
|
20
|
+
# And the confusability of a frame element fe of fr is
|
21
|
+
#
|
22
|
+
# c_{fr}(fe) = \sum_{gf \in gfs(fr)} p(gf|fe) gfe_{fr}(gf)
|
23
|
+
#
|
24
|
+
# where p(gf|fe) = f(gf, fe) / f(fe)
|
25
|
+
|
26
|
+
require "RosyConfigData"
|
27
|
+
require "RosyIterator"
|
28
|
+
require "RosyConventions"
|
29
|
+
require "TargetsMostFrequentFrame"
|
30
|
+
|
31
|
+
require "mysql"
|
32
|
+
|
33
|
+
class RosyConfusability
|
34
|
+
include TargetsMostFrequentSc
|
35
|
+
|
36
|
+
attr_reader :confusability, :counts_fe_glob, :frame_confusability, :overall_confusability
|
37
|
+
|
38
|
+
def initialize(exp) # RosyConfigData object
|
39
|
+
@exp = exp
|
40
|
+
|
41
|
+
@confusability = Hash.new(0.0)
|
42
|
+
@counts_fe_glob = Hash.new(0)
|
43
|
+
@counts_gffe_glob = Hash.new(0)
|
44
|
+
@frame_confusability = Hash.new(0.0)
|
45
|
+
@overall_confusability = 0.0
|
46
|
+
|
47
|
+
@frequent_gframes = [
|
48
|
+
# NO DUPLICATES
|
49
|
+
"Ext_Comp", "Mod", "Comp", "Gen",
|
50
|
+
"Ext_Obj", "Ext", "Ext_Obj_Comp", "Head",
|
51
|
+
"Ext_Mod", "Gen_Mod", "Mod_Comp", "Comp_Ext",
|
52
|
+
"Gen_Comp", "Ext_Gen", "Ext_Mod_Comp", "Head_Comp",
|
53
|
+
"Obj_Comp", "Obj", "Mod_Head", "Ext_Comp_Obj",
|
54
|
+
"Gen_Head", "Ext_Gen_Mod"
|
55
|
+
# with duplicates
|
56
|
+
# "Ext_Comp", "Mod", "Comp", "Gen",
|
57
|
+
# "Ext_Obj", "Ext", "", "Ext_Obj_Comp",
|
58
|
+
# "Ext_Comp_Comp", "Head", "Mod_Mod", "Gen_Mod",
|
59
|
+
# "Ext_Mod", "Comp_Comp", "Mod_Comp", "Ext_Gen",
|
60
|
+
# "Gen_Comp", "Head_Head", "Ext_Comp_Comp_Comp", "Head_Comp",
|
61
|
+
# # "Ext_Ext_Comp",
|
62
|
+
# # "Ext_Obj_Comp_Comp", "Obj_Comp",
|
63
|
+
# # "Ext_Mod_Mod", "Comp_Comp_Comp",
|
64
|
+
# # "Ext_Ext_Obj", "Ext_Mod_Comp", "Comp_Ext", "Obj",
|
65
|
+
# # "Ext_Ext", "Ext_Obj_Obj", "Mod_Mod_Mod", "Gen_Mod_Mod",
|
66
|
+
# # "Ext_Comp_Comp_Comp_Comp", "Gen_Head", "Mod_Head",
|
67
|
+
# # "Ext_Ext_Ext_Comp"
|
68
|
+
].map { |string|
|
69
|
+
string.split("_")
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def compute(splitID, # string: split ID, may be nil
|
74
|
+
additionals) # array:string: "target", "target_pos", "gframe", "fgframe"
|
75
|
+
###
|
76
|
+
# open and initialize stuff:
|
77
|
+
|
78
|
+
# open database
|
79
|
+
database = Mysql.real_connect(@exp.get('host'), @exp.get('user'),
|
80
|
+
@exp.get('passwd'), @exp.get('dbname'))
|
81
|
+
# make an object that creates views.
|
82
|
+
# read one frame at a time.
|
83
|
+
iterator = RosyIterator.new(database, @exp, "train",
|
84
|
+
"splitID" => splitID,
|
85
|
+
"xwise" => "frame")
|
86
|
+
# get value for "no val"
|
87
|
+
noval = @exp.get("noval")
|
88
|
+
|
89
|
+
counts_frame = Hash.new(0)
|
90
|
+
|
91
|
+
# iterate through all frames and compute confusability of each FE
|
92
|
+
iterator.each_group { |group_descr_hash, frame|
|
93
|
+
|
94
|
+
$stderr.puts "Computing confusability for #{frame}"
|
95
|
+
|
96
|
+
# read all instances of the frame, columns: FE and GF
|
97
|
+
view = iterator.get_a_view_for_current_group(["sentid","gold", "fn_gf",
|
98
|
+
"target","target_pos", "frame"])
|
99
|
+
|
100
|
+
if additionals.include? "tmfframe"
|
101
|
+
# find most frequent gframe for each target
|
102
|
+
tmfframe = determine_target_most_frequent_sc(view, noval)
|
103
|
+
end
|
104
|
+
|
105
|
+
# count occurrences
|
106
|
+
counts_gf = Hash.new(0)
|
107
|
+
counts_fe = Hash.new(0)
|
108
|
+
counts_gffe = Hash.new(0)
|
109
|
+
|
110
|
+
view.each_sentence { |sentence|
|
111
|
+
|
112
|
+
# make string consisting of all FN GFs of this sentence
|
113
|
+
allgfs = Array.new()
|
114
|
+
sentence.each { |inst|
|
115
|
+
if inst["fn_gf"] != noval
|
116
|
+
allgfs << inst["fn_gf"]
|
117
|
+
end
|
118
|
+
}
|
119
|
+
|
120
|
+
# assume uniqueness of GFs
|
121
|
+
# design decision, could also be done differently.
|
122
|
+
# rationale: if a GF occurs more than once,
|
123
|
+
# it's probable that this is because we get more than
|
124
|
+
# one constituent for this GF, not because
|
125
|
+
# it actually occurred more than once in the
|
126
|
+
# original FrameNet annotation.
|
127
|
+
allgfs.uniq!
|
128
|
+
|
129
|
+
# now count each instance
|
130
|
+
sentence.each { |row|
|
131
|
+
if row["gold"] == "target"
|
132
|
+
# don't count target among the FEs
|
133
|
+
next
|
134
|
+
end
|
135
|
+
|
136
|
+
if row["gold"] != noval
|
137
|
+
counts_fe[row["gold"]] += 1
|
138
|
+
end
|
139
|
+
if row["fn_gf"] != noval and row["fn_gf"] != "target"
|
140
|
+
gf = row["fn_gf"]
|
141
|
+
|
142
|
+
additionals.each { |additional|
|
143
|
+
case additional
|
144
|
+
when "target"
|
145
|
+
gf << "_" + row["target"]
|
146
|
+
when "target_pos"
|
147
|
+
gf << "_" + row["target_pos"]
|
148
|
+
when "gframe"
|
149
|
+
gf << "_" + allgfs.join("_")
|
150
|
+
|
151
|
+
when "fgframe"
|
152
|
+
# find the maximal frequent frame subsuming allgfs
|
153
|
+
maxfgf = nil
|
154
|
+
@frequent_gframes.each { |fgframe|
|
155
|
+
if fgframe.subsumed_by?(allgfs)
|
156
|
+
# fgframe is a subset of allgfs
|
157
|
+
if maxfgf.nil? or fgframe.length() > maxfgf.length()
|
158
|
+
maxfgf = fgframe
|
159
|
+
end
|
160
|
+
end
|
161
|
+
}
|
162
|
+
if maxfgf.nil?
|
163
|
+
# nothing there that fits
|
164
|
+
# leave GF as is
|
165
|
+
else
|
166
|
+
gf << "_" + maxfgf.join("_")
|
167
|
+
end
|
168
|
+
|
169
|
+
when "tmfframe"
|
170
|
+
gf << "_" + tmfframe[tmf_target_key(row)]
|
171
|
+
|
172
|
+
else
|
173
|
+
raise "Don't know how to compute #{additional}"
|
174
|
+
end
|
175
|
+
}
|
176
|
+
|
177
|
+
counts_gf[gf] += 1
|
178
|
+
end
|
179
|
+
|
180
|
+
if row["gold"] != noval and gf
|
181
|
+
counts_gffe[gf + " " + row["gold"]] += 1
|
182
|
+
end
|
183
|
+
} # each row of sentence
|
184
|
+
} # each sentence of view
|
185
|
+
|
186
|
+
# compute gf entropy
|
187
|
+
# gfe_{fr}(gf) = \sum_{fe \in fes(fr)} -p(fe|gf) log_2 p(fe|gf)
|
188
|
+
#
|
189
|
+
# where p(fe|gf) = f(gf, fe) / f(gf)
|
190
|
+
gf_entropy = Hash.new
|
191
|
+
|
192
|
+
counts_gf.keys.each { |gf|
|
193
|
+
gf_entropy[gf] = 0.0
|
194
|
+
|
195
|
+
counts_fe.keys.each { |fe|
|
196
|
+
if counts_gf[gf] > 0
|
197
|
+
p_gf_fe = counts_gffe[gf + " " + fe].to_f / counts_gf[gf].to_f
|
198
|
+
|
199
|
+
# get log_2 via log_10
|
200
|
+
if p_gf_fe > 0.0
|
201
|
+
gf_entropy[gf] -= p_gf_fe * Math.log10(p_gf_fe) * 3.32193
|
202
|
+
end
|
203
|
+
end
|
204
|
+
} # each FE for this GF
|
205
|
+
} # each GF (gf entropy)
|
206
|
+
|
207
|
+
# compute FE confusability
|
208
|
+
# c_{fr}(fe) = \sum_{gf \in gfs(fr)} p(gf|fe) gfe_{fr}(gf)
|
209
|
+
#
|
210
|
+
# where p(gf|fe) = f(gf, fe) / f(fe)
|
211
|
+
counts_fe.keys.each { |fe|
|
212
|
+
@confusability[frame + " " + fe] = 0.0
|
213
|
+
|
214
|
+
counts_gf.keys.each { |gf|
|
215
|
+
if counts_fe[fe] > 0
|
216
|
+
p_fe_gf = counts_gffe[gf + " " + fe].to_f / counts_fe[fe].to_f
|
217
|
+
|
218
|
+
@confusability[frame + " " + fe] += p_fe_gf * gf_entropy[gf]
|
219
|
+
end
|
220
|
+
} # each GF for this FE
|
221
|
+
} # each FE (fe confusability)
|
222
|
+
|
223
|
+
|
224
|
+
# remember counts for FEs and GF/FE pairs
|
225
|
+
counts_fe.keys.each { |fe|
|
226
|
+
@counts_fe_glob[frame + " " + fe] = counts_fe[fe]
|
227
|
+
}
|
228
|
+
counts_gffe.each_pair {|event,freq|
|
229
|
+
@counts_gffe_glob[frame+" " +event] = freq
|
230
|
+
}
|
231
|
+
|
232
|
+
# omit rare FEs:
|
233
|
+
# anything below 5 occurrences
|
234
|
+
counts_fe.each_key { |fe|
|
235
|
+
if counts_fe[fe] < 5
|
236
|
+
@confusability.delete(frame + " " + fe)
|
237
|
+
end
|
238
|
+
}
|
239
|
+
|
240
|
+
# compute overall frame confusability
|
241
|
+
# omitting rare FEs with below 5 occurrences:
|
242
|
+
#
|
243
|
+
# c(fr) = sum_{fe \in fes(fr)} f(fe)/f(fr) * c_{fr}(fe)
|
244
|
+
# = \sum_{gf \in gfs(fr)} p(gf|fr) gfe_{fr}(gf)
|
245
|
+
#
|
246
|
+
# where p(gf|fr) = (sum_{fe\in fes(fr)} f(gf, fe)) / f(fr)
|
247
|
+
counts_frame[frame] = 0
|
248
|
+
counts_fe.each_value { |count|
|
249
|
+
if count >= 5
|
250
|
+
counts_frame[frame] += count
|
251
|
+
end
|
252
|
+
}
|
253
|
+
@frame_confusability[frame] = 0.0
|
254
|
+
counts_fe.each_pair { |fe, count|
|
255
|
+
if count >= 5
|
256
|
+
@frame_confusability[frame] += (count.to_f / counts_frame[frame].to_f) * @confusability[frame + " " + fe]
|
257
|
+
end
|
258
|
+
}
|
259
|
+
} # each frame
|
260
|
+
|
261
|
+
# compute overall confusability
|
262
|
+
# c = \sum{fr \in frames} f(fr)/N * c(fr)
|
263
|
+
#
|
264
|
+
# where N is the number of FE occurrences overall
|
265
|
+
counts_overall = 0
|
266
|
+
counts_frame.each_value { |count|
|
267
|
+
counts_overall += count
|
268
|
+
}
|
269
|
+
@overall_confusability = 0.0
|
270
|
+
counts_frame.each_pair { |frame, count|
|
271
|
+
@overall_confusability += (count.to_f / counts_overall.to_f) * @frame_confusability[frame]
|
272
|
+
}
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
# return a copy of @counts_fe_glob, from which all fes with less than 5 occurrences are deleted
|
277
|
+
def get_global_counts
|
278
|
+
global_counts = @counts_fe_glob.clone
|
279
|
+
global_counts.delete_if {|key, value| value < 5}
|
280
|
+
return global_counts
|
281
|
+
end
|
282
|
+
|
283
|
+
###
|
284
|
+
#
|
285
|
+
# compute sparseness statistics over the set of
|
286
|
+
# base events used for computing the confusability
|
287
|
+
# returns an array of length 4:
|
288
|
+
# - number of events with freq 1
|
289
|
+
# - number of events with freq 2
|
290
|
+
# - number of events with freq 3-5
|
291
|
+
# - number of events with freq > 5
|
292
|
+
|
293
|
+
def counts()
|
294
|
+
counts = [0,0,0,0]
|
295
|
+
@counts_gffe_glob.each_value {|freq|
|
296
|
+
case freq
|
297
|
+
when 1
|
298
|
+
counts[0] += 1
|
299
|
+
when 2
|
300
|
+
counts[1] += 1
|
301
|
+
when 3..5
|
302
|
+
counts[2] += 1
|
303
|
+
else
|
304
|
+
counts[3] += 1
|
305
|
+
end
|
306
|
+
}
|
307
|
+
return counts
|
308
|
+
end
|
309
|
+
|
310
|
+
def to_file(filename)
|
311
|
+
begin
|
312
|
+
file = File.new(filename,"w")
|
313
|
+
rescue
|
314
|
+
raise "Couldn't open file #{filename} for writing."
|
315
|
+
end
|
316
|
+
Marshal.dump({"confusability" => @confusability,
|
317
|
+
"counts_fe_glob" => @counts_fe_glob,
|
318
|
+
"counts_gffe_glob" => @counts_gffe_glob,
|
319
|
+
"frame_confusability" => @frame_confusability,
|
320
|
+
"overall_confusability" => @overall_confusability
|
321
|
+
},
|
322
|
+
file)
|
323
|
+
end
|
324
|
+
|
325
|
+
def from_file(filename)
|
326
|
+
begin
|
327
|
+
file = File.new(filename)
|
328
|
+
rescue
|
329
|
+
raise "Couldn't open file #{filename} for reading."
|
330
|
+
end
|
331
|
+
hash = Marshal.load(file)
|
332
|
+
@confusability = hash["confusability"]
|
333
|
+
@counts_fe_glob = hash["counts_fe_glob"]
|
334
|
+
@counts_gffe_glob = hash["counts_gffe_glob"]
|
335
|
+
@frame_confusability = hash["frame_confusability"]
|
336
|
+
@overall_confusability = hash["overall_confusability"]
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,465 @@
|
|
1
|
+
# RosyEval
|
2
|
+
# KE May 05
|
3
|
+
#
|
4
|
+
# Evaluation for Rosy:
|
5
|
+
# Precision, Recall, F-score
|
6
|
+
# Output to evaluation file,
|
7
|
+
# plus optional output of evaluation log file.
|
8
|
+
#
|
9
|
+
# Builds on the general Salsa Eval package
|
10
|
+
|
11
|
+
# Salsa packages
|
12
|
+
require "common/Eval"
|
13
|
+
require "common/ruby_class_extensions"
|
14
|
+
|
15
|
+
# Rosy packages
|
16
|
+
require "rosy/RosyIterator"
|
17
|
+
require "rosy/RosySplit"
|
18
|
+
require "rosy/RosyTask"
|
19
|
+
require "rosy/RosyPruning"
|
20
|
+
|
21
|
+
# Frprep packages
|
22
|
+
require "common/FrPrepConfigData"
|
23
|
+
|
24
|
+
#######################################################################
|
25
|
+
# This class is a subclass of the general evaluation class
|
26
|
+
# Eval, which makes evaluation results readable via
|
27
|
+
# readable object variables
|
28
|
+
#
|
29
|
+
# step: can be argrec, arglab, onestep, as usual, but also
|
30
|
+
# - "all":
|
31
|
+
# evaluate argrec and arglab together.
|
32
|
+
# When argrec == NONE, use the argrec value, else use the arglab value
|
33
|
+
# - "prune":
|
34
|
+
# evaluate the pruning column as if it were an argrec assignment
|
35
|
+
#
|
36
|
+
# When step == argrec or prune, evaluate _only_ the target class FE
|
37
|
+
# Otherwise, evaluate all target classes
|
38
|
+
class RosyEval < Eval
|
39
|
+
def initialize(exp, # RosyConfigData object: experiment file
|
40
|
+
ttt_obj, # RosyTrainingTestTable object
|
41
|
+
step, # string: argrec, arglab, onestep, all, prune
|
42
|
+
splitID, # string: splitlog ID, or nil
|
43
|
+
testID, # string: test ID, or nil
|
44
|
+
outfilename, # string: name of file to print output to
|
45
|
+
logfilename, # string: name of file to print eval log to (may be nil)
|
46
|
+
dont_adjoin_frprep_exp) # string: if non-nil, don't re-adjoin frprep experiment obj
|
47
|
+
@exp = exp
|
48
|
+
@step = step
|
49
|
+
|
50
|
+
if outfilename
|
51
|
+
$stderr.puts "Rosy evaluation: printing results to " + outfilename
|
52
|
+
end
|
53
|
+
if logfilename
|
54
|
+
$stderr.puts "and printing an evaluation log to " + logfilename
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# add preprocessing information to the experiment file object
|
59
|
+
unless dont_adjoin_frprep_exp
|
60
|
+
if splitID
|
61
|
+
# use split data
|
62
|
+
preproc_expname = @exp.get("preproc_descr_file_train")
|
63
|
+
else
|
64
|
+
# use test data
|
65
|
+
preproc_expname = @exp.get("preproc_descr_file_test")
|
66
|
+
end
|
67
|
+
if not(preproc_expname)
|
68
|
+
$stderr.puts "Please set the name of the preprocessing exp. file name"
|
69
|
+
$stderr.puts "in the experiment file."
|
70
|
+
exit 1
|
71
|
+
elsif not(File.readable?(preproc_expname))
|
72
|
+
$stderr.puts "Error in the experiment file:"
|
73
|
+
$stderr.puts "Parameter preproc_descr_file_train has to be a readable file."
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
preproc_exp = FrPrepConfigData.new(preproc_expname)
|
77
|
+
@exp.adjoin(preproc_exp)
|
78
|
+
end
|
79
|
+
|
80
|
+
##
|
81
|
+
# evaluate which labels?
|
82
|
+
if ["argrec", "prune"].include? @step
|
83
|
+
# evaluate only the label "FE"
|
84
|
+
super(outfilename, logfilename, "FE")
|
85
|
+
else
|
86
|
+
# evaluate all target classes
|
87
|
+
super(outfilename, logfilename)
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# what are classifier columns?
|
92
|
+
case @step
|
93
|
+
when "all"
|
94
|
+
# read one argrec and one arglab classifier run column
|
95
|
+
@classif_column_argrec = ttt_obj.existing_runlog("argrec", "test", testID,splitID)
|
96
|
+
@classif_column_arglab = ttt_obj.existing_runlog("arglab", "test", testID,splitID)
|
97
|
+
@columns = ["gold", @classif_column_argrec, @classif_column_arglab]
|
98
|
+
|
99
|
+
if @classif_column_argrec.nil? or @classif_column_arglab.nil?
|
100
|
+
# no run found for the given specifications
|
101
|
+
$stderr.puts "Couldn't determine the run to evaluate."
|
102
|
+
$stderr.puts "There were either none or too many possible runs given your specification.\n"
|
103
|
+
$stderr.puts "Here is a list of all runs the system knows for this experiment ID:\n\n"
|
104
|
+
$stderr.puts ttt_obj.runlog_to_s("test", testID, splitID)
|
105
|
+
exit 1
|
106
|
+
end
|
107
|
+
|
108
|
+
when "prune"
|
109
|
+
# read pruning column, evaluate as a kind of argrec assignment
|
110
|
+
unless Pruning.prune?(@exp)
|
111
|
+
raise "Error: Pruning evaluation without pruning column. Skipping."
|
112
|
+
end
|
113
|
+
@classif_column = Pruning.colname(@exp)
|
114
|
+
@columns = ["gold", @classif_column]
|
115
|
+
|
116
|
+
else
|
117
|
+
# read the classifier run column for the current step
|
118
|
+
@classif_column = ttt_obj.existing_runlog(@step, "test", testID,splitID)
|
119
|
+
@columns = ["gold", @classif_column]
|
120
|
+
|
121
|
+
if @classif_column.nil?
|
122
|
+
# no run found for the given specifications
|
123
|
+
$stderr.puts "Couldn't determine the run to evaluate."
|
124
|
+
$stderr.puts "There were either none or too many possible runs given your specification.\n"
|
125
|
+
$stderr.puts "Here is a list of all runs the system knows for this experiment ID:\n\n"
|
126
|
+
$stderr.puts ttt_obj.runlog_to_s("test", testID, splitID)
|
127
|
+
exit 1
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
##
|
132
|
+
# make object for iterating through groups and making views
|
133
|
+
case @step
|
134
|
+
when "all"
|
135
|
+
# all: no step in particular
|
136
|
+
@iterator = RosyIterator.new(ttt_obj, exp, "test",
|
137
|
+
"step" => nil,
|
138
|
+
"testID" => testID,
|
139
|
+
"splitID" => splitID,
|
140
|
+
"xwise" => "frame")
|
141
|
+
when "prune"
|
142
|
+
# prune: use argrec
|
143
|
+
@iterator = RosyIterator.new(ttt_obj, exp, "test",
|
144
|
+
"step" => "argrec",
|
145
|
+
"testID" => testID,
|
146
|
+
"splitID" => splitID)
|
147
|
+
|
148
|
+
else
|
149
|
+
# use the given step
|
150
|
+
@iterator = RosyIterator.new(ttt_obj, exp, "test",
|
151
|
+
"step" => @step,
|
152
|
+
"testID" => testID,
|
153
|
+
"splitID" => splitID)
|
154
|
+
end
|
155
|
+
|
156
|
+
##
|
157
|
+
# xwise
|
158
|
+
if @step == "all"
|
159
|
+
# argrec and arglab may have different xwises,
|
160
|
+
# which would create trouble.
|
161
|
+
# just use "frame" instead
|
162
|
+
@xwise = ["frame"]
|
163
|
+
else
|
164
|
+
# evaluate as you have trained and tested
|
165
|
+
@xwise = @iterator.get_xwise_column_names()
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# split? then include FE labels from unparsed sentences
|
170
|
+
# in count of gold labels
|
171
|
+
if splitID
|
172
|
+
# get a FailedParses object for this split
|
173
|
+
@failed_parses_split = FailedParses.new()
|
174
|
+
fp_filename = File.new_filename(@exp.instantiate("rosy_dir",
|
175
|
+
"exp_ID" => @exp.get("experiment_ID")),
|
176
|
+
@exp.instantiate("failed_file",
|
177
|
+
"exp_ID" => @exp.get("experiment_ID"),
|
178
|
+
"split_ID" => splitID,
|
179
|
+
"dataset" => "test"))
|
180
|
+
@failed_parses_split.load(fp_filename)
|
181
|
+
end
|
182
|
+
|
183
|
+
# announce the task
|
184
|
+
$stderr.puts "---------"
|
185
|
+
$stderr.print "Rosy experiment #{@exp.get("experiment_ID")}: Evaluating "
|
186
|
+
if splitID
|
187
|
+
$stderr.puts "on split dataset #{splitID}"
|
188
|
+
else
|
189
|
+
$stderr.puts "on test dataset #{testID}"
|
190
|
+
end
|
191
|
+
$stderr.puts "---------"
|
192
|
+
end
|
193
|
+
|
194
|
+
###
|
195
|
+
protected
|
196
|
+
|
197
|
+
###
|
198
|
+
# each_group
|
199
|
+
#
|
200
|
+
# yield each group name in turn
|
201
|
+
def each_group()
|
202
|
+
|
203
|
+
@view = nil
|
204
|
+
|
205
|
+
# for the sake of the failed parses module:
|
206
|
+
# it can split the failed parses by frame, target and target_pos,
|
207
|
+
# but if our "xwise" splits the data along any further columns,
|
208
|
+
# the failed parses module cannot know how to split up its failed parses.
|
209
|
+
# so see whether we've got any column names besides the three named above
|
210
|
+
# in our xwise,
|
211
|
+
# and if so, count the groups and split the failed parses evenly between them
|
212
|
+
normal_xwise_cols = ["frame", "target", "target_pos"] & @xwise
|
213
|
+
extra_xwise_cols = @xwise - normal_xwise_cols
|
214
|
+
|
215
|
+
# num_groups_for_normalxwise: hash: normal_xwise_values(string) -> num. of
|
216
|
+
# groups with these normal xwise values(integer)
|
217
|
+
# where the key normal_xwise_values is a conjunction of
|
218
|
+
# strings <col_name>=<value> joined by commas,
|
219
|
+
# and the column names are in alphabetical order
|
220
|
+
num_groups_for_normalxwise = Hash.new(0)
|
221
|
+
|
222
|
+
unless extra_xwise_cols.empty?
|
223
|
+
# we do have extra columns
|
224
|
+
|
225
|
+
# for each value sequence for normal_xwise_cols: find out how many values
|
226
|
+
# of extra xwise col.s there are
|
227
|
+
@iterator.each_group() { |group_descr_hash, group_name|
|
228
|
+
|
229
|
+
# make the hash key
|
230
|
+
key = normal_xwise_cols.sort.map { |col_name|
|
231
|
+
col_name + "=" + group_descr_hash[col_name]
|
232
|
+
}.join(",")
|
233
|
+
|
234
|
+
# record one occurrence of this hash key
|
235
|
+
num_groups_for_normalxwise[key] += 1
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
@iterator.each_group() { |group_descr_hash, group_name|
|
240
|
+
|
241
|
+
if @exp.get("verbose")
|
242
|
+
$stderr.puts group_name
|
243
|
+
end
|
244
|
+
|
245
|
+
# construct view for the current group
|
246
|
+
@view = @iterator.get_a_view_for_current_group(@columns)
|
247
|
+
|
248
|
+
##
|
249
|
+
# get counts of FE labels from unparsed sentences:
|
250
|
+
|
251
|
+
# first take apart the group label to find
|
252
|
+
# the frame name, target name, target POS name in this group
|
253
|
+
# (all but one may be nil)
|
254
|
+
frame = target = target_pos = nil
|
255
|
+
|
256
|
+
# get a description of this group, array of pairs [column name, value]
|
257
|
+
# where column name is the name of one database column
|
258
|
+
@xwise.interleave(group_name.split()).each { |col_name, col_value|
|
259
|
+
case col_name
|
260
|
+
when "frame"
|
261
|
+
frame = col_value
|
262
|
+
when "target"
|
263
|
+
target = col_value
|
264
|
+
when "target_pos"
|
265
|
+
target_pos = col_value
|
266
|
+
else
|
267
|
+
# additional database columns: handled below
|
268
|
+
end
|
269
|
+
}
|
270
|
+
|
271
|
+
# do we have additional column names in "xwise", besides 'frame', 'target', 'target_pos'?
|
272
|
+
if extra_xwise_cols.empty?
|
273
|
+
split_between_groups = 1
|
274
|
+
else
|
275
|
+
key = normal_xwise_cols.sort.map { |col_name|
|
276
|
+
col_name + "=" + group_descr_hash[col_name]
|
277
|
+
}.join(",")
|
278
|
+
split_between_groups = num_groups_for_normalxwise[key]
|
279
|
+
|
280
|
+
# sanity check
|
281
|
+
if split_between_groups == 0
|
282
|
+
raise "shouldn't be here"
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
# failed_fes returns: hash that maps FE names [String] onto numbers of failed FEs [Int]
|
287
|
+
if @failed_parses_split
|
288
|
+
@failed_parses_split.failed_fes(frame, target, target_pos).each_pair { |fe, count|
|
289
|
+
# add this number of gold labels we failed to find
|
290
|
+
# to the number of gold labels that Eval counts
|
291
|
+
|
292
|
+
# if argrec, map all non-NONE FEs to "FE"
|
293
|
+
if @step == "argrec" and fe != @exp.get("noval")
|
294
|
+
fe = "FE"
|
295
|
+
end
|
296
|
+
inject_gold_counts(group_name, fe, (count.to_f / split_between_groups.to_f).round)
|
297
|
+
}
|
298
|
+
end
|
299
|
+
|
300
|
+
# yield the name of the group to the Eval object for evaluation
|
301
|
+
yield group_name
|
302
|
+
@view.close()
|
303
|
+
}
|
304
|
+
end
|
305
|
+
|
306
|
+
###
|
307
|
+
# each_instance
|
308
|
+
#
|
309
|
+
# given a group name, yield each instance of this group in turn,
|
310
|
+
# or rather: yield pairs [gold_class(string), assigned_class(string)]
|
311
|
+
#
|
312
|
+
# this method depends on each_group() having been called before and
|
313
|
+
# having initialized @view to the right view object
|
314
|
+
def each_instance(group) # string: group name
|
315
|
+
case @step
|
316
|
+
when "all"
|
317
|
+
# step "all":
|
318
|
+
# if the argrec label is "NONE", use that as the assigned label.
|
319
|
+
# else use the arglab-label
|
320
|
+
@view.each_hash { |row|
|
321
|
+
if row[@classif_column_argrec] == @exp.get("noval")
|
322
|
+
yield [ row["gold"], row[@classif_column_argrec] ]
|
323
|
+
else
|
324
|
+
yield [ row["gold"], row[@classif_column_arglab] ]
|
325
|
+
end
|
326
|
+
}
|
327
|
+
|
328
|
+
when "prune"
|
329
|
+
# step "prune":
|
330
|
+
# if the pruning column has entry 1, regard as assignment "FE",
|
331
|
+
# else regard as assignment "NONE".
|
332
|
+
@view.each_hash { |row|
|
333
|
+
if row[@classif_column] == "1"
|
334
|
+
yield [ row["gold"], "FE" ]
|
335
|
+
else
|
336
|
+
yield [ row["gold"], @exp.get("noval") ]
|
337
|
+
end
|
338
|
+
}
|
339
|
+
|
340
|
+
else
|
341
|
+
# argrec, arglab, onestep:
|
342
|
+
# just yield pairs [goldlabel, classif_column_label]
|
343
|
+
# as given in the view
|
344
|
+
|
345
|
+
@view.each_hash { |row|
|
346
|
+
yield [row["gold"], row[@classif_column]]
|
347
|
+
}
|
348
|
+
end
|
349
|
+
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
###########################################################33
|
354
|
+
# This is the class to be called from rosy.rb
|
355
|
+
###########################################################33
|
356
|
+
class RosyEvalTask < RosyTask
|
357
|
+
|
358
|
+
def initialize(exp, # RosyConfigData object: experiment description
|
359
|
+
opts, # hash: runtime argument option (string) -> value (string)
|
360
|
+
ttt_obj) # RosyTrainingTestTable object
|
361
|
+
|
362
|
+
#####
|
363
|
+
# In enduser mode, this whole task is unavailable
|
364
|
+
in_enduser_mode_unavailable()
|
365
|
+
|
366
|
+
@exp = exp
|
367
|
+
@ttt_obj = ttt_obj
|
368
|
+
|
369
|
+
##
|
370
|
+
# check runtime options
|
371
|
+
@step = "both"
|
372
|
+
@splitID = nil
|
373
|
+
@testID = default_test_ID()
|
374
|
+
|
375
|
+
opts.each do |opt,arg|
|
376
|
+
case opt
|
377
|
+
when "--step"
|
378
|
+
unless ["argrec", "arglab", "both", "onestep"].include? arg
|
379
|
+
raise "Classification step must be one of: argrec, arglab, both, onestep. I got: " + arg.to_s
|
380
|
+
end
|
381
|
+
@step = arg
|
382
|
+
when "--logID"
|
383
|
+
@splitID = arg
|
384
|
+
when "--testID"
|
385
|
+
@testID = arg
|
386
|
+
else
|
387
|
+
# this is an option that is okay but has already been read and used by rosy.rb
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def perform()
|
393
|
+
dont_adjoin_frprep_exp = nil
|
394
|
+
original_step = @step
|
395
|
+
|
396
|
+
if ["both", "argrec", "onestep"].include? original_step and
|
397
|
+
Pruning.prune?(@exp)
|
398
|
+
# evaluate pruning
|
399
|
+
$stderr.puts "Rosy evaluating pruning"
|
400
|
+
@step = "prune"
|
401
|
+
perform_aux()
|
402
|
+
dont_adjoin_frprep_exp = "dont_adjoin_frprep_exp"
|
403
|
+
end
|
404
|
+
|
405
|
+
if original_step == "both"
|
406
|
+
# both? then do first argrec, then arglab
|
407
|
+
$stderr.puts "Rosy evaluating step argrec"
|
408
|
+
@step = "argrec"
|
409
|
+
perform_aux(dont_adjoin_frprep_exp)
|
410
|
+
|
411
|
+
|
412
|
+
$stderr.puts "Rosy evaluating step arglab"
|
413
|
+
@step = "arglab"
|
414
|
+
perform_aux("dont_adjoin_frprep_exp")
|
415
|
+
|
416
|
+
# KE Jan 30, 2006: evaluation "all" deactivated until we've
|
417
|
+
# figured out how to evaluate accuracy for the NONE class
|
418
|
+
# $stderr.puts "Rosy overall evaluation"
|
419
|
+
# @step = "all"
|
420
|
+
# perform_aux("dont_adjoin_frprep_exp")
|
421
|
+
|
422
|
+
else
|
423
|
+
# not both? then just do one
|
424
|
+
@step = original_step
|
425
|
+
perform_aux(dont_adjoin_frprep_exp)
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
###############3
|
430
|
+
private
|
431
|
+
|
432
|
+
# perform_aux: do the actual work of the perform() method
|
433
|
+
# moved here because of the possibility of having @step=="both",
|
434
|
+
# which makes it necessary to perform two eval steps one after the other
|
435
|
+
def perform_aux(dont_adjoin_frprep_exp = nil) # string passed on to RosyEval initialize method
|
436
|
+
# construct names for evaluation output file
|
437
|
+
# and evaluation log file (which classifies each instances as correct/incorrect/unassigned)
|
438
|
+
if @splitID
|
439
|
+
outfilename_id = "split" + @splitID
|
440
|
+
else
|
441
|
+
outfilename_id = "test" + @testID
|
442
|
+
end
|
443
|
+
@outfilename = File.new_filename(@exp.instantiate("rosy_dir",
|
444
|
+
"exp_ID" => @exp.get("experiment_ID")),
|
445
|
+
@exp.instantiate("eval_file",
|
446
|
+
"exp_ID" => @exp.get("experiment_ID"),
|
447
|
+
"test_ID" => outfilename_id,
|
448
|
+
"step" => @step))
|
449
|
+
|
450
|
+
if @exp.get("print_eval_log")
|
451
|
+
@logfilename = File.new_filename(@exp.instantiate("rosy_dir",
|
452
|
+
"exp_ID" => @exp.get("experiment_ID")),
|
453
|
+
@exp.instantiate("log_file",
|
454
|
+
"exp_ID" => @exp.get("experiment_ID"),
|
455
|
+
"test_ID" => outfilename_id,
|
456
|
+
"step" => @step))
|
457
|
+
else
|
458
|
+
@logfilename = nil
|
459
|
+
end
|
460
|
+
@eval_obj = RosyEval.new(@exp, @ttt_obj, @step, @splitID, @testID,
|
461
|
+
@outfilename, @logfilename,
|
462
|
+
dont_adjoin_frprep_exp)
|
463
|
+
@eval_obj.compute()
|
464
|
+
end
|
465
|
+
end
|