2019-10-31 17:01:15 +03:00
|
|
|
import pytest
|
|
|
|
|
2020-02-27 20:42:27 +03:00
|
|
|
from spacy.ml.models.tok2vec import build_Tok2Vec_model
|
2020-07-29 14:47:37 +03:00
|
|
|
from spacy.ml.models.tok2vec import MultiHashEmbed, CharacterEmbed
|
|
|
|
from spacy.ml.models.tok2vec import MishWindowEncoder, MaxoutWindowEncoder
|
2020-08-31 13:41:39 +03:00
|
|
|
from spacy.pipeline.tok2vec import Tok2Vec, Tok2VecListener
|
2019-10-31 17:01:15 +03:00
|
|
|
from spacy.vocab import Vocab
|
|
|
|
from spacy.tokens import Doc
|
2020-09-09 11:31:03 +03:00
|
|
|
from spacy.training import Example
|
2020-08-31 13:41:39 +03:00
|
|
|
from spacy import util
|
|
|
|
from spacy.lang.en import English
|
2020-09-15 22:40:38 +03:00
|
|
|
from ..util import get_batch
|
2019-10-31 17:01:15 +03:00
|
|
|
|
2020-08-31 13:41:39 +03:00
|
|
|
from thinc.api import Config
|
|
|
|
|
|
|
|
from numpy.testing import assert_equal
|
|
|
|
|
2019-10-31 17:01:15 +03:00
|
|
|
|
|
|
|
def test_empty_doc():
|
|
|
|
width = 128
|
|
|
|
embed_size = 2000
|
|
|
|
vocab = Vocab()
|
|
|
|
doc = Doc(vocab, words=[])
|
2020-07-20 15:49:54 +03:00
|
|
|
tok2vec = build_Tok2Vec_model(
|
2020-07-29 00:06:46 +03:00
|
|
|
MultiHashEmbed(
|
|
|
|
width=width,
|
2020-10-05 20:57:45 +03:00
|
|
|
rows=[embed_size, embed_size, embed_size, embed_size],
|
2020-10-05 16:24:33 +03:00
|
|
|
include_static_vectors=False,
|
|
|
|
attrs=["NORM", "PREFIX", "SUFFIX", "SHAPE"],
|
2020-07-29 00:06:46 +03:00
|
|
|
),
|
2020-08-05 17:00:59 +03:00
|
|
|
MaxoutWindowEncoder(width=width, depth=4, window_size=1, maxout_pieces=3),
|
2020-07-20 15:49:54 +03:00
|
|
|
)
|
|
|
|
tok2vec.initialize()
|
2019-10-31 17:01:15 +03:00
|
|
|
vectors, backprop = tok2vec.begin_update([doc])
|
|
|
|
assert len(vectors) == 1
|
|
|
|
assert vectors[0].shape == (0, width)
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
|
|
"batch_size,width,embed_size", [[1, 128, 2000], [2, 128, 2000], [3, 8, 63]]
|
|
|
|
)
|
|
|
|
def test_tok2vec_batch_sizes(batch_size, width, embed_size):
|
|
|
|
batch = get_batch(batch_size)
|
2020-02-27 20:42:27 +03:00
|
|
|
tok2vec = build_Tok2Vec_model(
|
2020-07-29 00:06:46 +03:00
|
|
|
MultiHashEmbed(
|
|
|
|
width=width,
|
2020-10-05 20:57:45 +03:00
|
|
|
rows=[embed_size] * 4,
|
2020-10-05 16:27:06 +03:00
|
|
|
include_static_vectors=False,
|
|
|
|
attrs=["NORM", "PREFIX", "SUFFIX", "SHAPE"],
|
2020-07-29 00:06:46 +03:00
|
|
|
),
|
2020-08-31 13:41:39 +03:00
|
|
|
MaxoutWindowEncoder(width=width, depth=4, window_size=1, maxout_pieces=3),
|
2020-02-27 20:42:27 +03:00
|
|
|
)
|
2020-01-29 19:06:46 +03:00
|
|
|
tok2vec.initialize()
|
2019-10-31 17:01:15 +03:00
|
|
|
vectors, backprop = tok2vec.begin_update(batch)
|
|
|
|
assert len(vectors) == len(batch)
|
|
|
|
for doc_vec, doc in zip(vectors, batch):
|
|
|
|
assert doc_vec.shape == (len(doc), width)
|
|
|
|
|
|
|
|
|
2020-02-27 20:42:27 +03:00
|
|
|
# fmt: off
|
2019-10-31 17:01:15 +03:00
|
|
|
@pytest.mark.parametrize(
|
2020-07-29 14:47:37 +03:00
|
|
|
"width,embed_arch,embed_config,encode_arch,encode_config",
|
2019-10-31 17:01:15 +03:00
|
|
|
[
|
2020-10-05 20:57:45 +03:00
|
|
|
(8, MultiHashEmbed, {"rows": [100, 100], "attrs": ["SHAPE", "LOWER"], "include_static_vectors": False}, MaxoutWindowEncoder, {"window_size": 1, "maxout_pieces": 3, "depth": 2}),
|
|
|
|
(8, MultiHashEmbed, {"rows": [100, 20], "attrs": ["ORTH", "PREFIX"], "include_static_vectors": False}, MishWindowEncoder, {"window_size": 1, "depth": 6}),
|
2020-10-09 12:54:48 +03:00
|
|
|
(8, CharacterEmbed, {"rows": 100, "nM": 64, "nC": 8, "include_static_vectors": False}, MaxoutWindowEncoder, {"window_size": 1, "maxout_pieces": 3, "depth": 3}),
|
|
|
|
(8, CharacterEmbed, {"rows": 100, "nM": 16, "nC": 2, "include_static_vectors": False}, MishWindowEncoder, {"window_size": 1, "depth": 3}),
|
2019-10-31 17:01:15 +03:00
|
|
|
],
|
|
|
|
)
|
2020-02-27 20:42:27 +03:00
|
|
|
# fmt: on
|
2020-07-29 14:47:37 +03:00
|
|
|
def test_tok2vec_configs(width, embed_arch, embed_config, encode_arch, encode_config):
|
|
|
|
embed_config["width"] = width
|
|
|
|
encode_config["width"] = width
|
2019-10-31 17:01:15 +03:00
|
|
|
docs = get_batch(3)
|
2020-07-29 14:47:37 +03:00
|
|
|
tok2vec = build_Tok2Vec_model(
|
2020-09-29 22:39:28 +03:00
|
|
|
embed_arch(**embed_config), encode_arch(**encode_config)
|
2020-07-29 14:47:37 +03:00
|
|
|
)
|
2020-03-29 20:40:36 +03:00
|
|
|
tok2vec.initialize(docs)
|
2019-10-31 17:01:15 +03:00
|
|
|
vectors, backprop = tok2vec.begin_update(docs)
|
|
|
|
assert len(vectors) == len(docs)
|
2020-07-29 14:47:37 +03:00
|
|
|
assert vectors[0].shape == (len(docs[0]), width)
|
2019-10-31 17:01:15 +03:00
|
|
|
backprop(vectors)
|
2020-08-31 13:41:39 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_init_tok2vec():
|
|
|
|
# Simple test to initialize the default tok2vec
|
|
|
|
nlp = English()
|
|
|
|
tok2vec = nlp.add_pipe("tok2vec")
|
|
|
|
assert tok2vec.listeners == []
|
2020-09-28 22:35:09 +03:00
|
|
|
nlp.initialize()
|
2020-09-08 23:44:25 +03:00
|
|
|
assert tok2vec.model.get_dim("nO")
|
2020-08-31 13:41:39 +03:00
|
|
|
|
|
|
|
|
|
|
|
cfg_string = """
|
|
|
|
[nlp]
|
|
|
|
lang = "en"
|
|
|
|
pipeline = ["tok2vec","tagger"]
|
|
|
|
|
|
|
|
[components]
|
|
|
|
|
|
|
|
[components.tagger]
|
|
|
|
factory = "tagger"
|
|
|
|
|
|
|
|
[components.tagger.model]
|
|
|
|
@architectures = "spacy.Tagger.v1"
|
|
|
|
nO = null
|
|
|
|
|
|
|
|
[components.tagger.model.tok2vec]
|
|
|
|
@architectures = "spacy.Tok2VecListener.v1"
|
|
|
|
width = ${components.tok2vec.model.encode.width}
|
|
|
|
|
|
|
|
[components.tok2vec]
|
|
|
|
factory = "tok2vec"
|
|
|
|
|
|
|
|
[components.tok2vec.model]
|
2021-01-07 08:39:27 +03:00
|
|
|
@architectures = "spacy.Tok2Vec.v2"
|
2020-08-31 13:41:39 +03:00
|
|
|
|
|
|
|
[components.tok2vec.model.embed]
|
2020-10-05 20:59:30 +03:00
|
|
|
@architectures = "spacy.MultiHashEmbed.v1"
|
2020-08-31 13:41:39 +03:00
|
|
|
width = ${components.tok2vec.model.encode.width}
|
2020-10-05 20:57:45 +03:00
|
|
|
rows = [2000, 1000, 1000, 1000]
|
|
|
|
attrs = ["NORM", "PREFIX", "SUFFIX", "SHAPE"]
|
|
|
|
include_static_vectors = false
|
2020-08-31 13:41:39 +03:00
|
|
|
|
|
|
|
[components.tok2vec.model.encode]
|
2021-01-07 08:39:27 +03:00
|
|
|
@architectures = "spacy.MaxoutWindowEncoder.v2"
|
2020-08-31 13:41:39 +03:00
|
|
|
width = 96
|
|
|
|
depth = 4
|
|
|
|
window_size = 1
|
|
|
|
maxout_pieces = 3
|
|
|
|
"""
|
|
|
|
|
|
|
|
TRAIN_DATA = [
|
|
|
|
("I like green eggs", {"tags": ["N", "V", "J", "N"]}),
|
|
|
|
("Eat blue ham", {"tags": ["V", "J", "N"]}),
|
|
|
|
]
|
|
|
|
|
2020-09-04 14:42:33 +03:00
|
|
|
|
2020-08-31 13:41:39 +03:00
|
|
|
def test_tok2vec_listener():
|
|
|
|
orig_config = Config().from_str(cfg_string)
|
2020-09-27 23:21:31 +03:00
|
|
|
nlp = util.load_model_from_config(orig_config, auto_fill=True, validate=True)
|
2020-08-31 13:41:39 +03:00
|
|
|
assert nlp.pipe_names == ["tok2vec", "tagger"]
|
|
|
|
tagger = nlp.get_pipe("tagger")
|
|
|
|
tok2vec = nlp.get_pipe("tok2vec")
|
|
|
|
tagger_tok2vec = tagger.model.get_ref("tok2vec")
|
|
|
|
assert isinstance(tok2vec, Tok2Vec)
|
|
|
|
assert isinstance(tagger_tok2vec, Tok2VecListener)
|
|
|
|
train_examples = []
|
|
|
|
for t in TRAIN_DATA:
|
|
|
|
train_examples.append(Example.from_dict(nlp.make_doc(t[0]), t[1]))
|
|
|
|
for tag in t[1]["tags"]:
|
|
|
|
tagger.add_label(tag)
|
|
|
|
|
|
|
|
# Check that the Tok2Vec component finds it listeners
|
|
|
|
assert tok2vec.listeners == []
|
2020-09-28 22:35:09 +03:00
|
|
|
optimizer = nlp.initialize(lambda: train_examples)
|
2020-08-31 13:41:39 +03:00
|
|
|
assert tok2vec.listeners == [tagger_tok2vec]
|
|
|
|
|
|
|
|
for i in range(5):
|
|
|
|
losses = {}
|
|
|
|
nlp.update(train_examples, sgd=optimizer, losses=losses)
|
|
|
|
|
|
|
|
doc = nlp("Running the pipeline as a whole.")
|
|
|
|
doc_tensor = tagger_tok2vec.predict([doc])[0]
|
|
|
|
assert_equal(doc.tensor, doc_tensor)
|
|
|
|
|
|
|
|
# TODO: should this warn or error?
|
|
|
|
nlp.select_pipes(disable="tok2vec")
|
|
|
|
assert nlp.pipe_names == ["tagger"]
|
|
|
|
nlp("Running the pipeline with the Tok2Vec component disabled.")
|
2020-09-22 14:54:44 +03:00
|
|
|
|
|
|
|
|
|
|
|
def test_tok2vec_listener_callback():
|
|
|
|
orig_config = Config().from_str(cfg_string)
|
2020-09-27 23:21:31 +03:00
|
|
|
nlp = util.load_model_from_config(orig_config, auto_fill=True, validate=True)
|
2020-09-22 14:54:44 +03:00
|
|
|
assert nlp.pipe_names == ["tok2vec", "tagger"]
|
|
|
|
tagger = nlp.get_pipe("tagger")
|
|
|
|
tok2vec = nlp.get_pipe("tok2vec")
|
|
|
|
nlp._link_components()
|
|
|
|
docs = [nlp.make_doc("A random sentence")]
|
|
|
|
tok2vec.model.initialize(X=docs)
|
|
|
|
gold_array = [[1.0 for tag in ["V", "Z"]] for word in docs]
|
|
|
|
label_sample = [tagger.model.ops.asarray(gold_array, dtype="float32")]
|
|
|
|
tagger.model.initialize(X=docs, Y=label_sample)
|
|
|
|
docs = [nlp.make_doc("Another entirely random sentence")]
|
2020-09-22 22:54:52 +03:00
|
|
|
tok2vec.update([Example.from_dict(x, {}) for x in docs])
|
2020-09-22 14:54:44 +03:00
|
|
|
Y, get_dX = tagger.model.begin_update(docs)
|
|
|
|
# assure that the backprop call works (and doesn't hit a 'None' callback)
|
|
|
|
assert get_dX(Y) is not None
|