textslinger 0.2.0__tar.gz → 0.2.3__tar.gz
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.
- {textslinger-0.2.0/textslinger.egg-info → textslinger-0.2.3}/PKG-INFO +1 -1
- {textslinger-0.2.0 → textslinger-0.2.3}/pyproject.toml +17 -17
- textslinger-0.2.3/tests/conftest.py +65 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_causal_byte.py +240 -56
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_causal_subword_local.py +2 -10
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_causal_subword_repoid.py +1 -3
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_constants.py +12 -6
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_keystroke_savings_ngram.py +4 -6
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_ngram.py +50 -133
- textslinger-0.2.3/tests/test_ngram_predict_words.py +706 -0
- textslinger-0.2.3/textslinger/__init__.py +25 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/causal_byte.py +135 -102
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/causal_subword.py +69 -100
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/eval_helper.py +42 -49
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/language_model.py +43 -30
- textslinger-0.2.3/textslinger/ngram.py +642 -0
- {textslinger-0.2.0 → textslinger-0.2.3/textslinger.egg-info}/PKG-INFO +1 -1
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger.egg-info/SOURCES.txt +1 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger.egg-info/top_level.txt +0 -1
- textslinger-0.2.0/tests/conftest.py +0 -29
- textslinger-0.2.0/textslinger/__init__.py +0 -2
- textslinger-0.2.0/textslinger/ngram.py +0 -336
- {textslinger-0.2.0 → textslinger-0.2.3}/LICENSE +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/README.md +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/setup.cfg +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/__init__.py +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/helpers.py +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/tests/test_causal_subword_basic.py +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/exceptions.py +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger/helpers.py +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger.egg-info/dependency_links.txt +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger.egg-info/requires.txt +0 -0
- {textslinger-0.2.0 → textslinger-0.2.3}/textslinger.egg-info/zip-safe +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: textslinger
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: TextSlinger: Fast and accurate text predictions in Python
|
|
5
5
|
Author-email: Keith Vertanen <vertanen@mtu.edu>, Dylan Gaines <dgaine20@kennesaw.edu>, Soufia Bahmani <sbahmani@mtu.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -4,7 +4,7 @@ requires = ["setuptools>=77.0.3", "wheel>=0.37.1"]
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "textslinger"
|
|
7
|
-
|
|
7
|
+
dynamic = ["version"]
|
|
8
8
|
authors = [
|
|
9
9
|
{name="Keith Vertanen", email="vertanen@mtu.edu"},
|
|
10
10
|
{name="Dylan Gaines", email="dgaine20@kennesaw.edu"},
|
|
@@ -16,14 +16,14 @@ license-files = ["LICENSE"]
|
|
|
16
16
|
license = "MIT"
|
|
17
17
|
requires-python = ">=3.10,<3.11"
|
|
18
18
|
classifiers = [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Human Machine Interfaces",
|
|
21
|
+
"Natural Language :: English",
|
|
22
|
+
"Intended Audience :: Science/Research",
|
|
23
|
+
"Intended Audience :: End Users/Desktop",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Programming Language :: Python",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
27
|
]
|
|
28
28
|
|
|
29
29
|
dependencies = [
|
|
@@ -60,21 +60,21 @@ release = [
|
|
|
60
60
|
]
|
|
61
61
|
|
|
62
62
|
[project.urls]
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
Homepage = "https://github.com/kdv123/textslinger"
|
|
64
|
+
Source = "https://github.com/kdv123/textslinger"
|
|
65
65
|
|
|
66
66
|
[tool.setuptools]
|
|
67
|
-
# https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html
|
|
68
67
|
platforms = ["Linux", "Windows", "Mac OS-X"]
|
|
69
68
|
include-package-data = true
|
|
70
69
|
zip-safe = true
|
|
71
70
|
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
[tool.setuptools.dynamic]
|
|
72
|
+
version = {attr = "textslinger.__version__"}
|
|
73
|
+
|
|
74
|
+
# Python modules and packages included in the distribution
|
|
74
75
|
[tool.setuptools.packages.find]
|
|
75
76
|
exclude = ["examples", "scripts"]
|
|
76
77
|
|
|
77
78
|
[tool.black]
|
|
78
|
-
line-length =
|
|
79
|
-
target-version = ["py310"]
|
|
80
|
-
|
|
79
|
+
line-length = 100
|
|
80
|
+
target-version = ["py310"]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
For configuration options related to running the test suite
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
from textslinger.helpers import Device, Precision
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def pytest_addoption(parser):
|
|
10
|
+
parser.addoption("--device", type=Device, choices=list(Device), default=Device.CPU)
|
|
11
|
+
parser.addoption(
|
|
12
|
+
"--precision", type=Precision, choices=list(Precision), default=Precision.FP32
|
|
13
|
+
)
|
|
14
|
+
parser.addoption(
|
|
15
|
+
"--run-optional",
|
|
16
|
+
action="store_true",
|
|
17
|
+
default=False,
|
|
18
|
+
help="Run optional tests that are skipped by default",
|
|
19
|
+
)
|
|
20
|
+
parser.addoption(
|
|
21
|
+
"--run-long",
|
|
22
|
+
action="store_true",
|
|
23
|
+
default=False,
|
|
24
|
+
help="Run long tests that are skipped by default",
|
|
25
|
+
)
|
|
26
|
+
parser.addoption(
|
|
27
|
+
"--rewrite-ref-files",
|
|
28
|
+
action="store_true",
|
|
29
|
+
default=False,
|
|
30
|
+
help="Rewrite reference files, e.g. files with log probabilities obtained from a model",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def pytest_runtest_setup(item):
|
|
35
|
+
# Skip optional tests by default
|
|
36
|
+
if "optional" in item.keywords and not item.config.getoption("--run-optional"):
|
|
37
|
+
pytest.skip("Skipping optional test by default")
|
|
38
|
+
|
|
39
|
+
# Skip long tests by default unless --run-long is provided
|
|
40
|
+
if "long" in item.keywords and not item.config.getoption("--run-long"):
|
|
41
|
+
pytest.skip("Skipping long test by default")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture(scope="session")
|
|
45
|
+
def device(request):
|
|
46
|
+
"""
|
|
47
|
+
Fixture to determine whether tests run on auto, cpu, cuda, or mps
|
|
48
|
+
"""
|
|
49
|
+
return request.config.getoption("--device")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture(scope="session")
|
|
53
|
+
def precision(request):
|
|
54
|
+
"""
|
|
55
|
+
Fixture to set the precision models are loaded with
|
|
56
|
+
"""
|
|
57
|
+
return request.config.getoption("--precision")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@pytest.fixture(scope="session")
|
|
61
|
+
def rewrite_ref_files(request):
|
|
62
|
+
"""
|
|
63
|
+
Fixture to request some tests rewrite their reference files
|
|
64
|
+
"""
|
|
65
|
+
return request.config.getoption("--rewrite-ref-files")
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
from textslinger.causal_byte import (
|
|
2
2
|
CausalByteLanguageModel,
|
|
3
3
|
InvalidLanguageModelException,
|
|
4
|
+
DecodeConfigByte,
|
|
4
5
|
)
|
|
5
6
|
import pytest
|
|
6
|
-
from tests.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
SYMBOLS_LOWERCASE_SPACE,
|
|
7
|
+
from tests.helpers import (
|
|
8
|
+
check_match,
|
|
9
|
+
check_sum_to_one,
|
|
10
|
+
suppress_stderr_fd,
|
|
11
|
+
read_float_list,
|
|
12
12
|
)
|
|
13
|
-
from tests.
|
|
13
|
+
from tests.test_constants import *
|
|
14
|
+
from textslinger.eval_helper import (
|
|
15
|
+
load_phrases_plaintext,
|
|
16
|
+
filter_phrases,
|
|
17
|
+
normalize_phrases,
|
|
18
|
+
)
|
|
19
|
+
import copy
|
|
14
20
|
|
|
15
21
|
# NOTE: if the device is changed to MPS, many tests fail because the probabilities and/or hypotheses change
|
|
16
22
|
# Currently set high enough to pass on various CPUs and GPUs
|
|
17
23
|
EPSILON_BYTE_PREDICT = 0.04
|
|
18
24
|
|
|
25
|
+
# Higher epsilon for scoring items and comparing total log prob
|
|
26
|
+
EPSILON_BYTE_LOG_PROB = 0.2
|
|
27
|
+
|
|
19
28
|
|
|
20
29
|
@pytest.fixture(scope="module")
|
|
21
30
|
def shared_lm(device, precision):
|
|
@@ -34,6 +43,58 @@ def shared_lm(device, precision):
|
|
|
34
43
|
yield lm
|
|
35
44
|
|
|
36
45
|
|
|
46
|
+
@pytest.fixture(scope="module")
|
|
47
|
+
def config_default():
|
|
48
|
+
"""
|
|
49
|
+
A default set for our tests since we don't want to rely on the defaults in the class
|
|
50
|
+
since they might get changed
|
|
51
|
+
"""
|
|
52
|
+
config = DecodeConfigByte()
|
|
53
|
+
config.max_word_hypotheses = 256
|
|
54
|
+
config.beam_best = 8.0
|
|
55
|
+
config.beam_search_max = 300
|
|
56
|
+
config.max_word_len = 25
|
|
57
|
+
config.lm_scale = 1.0
|
|
58
|
+
config.ins_penalty = -20.0
|
|
59
|
+
config.ins_after_input_penalty = 0.0
|
|
60
|
+
config.del_penalty = -20.0
|
|
61
|
+
config.batch_size = None
|
|
62
|
+
yield config
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@pytest.fixture(scope="module")
|
|
66
|
+
def config_narrow(config_default):
|
|
67
|
+
"""
|
|
68
|
+
Narrow beam for fast decoding
|
|
69
|
+
"""
|
|
70
|
+
config = copy.deepcopy(config_default)
|
|
71
|
+
config.beam_best = 5.0
|
|
72
|
+
config.beam_search_max = 4
|
|
73
|
+
yield config
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@pytest.fixture(scope="module")
|
|
77
|
+
def config_narrower(config_default):
|
|
78
|
+
"""
|
|
79
|
+
Narrow beam for fast decoding
|
|
80
|
+
"""
|
|
81
|
+
config = copy.deepcopy(config_default)
|
|
82
|
+
config.beam_best = 1.3
|
|
83
|
+
config.beam_search_max = 4
|
|
84
|
+
yield config
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture(scope="module")
|
|
88
|
+
def config_narrowest(config_default):
|
|
89
|
+
"""
|
|
90
|
+
Narrow beam for fast decoding
|
|
91
|
+
"""
|
|
92
|
+
config = copy.deepcopy(config_default)
|
|
93
|
+
config.beam_best = 0.5
|
|
94
|
+
config.beam_search_max = 4
|
|
95
|
+
yield config
|
|
96
|
+
|
|
97
|
+
|
|
37
98
|
def test_bogus_model_name():
|
|
38
99
|
"""
|
|
39
100
|
Attempt to load with a bogus model name
|
|
@@ -65,9 +126,7 @@ def test_lowercase_symbol_set(shared_lm):
|
|
|
65
126
|
|
|
66
127
|
# First of all size should be correct
|
|
67
128
|
assert len(shared_lm.symbol_set) == len(SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS)
|
|
68
|
-
assert len(shared_lm.symbol_set_lower) == len(
|
|
69
|
-
SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS
|
|
70
|
-
)
|
|
129
|
+
assert len(shared_lm.symbol_set_lower) == len(SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS)
|
|
71
130
|
|
|
72
131
|
# Check all symbols are in both the normal and lowercase symbol sets
|
|
73
132
|
for symbol in SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS:
|
|
@@ -292,7 +351,7 @@ def test_predict_symbol_endings(shared_lm):
|
|
|
292
351
|
check_sum_to_one(predictions)
|
|
293
352
|
|
|
294
353
|
|
|
295
|
-
def test_predict_words_no_context(shared_lm):
|
|
354
|
+
def test_predict_words_no_context(shared_lm, config_narrowest):
|
|
296
355
|
"""
|
|
297
356
|
Make word predictions in the middle of a word that is likely a contraction
|
|
298
357
|
"""
|
|
@@ -300,14 +359,13 @@ def test_predict_words_no_context(shared_lm):
|
|
|
300
359
|
references = ["title", "what", "let"]
|
|
301
360
|
predictions = shared_lm.predict_words(
|
|
302
361
|
left_context="",
|
|
303
|
-
|
|
304
|
-
beam_search_max=4,
|
|
362
|
+
config=config_narrowest,
|
|
305
363
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
306
364
|
)
|
|
307
365
|
check_match(predictions, references)
|
|
308
366
|
|
|
309
367
|
|
|
310
|
-
def test_predict_words_full_word(shared_lm):
|
|
368
|
+
def test_predict_words_full_word(shared_lm, config_narrowest):
|
|
311
369
|
"""
|
|
312
370
|
Make word predictions at the end of a word but merge word end symbols
|
|
313
371
|
This causes all the end of word symbol hypotheses to collapse into blank hypothesis
|
|
@@ -316,14 +374,13 @@ def test_predict_words_full_word(shared_lm):
|
|
|
316
374
|
references = [""]
|
|
317
375
|
predictions = shared_lm.predict_words(
|
|
318
376
|
left_context="Bye bye",
|
|
319
|
-
|
|
320
|
-
beam_search_max=4,
|
|
377
|
+
config=config_narrowest,
|
|
321
378
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
322
379
|
)
|
|
323
380
|
check_match(predictions, references)
|
|
324
381
|
|
|
325
382
|
|
|
326
|
-
def test_predict_words_full_word_include_ending(shared_lm):
|
|
383
|
+
def test_predict_words_full_word_include_ending(shared_lm, config_narrow):
|
|
327
384
|
"""
|
|
328
385
|
Make word predictions at the end of a word and request the ending symbol be included in results
|
|
329
386
|
"""
|
|
@@ -331,15 +388,14 @@ def test_predict_words_full_word_include_ending(shared_lm):
|
|
|
331
388
|
references = [" ", ",", "!", ".", "?"]
|
|
332
389
|
predictions = shared_lm.predict_words(
|
|
333
390
|
left_context="Bye bye",
|
|
334
|
-
|
|
335
|
-
beam_search_max=4,
|
|
391
|
+
config=config_narrow,
|
|
336
392
|
include_ending_symbol=True,
|
|
337
393
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
338
394
|
)
|
|
339
395
|
check_match(predictions, references)
|
|
340
396
|
|
|
341
397
|
|
|
342
|
-
def test_predict_words_partial_word_with_log_probs(shared_lm):
|
|
398
|
+
def test_predict_words_partial_word_with_log_probs(shared_lm, config_narrow):
|
|
343
399
|
"""
|
|
344
400
|
Make word predictions at near the end of a word
|
|
345
401
|
This also checks the log probs in the results
|
|
@@ -348,15 +404,14 @@ def test_predict_words_partial_word_with_log_probs(shared_lm):
|
|
|
348
404
|
references = [("e", -0.11844532689588708), ("", -4.672425501089463)]
|
|
349
405
|
predictions = shared_lm.predict_words(
|
|
350
406
|
left_context="Bye by",
|
|
351
|
-
|
|
352
|
-
beam_search_max=4,
|
|
407
|
+
config=config_narrow,
|
|
353
408
|
return_log_probs=True,
|
|
354
409
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
355
410
|
)
|
|
356
411
|
check_match(predictions, references, epsilon=EPSILON_BYTE_PREDICT)
|
|
357
412
|
|
|
358
413
|
|
|
359
|
-
def test_predict_words_partial_word_include_ending(shared_lm):
|
|
414
|
+
def test_predict_words_partial_word_include_ending(shared_lm, config_narrow):
|
|
360
415
|
"""
|
|
361
416
|
Make word predictions at near the end of a word and request the ending symbol be included in results
|
|
362
417
|
"""
|
|
@@ -364,60 +419,59 @@ def test_predict_words_partial_word_include_ending(shared_lm):
|
|
|
364
419
|
references = ["e ", "e,", "e!", "e.", " ", "e?"]
|
|
365
420
|
predictions = shared_lm.predict_words(
|
|
366
421
|
left_context="Bye by",
|
|
367
|
-
|
|
368
|
-
beam_search_max=4,
|
|
422
|
+
config=config_narrow,
|
|
369
423
|
include_ending_symbol=True,
|
|
370
424
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
371
425
|
)
|
|
372
426
|
check_match(predictions, references)
|
|
373
427
|
|
|
374
428
|
|
|
375
|
-
def test_predict_words_contraction(shared_lm):
|
|
429
|
+
def test_predict_words_contraction(shared_lm, config_narrower):
|
|
376
430
|
"""
|
|
377
431
|
Make word predictions in the middle of a word that is likely a contraction
|
|
378
432
|
"""
|
|
379
433
|
shared_lm.change_symbol_set(SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS)
|
|
380
|
-
references = ["d", "dn't", "
|
|
434
|
+
references = ["d", "dn't", "e", "", "n"]
|
|
381
435
|
predictions = shared_lm.predict_words(
|
|
382
436
|
left_context="I do not know why you di",
|
|
383
|
-
|
|
437
|
+
config=config_narrower,
|
|
384
438
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
385
439
|
)
|
|
386
440
|
check_match(predictions, references)
|
|
387
441
|
|
|
388
442
|
|
|
389
|
-
def test_predict_words_contraction_no_apostrophe(shared_lm):
|
|
443
|
+
def test_predict_words_contraction_no_apostrophe(shared_lm, config_narrower):
|
|
390
444
|
"""
|
|
391
445
|
Make word predictions in the middle of a word that is likely a contraction
|
|
392
446
|
This version doesn't have apostrophe in the valid symbols
|
|
393
447
|
"""
|
|
394
448
|
shared_lm.change_symbol_set(SYMBOLS_LOWERCASE_SPACE)
|
|
395
|
-
references = ["d", "", "n"]
|
|
449
|
+
references = ["d", "e", "", "n"]
|
|
396
450
|
predictions = shared_lm.predict_words(
|
|
397
451
|
left_context="I do not know why you di",
|
|
398
|
-
|
|
452
|
+
config=config_narrower,
|
|
399
453
|
)
|
|
400
454
|
check_match(predictions, references, expected_predictions=len(references))
|
|
401
455
|
|
|
402
456
|
|
|
403
|
-
def test_predict_words_contraction_prune_max_words(shared_lm):
|
|
457
|
+
def test_predict_words_contraction_prune_max_words(shared_lm, config_narrower):
|
|
404
458
|
"""
|
|
405
459
|
Make word predictions in the middle of a word that is likely a contraction
|
|
406
460
|
This version prunes based on the maximum number of word hypotheses
|
|
407
461
|
"""
|
|
408
462
|
shared_lm.change_symbol_set(SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS)
|
|
409
|
-
|
|
410
|
-
|
|
463
|
+
config = copy.deepcopy(config_narrower)
|
|
464
|
+
config.max_word_hypotheses = 3
|
|
465
|
+
references = ["e", "", "n"]
|
|
411
466
|
predictions = shared_lm.predict_words(
|
|
412
467
|
left_context="I do not know why you di",
|
|
413
|
-
|
|
414
|
-
max_word_hypotheses=max_word_hypos,
|
|
468
|
+
config=config,
|
|
415
469
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
416
470
|
)
|
|
417
|
-
check_match(predictions, references, expected_predictions=
|
|
471
|
+
check_match(predictions, references, expected_predictions=config.max_word_hypotheses)
|
|
418
472
|
|
|
419
473
|
|
|
420
|
-
def test_predict_words_mixed_case(shared_lm):
|
|
474
|
+
def test_predict_words_mixed_case(shared_lm, config_narrower):
|
|
421
475
|
"""
|
|
422
476
|
Try and predict a word that is in mixed case
|
|
423
477
|
"""
|
|
@@ -425,15 +479,14 @@ def test_predict_words_mixed_case(shared_lm):
|
|
|
425
479
|
references = ["iPhone", "Apple"]
|
|
426
480
|
predictions = shared_lm.predict_words(
|
|
427
481
|
left_context="On Friday Apple released their latest phone the ",
|
|
428
|
-
|
|
429
|
-
beam_search_max=4,
|
|
482
|
+
config=config_narrower,
|
|
430
483
|
predict_lower=False,
|
|
431
484
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
432
485
|
)
|
|
433
486
|
check_match(predictions, references)
|
|
434
487
|
|
|
435
488
|
|
|
436
|
-
def test_predict_words_extra_endings(shared_lm):
|
|
489
|
+
def test_predict_words_extra_endings(shared_lm, config_narrowest):
|
|
437
490
|
"""
|
|
438
491
|
Predict the very end of a word but with extra word endings enabled
|
|
439
492
|
"""
|
|
@@ -441,31 +494,30 @@ def test_predict_words_extra_endings(shared_lm):
|
|
|
441
494
|
references = ["y", ""]
|
|
442
495
|
predictions = shared_lm.predict_words(
|
|
443
496
|
left_context="How are you feeling toda",
|
|
444
|
-
|
|
497
|
+
config=config_narrowest,
|
|
445
498
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
446
|
-
beam_search_max=4,
|
|
447
499
|
)
|
|
448
500
|
check_match(predictions, references, expected_predictions=len(references))
|
|
449
501
|
|
|
450
502
|
|
|
451
|
-
def test_predict_words_extra_endings_batch(shared_lm):
|
|
503
|
+
def test_predict_words_extra_endings_batch(shared_lm, config_narrowest):
|
|
452
504
|
"""
|
|
453
505
|
Predict the very end of a word but with extra word endings enabled
|
|
454
506
|
Uses a smaller mini batch size during inference
|
|
455
507
|
"""
|
|
456
508
|
shared_lm.change_symbol_set(SYMBOLS_LOWERCASE_SPACE_APOSTROPHE_ENDINGS)
|
|
509
|
+
config = copy.deepcopy(config_narrowest)
|
|
510
|
+
config.batch_size = 8
|
|
457
511
|
references = ["y", ""]
|
|
458
512
|
predictions = shared_lm.predict_words(
|
|
459
513
|
left_context="How are you feeling toda",
|
|
460
|
-
|
|
514
|
+
config=config,
|
|
461
515
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
462
|
-
beam_search_max=4,
|
|
463
|
-
batch_size=8,
|
|
464
516
|
)
|
|
465
517
|
check_match(predictions, references, expected_predictions=len(references))
|
|
466
518
|
|
|
467
519
|
|
|
468
|
-
def test_predict_words_curly_apostrophe(shared_lm):
|
|
520
|
+
def test_predict_words_curly_apostrophe(shared_lm, config_narrowest):
|
|
469
521
|
"""
|
|
470
522
|
Try and predict the ending of a word with curly apostrophe
|
|
471
523
|
"""
|
|
@@ -473,14 +525,13 @@ def test_predict_words_curly_apostrophe(shared_lm):
|
|
|
473
525
|
references = ["'t", "t", ""]
|
|
474
526
|
predictions = shared_lm.predict_words(
|
|
475
527
|
left_context="You didn’t do it and I also didn",
|
|
476
|
-
|
|
528
|
+
config=config_narrowest,
|
|
477
529
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
478
|
-
beam_search_max=4,
|
|
479
530
|
)
|
|
480
531
|
check_match(predictions, references, expected_predictions=len(references))
|
|
481
532
|
|
|
482
533
|
|
|
483
|
-
def test_predict_words_english(shared_lm):
|
|
534
|
+
def test_predict_words_english(shared_lm, config_narrowest):
|
|
484
535
|
"""
|
|
485
536
|
Prediction to compare with that uses English left context
|
|
486
537
|
"""
|
|
@@ -489,14 +540,13 @@ def test_predict_words_english(shared_lm):
|
|
|
489
540
|
references = ["d"]
|
|
490
541
|
predictions = shared_lm.predict_words(
|
|
491
542
|
left_context="Good day and I love you very much. Ad",
|
|
492
|
-
|
|
493
|
-
beam_search_max=4,
|
|
543
|
+
config=config_narrowest,
|
|
494
544
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
495
545
|
)
|
|
496
546
|
check_match(predictions, references)
|
|
497
547
|
|
|
498
548
|
|
|
499
|
-
def test_predict_words_spanish(shared_lm):
|
|
549
|
+
def test_predict_words_spanish(shared_lm, config_narrowest):
|
|
500
550
|
"""
|
|
501
551
|
Try and predict a word in Spanish using Spanish left context
|
|
502
552
|
"""
|
|
@@ -505,8 +555,142 @@ def test_predict_words_spanish(shared_lm):
|
|
|
505
555
|
references = ["ios"]
|
|
506
556
|
predictions = shared_lm.predict_words(
|
|
507
557
|
left_context="¡Buen día y te amo mucho! Ad",
|
|
508
|
-
|
|
509
|
-
beam_search_max=4,
|
|
558
|
+
config=config_narrowest,
|
|
510
559
|
word_end_symbols=[" ", ".", ",", "!", "?"],
|
|
511
560
|
)
|
|
512
561
|
check_match(predictions, references)
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def test_score_item(shared_lm):
|
|
565
|
+
"""
|
|
566
|
+
Test computing the log prob (base e) for a single sentence
|
|
567
|
+
"""
|
|
568
|
+
score = shared_lm.score_item(STR_PEOPLE_SHOCKED)
|
|
569
|
+
assert score == pytest.approx(LOG_PROB_BYTE_PEOPLE_SHOCKED, abs=EPSILON_BYTE_LOG_PROB)
|
|
570
|
+
|
|
571
|
+
|
|
572
|
+
def test_score_item_and_extension(shared_lm):
|
|
573
|
+
"""
|
|
574
|
+
Score sentence and then again with additional character on end
|
|
575
|
+
Verify that this matches adding prediction of that single character
|
|
576
|
+
"""
|
|
577
|
+
add_char = " "
|
|
578
|
+
score = shared_lm.score_item(STR_PEOPLE_SHOCKED)
|
|
579
|
+
score_plus_space = shared_lm.score_item(STR_PEOPLE_SHOCKED + add_char)
|
|
580
|
+
assert score > score_plus_space
|
|
581
|
+
char_to_log_prob = dict(
|
|
582
|
+
shared_lm.predict(evidence=list(STR_PEOPLE_SHOCKED), unnormalized_logprobs=True)
|
|
583
|
+
)
|
|
584
|
+
score_plus_predict = score + char_to_log_prob[add_char]
|
|
585
|
+
assert score_plus_predict == pytest.approx(score_plus_space, abs=EPSILON_BYTE_LOG_PROB)
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def test_score_items_identical(shared_lm):
|
|
589
|
+
"""
|
|
590
|
+
Test computing the log prob (base e) for multiple identical sentences
|
|
591
|
+
Do inference in one big batch
|
|
592
|
+
"""
|
|
593
|
+
num_repeats = 7
|
|
594
|
+
items = [STR_PEOPLE_SHOCKED] * num_repeats
|
|
595
|
+
scores = shared_lm.score_items(items=items, batch_size=None)
|
|
596
|
+
assert len(scores) == num_repeats
|
|
597
|
+
for score in scores:
|
|
598
|
+
assert score == pytest.approx(LOG_PROB_BYTE_PEOPLE_SHOCKED, abs=EPSILON_BYTE_LOG_PROB)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def test_score_items_identical_batch_size1(shared_lm):
|
|
602
|
+
"""
|
|
603
|
+
Test computing the log prob (base e) for multiple identical sentences
|
|
604
|
+
Do inference one item at a time
|
|
605
|
+
"""
|
|
606
|
+
num_repeats = 7
|
|
607
|
+
items = [STR_PEOPLE_SHOCKED] * num_repeats
|
|
608
|
+
scores = shared_lm.score_items(items=items, batch_size=1)
|
|
609
|
+
assert len(scores) == num_repeats
|
|
610
|
+
for score in scores:
|
|
611
|
+
assert score == pytest.approx(LOG_PROB_BYTE_PEOPLE_SHOCKED, abs=EPSILON_BYTE_LOG_PROB)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
def test_score_items_identical_batch_size2(shared_lm):
|
|
615
|
+
"""
|
|
616
|
+
Test computing the log prob (base e) for multiple identical sentences
|
|
617
|
+
Do inference one item at a time
|
|
618
|
+
"""
|
|
619
|
+
num_repeats = 7
|
|
620
|
+
items = [STR_PEOPLE_SHOCKED] * num_repeats
|
|
621
|
+
scores = shared_lm.score_items(items=items, batch_size=2)
|
|
622
|
+
assert len(scores) == num_repeats
|
|
623
|
+
for score in scores:
|
|
624
|
+
assert score == pytest.approx(LOG_PROB_BYTE_PEOPLE_SHOCKED, abs=EPSILON_BYTE_LOG_PROB)
|
|
625
|
+
|
|
626
|
+
|
|
627
|
+
def test_score_items_alternating(shared_lm):
|
|
628
|
+
"""
|
|
629
|
+
Test computing the log prob (base e) for multiple alternating sentences
|
|
630
|
+
"""
|
|
631
|
+
# Create a list with alternating sentences with known log probs
|
|
632
|
+
num_repeats = 7
|
|
633
|
+
items = []
|
|
634
|
+
for _ in range(num_repeats):
|
|
635
|
+
items.append(STR_PEOPLE_SHOCKED)
|
|
636
|
+
items.append(STR_LOWERCASE_LETTERS_SPACE)
|
|
637
|
+
scores = shared_lm.score_items(items=items)
|
|
638
|
+
|
|
639
|
+
for i in range(len(items)):
|
|
640
|
+
if i % 2 == 0:
|
|
641
|
+
assert scores[i] == pytest.approx(
|
|
642
|
+
LOG_PROB_BYTE_PEOPLE_SHOCKED, abs=EPSILON_BYTE_LOG_PROB
|
|
643
|
+
)
|
|
644
|
+
else:
|
|
645
|
+
assert scores[i] == pytest.approx(
|
|
646
|
+
LOG_PROB_BYTE_LOWERCASE_LETTERS_SPACE, abs=EPSILON_BYTE_LOG_PROB
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def test_score_items_empty(shared_lm):
|
|
651
|
+
"""
|
|
652
|
+
Test asking for scoring of empty list
|
|
653
|
+
"""
|
|
654
|
+
scores = shared_lm.score_items(items=[])
|
|
655
|
+
assert len(scores) == 0
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
def test_score_items_order(shared_lm):
|
|
659
|
+
"""
|
|
660
|
+
Score sentences that have an obvious order of probability
|
|
661
|
+
"""
|
|
662
|
+
items = [
|
|
663
|
+
"The United States of America",
|
|
664
|
+
"the united states of america",
|
|
665
|
+
"The United State of America",
|
|
666
|
+
"The United State of Alabama",
|
|
667
|
+
"The United State of Amerika",
|
|
668
|
+
"The United State of_Amerika",
|
|
669
|
+
]
|
|
670
|
+
scores = shared_lm.score_items(items=items)
|
|
671
|
+
print(scores)
|
|
672
|
+
for i in range(1, len(items)):
|
|
673
|
+
assert scores[i - 1] > scores[i]
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
@pytest.mark.long
|
|
677
|
+
def test_score_items_comm2(shared_lm, rewrite_ref_files):
|
|
678
|
+
"""
|
|
679
|
+
Compute the log prob of each sentence in the COMM2 test set
|
|
680
|
+
We filter to sentences without numbers, lowercase, and strip symbols other than apostrophe
|
|
681
|
+
Compare against log probs obtained from a previous run of the model
|
|
682
|
+
"""
|
|
683
|
+
phrases = load_phrases_plaintext(filename=COMM2_PHRASES, drop_first_col=True)
|
|
684
|
+
phrases = filter_phrases(phrases=phrases, drop_numbers=True)
|
|
685
|
+
phrases, _ = normalize_phrases(phrases=phrases, lower=True, strip_symbols=True)
|
|
686
|
+
scores = shared_lm.score_items(items=phrases, batch_size=32)
|
|
687
|
+
if rewrite_ref_files:
|
|
688
|
+
print(f"Rewriting log probs to: {COMM2_BYTE_LOG_PROBS}")
|
|
689
|
+
with open(COMM2_BYTE_LOG_PROBS, "w") as f:
|
|
690
|
+
for lp in scores:
|
|
691
|
+
f.write(f"{lp}\n")
|
|
692
|
+
known_log_probs = read_float_list(filename=COMM2_BYTE_LOG_PROBS)
|
|
693
|
+
assert len(scores) == len(known_log_probs)
|
|
694
|
+
for i in range(len(scores)):
|
|
695
|
+
# Note: we need a bit larger epsilon to pass on both CPU and CUDA devices
|
|
696
|
+
assert scores[i] == pytest.approx(known_log_probs[i], abs=2.0)
|
|
@@ -67,9 +67,7 @@ class TestCausalSubwordLocal(unittest.TestCase):
|
|
|
67
67
|
"Distribution should not be uniform even without context.",
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
-
self.assertAlmostEqual(
|
|
71
|
-
sum(probs), 1, places=3, msg="Probabilities should sum to 1."
|
|
72
|
-
)
|
|
70
|
+
self.assertAlmostEqual(sum(probs), 1, places=3, msg="Probabilities should sum to 1.")
|
|
73
71
|
|
|
74
72
|
def test_predict_middle_of_word(self):
|
|
75
73
|
symbol_probs = self.lm.predict(evidence=list("This is a tes"))
|
|
@@ -77,9 +75,7 @@ class TestCausalSubwordLocal(unittest.TestCase):
|
|
|
77
75
|
|
|
78
76
|
self.assertTrue(len(set(probs)) > 1, "Distribution should not be uniform.")
|
|
79
77
|
|
|
80
|
-
self.assertAlmostEqual(
|
|
81
|
-
sum(probs), 1, places=3, msg="Probabilities should sum to 1."
|
|
82
|
-
)
|
|
78
|
+
self.assertAlmostEqual(sum(probs), 1, places=3, msg="Probabilities should sum to 1.")
|
|
83
79
|
|
|
84
80
|
most_likely_sym, most_likely_prob = symbol_probs[0]
|
|
85
81
|
self.assertAlmostEqual(
|
|
@@ -97,15 +93,11 @@ class TestCausalSubwordLocal(unittest.TestCase):
|
|
|
97
93
|
|
|
98
94
|
"""Word Prediction Tests"""
|
|
99
95
|
|
|
100
|
-
@pytest.mark.hf
|
|
101
96
|
def test_local_model_predict_words_small_beam(self):
|
|
102
97
|
|
|
103
98
|
# Small, fast query: we only assert that we get at least one completion.
|
|
104
99
|
words = self.lm.predict_words(
|
|
105
100
|
"the ",
|
|
106
|
-
nbest=3,
|
|
107
|
-
max_word_len=12,
|
|
108
|
-
max_word_hypotheses=64,
|
|
109
101
|
return_log_probs=False,
|
|
110
102
|
)
|
|
111
103
|
assert isinstance(words, list) and len(words) > 0
|
|
@@ -22,7 +22,5 @@ def test_load_predict_word():
|
|
|
22
22
|
device=Device.CPU,
|
|
23
23
|
precision=Precision.FP32,
|
|
24
24
|
)
|
|
25
|
-
words = lm.predict_words(
|
|
26
|
-
"a ", nbest=2, max_word_len=10, max_word_hypotheses=32, return_log_probs=False
|
|
27
|
-
)
|
|
25
|
+
words = lm.predict_words("a ", nbest=2, return_log_probs=False)
|
|
28
26
|
assert isinstance(words, list) and len(words) > 0
|