-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathldm_query.py
More file actions
432 lines (382 loc) · 16.1 KB
/
Copy pathldm_query.py
File metadata and controls
432 lines (382 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
"""
===========================
Query corpora and linguistic distributional models.
===========================
Dr. Cai Wingfield
---------------------------
Embodied Cognition Lab
Department of Psychology
University of Lancaster
c.wingfield@lancaster.ac.uk
caiwingfield.net
---------------------------
2018
---------------------------
"""
import argparse
import logging
import sys
from enum import Enum, auto
from os import path
from ldm.corpus.corpus import CorpusMetadata
from ldm.corpus.indexing import FreqDist
from ldm.utils.maths import DistanceType
from ldm.corpus.multiword import VectorCombinatorType
from ldm.preferences.config import Config as LDMConfig
from operation import run_frequency, run_frequency_with_list, run_rank, run_rank_with_list, run_vector, \
run_vector_with_list, run_compare, run_compare_with_list, run_compare_with_pair_list
# Suppress logging
logger = logging.getLogger('my-logger')
logger.propagate = False
# shortname → dirname
_corpora = {
"bnc": "BNC",
"subtitles": "BBC",
"ukwac": "UKWAC",
}
_ngram_models = [
"log-ngram",
"conditional-probability-ngram",
"probability-ratio-ngram",
"ppmi-ngram",
]
_count_models = [
"log-cooccurrence",
"conditional-probability",
"probability-ratio",
"ppmi",
]
_predict_models = [
"skip-gram",
"cbow",
]
_models = _ngram_models + _count_models + _predict_models
_embedding_sizes = [50, 100, 200, 300, 500]
_window_radii = [1, 3, 5, 10]
_readme_path = path.join(path.dirname(path.realpath(__file__)), 'README.md')
_config_path = path.join(path.dirname(path.realpath(__file__)), 'config.yaml')
class Mode(Enum):
"""The main invocation mode of the program."""
Frequency = auto()
Rank = auto()
Vector = auto()
Compare = auto()
@property
def name(self) -> str:
if self is Mode.Frequency:
return "frequency"
elif self is Mode.Rank:
return "rank"
elif self is Mode.Vector:
return "vector"
elif self is Mode.Compare:
return "compare"
else:
raise NotImplementedError()
@property
def option(self) -> str:
return '--' + self.name
class WordMode(Enum):
"""How words will be supplied"""
# One word from CLI
SingleWord = auto()
# List of words from file
SingleWordList = auto()
# Word pair from CLI
WordPair = auto()
# List of word pairs from file
WordPairList = auto()
def main(ldm_config: LDMConfig):
argparser = build_argparser()
args = argparser.parse_args()
def _option_used(option_name):
if option_name in vars(args):
if vars(args)[option_name]:
return True
else:
return False
else:
return False
# region Get mode
if args.mode == Mode.Frequency.name:
mode = Mode.Frequency
elif args.mode == Mode.Rank.name:
mode = Mode.Rank
elif args.mode == Mode.Vector.name:
mode = Mode.Vector
elif args.mode == Mode.Compare.name:
mode = Mode.Compare
else:
raise NotImplementedError()
# region Validate args
# Validate model params
if _option_used("model"):
# For predict models, embedding size is required
if args.model[0].lower() in _predict_models:
if len(args.model) == 1:
argparser.error("Please specify embedding size when using predict models")
elif int(args.model[1]) not in _embedding_sizes:
argparser.error(f"Invalid embedding size {args.model[1]}, "
f"Please select an embedding size from the list {_embedding_sizes}")
# For count and ngram models, embedding size is forbidden
else:
if len(args.model) > 1:
argparser.error("Embedding size invalid for count and n-gram models")
# Validate vector mode
if mode is Mode.Vector:
if args.model[0].lower() in _ngram_models:
argparser.error("Cannot use n-gram model in vector mode.")
# Validate distance measure
if mode is Mode.Compare:
# All but n-grams require distance
if args.model[0].lower() in _ngram_models:
if args.distance is not None:
argparser.error("Distance not valid for n-gram models")
else:
if args.distance is None:
argparser.error("Distance is required for vector-based models.")
# Validate combinator type
if mode is Mode.Compare:
# Combinators can only be used with vector models, but is not required
if args.model[0].lower() in _ngram_models:
if args.combinator is not None:
argparser.error("Combinator not valid for n-gram models")
# endregion
# region Interpret args
# Get word_mode
# and words or path
if _option_used("word"):
word_mode = WordMode.SingleWord
words_or_path = args.word
elif _option_used("word_pair"):
word_mode = WordMode.WordPair
words_or_path = args.word_pair
elif _option_used("words_from_file"):
word_mode = WordMode.SingleWordList
words_or_path = args.words_from_file
elif _option_used("word_pairs_from_file"):
word_mode = WordMode.WordPairList
words_or_path = args.word_pairs_from_file
else:
raise NotImplementedError()
# get model spec
if not _option_used("model"):
model_type = None
embedding_size = None
elif len(args.model) == 1:
model_type = args.model[0]
embedding_size = None
elif len(args.model) == 2:
model_type = args.model[0]
embedding_size = int(args.model[1])
else:
raise NotImplementedError()
radius = int(args.window_radius) if "window_radius" in vars(args) else None
if not _option_used("distance"):
distance = None
elif args.distance.lower() == "cosine":
distance = DistanceType.cosine
elif args.distance.lower() == "correlation":
distance = DistanceType.correlation
elif args.distance.lower() == "euclidean":
distance = DistanceType.Euclidean
else:
raise NotImplementedError()
if not _option_used("combinator"):
combinator_type = VectorCombinatorType.none
elif args.combinator == VectorCombinatorType.none.name:
combinator_type = VectorCombinatorType.none
elif args.combinator == VectorCombinatorType.additive.name:
combinator_type = VectorCombinatorType.additive
elif args.combinator == VectorCombinatorType.multiplicative.name:
combinator_type = VectorCombinatorType.multiplicative
elif args.combinator == VectorCombinatorType.mean.name:
combinator_type = VectorCombinatorType.mean
else:
raise NotImplementedError()
# Get corpus and freqdist
corpus_name = args.corpus
corpus: CorpusMetadata = CorpusMetadata(
name=_corpora[corpus_name],
path=ldm_config.value_by_key_path("corpora", corpus_name, "path"),
freq_dist_path=ldm_config.value_by_key_path("corpora", corpus_name, "index"))
freq_dist: FreqDist = FreqDist.load(corpus.freq_dist_path)
# Get output file
output_file = args.output_file
# Build model
model = get_model_from_parameters(model_type, radius, embedding_size, corpus, freq_dist)
# endregion
# region Run appropriate function based on mode
if mode is Mode.Frequency:
if word_mode is WordMode.SingleWord:
run_frequency(words_or_path, freq_dist, output_file)
elif word_mode is WordMode.SingleWordList:
run_frequency_with_list(words_or_path, freq_dist, corpus, output_file)
else:
raise NotImplementedError()
elif mode is Mode.Rank:
if word_mode is WordMode.SingleWord:
run_rank(words_or_path, freq_dist, output_file)
elif word_mode is WordMode.SingleWordList:
run_rank_with_list(words_or_path, freq_dist, corpus, output_file)
else:
raise NotImplementedError()
elif mode is Mode.Vector:
if word_mode is WordMode.SingleWord:
run_vector(words_or_path, model, output_file)
elif word_mode is WordMode.SingleWordList:
run_vector_with_list(words_or_path, model, output_file)
else:
raise NotImplementedError()
elif mode is Mode.Compare:
if word_mode is WordMode.WordPair:
run_compare(words_or_path[0], words_or_path[1], model, distance, combinator_type, output_file)
elif word_mode is WordMode.SingleWordList:
run_compare_with_list(words_or_path, model, distance, combinator_type, output_file)
elif word_mode is WordMode.WordPairList:
run_compare_with_pair_list(words_or_path, model, distance, combinator_type, output_file)
else:
raise NotImplementedError()
else:
raise NotImplementedError()
# endregion
sys.exit(0)
def build_argparser():
argparser = argparse.ArgumentParser(
description="Query corpora and linguistic distributional models. See README.md for more info.")
# Add mode parsers
mode_subparsers = argparser.add_subparsers(dest="mode")
mode_subparsers.required = True
mode_frequency_parser = mode_subparsers.add_parser(
Mode.Frequency.name,
help="Look up frequency of word in corpus")
mode_rank_parser = mode_subparsers.add_parser(
Mode.Rank.name,
help="Look up rank of word in corpus by frequency")
mode_vector_parser = mode_subparsers.add_parser(
Mode.Vector.name,
help="Look up the vector representation of a word in a model.")
mode_compare_parser = mode_subparsers.add_parser(
Mode.Compare.name,
help="Compare word pairs using a model.")
# Add corpus and outfile options to all modes
for mode_subparser in [mode_frequency_parser, mode_rank_parser, mode_vector_parser, mode_compare_parser]:
mode_subparser.add_argument("--corpus",
type=str,
choices=_corpora.keys(),
required=True,
help="The name of the corpus.")
mode_subparser.add_argument("--output-file",
type=str,
required=False,
dest="output_file",
metavar="PATH",
help="Write the output to this file. Will overwrite existing files.")
# Add single word options to relevant parsers
for mode_subparser in [mode_frequency_parser, mode_rank_parser, mode_vector_parser]:
wordmode_group = mode_subparser.add_mutually_exclusive_group()
wordmode_group.add_argument("--word",
type=str,
required=False,
help="The word to look up.")
wordmode_group.add_argument("--words-from-file",
type=str,
dest="words_from_file",
required=False,
metavar="PATH",
help="The word to look up or compare.")
# Add all multi=word options to compare parser
wordmode_group = mode_compare_parser.add_mutually_exclusive_group()
wordmode_group.add_argument("--words-from-file",
type=str,
dest="words_from_file",
required=False,
metavar="PATH",
help="The word to look up or compare.")
wordmode_group.add_argument("--word-pair",
type=str,
dest="word_pair",
nargs=2,
required=False,
metavar=('FIRST WORD', 'SECOND WORD'),
help="The words to compare.")
wordmode_group.add_argument("--word-pairs-from-file",
type=str,
dest="word_pairs_from_file",
required=False,
metavar="PATH",
help="The word pairs to compare.")
# Add model arguments to relevant parsers
for mode_subparser in [mode_vector_parser, mode_compare_parser]:
mode_subparser.add_argument("--model",
type=str,
choices=_models,
nargs="+",
required=True,
dest="model",
metavar=("MODEL", "EMBEDDING"),
help="The model specification to use.")
mode_subparser.add_argument("--radius",
type=int,
choices=_window_radii,
dest="window_radius",
required=True,
help="The window radius to use.")
mode_compare_parser.add_argument("--distance",
type=str,
choices=[dt.name for dt in DistanceType],
required=False,
help="The distance type to use.")
mode_compare_parser.add_argument("--combinator",
choices=[vc.name for vc in VectorCombinatorType],
required=False,
help="The vector combinator to use for multi-word tokens.")
return argparser
def get_model_from_parameters(model_type: str, window_radius, embedding_size, corpus, freq_dist):
if model_type is None:
return None
# Don't care about difference between underscores and hyphens
model_type = model_type.lower().replace("_", "-")
# N-gram models
if model_type == "log-ngram":
from ldm.model.ngram import LogNgramModel
return LogNgramModel(corpus, window_radius, freq_dist)
if model_type == "conditional-probability-ngram":
from ldm.model.ngram import ConditionalProbabilityNgramModel
return ConditionalProbabilityNgramModel(corpus, window_radius, freq_dist)
if model_type == "probability-ratio-ngram":
from ldm.model.ngram import ProbabilityRatioNgramModel
return ProbabilityRatioNgramModel(corpus, window_radius, freq_dist)
if model_type == "pmi-ngram":
from ldm.model.ngram import PMINgramModel
return PMINgramModel(corpus, window_radius, freq_dist)
if model_type == "ppmi-ngram":
from ldm.model.ngram import PPMINgramModel
return PPMINgramModel(corpus, window_radius, freq_dist)
# Count vector models:
if model_type == "log-cooccurrence":
from ldm.model.count import LogCoOccurrenceCountModel
return LogCoOccurrenceCountModel(corpus, window_radius, freq_dist)
if model_type == "conditional-probability":
from ldm.model.count import ConditionalProbabilityModel
return ConditionalProbabilityModel(corpus, window_radius, freq_dist)
if model_type == "probability-ratio":
from ldm.model.count import ProbabilityRatioModel
return ProbabilityRatioModel(corpus, window_radius, freq_dist)
if model_type == "pmi":
from ldm.model.count import PMIModel
return PMIModel(corpus, window_radius, freq_dist)
if model_type == "ppmi":
from ldm.model.count import PPMIModel
return PPMIModel(corpus, window_radius, freq_dist)
# Predict vector models:
if model_type == "skip-gram":
from ldm.model.predict import SkipGramModel
return SkipGramModel(corpus, window_radius, embedding_size)
if model_type == "cbow":
from ldm.model.predict import CbowModel
return CbowModel(corpus, window_radius, embedding_size)
raise NotImplementedError()
if __name__ == '__main__':
with LDMConfig(use_config_overrides_from_file=_config_path) as config:
main(config)