frprep 0.0.1.prealpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +270 -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
|