Skip to content

Commit 39bce12

Browse files
committed
Implement the evaluation scripts
1 parent 2c051a4 commit 39bce12

File tree

6 files changed

+299
-0
lines changed

6 files changed

+299
-0
lines changed

scripts/eval/constraintgrammar_fit.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
import argparse
6+
import tempfile
7+
import subprocess
8+
from pathlib import Path
9+
10+
if __name__ == '__main__':
11+
parser = argparse.ArgumentParser(description='fit n models using n folds')
12+
parser.add_argument('-i', '--input_directory', required=True,
13+
help='input directory of the n folds')
14+
parser.add_argument('-b', '--apertium_bin', required=True,
15+
help='a compiled dictionary')
16+
parser.add_argument('-corpus', required=True,
17+
help='an untagged corpus')
18+
parser.add_argument('-cg', '--constraint_grammar', required=True,
19+
help='a compiled constraint grammar')
20+
parser.add_argument('-o', '--output_directory', required=True,
21+
help='output directory for weighted dictionaries')
22+
args = parser.parse_args()
23+
input_directory = args.input_directory
24+
output_directory = args.output_directory
25+
apertium_bin = args.apertium_bin
26+
corpus = args.corpus
27+
constraint_grammar = args.constraint_grammar
28+
if not os.path.exists(output_directory):
29+
os.mkdir(output_directory)
30+
31+
temp_dir = tempfile.mkdtemp()
32+
33+
temp_weightlist = Path(temp_dir, 'temp_weightlist')
34+
subprocess.run(['./unannotated-corpus-to-weightlist',
35+
apertium_bin, corpus, constraint_grammar, temp_weightlist])
36+
37+
for input_file in sorted(os.listdir(input_directory)):
38+
# Generate a bin file
39+
subprocess.run(['./lt-weight',
40+
apertium_bin,
41+
Path(output_directory, '{}.bin'.format(input_file)),
42+
temp_weightlist])

scripts/eval/corpus_split.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
import random
6+
import argparse
7+
from pathlib import Path
8+
9+
def write_to_file(lines_list, file_name):
10+
with open(file_name, 'w') as f:
11+
f.write('\n'.join(lines_list))
12+
13+
if __name__ == '__main__':
14+
parser = argparse.ArgumentParser(description='split a tagged corpus into n folds')
15+
parser.add_argument('input_tagged_corpus',
16+
type=argparse.FileType('r'),
17+
help='a tagged corpus')
18+
parser.add_argument('-n', '--no_of_folds', type=int, default=5,
19+
help='number of folds')
20+
parser.add_argument('-s', '--seed', type=int, default=42,
21+
help='number of folds')
22+
parser.add_argument('-o', '--output_directory',required=True,
23+
help='output directory')
24+
args = parser.parse_args()
25+
cv = args.no_of_folds
26+
output_directory = args.output_directory
27+
input_tagged_corpus = args.input_tagged_corpus
28+
random.seed(args.seed)
29+
30+
if not os.path.exists(output_directory):
31+
os.mkdir(output_directory)
32+
33+
splitted_corpus = [[] for _ in range(cv)]
34+
35+
# For each line, generate a random index
36+
for line_number, line in enumerate(input_tagged_corpus):
37+
splitted_corpus[random.randint(0, cv - 1)].append(line.strip())
38+
39+
# Extract the base file name of the corpus
40+
corpus_file_name = Path(input_tagged_corpus.name).name
41+
42+
for cv_index in range(cv):
43+
write_to_file(splitted_corpus[cv_index],
44+
str(Path(output_directory, '{}_{}'.format(corpus_file_name, cv_index))))

scripts/eval/equalweight_fit.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
import argparse
6+
import tempfile
7+
import subprocess
8+
from pathlib import Path
9+
10+
if __name__ == '__main__':
11+
parser = argparse.ArgumentParser(description='fit n models using n folds')
12+
parser.add_argument('-i', '--input_directory', required=True,
13+
help='input directory of the n folds')
14+
parser.add_argument('-b', '--apertium_bin', required=True,
15+
help='a compiled dictionary')
16+
parser.add_argument('-o', '--output_directory', required=True,
17+
help='output directory for weighted dictionaries')
18+
args = parser.parse_args()
19+
input_directory = args.input_directory
20+
output_directory = args.output_directory
21+
apertium_bin = args.apertium_bin
22+
if not os.path.exists(output_directory):
23+
os.mkdir(output_directory)
24+
25+
temp_dir = tempfile.mkdtemp()
26+
27+
temp_weightlist = Path(temp_dir, 'temp_weightlist')
28+
for input_file in sorted(os.listdir(input_directory)):
29+
subprocess.run(['./equal-weightlist', temp_weightlist])
30+
# Generate a bin file
31+
subprocess.run(['./lt-weight',
32+
apertium_bin,
33+
Path(output_directory, '{}.bin'.format(input_file)),
34+
temp_weightlist])

