2020-07-29 12:36:42 +03:00
|
|
|
from typing import Union, List, Iterable, Iterator, TYPE_CHECKING
|
|
|
|
from pathlib import Path
|
2020-06-26 20:34:12 +03:00
|
|
|
import random
|
2020-07-29 12:36:42 +03:00
|
|
|
|
2020-06-26 20:34:12 +03:00
|
|
|
from .. import util
|
|
|
|
from .example import Example
|
|
|
|
from ..tokens import DocBin, Doc
|
2020-07-29 12:36:42 +03:00
|
|
|
from ..vocab import Vocab
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
# This lets us add type hints for mypy etc. without causing circular imports
|
|
|
|
from ..language import Language # noqa: F401
|
2020-06-26 20:34:12 +03:00
|
|
|
|
|
|
|
|
|
|
|
class Corpus:
|
|
|
|
"""An annotated corpus, reading train and dev datasets from
|
|
|
|
the DocBin (.spacy) format.
|
|
|
|
|
2020-07-04 15:23:44 +03:00
|
|
|
DOCS: https://spacy.io/api/corpus
|
2020-06-26 20:34:12 +03:00
|
|
|
"""
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def __init__(
|
|
|
|
self, train_loc: Union[str, Path], dev_loc: Union[str, Path], limit: int = 0
|
|
|
|
) -> None:
|
2020-06-26 20:34:12 +03:00
|
|
|
"""Create a Corpus.
|
|
|
|
|
|
|
|
train (str / Path): File or directory of training data.
|
|
|
|
dev (str / Path): File or directory of development data.
|
2020-07-29 12:36:42 +03:00
|
|
|
limit (int): Max. number of examples returned.
|
|
|
|
|
|
|
|
DOCS: https://spacy.io/api/corpus#init
|
2020-06-26 20:34:12 +03:00
|
|
|
"""
|
|
|
|
self.train_loc = train_loc
|
|
|
|
self.dev_loc = dev_loc
|
|
|
|
self.limit = limit
|
|
|
|
|
|
|
|
@staticmethod
|
2020-07-29 12:36:42 +03:00
|
|
|
def walk_corpus(path: Union[str, Path]) -> List[Path]:
|
2020-06-26 20:34:12 +03:00
|
|
|
path = util.ensure_path(path)
|
|
|
|
if not path.is_dir():
|
|
|
|
return [path]
|
|
|
|
paths = [path]
|
|
|
|
locs = []
|
|
|
|
seen = set()
|
|
|
|
for path in paths:
|
|
|
|
if str(path) in seen:
|
|
|
|
continue
|
|
|
|
seen.add(str(path))
|
|
|
|
if path.parts[-1].startswith("."):
|
|
|
|
continue
|
|
|
|
elif path.is_dir():
|
|
|
|
paths.extend(path.iterdir())
|
|
|
|
elif path.parts[-1].endswith(".spacy"):
|
|
|
|
locs.append(path)
|
|
|
|
return locs
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def _make_example(
|
|
|
|
self, nlp: "Language", reference: Doc, gold_preproc: bool
|
|
|
|
) -> Example:
|
2020-07-03 13:58:16 +03:00
|
|
|
if gold_preproc or reference.has_unknown_spaces:
|
|
|
|
return Example(
|
|
|
|
Doc(
|
|
|
|
nlp.vocab,
|
|
|
|
words=[word.text for word in reference],
|
2020-07-04 15:23:44 +03:00
|
|
|
spaces=[bool(word.whitespace_) for word in reference],
|
2020-07-03 13:58:16 +03:00
|
|
|
),
|
2020-07-04 15:23:44 +03:00
|
|
|
reference,
|
2020-07-03 13:58:16 +03:00
|
|
|
)
|
|
|
|
else:
|
2020-07-04 15:23:44 +03:00
|
|
|
return Example(nlp.make_doc(reference.text), reference)
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def make_examples(
|
|
|
|
self, nlp: "Language", reference_docs: Iterable[Doc], max_length: int = 0
|
|
|
|
) -> Iterator[Example]:
|
2020-06-26 20:34:12 +03:00
|
|
|
for reference in reference_docs:
|
2020-07-01 16:16:43 +03:00
|
|
|
if len(reference) == 0:
|
|
|
|
continue
|
|
|
|
elif max_length == 0 or len(reference) < max_length:
|
2020-07-03 13:58:16 +03:00
|
|
|
yield self._make_example(nlp, reference, False)
|
2020-07-01 16:16:43 +03:00
|
|
|
elif reference.is_sentenced:
|
|
|
|
for ref_sent in reference.sents:
|
|
|
|
if len(ref_sent) == 0:
|
|
|
|
continue
|
|
|
|
elif max_length == 0 or len(ref_sent) < max_length:
|
2020-07-03 13:58:16 +03:00
|
|
|
yield self._make_example(nlp, ref_sent.as_doc(), False)
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def make_examples_gold_preproc(
|
|
|
|
self, nlp: "Language", reference_docs: Iterable[Doc]
|
|
|
|
) -> Iterator[Example]:
|
2020-06-26 20:34:12 +03:00
|
|
|
for reference in reference_docs:
|
|
|
|
if reference.is_sentenced:
|
|
|
|
ref_sents = [sent.as_doc() for sent in reference.sents]
|
|
|
|
else:
|
|
|
|
ref_sents = [reference]
|
|
|
|
for ref_sent in ref_sents:
|
2020-07-03 13:58:16 +03:00
|
|
|
eg = self._make_example(nlp, ref_sent, True)
|
2020-07-01 16:02:37 +03:00
|
|
|
if len(eg.x):
|
|
|
|
yield eg
|
2020-06-26 20:34:12 +03:00
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def read_docbin(
|
|
|
|
self, vocab: Vocab, locs: Iterable[Union[str, Path]]
|
|
|
|
) -> Iterator[Doc]:
|
2020-06-26 20:34:12 +03:00
|
|
|
""" Yield training examples as example dicts """
|
|
|
|
i = 0
|
|
|
|
for loc in locs:
|
|
|
|
loc = util.ensure_path(loc)
|
|
|
|
if loc.parts[-1].endswith(".spacy"):
|
|
|
|
with loc.open("rb") as file_:
|
|
|
|
doc_bin = DocBin().from_bytes(file_.read())
|
|
|
|
docs = doc_bin.get_docs(vocab)
|
|
|
|
for doc in docs:
|
|
|
|
if len(doc):
|
|
|
|
yield doc
|
|
|
|
i += 1
|
|
|
|
if self.limit >= 1 and i >= self.limit:
|
|
|
|
break
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def count_train(self, nlp: "Language") -> int:
|
|
|
|
"""Returns count of words in train examples.
|
|
|
|
|
|
|
|
nlp (Language): The current nlp. object.
|
|
|
|
RETURNS (int): The word count.
|
|
|
|
|
|
|
|
DOCS: https://spacy.io/api/corpus#count_train
|
|
|
|
"""
|
2020-06-26 20:34:12 +03:00
|
|
|
n = 0
|
|
|
|
i = 0
|
|
|
|
for example in self.train_dataset(nlp):
|
|
|
|
n += len(example.predicted)
|
|
|
|
if self.limit >= 0 and i >= self.limit:
|
|
|
|
break
|
|
|
|
i += 1
|
|
|
|
return n
|
|
|
|
|
2020-07-04 15:23:44 +03:00
|
|
|
def train_dataset(
|
2020-07-29 12:36:42 +03:00
|
|
|
self,
|
|
|
|
nlp: "Language",
|
|
|
|
*,
|
|
|
|
shuffle: bool = True,
|
|
|
|
gold_preproc: bool = False,
|
|
|
|
max_length: int = 0
|
|
|
|
) -> Iterator[Example]:
|
|
|
|
"""Yield examples from the training data.
|
|
|
|
|
|
|
|
nlp (Language): The current nlp object.
|
|
|
|
shuffle (bool): Whether to shuffle the examples.
|
|
|
|
gold_preproc (bool): Whether to train on gold-standard sentences and tokens.
|
|
|
|
max_length (int): Maximum document length. Longer documents will be
|
|
|
|
split into sentences, if sentence boundaries are available. 0 for
|
|
|
|
no limit.
|
|
|
|
YIELDS (Example): The examples.
|
|
|
|
|
|
|
|
DOCS: https://spacy.io/api/corpus#train_dataset
|
|
|
|
"""
|
2020-06-26 20:34:12 +03:00
|
|
|
ref_docs = self.read_docbin(nlp.vocab, self.walk_corpus(self.train_loc))
|
|
|
|
if gold_preproc:
|
|
|
|
examples = self.make_examples_gold_preproc(nlp, ref_docs)
|
|
|
|
else:
|
|
|
|
examples = self.make_examples(nlp, ref_docs, max_length)
|
|
|
|
if shuffle:
|
|
|
|
examples = list(examples)
|
|
|
|
random.shuffle(examples)
|
|
|
|
yield from examples
|
|
|
|
|
2020-07-29 12:36:42 +03:00
|
|
|
def dev_dataset(
|
|
|
|
self, nlp: "Language", *, gold_preproc: bool = False
|
|
|
|
) -> Iterator[Example]:
|
|
|
|
"""Yield examples from the development data.
|
|
|
|
|
|
|
|
nlp (Language): The current nlp object.
|
|
|
|
gold_preproc (bool): Whether to train on gold-standard sentences and tokens.
|
|
|
|
YIELDS (Example): The examples.
|
|
|
|
|
|
|
|
DOCS: https://spacy.io/api/corpus#dev_dataset
|
|
|
|
"""
|
2020-06-26 20:34:12 +03:00
|
|
|
ref_docs = self.read_docbin(nlp.vocab, self.walk_corpus(self.dev_loc))
|
|
|
|
if gold_preproc:
|
|
|
|
examples = self.make_examples_gold_preproc(nlp, ref_docs)
|
|
|
|
else:
|
|
|
|
examples = self.make_examples(nlp, ref_docs, max_length=0)
|
|
|
|
yield from examples
|