2017-04-15 10:05:47 +00:00
|
|
|
# coding: utf8
|
|
|
|
from __future__ import absolute_import, unicode_literals
|
2016-10-09 10:24:24 +00:00
|
|
|
from contextlib import contextmanager
|
2017-05-17 10:04:50 +00:00
|
|
|
import dill
|
2015-08-27 07:16:11 +00:00
|
|
|
|
2017-05-18 09:25:19 +00:00
|
|
|
import numpy
|
|
|
|
from thinc.neural import Model
|
|
|
|
from thinc.neural.ops import NumpyOps, CupyOps
|
2017-05-25 01:10:54 +00:00
|
|
|
from thinc.neural.optimizers import Adam, SGD
|
|
|
|
import random
|
2017-05-29 11:42:55 +00:00
|
|
|
import ujson
|
2017-05-29 13:40:45 +00:00
|
|
|
from collections import OrderedDict
|
2017-05-18 09:25:19 +00:00
|
|
|
|
2015-08-26 17:16:09 +00:00
|
|
|
from .tokenizer import Tokenizer
|
|
|
|
from .vocab import Vocab
|
|
|
|
from .tagger import Tagger
|
2016-09-25 13:37:33 +00:00
|
|
|
from .lemmatizer import Lemmatizer
|
2016-09-26 09:57:54 +00:00
|
|
|
from .syntax.parser import get_templates
|
2017-05-22 10:14:59 +00:00
|
|
|
from .syntax import nonproj
|
2017-05-16 14:17:30 +00:00
|
|
|
from .pipeline import NeuralDependencyParser, EntityRecognizer
|
|
|
|
from .pipeline import TokenVectorEncoder, NeuralTagger, NeuralEntityRecognizer
|
2017-05-21 22:52:30 +00:00
|
|
|
from .pipeline import NeuralLabeller
|
2017-04-16 23:46:14 +00:00
|
|
|
from .compat import json_dumps
|
2017-04-15 10:05:47 +00:00
|
|
|
from .attrs import IS_STOP
|
2017-05-08 21:58:31 +00:00
|
|
|
from .lang.punctuation import TOKENIZER_PREFIXES, TOKENIZER_SUFFIXES, TOKENIZER_INFIXES
|
|
|
|
from .lang.tokenizer_exceptions import TOKEN_MATCH
|
|
|
|
from .lang.tag_map import TAG_MAP
|
2017-05-08 22:58:10 +00:00
|
|
|
from .lang.lex_attrs import LEX_ATTRS
|
2017-04-15 10:05:47 +00:00
|
|
|
from . import util
|
2017-05-21 14:07:06 +00:00
|
|
|
from .scorer import Scorer
|
2016-10-09 10:24:24 +00:00
|
|
|
|
2015-08-27 07:16:11 +00:00
|
|
|
|
2016-09-24 18:26:17 +00:00
|
|
|
class BaseDefaults(object):
|
2016-10-18 14:18:25 +00:00
|
|
|
@classmethod
|
|
|
|
def create_lemmatizer(cls, nlp=None):
|
2017-03-15 09:52:50 +00:00
|
|
|
return Lemmatizer(cls.lemma_index, cls.lemma_exc, cls.lemma_rules)
|
2016-10-18 14:18:25 +00:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create_vocab(cls, nlp=None):
|
|
|
|
lemmatizer = cls.create_lemmatizer(nlp)
|
2017-05-16 09:21:59 +00:00
|
|
|
lex_attr_getters = dict(cls.lex_attr_getters)
|
|
|
|
# This is messy, but it's the minimal working fix to Issue #639.
|
|
|
|
lex_attr_getters[IS_STOP] = lambda string: string.lower() in cls.stop_words
|
|
|
|
vocab = Vocab(lex_attr_getters=lex_attr_getters, tag_map=cls.tag_map,
|
|
|
|
lemmatizer=lemmatizer)
|
2017-03-15 14:24:40 +00:00
|
|
|
for tag_str, exc in cls.morph_rules.items():
|
|
|
|
for orth_str, attrs in exc.items():
|
|
|
|
vocab.morphology.add_special_case(tag_str, orth_str, attrs)
|
|
|
|
return vocab
|
2016-12-18 15:54:52 +00:00
|
|
|
|
2016-10-18 14:18:25 +00:00
|
|
|
@classmethod
|
|
|
|
def create_tokenizer(cls, nlp=None):
|
|
|
|
rules = cls.tokenizer_exceptions
|
2017-05-16 09:21:59 +00:00
|
|
|
token_match = cls.token_match
|
|
|
|
prefix_search = util.compile_prefix_regex(cls.prefixes).search \
|
|
|
|
if cls.prefixes else None
|
|
|
|
suffix_search = util.compile_suffix_regex(cls.suffixes).search \
|
|
|
|
if cls.suffixes else None
|
|
|
|
infix_finditer = util.compile_infix_regex(cls.infixes).finditer \
|
|
|
|
if cls.infixes else None
|
2016-10-18 14:18:25 +00:00
|
|
|
vocab = nlp.vocab if nlp is not None else cls.create_vocab(nlp)
|
2016-11-26 11:36:04 +00:00
|
|
|
return Tokenizer(vocab, rules=rules,
|
2016-10-18 14:18:25 +00:00
|
|
|
prefix_search=prefix_search, suffix_search=suffix_search,
|
2017-01-03 17:17:57 +00:00
|
|
|
infix_finditer=infix_finditer, token_match=token_match)
|
2016-09-24 12:08:53 +00:00
|
|
|
|
2017-05-16 14:17:30 +00:00
|
|
|
@classmethod
|
|
|
|
def create_tagger(cls, nlp=None, **cfg):
|
|
|
|
if nlp is None:
|
|
|
|
return NeuralTagger(cls.create_vocab(nlp), **cfg)
|
|
|
|
else:
|
|
|
|
return NeuralTagger(nlp.vocab, **cfg)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create_parser(cls, nlp=None, **cfg):
|
|
|
|
if nlp is None:
|
|
|
|
return NeuralDependencyParser(cls.create_vocab(nlp), **cfg)
|
|
|
|
else:
|
|
|
|
return NeuralDependencyParser(nlp.vocab, **cfg)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def create_entity(cls, nlp=None, **cfg):
|
|
|
|
if nlp is None:
|
|
|
|
return NeuralEntityRecognizer(cls.create_vocab(nlp), **cfg)
|
|
|
|
else:
|
|
|
|
return NeuralEntityRecognizer(nlp.vocab, **cfg)
|
|
|
|
|
2016-10-18 14:18:25 +00:00
|
|
|
@classmethod
|
2017-05-16 09:21:59 +00:00
|
|
|
def create_pipeline(cls, nlp=None):
|
|
|
|
meta = nlp.meta if nlp is not None else {}
|
|
|
|
# Resolve strings, like "cnn", "lstm", etc
|
2016-10-14 15:38:29 +00:00
|
|
|
pipeline = []
|
2017-05-16 09:21:59 +00:00
|
|
|
for entry in cls.pipeline:
|
|
|
|
factory = cls.Defaults.factories[entry]
|
2017-05-16 14:17:30 +00:00
|
|
|
pipeline.append(factory(nlp, **meta.get(entry, {})))
|
2016-10-09 10:24:24 +00:00
|
|
|
return pipeline
|
|
|
|
|
2017-05-16 09:21:59 +00:00
|
|
|
factories = {
|
|
|
|
'make_doc': create_tokenizer,
|
2017-05-31 11:42:39 +00:00
|
|
|
'tensorizer': lambda nlp, **cfg: [TokenVectorEncoder(nlp.vocab, **cfg)],
|
|
|
|
'tagger': lambda nlp, **cfg: [NeuralTagger(nlp.vocab, **cfg)],
|
|
|
|
'parser': lambda nlp, **cfg: [
|
|
|
|
NeuralDependencyParser(nlp.vocab, **cfg),
|
|
|
|
nonproj.deprojectivize],
|
|
|
|
'ner': lambda nlp, **cfg: [NeuralEntityRecognizer(nlp.vocab, **cfg)],
|
|
|
|
# Temporary compatibility -- delete after pivot
|
2017-05-21 23:43:31 +00:00
|
|
|
'token_vectors': lambda nlp, **cfg: [TokenVectorEncoder(nlp.vocab, **cfg)],
|
|
|
|
'tags': lambda nlp, **cfg: [NeuralTagger(nlp.vocab, **cfg)],
|
|
|
|
'dependencies': lambda nlp, **cfg: [
|
|
|
|
NeuralDependencyParser(nlp.vocab, **cfg),
|
2017-05-22 09:48:02 +00:00
|
|
|
nonproj.deprojectivize],
|
2017-05-21 23:43:31 +00:00
|
|
|
'entities': lambda nlp, **cfg: [NeuralEntityRecognizer(nlp.vocab, **cfg)],
|
2017-05-16 09:21:59 +00:00
|
|
|
}
|
|
|
|
|
2017-05-08 21:58:31 +00:00
|
|
|
token_match = TOKEN_MATCH
|
|
|
|
prefixes = tuple(TOKENIZER_PREFIXES)
|
|
|
|
suffixes = tuple(TOKENIZER_SUFFIXES)
|
|
|
|
infixes = tuple(TOKENIZER_INFIXES)
|
|
|
|
tag_map = dict(TAG_MAP)
|
2016-10-09 10:24:24 +00:00
|
|
|
tokenizer_exceptions = {}
|
2016-09-26 09:57:54 +00:00
|
|
|
parser_features = get_templates('parser')
|
|
|
|
entity_features = get_templates('ner')
|
2016-10-18 14:18:25 +00:00
|
|
|
tagger_features = Tagger.feature_templates # TODO -- fix this
|
2016-09-24 18:26:17 +00:00
|
|
|
stop_words = set()
|
2016-12-18 14:50:09 +00:00
|
|
|
lemma_rules = {}
|
2017-03-15 09:52:50 +00:00
|
|
|
lemma_exc = {}
|
|
|
|
lemma_index = {}
|
2017-03-15 14:24:40 +00:00
|
|
|
morph_rules = {}
|
2017-05-08 22:58:10 +00:00
|
|
|
lex_attr_getters = LEX_ATTRS
|
2015-09-14 07:48:51 +00:00
|
|
|
|
2015-08-26 17:16:09 +00:00
|
|
|
|
2016-09-24 12:08:53 +00:00
|
|
|
class Language(object):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""A text-processing pipeline. Usually you'll load this once per process,
|
|
|
|
and pass the instance around your application.
|
2017-05-19 16:47:24 +00:00
|
|
|
|
|
|
|
Defaults (class): Settings, data and factory methods for creating the `nlp`
|
|
|
|
object and processing pipeline.
|
|
|
|
lang (unicode): Two-letter language ID, i.e. ISO code.
|
2017-04-15 09:59:21 +00:00
|
|
|
"""
|
2016-09-24 18:26:17 +00:00
|
|
|
Defaults = BaseDefaults
|
2016-09-24 12:08:53 +00:00
|
|
|
lang = None
|
2015-08-25 13:37:17 +00:00
|
|
|
|
2017-05-29 18:43:48 +00:00
|
|
|
def __init__(self, vocab=True, make_doc=True, pipeline=None, meta={}, **kwargs):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Initialise a Language object.
|
|
|
|
|
|
|
|
vocab (Vocab): A `Vocab` object. If `True`, a vocab is created via
|
|
|
|
`Language.Defaults.create_vocab`.
|
2017-05-21 11:17:40 +00:00
|
|
|
make_doc (callable): A function that takes text and returns a `Doc`
|
2017-05-18 21:57:38 +00:00
|
|
|
object. Usually a `Tokenizer`.
|
|
|
|
pipeline (list): A list of annotation processes or IDs of annotation,
|
|
|
|
processes, e.g. a `Tagger` object, or `'tagger'`. IDs are looked
|
|
|
|
up in `Language.Defaults.factories`.
|
|
|
|
meta (dict): Custom meta data for the Language class. Is written to by
|
|
|
|
models to add model meta data.
|
|
|
|
RETURNS (Language): The newly constructed object.
|
|
|
|
"""
|
2017-05-16 09:21:59 +00:00
|
|
|
self.meta = dict(meta)
|
|
|
|
|
|
|
|
if vocab is True:
|
|
|
|
factory = self.Defaults.create_vocab
|
|
|
|
vocab = factory(self, **meta.get('vocab', {}))
|
|
|
|
self.vocab = vocab
|
|
|
|
if make_doc is True:
|
|
|
|
factory = self.Defaults.create_tokenizer
|
|
|
|
make_doc = factory(self, **meta.get('tokenizer', {}))
|
2017-05-29 13:40:45 +00:00
|
|
|
self.tokenizer = make_doc
|
2017-05-16 09:21:59 +00:00
|
|
|
if pipeline is True:
|
|
|
|
self.pipeline = self.Defaults.create_pipeline(self)
|
|
|
|
elif pipeline:
|
|
|
|
self.pipeline = list(pipeline)
|
|
|
|
# Resolve strings, like "cnn", "lstm", etc
|
|
|
|
for i, entry in enumerate(self.pipeline):
|
|
|
|
if entry in self.Defaults.factories:
|
|
|
|
factory = self.Defaults.factories[entry]
|
|
|
|
self.pipeline[i] = factory(self, **meta.get(entry, {}))
|
2016-10-09 10:24:24 +00:00
|
|
|
else:
|
2017-05-16 09:21:59 +00:00
|
|
|
self.pipeline = []
|
2017-05-21 23:43:31 +00:00
|
|
|
flat_list = []
|
|
|
|
for pipe in self.pipeline:
|
|
|
|
if isinstance(pipe, list):
|
|
|
|
flat_list.extend(pipe)
|
|
|
|
else:
|
|
|
|
flat_list.append(pipe)
|
|
|
|
self.pipeline = flat_list
|
2015-10-12 08:33:11 +00:00
|
|
|
|
2017-05-26 10:33:54 +00:00
|
|
|
def __call__(self, text, disable=[]):
|
2017-05-21 18:46:23 +00:00
|
|
|
"""'Apply the pipeline to some text. The text can span multiple sentences,
|
2017-05-18 21:57:38 +00:00
|
|
|
and can contain arbtrary whitespace. Alignment into the original string
|
2015-08-25 13:37:17 +00:00
|
|
|
is preserved.
|
2016-12-18 15:54:52 +00:00
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
text (unicode): The text to be processed.
|
2017-05-26 10:33:54 +00:00
|
|
|
disable (list): Names of the pipeline components to disable.
|
2017-05-18 21:57:38 +00:00
|
|
|
RETURNS (Doc): A container for accessing the annotations.
|
2016-11-01 11:25:36 +00:00
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
EXAMPLE:
|
2016-11-01 11:25:36 +00:00
|
|
|
>>> tokens = nlp('An example sentence. Another example sentence.')
|
2017-05-18 21:57:38 +00:00
|
|
|
>>> tokens[0].text, tokens[0].head.tag_
|
2016-11-01 11:25:36 +00:00
|
|
|
('An', 'NN')
|
2015-08-25 13:37:17 +00:00
|
|
|
"""
|
2016-10-14 15:38:29 +00:00
|
|
|
doc = self.make_doc(text)
|
|
|
|
for proc in self.pipeline:
|
2017-05-16 09:21:59 +00:00
|
|
|
name = getattr(proc, 'name', None)
|
2017-05-26 10:33:54 +00:00
|
|
|
if name in disable:
|
2017-05-16 09:21:59 +00:00
|
|
|
continue
|
2017-05-28 13:11:58 +00:00
|
|
|
doc = proc(doc)
|
2016-05-17 14:55:42 +00:00
|
|
|
return doc
|
2015-08-25 13:37:17 +00:00
|
|
|
|
2017-05-29 13:40:45 +00:00
|
|
|
def make_doc(self, text):
|
|
|
|
return self.tokenizer(text)
|
|
|
|
|
2017-05-25 01:10:54 +00:00
|
|
|
def update(self, docs, golds, drop=0., sgd=None, losses=None):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Update the models in the pipeline.
|
|
|
|
|
|
|
|
docs (iterable): A batch of `Doc` objects.
|
|
|
|
golds (iterable): A batch of `GoldParse` objects.
|
|
|
|
drop (float): The droput rate.
|
2017-05-21 11:17:40 +00:00
|
|
|
sgd (callable): An optimizer.
|
2017-05-18 21:57:38 +00:00
|
|
|
RETURNS (dict): Results from the update.
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
>>> with nlp.begin_training(gold, use_gpu=True) as (trainer, optimizer):
|
|
|
|
>>> for epoch in trainer.epochs(gold):
|
|
|
|
>>> for docs, golds in epoch:
|
|
|
|
>>> state = nlp.update(docs, golds, sgd=optimizer)
|
|
|
|
"""
|
2017-05-19 18:25:42 +00:00
|
|
|
tok2vec = self.pipeline[0]
|
|
|
|
feats = tok2vec.doc2feats(docs)
|
2017-05-25 01:10:54 +00:00
|
|
|
grads = {}
|
|
|
|
def get_grads(W, dW, key=None):
|
|
|
|
grads[key] = (W, dW)
|
2017-05-27 23:32:21 +00:00
|
|
|
pipes = list(self.pipeline[1:])
|
|
|
|
random.shuffle(pipes)
|
|
|
|
for proc in pipes:
|
2017-05-21 23:43:31 +00:00
|
|
|
if not hasattr(proc, 'update'):
|
|
|
|
continue
|
2017-05-20 18:23:05 +00:00
|
|
|
tokvecses, bp_tokvecses = tok2vec.model.begin_update(feats, drop=drop)
|
2017-05-25 01:10:54 +00:00
|
|
|
d_tokvecses = proc.update((docs, tokvecses), golds,
|
2017-05-25 11:47:42 +00:00
|
|
|
drop=drop, sgd=get_grads, losses=losses)
|
2017-05-27 13:20:13 +00:00
|
|
|
if d_tokvecses is not None:
|
|
|
|
bp_tokvecses(d_tokvecses, sgd=sgd)
|
2017-05-25 01:10:54 +00:00
|
|
|
for key, (W, dW) in grads.items():
|
|
|
|
sgd(W, dW, key=key)
|
2017-05-23 09:29:10 +00:00
|
|
|
# Clear the tensor variable, to free GPU memory.
|
|
|
|
# If we don't do this, the memory leak gets pretty
|
|
|
|
# bad, because we may be holding part of a batch.
|
2017-05-21 14:07:06 +00:00
|
|
|
for doc in docs:
|
|
|
|
doc.tensor = None
|
2017-05-16 14:17:30 +00:00
|
|
|
|
2017-05-21 14:07:06 +00:00
|
|
|
def preprocess_gold(self, docs_golds):
|
2017-05-22 10:29:30 +00:00
|
|
|
"""Can be called before training to pre-process gold data. By default,
|
|
|
|
it handles nonprojectivity and adds missing tags to the tag map.
|
|
|
|
|
|
|
|
docs_golds (iterable): Tuples of `Doc` and `GoldParse` objects.
|
|
|
|
YIELDS (tuple): Tuples of preprocessed `Doc` and `GoldParse` objects.
|
|
|
|
"""
|
2017-05-21 14:07:06 +00:00
|
|
|
for proc in self.pipeline:
|
|
|
|
if hasattr(proc, 'preprocess_gold'):
|
|
|
|
docs_golds = proc.preprocess_gold(docs_golds)
|
|
|
|
for doc, gold in docs_golds:
|
|
|
|
yield doc, gold
|
|
|
|
|
2017-05-21 22:52:30 +00:00
|
|
|
def begin_training(self, get_gold_tuples, **cfg):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Allocate models, pre-process training data and acquire a trainer and
|
|
|
|
optimizer. Used as a contextmanager.
|
|
|
|
|
|
|
|
gold_tuples (iterable): Gold-standard training data.
|
|
|
|
**cfg: Config parameters.
|
|
|
|
YIELDS (tuple): A trainer and an optimizer.
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
>>> with nlp.begin_training(gold, use_gpu=True) as (trainer, optimizer):
|
|
|
|
>>> for epoch in trainer.epochs(gold):
|
|
|
|
>>> for docs, golds in epoch:
|
|
|
|
>>> state = nlp.update(docs, golds, sgd=optimizer)
|
|
|
|
"""
|
2017-05-21 22:52:30 +00:00
|
|
|
self.pipeline.append(NeuralLabeller(self.vocab))
|
2017-05-17 10:04:50 +00:00
|
|
|
# Populate vocab
|
2017-05-21 14:07:06 +00:00
|
|
|
for _, annots_brackets in get_gold_tuples():
|
2017-05-17 10:04:50 +00:00
|
|
|
for annots, _ in annots_brackets:
|
|
|
|
for word in annots[1]:
|
|
|
|
_ = self.vocab[word]
|
2017-05-16 09:21:59 +00:00
|
|
|
contexts = []
|
2017-05-18 09:25:19 +00:00
|
|
|
if cfg.get('use_gpu'):
|
|
|
|
Model.ops = CupyOps()
|
|
|
|
Model.Ops = CupyOps
|
2017-05-16 09:21:59 +00:00
|
|
|
for proc in self.pipeline:
|
|
|
|
if hasattr(proc, 'begin_training'):
|
2017-05-21 14:07:06 +00:00
|
|
|
context = proc.begin_training(get_gold_tuples(),
|
2017-05-17 10:04:50 +00:00
|
|
|
pipeline=self.pipeline)
|
2017-05-16 09:21:59 +00:00
|
|
|
contexts.append(context)
|
2017-05-25 16:19:26 +00:00
|
|
|
learn_rate = util.env_opt('learn_rate', 0.001)
|
|
|
|
beta1 = util.env_opt('optimizer_B1', 0.9)
|
|
|
|
beta2 = util.env_opt('optimizer_B2', 0.999)
|
|
|
|
eps = util.env_opt('optimizer_eps', 1e-08)
|
|
|
|
L2 = util.env_opt('L2_penalty', 1e-6)
|
|
|
|
max_grad_norm = util.env_opt('grad_norm_clip', 1.)
|
|
|
|
optimizer = Adam(Model.ops, learn_rate, L2=L2, beta1=beta1,
|
|
|
|
beta2=beta2, eps=eps)
|
|
|
|
optimizer.max_grad_norm = max_grad_norm
|
2017-05-21 14:07:06 +00:00
|
|
|
return optimizer
|
|
|
|
|
|
|
|
def evaluate(self, docs_golds):
|
|
|
|
docs, golds = zip(*docs_golds)
|
|
|
|
scorer = Scorer()
|
|
|
|
for doc, gold in zip(self.pipe(docs), golds):
|
|
|
|
scorer.score(doc, gold)
|
2017-05-21 18:38:46 +00:00
|
|
|
doc.tensor = None
|
2017-05-21 14:07:06 +00:00
|
|
|
return scorer
|
2017-05-16 09:21:59 +00:00
|
|
|
|
2017-05-18 09:25:19 +00:00
|
|
|
@contextmanager
|
|
|
|
def use_params(self, params, **cfg):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Replace weights of models in the pipeline with those provided in the
|
|
|
|
params dictionary. Can be used as a contextmanager, in which case,
|
|
|
|
models go back to their original weights after the block.
|
|
|
|
|
|
|
|
params (dict): A dictionary of parameters keyed by model ID.
|
|
|
|
**cfg: Config parameters.
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
>>> with nlp.use_params(optimizer.averages):
|
|
|
|
>>> nlp.to_disk('/tmp/checkpoint')
|
|
|
|
"""
|
2017-05-18 13:30:59 +00:00
|
|
|
contexts = [pipe.use_params(params) for pipe
|
|
|
|
in self.pipeline if hasattr(pipe, 'use_params')]
|
|
|
|
# TODO: Having trouble with contextlib
|
|
|
|
# Workaround: these aren't actually context managers atm.
|
|
|
|
for context in contexts:
|
|
|
|
try:
|
|
|
|
next(context)
|
|
|
|
except StopIteration:
|
|
|
|
pass
|
2017-05-18 09:25:19 +00:00
|
|
|
yield
|
|
|
|
for context in contexts:
|
|
|
|
try:
|
2017-05-18 13:30:59 +00:00
|
|
|
next(context)
|
2017-05-18 09:25:19 +00:00
|
|
|
except StopIteration:
|
|
|
|
pass
|
|
|
|
|
2017-05-26 10:33:54 +00:00
|
|
|
def pipe(self, texts, n_threads=2, batch_size=1000, disable=[]):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Process texts as a stream, and yield `Doc` objects in order. Supports
|
|
|
|
GIL-free multi-threading.
|
|
|
|
|
|
|
|
texts (iterator): A sequence of texts to process.
|
|
|
|
n_threads (int): The number of worker threads to use. If -1, OpenMP will
|
|
|
|
decide how many to use at run time. Default is 2.
|
|
|
|
batch_size (int): The number of texts to buffer.
|
2017-05-26 10:33:54 +00:00
|
|
|
disable (list): Names of the pipeline components to disable.
|
2017-05-18 21:57:38 +00:00
|
|
|
YIELDS (Doc): Documents in the order of the original text.
|
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
>>> texts = [u'One document.', u'...', u'Lots of documents']
|
|
|
|
>>> for doc in nlp.pipe(texts, batch_size=50, n_threads=4):
|
|
|
|
>>> assert doc.is_parsed
|
2017-04-15 09:59:21 +00:00
|
|
|
"""
|
2017-05-23 08:06:53 +00:00
|
|
|
docs = (self.make_doc(text) for text in texts)
|
2017-05-19 18:25:42 +00:00
|
|
|
docs = texts
|
2016-10-14 15:38:29 +00:00
|
|
|
for proc in self.pipeline:
|
2017-05-16 09:21:59 +00:00
|
|
|
name = getattr(proc, 'name', None)
|
2017-05-26 10:33:54 +00:00
|
|
|
if name in disable:
|
2017-05-16 09:21:59 +00:00
|
|
|
continue
|
|
|
|
if hasattr(proc, 'pipe'):
|
2017-05-19 18:25:42 +00:00
|
|
|
docs = proc.pipe(docs, n_threads=n_threads, batch_size=batch_size)
|
2017-05-16 09:21:59 +00:00
|
|
|
else:
|
2017-05-21 23:43:31 +00:00
|
|
|
# Apply the function, but yield the doc
|
|
|
|
docs = _pipe(proc, docs)
|
2017-05-19 18:25:42 +00:00
|
|
|
for doc in docs:
|
2016-02-03 01:04:55 +00:00
|
|
|
yield doc
|
2016-02-01 08:01:13 +00:00
|
|
|
|
2017-05-31 11:42:39 +00:00
|
|
|
def to_disk(self, path, disable=tuple()):
|
2017-05-26 10:33:54 +00:00
|
|
|
"""Save the current state to a directory. If a model is loaded, this
|
|
|
|
will include the model.
|
2017-04-16 23:40:26 +00:00
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
path (unicode or Path): A path to a directory, which will be created if
|
|
|
|
it doesn't exist. Paths may be either strings or `Path`-like objects.
|
2017-05-31 11:42:39 +00:00
|
|
|
disable (list): Names of pipeline components to disable and prevent
|
2017-05-26 10:33:54 +00:00
|
|
|
from being saved.
|
2017-05-18 21:57:38 +00:00
|
|
|
|
|
|
|
EXAMPLE:
|
|
|
|
>>> nlp.to_disk('/path/to/models')
|
2017-05-17 10:04:50 +00:00
|
|
|
"""
|
|
|
|
path = util.ensure_path(path)
|
2017-05-31 11:42:39 +00:00
|
|
|
serializers = OrderedDict((
|
|
|
|
('vocab', lambda p: self.vocab.to_disk(p)),
|
|
|
|
('tokenizer', lambda p: self.tokenizer.to_disk(p, vocab=False)),
|
|
|
|
('meta.json', lambda p: p.open('w').write(json_dumps(self.meta)))
|
|
|
|
))
|
|
|
|
for proc in self.pipeline:
|
|
|
|
if not hasattr(proc, 'name'):
|
|
|
|
continue
|
|
|
|
if proc.name in disable:
|
|
|
|
continue
|
|
|
|
if not hasattr(proc, 'to_disk'):
|
|
|
|
continue
|
|
|
|
serializers[proc.name] = lambda p, proc=proc: proc.to_disk(p, vocab=False)
|
|
|
|
util.to_disk(path, serializers, {p: False for p in disable})
|
|
|
|
|
|
|
|
def from_disk(self, path, disable=tuple()):
|
2017-05-18 21:57:38 +00:00
|
|
|
"""Loads state from a directory. Modifies the object in place and
|
2017-05-26 10:33:54 +00:00
|
|
|
returns it. If the saved `Language` object contains a model, the
|
|
|
|
model will be loaded.
|
2017-05-17 10:04:50 +00:00
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
path (unicode or Path): A path to a directory. Paths may be either
|
|
|
|
strings or `Path`-like objects.
|
2017-05-26 10:33:54 +00:00
|
|
|
disable (list): Names of the pipeline components to disable.
|
2017-05-18 21:57:38 +00:00
|
|
|
RETURNS (Language): The modified `Language` object.
|
2017-05-17 10:04:50 +00:00
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
EXAMPLE:
|
|
|
|
>>> from spacy.language import Language
|
|
|
|
>>> nlp = Language().from_disk('/path/to/models')
|
2017-05-17 10:04:50 +00:00
|
|
|
"""
|
|
|
|
path = util.ensure_path(path)
|
2017-05-31 11:42:39 +00:00
|
|
|
deserializers = OrderedDict((
|
|
|
|
('vocab', lambda p: self.vocab.from_disk(p)),
|
|
|
|
('tokenizer', lambda p: self.tokenizer.from_disk(p, vocab=False)),
|
|
|
|
('meta.json', lambda p: p.open('w').write(json_dumps(self.meta)))
|
|
|
|
))
|
|
|
|
for proc in self.pipeline:
|
|
|
|
if not hasattr(proc, 'name'):
|
|
|
|
continue
|
|
|
|
if proc.name in disable:
|
|
|
|
continue
|
|
|
|
if not hasattr(proc, 'to_disk'):
|
|
|
|
continue
|
|
|
|
deserializers[proc.name] = lambda p, proc=proc: proc.from_disk(p, vocab=False)
|
|
|
|
util.from_disk(path, deserializers, {p: False for p in disable})
|
|
|
|
return self
|
2017-05-17 10:04:50 +00:00
|
|
|
|
2017-05-26 10:33:54 +00:00
|
|
|
def to_bytes(self, disable=[]):
|
2017-05-17 10:04:50 +00:00
|
|
|
"""Serialize the current state to a binary string.
|
2016-12-18 15:54:52 +00:00
|
|
|
|
2017-05-26 10:33:54 +00:00
|
|
|
disable (list): Nameds of pipeline components to disable and prevent
|
|
|
|
from being serialized.
|
2017-05-18 21:57:38 +00:00
|
|
|
RETURNS (bytes): The serialized form of the `Language` object.
|
2017-05-17 10:04:50 +00:00
|
|
|
"""
|
2017-05-29 13:40:45 +00:00
|
|
|
serializers = OrderedDict((
|
|
|
|
('vocab', lambda: self.vocab.to_bytes()),
|
|
|
|
('tokenizer', lambda: self.tokenizer.to_bytes(vocab=False)),
|
|
|
|
('meta', lambda: ujson.dumps(self.meta))
|
|
|
|
))
|
|
|
|
for i, proc in enumerate(self.pipeline):
|
|
|
|
if getattr(proc, 'name', None) in disable:
|
2017-05-29 09:45:45 +00:00
|
|
|
continue
|
|
|
|
if not hasattr(proc, 'to_bytes'):
|
|
|
|
continue
|
2017-05-29 18:23:28 +00:00
|
|
|
serializers[i] = lambda proc=proc: proc.to_bytes(vocab=False)
|
2017-05-29 11:38:20 +00:00
|
|
|
return util.to_bytes(serializers, {})
|
2017-04-15 10:05:47 +00:00
|
|
|
|
2017-05-26 10:33:54 +00:00
|
|
|
def from_bytes(self, bytes_data, disable=[]):
|
2017-05-17 10:04:50 +00:00
|
|
|
"""Load state from a binary string.
|
|
|
|
|
2017-05-18 21:57:38 +00:00
|
|
|
bytes_data (bytes): The data to load from.
|
2017-05-26 10:33:54 +00:00
|
|
|
disable (list): Names of the pipeline components to disable.
|
2017-05-18 21:57:38 +00:00
|
|
|
RETURNS (Language): The `Language` object.
|
2017-05-17 10:04:50 +00:00
|
|
|
"""
|
2017-05-29 13:40:45 +00:00
|
|
|
deserializers = OrderedDict((
|
|
|
|
('vocab', lambda b: self.vocab.from_bytes(b)),
|
|
|
|
('tokenizer', lambda b: self.tokenizer.from_bytes(b, vocab=False)),
|
|
|
|
('meta', lambda b: self.meta.update(ujson.loads(b)))
|
|
|
|
))
|
|
|
|
for i, proc in enumerate(self.pipeline):
|
|
|
|
if getattr(proc, 'name', None) in disable:
|
2017-05-29 09:45:45 +00:00
|
|
|
continue
|
2017-05-29 13:40:45 +00:00
|
|
|
if not hasattr(proc, 'from_bytes'):
|
2017-05-29 09:45:45 +00:00
|
|
|
continue
|
2017-05-29 18:23:28 +00:00
|
|
|
deserializers[i] = lambda b, proc=proc: proc.from_bytes(b, vocab=False)
|
|
|
|
msg = util.from_bytes(bytes_data, deserializers, {})
|
2017-05-17 10:04:50 +00:00
|
|
|
return self
|
2017-05-21 23:43:31 +00:00
|
|
|
|
2017-05-29 09:45:45 +00:00
|
|
|
|
2017-05-21 23:43:31 +00:00
|
|
|
def _pipe(func, docs):
|
|
|
|
for doc in docs:
|
|
|
|
func(doc)
|
|
|
|
yield doc
|