scripts/eval/eval_utils.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python3
2+
3+
import re
4+
import subprocess
5+
from pathlib import Path
6+
7+
def get_apertium_analyses(X, weighted_bin, base_dir, only_one_analysis=True):
8+
#TODO: WHY DOES ADDING A DOT WORK AS A SEPARATOR?
9+
joined_X = ' .\n'.join([x for x in X + ['\n']])
10+
11+
base_input_as_file = str(Path(base_dir, 'base_input_as_file'))
12+
with open(base_input_as_file, 'w') as f:
13+
f.write(joined_X)
14+
15+
deformatted_input = str(Path(base_dir, 'formatted_input'))
16+
assert(subprocess.run(['apertium-destxt', '-n', base_input_as_file, deformatted_input]).returncode == 0)
17+
18+
analysed_output = str(Path(base_dir, 'analysis_output'))
19+
reformatted_output = str(Path(base_dir, 'reformatted_output'))
20+
# TODO: Is cleaning the reformatted output file needed?
21+
22+
processing_command = ['lt-proc', weighted_bin, deformatted_input, analysed_output]
23+
if only_one_analysis:
24+
processing_command.append('-N 1')
25+
26+
subprocess.run(processing_command)
27+
subprocess.run(['apertium-retxt', analysed_output, reformatted_output])
28+
29+
with open(reformatted_output, 'r') as f:
30+
analyses = [a.strip() for a in f.readlines() if a.strip()]
31+
if only_one_analysis:
32+
return [analysis[analysis.find('/') + 1: analysis.find('$')]
33+
for analysis in analyses]
34+
else:
35+
return [analysis.strip('$').split('/')[1:]
36+
for analysis in analyses]
37+
38+
def split_X_y(file_lines):
39+
'^With/with<pr>$'
40+
splitted_lines = [line.strip()[1:-1].split('/') for line in file_lines if line.strip()]
41+
42+
tokens = [l[0] for l in splitted_lines]
43+
targets = [l[1] for l in splitted_lines]
44+
45+
assert(len(tokens)==len(targets)), 'Token and Target vectors size mismatch ({}!={})'.format(len(tokens), len(targets))
46+
47+
return tokens, targets

scripts/eval/metrics_report.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import argparse
5+
import tempfile
6+
import tabulate
7+
import statistics
8+
from pathlib import Path
9+
from eval_utils import get_apertium_analyses, split_X_y
10+
11+
def get_sorted_files_in_directory(dir):
12+
return sorted(os.listdir(dir))
13+
14+
def compute_weighted_precision(tag, metrics_dict, corpus_size):
15+
den = (metrics_dict[tag]['TP'] + metrics_dict[tag]['FP'])
16+
if not den:
17+
return 0
18+
19+
return ((metrics_dict[tag]['support'] / corpus_size) *
20+
(metrics_dict[tag]['TP'] / den))
21+
22+
def compute_weighted_recall(tag, metrics_dict, corpus_size):
23+
den = (metrics_dict[tag]['TP'] + metrics_dict[tag]['FN'])
24+
if not den:
25+
return 0
26+
27+
return ((metrics_dict[tag]['support'] / corpus_size) *
28+
(metrics_dict[tag]['TP'] / den))
29+
30+
if __name__ == '__main__':
31+
parser = argparse.ArgumentParser(description='compute metrics for a compiled dictionary')
32+
parser.add_argument('-i', '--input_directory', required=True,
33+
help='input directory of n folds')
34+
parser.add_argument('-b', '--apertium_bins', required=True,
35+
help='directory of compiled dictionary')
36+
args = parser.parse_args()
37+
38+
base_dir = tempfile.mkdtemp()
39+
40+
precision = []
41+
recall = []
42+
for testing_corpus, bin_file in zip(
43+
get_sorted_files_in_directory(args.input_directory),
44+
get_sorted_files_in_directory(args.apertium_bins)):
45+
test_corpus = str(Path(args.input_directory, testing_corpus))
46+
47+
with open(test_corpus, 'r') as f:
48+
X, y = split_X_y(f.readlines())
49+
pred = get_apertium_analyses(X, Path(args.apertium_bins, bin_file), base_dir)
50+
assert(len(y) == len(pred)), 'Target and Predicted vectors size mismatch ({}!={})'.format(len(y), len(pred))
51+
metrics_dict = {}
52+
53+
metrics_vars = ['TP', 'FP', 'FN', 'support']
54+
for target, prediction in zip(y, pred):
55+
# IGNORE MISSING TARGET TAGS?
56+
if target.startswith('*'):
57+
continue
58+
# IGNORE MISSING PREDICTION TAGS?
59+
if prediction.startswith('*'):
60+
continue
61+
if not target in metrics_dict:
62+
metrics_dict[target] = {var:0 for var in metrics_vars}
63+
metrics_dict[target]['support'] += 1
64+
metrics_dict[target]['TP'] += target == prediction
65+
metrics_dict[target]['FN'] += target != prediction
66+
if not prediction in metrics_dict:
67+
metrics_dict[prediction] = {var:0 for var in metrics_vars}
68+
metrics_dict[prediction]['FP'] += target != prediction
69+
70+
average_precision = 0
71+
average_recall = 0
72+
for tag in metrics_dict:
73+
prec = compute_weighted_precision(tag, metrics_dict, len(X))
74+
if prec:
75+
average_precision += prec
76+
rec = compute_weighted_recall(tag, metrics_dict, len(X))
77+
if rec:
78+
average_recall += rec
79+
recall.append(average_recall)
80+
precision.append(average_precision)
81+
82+
metrics_dict = {'testing_corpus': get_sorted_files_in_directory(args.input_directory),
83+
'precision': precision,
84+
'recall': recall}
85+
86+
print('Precision: {0:0.5f} +- {1:0.5f}'.format(statistics.mean(precision), statistics.stdev(precision)))
87+
print('Recall: {0:0.5f} +- {1:0.5f}'.format(statistics.mean(recall), statistics.stdev(recall)))
88+
89+
print(tabulate.tabulate(metrics_dict, headers=metrics_dict.keys(), showindex=False, tablefmt='github'))
90+
print()

scripts/eval/unigram_fit.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import os
2+
import sys
3+
import argparse
4+
import tempfile
5+
import subprocess
6+
from pathlib import Path
7+
8+
if __name__ == '__main__':
9+
parser = argparse.ArgumentParser(description='fit n models using n folds')
10+
parser.add_argument('-i', '--input_directory', required=True,
11+
help='input directory of the n folds')
12+
parser.add_argument('-b', '--apertium_bin', required=True,
13+
help='a compiled dictionary')
14+
parser.add_argument('-o', '--output_directory', required=True,
15+
help='output directory for weighted dictionaries')
16+
args = parser.parse_args()
17+
input_directory = args.input_directory
18+
apertium_bin = args.apertium_bin
19+
output_directory = args.output_directory
20+
if not os.path.exists(output_directory):
21+
os.mkdir(output_directory)
22+
23+
temp_dir = tempfile.mkdtemp()
24+
25+
temp_weightlist = Path(temp_dir, 'temp_weightlist')
26+
temp_input_file = Path(temp_dir, 'temp_input')
27+
for input_file in sorted(os.listdir(input_directory)):
28+
temp_input_files = [Path(input_directory, input_file) for file in sorted(os.listdir(input_directory)) if file!=input_file]
29+
with open(temp_input_file, 'w') as f:
30+
for file in temp_input_files:
31+
with open(file, 'r') as fold_file:
32+
f.write(fold_file.read())
33+
34+
subprocess.run(['python',
35+
'annotated_corpus_to_weightlist.py',
36+
Path(input_directory, temp_input_file), temp_weightlist])
37+
38+
# Generate a bin file
39+
subprocess.run(['./lt-weight',
40+
apertium_bin,
41+
Path(output_directory, '{}.bin'.format(input_file)),
42+
temp_weightlist])

0 commit comments

Comments
 (0)