From 8e5f99ee25e3eeaac635232491ab7e21ee7f7370 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 16 Aug 2020 20:13:24 +0200 Subject: [PATCH 01/92] Update transformer docs intro. Also write system requirements --- website/docs/usage/transformers.md | 75 +++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/website/docs/usage/transformers.md b/website/docs/usage/transformers.md index c3130f57b..3bcb828f8 100644 --- a/website/docs/usage/transformers.md +++ b/website/docs/usage/transformers.md @@ -10,26 +10,58 @@ next: /usage/training ## Installation {#install hidden="true"} -spaCy v3.0 lets you use almost **any statistical model** to power your pipeline. -You can use models implemented in a variety of -[frameworks](https://thinc.ai/docs/usage-frameworks), including TensorFlow, -PyTorch and MXNet. To keep things sane, spaCy expects models from these -frameworks to be wrapped with a common interface, using our machine learning -library [Thinc](https://thinc.ai). A transformer model is just a statistical -model, so the -[`spacy-transformers`](https://github.com/explosion/spacy-transformers) package -actually has very little work to do: it just has to provide a few functions that -do the required plumbing. It also provides a pipeline component, -[`Transformer`](/api/transformer), that lets you do multi-task learning and lets -you save the transformer outputs for later use. +Transformers are a family of neural network architectures that compute dense, +context-sensitive representations for the tokens in your documents. Downstream +models in your pipeline can then use these representations as input features to +improve their predictions. You can connect multiple components to a single +transformer model, with any or all of those components giving feedback to the +transformer to fine-tune it to your tasks. spaCy's transformer support +interoperates with PyTorch and the [Huggingface transformers](https://huggingface.co/transformers/) +library, giving you access to thousands of pretrained models for your pipelines. +There are many [great guides](http://jalammar.github.io/illustrated-transformer/) +to transformer models, but for practical purposes, you can simply think of them +as a drop-in replacement that let you achieve higher accuracy in exchange for +higher training and runtime costs. -To use transformers with spaCy, you need the -[`spacy-transformers`](https://github.com/explosion/spacy-transformers) package -installed. It takes care of all the setup behind the scenes, and makes sure the -transformer pipeline component is available to spaCy. +## System requirements -```bash -$ pip install spacy-transformers +We recommend an NVIDIA GPU with at least 10GB of memory in order to work with +transformer models. The exact requirements will depend on the transformer you +model you choose and whether you're training the pipeline or simply running it. +Training a transformer-based model without a GPU will be too slow for most +practical purposes. A GPU will usually also achieve better +price-for-performance when processing large batches of documents. The only +context where a GPU might not be worthwhile is if you're serving the model in +a context where documents are received individually, rather than in batches. In +this context, CPU may be more cost effective. + +You'll also need to make sure your GPU drivers are up-to-date and v9+ of the +CUDA runtime is installed. Unfortunately, there's little we can do to help with +this part: the steps will vary depending on your device, operating system and +the version of CUDA you're targetting (you'll want to use one that's well +supported by cupy, PyTorch and Tensorflow). + +Once you have CUDA installed, you'll need to install two pip packages, `cupy` +and `spacy-transformers`. The `cupy` library is just like `numpy`, but for GPU. +The best way to install it is to choose a wheel that matches the version of CUDA +you're using. For instance, if you're using CUDA 10.2, you would run +`pip install cupy-cuda102`. Finally, if you've installed CUDA in a non-standard +location, you'll need to set the `CUDA_PATH` environment variable to the base +of your CUDA installation. See the cupy documentation for more details. +download a few extra dependencies. + +If provisioning a fresh environment, you'll generally have to download about +5GB of data in total: 3GB for CUDA, about 400MB for the CuPy wheel, 800MB for +PyTorch (required by `spacy-transformers`), 500MB for the transformer weights, +and about 200MB in various other binaries. + +In summary, let's say you're using CUDA 10.2, and you've installed it in +`/opt/nvidia/cuda`: + +``` +export CUDA_PATH="/opt/nvidia/cuda" +pip install cupy-cuda102 +pip install spacy-transformers ``` ## Runtime usage {#runtime} @@ -54,6 +86,13 @@ $ python -m spacy download en_core_trf_lg ```python ### Example import spacy +from thinc.api import use_pytorch_for_gpu_memory, require_gpu + +# Use the GPU, with memory allocations directed via PyTorch. +# This prevents out-of-memory errors that would otherwise occur from competing +# memory pools. +use_pytorch_for_gpu_memory() +require_gpu(0) nlp = spacy.load("en_core_trf_lg") for doc in nlp.pipe(["some text", "some other text"]): From be07567ac654318809864981c70a6fbc095af38c Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 16 Aug 2020 20:29:50 +0200 Subject: [PATCH 02/92] Update transformers page --- website/docs/usage/transformers.md | 40 +++++++++--------------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/website/docs/usage/transformers.md b/website/docs/usage/transformers.md index 3bcb828f8..b672d5612 100644 --- a/website/docs/usage/transformers.md +++ b/website/docs/usage/transformers.md @@ -29,34 +29,16 @@ We recommend an NVIDIA GPU with at least 10GB of memory in order to work with transformer models. The exact requirements will depend on the transformer you model you choose and whether you're training the pipeline or simply running it. Training a transformer-based model without a GPU will be too slow for most -practical purposes. A GPU will usually also achieve better -price-for-performance when processing large batches of documents. The only -context where a GPU might not be worthwhile is if you're serving the model in -a context where documents are received individually, rather than in batches. In -this context, CPU may be more cost effective. - -You'll also need to make sure your GPU drivers are up-to-date and v9+ of the -CUDA runtime is installed. Unfortunately, there's little we can do to help with -this part: the steps will vary depending on your device, operating system and -the version of CUDA you're targetting (you'll want to use one that's well -supported by cupy, PyTorch and Tensorflow). +practical purposes. You'll also need to make sure your GPU drivers are up-to-date +and v9+ of the CUDA runtime is installed. Once you have CUDA installed, you'll need to install two pip packages, `cupy` -and `spacy-transformers`. The `cupy` library is just like `numpy`, but for GPU. -The best way to install it is to choose a wheel that matches the version of CUDA -you're using. For instance, if you're using CUDA 10.2, you would run -`pip install cupy-cuda102`. Finally, if you've installed CUDA in a non-standard -location, you'll need to set the `CUDA_PATH` environment variable to the base -of your CUDA installation. See the cupy documentation for more details. -download a few extra dependencies. - -If provisioning a fresh environment, you'll generally have to download about -5GB of data in total: 3GB for CUDA, about 400MB for the CuPy wheel, 800MB for -PyTorch (required by `spacy-transformers`), 500MB for the transformer weights, -and about 200MB in various other binaries. - -In summary, let's say you're using CUDA 10.2, and you've installed it in -`/opt/nvidia/cuda`: +and `spacy-transformers`. [CuPy](https://docs.cupy.dev/en/stable/install.html) +is just like `numpy`, but for GPU. The best way to install it is to choose a +wheel that matches the version of CUDA you're using. You may also need to set the +`CUDA_PATH` environment variable if your CUDA runtime is installed in +a non-standard location. Putting it all together, if you had installed CUDA 10.2 +in `/opt/nvidia/cuda`, you would run: ``` export CUDA_PATH="/opt/nvidia/cuda" @@ -64,6 +46,10 @@ pip install cupy-cuda102 pip install spacy-transformers ``` +Provisioning a new machine will require about 5GB of data to be downloaded in total: +3GB for the CUDA runtime, 800MB for PyTorch, 400MB for CuPy, 500MB for the transformer +weights, and about 200MB for spaCy and its various requirements. + ## Runtime usage {#runtime} Transformer models can be used as **drop-in replacements** for other types of @@ -306,8 +292,6 @@ averages the wordpiece rows. We could instead use `reduce_last`, [`reduce_max`](https://thinc.ai/docs/api-layers#reduce_max), or a custom function you write yourself. - - You can have multiple components all listening to the same transformer model, and all passing gradients back to it. By default, all of the gradients will be **equally weighted**. You can control this with the `grad_factor` setting, which From 319692aa539fe275c76ed82030396ae41726500b Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 17 Aug 2020 14:05:48 +0200 Subject: [PATCH 03/92] fix typos --- CONTRIBUTING.md | 156 +++++++++++++-------------- website/docs/api/architectures.md | 45 ++++---- website/docs/usage/index.md | 2 +- website/docs/usage/saving-loading.md | 6 +- website/setup/jinja_to_js.py | 2 +- 5 files changed, 105 insertions(+), 106 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b7881dd2..81cfbf8cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,33 +43,33 @@ can also submit a [regression test](#fixing-bugs) straight away. When you're opening an issue to report the bug, simply refer to your pull request in the issue body. A few more tips: -- **Describing your issue:** Try to provide as many details as possible. What - exactly goes wrong? _How_ is it failing? Is there an error? - "XY doesn't work" usually isn't that helpful for tracking down problems. Always - remember to include the code you ran and if possible, extract only the relevant - parts and don't just dump your entire script. This will make it easier for us to - reproduce the error. +- **Describing your issue:** Try to provide as many details as possible. What + exactly goes wrong? _How_ is it failing? Is there an error? + "XY doesn't work" usually isn't that helpful for tracking down problems. Always + remember to include the code you ran and if possible, extract only the relevant + parts and don't just dump your entire script. This will make it easier for us to + reproduce the error. -- **Getting info about your spaCy installation and environment:** If you're - using spaCy v1.7+, you can use the command line interface to print details and - even format them as Markdown to copy-paste into GitHub issues: - `python -m spacy info --markdown`. +- **Getting info about your spaCy installation and environment:** If you're + using spaCy v1.7+, you can use the command line interface to print details and + even format them as Markdown to copy-paste into GitHub issues: + `python -m spacy info --markdown`. -- **Checking the model compatibility:** If you're having problems with a - [statistical model](https://spacy.io/models), it may be because the - model is incompatible with your spaCy installation. In spaCy v2.0+, you can check - this on the command line by running `python -m spacy validate`. +- **Checking the model compatibility:** If you're having problems with a + [statistical model](https://spacy.io/models), it may be because the + model is incompatible with your spaCy installation. In spaCy v2.0+, you can check + this on the command line by running `python -m spacy validate`. -- **Sharing a model's output, like dependencies and entities:** spaCy v2.0+ - comes with [built-in visualizers](https://spacy.io/usage/visualizers) that - you can run from within your script or a Jupyter notebook. For some issues, it's - helpful to **include a screenshot** of the visualization. You can simply drag and - drop the image into GitHub's editor and it will be uploaded and included. +- **Sharing a model's output, like dependencies and entities:** spaCy v2.0+ + comes with [built-in visualizers](https://spacy.io/usage/visualizers) that + you can run from within your script or a Jupyter notebook. For some issues, it's + helpful to **include a screenshot** of the visualization. You can simply drag and + drop the image into GitHub's editor and it will be uploaded and included. -- **Sharing long blocks of code or logs:** If you need to include long code, - logs or tracebacks, you can wrap them in `
` and `
`. This - [collapses the content](https://developer.mozilla.org/en/docs/Web/HTML/Element/details) - so it only becomes visible on click, making the issue easier to read and follow. +- **Sharing long blocks of code or logs:** If you need to include long code, + logs or tracebacks, you can wrap them in `
` and `
`. This + [collapses the content](https://developer.mozilla.org/en/docs/Web/HTML/Element/details) + so it only becomes visible on click, making the issue easier to read and follow. ### Issue labels @@ -94,39 +94,39 @@ shipped in the core library, and what could be provided in other packages. Our philosophy is to prefer a smaller core library. We generally ask the following questions: -- **What would this feature look like if implemented in a separate package?** - Some features would be very difficult to implement externally – for example, - changes to spaCy's built-in methods. In contrast, a library of word - alignment functions could easily live as a separate package that depended on - spaCy — there's little difference between writing `import word_aligner` and - `import spacy.word_aligner`. spaCy v2.0+ makes it easy to implement - [custom pipeline components](https://spacy.io/usage/processing-pipelines#custom-components), - and add your own attributes, properties and methods to the `Doc`, `Token` and - `Span`. If you're looking to implement a new spaCy feature, starting with a - custom component package is usually the best strategy. You won't have to worry - about spaCy's internals and you can test your module in an isolated - environment. And if it works well, we can always integrate it into the core - library later. +- **What would this feature look like if implemented in a separate package?** + Some features would be very difficult to implement externally – for example, + changes to spaCy's built-in methods. In contrast, a library of word + alignment functions could easily live as a separate package that depended on + spaCy — there's little difference between writing `import word_aligner` and + `import spacy.word_aligner`. spaCy v2.0+ makes it easy to implement + [custom pipeline components](https://spacy.io/usage/processing-pipelines#custom-components), + and add your own attributes, properties and methods to the `Doc`, `Token` and + `Span`. If you're looking to implement a new spaCy feature, starting with a + custom component package is usually the best strategy. You won't have to worry + about spaCy's internals and you can test your module in an isolated + environment. And if it works well, we can always integrate it into the core + library later. -- **Would the feature be easier to implement if it relied on "heavy" dependencies spaCy doesn't currently require?** - Python has a very rich ecosystem. Libraries like scikit-learn, SciPy, Gensim or - TensorFlow/Keras do lots of useful things — but we don't want to have them as - dependencies. If the feature requires functionality in one of these libraries, - it's probably better to break it out into a different package. +- **Would the feature be easier to implement if it relied on "heavy" dependencies spaCy doesn't currently require?** + Python has a very rich ecosystem. Libraries like scikit-learn, SciPy, Gensim or + TensorFlow/Keras do lots of useful things — but we don't want to have them as + dependencies. If the feature requires functionality in one of these libraries, + it's probably better to break it out into a different package. -- **Is the feature orthogonal to the current spaCy functionality, or overlapping?** - spaCy strongly prefers to avoid having 6 different ways of doing the same thing. - As better techniques are developed, we prefer to drop support for "the old way". - However, it's rare that one approach _entirely_ dominates another. It's very - common that there's still a use-case for the "obsolete" approach. For instance, - [WordNet](https://wordnet.princeton.edu/) is still very useful — but word - vectors are better for most use-cases, and the two approaches to lexical - semantics do a lot of the same things. spaCy therefore only supports word - vectors, and support for WordNet is currently left for other packages. +- **Is the feature orthogonal to the current spaCy functionality, or overlapping?** + spaCy strongly prefers to avoid having 6 different ways of doing the same thing. + As better techniques are developed, we prefer to drop support for "the old way". + However, it's rare that one approach _entirely_ dominates another. It's very + common that there's still a use-case for the "obsolete" approach. For instance, + [WordNet](https://wordnet.princeton.edu/) is still very useful — but word + vectors are better for most use-cases, and the two approaches to lexical + semantics do a lot of the same things. spaCy therefore only supports word + vectors, and support for WordNet is currently left for other packages. -- **Do you need the feature to get basic things done?** We do want spaCy to be - at least somewhat self-contained. If we keep needing some feature in our - recipes, that does provide some argument for bringing it "in house". +- **Do you need the feature to get basic things done?** We do want spaCy to be + at least somewhat self-contained. If we keep needing some feature in our + recipes, that does provide some argument for bringing it "in house". ### Getting started @@ -203,10 +203,10 @@ your files on save: ```json { - "python.formatting.provider": "black", - "[python]": { - "editor.formatOnSave": true - } + "python.formatting.provider": "black", + "[python]": { + "editor.formatOnSave": true + } } ``` @@ -216,7 +216,7 @@ list of available editor integrations. #### Disabling formatting There are a few cases where auto-formatting doesn't improve readability – for -example, in some of the the language data files like the `tag_map.py`, or in +example, in some of the language data files like the `tag_map.py`, or in the tests that construct `Doc` objects from lists of words and other labels. Wrapping a block in `# fmt: off` and `# fmt: on` lets you disable formatting for that particular code. Here's an example: @@ -397,10 +397,10 @@ Python. If it's not fast enough the first time, just switch to Cython. ### Resources to get you started -- [PEP 8 Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (python.org) -- [Official Cython documentation](http://docs.cython.org/en/latest/) (cython.org) -- [Writing C in Cython](https://explosion.ai/blog/writing-c-in-cython) (explosion.ai) -- [Multi-threading spaCy’s parser and named entity recogniser](https://explosion.ai/blog/multithreading-with-cython) (explosion.ai) +- [PEP 8 Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (python.org) +- [Official Cython documentation](http://docs.cython.org/en/latest/) (cython.org) +- [Writing C in Cython](https://explosion.ai/blog/writing-c-in-cython) (explosion.ai) +- [Multi-threading spaCy’s parser and named entity recogniser](https://explosion.ai/blog/multithreading-with-cython) (explosion.ai) ## Adding tests @@ -440,25 +440,25 @@ simply click on the "Suggest edits" button at the bottom of a page. We're very excited about all the new possibilities for **community extensions** and plugins in spaCy v2.0, and we can't wait to see what you build with it! -- An extension or plugin should add substantial functionality, be - **well-documented** and **open-source**. It should be available for users to download - and install as a Python package – for example via [PyPi](http://pypi.python.org). +- An extension or plugin should add substantial functionality, be + **well-documented** and **open-source**. It should be available for users to download + and install as a Python package – for example via [PyPi](http://pypi.python.org). -- Extensions that write to `Doc`, `Token` or `Span` attributes should be wrapped - as [pipeline components](https://spacy.io/usage/processing-pipelines#custom-components) - that users can **add to their processing pipeline** using `nlp.add_pipe()`. +- Extensions that write to `Doc`, `Token` or `Span` attributes should be wrapped + as [pipeline components](https://spacy.io/usage/processing-pipelines#custom-components) + that users can **add to their processing pipeline** using `nlp.add_pipe()`. -- When publishing your extension on GitHub, **tag it** with the topics - [`spacy`](https://github.com/topics/spacy?o=desc&s=stars) and - [`spacy-extensions`](https://github.com/topics/spacy-extension?o=desc&s=stars) - to make it easier to find. Those are also the topics we're linking to from the - spaCy website. If you're sharing your project on Twitter, feel free to tag - [@spacy_io](https://twitter.com/spacy_io) so we can check it out. +- When publishing your extension on GitHub, **tag it** with the topics + [`spacy`](https://github.com/topics/spacy?o=desc&s=stars) and + [`spacy-extensions`](https://github.com/topics/spacy-extension?o=desc&s=stars) + to make it easier to find. Those are also the topics we're linking to from the + spaCy website. If you're sharing your project on Twitter, feel free to tag + [@spacy_io](https://twitter.com/spacy_io) so we can check it out. -- Once your extension is published, you can open an issue on the - [issue tracker](https://github.com/explosion/spacy/issues) to suggest it for the - [resources directory](https://spacy.io/usage/resources#extensions) on the - website. +- Once your extension is published, you can open an issue on the + [issue tracker](https://github.com/explosion/spacy/issues) to suggest it for the + [resources directory](https://spacy.io/usage/resources#extensions) on the + website. 📖 **For more tips and best practices, see the [checklist for developing spaCy extensions](https://spacy.io/usage/processing-pipelines#extensions).** diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index cc6f44fcc..0ae874d8a 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -489,18 +489,17 @@ network has an internal CNN Tok2Vec layer and uses attention. > nO = null > ``` -| Name | Type | Description | -| --------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `pretrained_vectors` | bool | Whether or not pretrained vectors will be used in addition to the feature vectors. | -| `width` | int | Output dimension of the feature encoding step. | -| `embed_size` | int | Input dimension of the feature encoding step. | -| `conv_depth` | int | Depth of the Tok2Vec layer. | -| `window_size` | int | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. | -| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | -| `dropout` | float | The dropout rate. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when | -| `begin_training` is called. | +| Name | Type | Description | +| -------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | +| `pretrained_vectors` | bool | Whether or not pretrained vectors will be used in addition to the feature vectors. | +| `width` | int | Output dimension of the feature encoding step. | +| `embed_size` | int | Input dimension of the feature encoding step. | +| `conv_depth` | int | Depth of the Tok2Vec layer. | +| `window_size` | int | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. | +| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | +| `dropout` | float | The dropout rate. | +| `nO` | int | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | ### spacy.TextCatCNN.v1 {#TextCatCNN} @@ -527,11 +526,11 @@ A neural network model where token vectors are calculated using a CNN. The vectors are mean pooled and used as features in a feed-forward network. This architecture is usually less accurate than the ensemble, but runs faster. -| Name | Type | Description | -| ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | The [`tok2vec`](#tok2vec) layer of the model. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | +| Name | Type | Description | +| ------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | +| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | The [`tok2vec`](#tok2vec) layer of the model. | +| `nO` | int | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | ### spacy.TextCatBOW.v1 {#TextCatBOW} @@ -549,12 +548,12 @@ architecture is usually less accurate than the ensemble, but runs faster. An ngram "bag-of-words" model. This architecture should run much faster than the others, but may not be as accurate, especially if texts are short. -| Name | Type | Description | -| ------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | -| `no_output_layer` | float | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes=True`, else `Logistic`. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | +| Name | Type | Description | +| ------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | +| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | +| `no_output_layer` | float | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes=True`, else `Logistic`. | +| `nO` | int | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"} diff --git a/website/docs/usage/index.md b/website/docs/usage/index.md index bda9f76d6..a73753780 100644 --- a/website/docs/usage/index.md +++ b/website/docs/usage/index.md @@ -169,7 +169,7 @@ python setup.py build_ext --inplace # compile spaCy Compared to regular install via pip, the [`requirements.txt`](https://github.com/explosion/spaCy/tree/master/requirements.txt) -additionally installs developer dependencies such as Cython. See the the +additionally installs developer dependencies such as Cython. See the [quickstart widget](#quickstart) to get the right commands for your platform and Python version. diff --git a/website/docs/usage/saving-loading.md b/website/docs/usage/saving-loading.md index 904477733..35112c02e 100644 --- a/website/docs/usage/saving-loading.md +++ b/website/docs/usage/saving-loading.md @@ -551,9 +551,9 @@ setup( ) ``` -After installing the package, the the custom colors will be used when -visualizing text with `displacy`. Whenever the label `SNEK` is assigned, it will -be displayed in `#3dff74`. +After installing the package, the custom colors will be used when visualizing +text with `displacy`. Whenever the label `SNEK` is assigned, it will be +displayed in `#3dff74`. import DisplaCyEntSnekHtml from 'images/displacy-ent-snek.html' diff --git a/website/setup/jinja_to_js.py b/website/setup/jinja_to_js.py index a2c896151..0d363375e 100644 --- a/website/setup/jinja_to_js.py +++ b/website/setup/jinja_to_js.py @@ -2,7 +2,7 @@ # With additional functionality: in/not in, replace, pprint, round, + for lists, # rendering empty dicts # This script is mostly used to generate the JavaScript function for the -# training quicktart widget. +# training quickstart widget. import contextlib import json import re From 6b6f7f3e7379e721a64c22742ef83811a375a8f0 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 17 Aug 2020 14:48:58 +0200 Subject: [PATCH 04/92] fix windows compat --- website/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/package.json b/website/package.json index e61661c11..441d996fe 100644 --- a/website/package.json +++ b/website/package.json @@ -60,7 +60,7 @@ "clear": "rm -rf .cache", "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\"", "python:install": "pip install setup/requirements.txt", - "python:setup": "cd setup && ./setup.sh" + "python:setup": "cd setup && sh setup.sh" }, "devDependencies": { "@sindresorhus/slugify": "^0.8.0", From 961e818be67105c0f59db1c34749c470aa73eb1a Mon Sep 17 00:00:00 2001 From: svlandeg Date: Mon, 17 Aug 2020 15:02:39 +0200 Subject: [PATCH 05/92] p/r definitions --- website/docs/usage/training.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index fc1624ec1..c2034278d 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -430,13 +430,11 @@ components are weighted equally. - - | Name | Description | | -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | **Loss** | The training loss representing the amount of work left for the optimizer. Should decrease, but usually not to `0`. | -| **Precision** (P) | Should increase. | -| **Recall** (R) | Should increase. | +| **Precision** (P) | The percentage of generated predictions that are correct. Should increase. | +| **Recall** (R) | The percentage of gold-standard annotations that are in fact predicted. Should increase. | | **F-Score** (F) | The weighted average of precision and recall. Should increase. | | **UAS** / **LAS** | Unlabeled and labeled attachment score for the dependency parser, i.e. the percentage of correct arcs. Should increase. | | **Words per second** (WPS) | Prediction speed in words per second. Should stay stable. | From 052d82aa4e758f0e7fe4665240fe0c7a9e0497fc Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Mon, 17 Aug 2020 15:32:30 +0200 Subject: [PATCH 06/92] Suggest vectors changes --- website/docs/usage/vectors-embeddings.md | 222 +++++++++++++++++------ 1 file changed, 169 insertions(+), 53 deletions(-) diff --git a/website/docs/usage/vectors-embeddings.md b/website/docs/usage/vectors-embeddings.md index 823b30c20..184436d12 100644 --- a/website/docs/usage/vectors-embeddings.md +++ b/website/docs/usage/vectors-embeddings.md @@ -2,28 +2,17 @@ title: Vectors and Embeddings menu: - ["What's a Word Vector?", 'whats-a-vector'] - - ['Word Vectors', 'vectors'] - - ['Other Embeddings', 'embeddings'] + - ['Using Word Vectors', 'usage'] + - ['Converting and Importing', 'converting'] next: /usage/transformers --- -An old idea in linguistics is that you can "know a word by the company it -keeps": that is, word meanings can be understood relationally, based on their -patterns of usage. This idea inspired a branch of NLP research known as -"distributional semantics" that has aimed to compute databases of lexical -knowledge automatically. The [Word2vec](https://en.wikipedia.org/wiki/Word2vec) -family of algorithms are a key milestone in this line of research. For -simplicity, we will refer to a distributional word representation as a "word -vector", and algorithms that computes word vectors (such as -[GloVe](https://nlp.stanford.edu/projects/glove/), -[FastText](https://fasttext.cc), etc.) as "Word2vec algorithms". - +Word vector tables (or "embeddings") let you find similar terms, and can improve +the accuracy of some of your components. You can even use word vectors as a +quick-and-dirty text-classification solution when you don't have any training data. Word vector tables are included in some of the spaCy [model packages](/models) we distribute, and you can easily create your own model packages with word -vectors you train or download yourself. In some cases you can also add word -vectors to an existing pipeline, although each pipeline can only have a single -word vectors table, and a model package that already has word vectors is -unlikely to work correctly if you replace the vectors with new ones. +vectors you train or download yourself. ## What's a word vector? {#whats-a-vector} @@ -42,6 +31,17 @@ def what_is_a_word_vector( return vectors_table[key2row.get(word_id, default_row)] ``` +An old idea in linguistics is that you can "know a word by the company it +keeps": that is, word meanings can be understood relationally, based on their +patterns of usage. This idea inspired a branch of NLP research known as +"distributional semantics" that has aimed to compute databases of lexical +knowledge automatically. The [Word2vec](https://en.wikipedia.org/wiki/Word2vec) +family of algorithms are a key milestone in this line of research. For +simplicity, we will refer to a distributional word representation as a "word +vector", and algorithms that computes word vectors (such as +[GloVe](https://nlp.stanford.edu/projects/glove/), +[FastText](https://fasttext.cc), etc.) as "Word2vec algorithms". + Word2vec algorithms try to produce vectors tables that let you estimate useful relationships between words using simple linear algebra operations. For instance, you can often find close synonyms of a word by finding the vectors @@ -51,14 +51,15 @@ statistical models. ### Word vectors vs. contextual language models {#vectors-vs-language-models} -The key difference between word vectors and contextual language models such as -ElMo, BERT and GPT-2 is that word vectors model **lexical types**, rather than -_tokens_. If you have a list of terms with no context around them, a model like -BERT can't really help you. BERT is designed to understand language **in -context**, which isn't what you have. A word vectors table will be a much better -fit for your task. However, if you do have words in context — whole sentences or -paragraphs of running text — word vectors will only provide a very rough -approximation of what the text is about. +The key difference between word vectors and contextual language models such +as [transformers](/usage/transformers) +is that word vectors model **lexical types**, rather than +_tokens_. If you have a list of terms with no context around them, +a transformer model like BERT can't really help you. BERT is designed to understand +language **in context**, which isn't what you have. A word vectors table will be +a much better fit for your task. However, if you do have words in context — whole +sentences or paragraphs of running text — word vectors will only provide a very +rough approximation of what the text is about. Word vectors are also very computationally efficient, as they map a word to a vector with a single indexing operation. Word vectors are therefore useful as a @@ -69,7 +70,7 @@ gradients to the pretrained word vectors table. The static vectors table is usually used in combination with a smaller table of learned task-specific embeddings. -## Using word vectors directly {#vectors} +## Using word vectors {#usage} spaCy stores word vector information in the [`Vocab.vectors`](/api/vocab#attributes) attribute, so you can access the whole @@ -85,7 +86,141 @@ whether you've configured spaCy to use GPU memory), with dtype `float32`. The array is read-only so that spaCy can avoid unnecessary copy operations where possible. You can modify the vectors via the `Vocab` or `Vectors` table. -### Converting word vectors for use in spaCy +### Word vectors and similarity + +A common use-case of word vectors is to answer _similarity questions_. You can +ask how similar a `token`, `span`, `doc` or `lexeme` is to another object using +the `.similarity()` method. You can even check the similarity of mismatched +types, asking how similar a whole document is to a particular word, how similar +a span is to a document, etc. By default, the `.similarity()` method will use +return the cosine of the `.vector` attribute of the two objects being compared. +You can customize this behavior by setting one or more +[user hooks](/usage/processing-pipelines#custom-components-user-hooks) for the +types you want to customize. + +Word vector similarity is a practical technique for many situations, especially +since it's easy to use and relatively efficient to compute. However, it's +important to maintain realistic expectations about what information it can +provide. Words can be related to each over in many ways, so a single +"similarity" score will always be a mix of different signals. The word vectors +model is also not trained for your specific use-case, so you have no way of +telling it which results are more or less useful for your purpose. These +problems are even more accute when you go from measuring the similarity of +single words to the similarity of spans or documents. The vector averaging +process is insensitive to the order of the words, so `doc1.similarity(doc2)` +will mostly be based on the overlap in lexical items between the two documents +objects. Two documents expressing the same meaning with dissimilar wording will +return a lower similarity score than two documents that happen to contain the +same words while expressing different meanings. + +### Using word vectors in your models + +Many neural network models are able to use word vector tables as additional +features, which sometimes results in significant improvements in accuracy. +spaCy's built-in embedding layer, `spacy.MultiHashEmbed.v1`, can be configured +to use word vector tables using the `also_use_static_vectors` flag. This +setting is also available on the `spacy.MultiHashEmbedCNN.v1` layer, which +builds the default token-to-vector encoding architecture. + +``` +[tagger.model.tok2vec.embed] +@architectures = "spacy.MultiHashEmbed.v1" +width = 128 +rows = 7000 +also_embed_subwords = true +also_use_static_vectors = true +``` + + +The configuration system will look up the string `spacy.MultiHashEmbed.v1` +in the `architectures` registry, and call the returned object with the +rest of the arguments from the block. This will result in a call to the +`spacy.ml.models.tok2vec.MultiHashEmbed` function, which will return +a Thinc model object with the type signature `Model[List[Doc], +List[Floats2d]]`. Because the embedding layer takes a list of `Doc` objects as +input, it does not need to store a copy of the vectors table. The vectors will +be retrieved from the `Doc` objects that are passed in, via the +`doc.vocab.vectors` attribute. This part of the process is handled by the +`spacy.ml.staticvectors.StaticVectors` layer. + + +#### Creating a custom embedding layer + +The `MultiHashEmbed` layer is spaCy's recommended strategy for constructing +initial word representations for your neural network models, but you can also +implement your own. You can register any function to a string name, and then +reference that function within your config (see the [training]("/usage/training") +section for more details). To try this out, you can save the following little +example to a new Python file: + +``` +from spacy.ml.staticvectors import StaticVectors +from spacy.util import registry + +print("I was imported!") + +@registry.architectures("my_example.MyEmbedding.v1") +def MyEmbedding(output_width: int) -> Model[List[Doc], List[Floats2d]]: + print("I was called!") + return StaticVectors(nO=output_width) +``` + +If you pass the path to your file to the `spacy train` command using the `-c` +argument, your file will be imported, which means the decorator registering the +function will be run. Your function is now on equal footing with any of spaCy's +built-ins, so you can drop it in instead of any other model with the same input +and output signature. For instance, you could use it in the tagger model as +follows: + +``` +[tagger.model.tok2vec.embed] +@architectures = "my_example.MyEmbedding.v1" +output_width = 128 +``` + +Now that you have a custom function wired into the network, you can start +implementing the logic you're interested in. For example, let's say you want to +try a relatively simple embedding strategy that makes use of static word vectors, +but combines them via summation with a smaller table of learned embeddings. + +```python +from thinc.api import add, chain, remap_ids, Embed +from spacy.ml.staticvectors import StaticVectors + +@registry.architectures("my_example.MyEmbedding.v1") +def MyCustomVectors( + output_width: int, + vector_width: int, + embed_rows: int, + key2row: Dict[int, int] +) -> Model[List[Doc], List[Floats2d]]: + return add( + StaticVectors(nO=output_width), + chain( + FeatureExtractor(["ORTH"]), + remap_ids(key2row), + Embed(nO=output_width, nV=embed_rows) + ) + ) +``` + +#### When should you add word vectors to your model? + +Word vectors are not compatible with most [transformer models](/usage/transformers), +but if you're training another type of NLP network, it's almost always worth +adding word vectors to your model. As well as improving your final accuracy, +word vectors often make experiments more consistent, as the accuracy you +reach will be less sensitive to how the network is randomly initialized. High +variance due to random chance can slow down your progress significantly, as you +need to run many experiments to filter the signal from the noise. + +Word vector features need to be enabled prior to training, and the same word vectors +table will need to be available at runtime as well. You cannot add word vector +features once the model has already been trained, and you usually cannot +replace one word vectors table with another without causing a significant loss +of performance. + +## Converting word vectors for use in spaCy {#converting} Custom word vectors can be trained using a number of open-source libraries, such as [Gensim](https://radimrehurek.com/gensim), [Fast Text](https://fasttext.cc), @@ -185,6 +320,13 @@ vector among those retained. ### Adding vectors {#adding-vectors} +You can also add word vectors individually, using the method `vocab.set_vector`. +This is often the easiest approach if you have vectors in an arbitrary format, +as you can read in the vectors with your own logic, and just set them with +a simple loop. This method is likely to be slower than approaches that work +with the whole vectors table at once, but it's a great approach for once-off +conversions before you save out your model to disk. + ```python ### Adding vectors from spacy.vocab import Vocab @@ -196,29 +338,3 @@ vocab = Vocab() for word, vector in vector_data.items(): vocab.set_vector(word, vector) ``` - -### Using custom similarity methods {#custom-similarity} - -By default, [`Token.vector`](/api/token#vector) returns the vector for its -underlying [`Lexeme`](/api/lexeme), while [`Doc.vector`](/api/doc#vector) and -[`Span.vector`](/api/span#vector) return an average of the vectors of their -tokens. You can customize these behaviors by modifying the `doc.user_hooks`, -`doc.user_span_hooks` and `doc.user_token_hooks` dictionaries. - - - -For more details on **adding hooks** and **overwriting** the built-in `Doc`, -`Span` and `Token` methods, see the usage guide on -[user hooks](/usage/processing-pipelines#custom-components-user-hooks). - - - - - -## Other embeddings {#embeddings} - - From 3ae5e02f4f063c495e2029ca8932853395f8a86f Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Mon, 17 Aug 2020 16:45:24 +0200 Subject: [PATCH 07/92] Update docs, types and API consistency --- spacy/cli/evaluate.py | 2 +- spacy/displacy/__init__.py | 2 +- spacy/language.py | 16 +- spacy/scorer.py | 13 +- spacy/tests/pipeline/test_pipe_factories.py | 6 +- spacy/tokens/doc.pyx | 3 +- spacy/util.py | 9 + website/README.md | 91 +++- website/docs/api/architectures.md | 228 +++++----- website/docs/api/attributeruler.md | 128 +++--- website/docs/api/cli.md | 6 +- website/docs/api/corpus.md | 34 +- website/docs/api/cython-classes.md | 110 ++--- website/docs/api/cython-structs.md | 154 +++---- website/docs/api/data-formats.md | 120 ++--- website/docs/api/dependencymatcher.md | 78 ++-- website/docs/api/dependencyparser.md | 190 ++++---- website/docs/api/doc.md | 319 +++++++------- website/docs/api/docbin.md | 66 +-- website/docs/api/entitylinker.md | 147 +++---- website/docs/api/entityrecognizer.md | 182 ++++---- website/docs/api/entityruler.md | 120 ++--- website/docs/api/example.md | 181 ++++---- website/docs/api/kb.md | 156 +++---- website/docs/api/language.md | 464 ++++++++++---------- website/docs/api/lemmatizer.md | 165 ++++--- website/docs/api/lexeme.md | 132 +++--- website/docs/api/lookups.md | 134 +++--- website/docs/api/matcher.md | 110 ++--- website/docs/api/morphanalysis.md | 142 ------ website/docs/api/morphologizer.md | 186 ++++---- website/docs/api/morphology.md | 209 +++++++-- website/docs/api/phrasematcher.md | 62 +-- website/docs/api/pipe.md | 216 ++++----- website/docs/api/pipeline-functions.md | 26 +- website/docs/api/scorer.md | 109 ++--- website/docs/api/sentencerecognizer.md | 174 ++++---- website/docs/api/sentencizer.md | 70 +-- website/docs/api/span.md | 240 +++++----- website/docs/api/stringstore.md | 80 ++-- website/docs/api/tagger.md | 194 ++++---- website/docs/api/textcategorizer.md | 222 +++++----- website/docs/api/tok2vec.md | 140 +++--- website/docs/api/token.md | 302 ++++++------- website/docs/api/tokenizer.md | 136 +++--- website/docs/api/top-level.md | 386 ++++++++-------- website/docs/api/transformer.md | 226 +++++----- website/docs/api/vectors.md | 186 ++++---- website/docs/api/vocab.md | 161 +++---- website/docs/usage/101/_architecture.md | 22 +- website/docs/usage/linguistic-features.md | 2 +- website/docs/usage/models.md | 16 +- website/docs/usage/processing-pipelines.md | 39 +- website/docs/usage/rule-based-matching.md | 49 ++- website/docs/usage/transformers.md | 68 +-- website/docs/usage/v3.md | 1 + website/docs/usage/visualizers.md | 20 +- website/meta/sidebars.json | 1 - website/meta/type-annotations.json | 43 ++ website/src/components/code.js | 51 ++- website/src/styles/code.module.sass | 32 ++ website/src/styles/layout.sass | 9 + website/src/styles/table.module.sass | 3 +- website/src/templates/index.js | 3 +- 64 files changed, 3678 insertions(+), 3514 deletions(-) delete mode 100644 website/docs/api/morphanalysis.md create mode 100644 website/meta/type-annotations.json diff --git a/spacy/cli/evaluate.py b/spacy/cli/evaluate.py index cf8f513fc..3847c74f3 100644 --- a/spacy/cli/evaluate.py +++ b/spacy/cli/evaluate.py @@ -70,7 +70,7 @@ def evaluate( corpus = Corpus(data_path, gold_preproc=gold_preproc) nlp = util.load_model(model) dev_dataset = list(corpus(nlp)) - scores = nlp.evaluate(dev_dataset, verbose=False) + scores = nlp.evaluate(dev_dataset) metrics = { "TOK": "token_acc", "TAG": "tag_acc", diff --git a/spacy/displacy/__init__.py b/spacy/displacy/__init__.py index 3f885f09f..2df2bd61c 100644 --- a/spacy/displacy/__init__.py +++ b/spacy/displacy/__init__.py @@ -18,7 +18,7 @@ RENDER_WRAPPER = None def render( - docs: Union[Iterable[Doc], Doc], + docs: Union[Iterable[Union[Doc, Span]], Doc, Span], style: str = "dep", page: bool = False, minify: bool = False, diff --git a/spacy/language.py b/spacy/language.py index b67c55e3b..bf3bdb9aa 100644 --- a/spacy/language.py +++ b/spacy/language.py @@ -439,8 +439,6 @@ class Language: assigns: Iterable[str] = tuple(), requires: Iterable[str] = tuple(), retokenizes: bool = False, - scores: Iterable[str] = tuple(), - default_score_weights: Dict[str, float] = SimpleFrozenDict(), func: Optional[Callable[[Doc], Doc]] = None, ) -> Callable: """Register a new pipeline component. Can be used for stateless function @@ -456,12 +454,6 @@ class Language: e.g. "token.ent_id". Used for pipeline analyis. retokenizes (bool): Whether the component changes the tokenization. Used for pipeline analysis. - scores (Iterable[str]): All scores set by the component if it's trainable, - e.g. ["ents_f", "ents_r", "ents_p"]. - default_score_weights (Dict[str, float]): The scores to report during - training, and their default weight towards the final score used to - select the best model. Weights should sum to 1.0 per component and - will be combined and normalized for the whole pipeline. func (Optional[Callable]): Factory function if not used as a decorator. DOCS: https://spacy.io/api/language#component @@ -482,8 +474,6 @@ class Language: assigns=assigns, requires=requires, retokenizes=retokenizes, - scores=scores, - default_score_weights=default_score_weights, func=factory_func, ) return component_func @@ -1112,7 +1102,6 @@ class Language: self, examples: Iterable[Example], *, - verbose: bool = False, batch_size: int = 256, scorer: Optional[Scorer] = None, component_cfg: Optional[Dict[str, Dict[str, Any]]] = None, @@ -1121,7 +1110,6 @@ class Language: """Evaluate a model's pipeline components. examples (Iterable[Example]): `Example` objects. - verbose (bool): Print debugging information. batch_size (int): Batch size to use. scorer (Optional[Scorer]): Scorer to use. If not passed in, a new one will be created. @@ -1140,7 +1128,6 @@ class Language: scorer_cfg = {} if scorer is None: kwargs = dict(scorer_cfg) - kwargs.setdefault("verbose", verbose) kwargs.setdefault("nlp", self) scorer = Scorer(**kwargs) texts = [eg.reference.text for eg in examples] @@ -1163,8 +1150,7 @@ class Language: docs = list(docs) end_time = timer() for i, (doc, eg) in enumerate(zip(docs, examples)): - if verbose: - print(doc) + util.logger.debug(doc) eg.predicted = doc results = scorer.score(examples) n_words = sum(len(eg.predicted) for eg in examples) diff --git a/spacy/scorer.py b/spacy/scorer.py index d77881ad0..dc017f82f 100644 --- a/spacy/scorer.py +++ b/spacy/scorer.py @@ -2,7 +2,7 @@ from typing import Optional, Iterable, Dict, Any, Callable, Tuple, TYPE_CHECKING import numpy as np from .gold import Example -from .tokens import Token, Doc +from .tokens import Token, Doc, Span from .errors import Errors from .util import get_lang_class from .morphology import Morphology @@ -250,15 +250,16 @@ class Scorer: examples: Iterable[Example], attr: str, *, - getter: Callable[[Doc, str], Any] = getattr, + getter: Callable[[Doc, str], Iterable[Span]] = getattr, **cfg, ) -> Dict[str, Any]: """Returns PRF scores for labeled spans. examples (Iterable[Example]): Examples to score attr (str): The attribute to score. - getter (Callable[[Doc, str], Any]): Defaults to getattr. If provided, - getter(doc, attr) should return the spans for the individual doc. + getter (Callable[[Doc, str], Iterable[Span]]): Defaults to getattr. If + provided, getter(doc, attr) should return the spans for the + individual doc. RETURNS (Dict[str, Any]): A dictionary containing the PRF scores under the keys attr_p/r/f and the per-type PRF scores under attr_per_type. @@ -444,7 +445,7 @@ class Scorer: *, getter: Callable[[Token, str], Any] = getattr, head_attr: str = "head", - head_getter: Callable[[Token, str], Any] = getattr, + head_getter: Callable[[Token, str], Token] = getattr, ignore_labels: Tuple[str] = tuple(), **cfg, ) -> Dict[str, Any]: @@ -458,7 +459,7 @@ class Scorer: individual token. head_attr (str): The attribute containing the head token. Defaults to 'head'. - head_getter (Callable[[Token, str], Any]): Defaults to getattr. If provided, + head_getter (Callable[[Token, str], Token]): Defaults to getattr. If provided, head_getter(token, attr) should return the value of the head for an individual token. ignore_labels (Tuple): Labels to ignore while scoring (e.g., punct). diff --git a/spacy/tests/pipeline/test_pipe_factories.py b/spacy/tests/pipeline/test_pipe_factories.py index 9948f6bcd..aa682fefe 100644 --- a/spacy/tests/pipeline/test_pipe_factories.py +++ b/spacy/tests/pipeline/test_pipe_factories.py @@ -356,13 +356,13 @@ def test_language_factories_combine_score_weights(weights, expected): def test_language_factories_scores(): name = "test_language_factories_scores" - func = lambda doc: doc + func = lambda nlp, name: lambda doc: doc weights1 = {"a1": 0.5, "a2": 0.5} weights2 = {"b1": 0.2, "b2": 0.7, "b3": 0.1} - Language.component( + Language.factory( f"{name}1", scores=list(weights1), default_score_weights=weights1, func=func, ) - Language.component( + Language.factory( f"{name}2", scores=list(weights2), default_score_weights=weights2, func=func, ) meta1 = Language.get_factory_meta(f"{name}1") diff --git a/spacy/tokens/doc.pyx b/spacy/tokens/doc.pyx index 15dafb86d..d37423e2f 100644 --- a/spacy/tokens/doc.pyx +++ b/spacy/tokens/doc.pyx @@ -102,8 +102,7 @@ cdef class Doc: Construction 2 >>> from spacy.tokens import Doc - >>> doc = Doc(nlp.vocab, words=[u'hello', u'world', u'!'], - >>> spaces=[True, False, False]) + >>> doc = Doc(nlp.vocab, words=["hello", "world", "!"], spaces=[True, False, False]) DOCS: https://spacy.io/api/doc """ diff --git a/spacy/util.py b/spacy/util.py index 3cf165a4f..e8f78f2f2 100644 --- a/spacy/util.py +++ b/spacy/util.py @@ -886,6 +886,15 @@ def escape_html(text: str) -> str: def get_words_and_spaces( words: Iterable[str], text: str ) -> Tuple[List[str], List[bool]]: + """Given a list of words and a text, reconstruct the original tokens and + return a list of words and spaces that can be used to create a Doc. This + can help recover destructive tokenization that didn't preserve any + whitespace information. + + words (Iterable[str]): The words. + text (str): The original text. + RETURNS (Tuple[List[str], List[bool]]): The words and spaces. + """ if "".join("".join(words).split()) != "".join(text.split()): raise ValueError(Errors.E194.format(text=text, words=words)) text_words = [] diff --git a/website/README.md b/website/README.md index c1f6e5805..f3a64d1cb 100644 --- a/website/README.md +++ b/website/README.md @@ -75,7 +75,8 @@ import { H1, H2, H3, H4, H5, Label, InlineList, Comment } from Headlines are set in [HK Grotesk](http://cargocollective.com/hanken/HK-Grotesk-Open-Source-Font) by Hanken Design. All other body text and code uses the best-matching default -system font to provide a "native" reading experience. +system font to provide a "native" reading experience. All code uses the +[JetBrains Mono](https://www.jetbrains.com/lp/mono/) typeface by JetBrains. @@ -106,7 +107,7 @@ Tags are also available as standalone `` components. | Argument | Example | Result | | -------- | -------------------------- | ----------------------------------------- | | `tag` | `{tag="method"}` | method | -| `new` | `{new="2"}` | 2 | +| `new` | `{new="3"}` | 3 | | `model` | `{model="tagger, parser"}` | tagger, parser | | `hidden` | `{hidden="true"}` | | @@ -130,6 +131,8 @@ Special link styles are used depending on the link URL. - [I am a regular external link](https://explosion.ai) - [I am a link to the documentation](/api/doc) +- [I am a link to an architecture](/api/architectures#HashEmbedCNN) +- [I am a link to a model](/models/en#en_core_web_sm) - [I am a link to GitHub](https://github.com/explosion/spaCy) ### Abbreviations {#abbr} @@ -188,18 +191,20 @@ the buttons are implemented as styled links instead of native button elements. +
+ ## Components -### Table +### Table {#table} > #### Markdown > > ```markdown_ > | Header 1 | Header 2 | -> | --- | --- | +> | -------- | -------- | > | Column 1 | Column 2 | > ``` > @@ -213,7 +218,7 @@ the buttons are implemented as styled links instead of native button elements. > ``` Tables are used to present data and API documentation. Certain keywords can be -used to mark a footer row with a distinct style, for example to visualise the +used to mark a footer row with a distinct style, for example to visualize the return values of a documented function. | Header 1 | Header 2 | Header 3 | Header 4 | @@ -224,7 +229,73 @@ return values of a documented function. | Column 1 | Column 2 | Column 3 | Column 4 | | **RETURNS** | Column 2 | Column 3 | Column 4 | -### List +Tables also support optional "divider" rows that are typically used to denote +keyword-only arguments in API documentation. To turn a row into a dividing +headline, it should only include content in its first cell, and its value should +be italicized: + +> #### Markdown +> +> ```markdown_ +> | Header 1 | Header 2 | Header 3 | +> | -------- | -------- | -------- | +> | Column 1 | Column 2 | Column 3 | +> | _Hello_ | | | +> | Column 1 | Column 2 | Column 3 | +> ``` + +| Header 1 | Header 2 | Header 3 | +| -------- | -------- | -------- | +| Column 1 | Column 2 | Column 3 | +| _Hello_ | | | +| Column 1 | Column 2 | Column 3 | + +### Type Annotations {#type-annotations} + +> #### Markdown +> +> ```markdown_ +> ~~Model[List[Doc], Floats2d]~~ +> ``` +> +> #### JSX +> +> ```markup +> Model[List[Doc], Floats2d] +> ``` + +Type annotations are special inline code blocks are used to describe Python +types in the [type hints](https://docs.python.org/3/library/typing.html) format. +The special component will split the type, apply syntax highlighting and link +all types that specify links in `meta/type-annotations.json`. Types can link to +internal or external documentation pages. To make it easy to represent the type +annotations in Markdown, the rendering "hijacks" the `~~` tags that would +typically be converted to a `` element – but in this case, text surrounded +by `~~` becomes a type annotation. + +- ~~Dict[str, List[Union[Doc, Span]]]~~ +- ~~Model[List[Doc], List[numpy.ndarray]]~~ + +Type annotations support a special visual style in tables and will render as a +separate row, under the cell text. This allows the API docs to display complex +types without taking up too much space in the cell. The type annotation should +always be the **last element** in the row. + +> #### Markdown +> +> ```markdown_ +> | Header 1 | Header 2 | +> | -------- | ----------------------- | +> | Column 1 | Column 2 ~~List[Doc]~~ | +> ``` + +| Name | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) wrapping the transformer. ~~Model[List[Doc], FullTransformerBatch]~~ | +| `annotation_setter` | Function that takes a batch of `Doc` objects and transformer outputs can set additional annotations on the `Doc`. ~~Callable[[List[Doc], FullTransformerBatch], None]~~ | + +### List {#list} > #### Markdown > @@ -255,7 +326,7 @@ automatically. 3. Lorem ipsum dolor 4. consectetur adipiscing elit -### Aside +### Aside {#aside} > #### Markdown > @@ -280,7 +351,7 @@ To make them easier to use in Markdown, paragraphs formatted as blockquotes will turn into asides by default. Level 4 headlines (with a leading `####`) will become aside titles. -### Code Block +### Code Block {#code-block} > #### Markdown > @@ -387,7 +458,7 @@ original file is shown at the top of the widget. https://github.com/explosion/spaCy/tree/master/spacy/language.py ``` -### Infobox +### Infobox {#infobox} import Infobox from 'components/infobox' @@ -425,7 +496,7 @@ blocks.
-### Accordion +### Accordion {#accordion} import Accordion from 'components/accordion' diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index cc6f44fcc..3bc2ab578 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -33,18 +33,18 @@ TODO: intro and how architectures work, link to > subword_features = true > ``` -Build spaCy's 'standard' tok2vec layer, which uses hash embedding with subword +Build spaCy's "standard" tok2vec layer, which uses hash embedding with subword features and a CNN with layer-normalized maxout. -| Name | Type | Description | -| -------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `width` | int | The width of the input and output. These are required to be the same, so that residual connections can be used. Recommended values are `96`, `128` or `300`. | -| `depth` | int | The number of convolutional layers to use. Recommended values are between `2` and `8`. | -| `embed_size` | int | The number of rows in the hash embedding tables. This can be surprisingly small, due to the use of the hash embeddings. Recommended values are between `2000` and `10000`. | -| `window_size` | int | The number of tokens on either side to concatenate during the convolutions. The receptive field of the CNN will be `depth * (window_size * 2 + 1)`, so a 4-layer network with a window size of `2` will be sensitive to 17 words at a time. Recommended value is `1`. | -| `maxout_pieces` | int | The number of pieces to use in the maxout non-linearity. If `1`, the [`Mish`](https://thinc.ai/docs/api-layers#mish) non-linearity is used instead. Recommended values are `1`-`3`. | -| `subword_features` | bool | Whether to also embed subword features, specifically the prefix, suffix and word shape. This is recommended for alphabetic languages like English, but not if single-character tokens are used for a language such as Chinese. | -| `pretrained_vectors` | bool | Whether to also use static vectors. | +| Name | Description | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The width of the input and output. These are required to be the same, so that residual connections can be used. Recommended values are `96`, `128` or `300`. ~~int~~ | +| `depth` | The number of convolutional layers to use. Recommended values are between `2` and `8`. ~~int~~ | +| `embed_size` | The number of rows in the hash embedding tables. This can be surprisingly small, due to the use of the hash embeddings. Recommended values are between `2000` and `10000`. ~~int~~ | +| `window_size` | The number of tokens on either side to concatenate during the convolutions. The receptive field of the CNN will be `depth * (window_size * 2 + 1)`, so a 4-layer network with a window size of `2` will be sensitive to 17 words at a time. Recommended value is `1`. ~~int~~ | +| `maxout_pieces` | The number of pieces to use in the maxout non-linearity. If `1`, the [`Mish`](https://thinc.ai/docs/api-layers#mish) non-linearity is used instead. Recommended values are `1`-`3`. ~~int~~ | +| `subword_features` | Whether to also embed subword features, specifically the prefix, suffix and word shape. This is recommended for alphabetic languages like English, but not if single-character tokens are used for a language such as Chinese. ~~bool~~ | +| `pretrained_vectors` | Whether to also use static vectors. ~~bool~~ | ### spacy.Tok2Vec.v1 {#Tok2Vec} @@ -67,10 +67,10 @@ Construct a tok2vec model out of embedding and encoding subnetworks. See the ["Embed, Encode, Attend, Predict"](https://explosion.ai/blog/deep-learning-formula-nlp) blog post for background. -| Name | Type | Description | -| -------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `embed` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. Embed tokens into context-independent word vector representations. For example, [CharacterEmbed](/api/architectures#CharacterEmbed) or [MultiHashEmbed](/api/architectures#MultiHashEmbed) | -| `encode` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Floats2d]`. **Output:** `List[Floats2d]`. Encode context into the embeddings, using an architecture such as a CNN, BiLSTM or transformer. For example, [MaxoutWindowEncoder](/api/architectures#MaxoutWindowEncoder). | +| Name | Description | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `embed` | Embed tokens into context-independent word vector representations. For example, [CharacterEmbed](/api/architectures#CharacterEmbed) or [MultiHashEmbed](/api/architectures#MultiHashEmbed). ~~Model[List[Doc], List[Floats2d]]~~ | +| `encode` | Encode context into the embeddings, using an architecture such as a CNN, BiLSTM or transformer. For example, [MaxoutWindowEncoder](/api/architectures#MaxoutWindowEncoder). ~~Model[List[Floats2d], List[Floats2d]]~~ | ### spacy.Tok2VecListener.v1 {#Tok2VecListener} @@ -108,10 +108,10 @@ Instead of defining its own `Tok2Vec` instance, a model architecture like [Tagger](/api/architectures#tagger) can define a listener as its `tok2vec` argument that connects to the shared `tok2vec` component in the pipeline. -| Name | Type | Description | -| ---------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `width` | int | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. | -| `upstream` | str | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. | +| Name | Description | +| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. ~~int~~ | +| `upstream` | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. ~~str~~ | ### spacy.MultiHashEmbed.v1 {#MultiHashEmbed} @@ -134,12 +134,12 @@ definitions depending on the `Vocab` of the `Doc` object passed in. Vectors from pretrained static vectors can also be incorporated into the concatenated representation. -| Name | Type | Description | -| ------------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `width` | int | The output width. Also used as the width of the embedding tables. Recommended values are between `64` and `300`. | -| `rows` | int | The number of rows for the embedding tables. Can be low, due to the hashing trick. Embeddings for prefix, suffix and word shape use half as many rows. Recommended values are between `2000` and `10000`. | -| `also_embed_subwords` | bool | Whether to use the `PREFIX`, `SUFFIX` and `SHAPE` features in the embeddings. If not using these, you may need more rows in your hash embeddings, as there will be increased chance of collisions. | -| `also_use_static_vectors` | bool | Whether to also use static word vectors. Requires a vectors table to be loaded in the [Doc](/api/doc) objects' vocab. | +| Name | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The output width. Also used as the width of the embedding tables. Recommended values are between `64` and `300`. ~~int~~ | +| `rows` | The number of rows for the embedding tables. Can be low, due to the hashing trick. Embeddings for prefix, suffix and word shape use half as many rows. Recommended values are between `2000` and `10000`. ~~int~~ | +| `also_embed_subwords` | Whether to use the `PREFIX`, `SUFFIX` and `SHAPE` features in the embeddings. If not using these, you may need more rows in your hash embeddings, as there will be increased chance of collisions. ~~bool~~ | +| `also_use_static_vectors` | Whether to also use static word vectors. Requires a vectors table to be loaded in the [Doc](/api/doc) objects' vocab. ~~bool~~ | ### spacy.CharacterEmbed.v1 {#CharacterEmbed} @@ -170,12 +170,12 @@ concatenated. A hash-embedded vector of the `NORM` of the word is also concatenated on, and the result is then passed through a feed-forward network to construct a single vector to represent the information. -| Name | Type | Description | -| ------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `width` | int | The width of the output vector and the `NORM` hash embedding. | -| `rows` | int | The number of rows in the `NORM` hash embedding table. | -| `nM` | int | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. | -| `nC` | int | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. | +| Name | Description | +| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The width of the output vector and the `NORM` hash embedding. ~~int~~ | +| `rows` | The number of rows in the `NORM` hash embedding table. ~~int~~ | +| `nM` | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. ~~int~~ | +| `nC` | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. ~~int~~ | ### spacy.MaxoutWindowEncoder.v1 {#MaxoutWindowEncoder} @@ -193,12 +193,12 @@ construct a single vector to represent the information. Encode context using convolutions with maxout activation, layer normalization and residual connections. -| Name | Type | Description | -| --------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `width` | int | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. | -| `window_size` | int | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. | -| `maxout_pieces` | int | The number of maxout pieces to use. Recommended values are `2` or `3`. | -| `depth` | int | The number of convolutional layers. Recommended value is `4`. | +| Name | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. ~~int~~ | +| `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~ | +| `maxout_pieces` | The number of maxout pieces to use. Recommended values are `2` or `3`. ~~int~~ | +| `depth` | The number of convolutional layers. Recommended value is `4`. ~~int~~ | ### spacy.MishWindowEncoder.v1 {#MishWindowEncoder} @@ -216,11 +216,11 @@ Encode context using convolutions with [`Mish`](https://thinc.ai/docs/api-layers#mish) activation, layer normalization and residual connections. -| Name | Type | Description | -| ------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `width` | int | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. | -| `window_size` | int | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. | -| `depth` | int | The number of convolutional layers. Recommended value is `4`. | +| Name | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. ~~int~~ | +| `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~ | +| `depth` | The number of convolutional layers. Recommended value is `4`. ~~int~~ | ### spacy.TorchBiLSTMEncoder.v1 {#TorchBiLSTMEncoder} @@ -237,11 +237,11 @@ and residual connections. Encode context using bidirectional LSTM layers. Requires [PyTorch](https://pytorch.org). -| Name | Type | Description | -| ------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `width` | int | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. | -| `window_size` | int | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. | -| `depth` | int | The number of convolutional layers. Recommended value is `4`. | +| Name | Description | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `width` | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. ~~int~~ | +| `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~ | +| `depth` | The number of convolutional layers. Recommended value is `4`. ~~int~~ | ## Transformer architectures {#transformers source="github.com/explosion/spacy-transformers/blob/master/spacy_transformers/architectures.py"} @@ -268,11 +268,11 @@ architectures into your training config. -| Name | Type | Description | -| ------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | Any model name that can be loaded by [`transformers.AutoModel`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoModel). | -| `get_spans` | `Callable` | Function that takes a batch of [`Doc`](/api/doc) object and returns lists of [`Span`](/api) objects to process by the transformer. [See here](/api/transformer#span_getters) for built-in options and examples. | -| `tokenizer_config` | `Dict[str, Any]` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). | +| Name | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Any model name that can be loaded by [`transformers.AutoModel`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoModel). ~~str~~ | +| `get_spans` | Function that takes a batch of [`Doc`](/api/doc) object and returns lists of [`Span`](/api) objects to process by the transformer. [See here](/api/transformer#span_getters) for built-in options and examples. ~~Callable[[List[Doc]], List[Span]]~~ | +| `tokenizer_config` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). ~~Dict[str, Any]~~ | ### spacy-transformers.Tok2VecListener.v1 {#transformers-Tok2VecListener} @@ -297,10 +297,10 @@ operate over wordpieces, which usually don't align one-to-one against spaCy tokens. The layer therefore requires a reduction operation in order to calculate a single token vector given zero or more wordpiece vectors. -| Name | Type | Description | -| ------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `pooling` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** [`Ragged`](https://thinc.ai/docs/api-types#ragged). **Output:** [`Floats2d`](https://thinc.ai/docs/api-types#types) | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. | -| `grad_factor` | float | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. | +| Name | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pooling` | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. ~~Model[Ragged, Floats2d]~~ | +| `grad_factor` | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. ~~float~~ | ### spacy-transformers.Tok2VecTransformer.v1 {#Tok2VecTransformer} @@ -320,12 +320,12 @@ Use a transformer as a [`Tok2Vec`](/api/tok2vec) layer directly. This does object, but it's a **simpler solution** if you only need the transformer within one component. -| Name | Type | Description | -| ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `get_spans` | callable | Function that takes a batch of [`Doc`](/api/doc) object and returns lists of [`Span`](/api) objects to process by the transformer. [See here](/api/transformer#span_getters) for built-in options and examples. | -| `tokenizer_config` | `Dict[str, Any]` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). | -| `pooling` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** [`Ragged`](https://thinc.ai/docs/api-types#ragged). **Output:** [`Floats2d`](https://thinc.ai/docs/api-types#types) | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. | -| `grad_factor` | float | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. | +| Name | Description | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `get_spans` | Function that takes a batch of [`Doc`](/api/doc) object and returns lists of [`Span`](/api) objects to process by the transformer. [See here](/api/transformer#span_getters) for built-in options and examples. ~~Callable[[List[Doc]], List[Span]]~~ | +| `tokenizer_config` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). ~~Dict[str, Any]~~ | +| `pooling` | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. ~~Model[Ragged, Floats2d]~~ | +| `grad_factor` | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. ~~float~~ | ## Parser & NER architectures {#parser} @@ -368,14 +368,14 @@ consists of either two or three subnetworks: state representation. If not present, the output from the lower model is used as action scores directly. -| Name | Type | Description | -| ------------------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. Subnetwork to map tokens into vector representations. | -| `nr_feature_tokens` | int | The number of tokens in the context to use to construct the state vector. Valid choices are `1`, `2`, `3`, `6`, `8` and `13`. The `2`, `8` and `13` feature sets are designed for the parser, while the `3` and `6` feature sets are designed for the entity recognizer. The recommended feature sets are `3` for NER, and `8` for the dependency parser. | -| `hidden_width` | int | The width of the hidden layer. | -| `maxout_pieces` | int | How many pieces to use in the state prediction layer. Recommended values are `1`, `2` or `3`. If `1`, the maxout non-linearity is replaced with a [`Relu`](https://thinc.ai/docs/api-layers#relu) non-linearity if `use_upper` is `True`, and no non-linearity if `False`. | -| `use_upper` | bool | Whether to use an additional hidden layer after the state vector in order to predict the action scores. It is recommended to set this to `False` for large pretrained models such as transformers, and `True` for smaller networks. The upper layer is computed on CPU, which becomes a bottleneck on larger GPU-based models, where it's also less necessary. | -| `nO` | int | The number of actions the model will predict between. Usually inferred from data at the beginning of training, or loaded from disk. | +| Name | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ | +| `nr_feature_tokens` | The number of tokens in the context to use to construct the state vector. Valid choices are `1`, `2`, `3`, `6`, `8` and `13`. The `2`, `8` and `13` feature sets are designed for the parser, while the `3` and `6` feature sets are designed for the entity recognizer. The recommended feature sets are `3` for NER, and `8` for the dependency parser. ~~int~~ | +| `hidden_width` | The width of the hidden layer. ~~int~~ | +| `maxout_pieces` | How many pieces to use in the state prediction layer. Recommended values are `1`, `2` or `3`. If `1`, the maxout non-linearity is replaced with a [`Relu`](https://thinc.ai/docs/api-layers#relu) non-linearity if `use_upper` is `True`, and no non-linearity if `False`. ~~int~~ | +| `use_upper` | Whether to use an additional hidden layer after the state vector in order to predict the action scores. It is recommended to set this to `False` for large pretrained models such as transformers, and `True` for smaller networks. The upper layer is computed on CPU, which becomes a bottleneck on larger GPU-based models, where it's also less necessary. ~~bool~~ | +| `nO` | The number of actions the model will predict between. Usually inferred from data at the beginning of training, or loaded from disk. ~~int~~ | ### spacy.BILUOTagger.v1 {#BILUOTagger source="spacy/ml/models/simple_ner.py"} @@ -402,9 +402,9 @@ generally results in better linear separation between classes, especially for non-CRF models, because there are more distinct classes for the different situations ([Ratinov et al., 2009](https://www.aclweb.org/anthology/W09-1119/)). -| Name | Type | Description | -| --------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. Subnetwork to map tokens into vector representations. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------ | +| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.IOBTagger.v1 {#IOBTagger source="spacy/ml/models/simple_ner.py"} @@ -427,9 +427,9 @@ spans into tags assigned to each token. The first token of a span is given the tag B-LABEL, and subsequent tokens are given the tag I-LABEL. All other tokens are assigned the tag O. -| Name | Type | Description | -| --------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. Subnetwork to map tokens into vector representations. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------ | +| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ | ## Tagging architectures {#tagger source="spacy/ml/models/tagger.py"} @@ -450,10 +450,10 @@ Build a tagger model, using a provided token-to-vector component. The tagger model simply adds a linear layer with softmax activation to predict scores given the token vectors. -| Name | Type | Description | -| --------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------- | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. Subnetwork to map tokens into vector representations. | -| `nO` | int | The number of tags to output. Inferred from the data if `None`. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------ | +| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ | +| `nO` | The number of tags to output. Inferred from the data if `None`. ~~Optional[int]~~ | ## Text classification architectures {#textcat source="spacy/ml/models/textcat.py"} @@ -489,18 +489,17 @@ network has an internal CNN Tok2Vec layer and uses attention. > nO = null > ``` -| Name | Type | Description | -| --------------------------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `pretrained_vectors` | bool | Whether or not pretrained vectors will be used in addition to the feature vectors. | -| `width` | int | Output dimension of the feature encoding step. | -| `embed_size` | int | Input dimension of the feature encoding step. | -| `conv_depth` | int | Depth of the Tok2Vec layer. | -| `window_size` | int | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. | -| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | -| `dropout` | float | The dropout rate. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when | -| `begin_training` is called. | +| Name | Description | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | +| `pretrained_vectors` | Whether or not pretrained vectors will be used in addition to the feature vectors. ~~bool~~ | +| `width` | Output dimension of the feature encoding step. ~~int~~ | +| `embed_size` | Input dimension of the feature encoding step. ~~int~~ | +| `conv_depth` | Depth of the tok2vec layer. ~~int~~ | +| `window_size` | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. ~~int~~ | +| `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | +| `dropout` | The dropout rate. ~~float~~ | +| `nO` | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | ### spacy.TextCatCNN.v1 {#TextCatCNN} @@ -527,11 +526,11 @@ A neural network model where token vectors are calculated using a CNN. The vectors are mean pooled and used as features in a feed-forward network. This architecture is usually less accurate than the ensemble, but runs faster. -| Name | Type | Description | -| ------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | The [`tok2vec`](#tok2vec) layer of the model. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | +| Name | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | +| `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | +| `nO` | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | ### spacy.TextCatBOW.v1 {#TextCatBOW} @@ -549,18 +548,18 @@ architecture is usually less accurate than the ensemble, but runs faster. An ngram "bag-of-words" model. This architecture should run much faster than the others, but may not be as accurate, especially if texts are short. -| Name | Type | Description | -| ------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `exclusive_classes` | bool | Whether or not categories are mutually exclusive. | -| `ngram_size` | int | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. | -| `no_output_layer` | float | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes=True`, else `Logistic`. | -| `nO` | int | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. | +| Name | Description | +| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | +| `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | +| `no_output_layer` | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~ | +| `nO` | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"} An [`EntityLinker`](/api/entitylinker) component disambiguates textual mentions (tagged as named entities) to unique identifiers, grounding the named entities -into the "real world". This requires 3 main components: +into the "real world". This requires 3 main component - A [`KnowledgeBase`](/api/kb) (KB) holding the unique identifiers, potential synonyms and prior probabilities. @@ -571,8 +570,8 @@ into the "real world". This requires 3 main components: ### spacy.EntityLinker.v1 {#EntityLinker} -The `EntityLinker` model architecture is a `Thinc` `Model` with a Linear output -layer. +The `EntityLinker` model architecture is a Thinc `Model` with a +[`Linear`](https://thinc.ai/api-layers#linear) output layer. > #### Example Config > @@ -599,27 +598,24 @@ layer. > @assets = "spacy.CandidateGenerator.v1" > ``` -| Name | Type | Description | -| --------- | ------------------------------------------ | ---------------------------------------------------------------------------------------- | -| `tok2vec` | [`Model`](https://thinc.ai/docs/api-model) | The [`tok2vec`](#tok2vec) layer of the model. | -| `nO` | int | Output dimension, determined by the length of the vectors encoding each entity in the KB | - -If the `nO` dimension is not set, the Entity Linking component will set it when -`begin_training` is called. +| Name | Description | +| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | +| `nO` | Output dimension, determined by the length of the vectors encoding each entity in the KB. If the `nO` dimension is not set, the entity linking component will set it when `begin_training` is called. ~~Optional[int]~~ | ### spacy.EmptyKB.v1 {#EmptyKB} A function that creates a default, empty `KnowledgeBase` from a [`Vocab`](/api/vocab) instance. -| Name | Type | Description | -| ---------------------- | ---- | ------------------------------------------------------------------------- | -| `entity_vector_length` | int | The length of the vectors encoding each entity in the KB - 64 by default. | +| Name | Description | +| ---------------------- | ----------------------------------------------------------------------------------- | +| `entity_vector_length` | The length of the vectors encoding each entity in the KB. Defaults to `64`. ~~int~~ | ### spacy.CandidateGenerator.v1 {#CandidateGenerator} A function that takes as input a [`KnowledgeBase`](/api/kb) and a [`Span`](/api/span) object denoting a named entity, and returns a list of -plausible [`Candidate` objects](/api/kb/#candidate_init). The default +plausible [`Candidate`](/api/kb/#candidate) objects. The default `CandidateGenerator` simply uses the text of a mention to find its potential aliases in the `KnowledgeBase`. Note that this function is case-dependent. diff --git a/website/docs/api/attributeruler.md b/website/docs/api/attributeruler.md index e2f009cad..98f267e87 100644 --- a/website/docs/api/attributeruler.md +++ b/website/docs/api/attributeruler.md @@ -31,10 +31,10 @@ how the component should be configured. You can override its settings via the > nlp.add_pipe("attribute_ruler", config=config) > ``` -| Setting | Type | Description | Default | -| --------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `pattern_dicts` | `Iterable[dict]` | A list of pattern dicts with the keys as the arguments to [`AttributeRuler.add`](#add) (`patterns`/`attrs`/`index`) to add as patterns. | `None` | -| `validate` | bool | Whether patterns should be validated (passed to the `Matcher`). | `False` | +| Setting | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `pattern_dicts` | A list of pattern dicts with the keys as the arguments to [`AttributeRuler.add`](/api/attributeruler#add) (`patterns`/`attrs`/`index`) to add as patterns. Defaults to `None`. ~~Optional[Iterable[Dict[str, Union[List[dict], dict, int]]]]~~ | +| `validate` | Whether patterns should be validated (passed to the `Matcher`). Defaults to `False`. ~~bool~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/attributeruler.py @@ -47,10 +47,10 @@ be a list of dictionaries with `"patterns"`, `"attrs"`, and optional `"index"` keys, e.g.: ```python -pattern_dicts = \[ - {"patterns": \[\[{"TAG": "VB"}\]\], "attrs": {"POS": "VERB"}}, - {"patterns": \[\[{"LOWER": "an"}\]\], "attrs": {"LEMMA": "a"}}, -\] +pattern_dicts = [ + {"patterns": [[{"TAG": "VB"}]], "attrs": {"POS": "VERB"}}, + {"patterns": [[{"LOWER": "an"}]], "attrs": {"LEMMA": "a"}}, +] ``` > #### Example @@ -60,23 +60,23 @@ pattern_dicts = \[ > attribute_ruler = nlp.add_pipe("attribute_ruler") > ``` -| Name | Type | Description | -| --------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared nlp object to pass the vocab to the matchers and process phrase patterns. | -| `name` | str | Instance name of the current pipeline component. Typically passed in automatically from the factory when the component is added. Used to disable the current entity ruler while creating phrase patterns with the nlp object. | -| _keyword-only_ | | | -| `pattern_dicts` | `Iterable[Dict]]` | Optional patterns to load in on initialization. Defaults to `None`. | -| `validate` | bool | Whether patterns should be validated (passed to the `Matcher`). Defaults to `False`. | +| Name | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary to pass to the matcher. ~~Vocab~~ | +| `name` | Instance name of the current pipeline component. Typically passed in automatically from the factory when the component is added. ~~str~~ | +| _keyword-only_ | | +| `pattern_dicts` | Optional patterns to load in on initialization. Defaults to `None`. ~~Optional[Iterable[Dict[str, Union[List[dict], dict, int]]]]~~ | +| `validate` | Whether patterns should be validated (passed to the [`Matcher`](/api/matcher#init)). Defaults to `False`. ~~bool~~ | ## AttributeRuler.\_\_call\_\_ {#call tag="method"} Apply the attribute ruler to a Doc, setting token attributes for tokens matched by the provided patterns. -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| **RETURNS** | `Doc` | The modified `Doc` with added entities, if available. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## AttributeRuler.add {#add tag="method"} @@ -95,11 +95,11 @@ may be negative to index from the end of the span. > attribute_ruler.add(patterns=patterns, attrs=attrs) > ``` -| Name | Type | Description | -| -------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| patterns | `Iterable[List[Dict]]` | A list of Matcher patterns. | -| attrs | dict | The attributes to assign to the target token in the matched span. | -| index | int | The index of the token in the matched span to modify. May be negative to index from the end of the span. Defaults to 0. | +| Name | Description | +| ---------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `patterns` | The `Matcher` patterns to add. ~~Iterable[List[Dict[Union[int, str], Any]]]~~ | +| `attrs` | The attributes to assign to the target token in the matched span. ~~Dict[str, Any]~~ | +| `index` | The index of the token in the matched span to modify. May be negative to index from the end of the span. Defaults to `0`. ~~int~~ | ## AttributeRuler.add_patterns {#add_patterns tag="method"} @@ -107,52 +107,52 @@ may be negative to index from the end of the span. > > ```python > attribute_ruler = nlp.add_pipe("attribute_ruler") -> pattern_dicts = \[ +> pattern_dicts = [ > { -> "patterns": \[\[{"TAG": "VB"}\]\], +> "patterns": [[{"TAG": "VB"}]], > "attrs": {"POS": "VERB"} > }, > { -> "patterns": \[\[{"LOWER": "two"}, {"LOWER": "apples"}\]\], +> "patterns": [[{"LOWER": "two"}, {"LOWER": "apples"}]], > "attrs": {"LEMMA": "apple"}, > "index": -1 > }, -> \] +> ] > attribute_ruler.add_patterns(pattern_dicts) > ``` Add patterns from a list of pattern dicts with the keys as the arguments to -[`AttributeRuler.add`](#add). +[`AttributeRuler.add`](/api/attributeruler#add). -| Name | Type | Description | -| --------------- | ----------------- | -------------------- | -| `pattern_dicts` | `Iterable[Dict]]` | The patterns to add. | +| Name | Description | +| --------------- | -------------------------------------------------------------------------- | +| `pattern_dicts` | The patterns to add. ~~Iterable[Dict[str, Union[List[dict], dict, int]]]~~ | ## AttributeRuler.patterns {#patterns tag="property"} Get all patterns that have been added to the attribute ruler in the `patterns_dict` format accepted by -[`AttributeRuler.add_patterns`](#add_patterns). +[`AttributeRuler.add_patterns`](/api/attributeruler#add_patterns). -| Name | Type | Description | -| ----------- | ------------ | ------------------------------------------ | -| **RETURNS** | `List[dict]` | The patterns added to the attribute ruler. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------- | +| **RETURNS** | The patterns added to the attribute ruler. ~~List[Dict[str, Union[List[dict], dict, int]]]~~ | ## AttributeRuler.load_from_tag_map {#load_from_tag_map tag="method"} Load attribute ruler patterns from a tag map. -| Name | Type | Description | -| --------- | ---- | ------------------------------------------------------------------------------------------ | -| `tag_map` | dict | The tag map that maps fine-grained tags to coarse-grained tags and morphological features. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `tag_map` | The tag map that maps fine-grained tags to coarse-grained tags and morphological features. ~~Dict[str, Dict[Union[int, str], Union[int, str]]]~~ | ## AttributeRuler.load_from_morph_rules {#load_from_morph_rules tag="method"} Load attribute ruler patterns from morph rules. -| Name | Type | Description | -| ------------- | ---- | -------------------------------------------------------------------------------------------------------------------- | -| `morph_rules` | dict | The morph rules that map token text and fine-grained tags to coarse-grained tags, lemmas and morphological features. | +| Name | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `morph_rules` | The morph rules that map token text and fine-grained tags to coarse-grained tags, lemmas and morphological features. ~~Dict[str, Dict[str, Dict[Union[int, str], Union[int, str]]]]~~ | ## AttributeRuler.to_disk {#to_disk tag="method"} @@ -165,11 +165,11 @@ Serialize the pipe to disk. > attribute_ruler.to_disk("/path/to/attribute_ruler") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## AttributeRuler.from_disk {#from_disk tag="method"} @@ -182,12 +182,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > attribute_ruler.from_disk("/path/to/attribute_ruler") > ``` -| Name | Type | Description | -| -------------- | ---------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `AttributeRuler` | The modified `AttributeRuler` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `AttributeRuler` object. ~~AttributeRuler~~ | ## AttributeRuler.to_bytes {#to_bytes tag="method"} @@ -200,11 +200,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `AttributeRuler` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `AttributeRuler` object. ~~bytes~~ | ## AttributeRuler.from_bytes {#from_bytes tag="method"} @@ -218,12 +218,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > attribute_ruler.from_bytes(attribute_ruler_bytes) > ``` -| Name | Type | Description | -| -------------- | ---------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `AttributeRuler` | The `AttributeRuler` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `AttributeRuler` object. ~~AttributeRuler~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md index be7a2b499..c9e2a28c1 100644 --- a/website/docs/api/cli.md +++ b/website/docs/api/cli.md @@ -598,9 +598,9 @@ $ python -m spacy debug model ./config.cfg tagger -l "5,15" -DIM -PAR -P0 -P1 -P | Argument | Type | Description | | ----------------------- | ---------- | ----------------------------------------------------------------------------------------------------- | -| `config_path` | positional | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. | | -| `component` | positional | Name of the pipeline component of which the model should be analyzed. |   | -| `--layers`, `-l` | option | Comma-separated names of layer IDs to print. | | +| `config_path` | positional | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. | +| `component` | positional | Name of the pipeline component of which the model should be analyzed. | +| `--layers`, `-l` | option | Comma-separated names of layer IDs to print. | | `--dimensions`, `-DIM` | option | Show dimensions of each layer. | | `--parameters`, `-PAR` | option | Show parameters of each layer. | | `--gradients`, `-GRAD` | option | Show gradients of each layer. | diff --git a/website/docs/api/corpus.md b/website/docs/api/corpus.md index 5f9fd49db..3631e201e 100644 --- a/website/docs/api/corpus.md +++ b/website/docs/api/corpus.md @@ -34,12 +34,12 @@ streaming. > limit = 0 > ``` -| Name | Type | Description | -| --------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `path` | `Path` | The directory or filename to read from. Expects data in spaCy's binary [`.spacy` format](/api/data-formats#binary-training). | -|  `gold_preproc` | bool | Whether to set up the Example object with gold-standard sentences and tokens for the predictions. See [`Corpus`](/api/corpus#init) for details. | -| `max_length` | int | Maximum document length. Longer documents will be split into sentences, if sentence boundaries are available. Defaults to `0` for no limit. | -| `limit` | int | Limit corpus to a subset of examples, e.g. for debugging. Defaults to `0` for no limit. | +| Name | Description | +| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `path` | The directory or filename to read from. Expects data in spaCy's binary [`.spacy` format](/api/data-formats#binary-training). ~~Path~~ | +|  `gold_preproc` | Whether to set up the Example object with gold-standard sentences and tokens for the predictions. See [`Corpus`](/api/corpus#init) for details. ~~bool~~ | +| `max_length` | Maximum document length. Longer documents will be split into sentences, if sentence boundaries are available. Defaults to `0` for no limit. ~~int~~ | +| `limit` | Limit corpus to a subset of examples, e.g. for debugging. Defaults to `0` for no limit. ~~int~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/gold/corpus.py @@ -67,13 +67,13 @@ train/test skew. > corpus = Corpus("./data", limit=10) > ``` -| Name | Type | Description | -| --------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | The directory or filename to read from. | -| _keyword-only_ | | | -|  `gold_preproc` | bool | Whether to set up the Example object with gold-standard sentences and tokens for the predictions. Defaults to `False`. | -| `max_length` | int | Maximum document length. Longer documents will be split into sentences, if sentence boundaries are available. Defaults to `0` for no limit. | -| `limit` | int | Limit corpus to a subset of examples, e.g. for debugging. Defaults to `0` for no limit. | +| Name | Description | +| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `path` | The directory or filename to read from. ~~Union[str, Path]~~ | +| _keyword-only_ | | +|  `gold_preproc` | Whether to set up the Example object with gold-standard sentences and tokens for the predictions. Defaults to `False`. ~~bool~~ | +| `max_length` | Maximum document length. Longer documents will be split into sentences, if sentence boundaries are available. Defaults to `0` for no limit. ~~int~~ | +| `limit` | Limit corpus to a subset of examples, e.g. for debugging. Defaults to `0` for no limit. ~~int~~ | ## Corpus.\_\_call\_\_ {#call tag="method"} @@ -90,7 +90,7 @@ Yield examples from the data. > train_data = corpus(nlp) > ``` -| Name | Type | Description | -| ---------- | ---------- | ------------------------- | -| `nlp` | `Language` | The current `nlp` object. | -| **YIELDS** | `Example` | The examples. | +| Name | Description | +| ---------- | -------------------------------------- | +| `nlp` | The current `nlp` object. ~~Language~~ | +| **YIELDS** | The examples. ~~Example~~ | diff --git a/website/docs/api/cython-classes.md b/website/docs/api/cython-classes.md index 6e54fb112..a4ecf294a 100644 --- a/website/docs/api/cython-classes.md +++ b/website/docs/api/cython-classes.md @@ -23,13 +23,13 @@ accessed from Python. For the Python documentation, see [`Doc`](/api/doc). ### Attributes {#doc_attributes} -| Name | Type | Description | -| ------------ | ------------ | ----------------------------------------------------------------------------------------- | -| `mem` | `cymem.Pool` | A memory pool. Allocated memory will be freed once the `Doc` object is garbage collected. | -| `vocab` | `Vocab` | A reference to the shared `Vocab` object. | -| `c` | `TokenC*` | A pointer to a [`TokenC`](/api/cython-structs#tokenc) struct. | -| `length` | `int` | The number of tokens in the document. | -| `max_length` | `int` | The underlying size of the `Doc.c` array. | +| Name | Description | +| ------------ | -------------------------------------------------------------------------------------------------------- | +| `mem` | A memory pool. Allocated memory will be freed once the `Doc` object is garbage collected. ~~cymem.Pool~~ | +| `vocab` | A reference to the shared `Vocab` object. ~~Vocab~~ | +| `c` | A pointer to a [`TokenC`](/api/cython-structs#tokenc) struct. ~~TokenC\*~~ | +| `length` | The number of tokens in the document. ~~int~~ | +| `max_length` | The underlying size of the `Doc.c` array. ~~int~~ | ### Doc.push_back {#doc_push_back tag="method"} @@ -50,10 +50,10 @@ Append a token to the `Doc`. The token can be provided as a > assert doc.text == "hello " > ``` -| Name | Type | Description | -| ------------ | --------------- | ----------------------------------------- | -| `lex_or_tok` | `LexemeOrToken` | The word to append to the `Doc`. | -| `has_space` | `bint` | Whether the word has trailing whitespace. | +| Name | Description | +| ------------ | -------------------------------------------------- | +| `lex_or_tok` | The word to append to the `Doc`. ~~LexemeOrToken~~ | +| `has_space` | Whether the word has trailing whitespace. ~~bint~~ | ## Token {#token tag="cdef class" source="spacy/tokens/token.pxd"} @@ -70,12 +70,12 @@ accessed from Python. For the Python documentation, see [`Token`](/api/token). ### Attributes {#token_attributes} -| Name | Type | Description | -| ------- | --------- | ------------------------------------------------------------- | -| `vocab` | `Vocab` | A reference to the shared `Vocab` object. | -| `c` | `TokenC*` | A pointer to a [`TokenC`](/api/cython-structs#tokenc) struct. | -| `i` | `int` | The offset of the token within the document. | -| `doc` | `Doc` | The parent document. | +| Name | Description | +| ------- | -------------------------------------------------------------------------- | +| `vocab` | A reference to the shared `Vocab` object. ~~Vocab~~ | +| `c` | A pointer to a [`TokenC`](/api/cython-structs#tokenc) struct. ~~TokenC\*~~ | +| `i` | The offset of the token within the document. ~~int~~ | +| `doc` | The parent document. ~~Doc~~ | ### Token.cinit {#token_cinit tag="method"} @@ -87,12 +87,12 @@ Create a `Token` object from a `TokenC*` pointer. > token = Token.cinit(&doc.c[3], doc, 3) > ``` -| Name | Type | Description | -| -------- | --------- | ------------------------------------------------------------ | -| `vocab` | `Vocab` | A reference to the shared `Vocab`. | -| `c` | `TokenC*` | A pointer to a [`TokenC`](/api/cython-structs#tokenc)struct. | -| `offset` | `int` | The offset of the token within the document. | -| `doc` | `Doc` | The parent document. | +| Name | Description | +| -------- | -------------------------------------------------------------------------- | +| `vocab` | A reference to the shared `Vocab`. ~~Vocab~~ | +| `c` | A pointer to a [`TokenC`](/api/cython-structs#tokenc) struct. ~~TokenC\*~~ | +| `offset` | The offset of the token within the document. ~~int~~ | +| `doc` | The parent document. ~~int~~ | ## Span {#span tag="cdef class" source="spacy/tokens/span.pxd"} @@ -107,14 +107,14 @@ accessed from Python. For the Python documentation, see [`Span`](/api/span). ### Attributes {#span_attributes} -| Name | Type | Description | -| ------------ | -------------------------------------- | ------------------------------------------------------- | -| `doc` | `Doc` | The parent document. | -| `start` | `int` | The index of the first token of the span. | -| `end` | `int` | The index of the first token after the span. | -| `start_char` | `int` | The index of the first character of the span. | -| `end_char` | `int` | The index of the last character of the span. | -| `label` | `attr_t` | A label to attach to the span, e.g. for named entities. | +| Name | Description | +| ------------ | ----------------------------------------------------------------------------- | +| `doc` | The parent document. ~~Doc~~ | +| `start` | The index of the first token of the span. ~~int~~ | +| `end` | The index of the first token after the span. ~~int~~ | +| `start_char` | The index of the first character of the span. ~~int~~ | +| `end_char` | The index of the last character of the span. ~~int~~ | +| `label` | A label to attach to the span, e.g. for named entities. ~~attr_t (uint64_t)~~ | ## Lexeme {#lexeme tag="cdef class" source="spacy/lexeme.pxd"} @@ -129,11 +129,11 @@ accessed from Python. For the Python documentation, see [`Lexeme`](/api/lexeme). ### Attributes {#lexeme_attributes} -| Name | Type | Description | -| ------- | -------------------------------------- | --------------------------------------------------------------- | -| `c` | `LexemeC*` | A pointer to a [`LexemeC`](/api/cython-structs#lexemec) struct. | -| `vocab` | `Vocab` | A reference to the shared `Vocab` object. | -| `orth` | `attr_t` | ID of the verbatim text content. | +| Name | Description | +| ------- | ----------------------------------------------------------------------------- | +| `c` | A pointer to a [`LexemeC`](/api/cython-structs#lexemec) struct. ~~LexemeC\*~~ | +| `vocab` | A reference to the shared `Vocab` object. ~~Vocab~~ | +| `orth` | ID of the verbatim text content. ~~attr_t (uint64_t)~~ | ## Vocab {#vocab tag="cdef class" source="spacy/vocab.pxd"} @@ -149,11 +149,11 @@ accessed from Python. For the Python documentation, see [`Vocab`](/api/vocab). ### Attributes {#vocab_attributes} -| Name | Type | Description | -| --------- | ------------- | ------------------------------------------------------------------------------------------- | -| `mem` | `cymem.Pool` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. | -| `strings` | `StringStore` | A `StringStore` that maps string to hash values and vice versa. | -| `length` | `int` | The number of entries in the vocabulary. | +| Name | Description | +| --------- | ---------------------------------------------------------------------------------------------------------- | +| `mem` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. ~~cymem.Pool~~ | +| `strings` | A `StringStore` that maps string to hash values and vice versa. ~~StringStore~~ | +| `length` | The number of entries in the vocabulary. ~~int~~ | ### Vocab.get {#vocab_get tag="method"} @@ -166,11 +166,11 @@ vocabulary. > lexeme = vocab.get(vocab.mem, "hello") > ``` -| Name | Type | Description | -| ----------- | ---------------- | ------------------------------------------------------------------------------------------- | -| `mem` | `cymem.Pool` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. | -| `string` | str | The string of the word to look up. | -| **RETURNS** | `const LexemeC*` | The lexeme in the vocabulary. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------- | +| `mem` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. ~~cymem.Pool~~ | +| `string` | The string of the word to look up. ~~str~~ | +| **RETURNS** | The lexeme in the vocabulary. ~~const LexemeC\*~~ | ### Vocab.get_by_orth {#vocab_get_by_orth tag="method"} @@ -183,11 +183,11 @@ vocabulary. > lexeme = vocab.get_by_orth(doc[0].lex.norm) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | ------------------------------------------------------------------------------------------- | -| `mem` | `cymem.Pool` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. | -| `orth` | `attr_t` | ID of the verbatim text content. | -| **RETURNS** | `const LexemeC*` | The lexeme in the vocabulary. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------- | +| `mem` | A memory pool. Allocated memory will be freed once the `Vocab` object is garbage collected. ~~cymem.Pool~~ | +| `orth` | ID of the verbatim text content. ~~attr_t (uint64_t)~~ | +| **RETURNS** | The lexeme in the vocabulary. ~~const LexemeC\*~~ | ## StringStore {#stringstore tag="cdef class" source="spacy/strings.pxd"} @@ -203,7 +203,7 @@ accessed from Python. For the Python documentation, see ### Attributes {#stringstore_attributes} -| Name | Type | Description | -| ------ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -| `mem` | `cymem.Pool` | A memory pool. Allocated memory will be freed once the`StringStore` object is garbage collected. | -| `keys` | `vector[hash_t]` | A list of hash values in the `StringStore`. | +| Name | Description | +| ------ | ---------------------------------------------------------------------------------------------------------------- | +| `mem` | A memory pool. Allocated memory will be freed once the `StringStore` object is garbage collected. ~~cymem.Pool~~ | +| `keys` | A list of hash values in the `StringStore`. ~~vector[hash_t] \(vector[uint64_t])~~ | diff --git a/website/docs/api/cython-structs.md b/website/docs/api/cython-structs.md index 8ee1f1b9a..4c8514b64 100644 --- a/website/docs/api/cython-structs.md +++ b/website/docs/api/cython-structs.md @@ -18,26 +18,26 @@ Cython data container for the `Token` object. > token_ptr = &doc.c[3] > ``` -| Name | Type | Description | -| ------------ | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `lex` | `const LexemeC*` | A pointer to the lexeme for the token. | -| `morph` | `uint64_t` | An ID allowing lookup of morphological attributes. | -| `pos` | `univ_pos_t` | Coarse-grained part-of-speech tag. | -| `spacy` | `bint` | A binary value indicating whether the token has trailing whitespace. | -| `tag` | `attr_t` | Fine-grained part-of-speech tag. | -| `idx` | `int` | The character offset of the token within the parent document. | -| `lemma` | `attr_t` | Base form of the token, with no inflectional suffixes. | -| `sense` | `attr_t` | Space for storing a word sense ID, currently unused. | -| `head` | `int` | Offset of the syntactic parent relative to the token. | -| `dep` | `attr_t` | Syntactic dependency relation. | -| `l_kids` | `uint32_t` | Number of left children. | -| `r_kids` | `uint32_t` | Number of right children. | -| `l_edge` | `uint32_t` | Offset of the leftmost token of this token's syntactic descendants. | -| `r_edge` | `uint32_t` | Offset of the rightmost token of this token's syntactic descendants. | -| `sent_start` | `int` | Ternary value indicating whether the token is the first word of a sentence. `0` indicates a missing value, `-1` indicates `False` and `1` indicates `True`. The default value, 0, is interpreted as no sentence break. Sentence boundary detectors will usually set 0 for all tokens except tokens that follow a sentence boundary. | -| `ent_iob` | `int` | IOB code of named entity tag. `0` indicates a missing value, `1` indicates `I`, `2` indicates `0` and `3` indicates `B`. | -| `ent_type` | `attr_t` | Named entity type. | -| `ent_id` | `attr_t` | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. | +| Name | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `lex` | A pointer to the lexeme for the token. ~~const LexemeC\*~~ | +| `morph` | An ID allowing lookup of morphological attributes. ~~uint64_t~~ | +| `pos` | Coarse-grained part-of-speech tag. ~~univ_pos_t~~ | +| `spacy` | A binary value indicating whether the token has trailing whitespace. ~~bint~~ | +| `tag` | Fine-grained part-of-speech tag. ~~attr_t (uint64_t)~~ | +| `idx` | The character offset of the token within the parent document. ~~int~~ | +| `lemma` | Base form of the token, with no inflectional suffixes. ~~attr_t (uint64_t)~~ | +| `sense` | Space for storing a word sense ID, currently unused. ~~attr_t (uint64_t)~~ | +| `head` | Offset of the syntactic parent relative to the token. ~~int~~ | +| `dep` | Syntactic dependency relation. ~~attr_t (uint64_t)~~ | +| `l_kids` | Number of left children. ~~uint32_t~~ | +| `r_kids` | Number of right children. ~~uint32_t~~ | +| `l_edge` | Offset of the leftmost token of this token's syntactic descendants. ~~uint32_t~~ | +| `r_edge` | Offset of the rightmost token of this token's syntactic descendants. ~~uint32_t~~ | +| `sent_start` | Ternary value indicating whether the token is the first word of a sentence. `0` indicates a missing value, `-1` indicates `False` and `1` indicates `True`. The default value, 0, is interpreted as no sentence break. Sentence boundary detectors will usually set 0 for all tokens except tokens that follow a sentence boundary. ~~int~~ | +| `ent_iob` | IOB code of named entity tag. `0` indicates a missing value, `1` indicates `I`, `2` indicates `0` and `3` indicates `B`. ~~int~~ | +| `ent_type` | Named entity type. ~~attr_t (uint64_t)~~ | +| `ent_id` | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. ~~attr_t (uint64_t)~~ | ### Token.get_struct_attr {#token_get_struct_attr tag="staticmethod, nogil" source="spacy/tokens/token.pxd"} @@ -52,11 +52,11 @@ Get the value of an attribute from the `TokenC` struct by attribute ID. > is_alpha = Token.get_struct_attr(&doc.c[3], IS_ALPHA) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | -------------------------------------------------------------------------------------- | -| `token` | `const TokenC*` | A pointer to a `TokenC` struct. | -| `feat_name` | `attr_id_t` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. | -| **RETURNS** | `attr_t` | The value of the attribute. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| `token` | A pointer to a `TokenC` struct. ~~const TokenC\*~~ | +| `feat_name` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| **RETURNS** | The value of the attribute. ~~attr_t (uint64_t)~~ | ### Token.set_struct_attr {#token_set_struct_attr tag="staticmethod, nogil" source="spacy/tokens/token.pxd"} @@ -72,11 +72,11 @@ Set the value of an attribute of the `TokenC` struct by attribute ID. > Token.set_struct_attr(token, TAG, 0) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | -------------------------------------------------------------------------------------- | -| `token` | `const TokenC*` | A pointer to a `TokenC` struct. | -| `feat_name` | `attr_id_t` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. | -| `value` | `attr_t` | The value to set. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| `token` | A pointer to a `TokenC` struct. ~~const TokenC\*~~ | +| `feat_name` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| `value` | The value to set. ~~attr_t (uint64_t)~~ | ### token_by_start {#token_by_start tag="function" source="spacy/tokens/doc.pxd"} @@ -93,12 +93,12 @@ Find a token in a `TokenC*` array by the offset of its first character. > assert token_by_start(doc.c, doc.length, 4) == -1 > ``` -| Name | Type | Description | -| ------------ | --------------- | --------------------------------------------------------- | -| `tokens` | `const TokenC*` | A `TokenC*` array. | -| `length` | `int` | The number of tokens in the array. | -| `start_char` | `int` | The start index to search for. | -| **RETURNS** | `int` | The index of the token in the array or `-1` if not found. | +| Name | Description | +| ------------ | ----------------------------------------------------------------- | +| `tokens` | A `TokenC*` array. ~~const TokenC\*~~ | +| `length` | The number of tokens in the array. ~~int~~ | +| `start_char` | The start index to search for. ~~int~~ | +| **RETURNS** | The index of the token in the array or `-1` if not found. ~~int~~ | ### token_by_end {#token_by_end tag="function" source="spacy/tokens/doc.pxd"} @@ -115,12 +115,12 @@ Find a token in a `TokenC*` array by the offset of its final character. > assert token_by_end(doc.c, doc.length, 1) == -1 > ``` -| Name | Type | Description | -| ----------- | --------------- | --------------------------------------------------------- | -| `tokens` | `const TokenC*` | A `TokenC*` array. | -| `length` | `int` | The number of tokens in the array. | -| `end_char` | `int` | The end index to search for. | -| **RETURNS** | `int` | The index of the token in the array or `-1` if not found. | +| Name | Description | +| ----------- | ----------------------------------------------------------------- | +| `tokens` | A `TokenC*` array. ~~const TokenC\*~~ | +| `length` | The number of tokens in the array. ~~int~~ | +| `end_char` | The end index to search for. ~~int~~ | +| **RETURNS** | The index of the token in the array or `-1` if not found. ~~int~~ | ### set_children_from_heads {#set_children_from_heads tag="function" source="spacy/tokens/doc.pxd"} @@ -143,10 +143,10 @@ attribute, in order to make the parse tree navigation consistent. > assert doc.c[3].l_kids == 1 > ``` -| Name | Type | Description | -| -------- | --------------- | ---------------------------------- | -| `tokens` | `const TokenC*` | A `TokenC*` array. | -| `length` | `int` | The number of tokens in the array. | +| Name | Description | +| -------- | ------------------------------------------ | +| `tokens` | A `TokenC*` array. ~~const TokenC\*~~ | +| `length` | The number of tokens in the array. ~~int~~ | ## LexemeC {#lexemec tag="C struct" source="spacy/structs.pxd"} @@ -160,17 +160,17 @@ struct. > lex = doc.c[3].lex > ``` -| Name | Type | Description | -| ----------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `flags` | `flags_t` | Bit-field for binary lexical flag values. | -| `id` | `attr_t` | Usually used to map lexemes to rows in a matrix, e.g. for word vectors. Does not need to be unique, so currently misnamed. | -| `length` | `attr_t` | Number of unicode characters in the lexeme. | -| `orth` | `attr_t` | ID of the verbatim text content. | -| `lower` | `attr_t` | ID of the lowercase form of the lexeme. | -| `norm` | `attr_t` | ID of the lexeme's norm, i.e. a normalized form of the text. | -| `shape` | `attr_t` | Transform of the lexeme's string, to show orthographic features. | -| `prefix` | `attr_t` | Length-N substring from the start of the lexeme. Defaults to `N=1`. | -| `suffix` | `attr_t` | Length-N substring from the end of the lexeme. Defaults to `N=3`. | +| Name | Description | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `flags` | Bit-field for binary lexical flag values. ~~flags_t (uint64_t)~~ | +| `id` | Usually used to map lexemes to rows in a matrix, e.g. for word vectors. Does not need to be unique, so currently misnamed. ~~attr_t (uint64_t)~~ | +| `length` | Number of unicode characters in the lexeme. ~~attr_t (uint64_t)~~ | +| `orth` | ID of the verbatim text content. ~~attr_t (uint64_t)~~ | +| `lower` | ID of the lowercase form of the lexeme. ~~attr_t (uint64_t)~~ | +| `norm` | ID of the lexeme's norm, i.e. a normalized form of the text. ~~attr_t (uint64_t)~~ | +| `shape` | Transform of the lexeme's string, to show orthographic features. ~~attr_t (uint64_t)~~ | +| `prefix` | Length-N substring from the start of the lexeme. Defaults to `N=1`. ~~attr_t (uint64_t)~~ | +| `suffix` | Length-N substring from the end of the lexeme. Defaults to `N=3`. ~~attr_t (uint64_t)~~ | ### Lexeme.get_struct_attr {#lexeme_get_struct_attr tag="staticmethod, nogil" source="spacy/lexeme.pxd"} @@ -186,11 +186,11 @@ Get the value of an attribute from the `LexemeC` struct by attribute ID. > is_alpha = Lexeme.get_struct_attr(lexeme, IS_ALPHA) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | -------------------------------------------------------------------------------------- | -| `lex` | `const LexemeC*` | A pointer to a `LexemeC` struct. | -| `feat_name` | `attr_id_t` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. | -| **RETURNS** | `attr_t` | The value of the attribute. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| `lex` | A pointer to a `LexemeC` struct. ~~const LexemeC\*~~ | +| `feat_name` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| **RETURNS** | The value of the attribute. ~~attr_t (uint64_t)~~ | ### Lexeme.set_struct_attr {#lexeme_set_struct_attr tag="staticmethod, nogil" source="spacy/lexeme.pxd"} @@ -206,11 +206,11 @@ Set the value of an attribute of the `LexemeC` struct by attribute ID. > Lexeme.set_struct_attr(lexeme, NORM, lexeme.lower) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | -------------------------------------------------------------------------------------- | -| `lex` | `const LexemeC*` | A pointer to a `LexemeC` struct. | -| `feat_name` | `attr_id_t` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. | -| `value` | `attr_t` | The value to set. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------- | +| `lex` | A pointer to a `LexemeC` struct. ~~const LexemeC\*~~ | +| `feat_name` | The ID of the attribute to look up. The attributes are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| `value` | The value to set. ~~attr_t (uint64_t)~~ | ### Lexeme.c_check_flag {#lexeme_c_check_flag tag="staticmethod, nogil" source="spacy/lexeme.pxd"} @@ -226,11 +226,11 @@ Check the value of a binary flag attribute. > is_stop = Lexeme.c_check_flag(lexeme, IS_STOP) > ``` -| Name | Type | Description | -| ----------- | ---------------- | ------------------------------------------------------------------------------- | -| `lexeme` | `const LexemeC*` | A pointer to a `LexemeC` struct. | -| `flag_id` | `attr_id_t` | The ID of the flag to look up. The flag IDs are enumerated in `spacy.typedefs`. | -| **RETURNS** | `bint` | The boolean value of the flag. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------- | +| `lexeme` | A pointer to a `LexemeC` struct. ~~const LexemeC\*~~ | +| `flag_id` | The ID of the flag to look up. The flag IDs are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| **RETURNS** | The boolean value of the flag. ~~bint~~ | ### Lexeme.c_set_flag {#lexeme_c_set_flag tag="staticmethod, nogil" source="spacy/lexeme.pxd"} @@ -246,8 +246,8 @@ Set the value of a binary flag attribute. > Lexeme.c_set_flag(lexeme, IS_STOP, 0) > ``` -| Name | Type | Description | -| --------- | ---------------- | ------------------------------------------------------------------------------- | -| `lexeme` | `const LexemeC*` | A pointer to a `LexemeC` struct. | -| `flag_id` | `attr_id_t` | The ID of the flag to look up. The flag IDs are enumerated in `spacy.typedefs`. | -| `value` | `bint` | The value to set. | +| Name | Description | +| --------- | --------------------------------------------------------------------------------------------- | +| `lexeme` | A pointer to a `LexemeC` struct. ~~const LexemeC\*~~ | +| `flag_id` | The ID of the flag to look up. The flag IDs are enumerated in `spacy.typedefs`. ~~attr_id_t~~ | +| `value` | The value to set. ~~bint~~ | diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index 6245c219f..4577d7ef3 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -73,15 +73,15 @@ your config and check that it's valid, you can run the Defines the `nlp` object, its tokenizer and [processing pipeline](/usage/processing-pipelines) component names. -| Name | Type | Description | Default | -| ------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------- | -| `lang` | str | The language code to use. | `null` | -| `pipeline` | `List[str]` | Names of pipeline components in order. Should correspond to sections in the `[components]` block, e.g. `[components.ner]`. See docs on [defining components](/usage/training#config-components). | `[]` | -| `load_vocab_data` | bool | Whether to load additional lexeme and vocab data from [`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data) if available. | `true` | -| `before_creation` | callable | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `Language` subclass before it's initialized. | `null` | -| `after_creation` | callable | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object right after it's initialized. | `null` | -| `after_pipeline_creation` | callable | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object after the pipeline components have been added. | `null` | -| `tokenizer` | callable | The tokenizer to use. | [`Tokenizer`](/api/tokenizer) | +| Name | Description | Default | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | +| `lang` | The language code to use. ~~str~~ | `null` | +| `pipeline` | Names of pipeline components in order. Should correspond to sections in the `[components]` block, e.g. `[components.ner]`. See docs on [defining components](/usage/training#config-components). ~~List[str]~~ | `[]` | +| `load_vocab_data` | Whether to load additional lexeme and vocab data from [`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data) if available. ~~bool~~ | `true` | +| `before_creation` | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `Language` subclass before it's initialized. ~~Optional[Callable[[Type[Language]], Type[Language]]]~~ | `null` | +| `after_creation` | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object right after it's initialized. ~~Optional[Callable[[Language], Language]]~~ | `null` | +| `after_pipeline_creation` | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object after the pipeline components have been added. ~~Optional[Callable[[Language], Language]]~~ | `null` | +| `tokenizer` | The tokenizer to use. ~~Callable[[str], Doc]~~ | [`Tokenizer`](/api/tokenizer) | ### components {#config-components tag="section"} @@ -128,24 +128,24 @@ process that are used when you run [`spacy train`](/api/cli#train). -| Name | Type | Description | Default | -| --------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `seed` | int | The random seed. | `${system:seed}` | -| `dropout` | float | The dropout rate. | `0.1` | -| `accumulate_gradient` | int | Whether to divide the batch up into substeps. | `1` | -| `init_tok2vec` | str | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). | `${paths:init_tok2vec}` | -| `raw_text` | str | | `${paths:raw}` | -| `vectors` | str | | `null` | -| `patience` | int | How many steps to continue without improvement in evaluation score. | `1600` | -| `max_epochs` | int | Maximum number of epochs to train for. | `0` | -| `max_steps` | int | Maximum number of update steps to train for. | `20000` | -| `eval_frequency` | int | How often to evaluate during training (steps). | `200` | -| `score_weights` | `Dict[str, float]` | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. | `{}` | -| `frozen_components` | `List[str]` | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. | `[]` | -| `train_corpus` | callable | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. | [`Corpus`](/api/corpus) | -| `dev_corpus` | callable | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. | [`Corpus`](/api/corpus) | -| `batcher` | callable | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. | [`batch_by_words`](/api/top-level#batch_by_words) | -| `optimizer` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. The learning rate schedule and other settings can be configured as part of the optimizer. | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) | +| Name | Description | Default | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `seed` | The random seed. ~~int~~ | `${system:seed}` | +| `dropout` | The dropout rate. ~~float~~ | `0.1` | +| `accumulate_gradient` | Whether to divide the batch up into substeps. ~~int~~ | `1` | +| `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). ~~Optional[str]~~ | `${paths:init_tok2vec}` | +| `raw_text` | ~~Optional[str]~~ | `${paths:raw}` | +| `vectors` | ~~Optional[str]~~ | `null` | +| `patience` | How many steps to continue without improvement in evaluation score. ~~int~~ | `1600` | +| `max_epochs` | Maximum number of epochs to train for. ~~int~~ | `0` | +| `max_steps` | Maximum number of update steps to train for. ~~int~~ | `20000` | +| `eval_frequency` | How often to evaluate during training (steps). ~~int~~ | `200` | +| `score_weights` | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. ~~Dict[str, float]~~ | `{}` | +| `frozen_components` | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. ~~List[str]~~ | `[]` | +| `train_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. ~~Callable[[Language], Iterator[Example]]~~ | [`Corpus`](/api/corpus) | +| `dev_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. ~~Callable[[Language], Iterator[Example]]~~ | [`Corpus`](/api/corpus) | +| `batcher` | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. ~~Callable[[Iterator[Doc], Iterator[List[Doc]]]]~~ | [`batch_by_words`](/api/top-level#batch_by_words) | +| `optimizer` | The optimizer. The learning rate schedule and other settings can be configured as part of the optimizer. ~~Optimizer~~ | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) | ### pretraining {#config-pretraining tag="section,optional"} @@ -153,19 +153,19 @@ This section is optional and defines settings and controls for [language model pretraining](/usage/training#pretraining). It's used when you run [`spacy pretrain`](/api/cli#pretrain). -| Name | Type | Description | Default | -| ---------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------- | --------------------------------------------------- | -| `max_epochs` | int | Maximum number of epochs. | `1000` | -| `min_length` | int | Minimum length of examples. | `5` | -| `max_length` | int | Maximum length of examples. | `500` | -| `dropout` | float | The dropout rate. | `0.2` | -| `n_save_every` | int | Saving frequency. | `null` | -| `batch_size` | int / `Sequence[int]` | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). | `3000` | -| `seed` | int | The random seed. | `${system.seed}` | -| `use_pytorch_for_gpu_memory` | bool | Allocate memory via PyTorch. | `${system:use_pytorch_for_gpu_memory}` | -| `tok2vec_model` | str | tok2vec model section in the config. | `"components.tok2vec.model"` | -| `objective` | dict | The pretraining objective. | `{"type": "characters", "n_characters": 4}` | -| `optimizer` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) | +| Name | Description | Default | +| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `max_epochs` | Maximum number of epochs. ~~int~~ | `1000` | +| `min_length` | Minimum length of examples. ~~int~~ | `5` | +| `max_length` | Maximum length of examples. ~~int~~ | `500` | +| `dropout` | The dropout rate. ~~float~~ | `0.2` | +| `n_save_every` | Saving frequency. ~~int~~ | `null` | +| `batch_size` | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). ~~Union[int, Sequence[int]]~~ | `3000` | +| `seed` | The random seed. ~~int~~ | `${system.seed}` | +| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. ~~bool~~ | `${system:use_pytorch_for_gpu_memory}` | +| `tok2vec_model` | tok2vec model section in the config. ~~str~~ | `"components.tok2vec.model"` | +| `objective` | The pretraining objective. ~~Dict[str, Any]~~ | `{"type": "characters", "n_characters": 4}` | +| `optimizer` | The optimizer. ~~Optimizer~~ | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) | ## Training data {#training} @@ -313,22 +313,22 @@ to keep track of your settings and hyperparameters and your own > } > ``` -| Name | Type | Description | -| ------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `text` | str | Raw text. | -| `words` | `List[str]` | List of gold-standard tokens. | -| `lemmas` | `List[str]` | List of lemmas. | -| `spaces` | `List[bool]` | List of boolean values indicating whether the corresponding tokens is followed by a space or not. | -| `tags` | `List[str]` | List of fine-grained [POS tags](/usage/linguistic-features#pos-tagging). | -| `pos` | `List[str]` | List of coarse-grained [POS tags](/usage/linguistic-features#pos-tagging). | -| `morphs` | `List[str]` | List of [morphological features](/usage/linguistic-features#rule-based-morphology). | -| `sent_starts` | `List[bool]` | List of boolean values indicating whether each token is the first of a sentence or not. | -| `deps` | `List[str]` | List of string values indicating the [dependency relation](/usage/linguistic-features#dependency-parse) of a token to its head. | -| `heads` | `List[int]` | List of integer values indicating the dependency head of each token, referring to the absolute index of each token in the text. | -| `entities` | `List[str]` | **Option 1:** List of [BILUO tags](/usage/linguistic-features#accessing-ner) per token of the format `"{action}-{label}"`, or `None` for unannotated tokens. | -| `entities` | `List[Tuple[int, int, str]]` | **Option 2:** List of `"(start, end, label)"` tuples defining all entities in the text. | -| `cats` | `Dict[str, float]` | Dictionary of `label`/`value` pairs indicating how relevant a certain [text category](/api/textcategorizer) is for the text. | -| `links` | `Dict[(int, int), Dict]` | Dictionary of `offset`/`dict` pairs defining [named entity links](/usage/linguistic-features#entity-linking). The character offsets are linked to a dictionary of relevant knowledge base IDs. | +| Name | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `text` | Raw text. ~~str~~ | +| `words` | List of gold-standard tokens. ~~List[str]~~ | +| `lemmas` | List of lemmas. ~~List[str]~~ | +| `spaces` | List of boolean values indicating whether the corresponding tokens is followed by a space or not. ~~List[bool]~~ | +| `tags` | List of fine-grained [POS tags](/usage/linguistic-features#pos-tagging). ~~List[str]~~ | +| `pos` | List of coarse-grained [POS tags](/usage/linguistic-features#pos-tagging). ~~List[str]~~ | +| `morphs` | List of [morphological features](/usage/linguistic-features#rule-based-morphology). ~~List[str]~~ | +| `sent_starts` | List of boolean values indicating whether each token is the first of a sentence or not. ~~List[bool]~~ | +| `deps` | List of string values indicating the [dependency relation](/usage/linguistic-features#dependency-parse) of a token to its head. ~~List[str]~~ | +| `heads` | List of integer values indicating the dependency head of each token, referring to the absolute index of each token in the text. ~~List[int]~~ | +| `entities` | **Option 1:** List of [BILUO tags](/usage/linguistic-features#accessing-ner) per token of the format `"{action}-{label}"`, or `None` for unannotated tokens. ~~List[str]~~ | +| `entities` | **Option 2:** List of `"(start, end, label)"` tuples defining all entities in the text. ~~List[Tuple[int, int, str]]~~ | +| `cats` | Dictionary of `label`/`value` pairs indicating how relevant a certain [text category](/api/textcategorizer) is for the text. ~~Dict[str, float]~~ | +| `links` | Dictionary of `offset`/`dict` pairs defining [named entity links](/usage/linguistic-features#entity-linking). The character offsets are linked to a dictionary of relevant knowledge base IDs. ~~Dict[Tuple[int, int], Dict]~~ | @@ -390,10 +390,10 @@ provided. > srsly.write_jsonl("/path/to/text.jsonl", data) > ``` -| Key | Type | Description | -| -------- | ---- | ---------------------------------------------------------- | -| `text` | str | The raw input text. Is not required if `tokens` available. | -| `tokens` | list | Optional tokenization, one string per token. | +| Key | Description | +| -------- | ------------------------------------------------------------------ | +| `text` | The raw input text. Is not required if `tokens` available. ~~str~~ | +| `tokens` | Optional tokenization, one string per token. ~~List[str]~~ | ```json ### Example diff --git a/website/docs/api/dependencymatcher.md b/website/docs/api/dependencymatcher.md index 4f192783f..2fb903100 100644 --- a/website/docs/api/dependencymatcher.md +++ b/website/docs/api/dependencymatcher.md @@ -44,18 +44,18 @@ A pattern added to the `DependencyMatcher` consists of a list of dictionaries, with each dictionary describing a node to match. Each pattern should have the following top-level keys: -| Name | Type | Description | -| --------- | ---- | --------------------------------------------------------------------------------------------------------------------------- | -| `PATTERN` | dict | The token attributes to match in the same format as patterns provided to the regular token-based [`Matcher`](/api/matcher). | -| `SPEC` | dict | The relationships of the nodes in the subtree that should be matched. | +| Name | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `PATTERN` | The token attributes to match in the same format as patterns provided to the regular token-based [`Matcher`](/api/matcher). ~~Dict[str, Any]~~ | +| `SPEC` | The relationships of the nodes in the subtree that should be matched. ~~Dict[str, str]~~ | The `SPEC` includes the following fields: -| Name | Type | Description | -| ------------ | ---- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `NODE_NAME` | str | A unique name for this node to refer to it in other specs. | -| `NBOR_RELOP` | str | A [Semgrex](https://nlp.stanford.edu/nlp/javadoc/javanlp/edu/stanford/nlp/semgraph/semgrex/SemgrexPattern.html) operator that describes how the two nodes are related. | -| `NBOR_NAME` | str | The unique name of the node that this node is connected to. | +| Name | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `NODE_NAME` | A unique name for this node to refer to it in other specs. ~~str~~ | +| `NBOR_RELOP` | A [Semgrex](https://nlp.stanford.edu/nlp/javadoc/javanlp/edu/stanford/nlp/semgraph/semgrex/SemgrexPattern.html) operator that describes how the two nodes are related. ~~str~~ | +| `NBOR_NAME` | The unique name of the node that this node is connected to. ~~str~~ | ## DependencyMatcher.\_\_init\_\_ {#init tag="method"} @@ -68,9 +68,9 @@ Create a rule-based `DependencyMatcher`. > matcher = DependencyMatcher(nlp.vocab) > ``` -| Name | Type | Description | -| ------- | ------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. | +| Name | Description | +| ------- | ----------------------------------------------------------------------------------------------------- | +| `vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. ~~Vocab~~ | ## DependencyMatcher.\_\call\_\_ {#call tag="method"} @@ -79,9 +79,9 @@ Find all token sequences matching the supplied patterns on the `Doc` or `Span`. > #### Example > > ```python -> from spacy.matcher import Matcher +> from spacy.matcher import DependencyMatcher > -> matcher = Matcher(nlp.vocab) +> matcher = DependencyMatcher(nlp.vocab) > pattern = [ > {"SPEC": {"NODE_NAME": "founded"}, "PATTERN": {"ORTH": "founded"}}, > {"SPEC": {"NODE_NAME": "founder", "NBOR_RELOP": ">", "NBOR_NAME": "founded"}, "PATTERN": {"DEP": "nsubj"}}, @@ -91,10 +91,10 @@ Find all token sequences matching the supplied patterns on the `Doc` or `Span`. > matches = matcher(doc) > ``` -| Name | Type | Description | -| ----------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `doclike` | `Doc`/`Span` | The `Doc` or `Span` to match over. | -| **RETURNS** | list | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. The `match_id` is the ID of the added match pattern. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `doclike` | The `Doc` or `Span` to match over. ~~Union[Doc, Span]~~ | +| **RETURNS** | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. The `match_id` is the ID of the added match pattern. ~~List[Tuple[int, int, int]]~~ | ## DependencyMatcher.\_\_len\_\_ {#len tag="method"} @@ -115,9 +115,9 @@ number of individual patterns. > assert len(matcher) == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------- | -| **RETURNS** | int | The number of rules. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The number of rules. ~~int~~ | ## DependencyMatcher.\_\_contains\_\_ {#contains tag="method"} @@ -132,10 +132,10 @@ Check whether the matcher contains rules for a match ID. > assert "Rule" in matcher > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------------------------- | -| `key` | str | The match ID. | -| **RETURNS** | bool | Whether the matcher contains rules for this match ID. | +| Name | Description | +| ----------- | -------------------------------------------------------------- | +| `key` | The match ID. ~~str~~ | +| **RETURNS** | Whether the matcher contains rules for this match ID. ~~bool~~ | ## DependencyMatcher.add {#add tag="method"} @@ -151,16 +151,16 @@ will be overwritten. > def on_match(matcher, doc, id, matches): > print('Matched!', matches) > -> matcher = Matcher(nlp.vocab) +> matcher = DependencyMatcher(nlp.vocab) > matcher.add("TEST_PATTERNS", patterns) > ``` -| Name | Type | Description | -| -------------- | ------------------ | --------------------------------------------------------------------------------------------- | -| `match_id` | str | An ID for the thing you're matching. | -| `patterns` | list | Match pattern. A pattern consists of a list of dicts, where each dict describes a token. | -| _keyword-only_ | | | -| `on_match` | callable or `None` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. | +| Name | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `match_id` | An ID for the thing you're matching. ~~str~~ | +| `patterns` | list | Match pattern. A pattern consists of a list of dicts, where each dict describes a `"PATTERN"` and `"SPEC"`. ~~List[List[Dict[str, dict]]]~~ | +| _keyword-only_ | | | +| `on_match` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. ~~Optional[Callable[[Matcher, Doc, int, List[tuple], Any]]~~ | ## DependencyMatcher.remove {#remove tag="method"} @@ -176,9 +176,9 @@ exist. > assert "Rule" not in matcher > ``` -| Name | Type | Description | -| ----- | ---- | ------------------------- | -| `key` | str | The ID of the match rule. | +| Name | Description | +| ----- | --------------------------------- | +| `key` | The ID of the match rule. ~~str~~ | ## DependencyMatcher.get {#get tag="method"} @@ -192,7 +192,7 @@ Retrieve the pattern stored for a key. Returns the rule as an > on_match, patterns = matcher.get("Rule") > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------------- | -| `key` | str | The ID of the match rule. | -| **RETURNS** | tuple | The rule, as an `(on_match, patterns)` tuple. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------- | +| `key` | The ID of the match rule. ~~str~~ | +| **RETURNS** | The rule, as an `(on_match, patterns)` tuple. ~~Tuple[Optional[Callable], List[List[dict]]]~~ | diff --git a/website/docs/api/dependencyparser.md b/website/docs/api/dependencyparser.md index c7af8ffae..7a09a840a 100644 --- a/website/docs/api/dependencyparser.md +++ b/website/docs/api/dependencyparser.md @@ -48,13 +48,13 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("parser", config=config) > ``` -| Setting | Type | Description | Default | -| ----------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- | -| `moves` | `List[str]` | A list of transition names. Inferred from the data if not provided. | `None` | -| `update_with_oracle_cut_size` | int | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. | `100` | -| `learn_tokens` | bool | Whether to learn to merge subtokens that are split relative to the gold standard. Experimental. | `False` | -| `min_action_freq` | int | The minimum frequency of labelled actions to retain. Rarer labelled actions have their label backed-off to "dep". While this primarily affects the label accuracy, it can also affect the attachment structure, as the labels are used to represent the pseudo-projectivity transformation. | `30` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model to use. | [TransitionBasedParser](/api/architectures#TransitionBasedParser) | +| Setting | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `moves` | A list of transition names. Inferred from the data if not provided. Defaults to `None`. ~~Optional[List[str]]~~ | +| `update_with_oracle_cut_size` | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. Defaults to `100`. ~~int~~ | +| `learn_tokens` | Whether to learn to merge subtokens that are split relative to the gold standard. Experimental. Defaults to `False`. ~~bool~~ | +| `min_action_freq` | The minimum frequency of labelled actions to retain. Rarer labelled actions have their label backed-off to "dep". While this primarily affects the label accuracy, it can also affect the attachment structure, as the labels are used to represent the pseudo-projectivity transformation. Defaults to `30`. ~~int~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [TransitionBasedParser](/api/architectures#TransitionBasedParser). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/dep_parser.pyx @@ -81,16 +81,16 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#add_pipe). -| Name | Type | Description | -| ----------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| `moves` | `List[str]` | A list of transition names. Inferred from the data if not provided. | -| _keyword-only_ | | | -| `update_with_oracle_cut_size` | int | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. `100` is a good default. | -| `learn_tokens` | bool | Whether to learn to merge subtokens that are split relative to the gold standard. Experimental. | -| `min_action_freq` | int | The minimum frequency of labelled actions to retain. Rarer labelled actions have their label backed-off to "dep". While this primarily affects the label accuracy, it can also affect the attachment structure, as the labels are used to represent the pseudo-projectivity transformation. | +| Name | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| `moves` | A list of transition names. Inferred from the data if not provided. ~~Optional[List[str]]~~ | +| _keyword-only_ | | +| `update_with_oracle_cut_size` | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. `100` is a good default. ~~int~~ | +| `learn_tokens` | Whether to learn to merge subtokens that are split relative to the gold standard. Experimental. ~~bool~~ | +| `min_action_freq` | The minimum frequency of labelled actions to retain. Rarer labelled actions have their label backed-off to "dep". While this primarily affects the label accuracy, it can also affect the attachment structure, as the labels are used to represent the pseudo-projectivity transformation. ~~int~~ | ## DependencyParser.\_\_call\_\_ {#call tag="method"} @@ -111,10 +111,10 @@ and all pipeline components are applied to the `Doc` in order. Both > processed = parser(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## DependencyParser.pipe {#pipe tag="method"} @@ -133,12 +133,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/dependencyparser#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `docs` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## DependencyParser.begin_training {#begin_training tag="method"} @@ -158,13 +158,13 @@ setting up the label scheme based on the data. > optimizer = parser.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/dependencyparser#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## DependencyParser.predict {#predict tag="method"} @@ -178,10 +178,10 @@ modifying them. > scores = parser.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | ------------------- | ---------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | `syntax.StateClass` | A helper class for the parse state (internal). | +| Name | Description | +| ----------- | ------------------------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | A helper class for the parse state (internal). ~~StateClass~~ | ## DependencyParser.set_annotations {#set_annotations tag="method"} @@ -195,10 +195,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > parser.set_annotations([doc1, doc2], scores) > ``` -| Name | Type | Description | -| -------- | ------------------- | ---------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | `syntax.StateClass` | The scores to set, produced by `DependencyParser.predict`. | +| Name | Description | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `DependencyParser.predict`. Returns an internal helper class for the parse state. ~~List[StateClass]~~ | ## DependencyParser.update {#update tag="method"} @@ -214,15 +214,15 @@ model. Delegates to [`predict`](/api/dependencyparser#predict) and > losses = parser.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/dependencyparser#set_annotations). | -| `sgd` | `Optimizer` | The [`Optimizer`](https://thinc.ai/docs/api-optimizers) object. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## DependencyParser.get_loss {#get_loss tag="method"} @@ -237,11 +237,11 @@ predicted scores. > loss, d_loss = parser.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | `syntax.StateClass` | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. ~~StateClass~~ | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## DependencyParser.score {#score tag="method" new="3"} @@ -253,10 +253,10 @@ Score a batch of examples. > scores = parser.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans) and [`Scorer.score_deps`](/api/scorer#score_deps). | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans) and [`Scorer.score_deps`](/api/scorer#score_deps). ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## DependencyParser.create_optimizer {#create_optimizer tag="method"} @@ -270,9 +270,9 @@ component. > optimizer = parser.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## DependencyParser.use_params {#use_params tag="method, contextmanager"} @@ -287,9 +287,9 @@ context, the original parameters are restored. > parser.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## DependencyParser.add_label {#add_label tag="method"} @@ -302,10 +302,10 @@ Add a new label to the pipe. > parser.add_label("MY_LABEL") > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## DependencyParser.to_disk {#to_disk tag="method"} @@ -318,11 +318,11 @@ Serialize the pipe to disk. > parser.to_disk("/path/to/parser") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## DependencyParser.from_disk {#from_disk tag="method"} @@ -335,12 +335,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > parser.from_disk("/path/to/parser") > ``` -| Name | Type | Description | -| -------------- | ------------------ | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `DependencyParser` | The modified `DependencyParser` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `DependencyParser` object. ~~DependencyParser~~ | ## DependencyParser.to_bytes {#to_bytes tag="method"} @@ -353,11 +353,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `DependencyParser` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `DependencyParser` object. ~~bytes~~ | ## DependencyParser.from_bytes {#from_bytes tag="method"} @@ -371,12 +371,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > parser.from_bytes(parser_bytes) > ``` -| Name | Type | Description | -| -------------- | ------------------ | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `DependencyParser` | The `DependencyParser` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `DependencyParser` object. ~~DependencyParser~~ | ## DependencyParser.labels {#labels tag="property"} @@ -389,9 +389,9 @@ The labels currently added to the component. > assert "MY_LABEL" in parser.labels > ``` -| Name | Type | Description | -| ----------- | ----- | ---------------------------------- | -| **RETURNS** | tuple | The labels added to the component. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The labels added to the component. ~~Tuple[str, ...]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/doc.md b/website/docs/api/doc.md index 45bfa31a2..e8ce7343d 100644 --- a/website/docs/api/doc.md +++ b/website/docs/api/doc.md @@ -30,11 +30,11 @@ Construct a `Doc` object. The most common way to get a `Doc` object is via the > doc = Doc(nlp.vocab, words=words, spaces=spaces) > ``` -| Name | Type | Description | -| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | A storage container for lexical types. | -| `words` | iterable | A list of strings to add to the container. | -| `spaces` | iterable | A list of boolean values indicating whether each word has a subsequent space. Must have the same length as `words`, if specified. Defaults to a sequence of `True`. | +| Name | Description | +| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | A storage container for lexical types. ~~Vocab~~ | +| `words` | A list of strings to add to the container. ~~Optional[List[str]]~~ | +| `spaces` | A list of boolean values indicating whether each word has a subsequent space. Must have the same length as `words`, if specified. Defaults to a sequence of `True`. ~~Optional[List[bool]]~~ | ## Doc.\_\_getitem\_\_ {#getitem tag="method"} @@ -52,10 +52,10 @@ Negative indexing is supported, and follows the usual Python semantics, i.e. > assert span.text == "it back" > ``` -| Name | Type | Description | -| ----------- | ------- | ----------------------- | -| `i` | int | The index of the token. | -| **RETURNS** | `Token` | The token at `doc[i]`. | +| Name | Description | +| ----------- | -------------------------------- | +| `i` | The index of the token. ~~int~~ | +| **RETURNS** | The token at `doc[i]`. ~~Token~~ | Get a [`Span`](/api/span) object, starting at position `start` (token index) and ending at position `end` (token index). For instance, `doc[2:5]` produces a span @@ -64,10 +64,10 @@ are not supported, as `Span` objects must be contiguous (cannot have gaps). You can use negative indices and open-ended ranges, which have their normal Python semantics. -| Name | Type | Description | -| ----------- | ------ | --------------------------------- | -| `start_end` | tuple | The slice of the document to get. | -| **RETURNS** | `Span` | The span at `doc[start:end]`. | +| Name | Description | +| ----------- | ----------------------------------------------------- | +| `start_end` | The slice of the document to get. ~~Tuple[int, int]~~ | +| **RETURNS** | The span at `doc[start:end]`. ~~Span~~ | ## Doc.\_\_iter\_\_ {#iter tag="method"} @@ -85,9 +85,9 @@ main way annotations are accessed from Python. If faster-than-Python speeds are required, you can instead access the annotations as a numpy array, or access the underlying C data directly from Cython. -| Name | Type | Description | -| ---------- | ------- | ----------------- | -| **YIELDS** | `Token` | A `Token` object. | +| Name | Description | +| ---------- | --------------------------- | +| **YIELDS** | A `Token` object. ~~Token~~ | ## Doc.\_\_len\_\_ {#len tag="method"} @@ -100,9 +100,9 @@ Get the number of tokens in the document. > assert len(doc) == 7 > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------- | -| **RETURNS** | int | The number of tokens in the document. | +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | The number of tokens in the document. ~~int~~ | ## Doc.set_extension {#set_extension tag="classmethod" new="2"} @@ -120,14 +120,14 @@ details, see the documentation on > assert doc._.has_city > ``` -| Name | Type | Description | -| --------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `doc._.my_attr`. | -| `default` | - | Optional default value of the attribute if no getter or method is defined. | -| `method` | callable | Set a custom method on the object, for example `doc._.compare(other_doc)`. | -| `getter` | callable | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. | -| `setter` | callable | Setter function that takes the `Doc` and a value, and modifies the object. Is called when the user writes to the `Doc._` attribute. | -| `force` | bool | Force overwriting existing attribute. | +| Name | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `doc._.my_attr`. ~~str~~ | +| `default` | Optional default value of the attribute if no getter or method is defined. ~~Optional[Any]~~ | +| `method` | Set a custom method on the object, for example `doc._.compare(other_doc)`. ~~Optional[Callable[[Doc, ...], Any]]~~ | +| `getter` | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. ~~Optional[Callable[[Doc], Any]]~~ | +| `setter` | Setter function that takes the `Doc` and a value, and modifies the object. Is called when the user writes to the `Doc._` attribute. ~~Optional[Callable[[Doc, Any], None]]~~ | +| `force` | Force overwriting existing attribute. ~~bool~~ | ## Doc.get_extension {#get_extension tag="classmethod" new="2"} @@ -144,10 +144,10 @@ Look up a previously registered extension by name. Returns a 4-tuple > assert extension == (False, None, None, None) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the extension. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Doc.has_extension {#has_extension tag="classmethod" new="2"} @@ -161,10 +161,10 @@ Check whether an extension has been registered on the `Doc` class. > assert Doc.has_extension("has_city") > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------ | -| `name` | str | Name of the extension to check. | -| **RETURNS** | bool | Whether the extension has been registered. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| `name` | Name of the extension to check. ~~str~~ | +| **RETURNS** | Whether the extension has been registered. ~~bool~~ | ## Doc.remove_extension {#remove_extension tag="classmethod" new="2.0.12"} @@ -179,10 +179,10 @@ Remove a previously registered extension. > assert not Doc.has_extension("has_city") > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the removed extension. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the removed extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Doc.char_span {#char_span tag="method" new="2"} @@ -197,14 +197,14 @@ the character indices don't map to a valid span. > assert span.text == "New York" > ``` -| Name | Type | Description | -| ------------------------------------ | ---------------------------------------- | --------------------------------------------------------------------- | -| `start` | int | The index of the first character of the span. | -| `end` | int | The index of the last character after the span. | -| `label` | uint64 / str | A label to attach to the span, e.g. for named entities. | -| `kb_id` 2.2 | uint64 / str | An ID from a knowledge base to capture the meaning of a named entity. | -| `vector` | `numpy.ndarray[ndim=1, dtype="float32"]` | A meaning representation of the span. | -| **RETURNS** | `Span` | The newly constructed object or `None`. | +| Name | Description | +| ------------------------------------ | ----------------------------------------------------------------------------------------- | +| `start` | The index of the first character of the span. ~~int~~ | +| `end` | The index of the last character after the span. ~int~~ | +| `label` | A label to attach to the span, e.g. for named entities. ~~Union[int, str]~~ | +| `kb_id` 2.2 | An ID from a knowledge base to capture the meaning of a named entity. ~~Union[int, str]~~ | +| `vector` | A meaning representation of the span. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | +| **RETURNS** | The newly constructed object or `None`. ~~Optional[Span]~~ | ## Doc.similarity {#similarity tag="method" model="vectors"} @@ -221,10 +221,10 @@ using an average of word vectors. > assert apples_oranges == oranges_apples > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------------------------------------- | -| `other` | - | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. | -| **RETURNS** | float | A scalar similarity score. Higher is more similar. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `other` | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. ~~Union[Doc, Span, Token, Lexeme]~~ | +| **RETURNS** | A scalar similarity score. Higher is more similar. ~~float~~ | ## Doc.count_by {#count_by tag="method"} @@ -237,15 +237,15 @@ attribute ID. > ```python > from spacy.attrs import ORTH > doc = nlp("apple apple orange banana") -> assert doc.count_by(ORTH) == {7024L: 1, 119552L: 1, 2087L: 2} +> assert doc.count_by(ORTH) == {7024: 1, 119552: 1, 2087: 2} > doc.to_array([ORTH]) > # array([[11880], [11880], [7561], [12800]]) > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------- | -| `attr_id` | int | The attribute ID | -| **RETURNS** | dict | A dictionary mapping attributes to integer counts. | +| Name | Description | +| ----------- | --------------------------------------------------------------------- | +| `attr_id` | The attribute ID. ~~int~~ | +| **RETURNS** | A dictionary mapping attributes to integer counts. ~~Dict[int, int]~~ | ## Doc.get_lca_matrix {#get_lca_matrix tag="method"} @@ -261,9 +261,9 @@ ancestor is found, e.g. if span excludes a necessary ancestor. > # array([[0, 1, 1, 1], [1, 1, 1, 1], [1, 1, 2, 3], [1, 1, 3, 3]], dtype=int32) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | ----------------------------------------------- | -| **RETURNS** | `numpy.ndarray[ndim=2, dtype="int32"]` | The lowest common ancestor matrix of the `Doc`. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------- | +| **RETURNS** | The lowest common ancestor matrix of the `Doc`. ~~numpy.ndarray[ndim=2, dtype=int32]~~ | ## Doc.to_array {#to_array tag="method"} @@ -288,10 +288,10 @@ Returns a 2D array with one row per token and one column per attribute (when > np_array = doc.to_array("POS") > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `attr_ids` | list or int or string | A list of attributes (int IDs or string names) or a single attribute (int ID or string name) | -| **RETURNS** | `numpy.ndarray[ndim=2, dtype="uint64"]` or `numpy.ndarray[ndim=1, dtype="uint64"]` | The exported attributes as a numpy array. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `attr_ids` | A list of attributes (int IDs or string names) or a single attribute (int ID or string name). ~~Union[int, str, List[Union[int, str]]]~~ | +| **RETURNS** | The exported attributes as a numpy array. ~~Union[numpy.ndarray[ndim=2, dtype=uint64], numpy.ndarray[ndim=1, dtype=uint64]]~~ | ## Doc.from_array {#from_array tag="method"} @@ -310,15 +310,17 @@ array of attributes. > assert doc[0].pos_ == doc2[0].pos_ > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | ------------------------------------------------------------------------- | -| `attrs` | list | A list of attribute ID ints. | -| `array` | `numpy.ndarray[ndim=2, dtype="int32"]` | The attribute values to load. | -| `exclude` | list | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Doc` | Itself. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------- | +| `attrs` | A list of attribute ID ints. ~~List[int]~~ | +| `array` | The attribute values to load. ~~numpy.ndarray[ndim=2, dtype=int32]~~ | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Doc` itself. ~~Doc~~ | ## Doc.from_docs {#from_docs tag="staticmethod"} + + Concatenate multiple `Doc` objects to form a new one. Raises an error if the `Doc` objects do not all share the same `Vocab`. @@ -337,12 +339,12 @@ Concatenate multiple `Doc` objects to form a new one. Raises an error if the > [str(ent) for doc in docs for ent in doc.ents] > ``` -| Name | Type | Description | -| ------------------- | ----- | ----------------------------------------------------------------------------------------------- | -| `docs` | list | A list of `Doc` objects. | -| `ensure_whitespace` | bool | Insert a space between two adjacent docs whenever the first doc does not end in whitespace. | -| `attrs` | list | Optional list of attribute ID ints or attribute name strings. | -| **RETURNS** | `Doc` | The new `Doc` object that is containing the other docs or `None`, if `docs` is empty or `None`. | +| Name | Description | +| ------------------- | ----------------------------------------------------------------------------------------------------------------- | +| `docs` | A list of `Doc` objects. ~~List[Doc]~~ | +| `ensure_whitespace` | Insert a space between two adjacent docs whenever the first doc does not end in whitespace. ~~bool~~ | +| `attrs` | Optional list of attribute ID ints or attribute name strings. ~~Optional[List[Union[str, int]]]~~ | +| **RETURNS** | The new `Doc` object that is containing the other docs or `None`, if `docs` is empty or `None`. ~~Optional[Doc]~~ | ## Doc.to_disk {#to_disk tag="method" new="2"} @@ -354,11 +356,11 @@ Save the current state to a directory. > doc.to_disk("/path/to/doc") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Doc.from_disk {#from_disk tag="method" new="2"} @@ -372,12 +374,12 @@ Loads state from a directory. Modifies the object in place and returns it. > doc = Doc(Vocab()).from_disk("/path/to/doc") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Doc` | The modified `Doc` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Doc` object. ~~Doc~~ | ## Doc.to_bytes {#to_bytes tag="method"} @@ -390,11 +392,11 @@ Serialize, i.e. export the document contents to a binary string. > doc_bytes = doc.to_bytes() > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | A losslessly serialized copy of the `Doc`, including all annotations. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | A losslessly serialized copy of the `Doc`, including all annotations. ~~bytes~~ | ## Doc.from_bytes {#from_bytes tag="method"} @@ -410,12 +412,12 @@ Deserialize, i.e. import the document contents from a binary string. > assert doc.text == doc2.text > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `data` | bytes | The string to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Doc` | The `Doc` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `data` | The string to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Doc` object. ~~Doc~~ | ## Doc.retokenize {#retokenize tag="contextmanager" new="2.1"} @@ -433,9 +435,9 @@ invalidated, although they may accidentally continue to work. > retokenizer.merge(doc[0:2]) > ``` -| Name | Type | Description | -| ----------- | ------------- | ---------------- | -| **RETURNS** | `Retokenizer` | The retokenizer. | +| Name | Description | +| ----------- | -------------------------------- | +| **RETURNS** | The retokenizer. ~~Retokenizer~~ | ### Retokenizer.merge {#retokenizer.merge tag="method"} @@ -454,10 +456,10 @@ dictionary mapping attribute names to values as the `"_"` key. > retokenizer.merge(doc[2:4], attrs=attrs) > ``` -| Name | Type | Description | -| ------- | ------ | -------------------------------------- | -| `span` | `Span` | The span to merge. | -| `attrs` | dict | Attributes to set on the merged token. | +| Name | Description | +| ------- | --------------------------------------------------------------------- | +| `span` | The span to merge. ~~Span~~ | +| `attrs` | Attributes to set on the merged token. ~~Dict[Union[str, int], Any]~~ | ### Retokenizer.split {#retokenizer.split tag="method"} @@ -488,33 +490,12 @@ underlying lexeme (if they're context-independent lexical attributes like > retokenizer.split(doc[3], ["New", "York"], heads=heads, attrs=attrs) > ``` -| Name | Type | Description | -| ------- | ------- | ----------------------------------------------------------------------------------------------------------- | -| `token` | `Token` | The token to split. | -| `orths` | list | The verbatim text of the split tokens. Needs to match the text of the original token. | -| `heads` | list | List of `token` or `(token, subtoken)` tuples specifying the tokens to attach the newly split subtokens to. | -| `attrs` | dict | Attributes to set on all split tokens. Attribute names mapped to list of per-token attribute values. | - -## Doc.merge {#merge tag="method"} - -Retokenize the document, such that the span at `doc.text[start_idx : end_idx]` -is merged into a single token. If `start_idx` and `end_idx` do not mark start -and end token boundaries, the document remains unchanged. - -> #### Example -> -> ```python -> doc = nlp("Los Angeles start.") -> doc.merge(0, len("Los Angeles"), "NNP", "Los Angeles", "GPE") -> assert [t.text for t in doc] == ["Los Angeles", "start", "."] -> ``` - -| Name | Type | Description | -| -------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- | -| `start_idx` | int | The character index of the start of the slice to merge. | -| `end_idx` | int | The character index after the end of the slice to merge. | -| `**attributes` | - | Attributes to assign to the merged token. By default, attributes are inherited from the syntactic root token of the span. | -| **RETURNS** | `Token` | The newly merged token, or `None` if the start and end indices did not fall at token boundaries | +| Name | Description | +| ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| `token` | The token to split. ~~Token~~ | +| `orths` | The verbatim text of the split tokens. Needs to match the text of the original token. ~~List[str]~~ | +| `heads` | List of `token` or `(token, subtoken)` tuples specifying the tokens to attach the newly split subtokens to. ~~List[Union[Token, Tuple[Token, int]]]~~ | +| `attrs` | Attributes to set on all split tokens. Attribute names mapped to list of per-token attribute values. ~~Dict[Union[str, int], List[Any]]~~ | ## Doc.ents {#ents tag="property" model="NER"} @@ -531,9 +512,9 @@ objects, if the entity recognizer has been applied. > assert ents[0].text == "Mr. Best" > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------ | -| **RETURNS** | tuple | Entities in the document, one `Span` per entity. | +| Name | Description | +| ----------- | --------------------------------------------------------------------- | +| **RETURNS** | Entities in the document, one `Span` per entity. ~~Tuple[Span, ...]~~ | ## Doc.noun_chunks {#noun_chunks tag="property" model="parser"} @@ -552,9 +533,9 @@ relative clauses. > assert chunks[1].text == "another phrase" > ``` -| Name | Type | Description | -| ---------- | ------ | ---------------------------- | -| **YIELDS** | `Span` | Noun chunks in the document. | +| Name | Description | +| ---------- | ------------------------------------- | +| **YIELDS** | Noun chunks in the document. ~~Span~~ | ## Doc.sents {#sents tag="property" model="parser"} @@ -572,9 +553,9 @@ will be unavailable. > assert [s.root.text for s in sents] == ["is", "'s"] > ``` -| Name | Type | Description | -| ---------- | ------ | -------------------------- | -| **YIELDS** | `Span` | Sentences in the document. | +| Name | Description | +| ---------- | ----------------------------------- | +| **YIELDS** | Sentences in the document. ~~Span~~ | ## Doc.has_vector {#has_vector tag="property" model="vectors"} @@ -587,9 +568,9 @@ A boolean value indicating whether a word vector is associated with the object. > assert doc.has_vector > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------------ | -| **RETURNS** | bool | Whether the document has a vector data attached. | +| Name | Description | +| ----------- | --------------------------------------------------------- | +| **RETURNS** | Whether the document has a vector data attached. ~~bool~~ | ## Doc.vector {#vector tag="property" model="vectors"} @@ -604,9 +585,9 @@ vectors. > assert doc.vector.shape == (300,) > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------- | ------------------------------------------------------- | -| **RETURNS** | `numpy.ndarray[ndim=1, dtype="float32"]` | A 1D numpy array representing the document's semantics. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------- | +| **RETURNS** | A 1-dimensional array representing the document's vector. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Doc.vector_norm {#vector_norm tag="property" model="vectors"} @@ -622,32 +603,32 @@ The L2 norm of the document's vector representation. > assert doc1.vector_norm != doc2.vector_norm > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------------------------- | -| **RETURNS** | float | The L2 norm of the vector representation. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| **RETURNS** | The L2 norm of the vector representation. ~~float~~ | ## Attributes {#attributes} -| Name | Type | Description | -| --------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `text` | str | A string representation of the document text. | -| `text_with_ws` | str | An alias of `Doc.text`, provided for duck-type compatibility with `Span` and `Token`. | -| `mem` | `Pool` | The document's local memory heap, for all C data it owns. | -| `vocab` | `Vocab` | The store of lexical types. | -| `tensor` 2 | `ndarray` | Container for dense vector representations. | -| `cats` 2 | dict | Maps a label to a score for categories applied to the document. The label is a string and the score should be a float. | -| `user_data` | - | A generic storage area, for user custom data. | -| `lang` 2.1 | int | Language of the document's vocabulary. | -| `lang_` 2.1 | str | Language of the document's vocabulary. | -| `is_tagged` | bool | A flag indicating that the document has been part-of-speech tagged. Returns `True` if the `Doc` is empty. | -| `is_parsed` | bool | A flag indicating that the document has been syntactically parsed. Returns `True` if the `Doc` is empty. | -| `is_sentenced` | bool | A flag indicating that sentence boundaries have been applied to the document. Returns `True` if the `Doc` is empty. | -| `is_nered` 2.1 | bool | A flag indicating that named entities have been set. Will return `True` if the `Doc` is empty, or if _any_ of the tokens has an entity tag set, even if the others are unknown. | -| `sentiment` | float | The document's positivity/negativity score, if available. | -| `user_hooks` | dict | A dictionary that allows customization of the `Doc`'s properties. | -| `user_token_hooks` | dict | A dictionary that allows customization of properties of `Token` children. | -| `user_span_hooks` | dict | A dictionary that allows customization of properties of `Span` children. | -| `_` | `Underscore` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). | +| Name | Description | +| --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `text` | A string representation of the document text. ~~str~~ | +| `text_with_ws` | An alias of `Doc.text`, provided for duck-type compatibility with `Span` and `Token`. ~~str~~ | +| `mem` | The document's local memory heap, for all C data it owns. ~~cymem.Pool~~ | +| `vocab` | The store of lexical types. ~~Vocab~~ | +| `tensor` 2 | Container for dense vector representations. ~~numpy.ndarray~~ | +| `cats` 2 | Maps a label to a score for categories applied to the document. The label is a string and the score should be a float. ~~Dict[str, float]~~ | +| `user_data` | A generic storage area, for user custom data. ~~Dict[str, Any]~~ | +| `lang` 2.1 | Language of the document's vocabulary. ~~int~~ | +| `lang_` 2.1 | Language of the document's vocabulary. ~~str~~ | +| `is_tagged` | A flag indicating that the document has been part-of-speech tagged. Returns `True` if the `Doc` is empty. ~~bool~~ | +| `is_parsed` | A flag indicating that the document has been syntactically parsed. Returns `True` if the `Doc` is empty. ~~bool~~ | +| `is_sentenced` | A flag indicating that sentence boundaries have been applied to the document. Returns `True` if the `Doc` is empty. ~~bool~~ | +| `is_nered` 2.1 | A flag indicating that named entities have been set. Will return `True` if the `Doc` is empty, or if _any_ of the tokens has an entity tag set, even if the others are unknown. ~~bool~~ | +| `sentiment` | The document's positivity/negativity score, if available. ~~float~~ | +| `user_hooks` | A dictionary that allows customization of the `Doc`'s properties. ~~Dict[str, Callable]~~ | +| `user_token_hooks` | A dictionary that allows customization of properties of `Token` children. ~~Dict[str, Callable]~~ | +| `user_span_hooks` | A dictionary that allows customization of properties of `Span` children. ~~Dict[str, Callable]~~ | +| `_` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). ~~Underscore~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/docbin.md b/website/docs/api/docbin.md index ced742045..03aff2f6e 100644 --- a/website/docs/api/docbin.md +++ b/website/docs/api/docbin.md @@ -44,11 +44,11 @@ Create a `DocBin` object to hold serialized annotations. > doc_bin = DocBin(attrs=["ENT_IOB", "ENT_TYPE"]) > ``` -| Argument | Type | Description | -| ----------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `attrs` | `Iterable[str]` | List of attributes to serialize. `ORTH` (hash of token text) and `SPACY` (whether the token is followed by whitespace) are always serialized, so they're not required. Defaults to `("ORTH", "TAG", "HEAD", "DEP", "ENT_IOB", "ENT_TYPE", "ENT_KB_ID", "LEMMA", "MORPH", "POS")`. | -| `store_user_data` | bool | Whether to include the `Doc.user_data` and the values of custom extension attributes. Defaults to `False`. | -| `docs` | `Iterable[Doc]` | `Doc` objects to add on initialization. | +| Argument | Description | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `attrs` | List of attributes to serialize. `ORTH` (hash of token text) and `SPACY` (whether the token is followed by whitespace) are always serialized, so they're not required. Defaults to `("ORTH", "TAG", "HEAD", "DEP", "ENT_IOB", "ENT_TYPE", "ENT_KB_ID", "LEMMA", "MORPH", "POS")`. ~~Iterable[str]~~ | +| `store_user_data` | Whether to include the `Doc.user_data` and the values of custom extension attributes. Defaults to `False`. ~~bool~~ | +| `docs` | `Doc` objects to add on initialization. ~~Iterable[Doc]~~ | ## DocBin.\_\len\_\_ {#len tag="method"} @@ -63,9 +63,9 @@ Get the number of `Doc` objects that were added to the `DocBin`. > assert len(doc_bin) == 1 > ``` -| Argument | Type | Description | -| ----------- | ---- | ------------------------------------------- | -| **RETURNS** | int | The number of `Doc`s added to the `DocBin`. | +| Argument | Description | +| ----------- | --------------------------------------------------- | +| **RETURNS** | The number of `Doc`s added to the `DocBin`. ~~int~~ | ## DocBin.add {#add tag="method"} @@ -79,9 +79,9 @@ Add a `Doc`'s annotations to the `DocBin` for serialization. > doc_bin.add(doc) > ``` -| Argument | Type | Description | -| -------- | ----- | ------------------------ | -| `doc` | `Doc` | The `Doc` object to add. | +| Argument | Description | +| -------- | -------------------------------- | +| `doc` | The `Doc` object to add. ~~Doc~~ | ## DocBin.get_docs {#get_docs tag="method"} @@ -93,15 +93,15 @@ Recover `Doc` objects from the annotations, using the given vocab. > docs = list(doc_bin.get_docs(nlp.vocab)) > ``` -| Argument | Type | Description | -| ---------- | ------- | ------------------ | -| `vocab` | `Vocab` | The shared vocab. | -| **YIELDS** | `Doc` | The `Doc` objects. | +| Argument | Description | +| ---------- | --------------------------- | +| `vocab` | The shared vocab. ~~Vocab~~ | +| **YIELDS** | The `Doc` objects. ~~Doc~~ | ## DocBin.merge {#merge tag="method"} Extend the annotations of this `DocBin` with the annotations from another. Will -raise an error if the pre-defined attrs of the two `DocBin`s don't match. +raise an error if the pre-defined `attrs` of the two `DocBin`s don't match. > #### Example > @@ -114,9 +114,9 @@ raise an error if the pre-defined attrs of the two `DocBin`s don't match. > assert len(doc_bin1) == 2 > ``` -| Argument | Type | Description | -| -------- | -------- | ------------------------------------------- | -| `other` | `DocBin` | The `DocBin` to merge into the current bin. | +| Argument | Description | +| -------- | ------------------------------------------------------ | +| `other` | The `DocBin` to merge into the current bin. ~~DocBin~~ | ## DocBin.to_bytes {#to_bytes tag="method"} @@ -130,9 +130,9 @@ Serialize the `DocBin`'s annotations to a bytestring. > doc_bin_bytes = doc_bin.to_bytes() > ``` -| Argument | Type | Description | -| ----------- | ----- | ------------------------ | -| **RETURNS** | bytes | The serialized `DocBin`. | +| Argument | Description | +| ----------- | ---------------------------------- | +| **RETURNS** | The serialized `DocBin`. ~~bytes~~ | ## DocBin.from_bytes {#from_bytes tag="method"} @@ -145,10 +145,10 @@ Deserialize the `DocBin`'s annotations from a bytestring. > new_doc_bin = DocBin().from_bytes(doc_bin_bytes) > ``` -| Argument | Type | Description | -| ------------ | -------- | ---------------------- | -| `bytes_data` | bytes | The data to load from. | -| **RETURNS** | `DocBin` | The loaded `DocBin`. | +| Argument | Description | +| ------------ | -------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| **RETURNS** | The loaded `DocBin`. ~~DocBin~~ | ## DocBin.to_disk {#to_disk tag="method" new="3"} @@ -164,9 +164,9 @@ and the result can be used as the input data for > doc_bin.to_disk("./data.spacy") > ``` -| Argument | Type | Description | -| -------- | ------------ | ----------------------------------------------------- | -| `path` | str / `Path` | The file path, typically with the `.spacy` extension. | +| Argument | Description | +| -------- | -------------------------------------------------------------------------- | +| `path` | The file path, typically with the `.spacy` extension. ~~Union[str, Path]~~ | ## DocBin.from_disk {#from_disk tag="method" new="3"} @@ -178,7 +178,7 @@ Load a serialized `DocBin` from a file. Typically uses the `.spacy` extension. > doc_bin = DocBin().from_disk("./data.spacy") > ``` -| Argument | Type | Description | -| ----------- | ------------ | ----------------------------------------------------- | -| `path` | str / `Path` | The file path, typically with the `.spacy` extension. | -| **RETURNS** | `DocBin` | The loaded `DocBin`. | +| Argument | Description | +| ----------- | -------------------------------------------------------------------------- | +| `path` | The file path, typically with the `.spacy` extension. ~~Union[str, Path]~~ | +| **RETURNS** | The loaded `DocBin`. ~~DocBin~~ | diff --git a/website/docs/api/entitylinker.md b/website/docs/api/entitylinker.md index fa8918dba..a1bc52199 100644 --- a/website/docs/api/entitylinker.md +++ b/website/docs/api/entitylinker.md @@ -40,14 +40,13 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("entity_linker", config=config) > ``` -| Setting | Type | Description | Default | -| ---------------- | -------------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------ | -| `labels_discard` | `Iterable[str]` | NER labels that will automatically get a "NIL" prediction. | `[]` | -| `incl_prior` | bool | Whether or not to include prior probabilities from the KB in the model. | `True` | -| `incl_context` | bool | Whether or not to include the local context in the model. | `True` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model to use. | [EntityLinker](/api/architectures#EntityLinker) | -| `kb_loader` | `Callable[[Vocab], KnowledgeBase]` | Function that creates a [`KnowledgeBase`](/api/kb) from a `Vocab` instance. | An empty KnowledgeBase with `entity_vector_length` 64. | -| `get_candidates` | `Callable[[KnowledgeBase, "Span"], Iterable[Candidate]]` | Function that generates plausible candidates for a given `Span` object. | Built-in dictionary-lookup function. | +| Setting | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `labels_discard` | NER labels that will automatically get a "NIL" prediction. Defaults to `[]`. ~~Iterable[str]~~ | +| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. Defaults to `True`. ~~bool~~ | +| `incl_context` | Whether or not to include the local context in the model. Defaults to `True`. ~~bool~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [EntityLinker](/api/architectures#EntityLinker). ~~Model~~ | +| `kb` | The [`KnowledgeBase`](/api/kb). Defaults to [EmptyKB](/api/architectures#EmptyKB), a function returning an empty `KnowledgeBase` with an `entity_vector_length` of `64`. ~~KnowledgeBase~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/entity_linker.py @@ -66,7 +65,7 @@ https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/entity_linker.py > entity_linker = nlp.add_pipe("entity_linker", config=config) > > # Construction via add_pipe with custom KB and candidate generation -> config = {"kb_loader": {"@assets": "my_kb.v1"}, "get_candidates": {"@assets": "my_candidates.v1"},} +> config = {"kb": {"@assets": "my_kb.v1"}} > entity_linker = nlp.add_pipe("entity_linker", config=config) > > # Construction from class @@ -76,22 +75,20 @@ https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/entity_linker.py Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and -[`nlp.add_pipe`](/api/language#add_pipe). +[`nlp.add_pipe`](/api/language#add_pipe). Note that both the internal +`KnowledgeBase` as well as the Candidate generator can be customized by +providing custom registered functions. -Note that both the internal KB as well as the Candidate generator can be -customized by providing custom registered functions. - -| Name | Type | Description | -| ---------------- | -------------------------------------------------------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | `Model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| _keyword-only_ | | | -| `kb_loader` | `Callable[[Vocab], KnowledgeBase]` | Function that creates a [`KnowledgeBase`](/api/kb) from a `Vocab` instance. | -| `get_candidates` | `Callable[[KnowledgeBase, "Span"], Iterable[Candidate]]` | Function that generates plausible candidates for a given `Span` object. | -| `labels_discard` | `Iterable[str]` | NER labels that will automatically get a "NIL" prediction. | -| `incl_prior` | bool | Whether or not to include prior probabilities from the KB in the model. | -| `incl_context` | bool | Whether or not to include the local context in the model. | +| Name | Description | +| ---------------- | --------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | | +| `kb` | The [`KnowledgeBase`](/api/kb). ~~KnowledgeBase~~ | +| `labels_discard` | NER labels that will automatically get a `"NIL"` prediction. ~~Iterable[str]~~ | +| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. ~~bool~~ | +| `incl_context` | Whether or not to include the local context in the model. ~~bool~~ | ## EntityLinker.\_\_call\_\_ {#call tag="method"} @@ -111,10 +108,10 @@ delegate to the [`predict`](/api/entitylinker#predict) and > processed = entity_linker(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## EntityLinker.pipe {#pipe tag="method"} @@ -133,12 +130,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/entitylinker#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## EntityLinker.begin_training {#begin_training tag="method"} @@ -158,13 +155,13 @@ setting up the label scheme based on the data. > optimizer = entity_linker.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/dependencyparser#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## EntityLinker.predict {#predict tag="method"} @@ -179,10 +176,10 @@ if there is no prediction. > kb_ids = entity_linker.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ------------------------------------------------------------ | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | `List[str]` | The predicted KB identifiers for the entities in the `docs`. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | `List[str]` | The predicted KB identifiers for the entities in the `docs`. ~~List[str]~~ | ## EntityLinker.set_annotations {#set_annotations tag="method"} @@ -197,10 +194,10 @@ entities. > entity_linker.set_annotations([doc1, doc2], kb_ids) > ``` -| Name | Type | Description | -| -------- | --------------- | ------------------------------------------------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `kb_ids` | `List[str]` | The knowledge base identifiers for the entities in the docs, predicted by `EntityLinker.predict`. | +| Name | Description | +| -------- | --------------------------------------------------------------------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `kb_ids` | The knowledge base identifiers for the entities in the docs, predicted by `EntityLinker.predict`. ~~List[str]~~ | ## EntityLinker.update {#update tag="method"} @@ -216,15 +213,15 @@ pipe's entity linking model and context encoder. Delegates to > losses = entity_linker.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/textcategorizer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## EntityLinker.create_optimizer {#create_optimizer tag="method"} @@ -237,9 +234,9 @@ Create an optimizer for the pipeline component. > optimizer = entity_linker.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## EntityLinker.use_params {#use_params tag="method, contextmanager"} @@ -254,9 +251,9 @@ context, the original parameters are restored. > entity_linker.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## EntityLinker.to_disk {#to_disk tag="method"} @@ -269,11 +266,11 @@ Serialize the pipe to disk. > entity_linker.to_disk("/path/to/entity_linker") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## EntityLinker.from_disk {#from_disk tag="method"} @@ -286,12 +283,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > entity_linker.from_disk("/path/to/entity_linker") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `EntityLinker` | The modified `EntityLinker` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `EntityLinker` object. ~~EntityLinker~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/entityrecognizer.md b/website/docs/api/entityrecognizer.md index 8d30463ff..b6b9caa84 100644 --- a/website/docs/api/entityrecognizer.md +++ b/website/docs/api/entityrecognizer.md @@ -41,11 +41,11 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("ner", config=config) > ``` -| Setting | Type | Description | Default | -| ----------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------- | -| `moves` | `List[str]` | A list of transition names. Inferred from the data if not provided. | -| `update_with_oracle_cut_size` | int | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. | `100` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model to use. | [TransitionBasedParser](/api/architectures#TransitionBasedParser) | +| Setting | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `moves` | A list of transition names. Inferred from the data if not provided. Defaults to `None`. ~~Optional[List[str]] | +| `update_with_oracle_cut_size` | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. Defaults to `100`. ~~int~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [TransitionBasedParser](/api/architectures#TransitionBasedParser). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/ner.pyx @@ -72,14 +72,14 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#add_pipe). -| Name | Type | Description | -| ----------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| `moves` | `List[str]` | A list of transition names. Inferred from the data if not provided. | -| _keyword-only_ | | | -| `update_with_oracle_cut_size` | int | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. `100` is a good default. | +| Name | Description | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| `moves` | A list of transition names. Inferred from the data if not provided. ~~Optional[List[str]]~~ | +| _keyword-only_ | | +| `update_with_oracle_cut_size` | During training, cut long sequences into shorter segments by creating intermediate states based on the gold-standard history. The model is not very sensitive to this parameter, so you usually won't need to change it. `100` is a good default. ~~int~~ | ## EntityRecognizer.\_\_call\_\_ {#call tag="method"} @@ -100,10 +100,10 @@ and all pipeline components are applied to the `Doc` in order. Both > processed = ner(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## EntityRecognizer.pipe {#pipe tag="method"} @@ -122,12 +122,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/entityrecognizer#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `docs` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `docs` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## EntityRecognizer.begin_training {#begin_training tag="method"} @@ -147,13 +147,13 @@ setting up the label scheme based on the data. > optimizer = ner.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/entityrecognizer#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## EntityRecognizer.predict {#predict tag="method"} @@ -167,10 +167,10 @@ modifying them. > scores = ner.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | ------------------ | ---------------------------------------------------------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | `List[StateClass]` | List of `syntax.StateClass` objects. `syntax.StateClass` is a helper class for the parse state (internal). | +| Name | Description | +| ----------- | ------------------------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | A helper class for the parse state (internal). ~~StateClass~~ | ## EntityRecognizer.set_annotations {#set_annotations tag="method"} @@ -184,10 +184,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > ner.set_annotations([doc1, doc2], scores) > ``` -| Name | Type | Description | -| -------- | ------------------ | ---------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | `List[StateClass]` | The scores to set, produced by `EntityRecognizer.predict`. | +| Name | Description | +| -------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `EntityRecognizer.predict`. Returns an internal helper class for the parse state. ~~List[StateClass]~~ | ## EntityRecognizer.update {#update tag="method"} @@ -203,15 +203,15 @@ model. Delegates to [`predict`](/api/entityrecognizer#predict) and > losses = ner.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/entityrecognizer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## EntityRecognizer.get_loss {#get_loss tag="method"} @@ -226,11 +226,11 @@ predicted scores. > loss, d_loss = ner.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | `List[StateClass]` | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. ~~StateClass~~ | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## EntityRecognizer.score {#score tag="method" new="3"} @@ -242,10 +242,10 @@ Score a batch of examples. > scores = ner.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | ------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans). | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans). ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## EntityRecognizer.create_optimizer {#create_optimizer tag="method"} @@ -258,9 +258,9 @@ Create an optimizer for the pipeline component. > optimizer = ner.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## EntityRecognizer.use_params {#use_params tag="method, contextmanager"} @@ -275,9 +275,9 @@ context, the original parameters are restored. > ner.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## EntityRecognizer.add_label {#add_label tag="method"} @@ -290,10 +290,10 @@ Add a new label to the pipe. > ner.add_label("MY_LABEL") > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## EntityRecognizer.to_disk {#to_disk tag="method"} @@ -306,11 +306,11 @@ Serialize the pipe to disk. > ner.to_disk("/path/to/ner") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## EntityRecognizer.from_disk {#from_disk tag="method"} @@ -323,12 +323,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > ner.from_disk("/path/to/ner") > ``` -| Name | Type | Description | -| -------------- | ------------------ | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `EntityRecognizer` | The modified `EntityRecognizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `EntityRecognizer` object. ~~EntityRecognizer~~ | ## EntityRecognizer.to_bytes {#to_bytes tag="method"} @@ -341,11 +341,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `EntityRecognizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `EntityRecognizer` object. ~~bytes~~ | ## EntityRecognizer.from_bytes {#from_bytes tag="method"} @@ -359,12 +359,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > ner.from_bytes(ner_bytes) > ``` -| Name | Type | Description | -| -------------- | ------------------ | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `EntityRecognizer` | The `EntityRecognizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `EntityRecognizer` object. ~~EntityRecognizer~~ | ## EntityRecognizer.labels {#labels tag="property"} @@ -377,9 +377,9 @@ The labels currently added to the component. > assert "MY_LABEL" in ner.labels > ``` -| Name | Type | Description | -| ----------- | ----- | ---------------------------------- | -| **RETURNS** | tuple | The labels added to the component. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The labels added to the component. ~~Tuple[str, ...]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/entityruler.md b/website/docs/api/entityruler.md index 1b98a659d..454b2a04b 100644 --- a/website/docs/api/entityruler.md +++ b/website/docs/api/entityruler.md @@ -34,12 +34,12 @@ how the component should be configured. You can override its settings via the > nlp.add_pipe("entity_ruler", config=config) > ``` -| Setting | Type | Description | Default | -| --------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `phrase_matcher_attr` | str | Optional attribute name match on for the internal [`PhraseMatcher`](/api/phrasematcher), e.g. `LOWER` to match on the lowercase token text. | `None` | -| `validate` | bool | Whether patterns should be validated (passed to the `Matcher` and `PhraseMatcher`). | `False` | -| `overwrite_ents` | bool | If existing entities are present, e.g. entities added by the model, overwrite them by matches if necessary. | `False` | -| `ent_id_sep` | str | Separator used internally for entity IDs. | `"||"` | +| Setting | Description | +| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `phrase_matcher_attr` | Optional attribute name match on for the internal [`PhraseMatcher`](/api/phrasematcher), e.g. `LOWER` to match on the lowercase token text. Defaults to `None`. ~~Optional[Union[int, str]]~~ | +| `validate` | Whether patterns should be validated (passed to the `Matcher` and `PhraseMatcher`). Defaults to `False`. ~~bool~~ | +| `overwrite_ents` | If existing entities are present, e.g. entities added by the model, overwrite them by matches if necessary. Defaults to `False`. ~~bool~~ | +| `ent_id_sep` | Separator used internally for entity IDs. Defaults to `"||"`. ~~str~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/entityruler.py @@ -63,16 +63,16 @@ be a token pattern (list) or a phrase pattern (string). For example: > ruler = EntityRuler(nlp, overwrite_ents=True) > ``` -| Name | Type | Description | -| --------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nlp` | `Language` | The shared nlp object to pass the vocab to the matchers and process phrase patterns. | -| `name` 3 | str | Instance name of the current pipeline component. Typically passed in automatically from the factory when the component is added. Used to disable the current entity ruler while creating phrase patterns with the nlp object. | -| _keyword-only_ | | | -| `phrase_matcher_attr` | int / str | Optional attribute name match on for the internal [`PhraseMatcher`](/api/phrasematcher), e.g. `LOWER` to match on the lowercase token text. Defaults to `None`. | -| `validate` | bool | Whether patterns should be validated, passed to Matcher and PhraseMatcher as `validate`. Defaults to `False`. | -| `overwrite_ents` | bool | If existing entities are present, e.g. entities added by the model, overwrite them by matches if necessary. Defaults to `False`. | -| `ent_id_sep` | str | Separator used internally for entity IDs. Defaults to `"||"`. | -| `patterns` | iterable | Optional patterns to load in on initialization. | +| Name | Description | +| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `nlp` | The shared nlp object to pass the vocab to the matchers and process phrase patterns. ~~Language~~ | +| `name` 3 | Instance name of the current pipeline component. Typically passed in automatically from the factory when the component is added. Used to disable the current entity ruler while creating phrase patterns with the nlp object. ~~str~~ | +| _keyword-only_ | | +| `phrase_matcher_attr` | Optional attribute name match on for the internal [`PhraseMatcher`](/api/phrasematcher), e.g. `LOWER` to match on the lowercase token text. Defaults to `None`. ~~Optional[Union[int, str]]~~ | +| `validate` | Whether patterns should be validated, passed to Matcher and PhraseMatcher as `validate`. Defaults to `False`. ~~bool~~ | +| `overwrite_ents` | If existing entities are present, e.g. entities added by the model, overwrite them by matches if necessary. Defaults to `False`. ~~bool~~ | +| `ent_id_sep` | Separator used internally for entity IDs. Defaults to `"||"`. ~~str~~ | +| `patterns` | Optional patterns to load in on initialization. ~~Optional[List[Dict[str, Union[str, List[dict]]]]]~~ | ## EntityRuler.\_\len\_\_ {#len tag="method"} @@ -87,9 +87,9 @@ The number of all patterns added to the entity ruler. > assert len(ruler) == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------- | -| **RETURNS** | int | The number of patterns. | +| Name | Description | +| ----------- | ------------------------------- | +| **RETURNS** | The number of patterns. ~~int~~ | ## EntityRuler.\_\_contains\_\_ {#contains tag="method"} @@ -104,10 +104,10 @@ Whether a label is present in the patterns. > assert not "PERSON" in ruler > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------- | -| `label` | str | The label to check. | -| **RETURNS** | bool | Whether the entity ruler contains the label. | +| Name | Description | +| ----------- | ----------------------------------------------------- | +| `label` | The label to check. ~~str~~ | +| **RETURNS** | Whether the entity ruler contains the label. ~~bool~~ | ## EntityRuler.\_\_call\_\_ {#call tag="method"} @@ -130,10 +130,10 @@ is chosen. > assert ents == [("Apple", "ORG")] > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| **RETURNS** | `Doc` | The modified `Doc` with added entities, if available. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. ~~Doc~~ | +| **RETURNS** | The modified `Doc` with added entities, if available. ~~Doc~~ | ## EntityRuler.add_patterns {#add_patterns tag="method"} @@ -152,9 +152,9 @@ of dicts) or a phrase pattern (string). For more details, see the usage guide on > ruler.add_patterns(patterns) > ``` -| Name | Type | Description | -| ---------- | ---- | -------------------- | -| `patterns` | list | The patterns to add. | +| Name | Description | +| ---------- | ---------------------------------------------------------------- | +| `patterns` | The patterns to add. ~~List[Dict[str, Union[str, List[dict]]]]~~ | ## EntityRuler.to_disk {#to_disk tag="method"} @@ -171,9 +171,9 @@ only the patterns are saved as JSONL. If a directory name is provided, a > ruler.to_disk("/path/to/entity_ruler") # saves patterns and config > ``` -| Name | Type | Description | -| ------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a JSONL file or directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `path` | A path to a JSONL file or directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## EntityRuler.from_disk {#from_disk tag="method"} @@ -190,10 +190,10 @@ configuration. > ruler.from_disk("/path/to/entity_ruler") # loads patterns and config > ``` -| Name | Type | Description | -| ----------- | ------------- | ---------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a JSONL file or directory. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `EntityRuler` | The modified `EntityRuler` object. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------- | +| `path` | A path to a JSONL file or directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The modified `EntityRuler` object. ~~EntityRuler~~ | ## EntityRuler.to_bytes {#to_bytes tag="method"} @@ -206,9 +206,9 @@ Serialize the entity ruler patterns to a bytestring. > ruler_bytes = ruler.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| **RETURNS** | bytes | The serialized patterns. | +| Name | Description | +| ----------- | ---------------------------------- | +| **RETURNS** | The serialized patterns. ~~bytes~~ | ## EntityRuler.from_bytes {#from_bytes tag="method"} @@ -222,40 +222,40 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > ruler.from_bytes(ruler_bytes) > ``` -| Name | Type | Description | -| ------------ | ------------- | ---------------------------------- | -| `bytes_data` | bytes | The bytestring to load. | -| **RETURNS** | `EntityRuler` | The modified `EntityRuler` object. | +| Name | Description | +| ------------ | -------------------------------------------------- | +| `bytes_data` | The bytestring to load. ~~bytes~~ | +| **RETURNS** | The modified `EntityRuler` object. ~~EntityRuler~~ | ## EntityRuler.labels {#labels tag="property"} All labels present in the match patterns. -| Name | Type | Description | -| ----------- | ----- | ------------------ | -| **RETURNS** | tuple | The string labels. | +| Name | Description | +| ----------- | -------------------------------------- | +| **RETURNS** | The string labels. ~~Tuple[str, ...]~~ | ## EntityRuler.ent_ids {#labels tag="property" new="2.2.2"} -All entity ids present in the match patterns `id` properties. +All entity IDs present in the `id` properties of the match patterns. -| Name | Type | Description | -| ----------- | ----- | ------------------- | -| **RETURNS** | tuple | The string ent_ids. | +| Name | Description | +| ----------- | ----------------------------------- | +| **RETURNS** | The string IDs. ~~Tuple[str, ...]~~ | ## EntityRuler.patterns {#patterns tag="property"} Get all patterns that were added to the entity ruler. -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------- | -| **RETURNS** | list | The original patterns, one dictionary per pattern. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------- | +| **RETURNS** | The original patterns, one dictionary per pattern. ~~List[Dict[str, Union[str, dict]]]~~ | ## Attributes {#attributes} -| Name | Type | Description | -| ----------------- | ------------------------------------- | ---------------------------------------------------------------- | -| `matcher` | [`Matcher`](/api/matcher) | The underlying matcher used to process token patterns. | -| `phrase_matcher` | [`PhraseMatcher`](/api/phrasematcher) | The underlying phrase matcher, used to process phrase patterns. | -| `token_patterns` | dict | The token patterns present in the entity ruler, keyed by label. | -| `phrase_patterns` | dict | The phrase patterns present in the entity ruler, keyed by label. | +| Name | Description | +| ----------------- | --------------------------------------------------------------------------------------------------------------------- | +| `matcher` | The underlying matcher used to process token patterns. ~~Matcher~~ | | +| `phrase_matcher` | The underlying phrase matcher, used to process phrase patterns. ~~PhraseMatcher~~ | +| `token_patterns` | The token patterns present in the entity ruler, keyed by label. ~~Dict[str, List[Dict[str, Union[str, List[dict]]]]~~ | +| `phrase_patterns` | The phrase patterns present in the entity ruler, keyed by label. ~~Dict[str, List[Doc]]~~ | diff --git a/website/docs/api/example.md b/website/docs/api/example.md index 190729490..2434cce43 100644 --- a/website/docs/api/example.md +++ b/website/docs/api/example.md @@ -8,9 +8,9 @@ new: 3.0 An `Example` holds the information for one training instance. It stores two `Doc` objects: one for holding the gold-standard reference data, and one for -holding the predictions of the pipeline. An [`Alignment`](#alignment-object) -object stores the alignment between these two documents, as they can differ in -tokenization. +holding the predictions of the pipeline. An +[`Alignment`](/api/example#alignment-object) object stores the alignment between +these two documents, as they can differ in tokenization. ## Example.\_\_init\_\_ {#init tag="method"} @@ -31,12 +31,12 @@ both documents. > example = Example(predicted, reference) > ``` -| Name | Type | Description | -| -------------- | ----------- | ------------------------------------------------------------------------------------------------ | -| `predicted` | `Doc` | The document containing (partial) predictions. Can not be `None`. | -| `reference` | `Doc` | The document containing gold-standard annotations. Can not be `None`. | -| _keyword-only_ | | | -| `alignment` | `Alignment` | An object holding the alignment between the tokens of the `predicted` and `reference` documents. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `predicted` | The document containing (partial) predictions. Can not be `None`. ~~Doc~~ | +| `reference` | The document containing gold-standard annotations. Can not be `None`. ~~Doc~~ | +| _keyword-only_ | | +| `alignment` | An object holding the alignment between the tokens of the `predicted` and `reference` documents. ~~Optional[Alignment]~~ | ## Example.from_dict {#from_dict tag="classmethod"} @@ -56,11 +56,11 @@ see the [training format documentation](/api/data-formats#dict-input). > example = Example.from_dict(predicted, {"words": token_ref, "tags": tags_ref}) > ``` -| Name | Type | Description | -| -------------- | ---------------- | ----------------------------------------------------------------- | -| `predicted` | `Doc` | The document containing (partial) predictions. Can not be `None`. | -| `example_dict` | `Dict[str, obj]` | The gold-standard annotations as a dictionary. Can not be `None`. | -| **RETURNS** | `Example` | The newly constructed object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------- | +| `predicted` | The document containing (partial) predictions. Can not be `None`. ~~Doc~~ | +| `example_dict` | `Dict[str, obj]` | The gold-standard annotations as a dictionary. Can not be `None`. ~~Dict[str, Any]~~ | +| **RETURNS** | The newly constructed object. ~~Example~~ | ## Example.text {#text tag="property"} @@ -72,12 +72,14 @@ The text of the `predicted` document in this `Example`. > raw_text = example.text > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------- | -| **RETURNS** | str | The text of the `predicted` document. | +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | The text of the `predicted` document. ~~str~~ | ## Example.predicted {#predicted tag="property"} +The `Doc` holding the predictions. Occasionally also referred to as `example.x`. + > #### Example > > ```python @@ -86,14 +88,15 @@ The text of the `predicted` document in this `Example`. > set_annotations(docs, predictions) > ``` -The `Doc` holding the predictions. Occassionally also refered to as `example.x`. - -| Name | Type | Description | -| ----------- | ----- | ---------------------------------------------- | -| **RETURNS** | `Doc` | The document containing (partial) predictions. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The document containing (partial) predictions. ~~Doc~~ | ## Example.reference {#reference tag="property"} +The `Doc` holding the gold-standard annotations. Occasionally also referred to +as `example.y`. + > #### Example > > ```python @@ -102,15 +105,15 @@ The `Doc` holding the predictions. Occassionally also refered to as `example.x`. > gold_labels[i][j] = eg.reference.cats.get(label, 0.0) > ``` -The `Doc` holding the gold-standard annotations. Occassionally also refered to -as `example.y`. - -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------- | -| **RETURNS** | `Doc` | The document containing gold-standard annotations. | +| Name | Description | +| ----------- | ---------------------------------------------------------- | +| **RETURNS** | The document containing gold-standard annotations. ~~Doc~~ | ## Example.alignment {#alignment tag="property"} +The [`Alignment`](/api/example#alignment-object) object mapping the tokens of +the `predicted` document to those of the `reference` document. + > #### Example > > ```python @@ -122,15 +125,15 @@ as `example.y`. > assert list(alignment.y2x.data) == [[0], [1], [2], [2]] > ``` -The `Alignment` object mapping the tokens of the `predicted` document to those -of the `reference` document. - -| Name | Type | Description | -| ----------- | ----------- | -------------------------------------------------- | -| **RETURNS** | `Alignment` | The document containing gold-standard annotations. | +| Name | Description | +| ----------- | ---------------------------------------------------------------- | +| **RETURNS** | The document containing gold-standard annotations. ~~Alignment~~ | ## Example.get_aligned {#get_aligned tag="method"} +Get the aligned view of a certain token attribute, denoted by its int ID or +string name. + > #### Example > > ```python @@ -141,17 +144,18 @@ of the `reference` document. > assert example.get_aligned("TAG", as_string=True) == ["VERB", "DET", "NOUN"] > ``` -Get the aligned view of a certain token attribute, denoted by its int ID or -string name. - -| Name | Type | Description | Default | -| ----------- | -------------------------- | ------------------------------------------------------------------ | ------- | -| `field` | int or str | Attribute ID or string name | | -| `as_string` | bool | Whether or not to return the list of values as strings. | `False` | -| **RETURNS** | `List[int]` or `List[str]` | List of integer values, or string values if `as_string` is `True`. | | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------- | +| `field` | Attribute ID or string name. ~~Union[int, str]~~ | +| `as_string` | Whether or not to return the list of values as strings. Defaults to `False`. ~~bool~~ | +| **RETURNS** | List of integer values, or string values if `as_string` is `True`. ~~Union[List[int], List[str]]~~ | ## Example.get_aligned_parse {#get_aligned_parse tag="method"} +Get the aligned view of the dependency parse. If `projectivize` is set to +`True`, non-projective dependency trees are made projective through the +Pseudo-Projective Dependency Parsing algorithm by Nivre and Nilsson (2005). + > #### Example > > ```python @@ -161,17 +165,16 @@ string name. > assert proj_heads == [3, 2, 3, 0, 3] > ``` -Get the aligned view of the dependency parse. If `projectivize` is set to -`True`, non-projective dependency trees are made projective through the -Pseudo-Projective Dependency Parsing algorithm by Nivre and Nilsson (2005). - -| Name | Type | Description | Default | -| -------------- | -------------------------- | ------------------------------------------------------------------ | ------- | -| `projectivize` | bool | Whether or not to projectivize the dependency trees | `True` | -| **RETURNS** | `List[int]` or `List[str]` | List of integer values, or string values if `as_string` is `True`. | | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------- | +| `projectivize` | Whether or not to projectivize the dependency trees. Defaults to `True`. ~~bool~~ | +| **RETURNS** | List of integer values, or string values if `as_string` is `True`. ~~Union[List[int], List[str]]~~ | ## Example.get_aligned_ner {#get_aligned_ner tag="method"} +Get the aligned view of the NER +[BILUO](/usage/linguistic-features#accessing-ner) tags. + > #### Example > > ```python @@ -184,15 +187,16 @@ Pseudo-Projective Dependency Parsing algorithm by Nivre and Nilsson (2005). > assert ner_tags == ["B-PERSON", "L-PERSON", "O", "O", "U-LOC"] > ``` -Get the aligned view of the NER -[BILUO](/usage/linguistic-features#accessing-ner) tags. - -| Name | Type | Description | -| ----------- | ----------- | ----------------------------------------------------------------------------------- | -| **RETURNS** | `List[str]` | List of BILUO values, denoting whether tokens are part of an NER annotation or not. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------- | +| **RETURNS** | List of BILUO values, denoting whether tokens are part of an NER annotation or not. ~~List[str]~~ | ## Example.get_aligned_spans_y2x {#get_aligned_spans_y2x tag="method"} +Get the aligned view of any set of [`Span`](/api/span) objects defined over +[`Example.reference`](/api/example#reference). The resulting span indices will +align to the tokenization in [`Example.predicted`](/api/example#predicted). + > #### Example > > ```python @@ -207,17 +211,19 @@ Get the aligned view of the NER > assert [(ent.start, ent.end) for ent in ents_y2x] == [(0, 1)] > ``` -Get the aligned view of any set of [`Span`](/api/span) objects defined over -`example.reference`. The resulting span indices will align to the tokenization -in `example.predicted`. - -| Name | Type | Description | -| ----------- | ---------------- | --------------------------------------------------------------- | -| `y_spans` | `Iterable[Span]` | `Span` objects aligned to the tokenization of `self.reference`. | -| **RETURNS** | `Iterable[Span]` | `Span` objects aligned to the tokenization of `self.predicted`. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------- | +| `y_spans` | `Span` objects aligned to the tokenization of `reference`. ~~Iterable[Span]~~ | +| **RETURNS** | `Span` objects aligned to the tokenization of `predicted`. ~~List[Span]~~ | ## Example.get_aligned_spans_x2y {#get_aligned_spans_x2y tag="method"} +Get the aligned view of any set of [`Span`](/api/span) objects defined over +[`Example.predicted`](/api/example#predicted). The resulting span indices will +align to the tokenization in [`Example.reference`](/api/example#reference). This +method is particularly useful to assess the accuracy of predicted entities +against the original gold-standard annotation. + > #### Example > > ```python @@ -232,15 +238,10 @@ in `example.predicted`. > assert [(ent.start, ent.end) for ent in ents_x2y] == [(0, 2)] > ``` -Get the aligned view of any set of [`Span`](/api/span) objects defined over -`example.predicted`. The resulting span indices will align to the tokenization -in `example.reference`. This method is particularly useful to assess the -accuracy of predicted entities against the original gold-standard annotation. - -| Name | Type | Description | -| ----------- | ---------------- | --------------------------------------------------------------- | -| `x_spans` | `Iterable[Span]` | `Span` objects aligned to the tokenization of `self.predicted`. | -| **RETURNS** | `Iterable[Span]` | `Span` objects aligned to the tokenization of `self.reference`. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------- | +| `x_spans` | `Span` objects aligned to the tokenization of `predicted`. ~~Iterable[Span]~~ | +| **RETURNS** | `Span` objects aligned to the tokenization of `reference`. ~~List[Span]~~ | ## Example.to_dict {#to_dict tag="method"} @@ -253,12 +254,14 @@ reference annotation contained in this `Example`. > eg_dict = example.to_dict() > ``` -| Name | Type | Description | -| ----------- | ---------------- | ------------------------------------------------------ | -| **RETURNS** | `Dict[str, Any]` | Dictionary representation of the reference annotation. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------- | +| **RETURNS** | Dictionary representation of the reference annotation. ~~Dict[str, Any]~~ | ## Example.split_sents {#split_sents tag="method"} +Split one `Example` into multiple `Example` objects, one for each sentence. + > #### Example > > ```python @@ -271,11 +274,9 @@ reference annotation contained in this `Example`. > assert split_examples[1].text == "had lots of fun" > ``` -Split one `Example` into multiple `Example` objects, one for each sentence. - -| Name | Type | Description | -| ----------- | --------------- | ---------------------------------------------------------- | -| **RETURNS** | `List[Example]` | List of `Example` objects, one for each original sentence. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------- | +| **RETURNS** | List of `Example` objects, one for each original sentence. ~~List[Example]~~ | ## Alignment {#alignment-object new="3"} @@ -283,10 +284,10 @@ Calculate alignment tables between two tokenizations. ### Alignment attributes {#alignment-attributes"} -| Name | Type | Description | -| ----- | -------------------------------------------------- | ---------------------------------------------------------- | -| `x2y` | [`Ragged`](https://thinc.ai/docs/api-types#ragged) | The `Ragged` object holding the alignment from `x` to `y`. | -| `y2x` | [`Ragged`](https://thinc.ai/docs/api-types#ragged) | The `Ragged` object holding the alignment from `y` to `x`. | +| Name | Description | +| ----- | --------------------------------------------------------------------- | +| `x2y` | The `Ragged` object holding the alignment from `x` to `y`. ~~Ragged~~ | +| `y2x` | The `Ragged` object holding the alignment from `y` to `x`. ~~Ragged~~ | @@ -314,8 +315,8 @@ tokenizations add up to the same string. For example, you'll be able to align ### Alignment.from_strings {#classmethod tag="function"} -| Name | Type | Description | -| ----------- | ----------- | ----------------------------------------------- | -| `A` | list | String values of candidate tokens to align. | -| `B` | list | String values of reference tokens to align. | -| **RETURNS** | `Alignment` | An `Alignment` object describing the alignment. | +| Name | Description | +| ----------- | ------------------------------------------------------------- | +| `A` | String values of candidate tokens to align. ~~List[str]~~ | +| `B` | String values of reference tokens to align. ~~List[str]~~ | +| **RETURNS** | An `Alignment` object describing the alignment. ~~Alignment~~ | diff --git a/website/docs/api/kb.md b/website/docs/api/kb.md index 7b2c4edf4..3db5d6bac 100644 --- a/website/docs/api/kb.md +++ b/website/docs/api/kb.md @@ -9,7 +9,7 @@ new: 2.2 --- The `KnowledgeBase` object provides a method to generate -[`Candidate`](/api/kb/#candidate_init) objects, which are plausible external +[`Candidate`](/api/kb/#candidate) objects, which are plausible external identifiers given a certain textual mention. Each such `Candidate` holds information from the relevant KB entities, such as its frequency in text and possible aliases. Each entity in the knowledge base also has a pretrained entity @@ -27,18 +27,18 @@ Create the knowledge base. > kb = KnowledgeBase(vocab=vocab, entity_vector_length=64) > ``` -| Name | Type | Description | -| ---------------------- | ------- | ---------------------------------------- | -| `vocab` | `Vocab` | A `Vocab` object. | -| `entity_vector_length` | int | Length of the fixed-size entity vectors. | +| Name | Description | +| ---------------------- | ------------------------------------------------ | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `entity_vector_length` | Length of the fixed-size entity vectors. ~~int~~ | ## KnowledgeBase.entity_vector_length {#entity_vector_length tag="property"} The length of the fixed-size entity vectors in the knowledge base. -| Name | Type | Description | -| ----------- | ---- | ---------------------------------------- | -| **RETURNS** | int | Length of the fixed-size entity vectors. | +| Name | Description | +| ----------- | ------------------------------------------------ | +| **RETURNS** | Length of the fixed-size entity vectors. ~~int~~ | ## KnowledgeBase.add_entity {#add_entity tag="method"} @@ -53,11 +53,11 @@ vector, which should be of length > kb.add_entity(entity="Q463035", freq=111, entity_vector=vector2) > ``` -| Name | Type | Description | -| --------------- | ------ | ----------------------------------------------- | -| `entity` | str | The unique entity identifier | -| `freq` | float | The frequency of the entity in a typical corpus | -| `entity_vector` | vector | The pretrained vector of the entity | +| Name | Description | +| --------------- | ---------------------------------------------------------- | +| `entity` | The unique entity identifier. ~~str~~ | +| `freq` | The frequency of the entity in a typical corpus. ~~float~~ | +| `entity_vector` | The pretrained vector of the entity. ~~numpy.ndarray~~ | ## KnowledgeBase.set_entities {#set_entities tag="method"} @@ -70,11 +70,11 @@ frequency and entity vector for each entity. > kb.set_entities(entity_list=["Q42", "Q463035"], freq_list=[32, 111], vector_list=[vector1, vector2]) > ``` -| Name | Type | Description | -| ------------- | -------- | --------------------------------- | -| `entity_list` | iterable | List of unique entity identifiers | -| `freq_list` | iterable | List of entity frequencies | -| `vector_list` | iterable | List of entity vectors | +| Name | Description | +| ------------- | ---------------------------------------------------------------- | +| `entity_list` | List of unique entity identifiers. ~~Iterable[Union[str, int]]~~ | +| `freq_list` | List of entity frequencies. ~~Iterable[int]~~ | +| `vector_list` | List of entity vectors. ~~Iterable[numpy.ndarray]~~ | ## KnowledgeBase.add_alias {#add_alias tag="method"} @@ -90,11 +90,11 @@ should not exceed 1. > kb.add_alias(alias="Douglas", entities=["Q42", "Q463035"], probabilities=[0.6, 0.3]) > ``` -| Name | Type | Description | -| --------------- | -------- | -------------------------------------------------- | -| `alias` | str | The textual mention or alias | -| `entities` | iterable | The potential entities that the alias may refer to | -| `probabilities` | iterable | The prior probabilities of each entity | +| Name | Description | +| --------------- | --------------------------------------------------------------------------------- | +| `alias` | The textual mention or alias. ~~str~~ | +| `entities` | The potential entities that the alias may refer to. ~~Iterable[Union[str, int]]~~ | +| `probabilities` | The prior probabilities of each entity. ~~Iterable[float]~~ | ## KnowledgeBase.\_\_len\_\_ {#len tag="method"} @@ -106,9 +106,9 @@ Get the total number of entities in the knowledge base. > total_entities = len(kb) > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------- | -| **RETURNS** | int | The number of entities in the knowledge base. | +| Name | Description | +| ----------- | ----------------------------------------------------- | +| **RETURNS** | The number of entities in the knowledge base. ~~int~~ | ## KnowledgeBase.get_entity_strings {#get_entity_strings tag="method"} @@ -120,9 +120,9 @@ Get a list of all entity IDs in the knowledge base. > all_entities = kb.get_entity_strings() > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------- | -| **RETURNS** | list | The list of entities in the knowledge base. | +| Name | Description | +| ----------- | --------------------------------------------------------- | +| **RETURNS** | The list of entities in the knowledge base. ~~List[str]~~ | ## KnowledgeBase.get_size_aliases {#get_size_aliases tag="method"} @@ -134,9 +134,9 @@ Get the total number of aliases in the knowledge base. > total_aliases = kb.get_size_aliases() > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------- | -| **RETURNS** | int | The number of aliases in the knowledge base. | +| Name | Description | +| ----------- | ---------------------------------------------------- | +| **RETURNS** | The number of aliases in the knowledge base. ~~int~~ | ## KnowledgeBase.get_alias_strings {#get_alias_strings tag="method"} @@ -148,14 +148,14 @@ Get a list of all aliases in the knowledge base. > all_aliases = kb.get_alias_strings() > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------ | -| **RETURNS** | list | The list of aliases in the knowledge base. | +| Name | Description | +| ----------- | -------------------------------------------------------- | +| **RETURNS** | The list of aliases in the knowledge base. ~~List[str]~~ | ## KnowledgeBase.get_candidates {#get_candidates tag="method"} Given a certain textual mention as input, retrieve a list of candidate entities -of type [`Candidate`](/api/kb/#candidate_init). +of type [`Candidate`](/api/kb/#candidate). > #### Example > @@ -163,10 +163,10 @@ of type [`Candidate`](/api/kb/#candidate_init). > candidates = kb.get_candidates("Douglas") > ``` -| Name | Type | Description | -| ----------- | -------- | ---------------------------------------- | -| `alias` | str | The textual mention or alias | -| **RETURNS** | iterable | The list of relevant `Candidate` objects | +| Name | Description | +| ----------- | ------------------------------------- | +| `alias` | The textual mention or alias. ~~str~~ | +| **RETURNS** | iterable | The list of relevant `Candidate` objects. ~~List[Candidate]~~ | ## KnowledgeBase.get_vector {#get_vector tag="method"} @@ -178,10 +178,10 @@ Given a certain entity ID, retrieve its pretrained entity vector. > vector = kb.get_vector("Q42") > ``` -| Name | Type | Description | -| ----------- | ------ | ----------------- | -| `entity` | str | The entity ID | -| **RETURNS** | vector | The entity vector | +| Name | Description | +| ----------- | ------------------------------------ | +| `entity` | The entity ID. ~~str~~ | +| **RETURNS** | The entity vector. ~~numpy.ndarray~~ | ## KnowledgeBase.get_prior_prob {#get_prior_prob tag="method"} @@ -194,11 +194,11 @@ probability of the fact that the mention links to the entity ID. > probability = kb.get_prior_prob("Q42", "Douglas") > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------- | -| `entity` | str | The entity ID | -| `alias` | str | The textual mention or alias | -| **RETURNS** | float | The prior probability of the `alias` referring to the `entity` | +| Name | Description | +| ----------- | ------------------------------------------------------------------------- | +| `entity` | The entity ID. ~~str~~ | +| `alias` | The textual mention or alias. ~~str~~ | +| **RETURNS** | The prior probability of the `alias` referring to the `entity`. ~~float~~ | ## KnowledgeBase.dump {#dump tag="method"} @@ -210,9 +210,9 @@ Save the current state of the knowledge base to a directory. > kb.dump(loc) > ``` -| Name | Type | Description | -| ----- | ------------ | --------------------------------------------------------------------------------------------------------------------- | -| `loc` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ----- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `loc` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## KnowledgeBase.load_bulk {#load_bulk tag="method"} @@ -229,12 +229,20 @@ Restore the state of the knowledge base from a given directory. Note that the > kb.load_bulk("/path/to/kb") > ``` -| Name | Type | Description | -| ----------- | --------------- | -------------------------------------------------------------------------- | -| `loc` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `KnowledgeBase` | The modified `KnowledgeBase` object. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `loc` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The modified `KnowledgeBase` object. ~~KnowledgeBase~~ | -## Candidate.\_\_init\_\_ {#candidate_init tag="method"} +## Candidate {#candidate tag="class"} + +A `Candidate` object refers to a textual mention (alias) that may or may not be +resolved to a specific entity from a `KnowledgeBase`. This will be used as input +for the entity linking algorithm which will disambiguate the various candidates +to the correct one. Each candidate `(alias, entity)` pair is assigned to a +certain prior probability. + +### Candidate.\_\_init\_\_ {#candidate-init tag="method"} Construct a `Candidate` object. Usually this constructor is not called directly, but instead these objects are returned by the @@ -247,22 +255,22 @@ but instead these objects are returned by the > candidate = Candidate(kb, entity_hash, entity_freq, entity_vector, alias_hash, prior_prob) > ``` -| Name | Type | Description | -| ------------- | --------------- | -------------------------------------------------------------- | -| `kb` | `KnowledgeBase` | The knowledge base that defined this candidate. | -| `entity_hash` | int | The hash of the entity's KB ID. | -| `entity_freq` | float | The entity frequency as recorded in the KB. | -| `alias_hash` | int | The hash of the textual mention or alias. | -| `prior_prob` | float | The prior probability of the `alias` referring to the `entity` | +| Name | Description | +| ------------- | ------------------------------------------------------------------------- | +| `kb` | The knowledge base that defined this candidate. ~~KnowledgeBase~~ | +| `entity_hash` | The hash of the entity's KB ID. ~~int~~ | +| `entity_freq` | The entity frequency as recorded in the KB. ~~float~~ | +| `alias_hash` | The hash of the textual mention or alias. ~~int~~ | +| `prior_prob` | The prior probability of the `alias` referring to the `entity`. ~~float~~ | -## Candidate attributes {#candidate_attributes} +## Candidate attributes {#candidate-attributes} -| Name | Type | Description | -| --------------- | ------ | -------------------------------------------------------------- | -| `entity` | int | The entity's unique KB identifier | -| `entity_` | str | The entity's unique KB identifier | -| `alias` | int | The alias or textual mention | -| `alias_` | str | The alias or textual mention | -| `prior_prob` | long | The prior probability of the `alias` referring to the `entity` | -| `entity_freq` | long | The frequency of the entity in a typical corpus | -| `entity_vector` | vector | The pretrained vector of the entity | +| Name | Description | +| --------------- | ------------------------------------------------------------------------ | +| `entity` | The entity's unique KB identifier. ~~int~~ | +| `entity_` | The entity's unique KB identifier. ~~str~~ | +| `alias` | The alias or textual mention. ~~int~~ | +| `alias_` | The alias or textual mention. ~~str~~ | +| `prior_prob` | The prior probability of the `alias` referring to the `entity`. ~~long~~ | +| `entity_freq` | The frequency of the entity in a typical corpus. ~~long~~ | +| `entity_vector` | The pretrained vector of the entity. ~~numpy.ndarray~~ | diff --git a/website/docs/api/language.md b/website/docs/api/language.md index 41d660421..7d44d47d9 100644 --- a/website/docs/api/language.md +++ b/website/docs/api/language.md @@ -32,13 +32,13 @@ Initialize a `Language` object. > nlp = Language(Vocab()) > ``` -| Name | Type | Description | -| ------------------ | ----------- | ------------------------------------------------------------------------------------------ | -| `vocab` | `Vocab` | A `Vocab` object. If `True`, a vocab is created using the default language data settings. | -| _keyword-only_ | | | -| `max_length` | int | Maximum number of characters allowed in a single text. Defaults to `10 ** 6`. | -| `meta` | dict | Custom meta data for the `Language` class. Is written to by models to add model meta data. | -| `create_tokenizer` |  `Callable` | Optional function that receives the `nlp` object and returns a tokenizer. | +| Name | Description | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------ | +| `vocab` | A `Vocab` object. If `True`, a vocab is created using the default language data settings. ~~Vocab~~ | +| _keyword-only_ | | +| `max_length` | Maximum number of characters allowed in a single text. Defaults to `10 ** 6`. ~~int~~ | +| `meta` | Custom meta data for the `Language` class. Is written to by models to add model meta data. ~~dict~~ | +| `create_tokenizer` | Optional function that receives the `nlp` object and returns a tokenizer. ~~Callable[[Language], Callable[[str], Doc]]~~ | ## Language.from_config {#from_config tag="classmethod"} @@ -58,14 +58,14 @@ model under the hood based on its [`config.cfg`](/api/data-formats#config). > nlp = Language.from_config(config) > ``` -| Name | Type | Description | -| -------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `config` | `Dict[str, Any]` / [`Config`](https://thinc.ai/docs/api-config#config) | The loaded config. | -| _keyword-only_ | | -| `disable` | `Iterable[str]` | List of pipeline component names to disable. | -| `auto_fill` | bool | Whether to automatically fill in missing values in the config, based on defaults and function argument annotations. Defaults to `True`. | -| `validate` | bool | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. | -| **RETURNS** | `Language` | The initialized object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| `config` | The loaded config. ~~Union[Dict[str, Any], Config]~~ | +| _keyword-only_ | | +| `disable` | List of pipeline component names to disable. ~~Iterable[str]~~ | +| `auto_fill` | Whether to automatically fill in missing values in the config, based on defaults and function argument annotations. Defaults to `True`. ~~bool~~ | +| `validate` | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. ~~bool~~ | +| **RETURNS** | The initialized object. ~~Language~~ | ## Language.component {#component tag="classmethod" new="3"} @@ -94,16 +94,14 @@ decorator. For more details and examples, see the > Language.component("my_component2", func=my_component) > ``` -| Name | Type | Description | -| ----------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | The name of the component factory. | -| _keyword-only_ | | | -| `assigns` | `Iterable[str]` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis).. | -| `requires` | `Iterable[str]` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `retokenizes` | bool | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `scores` | `Iterable[str]` | All scores set by the components if it's trainable, e.g. `["ents_f", "ents_r", "ents_p"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `default_score_weights` | `Dict[str, float]` | The scores to report during training, and their default weight towards the final score used to select the best model. Weights should sum to `1.0` per component and will be combined and normalized for the whole pipeline. | -| `func` | `Optional[Callable]` | Optional function if not used a a decorator. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | The name of the component factory. ~~str~~ | +| _keyword-only_ | | +| `assigns` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `requires` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `retokenizes` | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~bool~~ | +| `func` | Optional function if not used a a decorator. ~~Optional[Callable[[Doc], Doc]]~~ | ## Language.factory {#factory tag="classmethod"} @@ -141,17 +139,17 @@ examples, see the > ) > ``` -| Name | Type | Description | -| ----------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | The name of the component factory. | -| _keyword-only_ | | | -| `default_config` | `Dict[str, any]` | The default config, describing the default values of the factory arguments. | -| `assigns` | `Iterable[str]` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `requires` | `Iterable[str]` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `retokenizes` | bool | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `scores` | `Iterable[str]` | All scores set by the components if it's trainable, e.g. `["ents_f", "ents_r", "ents_p"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `default_score_weights` | `Dict[str, float]` | The scores to report during training, and their default weight towards the final score used to select the best model. Weights should sum to `1.0` per component and will be combined and normalized for the whole pipeline. | -| `func` | `Optional[Callable]` | Optional function if not used a a decorator. | +| Name | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | The name of the component factory. ~~str~~ | +| _keyword-only_ | | +| `default_config` | The default config, describing the default values of the factory arguments. ~~Dict[str, Any]~~ | +| `assigns` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `requires` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `retokenizes` | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~bool~~ | +| `scores` | All scores set by the components if it's trainable, e.g. `["ents_f", "ents_r", "ents_p"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `default_score_weights` | The scores to report during training, and their default weight towards the final score used to select the best model. Weights should sum to `1.0` per component and will be combined and normalized for the whole pipeline. ~~Dict[str, float]~~ | +| `func` | Optional function if not used a a decorator. ~~Optional[Callable[[...], Callable[[Doc], Doc]]]~~ | ## Language.\_\_call\_\_ {#call tag="method"} @@ -165,13 +163,13 @@ contain arbitrary whitespace. Alignment into the original string is preserved. > assert (doc[0].text, doc[0].head.tag_) == ("An", "NN") > ``` -| Name | Type | Description | -| --------------- | ----------------- | ------------------------------------------------------------------------------------------------------ | -| `text` | str | The text to be processed. | -| _keyword-only_ | | | -| `disable` | `List[str]` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). | -| `component_cfg` | `Dict[str, dict]` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. | -| **RETURNS** | [`Doc`](/api/doc) | A container for accessing the annotations. | +| Name | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `text` | The text to be processed. ~~str~~ | +| _keyword-only_ | | +| `disable` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). ~~List[str]~~ | +| `component_cfg` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. ~~Optional[Dict[str, Dict[str, Any]]]~~ | +| **RETURNS** | A container for accessing the annotations. ~~Doc~~ | ## Language.pipe {#pipe tag="method"} @@ -186,17 +184,17 @@ more efficient than processing texts one-by-one. > assert doc.is_parsed > ``` -| Name | Type | Description | -| ------------------------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `texts` | `Iterable[str]` | A sequence of strings. | -| _keyword-only_ | | | -| `as_tuples` | bool | If set to `True`, inputs should be a sequence of `(text, context)` tuples. Output will then be a sequence of `(doc, context)` tuples. Defaults to `False`. | -| `batch_size` | int | The number of texts to buffer. | -| `disable` | `List[str]` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). | -| `cleanup` | bool | If `True`, unneeded strings are freed to control memory use. Experimental. | -| `component_cfg` | `Dict[str, dict]` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. | -| `n_process` 2.2.2 | int | Number of processors to use, only supported in Python 3. Defaults to `1`. | -| **YIELDS** | `Doc` | Documents in the order of the original text. | +| Name | Description | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `texts` | A sequence of strings. ~~Iterable[str]~~ | +| _keyword-only_ | | +| `as_tuples` | If set to `True`, inputs should be a sequence of `(text, context)` tuples. Output will then be a sequence of `(doc, context)` tuples. Defaults to `False`. ~~bool~~ | +| `batch_size` | The number of texts to buffer. ~~int~~ | +| `disable` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). ~~List[str]~~ | +| `cleanup` | If `True`, unneeded strings are freed to control memory use. Experimental. ~~bool~~ | +| `component_cfg` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. ~~Optional[Dict[str, Dict[str, Any]]]~~ | +| `n_process` 2.2.2 | Number of processors to use. Defaults to `1`. ~~int~~ | +| **YIELDS** | Documents in the order of the original text. ~~Doc~~ | ## Language.begin_training {#begin_training tag="method"} @@ -225,12 +223,12 @@ tuples of `Doc` and `GoldParse` objects. > optimizer = nlp.begin_training(get_examples) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/language#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Optional[Callable[[], Iterable[Example]]]~~ | +| _keyword-only_ | | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Language.resume_training {#resume_training tag="method,experimental" new="3"} @@ -248,11 +246,11 @@ a batch of [Example](/api/example) objects. > nlp.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/language#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Language.update {#update tag="method"} @@ -282,15 +280,15 @@ and custom registered functions if needed. See the > nlp.update([example], sgd=optimizer) > ``` -| Name | Type | Description | -| --------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Dictionary to update with the loss, keyed by pipeline component. | -| `component_cfg` | `Dict[str, dict]` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Dictionary to update with the loss, keyed by pipeline component. ~~Optional[Dict[str, float]]~~ | +| `component_cfg` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. ~~Optional[Dict[str, Dict[str, Any]]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Language.rehearse {#rehearse tag="method,experimental" new="3"} @@ -305,14 +303,14 @@ the "catastrophic forgetting" problem. This feature is experimental. > losses = nlp.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Dictionary to update with the loss, keyed by pipeline component. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Language.evaluate {#evaluate tag="method"} @@ -328,20 +326,19 @@ objects instead of tuples of `Doc` and `GoldParse` objects. > #### Example > > ```python -> scores = nlp.evaluate(examples, verbose=True) +> scores = nlp.evaluate(examples) > print(scores) > ``` -| Name | Type | Description | -| --------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `verbose` | bool | Print debugging information. | -| `batch_size` | int | The batch size to use. | -| `scorer` | `Scorer` | Optional [`Scorer`](/api/scorer) to use. If not passed in, a new one will be created. | -| `component_cfg` | `Dict[str, dict]` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. | -| `scorer_cfg` | `Dict[str, Any]` | Optional dictionary of keyword arguments for the `Scorer`. Defaults to `None`. | -| **RETURNS** | `Dict[str, Union[float, dict]]` | A dictionary of evaluation scores. | +| Name | Description | +| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | +| `batch_size` | The batch size to use. ~~int~~ | +| `scorer` | Optional [`Scorer`](/api/scorer) to use. If not passed in, a new one will be created. ~~Optional[Scorer]~~ | +| `component_cfg` | Optional dictionary of keyword arguments for components, keyed by component names. Defaults to `None`. ~~Optional[Dict[str, Dict[str, Any]]]~~ | +| `scorer_cfg` | Optional dictionary of keyword arguments for the `Scorer`. Defaults to `None`. ~~Optional[Dict[str, Any]]~~ | +| **RETURNS** | A dictionary of evaluation scores. ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## Language.use_params {#use_params tag="contextmanager, method"} @@ -356,9 +353,9 @@ their original weights after the block. > nlp.to_disk("/tmp/checkpoint") > ``` -| Name | Type | Description | -| -------- | ---- | --------------------------------------------- | -| `params` | dict | A dictionary of parameters keyed by model ID. | +| Name | Description | +| -------- | ------------------------------------------------------ | +| `params` | A dictionary of parameters keyed by model ID. ~~dict~~ | ## Language.create_pipe {#create_pipe tag="method" new="2"} @@ -380,14 +377,14 @@ To create a component and add it to the pipeline, you should always use > parser = nlp.create_pipe("parser") > ``` -| Name | Type | Description | -| ------------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `factory_name` | str | Name of the registered component factory. | -| `name` | str | Optional unique name of pipeline component instance. If not set, the factory name is used. An error is raised if the name already exists in the pipeline. | -| _keyword-only_ | | | -| `config` 3 | `Dict[str, Any]` | Optional config parameters to use for this component. Will be merged with the `default_config` specified by the component factory. | -| `validate` 3 | bool | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. | -| **RETURNS** | callable | The pipeline component. | +| Name | Description | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `factory_name` | Name of the registered component factory. ~~str~~ | +| `name` | Optional unique name of pipeline component instance. If not set, the factory name is used. An error is raised if the name already exists in the pipeline. ~~Optional[str]~~ | +| _keyword-only_ | | +| `config` 3 | Optional config parameters to use for this component. Will be merged with the `default_config` specified by the component factory. ~~Optional[Dict[str, Any]]~~ | +| `validate` 3 | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. ~~bool~~ | +| **RETURNS** | The pipeline component. ~~Callable[[Doc], Doc]~~ | ## Language.add_pipe {#add_pipe tag="method" new="2"} @@ -423,19 +420,19 @@ component, adds it to the pipeline and returns it. > nlp.add_pipe("ner", source=source_nlp) > ``` -| Name | Type | Description | -| -------------------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `factory_name` | str | Name of the registered component factory. | -| `name` | str | Optional unique name of pipeline component instance. If not set, the factory name is used. An error is raised if the name already exists in the pipeline. | -| _keyword-only_ | | | -| `before` | str / int | Component name or index to insert component directly before. | -| `after` | str / int | Component name or index to insert component directly after: | -| `first` | bool | Insert component first / not first in the pipeline. | -| `last` | bool | Insert component last / not last in the pipeline. | -| `config` 3 | `Dict[str, Any]` | Optional config parameters to use for this component. Will be merged with the `default_config` specified by the component factory. | -| `source` 3 | `Language` | Optional source model to copy component from. If a source is provided, the `factory_name` is interpreted as the name of the component in the source pipeline. Make sure that the vocab, vectors and settings of the source model match the target model. | -| `validate` 3 | bool | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. | -| **RETURNS** 3 | callable | The pipeline component. | +| Name | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `factory_name` | Name of the registered component factory. ~~str~~ | +| `name` | Optional unique name of pipeline component instance. If not set, the factory name is used. An error is raised if the name already exists in the pipeline. ~~Optional[str]~~ | +| _keyword-only_ | | +| `before` | Component name or index to insert component directly before. ~~Optional[Union[str, int]]~~ | +| `after` | Component name or index to insert component directly after. ~~Optional[Union[str, int]]~~ | +| `first` | Insert component first / not first in the pipeline. ~~Optional[bool]~~ | +| `last` | Insert component last / not last in the pipeline. ~~Optional[bool]~~ | +| `config` 3 | Optional config parameters to use for this component. Will be merged with the `default_config` specified by the component factory. ~~Optional[Dict[str, Any]]~~ | +| `source` 3 | Optional source model to copy component from. If a source is provided, the `factory_name` is interpreted as the name of the component in the source pipeline. Make sure that the vocab, vectors and settings of the source model match the target model. ~~Optional[Language]~~ | +| `validate` 3 | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. ~~bool~~ | +| **RETURNS** | The pipeline component. ~~Callable[[Doc], Doc]~~ | ## Language.has_factory {#has_factory tag="classmethod" new="3"} @@ -459,10 +456,10 @@ the `Language` base class, available to all subclasses. > assert not Language.has_factory("component") > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------------------------------------------------- | -| `name` | str | Name of the pipeline factory to check. | -| **RETURNS** | bool | Whether a factory of that name is registered on the class. | +| Name | Description | +| ----------- | ------------------------------------------------------------------- | +| `name` | Name of the pipeline factory to check. ~~str~~ | +| **RETURNS** | Whether a factory of that name is registered on the class. ~~bool~~ | ## Language.has_pipe {#has_pipe tag="method" new="2"} @@ -481,10 +478,10 @@ Check whether a component is present in the pipeline. Equivalent to > assert nlp.has_pipe("my_component") > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------------- | -| `name` | str | Name of the pipeline component to check. | -| **RETURNS** | bool | Whether a component of that name exists in the pipeline. | +| Name | Description | +| ----------- | ----------------------------------------------------------------- | +| `name` | Name of the pipeline component to check. ~~str~~ | +| **RETURNS** | Whether a component of that name exists in the pipeline. ~~bool~~ | ## Language.get_pipe {#get_pipe tag="method" new="2"} @@ -497,28 +494,37 @@ Get a pipeline component for a given component name. > custom_component = nlp.get_pipe("custom_component") > ``` -| Name | Type | Description | -| ----------- | -------- | -------------------------------------- | -| `name` | str | Name of the pipeline component to get. | -| **RETURNS** | callable | The pipeline component. | +| Name | Description | +| ----------- | ------------------------------------------------ | +| `name` | Name of the pipeline component to get. ~~str~~ | +| **RETURNS** | The pipeline component. ~~Callable[[Doc], Doc]~~ | ## Language.replace_pipe {#replace_pipe tag="method" new="2"} Replace a component in the pipeline. + + +As of v3.0, the `Language.replace_pipe` method doesn't take callables anymore +and instead expects the **name of a component factory** registered using +[`@Language.component`](/api/language#component) or +[`@Language.factory`](/api/language#factory). + + + > #### Example > > ```python > nlp.replace_pipe("parser", my_custom_parser) > ``` -| Name | Type | Description | -| ------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | Name of the component to replace. | -| `component` | callable | The pipeline component to insert. | -| _keyword-only_ | | | -| `config` 3 | `Dict[str, Any]` | Optional config parameters to use for the new component. Will be merged with the `default_config` specified by the component factory. | -| `validate` 3 | bool | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. | +| Name | Description | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | Name of the component to replace. ~~str~~ | +| `component` | The factory name of the component to insert. ~~str~~ | +| _keyword-only_ | | +| `config` 3 | Optional config parameters to use for the new component. Will be merged with the `default_config` specified by the component factory. ~~Optional[Dict[str, Any]]~~ | +| `validate` 3 | Whether to validate the component config and arguments against the types expected by the factory. Defaults to `True`. ~~bool~~ | ## Language.rename_pipe {#rename_pipe tag="method" new="2"} @@ -533,10 +539,10 @@ added to the pipeline, you can also use the `name` argument on > nlp.rename_pipe("parser", "spacy_parser") > ``` -| Name | Type | Description | -| ---------- | ---- | -------------------------------- | -| `old_name` | str | Name of the component to rename. | -| `new_name` | str | New name of the component. | +| Name | Description | +| ---------- | ---------------------------------------- | +| `old_name` | Name of the component to rename. ~~str~~ | +| `new_name` | New name of the component. ~~str~~ | ## Language.remove_pipe {#remove_pipe tag="method" new="2"} @@ -550,10 +556,10 @@ component function. > assert name == "parser" > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------------------------------------- | -| `name` | str | Name of the component to remove. | -| **RETURNS** | tuple | A `(name, component)` tuple of the removed component. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------ | +| `name` | Name of the component to remove. ~~str~~ | +| **RETURNS** | A `(name, component)` tuple of the removed component. ~~Tuple[str, Callable[[Doc], Doc]]~~ | ## Language.select_pipes {#select_pipes tag="contextmanager, method" new="3"} @@ -589,12 +595,12 @@ As of spaCy v3.0, the `disable_pipes` method has been renamed to `select_pipes`: -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------------------ | -| _keyword-only_ | | | -| `disable` | str / list | Name(s) of pipeline components to disable. | -| `enable` | str / list | Names(s) of pipeline components that will not be disabled. | -| **RETURNS** | `DisabledPipes` | The disabled pipes that can be restored by calling the object's `.restore()` method. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| _keyword-only_ | | +| `disable` | Name(s) of pipeline components to disable. ~~Optional[Union[str, Iterable[str]]]~~ | +| `enable` | Names(s) of pipeline components that will not be disabled. ~~Optional[Union[str, Iterable[str]]]~~ | +| **RETURNS** | The disabled pipes that can be restored by calling the object's `.restore()` method. ~~DisabledPipes~~ | ## Language.get_factory_meta {#get_factory_meta tag="classmethod" new="3"} @@ -613,10 +619,10 @@ information about the component and its default provided by the > print(factory_meta.default_config) > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | ------------------ | -| `name` | str | The factory name. | -| **RETURNS** | [`FactoryMeta`](#factorymeta) |  The factory meta. | +| Name | Description | +| ----------- | --------------------------------- | +| `name` | The factory name. ~~str~~ | +| **RETURNS** | The factory meta. ~~FactoryMeta~~ | ## Language.get_pipe_meta {#get_pipe_meta tag="method" new="3"} @@ -636,10 +642,10 @@ contains the information about the component and its default provided by the > print(factory_meta.default_config) > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | ---------------------------- | -| `name` | str | The pipeline component name. | -| **RETURNS** | [`FactoryMeta`](#factorymeta) |  The factory meta. | +| Name | Description | +| ----------- | ------------------------------------ | +| `name` | The pipeline component name. ~~str~~ | +| **RETURNS** | The factory meta. ~~FactoryMeta~~ | ## Language.analyze_pipes {#analyze_pipes tag="method" new="3"} @@ -725,12 +731,12 @@ token.ent_iob, token.ent_type
-| Name | Type | Description | -| -------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `keys` | `List[str]` | The values to display in the table. Corresponds to attributes of the [`FactoryMeta`](/api/language#factorymeta). Defaults to `["assigns", "requires", "scores", "retokenizes"]`. | -| `pretty` | bool | Pretty-print the results as a table. Defaults to `False`. | -| **RETURNS** | dict | Dictionary containing the pipe analysis, keyed by `"summary"` (component meta by pipe), `"problems"` (attribute names by pipe) and `"attrs"` (pipes that assign and require an attribute, keyed by attribute). | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `keys` | The values to display in the table. Corresponds to attributes of the [`FactoryMeta`](/api/language#factorymeta). Defaults to `["assigns", "requires", "scores", "retokenizes"]`. ~~List[str]~~ | +| `pretty` | Pretty-print the results as a table. Defaults to `False`. ~~bool~~ | +| **RETURNS** | Dictionary containing the pipe analysis, keyed by `"summary"` (component meta by pipe), `"problems"` (attribute names by pipe) and `"attrs"` (pipes that assign and require an attribute, keyed by attribute). ~~Optional[Dict[str, Any]]~~ | ## Language.meta {#meta tag="property"} @@ -744,9 +750,9 @@ data of the model. The `Language.meta` is also what's serialized as the > print(nlp.meta) > ``` -| Name | Type | Description | -| ----------- | ---- | -------------- | -| **RETURNS** | dict | The meta data. | +| Name | Description | +| ----------- | --------------------------------- | +| **RETURNS** | The meta data. ~~Dict[str, Any]~~ | ## Language.config {#config tag="property" new="3"} @@ -765,9 +771,9 @@ subclass of the built-in `dict`. It supports the additional methods `to_disk` > print(nlp.config.to_str()) > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | ----------- | -| **RETURNS** | [`Config`](https://thinc.ai/docs/api-config#config) | The config. | +| Name | Description | +| ----------- | ---------------------- | +| **RETURNS** | The config. ~~Config~~ | ## Language.to_disk {#to_disk tag="method" new="2"} @@ -780,11 +786,11 @@ the model**. > nlp.to_disk("/path/to/models") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Language.from_disk {#from_disk tag="method" new="2"} @@ -806,12 +812,12 @@ loaded object. > nlp = English().from_disk("/path/to/en_model") > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Language` | The modified `Language` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Language` object. ~~Language~~ | ## Language.to_bytes {#to_bytes tag="method"} @@ -823,11 +829,11 @@ Serialize the current state to a binary string. > nlp_bytes = nlp.to_bytes() > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Language` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------ | +| _keyword-only_ | | +| `exclude` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. ~~iterable~~ | +| **RETURNS** | The serialized form of the `Language` object. ~~bytes~~ | ## Language.from_bytes {#from_bytes tag="method"} @@ -845,35 +851,35 @@ available to the loaded object. > nlp2.from_bytes(nlp_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Language` | The `Language` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | Names of pipeline components or [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Language` object. ~~Language~~ | ## Attributes {#attributes} -| Name | Type | Description | -| --------------------------------------------- | ---------------------- | ---------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | A container for the lexical types. | -| `tokenizer` | `Tokenizer` | The tokenizer. | -| `make_doc` | `Callable` | Callable that takes a string and returns a `Doc`. | -| `pipeline` | `List[str, Callable]` | List of `(name, component)` tuples describing the current processing pipeline, in order. | -| `pipe_names` 2 | `List[str]` | List of pipeline component names, in order. | -| `pipe_labels` 2.2 | `Dict[str, List[str]]` | List of labels set by the pipeline components, if available, keyed by component name. | -| `pipe_factories` 2.2 | `Dict[str, str]` | Dictionary of pipeline component names, mapped to their factory names. | -| `factories` | `Dict[str, Callable]` | All available factory functions, keyed by name. | -| `factory_names` 3 | `List[str]` | List of all available factory names. | -| `path` 2 | `Path` | Path to the model data directory, if a model is loaded. Otherwise `None`. | +| Name | Description | +| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | A container for the lexical types. ~~Vocab~~ | +| `tokenizer` | The tokenizer. ~~Tokenizer~~ | +| `make_doc` | Callable that takes a string and returns a `Doc`. ~~Callable[[str], Doc]~~ | +| `pipeline` | List of `(name, component)` tuples describing the current processing pipeline, in order. ~~List[str, Callable[[Doc], Doc]]~~ | +| `pipe_names` 2 | List of pipeline component names, in order. ~~List[str]~~ | +| `pipe_labels` 2.2 | List of labels set by the pipeline components, if available, keyed by component name. ~~Dict[str, List[str]]~~ | +| `pipe_factories` 2.2 | Dictionary of pipeline component names, mapped to their factory names. ~~Dict[str, str]~~ | +| `factories` | All available factory functions, keyed by name. ~~Dict[str, Callable[[...], Callable[[Doc], Doc]]]~~ | +| `factory_names` 3 | List of all available factory names. ~~List[str]~~ | +| `path` 2 | Path to the model data directory, if a model is loaded. Otherwise `None`. ~~Optional[Path]~~ | ## Class attributes {#class-attributes} -| Name | Type | Description | -| ---------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `Defaults` | class | Settings, data and factory methods for creating the `nlp` object and processing pipeline. | -| `lang` | str | Two-letter language ID, i.e. [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). | -| `default_config` | dict | Base [config](/usage/training#config) to use for [Language.config](/api/language#config). Defaults to [`default_config.cfg`](https://github.com/explosion/spaCy/tree/develop/spacy/default_config.cfg). | +| Name | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `Defaults` | Settings, data and factory methods for creating the `nlp` object and processing pipeline. ~~Defaults~~ | +| `lang` | Two-letter language ID, i.e. [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). ~~str~~ | +| `default_config` | Base [config](/usage/training#config) to use for [Language.config](/api/language#config). Defaults to [`default_config.cfg`](https://github.com/explosion/spaCy/tree/develop/spacy/default_config.cfg). ~~Config~~ | ## Defaults {#defaults} @@ -906,17 +912,17 @@ customize the default language data: > config = Config().from_str(DEFAULT_CONFIG) > ``` -| Name | Description | -| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `stop_words` | List of stop words, used for `Token.is_stop`.
**Example:** [`stop_words.py`][stop_words.py] | -| `tokenizer_exceptions` | Tokenizer exception rules, string mapped to list of token attributes.
**Example:** [`de/tokenizer_exceptions.py`][de/tokenizer_exceptions.py] | -| `prefixes`, `suffixes`, `infixes` | Prefix, suffix and infix rules for the default tokenizer.
**Example:** [`puncutation.py`][punctuation.py] | -| `token_match` | Optional regex for matching strings that should never be split, overriding the infix rules.
**Example:** [`fr/tokenizer_exceptions.py`][fr/tokenizer_exceptions.py] | -| `url_match` | Regular expression for matching URLs. Prefixes and suffixes are removed before applying the match.
**Example:** [`tokenizer_exceptions.py`][tokenizer_exceptions.py] | -| `lex_attr_getters` | Custom functions for setting lexical attributes on tokens, e.g. `like_num`.
**Example:** [`lex_attrs.py`][lex_attrs.py] | -| `syntax_iterators` | Functions that compute views of a `Doc` object based on its syntax. At the moment, only used for [noun chunks](/usage/linguistic-features#noun-chunks).
**Example:** [`syntax_iterators.py`][syntax_iterators.py]. | -| `writing_system` | Information about the language's writing system, available via `Vocab.writing_system`. Defaults to: `{"direction": "ltr", "has_case": True, "has_letters": True}.`.
**Example:** [`zh/__init__.py`][zh/__init__.py] | -| `config` | Default [config](/usage/training#config) added to `nlp.config`. This can include references to custom tokenizers or lemmatizers.
**Example:** [`zh/__init__.py`][zh/__init__.py] | +| Name | Description | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `stop_words` | List of stop words, used for `Token.is_stop`.
**Example:** [`stop_words.py`][stop_words.py] ~~Set[str]~~ | +| `tokenizer_exceptions` | Tokenizer exception rules, string mapped to list of token attributes.
**Example:** [`de/tokenizer_exceptions.py`][de/tokenizer_exceptions.py] ~~Dict[str, List[dict]]~~ | +| `prefixes`, `suffixes`, `infixes` | Prefix, suffix and infix rules for the default tokenizer.
**Example:** [`puncutation.py`][punctuation.py] ~~Optional[List[Union[str, Pattern]]]~~ | +| `token_match` | Optional regex for matching strings that should never be split, overriding the infix rules.
**Example:** [`fr/tokenizer_exceptions.py`][fr/tokenizer_exceptions.py] ~~Optional[Pattern]~~ | +| `url_match` | Regular expression for matching URLs. Prefixes and suffixes are removed before applying the match.
**Example:** [`tokenizer_exceptions.py`][tokenizer_exceptions.py] ~~Optional[Pattern]~~ | +| `lex_attr_getters` | Custom functions for setting lexical attributes on tokens, e.g. `like_num`.
**Example:** [`lex_attrs.py`][lex_attrs.py] ~~Dict[int, Callable[[str], Any]]~~ | +| `syntax_iterators` | Functions that compute views of a `Doc` object based on its syntax. At the moment, only used for [noun chunks](/usage/linguistic-features#noun-chunks).
**Example:** [`syntax_iterators.py`][syntax_iterators.py]. ~~Dict[str, Callable[[Union[Doc, Span]], Iterator[Span]]]~~ | +| `writing_system` | Information about the language's writing system, available via `Vocab.writing_system`. Defaults to: `{"direction": "ltr", "has_case": True, "has_letters": True}.`.
**Example:** [`zh/__init__.py`][zh/__init__.py] ~~Dict[str, Any]~~ | +| `config` | Default [config](/usage/training#config) added to `nlp.config`. This can include references to custom tokenizers or lemmatizers.
**Example:** [`zh/__init__.py`][zh/__init__.py] ~~Config~~ | [stop_words.py]: https://github.com/explosion/spaCy/tree/master/spacy/lang/en/stop_words.py @@ -963,12 +969,12 @@ provided by the [`@Language.component`](/api/language#component) or component is defined and stored on the `Language` class for each component instance and factory instance. -| Name | Type | Description | -| ----------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `factory` | str | The name of the registered component factory. | -| `default_config` | `Dict[str, Any]` | The default config, describing the default values of the factory arguments. | -| `assigns` | `Iterable[str]` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `requires` | `Iterable[str]` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis).  | -| `retokenizes` | bool | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis).  | -| `scores` | `Iterable[str]` | All scores set by the components if it's trainable, e.g. `["ents_f", "ents_r", "ents_p"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). | -| `default_score_weights` | `Dict[str, float]` | The scores to report during training, and their default weight towards the final score used to select the best model. Weights should sum to `1.0` per component and will be combined and normalized for the whole pipeline. | +| Name | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `factory` | The name of the registered component factory. ~~str~~ | +| `default_config` | The default config, describing the default values of the factory arguments. ~~Dict[str, Any]~~ | +| `assigns` | `Doc` or `Token` attributes assigned by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `requires` | `Doc` or `Token` attributes required by this component, e.g. `["token.ent_id"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~  | +| `retokenizes` | Whether the component changes tokenization. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~bool~~  | +| `scores` | All scores set by the components if it's trainable, e.g. `["ents_f", "ents_r", "ents_p"]`. Used for [pipe analysis](/usage/processing-pipelines#analysis). ~~Iterable[str]~~ | +| `default_score_weights` | The scores to report during training, and their default weight towards the final score used to select the best model. Weights should sum to `1.0` per component and will be combined and normalized for the whole pipeline. ~~Dict[str, float]~~ | diff --git a/website/docs/api/lemmatizer.md b/website/docs/api/lemmatizer.md index f1242d193..8417fd5e8 100644 --- a/website/docs/api/lemmatizer.md +++ b/website/docs/api/lemmatizer.md @@ -36,11 +36,9 @@ tags is available in the pipeline and runs _before_ the lemmatizer. The default config is defined by the pipeline component factory and describes how the component should be configured. You can override its settings via the `config` argument on [`nlp.add_pipe`](/api/language#add_pipe) or in your -[`config.cfg` for training](/usage/training#config). - -For examples of the lookups data formats used by the lookup and rule-based -lemmatizers, see the -[`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data) repo. +[`config.cfg` for training](/usage/training#config). For examples of the lookups +data formats used by the lookup and rule-based lemmatizers, see +[`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data). > #### Example > @@ -49,12 +47,12 @@ lemmatizers, see the > nlp.add_pipe("lemmatizer", config=config) > ``` -| Setting | Type | Description | Default | -| ----------- | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | -| `mode` | str | The lemmatizer mode, e.g. `"lookup"` or `"rule"`. | `"lookup"` | -| `lookups` | [`Lookups`](/api/lookups) | The lookups object containing the tables such as `"lemma_rules"`, `"lemma_index"`, `"lemma_exc"` and `"lemma_lookup"`. If `None`, default tables are loaded from `spacy-lookups-data`. | `None` | -| `overwrite` | bool | Whether to overwrite existing lemmas. | `False` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | **Not yet implemented:** the model to use. | `None` | +| Setting | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mode` | The lemmatizer mode, e.g. `"lookup"` or `"rule"`. Defaults to `"lookup"`. ~~str~~ | +| `lookups` | The lookups object containing the tables such as `"lemma_rules"`, `"lemma_index"`, `"lemma_exc"` and `"lemma_lookup"`. If `None`, default tables are loaded from [`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data). Defaults to `None`. ~~Optional[Lookups]~~ | +| `overwrite` | Whether to overwrite existing lemmas. Defaults to `False`. ~~bool~~ | +| `model` | **Not yet implemented:** the model to use. ~~Model~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/lemmatizer.py @@ -77,15 +75,15 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#add_pipe). -| Name | Type | Description | -| -------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | [`Vocab`](/api/vocab) | The vocab. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | A model (not yet implemented). | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| _keyword-only_ | | | -| mode | str | The lemmatizer mode, e.g. `"lookup"` or `"rule"`. Defaults to `"lookup"`. | -| lookups | [`Lookups`](/api/lookups) | A lookups object containing the tables such as `"lemma_rules"`, `"lemma_index"`, `"lemma_exc"` and `"lemma_lookup"`. Defaults to `None`. | -| overwrite | bool | Whether to overwrite existing lemmas. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | **Not yet implemented:** The model to use. ~~Model~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | | +| mode | The lemmatizer mode, e.g. `"lookup"` or `"rule"`. Defaults to `"lookup"`. ~~str~~ | +| lookups | A lookups object containing the tables such as `"lemma_rules"`, `"lemma_index"`, `"lemma_exc"` and `"lemma_lookup"`. Defaults to `None`. ~~Optional[Lookups]~~ | +| overwrite | Whether to overwrite existing lemmas. ~~bool~ | ## Lemmatizer.\_\_call\_\_ {#call tag="method"} @@ -102,10 +100,10 @@ and all pipeline components are applied to the `Doc` in order. > processed = lemmatizer(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Lemmatizer.pipe {#pipe tag="method"} @@ -121,12 +119,12 @@ applied to the `Doc` in order. > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Lemmatizer.lookup_lemmatize {#lookup_lemmatize tag="method"} @@ -134,39 +132,39 @@ Lemmatize a token using a lookup-based approach. If no lemma is found, the original string is returned. Languages can provide a [lookup table](/usage/adding-languages#lemmatizer) via the `Lookups`. -| Name | Type | Description | -| ----------- | --------------------- | ------------------------------------- | -| `token` | [`Token`](/api/token) | The token to lemmatize. | -| **RETURNS** | `List[str]` | A list containing one or more lemmas. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| `token` | The token to lemmatize. ~~Token~~ | +| **RETURNS** | A list containing one or more lemmas. ~~List[str]~~ | ## Lemmatizer.rule_lemmatize {#rule_lemmatize tag="method"} Lemmatize a token using a rule-based approach. Typically relies on POS tags. -| Name | Type | Description | -| ----------- | --------------------- | ------------------------------------- | -| `token` | [`Token`](/api/token) | The token to lemmatize. | -| **RETURNS** | `List[str]` | A list containing one or more lemmas. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| `token` | The token to lemmatize. ~~Token~~ | +| **RETURNS** | A list containing one or more lemmas. ~~List[str]~~ | ## Lemmatizer.is_base_form {#is_base_form tag="method"} Check whether we're dealing with an uninflected paradigm, so we can avoid lemmatization entirely. -| Name | Type | Description | -| ----------- | --------------------- | ------------------------------------------------------------------------------------------------------- | -| `token` | [`Token`](/api/token) | The token to analyze. | -| **RETURNS** | bool | Whether the token's attributes (e.g., part-of-speech tag, morphological features) describe a base form. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------- | +| `token` | The token to analyze. ~~Token~~ | +| **RETURNS** | Whether the token's attributes (e.g., part-of-speech tag, morphological features) describe a base form. ~~bool~~ | ## Lemmatizer.get_lookups_config {#get_lookups_config tag="classmethod"} Returns the lookups configuration settings for a given mode for use in -[`Lemmatizer.load_lookups`](#load_lookups). +[`Lemmatizer.load_lookups`](/api/lemmatizer#load_lookups). -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------------- | -| `mode` | str | The lemmatizer mode. | -| **RETURNS** | dict | The lookups configuration settings for this mode. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mode` | The lemmatizer mode. ~~str~~ | +| **RETURNS** | The lookups configuration settings for this mode. Includes the keys `"required_tables"` and `"optional_tables"`, mapped to a list of table string names. ~~Dict[str, List[str]]~~ | ## Lemmatizer.load_lookups {#load_lookups tag="classmethod"} @@ -174,12 +172,12 @@ Load and validate lookups tables. If the provided lookups is `None`, load the default lookups tables according to the language and mode settings. Confirm that all required tables for the language and mode are present. -| Name | Type | Description | -| ----------- | ------------------------- | ---------------------------------------------------------------------------- | -| `lang` | str | The language. | -| `mode` | str | The lemmatizer mode. | -| `lookups` | [`Lookups`](/api/lookups) | The provided lookups, may be `None` if the default lookups should be loaded. | -| **RETURNS** | [`Lookups`](/api/lookups) | The lookups object. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------- | +| `lang` | The language. ~~str~~ | +| `mode` | The lemmatizer mode. ~~str~~ | +| `lookups` | The provided lookups, may be `None` if the default lookups should be loaded. ~~Optional[Lookups]~~ | +| **RETURNS** | The lookups. ~~Lookups~~ | ## Lemmatizer.to_disk {#to_disk tag="method"} @@ -192,11 +190,11 @@ Serialize the pipe to disk. > lemmatizer.to_disk("/path/to/lemmatizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Lemmatizer.from_disk {#from_disk tag="method"} @@ -209,12 +207,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > lemmatizer.from_disk("/path/to/lemmatizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Lemmatizer` | The modified `Lemmatizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Lemmatizer` object. ~~Lemmatizer~~ | ## Lemmatizer.to_bytes {#to_bytes tag="method"} @@ -227,11 +225,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Lemmatizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Lemmatizer` object. ~~bytes~~ | ## Lemmatizer.from_bytes {#from_bytes tag="method"} @@ -245,27 +243,20 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > lemmatizer.from_bytes(lemmatizer_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Lemmatizer` | The `Lemmatizer` object. | - -## Lemmatizer.mode {#mode tag="property"} - -The lemmatizer mode. - -| Name | Type | Description | -| ----------- | ----- | -------------------- | -| **RETURNS** | `str` | The lemmatizer mode. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Lemmatizer` object. ~~Lemmatizer~~ | ## Attributes {#attributes} -| Name | Type | Description | -| --------- | --------------------------------- | ------------------- | -| `vocab` | The shared [`Vocab`](/api/vocab). | -| `lookups` | [`Lookups`](/api/lookups) | The lookups object. | +| Name | Description | +| --------- | ------------------------------------------- | +| `vocab` | The shared [`Vocab`](/api/vocab). ~~Vocab~~ | +| `lookups` | The lookups object. ~~Lookups~~ | +| `mode` | The lemmatizer mode. ~~str~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/lexeme.md b/website/docs/api/lexeme.md index 625a26412..a7e1d1ca0 100644 --- a/website/docs/api/lexeme.md +++ b/website/docs/api/lexeme.md @@ -13,10 +13,10 @@ lemmatization depends on the part-of-speech tag). Create a `Lexeme` object. -| Name | Type | Description | -| ------- | ------- | -------------------------- | -| `vocab` | `Vocab` | The parent vocabulary. | -| `orth` | int | The orth id of the lexeme. | +| Name | Description | +| ------- | ---------------------------------- | +| `vocab` | The parent vocabulary. ~~Vocab~~ | +| `orth` | The orth id of the lexeme. ~~int~~ | ## Lexeme.set_flag {#set_flag tag="method"} @@ -29,10 +29,10 @@ Change the value of a boolean flag. > nlp.vocab["spaCy"].set_flag(COOL_FLAG, True) > ``` -| Name | Type | Description | -| --------- | ---- | ------------------------------------ | -| `flag_id` | int | The attribute ID of the flag to set. | -| `value` | bool | The new value of the flag. | +| Name | Description | +| --------- | -------------------------------------------- | +| `flag_id` | The attribute ID of the flag to set. ~~int~~ | +| `value` | The new value of the flag. ~~bool~~ | ## Lexeme.check_flag {#check_flag tag="method"} @@ -46,10 +46,10 @@ Check the value of a boolean flag. > assert nlp.vocab["spaCy"].check_flag(MY_LIBRARY) == True > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------- | -| `flag_id` | int | The attribute ID of the flag to query. | -| **RETURNS** | bool | The value of the flag. | +| Name | Description | +| ----------- | ---------------------------------------------- | +| `flag_id` | The attribute ID of the flag to query. ~~int~~ | +| **RETURNS** | The value of the flag. ~~bool~~ | ## Lexeme.similarity {#similarity tag="method" model="vectors"} @@ -65,10 +65,10 @@ Compute a semantic similarity estimate. Defaults to cosine over vectors. > assert apple_orange == orange_apple > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------------------------------------- | -| other | - | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. | -| **RETURNS** | float | A scalar similarity score. Higher is more similar. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------- | +| other | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. ~~Union[Doc, Span, Token, Lexeme]~~ | +| **RETURNS** | A scalar similarity score. Higher is more similar. ~~float~~ | ## Lexeme.has_vector {#has_vector tag="property" model="vectors"} @@ -81,9 +81,9 @@ A boolean value indicating whether a word vector is associated with the lexeme. > assert apple.has_vector > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------------------------------------- | -| **RETURNS** | bool | Whether the lexeme has a vector data attached. | +| Name | Description | +| ----------- | ------------------------------------------------------- | +| **RETURNS** | Whether the lexeme has a vector data attached. ~~bool~~ | ## Lexeme.vector {#vector tag="property" model="vectors"} @@ -97,9 +97,9 @@ A real-valued meaning representation. > assert apple.vector.shape == (300,) > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------- | ----------------------------------------------------- | -| **RETURNS** | `numpy.ndarray[ndim=1, dtype='float32']` | A 1D numpy array representing the lexeme's semantics. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------ | +| **RETURNS** | A 1-dimensional array representing the lexeme's vector. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Lexeme.vector_norm {#vector_norm tag="property" model="vectors"} @@ -115,50 +115,50 @@ The L2 norm of the lexeme's vector representation. > assert apple.vector_norm != pasta.vector_norm > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------------------------- | -| **RETURNS** | float | The L2 norm of the vector representation. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| **RETURNS** | The L2 norm of the vector representation. ~~float~~ | ## Attributes {#attributes} -| Name | Type | Description | -| -------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `vocab` | `Vocab` | The lexeme's vocabulary. | -| `text` | str | Verbatim text content. | -| `orth` | int | ID of the verbatim text content. | -| `orth_` | str | Verbatim text content (identical to `Lexeme.text`). Exists mostly for consistency with the other attributes. | -| `rank` | int | Sequential ID of the lexemes's lexical type, used to index into tables, e.g. for word vectors. | -| `flags` | int | Container of the lexeme's binary flags. | -| `norm` | int | The lexemes's norm, i.e. a normalized form of the lexeme text. | -| `norm_` | str | The lexemes's norm, i.e. a normalized form of the lexeme text. | -| `lower` | int | Lowercase form of the word. | -| `lower_` | str | Lowercase form of the word. | -| `shape` | int | Transform of the words's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. | -| `shape_` | str | Transform of the word's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. | -| `prefix` | int | Length-N substring from the start of the word. Defaults to `N=1`. | -| `prefix_` | str | Length-N substring from the start of the word. Defaults to `N=1`. | -| `suffix` | int | Length-N substring from the end of the word. Defaults to `N=3`. | -| `suffix_` | str | Length-N substring from the start of the word. Defaults to `N=3`. | -| `is_alpha` | bool | Does the lexeme consist of alphabetic characters? Equivalent to `lexeme.text.isalpha()`. | -| `is_ascii` | bool | Does the lexeme consist of ASCII characters? Equivalent to `[any(ord(c) >= 128 for c in lexeme.text)]`. | -| `is_digit` | bool | Does the lexeme consist of digits? Equivalent to `lexeme.text.isdigit()`. | -| `is_lower` | bool | Is the lexeme in lowercase? Equivalent to `lexeme.text.islower()`. | -| `is_upper` | bool | Is the lexeme in uppercase? Equivalent to `lexeme.text.isupper()`. | -| `is_title` | bool | Is the lexeme in titlecase? Equivalent to `lexeme.text.istitle()`. | -| `is_punct` | bool | Is the lexeme punctuation? | -| `is_left_punct` | bool | Is the lexeme a left punctuation mark, e.g. `(`? | -| `is_right_punct` | bool | Is the lexeme a right punctuation mark, e.g. `)`? | -| `is_space` | bool | Does the lexeme consist of whitespace characters? Equivalent to `lexeme.text.isspace()`. | -| `is_bracket` | bool | Is the lexeme a bracket? | -| `is_quote` | bool | Is the lexeme a quotation mark? | -| `is_currency` 2.0.8 | bool | Is the lexeme a currency symbol? | -| `like_url` | bool | Does the lexeme resemble a URL? | -| `like_num` | bool | Does the lexeme represent a number? e.g. "10.9", "10", "ten", etc. | -| `like_email` | bool | Does the lexeme resemble an email address? | -| `is_oov` | bool | Does the lexeme have a word vector? | -| `is_stop` | bool | Is the lexeme part of a "stop list"? | -| `lang` | int | Language of the parent vocabulary. | -| `lang_` | str | Language of the parent vocabulary. | -| `prob` | float | Smoothed log probability estimate of the lexeme's word type (context-independent entry in the vocabulary). | -| `cluster` | int | Brown cluster ID. | -| `sentiment` | float | A scalar value indicating the positivity or negativity of the lexeme. | +| Name | Description | +| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The lexeme's vocabulary. ~~Vocab~~ | +| `text` | Verbatim text content. ~~str~~ | +| `orth` | ID of the verbatim text content. ~~int~~ | +| `orth_` | Verbatim text content (identical to `Lexeme.text`). Exists mostly for consistency with the other attributes. ~~str~~ | +| `rank` | Sequential ID of the lexemes's lexical type, used to index into tables, e.g. for word vectors. ~~int~~ | +| `flags` | Container of the lexeme's binary flags. ~~int~~ | +| `norm` | The lexemes's norm, i.e. a normalized form of the lexeme text. ~~int~~ | +| `norm_` | The lexemes's norm, i.e. a normalized form of the lexeme text. ~~str~~ | +| `lower` | Lowercase form of the word. ~~int~~ | +| `lower_` | Lowercase form of the word. ~~str~~ | +| `shape` | Transform of the words's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. ~~int~~ | +| `shape_` | Transform of the word's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. ~~str~~ | +| `prefix` | Length-N substring from the start of the word. Defaults to `N=1`. ~~int~~ | +| `prefix_` | Length-N substring from the start of the word. Defaults to `N=1`. ~~str~~ | +| `suffix` | Length-N substring from the end of the word. Defaults to `N=3`. ~~int~~ | +| `suffix_` | Length-N substring from the start of the word. Defaults to `N=3`. ~~str~~ | +| `is_alpha` | Does the lexeme consist of alphabetic characters? Equivalent to `lexeme.text.isalpha()`. ~~bool~~ | +| `is_ascii` | Does the lexeme consist of ASCII characters? Equivalent to `[any(ord(c) >= 128 for c in lexeme.text)]`. ~~bool~~ | +| `is_digit` | Does the lexeme consist of digits? Equivalent to `lexeme.text.isdigit()`. ~~bool~~ | +| `is_lower` | Is the lexeme in lowercase? Equivalent to `lexeme.text.islower()`. ~~bool~~ | +| `is_upper` | Is the lexeme in uppercase? Equivalent to `lexeme.text.isupper()`. ~~bool~~ | +| `is_title` | Is the lexeme in titlecase? Equivalent to `lexeme.text.istitle()`. ~~bool~~ | +| `is_punct` | Is the lexeme punctuation? ~~bool~~ | +| `is_left_punct` | Is the lexeme a left punctuation mark, e.g. `(`? ~~bool~~ | +| `is_right_punct` | Is the lexeme a right punctuation mark, e.g. `)`? ~~bool~~ | +| `is_space` | Does the lexeme consist of whitespace characters? Equivalent to `lexeme.text.isspace()`. ~~bool~~ | +| `is_bracket` | Is the lexeme a bracket? ~~bool~~ | +| `is_quote` | Is the lexeme a quotation mark? ~~bool~~ | +| `is_currency` 2.0.8 | Is the lexeme a currency symbol? ~~bool~~ | +| `like_url` | Does the lexeme resemble a URL? ~~bool~~ | +| `like_num` | Does the lexeme represent a number? e.g. "10.9", "10", "ten", etc. ~~bool~~ | +| `like_email` | Does the lexeme resemble an email address? ~~bool~~ | +| `is_oov` | Does the lexeme have a word vector? ~~bool~~ | +| `is_stop` | Is the lexeme part of a "stop list"? ~~bool~~ | +| `lang` | Language of the parent vocabulary. ~~int~~ | +| `lang_` | Language of the parent vocabulary. ~~str~~ | +| `prob` | Smoothed log probability estimate of the lexeme's word type (context-independent entry in the vocabulary). ~~float~~ | +| `cluster` | Brown cluster ID. ~~int~~ | +| `sentiment` | A scalar value indicating the positivity or negativity of the lexeme. ~~float~~ | diff --git a/website/docs/api/lookups.md b/website/docs/api/lookups.md index 099b5306e..9565e478f 100644 --- a/website/docs/api/lookups.md +++ b/website/docs/api/lookups.md @@ -24,10 +24,6 @@ Create a `Lookups` object. > lookups = Lookups() > ``` -| Name | Type | Description | -| ----------- | --------- | ----------------------------- | -| **RETURNS** | `Lookups` | The newly constructed object. | - ## Lookups.\_\_len\_\_ {#len tag="method"} Get the current number of tables in the lookups. @@ -39,9 +35,9 @@ Get the current number of tables in the lookups. > assert len(lookups) == 0 > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------ | -| **RETURNS** | int | The number of tables in the lookups. | +| Name | Description | +| ----------- | -------------------------------------------- | +| **RETURNS** | The number of tables in the lookups. ~~int~~ | ## Lookups.\_\contains\_\_ {#contains tag="method"} @@ -56,10 +52,10 @@ Check if the lookups contain a table of a given name. Delegates to > assert "some_table" in lookups > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------------------- | -| `name` | str | Name of the table. | -| **RETURNS** | bool | Whether a table of that name is in the lookups. | +| Name | Description | +| ----------- | -------------------------------------------------------- | +| `name` | Name of the table. ~~str~~ | +| **RETURNS** | Whether a table of that name is in the lookups. ~~bool~~ | ## Lookups.tables {#tables tag="property"} @@ -73,9 +69,9 @@ Get the names of all tables in the lookups. > assert lookups.tables == ["some_table"] > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------- | -| **RETURNS** | list | Names of the tables in the lookups. | +| Name | Description | +| ----------- | ------------------------------------------------- | +| **RETURNS** | Names of the tables in the lookups. ~~List[str]~~ | ## Lookups.add_table {#add_table tag="method"} @@ -89,11 +85,11 @@ exists. > lookups.add_table("some_table", {"foo": "bar"}) > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | ---------------------------------- | -| `name` | str | Unique name of the table. | -| `data` | dict | Optional data to add to the table. | -| **RETURNS** | [`Table`](/api/lookups#table) | The newly added table. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `name` | Unique name of the table. ~~str~~ | +| `data` | Optional data to add to the table. ~~dict~~ | +| **RETURNS** | The newly added table. ~~Table~~ | ## Lookups.get_table {#get_table tag="method"} @@ -108,10 +104,10 @@ Get a table from the lookups. Raises an error if the table doesn't exist. > assert table["foo"] == "bar" > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | ------------------ | -| `name` | str | Name of the table. | -| **RETURNS** | [`Table`](/api/lookups#table) | The table. | +| Name | Description | +| ----------- | -------------------------- | +| `name` | Name of the table. ~~str~~ | +| **RETURNS** | The table. ~~Table~~ | ## Lookups.remove_table {#remove_table tag="method"} @@ -126,10 +122,10 @@ Remove a table from the lookups. Raises an error if the table doesn't exist. > assert "some_table" not in lookups > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | ---------------------------- | -| `name` | str | Name of the table to remove. | -| **RETURNS** | [`Table`](/api/lookups#table) | The removed table. | +| Name | Description | +| ----------- | ------------------------------------ | +| `name` | Name of the table to remove. ~~str~~ | +| **RETURNS** | The removed table. ~~Table~~ | ## Lookups.has_table {#has_table tag="method"} @@ -144,10 +140,10 @@ Check if the lookups contain a table of a given name. Equivalent to > assert lookups.has_table("some_table") > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------------------- | -| `name` | str | Name of the table. | -| **RETURNS** | bool | Whether a table of that name is in the lookups. | +| Name | Description | +| ----------- | -------------------------------------------------------- | +| `name` | Name of the table. ~~str~~ | +| **RETURNS** | Whether a table of that name is in the lookups. ~~bool~~ | ## Lookups.to_bytes {#to_bytes tag="method"} @@ -159,9 +155,9 @@ Serialize the lookups to a bytestring. > lookup_bytes = lookups.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------- | -| **RETURNS** | bytes | The serialized lookups. | +| Name | Description | +| ----------- | --------------------------------- | +| **RETURNS** | The serialized lookups. ~~bytes~~ | ## Lookups.from_bytes {#from_bytes tag="method"} @@ -175,10 +171,10 @@ Load the lookups from a bytestring. > lookups.from_bytes(lookup_bytes) > ``` -| Name | Type | Description | -| ------------ | --------- | ---------------------- | -| `bytes_data` | bytes | The data to load from. | -| **RETURNS** | `Lookups` | The loaded lookups. | +| Name | Description | +| ------------ | -------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| **RETURNS** | The loaded lookups. ~~Lookups~~ | ## Lookups.to_disk {#to_disk tag="method"} @@ -191,9 +187,9 @@ which will be created if it doesn't exist. > lookups.to_disk("/path/to/lookups") > ``` -| Name | Type | Description | -| ------ | ------------ | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## Lookups.from_disk {#from_disk tag="method"} @@ -208,10 +204,10 @@ the file doesn't exist. > lookups.from_disk("/path/to/lookups") > ``` -| Name | Type | Description | -| ----------- | ------------ | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `Lookups` | The loaded lookups. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The loaded lookups. ~~Lookups~~ | ## Table {#table tag="class, ordererddict"} @@ -236,9 +232,9 @@ Initialize a new table. > assert table["foo"] == "bar" > ``` -| Name | Type | Description | -| ------ | ---- | ---------------------------------- | -| `name` | str | Optional table name for reference. | +| Name | Description | +| ------ | ------------------------------------------ | +| `name` | Optional table name for reference. ~~str~~ | ### Table.from_dict {#table.from_dict tag="classmethod"} @@ -252,11 +248,11 @@ Initialize a new table from a dict. > table = Table.from_dict(data, name="some_table") > ``` -| Name | Type | Description | -| ----------- | ------- | ---------------------------------- | -| `data` | dict | The dictionary. | -| `name` | str | Optional table name for reference. | -| **RETURNS** | `Table` | The newly constructed object. | +| Name | Description | +| ----------- | ------------------------------------------ | +| `data` | The dictionary. ~~dict~~ | +| `name` | Optional table name for reference. ~~str~~ | +| **RETURNS** | The newly constructed object. ~~Table~~ | ### Table.set {#table.set tag="method"} @@ -272,10 +268,10 @@ Set a new key / value pair. String keys will be hashed. Same as > assert table["foo"] == "bar" > ``` -| Name | Type | Description | -| ------- | --------- | ----------- | -| `key` | str / int | The key. | -| `value` | - | The value. | +| Name | Description | +| ------- | ---------------------------- | +| `key` | The key. ~~Union[str, int]~~ | +| `value` | The value. | ### Table.to_bytes {#table.to_bytes tag="method"} @@ -287,9 +283,9 @@ Serialize the table to a bytestring. > table_bytes = table.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------- | -| **RETURNS** | bytes | The serialized table. | +| Name | Description | +| ----------- | ------------------------------- | +| **RETURNS** | The serialized table. ~~bytes~~ | ### Table.from_bytes {#table.from_bytes tag="method"} @@ -303,15 +299,15 @@ Load a table from a bytestring. > table.from_bytes(table_bytes) > ``` -| Name | Type | Description | -| ------------ | ------- | ----------------- | -| `bytes_data` | bytes | The data to load. | -| **RETURNS** | `Table` | The loaded table. | +| Name | Description | +| ------------ | --------------------------- | +| `bytes_data` | The data to load. ~~bytes~~ | +| **RETURNS** | The loaded table. ~~Table~~ | ### Attributes {#table-attributes} -| Name | Type | Description | -| -------------- | --------------------------- | ----------------------------------------------------- | -| `name` | str | Table name. | -| `default_size` | int | Default size of bloom filters if no data is provided. | -| `bloom` | `preshed.bloom.BloomFilter` | The bloom filters. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `name` | Table name. ~~str~~ | +| `default_size` | Default size of bloom filters if no data is provided. ~~int~~ | +| `bloom` | The bloom filters. ~~preshed.BloomFilter~~ | diff --git a/website/docs/api/matcher.md b/website/docs/api/matcher.md index b481f1972..f259174e2 100644 --- a/website/docs/api/matcher.md +++ b/website/docs/api/matcher.md @@ -30,20 +30,20 @@ pattern keys correspond to a number of [`Token` attributes](/api/token#attributes). The supported attributes for rule-based matching are: -| Attribute | Type |  Description | -| -------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------ | -| `ORTH` | str | The exact verbatim text of a token. | -| `TEXT` 2.1 | str | The exact verbatim text of a token. | -| `LOWER` | str | The lowercase form of the token text. | -|  `LENGTH` | int | The length of the token text. | -|  `IS_ALPHA`, `IS_ASCII`, `IS_DIGIT` | bool | Token text consists of alphabetic characters, ASCII characters, digits. | -|  `IS_LOWER`, `IS_UPPER`, `IS_TITLE` | bool | Token text is in lowercase, uppercase, titlecase. | -|  `IS_PUNCT`, `IS_SPACE`, `IS_STOP` | bool | Token is punctuation, whitespace, stop word. | -|  `LIKE_NUM`, `LIKE_URL`, `LIKE_EMAIL` | bool | Token text resembles a number, URL, email. | -|  `POS`, `TAG`, `DEP`, `LEMMA`, `SHAPE` | str | The token's simple and extended part-of-speech tag, dependency label, lemma, shape. | -| `ENT_TYPE` | str | The token's entity label. | -| `_` 2.1 | dict | Properties in [custom extension attributes](/usage/processing-pipelines#custom-components-attributes). | -| `OP` | str | Operator or quantifier to determine how often to match a token pattern. | +| Attribute |  Description | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `ORTH` | The exact verbatim text of a token. ~~str~~ | +| `TEXT` 2.1 | The exact verbatim text of a token. ~~str~~ | +| `LOWER` | The lowercase form of the token text. ~~str~~ | +|  `LENGTH` | The length of the token text. ~~int~~ | +|  `IS_ALPHA`, `IS_ASCII`, `IS_DIGIT` | Token text consists of alphabetic characters, ASCII characters, digits. ~~bool~~ | +|  `IS_LOWER`, `IS_UPPER`, `IS_TITLE` | Token text is in lowercase, uppercase, titlecase. ~~bool~~ | +|  `IS_PUNCT`, `IS_SPACE`, `IS_STOP` | Token is punctuation, whitespace, stop word. ~~bool~~ | +|  `LIKE_NUM`, `LIKE_URL`, `LIKE_EMAIL` | Token text resembles a number, URL, email. ~~bool~~ | +|  `POS`, `TAG`, `DEP`, `LEMMA`, `SHAPE` | The token's simple and extended part-of-speech tag, dependency label, lemma, shape. ~~str~~ | +| `ENT_TYPE` | The token's entity label. ~~str~~ | +| `_` 2.1 | Properties in [custom extension attributes](/usage/processing-pipelines#custom-components-attributes). ~~Dict[str, Any]~~ | +| `OP` | Operator or quantifier to determine how often to match a token pattern. ~~str~~ | Operators and quantifiers define **how often** a token pattern should be matched: @@ -75,11 +75,11 @@ it compares to another value. > ] > ``` -| Attribute | Type | Description | -| -------------------------- | ---------- | --------------------------------------------------------------------------------- | -| `IN` | any | Attribute value is member of a list. | -| `NOT_IN` | any | Attribute value is _not_ member of a list. | -| `==`, `>=`, `<=`, `>`, `<` | int, float | Attribute value is equal, greater or equal, smaller or equal, greater or smaller. | +| Attribute | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------- | +| `IN` | Attribute value is member of a list. ~~Any~~ | +| `NOT_IN` | Attribute value is _not_ member of a list. ~~Any~~ | +| `==`, `>=`, `<=`, `>`, `<` | Attribute value is equal, greater or equal, smaller or equal, greater or smaller. ~~Union[int, float]~~ | ## Matcher.\_\_init\_\_ {#init tag="method"} @@ -95,10 +95,10 @@ string where an integer is expected) or unexpected property names. > matcher = Matcher(nlp.vocab) > ``` -| Name | Type | Description | -| --------------------------------------- | ------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. | -| `validate` 2.1 | bool | Validate all patterns added to this matcher. | +| Name | Description | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| `vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. ~~Vocab~~ | +| `validate` 2.1 | Validate all patterns added to this matcher. ~~bool~~ | ## Matcher.\_\_call\_\_ {#call tag="method"} @@ -116,10 +116,10 @@ Find all token sequences matching the supplied patterns on the `Doc` or `Span`. > matches = matcher(doc) > ``` -| Name | Type | Description | -| ----------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `doclike` | `Doc`/`Span` | The `Doc` or `Span` to match over. | -| **RETURNS** | list | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. The `match_id` is the ID of the added match pattern. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `doclike` | The `Doc` or `Span` to match over. ~~Union[Doc, Span]~~ | +| **RETURNS** | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. The `match_id` is the ID of the added match pattern. ~~List[Tuple[int, int, int]]~~ | ## Matcher.pipe {#pipe tag="method"} @@ -134,13 +134,13 @@ Match a stream of documents, yielding them in turn. > pass > ``` -| Name | Type | Description | -| --------------------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `docs` | iterable | A stream of documents or spans. | -| `batch_size` | int | The number of documents to accumulate into a working set. | -| `return_matches` 2.1 | bool | Yield the match lists along with the docs, making results `(doc, matches)` tuples. | -| `as_tuples` | bool | Interpret the input stream as `(doc, context)` tuples, and yield `(result, context)` tuples out. If both `return_matches` and `as_tuples` are `True`, the output will be a sequence of `((doc, matches), context)` tuples. | -| **YIELDS** | `Doc` | Documents, in order. | +| Name | Description | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `docs` | A stream of documents or spans. ~~Iterable[Union[Doc, Span]]~~ | +| `batch_size` | The number of documents to accumulate into a working set. ~~int~~ | +| `return_matches` 2.1 | Yield the match lists along with the docs, making results `(doc, matches)` tuples. ~~bool~~ | +| `as_tuples` | Interpret the input stream as `(doc, context)` tuples, and yield `(result, context)` tuples out. If both `return_matches` and `as_tuples` are `True`, the output will be a sequence of `((doc, matches), context)` tuples. ~~bool~~ | +| **YIELDS** | Documents, in order. ~~Union[Doc, Tuple[Doc, Any], Tuple[Tuple[Doc, Any], Any]]~~ | ## Matcher.\_\_len\_\_ {#len tag="method" new="2"} @@ -157,9 +157,9 @@ patterns. > assert len(matcher) == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------- | -| **RETURNS** | int | The number of rules. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The number of rules. ~~int~~ | ## Matcher.\_\_contains\_\_ {#contains tag="method" new="2"} @@ -174,10 +174,10 @@ Check whether the matcher contains rules for a match ID. > assert "Rule" in matcher > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------------------------- | -| `key` | str | The match ID. | -| **RETURNS** | bool | Whether the matcher contains rules for this match ID. | +| Name | Description | +| ----------- | -------------------------------------------------------------- | +| `key` | The match ID. ~~str~~ | +| **RETURNS** | Whether the matcher contains rules for this match ID. ~~bool~~ | ## Matcher.add {#add tag="method" new="2"} @@ -217,13 +217,13 @@ patterns = [[{"TEXT": "Google"}, {"TEXT": "Now"}], [{"TEXT": "GoogleNow"}]] -| Name | Type | Description | -| ----------------------------------- | ------------------ | --------------------------------------------------------------------------------------------- | -| `match_id` | str | An ID for the thing you're matching. | -| `patterns` | `List[List[dict]]` | Match pattern. A pattern consists of a list of dicts, where each dict describes a token. | -| _keyword-only_ | | | -| `on_match` | callable / `None` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. | -| `greedy` 3 | str | Optional filter for greedy matches. Can either be `"FIRST"` or `"LONGEST"`. | +| Name | Description | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `match_id` | An ID for the thing you're matching. ~~str~~ | +| `patterns` | Match pattern. A pattern consists of a list of dicts, where each dict describes a token. ~~List[List[Dict[str, Any]]]~~ | +| _keyword-only_ | | +| `on_match` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. ~~Optional[Callable[[Matcher, Doc, int, List[tuple], Any]]~~ | +| `greedy` 3 | Optional filter for greedy matches. Can either be `"FIRST"` or `"LONGEST"`. ~~Optional[str]~~ | ## Matcher.remove {#remove tag="method" new="2"} @@ -239,9 +239,9 @@ exist. > assert "Rule" not in matcher > ``` -| Name | Type | Description | -| ----- | ---- | ------------------------- | -| `key` | str | The ID of the match rule. | +| Name | Description | +| ----- | --------------------------------- | +| `key` | The ID of the match rule. ~~str~~ | ## Matcher.get {#get tag="method" new="2"} @@ -255,7 +255,7 @@ Retrieve the pattern stored for a key. Returns the rule as an > on_match, patterns = matcher.get("Rule") > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------------- | -| `key` | str | The ID of the match rule. | -| **RETURNS** | tuple | The rule, as an `(on_match, patterns)` tuple. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------- | +| `key` | The ID of the match rule. ~~str~~ | +| **RETURNS** | The rule, as an `(on_match, patterns)` tuple. ~~Tuple[Optional[Callable], List[List[dict]]]~~ | diff --git a/website/docs/api/morphanalysis.md b/website/docs/api/morphanalysis.md deleted file mode 100644 index 4df9a3f7f..000000000 --- a/website/docs/api/morphanalysis.md +++ /dev/null @@ -1,142 +0,0 @@ ---- -title: MorphAnalysis -tag: class -source: spacy/tokens/morphanalysis.pyx ---- - -Stores a single morphological analysis. - -## MorphAnalysis.\_\_init\_\_ {#init tag="method"} - -Initialize a MorphAnalysis object from a UD FEATS string or a dictionary of -morphological features. - -> #### Example -> -> ```python -> from spacy.tokens import MorphAnalysis -> -> feats = "Feat1=Val1|Feat2=Val2" -> m = MorphAnalysis(nlp.vocab, feats) -> ``` - -| Name | Type | Description | -| ---------- | ------------------ | --------------------------- | -| `vocab` | `Vocab` | The vocab. | -| `features` | `Union[Dict, str]` | The morphological features. | - -## MorphAnalysis.\_\_contains\_\_ {#contains tag="method"} - -Whether a feature/value pair is in the analysis. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val2|Feat2=Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert "Feat1=Val1" in morph -> ``` - -| Name | Type | Description | -| ----------- | ----- | ------------------------------------- | -| **RETURNS** | `str` | A feature/value pair in the analysis. | - -## MorphAnalysis.\_\_iter\_\_ {#iter tag="method"} - -Iterate over the feature/value pairs in the analysis. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val3|Feat2=Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert list(morph) == ["Feat1=Va1", "Feat1=Val3", "Feat2=Val2"] -> ``` - -| Name | Type | Description | -| ---------- | ----- | ------------------------------------- | -| **YIELDS** | `str` | A feature/value pair in the analysis. | - -## MorphAnalysis.\_\_len\_\_ {#len tag="method"} - -Returns the number of features in the analysis. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val2|Feat2=Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert len(morph) == 3 -> ``` - -| Name | Type | Description | -| ----------- | ----- | --------------------------------------- | -| **RETURNS** | `int` | The number of features in the analysis. | - -## MorphAnalysis.\_\_str\_\_ {#str tag="method"} - -Returns the morphological analysis in the UD FEATS string format. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val2|Feat2=Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert str(morph) == feats -> ``` - -| Name | Type | Description | -| ----------- | ----- | -------------------------------- | -| **RETURNS** | `str` | The analysis in UD FEATS format. | - -## MorphAnalysis.get {#get tag="method"} - -Retrieve values for a feature by field. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert morph.get("Feat1") == ["Val1", "Val2"] -> ``` - -| Name | Type | Description | -| ----------- | ------ | ---------------------------------- | -| `field` | `str` | The field to retrieve. | -| **RETURNS** | `list` | A list of the individual features. | - -## MorphAnalysis.to_dict {#to_dict tag="method"} - -Produce a dict representation of the analysis, in the same format as the tag -map. - -> #### Example -> -> ```python -> feats = "Feat1=Val1,Val2|Feat2=Val2" -> morph = MorphAnalysis(nlp.vocab, feats) -> assert morph.to_dict() == {"Feat1": "Val1,Val2", "Feat2": "Val2"} -> ``` - -| Name | Type | Description | -| ----------- | ------ | ---------------------------------------- | -| **RETURNS** | `dict` | The dict representation of the analysis. | - -## MorphAnalysis.from_id {#from_id tag="classmethod"} - -Create a morphological analysis from a given hash ID. - -> #### Example -> -> ```python -> feats = "Feat1=Val1|Feat2=Val2" -> hash = nlp.vocab.strings[feats] -> morph = MorphAnalysis.from_id(nlp.vocab, hash) -> assert str(morph) == feats -> ``` - -| Name | Type | Description | -| ------- | ------- | -------------------------------- | -| `vocab` | `Vocab` | The vocab. | -| `key` | `int` | The hash of the features string. | diff --git a/website/docs/api/morphologizer.md b/website/docs/api/morphologizer.md index 12d3050f6..069856ea3 100644 --- a/website/docs/api/morphologizer.md +++ b/website/docs/api/morphologizer.md @@ -32,9 +32,9 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("morphologizer", config=config) > ``` -| Setting | Type | Description | Default | -| ------- | ------------------------------------------ | ----------------- | ----------------------------------- | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model to use. | [Tagger](/api/architectures#Tagger) | +| Setting | Description | +| ------- | ------------------------------------------------------------------------------------------------------- | +| `model` | The model to use. Defaults to [Tagger](/api/architectures#Tagger). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/morphologizer.pyx @@ -42,7 +42,9 @@ https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/morphologizer.pyx ## Morphologizer.\_\_init\_\_ {#init tag="method"} -Initialize the morphologizer. +Create a new pipeline instance. In your application, you would normally use a +shortcut for this and instantiate the component using its string name and +[`nlp.add_pipe`](/api/language#add_pipe). > #### Example > @@ -59,18 +61,14 @@ Initialize the morphologizer. > morphologizer = Morphologizer(nlp.vocab, model) > ``` -Create a new pipeline instance. In your application, you would normally use a -shortcut for this and instantiate the component using its string name and -[`nlp.add_pipe`](/api/language#add_pipe). - -| Name | Type | Description | -| -------------- | ------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | `Model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| _keyword-only_ | | | -| `labels_morph` | dict | Mapping of morph + POS tags to morph labels. | -| `labels_pos` | dict | Mapping of morph + POS tags to POS tags. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | +| `labels_morph` | Mapping of morph + POS tags to morph labels. ~~Dict[str, str]~~ | +| `labels_pos` | Mapping of morph + POS tags to POS tags. ~~Dict[str, str]~~ | ## Morphologizer.\_\_call\_\_ {#call tag="method"} @@ -90,10 +88,10 @@ delegate to the [`predict`](/api/morphologizer#predict) and > processed = morphologizer(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Morphologizer.pipe {#pipe tag="method"} @@ -112,12 +110,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/morphologizer#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Morphologizer.begin_training {#begin_training tag="method"} @@ -138,13 +136,13 @@ setting up the label scheme based on the data. > optimizer = morphologizer.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/sentencerecognizer#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Morphologizer.predict {#predict tag="method"} @@ -158,10 +156,10 @@ modifying them. > scores = morphologizer.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## Morphologizer.set_annotations {#set_annotations tag="method"} @@ -175,10 +173,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > morphologizer.set_annotations([doc1, doc2], scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `Morphologizer.predict`. | +| Name | Description | +| -------- | ------------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `Morphologizer.predict`. | ## Morphologizer.update {#update tag="method"} @@ -195,15 +193,15 @@ Delegates to [`predict`](/api/morphologizer#predict) and > losses = morphologizer.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/sentencerecognizer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. The value keyed by the model's name is updated. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Morphologizer.get_loss {#get_loss tag="method"} @@ -218,11 +216,11 @@ predicted scores. > loss, d_loss = morphologizer.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | - | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## Morphologizer.create_optimizer {#create_optimizer tag="method"} @@ -235,9 +233,9 @@ Create an optimizer for the pipeline component. > optimizer = morphologizer.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Morphologizer.use_params {#use_params tag="method, contextmanager"} @@ -252,9 +250,9 @@ context, the original parameters are restored. > morphologizer.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## Morphologizer.add_label {#add_label tag="method"} @@ -268,10 +266,10 @@ both `pos` and `morph`, the label should include the UPOS as the feature `POS`. > morphologizer.add_label("Mood=Ind|POS=VERB|Tense=Past|VerbForm=Fin") > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## Morphologizer.to_disk {#to_disk tag="method"} @@ -284,11 +282,11 @@ Serialize the pipe to disk. > morphologizer.to_disk("/path/to/morphologizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Morphologizer.from_disk {#from_disk tag="method"} @@ -301,12 +299,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > morphologizer.from_disk("/path/to/morphologizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Morphologizer` | The modified `Morphologizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Morphologizer` object. ~~Morphologizer~~ | ## Morphologizer.to_bytes {#to_bytes tag="method"} @@ -319,11 +317,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Morphologizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Morphologizer` object. ~~bytes~~ | ## Morphologizer.from_bytes {#from_bytes tag="method"} @@ -337,19 +335,19 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > morphologizer.from_bytes(morphologizer_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Morphologizer` | The `Morphologizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Morphologizer` object. ~~Morphologizer~~ | ## Morphologizer.labels {#labels tag="property"} -The labels currently added to the component in Universal Dependencies -[FEATS format](https://universaldependencies.org/format.html#morphological-annotation). -Note that even for a blank component, this will always include the internal -empty label `_`. If POS features are used, the labels will include the +The labels currently added to the component in the Universal Dependencies +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +format. Note that even for a blank component, this will always include the +internal empty label `_`. If POS features are used, the labels will include the coarse-grained POS as the feature `POS`. > #### Example @@ -359,9 +357,9 @@ coarse-grained POS as the feature `POS`. > assert "Mood=Ind|POS=VERB|Tense=Past|VerbForm=Fin" in morphologizer.labels > ``` -| Name | Type | Description | -| ----------- | ----- | ---------------------------------- | -| **RETURNS** | tuple | The labels added to the component. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The labels added to the component. ~~Tuple[str, ...]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/morphology.md b/website/docs/api/morphology.md index 3c5bf6fe4..1b2e159d0 100644 --- a/website/docs/api/morphology.md +++ b/website/docs/api/morphology.md @@ -7,7 +7,8 @@ source: spacy/morphology.pyx Store the possible morphological analyses for a language, and index them by hash. To save space on each token, tokens only know the hash of their morphological analysis, so queries of morphological attributes are delegated to -this class. +this class. See [`MorphAnalysis`](/api/morphology#morphansalysis) for the +container storing a single morphological analysis. ## Morphology.\_\_init\_\_ {#init tag="method"} @@ -21,15 +22,17 @@ Create a Morphology object. > morphology = Morphology(strings) > ``` -| Name | Type | Description | -| --------- | ------------- | ----------------- | -| `strings` | `StringStore` | The string store. | +| Name | Description | +| --------- | --------------------------------- | +| `strings` | The string store. ~~StringStore~~ | ## Morphology.add {#add tag="method"} Insert a morphological analysis in the morphology table, if not already present. -The morphological analysis may be provided in the UD FEATS format as a string or -in the tag map dictionary format. Returns the hash of the new analysis. +The morphological analysis may be provided in the Universal Dependencies +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +format as a string or in the tag map dictionary format. Returns the hash of the +new analysis. > #### Example > @@ -39,9 +42,9 @@ in the tag map dictionary format. Returns the hash of the new analysis. > assert hash == nlp.vocab.strings[feats] > ``` -| Name | Type | Description | -| ---------- | ------------------ | --------------------------- | -| `features` | `Union[Dict, str]` | The morphological features. | +| Name | Description | +| ---------- | ------------------------------------------------ | +| `features` | The morphological features. ~~Union[Dict, str]~~ | ## Morphology.get {#get tag="method"} @@ -53,16 +56,20 @@ in the tag map dictionary format. Returns the hash of the new analysis. > assert nlp.vocab.morphology.get(hash) == feats > ``` -Get the FEATS string for the hash of the morphological analysis. +Get the +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +string for the hash of the morphological analysis. -| Name | Type | Description | -| ------- | ---- | --------------------------------------- | -| `morph` | int | The hash of the morphological analysis. | +| Name | Description | +| ------- | ----------------------------------------------- | +| `morph` | The hash of the morphological analysis. ~~int~~ | ## Morphology.feats_to_dict {#feats_to_dict tag="staticmethod"} -Convert a string FEATS representation to a dictionary of features and values in -the same format as the tag map. +Convert a string +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +representation to a dictionary of features and values in the same format as the +tag map. > #### Example > @@ -72,14 +79,16 @@ the same format as the tag map. > assert d == {"Feat1": "Val1", "Feat2": "Val2"} > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------------------------------ | -| `feats` | str | The morphological features in Universal Dependencies FEATS format. | -| **RETURNS** | dict | The morphological features as a dictionary. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | +| `feats` | The morphological features in Universal Dependencies [FEATS](https://universaldependencies.org/format.html#morphological-annotation) format. ~~str~~ | +| **RETURNS** | The morphological features as a dictionary. ~~Dict[str, str]~~ | ## Morphology.dict_to_feats {#dict_to_feats tag="staticmethod"} -Convert a dictionary of features and values to a string FEATS representation. +Convert a dictionary of features and values to a string +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +representation. > #### Example > @@ -89,15 +98,157 @@ Convert a dictionary of features and values to a string FEATS representation. > assert f == "Feat1=Val1|Feat2=Val2" > ``` -| Name | Type | Description | -| ------------ | ----------------- | --------------------------------------------------------------------- | -| `feats_dict` | `Dict[str, Dict]` | The morphological features as a dictionary. | -| **RETURNS** | str | The morphological features as in Universal Dependencies FEATS format. | +| Name | Description | +| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `feats_dict` | The morphological features as a dictionary. ~~Dict[str, str]~~ | +| **RETURNS** | The morphological features as in Universal Dependencies [FEATS](https://universaldependencies.org/format.html#morphological-annotation) format. ~~str~~ | ## Attributes {#attributes} -| Name | Type | Description | -| ------------- | ----- | -------------------------------------------- | -| `FEATURE_SEP` | `str` | The FEATS feature separator. Default is `|`. | -| `FIELD_SEP` | `str` | The FEATS field separator. Default is `=`. | -| `VALUE_SEP` | `str` | The FEATS value separator. Default is `,`. | +| Name | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------ | +| `FEATURE_SEP` | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) feature separator. Default is `|`. ~~str~~ | +| `FIELD_SEP` | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) field separator. Default is `=`. ~~str~~ | +| `VALUE_SEP` | The [FEATS](https://universaldependencies.org/format.html#morphological-annotation) value separator. Default is `,`. ~~str~~ | + +## MorphAnalysis {#morphanalysis tag="class" source="spacy/tokens/morphanalysis.pyx"} + +Stores a single morphological analysis. + +### MorphAnalysis.\_\_init\_\_ {#morphanalysis-init tag="method"} + +Initialize a MorphAnalysis object from a Universal Dependencies +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +string or a dictionary of morphological features. + +> #### Example +> +> ```python +> from spacy.tokens import MorphAnalysis +> +> feats = "Feat1=Val1|Feat2=Val2" +> m = MorphAnalysis(nlp.vocab, feats) +> ``` + +| Name | Description | +| ---------- | ---------------------------------------------------------- | +| `vocab` | The vocab. ~~Vocab~~ | +| `features` | The morphological features. ~~Union[Dict[str, str], str]~~ | + +### MorphAnalysis.\_\_contains\_\_ {#morphanalysis-contains tag="method"} + +Whether a feature/value pair is in the analysis. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val2|Feat2=Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert "Feat1=Val1" in morph +> ``` + +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | A feature/value pair in the analysis. ~~str~~ | + +### MorphAnalysis.\_\_iter\_\_ {#morphanalysis-iter tag="method"} + +Iterate over the feature/value pairs in the analysis. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val3|Feat2=Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert list(morph) == ["Feat1=Va1", "Feat1=Val3", "Feat2=Val2"] +> ``` + +| Name | Description | +| ---------- | --------------------------------------------- | +| **YIELDS** | A feature/value pair in the analysis. ~~str~~ | + +### MorphAnalysis.\_\_len\_\_ {#morphanalysis-len tag="method"} + +Returns the number of features in the analysis. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val2|Feat2=Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert len(morph) == 3 +> ``` + +| Name | Description | +| ----------- | ----------------------------------------------- | +| **RETURNS** | The number of features in the analysis. ~~int~~ | + +### MorphAnalysis.\_\_str\_\_ {#morphanalysis-str tag="method"} + +Returns the morphological analysis in the Universal Dependencies +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) +string format. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val2|Feat2=Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert str(morph) == feats +> ``` + +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| **RETURNS** | The analysis in the Universal Dependencies [FEATS](https://universaldependencies.org/format.html#morphological-annotation) format. ~~str~~ | + +### MorphAnalysis.get {#morphanalysis-get tag="method"} + +Retrieve values for a feature by field. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert morph.get("Feat1") == ["Val1", "Val2"] +> ``` + +| Name | Description | +| ----------- | ------------------------------------------------ | +| `field` | The field to retrieve. ~~str~~ | +| **RETURNS** | A list of the individual features. ~~List[str]~~ | + +### MorphAnalysis.to_dict {#morphanalysis-to_dict tag="method"} + +Produce a dict representation of the analysis, in the same format as the tag +map. + +> #### Example +> +> ```python +> feats = "Feat1=Val1,Val2|Feat2=Val2" +> morph = MorphAnalysis(nlp.vocab, feats) +> assert morph.to_dict() == {"Feat1": "Val1,Val2", "Feat2": "Val2"} +> ``` + +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| **RETURNS** | The dict representation of the analysis. ~~Dict[str, str]~~ | + +### MorphAnalysis.from_id {#morphanalysis-from_id tag="classmethod"} + +Create a morphological analysis from a given hash ID. + +> #### Example +> +> ```python +> feats = "Feat1=Val1|Feat2=Val2" +> hash = nlp.vocab.strings[feats] +> morph = MorphAnalysis.from_id(nlp.vocab, hash) +> assert str(morph) == feats +> ``` + +| Name | Description | +| ------- | ---------------------------------------- | +| `vocab` | The vocab. ~~Vocab~~ | +| `key` | The hash of the features string. ~~int~~ | diff --git a/website/docs/api/phrasematcher.md b/website/docs/api/phrasematcher.md index 71c7a463b..143eb9edf 100644 --- a/website/docs/api/phrasematcher.md +++ b/website/docs/api/phrasematcher.md @@ -36,11 +36,11 @@ be shown. > matcher = PhraseMatcher(nlp.vocab) > ``` -| Name | Type | Description | -| --------------------------------------- | --------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. | -| `attr` 2.1 | int / str | The token attribute to match on. Defaults to `ORTH`, i.e. the verbatim token text. | -| `validate` 2.1 | bool | Validate patterns added to the matcher. | +| Name | Description | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `vocab` | The vocabulary object, which must be shared with the documents the matcher will operate on. ~~Vocab~~ | +| `attr` 2.1 | The token attribute to match on. Defaults to `ORTH`, i.e. the verbatim token text. ~~Union[int, str]~~ | +| `validate` 2.1 | Validate patterns added to the matcher. ~~bool~~ | ## PhraseMatcher.\_\_call\_\_ {#call tag="method"} @@ -57,10 +57,10 @@ Find all token sequences matching the supplied patterns on the `Doc`. > matches = matcher(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `doc` | `Doc` | The document to match over. | -| **RETURNS** | list | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end]`. The `match_id` is the ID of the added match pattern. | +| Name | Description | +| ----------- | ----------------------------------- | +| `doc` | The document to match over. ~~Doc~~ | +| **RETURNS** | list | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end]`. The `match_id` is the ID of the added match pattern. ~~List[Tuple[int, int, int]]~~ | @@ -87,11 +87,13 @@ Match a stream of documents, yielding them in turn. > pass > ``` -| Name | Type | Description | -| ------------ | -------- | --------------------------------------------------------- | -| `docs` | iterable | A stream of documents. | -| `batch_size` | int | The number of documents to accumulate into a working set. | -| **YIELDS** | `Doc` | Documents, in order. | +| Name | Description | +| --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `docs` | A stream of documents. ~~Iterable[Doc]~~ | +| `batch_size` | The number of documents to accumulate into a working set. ~~int~~ | +| `return_matches` 2.1 | Yield the match lists along with the docs, making results `(doc, matches)` tuples. ~~bool~~ | +| `as_tuples` | Interpret the input stream as `(doc, context)` tuples, and yield `(result, context)` tuples out. If both `return_matches` and `as_tuples` are `True`, the output will be a sequence of `((doc, matches), context)` tuples. ~~bool~~ | +| **YIELDS** | Documents and optional matches or context in order. ~~Union[Doc, Tuple[Doc, Any], Tuple[Tuple[Doc, Any], Any]]~~ | ## PhraseMatcher.\_\_len\_\_ {#len tag="method"} @@ -108,9 +110,9 @@ patterns. > assert len(matcher) == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------- | -| **RETURNS** | int | The number of rules. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The number of rules. ~~int~~ | ## PhraseMatcher.\_\_contains\_\_ {#contains tag="method"} @@ -125,10 +127,10 @@ Check whether the matcher contains rules for a match ID. > assert "OBAMA" in matcher > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------------------------- | -| `key` | str | The match ID. | -| **RETURNS** | bool | Whether the matcher contains rules for this match ID. | +| Name | Description | +| ----------- | -------------------------------------------------------------- | +| `key` | The match ID. ~~str~~ | +| **RETURNS** | Whether the matcher contains rules for this match ID. ~~bool~~ | ## PhraseMatcher.add {#add tag="method"} @@ -165,12 +167,12 @@ patterns = [nlp("health care reform"), nlp("healthcare reform")] -| Name | Type | Description | -| -------------- | ------------------ | --------------------------------------------------------------------------------------------- | -| `match_id` | str | An ID for the thing you're matching. | -| `docs` | list | `Doc` objects of the phrases to match. | -| _keyword-only_ | | | -| `on_match` | callable or `None` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. | +| Name | Description | +| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `match_id` | str | An ID for the thing you're matching. ~~str~~ | +| `docs` | `Doc` objects of the phrases to match. ~~List[Doc]~~ | +| _keyword-only_ | | | +| `on_match` | Callback function to act on matches. Takes the arguments `matcher`, `doc`, `i` and `matches`. ~~Optional[Callable[[Matcher, Doc, int, List[tuple], Any]]~~ | ## PhraseMatcher.remove {#remove tag="method" new="2.2"} @@ -187,6 +189,6 @@ does not exist. > assert "OBAMA" not in matcher > ``` -| Name | Type | Description | -| ----- | ---- | ------------------------- | -| `key` | str | The ID of the match rule. | +| Name | Description | +| ----- | --------------------------------- | +| `key` | The ID of the match rule. ~~str~~ | diff --git a/website/docs/api/pipe.md b/website/docs/api/pipe.md index 8302c2e8b..9c3a4104e 100644 --- a/website/docs/api/pipe.md +++ b/website/docs/api/pipe.md @@ -45,12 +45,12 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#create_pipe). -| Name | Type | Description | -| ------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| `**cfg` | | Additional config parameters and settings. Will be available as the dictionary `Pipe.cfg` and is serialized with the component. | +| Name | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], Any]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| `**cfg` | Additional config parameters and settings. Will be available as the dictionary `Pipe.cfg` and is serialized with the component. | ## Pipe.\_\_call\_\_ {#call tag="method"} @@ -70,10 +70,10 @@ and all pipeline components are applied to the `Doc` in order. Both > processed = pipe(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Pipe.pipe {#pipe tag="method"} @@ -91,12 +91,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/pipe#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------- | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of documents to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | The processed documents in order. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Pipe.begin_training {#begin_training tag="method"} @@ -116,13 +116,13 @@ setting up the label scheme based on the data. > optimizer = pipe.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/pipe#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Pipe.predict {#predict tag="method"} @@ -142,10 +142,10 @@ This method needs to be overwritten with your own custom `predict` method. > scores = pipe.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## Pipe.set_annotations {#set_annotations tag="method"} @@ -166,10 +166,10 @@ method. > pipe.set_annotations(docs, scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ---------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `Pipe.predict`. | +| Name | Description | +| -------- | ------------------------------------------------ | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `Tagger.predict`. | ## Pipe.update {#update tag="method"} @@ -184,15 +184,15 @@ predictions and gold-standard annotations, and update the component's model. > losses = pipe.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/pipe#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Pipe.rehearse {#rehearse tag="method,experimental" new="3"} @@ -208,14 +208,14 @@ the "catastrophic forgetting" problem. This feature is experimental. > losses = pipe.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Pipe.get_loss {#get_loss tag="method"} @@ -230,11 +230,11 @@ predicted scores. > loss, d_loss = ner.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## Pipe.score {#score tag="method" new="3"} @@ -246,10 +246,10 @@ Score a batch of examples. > scores = pipe.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | --------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, e.g. produced by the [`Scorer`](/api/scorer). | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, e.g. produced by the [`Scorer`](/api/scorer). ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## Pipe.create_optimizer {#create_optimizer tag="method"} @@ -263,26 +263,9 @@ Create an optimizer for the pipeline component. Defaults to > optimizer = pipe.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | - -## Pipe.add_label {#add_label tag="method"} - -Add a new label to the pipe. It's possible to extend pretrained models with new -labels, but care should be taken to avoid the "catastrophic forgetting" problem. - -> #### Example -> -> ```python -> pipe = nlp.add_pipe("your_custom_pipe") -> pipe.add_label("MY_LABEL") -> ``` - -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Pipe.use_params {#use_params tag="method, contextmanager"} @@ -297,9 +280,26 @@ context, the original parameters are restored. > pipe.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | + +## Pipe.add_label {#add_label tag="method"} + +Add a new label to the pipe. It's possible to extend pretrained models with new +labels, but care should be taken to avoid the "catastrophic forgetting" problem. + +> #### Example +> +> ```python +> pipe = nlp.add_pipe("your_custom_pipe") +> pipe.add_label("MY_LABEL") +> ``` + +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## Pipe.to_disk {#to_disk tag="method"} @@ -312,11 +312,11 @@ Serialize the pipe to disk. > pipe.to_disk("/path/to/pipe") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Pipe.from_disk {#from_disk tag="method"} @@ -329,12 +329,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > pipe.from_disk("/path/to/pipe") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Pipe` | The modified pipe. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified pipe. ~~Pipe~~ | ## Pipe.to_bytes {#to_bytes tag="method"} @@ -347,11 +347,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the pipe. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the pipe. ~~bytes~~ | ## Pipe.from_bytes {#from_bytes tag="method"} @@ -365,21 +365,21 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > pipe.from_bytes(pipe_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Pipe` | The pipe. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The pipe. ~~Pipe~~ | ## Attributes {#attributes} -| Name | Type | Description | -| ------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------- | -| `vocab` | [`Vocab`](/api/vocab) | The shared vocabulary that's passed in on initialization. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model powering the component. | -| `name` | str | The name of the component instance in the pipeline. Can be used in the losses. | -| `cfg` | dict | Keyword arguments passed to [`Pipe.__init__`](/api/pipe#init). Will be serialized with the component. | +| Name | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------------ | +| `vocab` | The shared vocabulary that's passed in on initialization. ~~Vocab~~ | +| `model` | The model powering the component. ~~Model[List[Doc], Any]~~ | +| `name` | The name of the component instance in the pipeline. Can be used in the losses. ~~str~~ | +| `cfg` | Keyword arguments passed to [`Pipe.__init__`](/api/pipe#init). Will be serialized with the component. ~~Dict[str, Any]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/pipeline-functions.md b/website/docs/api/pipeline-functions.md index 5c2eb2b97..0dc03a16a 100644 --- a/website/docs/api/pipeline-functions.md +++ b/website/docs/api/pipeline-functions.md @@ -33,10 +33,10 @@ all other components. -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| **RETURNS** | `Doc` | The modified `Doc` with merged noun chunks. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. ~~Doc~~ | +| **RETURNS** | The modified `Doc` with merged noun chunks. ~~Doc~~ | ## merge_entities {#merge_entities tag="function"} @@ -63,10 +63,10 @@ components to the end of the pipeline and after all other components. -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| **RETURNS** | `Doc` | The modified `Doc` with merged entities. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. ~~Doc~~ | +| **RETURNS** | The modified `Doc` with merged entities. ~~Doc~~ | ## merge_subtokens {#merge_subtokens tag="function" new="2.1"} @@ -102,8 +102,8 @@ end of the pipeline and after all other components. -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| `label` | str | The subtoken dependency label. Defaults to `"subtok"`. | -| **RETURNS** | `Doc` | The modified `Doc` with merged subtokens. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. ~~Doc~~ | +| `label` | The subtoken dependency label. Defaults to `"subtok"`. ~~str~~ | +| **RETURNS** | The modified `Doc` with merged subtokens. ~~Doc~~ | diff --git a/website/docs/api/scorer.md b/website/docs/api/scorer.md index 2f37843a0..1c0895bcf 100644 --- a/website/docs/api/scorer.md +++ b/website/docs/api/scorer.md @@ -27,9 +27,9 @@ Create a new `Scorer`. > scorer = Scorer(nlp) > ``` -| Name | Type | Description | -| ----- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nlp` | Language | The pipeline to use for scoring, where each pipeline component may provide a scoring method. If none is provided, then a default pipeline for the multi-language code `xx` is constructed containing: `senter`, `tagger`, `morphologizer`, `parser`, `ner`, `textcat`. | +| Name | Description | +| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `nlp` | The pipeline to use for scoring, where each pipeline component may provide a scoring method. If none is provided, then a default pipeline for the multi-language code `xx` is constructed containing: `senter`, `tagger`, `morphologizer`, `parser`, `ner`, `textcat`. ~~Language~~ | ## Scorer.score {#score tag="method"} @@ -55,10 +55,10 @@ attribute being scored: > scores = scorer.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | --------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| **RETURNS** | `Dict` | A dictionary of scores. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| **RETURNS** | A dictionary of scores. ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## Scorer.score_tokenization {#score_tokenization tag="staticmethod" new="3"} @@ -74,10 +74,10 @@ Scores the tokenization: > scores = Scorer.score_tokenization(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | --------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| **RETURNS** | `Dict` | A dictionary containing the scores `token_acc`, `token_p`, `token_r`, `token_f`. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| **RETURNS** | `Dict` | A dictionary containing the scores `token_acc`, `token_p`, `token_r`, `token_f`. ~~Dict[str, float]]~~ | ## Scorer.score_token_attr {#score_token_attr tag="staticmethod" new="3"} @@ -90,18 +90,19 @@ Scores a single token attribute. > print(scores["pos_acc"]) > ``` -| Name | Type | Description | -| -------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| `attr` | `str` | The attribute to score. | -| _keyword-only_ | | | -| `getter` | `Callable` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. | -| **RETURNS** | `Dict[str, float]` | A dictionary containing the score `{attr}_acc`. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| `attr` | The attribute to score. ~~str~~ | +| _keyword-only_ | | +| `getter` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. ~~Callable[[Token, str], Any]~~ | +| **RETURNS** | A dictionary containing the score `{attr}_acc`. ~~Dict[str, float]~~ | ## Scorer.score_token_attr_per_feat {#score_token_attr_per_feat tag="staticmethod" new="3"} -Scores a single token attribute per feature for a token attribute in -[UFEATS](https://universaldependencies.org/format.html#morphological-annotation) +Scores a single token attribute per feature for a token attribute in the +Universal Dependencies +[FEATS](https://universaldependencies.org/format.html#morphological-annotation) format. > #### Example @@ -111,13 +112,13 @@ format. > print(scores["morph_per_feat"]) > ``` -| Name | Type | Description | -| -------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| `attr` | `str` | The attribute to score. | -| _keyword-only_ | | | -| `getter` | `Callable` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. | -| **RETURNS** | `Dict` | A dictionary containing the per-feature PRF scores under the key `{attr}_per_feat`. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| `attr` | The attribute to score. ~~str~~ | +| _keyword-only_ | | +| `getter` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. ~~Callable[[Token, str], Any]~~ | +| **RETURNS** | A dictionary containing the per-feature PRF scores under the key `{attr}_per_feat`. ~~Dict[str, Dict[str, float]]~~ | ## Scorer.score_spans {#score_spans tag="staticmethod" new="3"} @@ -130,13 +131,13 @@ Returns PRF scores for labeled or unlabeled spans. > print(scores["ents_f"]) > ``` -| Name | Type | Description | -| -------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| `attr` | `str` | The attribute to score. | -| _keyword-only_ | | | -| `getter` | `Callable` | Defaults to `getattr`. If provided, `getter(doc, attr)` should return the `Span` objects for an individual `Doc`. | -| **RETURNS** | `Dict` | A dictionary containing the PRF scores under the keys `{attr}_p`, `{attr}_r`, `{attr}_f` and the per-type PRF scores under `{attr}_per_type`. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| `attr` | The attribute to score. ~~str~~ | +| _keyword-only_ | | +| `getter` | Defaults to `getattr`. If provided, `getter(doc, attr)` should return the `Span` objects for an individual `Doc`. ~~Callable[[Doc, str], Iterable[Span]]~~ | +| **RETURNS** | A dictionary containing the PRF scores under the keys `{attr}_p`, `{attr}_r`, `{attr}_f` and the per-type PRF scores under `{attr}_per_type`. ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## Scorer.score_deps {#score_deps tag="staticmethod" new="3"} @@ -159,16 +160,16 @@ Calculate the UAS, LAS, and LAS per type scores for dependency parses. > print(scores["dep_uas"], scores["dep_las"]) > ``` -| Name | Type | Description | -| --------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| `attr` | `str` | The attribute containing the dependency label. | -| _keyword-only_ | | | -| `getter` | `Callable` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. | -| `head_attr` | `str` | The attribute containing the head token. | -| `head_getter` | `callable` | Defaults to `getattr`. If provided, `head_getter(token, attr)` should return the head for an individual `Token`. | -| `ignore_labels` | `Tuple` | Labels to ignore while scoring (e.g., `punct`). | -| **RETURNS** | `Dict` | A dictionary containing the scores: `{attr}_uas`, `{attr}_las`, and `{attr}_las_per_type`. | +| Name | Description | +| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| `attr` | The attribute to score. ~~str~~ | +| _keyword-only_ | | +| `getter` | Defaults to `getattr`. If provided, `getter(token, attr)` should return the value of the attribute for an individual `Token`. ~~Callable[[Token, str], Any]~~ | +| `head_attr` | The attribute containing the head token. ~~str~~ | +| `head_getter` | Defaults to `getattr`. If provided, `head_getter(token, attr)` should return the head for an individual `Token`. ~~Callable[[Doc, str], Token]~~ | +| `ignore_labels` | Labels to ignore while scoring (e.g. `"punct"`). ~~Iterable[str]~~ | +| **RETURNS** | A dictionary containing the scores: `{attr}_uas`, `{attr}_las`, and `{attr}_las_per_type`. ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## Scorer.score_cats {#score_cats tag="staticmethod" new="3"} @@ -195,13 +196,13 @@ depends on the scorer settings: > print(scores["cats_macro_auc"]) > ``` -| Name | Type | Description | -| ---------------- | ------------------- | ------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The `Example` objects holding both the predictions and the correct gold-standard annotations. | -| `attr` | `str` | The attribute to score. | -| _keyword-only_ | | | -| `getter` | `Callable` | Defaults to `getattr`. If provided, `getter(doc, attr)` should return the cats for an individual `Doc`. | -| labels | `Iterable[str]` | The set of possible labels. Defaults to `[]`. | -| `multi_label` | `bool` | Whether the attribute allows multiple labels. Defaults to `True`. | -| `positive_label` | `str` | The positive label for a binary task with exclusive classes. Defaults to `None`. | -| **RETURNS** | `Dict` | A dictionary containing the scores, with inapplicable scores as `None`. | +| Name | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The `Example` objects holding both the predictions and the correct gold-standard annotations. ~~Iterable[Example]~~ | +| `attr` | The attribute to score. ~~str~~ | +| _keyword-only_ | | +| `getter` | Defaults to `getattr`. If provided, `getter(doc, attr)` should return the cats for an individual `Doc`. ~~Callable[[Doc, str], Dict[str, float]]~~ | +| labels | The set of possible labels. Defaults to `[]`. ~~Iterable[str]~~ | +| `multi_label` | Whether the attribute allows multiple labels. Defaults to `True`. ~~bool~~ | +| `positive_label` | The positive label for a binary task with exclusive classes. Defaults to `None`. ~~Optional[str]~~ | +| **RETURNS** | A dictionary containing the scores, with inapplicable scores as `None`. ~~Dict[str, Optional[float]]~~ | diff --git a/website/docs/api/sentencerecognizer.md b/website/docs/api/sentencerecognizer.md index cefdbea88..06bef32ba 100644 --- a/website/docs/api/sentencerecognizer.md +++ b/website/docs/api/sentencerecognizer.md @@ -29,9 +29,9 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("senter", config=config) > ``` -| Setting | Type | Description | Default | -| ------- | ------------------------------------------ | ----------------- | ----------------------------------- | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The model to use. | [Tagger](/api/architectures#Tagger) | +| Setting | Description | +| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [Tagger](/api/architectures#Tagger). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/senter.pyx @@ -60,11 +60,11 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#add_pipe). -| Name | Type | Description | -| ------- | ------- | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | `Model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | +| Name | Description | +| ------- | -------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | ## SentenceRecognizer.\_\_call\_\_ {#call tag="method"} @@ -85,10 +85,10 @@ and all pipeline components are applied to the `Doc` in order. Both > processed = senter(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## SentenceRecognizer.pipe {#pipe tag="method"} @@ -107,12 +107,12 @@ and [`pipe`](/api/sentencerecognizer#pipe) delegate to the > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## SentenceRecognizer.begin_training {#begin_training tag="method"} @@ -132,13 +132,13 @@ setting up the label scheme based on the data. > optimizer = senter.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/sentencerecognizer#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## SentenceRecognizer.predict {#predict tag="method"} @@ -152,10 +152,10 @@ modifying them. > scores = senter.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## SentenceRecognizer.set_annotations {#set_annotations tag="method"} @@ -169,10 +169,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > senter.set_annotations([doc1, doc2], scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ------------------------------------------------------------ | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `SentenceRecognizer.predict`. | +| Name | Description | +| -------- | ------------------------------------------------------------ | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `SentenceRecognizer.predict`. | ## SentenceRecognizer.update {#update tag="method"} @@ -189,15 +189,15 @@ Delegates to [`predict`](/api/sentencerecognizer#predict) and > losses = senter.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/sentencerecognizer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. The value keyed by the model's name is updated. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## SentenceRecognizer.rehearse {#rehearse tag="method,experimental" new="3"} @@ -213,14 +213,14 @@ the "catastrophic forgetting" problem. This feature is experimental. > losses = senter.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## SentenceRecognizer.get_loss {#get_loss tag="method"} @@ -235,11 +235,11 @@ predicted scores. > loss, d_loss = senter.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | - | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## SentenceRecognizer.score {#score tag="method" new="3"} @@ -251,10 +251,10 @@ Score a batch of examples. > scores = senter.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | ------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans). | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_token_attr`](/api/scorer#score_token_attr) for the attributes `"pos"`, `"tag"` and `"lemma"`. ~~Dict[str, float]~~ | ## SentenceRecognizer.create_optimizer {#create_optimizer tag="method"} @@ -267,9 +267,9 @@ Create an optimizer for the pipeline component. > optimizer = senter.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## SentenceRecognizer.use_params {#use_params tag="method, contextmanager"} @@ -284,9 +284,9 @@ context, the original parameters are restored. > senter.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## SentenceRecognizer.to_disk {#to_disk tag="method"} @@ -299,11 +299,11 @@ Serialize the pipe to disk. > senter.to_disk("/path/to/senter") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## SentenceRecognizer.from_disk {#from_disk tag="method"} @@ -316,12 +316,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > senter.from_disk("/path/to/senter") > ``` -| Name | Type | Description | -| -------------- | -------------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `SentenceRecognizer` | The modified `SentenceRecognizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `SentenceRecognizer` object. ~~SentenceRecognizer~~ | ## SentenceRecognizer.to_bytes {#to_bytes tag="method"} @@ -334,11 +334,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `SentenceRecognizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `SentenceRecognizer` object. ~~bytes~~ | ## SentenceRecognizer.from_bytes {#from_bytes tag="method"} @@ -352,12 +352,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > senter.from_bytes(senter_bytes) > ``` -| Name | Type | Description | -| -------------- | -------------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `SentenceRecognizer` | The `SentenceRecognizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `SentenceRecognizer` object. ~~SentenceRecognizer~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/sentencizer.md b/website/docs/api/sentencizer.md index 198215cfa..8104b1151 100644 --- a/website/docs/api/sentencizer.md +++ b/website/docs/api/sentencizer.md @@ -28,9 +28,9 @@ how the component should be configured. You can override its settings via the > nlp.add_pipe("entity_ruler", config=config) > ``` -| Setting | Type | Description | Default | -| ------------- | ----------- | ---------------------------------------------------------------------------------------------------------- | ------- | -| `punct_chars` | `List[str]` | Optional custom list of punctuation characters that mark sentence ends. See below for defaults if not set. | `None` | +| Setting | Description | +| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `punct_chars` | Optional custom list of punctuation characters that mark sentence ends. See below for defaults if not set. Defaults to `None`. ~~Optional[List[str]]~~ | `None` | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/sentencizer.pyx @@ -51,10 +51,10 @@ Initialize the sentencizer. > sentencizer = Sentencizer() > ``` -| Name | Type | Description | -| -------------- | ----------- | ----------------------------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `punct_chars` | `List[str]` | Optional custom list of punctuation characters that mark sentence ends. See below for defaults. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------------------------------- | +| _keyword-only_ | | | +| `punct_chars` | Optional custom list of punctuation characters that mark sentence ends. See below for defaults. ~~Optional[List[str]]~~ | ```python ### punct_chars defaults @@ -87,10 +87,10 @@ the component has been added to the pipeline using > assert len(list(doc.sents)) == 2 > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. | -| **RETURNS** | `Doc` | The modified `Doc` with added sentence boundaries. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `doc` | The `Doc` object to process, e.g. the `Doc` in the pipeline. ~~Doc~~ | +| **RETURNS** | The modified `Doc` with added sentence boundaries. ~~Doc~~ | ## Sentencizer.pipe {#pipe tag="method"} @@ -106,12 +106,12 @@ applied to the `Doc` in order. > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------- | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of documents to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | The processed documents in order. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Sentencizer.score {#score tag="method" new="3"} @@ -123,10 +123,10 @@ Score a batch of examples. > scores = sentencizer.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | ------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans). | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_spans`](/api/scorer#score_spans). ~~Dict[str, Union[float, Dict[str, float]]~~ | ## Sentencizer.to_disk {#to_disk tag="method"} @@ -142,9 +142,9 @@ a file `sentencizer.json`. This also happens automatically when you save an > sentencizer.to_disk("/path/to/sentencizer.json") > ``` -| Name | Type | Description | -| ------ | ------------ | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a JSON file, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a JSON file, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## Sentencizer.from_disk {#from_disk tag="method"} @@ -159,10 +159,10 @@ added to its pipeline. > sentencizer.from_disk("/path/to/sentencizer.json") > ``` -| Name | Type | Description | -| ----------- | ------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a JSON file. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `Sentencizer` | The modified `Sentencizer` object. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a JSON file. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The modified `Sentencizer` object. ~~Sentencizer~~ | ## Sentencizer.to_bytes {#to_bytes tag="method"} @@ -176,9 +176,9 @@ Serialize the sentencizer settings to a bytestring. > sentencizer_bytes = sentencizer.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------- | -| **RETURNS** | bytes | The serialized data. | +| Name | Description | +| ----------- | ------------------------------ | +| **RETURNS** | The serialized data. ~~bytes~~ | ## Sentencizer.from_bytes {#from_bytes tag="method"} @@ -192,7 +192,7 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > sentencizer.from_bytes(sentencizer_bytes) > ``` -| Name | Type | Description | -| ------------ | ------------- | ---------------------------------- | -| `bytes_data` | bytes | The bytestring to load. | -| **RETURNS** | `Sentencizer` | The modified `Sentencizer` object. | +| Name | Description | +| ------------ | -------------------------------------------------- | +| `bytes_data` | The bytestring to load. ~~bytes~~ | +| **RETURNS** | The modified `Sentencizer` object. ~~Sentencizer~~ | diff --git a/website/docs/api/span.md b/website/docs/api/span.md index 9237b5538..1c7bc9592 100644 --- a/website/docs/api/span.md +++ b/website/docs/api/span.md @@ -18,14 +18,14 @@ Create a Span object from the slice `doc[start : end]`. > assert [t.text for t in span] == ["it", "back", "!"] > ``` -| Name | Type | Description | -| -------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The parent document. | -| `start` | int | The index of the first token of the span. | -| `end` | int | The index of the first token after the span. | -| `label` | int / str | A label to attach to the span, e.g. for named entities. As of v2.1, the label can also be a string. | -| `kb_id` | int / str | A knowledge base ID to attach to the span, e.g. for named entities. The ID can be an integer or a string. | -| `vector` | `numpy.ndarray[ndim=1, dtype="float32"]` | A meaning representation of the span. | +| Name | Description | +| -------- | --------------------------------------------------------------------------------------- | +| `doc` | The parent document. ~~Doc~~ | +| `start` | The index of the first token of the span. ~~int~~ | +| `end` | The index of the first token after the span. ~~int~~ | +| `label` | A label to attach to the span, e.g. for named entities. ~~Union[str, int]~~ | +| `kb_id` | A knowledge base ID to attach to the span, e.g. for named entities. ~~Union[str, int]~~ | +| `vector` | A meaning representation of the span. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Span.\_\_getitem\_\_ {#getitem tag="method"} @@ -39,10 +39,10 @@ Get a `Token` object. > assert span[1].text == "back" > ``` -| Name | Type | Description | -| ----------- | ------- | --------------------------------------- | -| `i` | int | The index of the token within the span. | -| **RETURNS** | `Token` | The token at `span[i]`. | +| Name | Description | +| ----------- | ----------------------------------------------- | +| `i` | The index of the token within the span. ~~int~~ | +| **RETURNS** | The token at `span[i]`. ~~Token~~ | Get a `Span` object. @@ -54,10 +54,10 @@ Get a `Span` object. > assert span[1:3].text == "back!" > ``` -| Name | Type | Description | -| ----------- | ------ | -------------------------------- | -| `start_end` | tuple | The slice of the span to get. | -| **RETURNS** | `Span` | The span at `span[start : end]`. | +| Name | Description | +| ----------- | ------------------------------------------------- | +| `start_end` | The slice of the span to get. ~~Tuple[int, int]~~ | +| **RETURNS** | The span at `span[start : end]`. ~~Span~~ | ## Span.\_\_iter\_\_ {#iter tag="method"} @@ -71,9 +71,9 @@ Iterate over `Token` objects. > assert [t.text for t in span] == ["it", "back", "!"] > ``` -| Name | Type | Description | -| ---------- | ------- | ----------------- | -| **YIELDS** | `Token` | A `Token` object. | +| Name | Description | +| ---------- | --------------------------- | +| **YIELDS** | A `Token` object. ~~Token~~ | ## Span.\_\_len\_\_ {#len tag="method"} @@ -87,9 +87,9 @@ Get the number of tokens in the span. > assert len(span) == 3 > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------- | -| **RETURNS** | int | The number of tokens in the span. | +| Name | Description | +| ----------- | ----------------------------------------- | +| **RETURNS** | The number of tokens in the span. ~~int~~ | ## Span.set_extension {#set_extension tag="classmethod" new="2"} @@ -107,14 +107,14 @@ For details, see the documentation on > assert doc[1:4]._.has_city > ``` -| Name | Type | Description | -| --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `span._.my_attr`. | -| `default` | - | Optional default value of the attribute if no getter or method is defined. | -| `method` | callable | Set a custom method on the object, for example `span._.compare(other_span)`. | -| `getter` | callable | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. | -| `setter` | callable | Setter function that takes the `Span` and a value, and modifies the object. Is called when the user writes to the `Span._` attribute. | -| `force` | bool | Force overwriting existing attribute. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `span._.my_attr`. ~~str~~ | +| `default` | Optional default value of the attribute if no getter or method is defined. ~~Optional[Any]~~ | +| `method` | Set a custom method on the object, for example `span._.compare(other_span)`. ~~Optional[Callable[[Span, ...], Any]]~~ | +| `getter` | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. ~~Optional[Callable[[Span], Any]]~~ | +| `setter` | Setter function that takes the `Span` and a value, and modifies the object. Is called when the user writes to the `Span._` attribute. ~~Optional[Callable[[Span, Any], None]]~~ | +| `force` | Force overwriting existing attribute. ~~bool~~ | ## Span.get_extension {#get_extension tag="classmethod" new="2"} @@ -131,10 +131,10 @@ Look up a previously registered extension by name. Returns a 4-tuple > assert extension == (False, None, None, None) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the extension. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Span.has_extension {#has_extension tag="classmethod" new="2"} @@ -148,10 +148,10 @@ Check whether an extension has been registered on the `Span` class. > assert Span.has_extension("is_city") > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------ | -| `name` | str | Name of the extension to check. | -| **RETURNS** | bool | Whether the extension has been registered. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| `name` | Name of the extension to check. ~~str~~ | +| **RETURNS** | Whether the extension has been registered. ~~bool~~ | ## Span.remove_extension {#remove_extension tag="classmethod" new="2.0.12"} @@ -166,10 +166,10 @@ Remove a previously registered extension. > assert not Span.has_extension("is_city") > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the removed extension. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the removed extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Span.char_span {#char_span tag="method" new="2.2.4"} @@ -184,14 +184,14 @@ the character indices don't map to a valid span. > assert span.text == "New York" > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------- | --------------------------------------------------------------------- | -| `start` | int | The index of the first character of the span. | -| `end` | int | The index of the last character after the span. | -| `label` | uint64 / str | A label to attach to the span, e.g. for named entities. | -| `kb_id` | uint64 / str | An ID from a knowledge base to capture the meaning of a named entity. | -| `vector` | `numpy.ndarray[ndim=1, dtype="float32"]` | A meaning representation of the span. | -| **RETURNS** | `Span` | The newly constructed object or `None`. | +| Name | Description | +| ------------------------------------ | ----------------------------------------------------------------------------------------- | +| `start` | The index of the first character of the span. ~~int~~ | +| `end` | The index of the last character after the span. ~int~~ | +| `label` | A label to attach to the span, e.g. for named entities. ~~Union[int, str]~~ | +| `kb_id` 2.2 | An ID from a knowledge base to capture the meaning of a named entity. ~~Union[int, str]~~ | +| `vector` | A meaning representation of the span. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | +| **RETURNS** | The newly constructed object or `None`. ~~Optional[Span]~~ | ## Span.similarity {#similarity tag="method" model="vectors"} @@ -209,10 +209,10 @@ using an average of word vectors. > assert apples_oranges == oranges_apples > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------------------------------------- | -| `other` | - | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. | -| **RETURNS** | float | A scalar similarity score. Higher is more similar. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `other` | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. ~~Union[Doc, Span, Token, Lexeme]~~ | +| **RETURNS** | A scalar similarity score. Higher is more similar. ~~float~~ | ## Span.get_lca_matrix {#get_lca_matrix tag="method"} @@ -229,9 +229,9 @@ ancestor is found, e.g. if span excludes a necessary ancestor. > # array([[0, 0, 0], [0, 1, 2], [0, 2, 2]], dtype=int32) > ``` -| Name | Type | Description | -| ----------- | -------------------------------------- | ------------------------------------------------ | -| **RETURNS** | `numpy.ndarray[ndim=2, dtype="int32"]` | The lowest common ancestor matrix of the `Span`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------- | +| **RETURNS** | The lowest common ancestor matrix of the `Span`. ~~numpy.ndarray[ndim=2, dtype=int32]~~ | ## Span.to_array {#to_array tag="method" new="2"} @@ -249,10 +249,10 @@ shape `(N, M)`, where `N` is the length of the document. The values will be > np_array = span.to_array([LOWER, POS, ENT_TYPE, IS_ALPHA]) > ``` -| Name | Type | Description | -| ----------- | ----------------------------- | -------------------------------------------------------------------------------------------------------- | -| `attr_ids` | list | A list of attribute ID ints. | -| **RETURNS** | `numpy.ndarray[long, ndim=2]` | A feature matrix, with one row per word, and one column per attribute indicated in the input `attr_ids`. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `attr_ids` | A list of attributes (int IDs or string names) or a single attribute (int ID or string name). ~~Union[int, str, List[Union[int, str]]]~~ | +| **RETURNS** | The exported attributes as a numpy array. ~~Union[numpy.ndarray[ndim=2, dtype=uint64], numpy.ndarray[ndim=1, dtype=uint64]]~~ | ## Span.ents {#ents tag="property" new="2.0.13" model="ner"} @@ -270,9 +270,9 @@ if the entity recognizer has been applied. > assert ents[0].text == "Mr. Best" > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------- | -| **RETURNS** | tuple | Entities in the span, one `Span` per entity. | +| Name | Description | +| ----------- | ----------------------------------------------------------------- | +| **RETURNS** | Entities in the span, one `Span` per entity. ~~Tuple[Span, ...]~~ | ## Span.as_doc {#as_doc tag="method"} @@ -287,10 +287,10 @@ Create a new `Doc` object corresponding to the `Span`, with a copy of the data. > assert doc2.text == "New York" > ``` -| Name | Type | Description | -| ---------------- | ----- | ---------------------------------------------------- | -| `copy_user_data` | bool | Whether or not to copy the original doc's user data. | -| **RETURNS** | `Doc` | A `Doc` object of the `Span`'s content. | +| Name | Description | +| ---------------- | ------------------------------------------------------------- | +| `copy_user_data` | Whether or not to copy the original doc's user data. ~~bool~~ | +| **RETURNS** | A `Doc` object of the `Span`'s content. ~~Doc~~ | ## Span.root {#root tag="property" model="parser"} @@ -309,9 +309,9 @@ taken. > assert new_york.root.text == "York" > ``` -| Name | Type | Description | -| ----------- | ------- | --------------- | -| **RETURNS** | `Token` | The root token. | +| Name | Description | +| ----------- | ------------------------- | +| **RETURNS** | The root token. ~~Token~~ | ## Span.conjuncts {#conjuncts tag="property" model="parser"} @@ -325,9 +325,9 @@ A tuple of tokens coordinated to `span.root`. > assert [t.text for t in apples_conjuncts] == ["oranges"] > ``` -| Name | Type | Description | -| ----------- | ------- | ----------------------- | -| **RETURNS** | `tuple` | The coordinated tokens. | +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | The coordinated tokens. ~~Tuple[Token, ...]~~ | ## Span.lefts {#lefts tag="property" model="parser"} @@ -341,9 +341,9 @@ Tokens that are to the left of the span, whose heads are within the span. > assert lefts == ["New"] > ``` -| Name | Type | Description | -| ---------- | ------- | ------------------------------------ | -| **YIELDS** | `Token` | A left-child of a token of the span. | +| Name | Description | +| ---------- | ---------------------------------------------- | +| **YIELDS** | A left-child of a token of the span. ~~Token~~ | ## Span.rights {#rights tag="property" model="parser"} @@ -357,9 +357,9 @@ Tokens that are to the right of the span, whose heads are within the span. > assert rights == ["in"] > ``` -| Name | Type | Description | -| ---------- | ------- | ------------------------------------- | -| **YIELDS** | `Token` | A right-child of a token of the span. | +| Name | Description | +| ---------- | ----------------------------------------------- | +| **YIELDS** | A right-child of a token of the span. ~~Token~~ | ## Span.n_lefts {#n_lefts tag="property" model="parser"} @@ -373,9 +373,9 @@ the span. > assert doc[3:7].n_lefts == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------- | -| **RETURNS** | int | The number of left-child tokens. | +| Name | Description | +| ----------- | ---------------------------------------- | +| **RETURNS** | The number of left-child tokens. ~~int~~ | ## Span.n_rights {#n_rights tag="property" model="parser"} @@ -389,9 +389,9 @@ the span. > assert doc[2:4].n_rights == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------- | -| **RETURNS** | int | The number of right-child tokens. | +| Name | Description | +| ----------- | ----------------------------------------- | +| **RETURNS** | The number of right-child tokens. ~~int~~ | ## Span.subtree {#subtree tag="property" model="parser"} @@ -405,9 +405,9 @@ Tokens within the span and tokens which descend from them. > assert subtree == ["Give", "it", "back", "!"] > ``` -| Name | Type | Description | -| ---------- | ------- | ------------------------------------------------- | -| **YIELDS** | `Token` | A token within the span, or a descendant from it. | +| Name | Description | +| ---------- | ----------------------------------------------------------- | +| **YIELDS** | A token within the span, or a descendant from it. ~~Token~~ | ## Span.has_vector {#has_vector tag="property" model="vectors"} @@ -420,9 +420,9 @@ A boolean value indicating whether a word vector is associated with the object. > assert doc[1:].has_vector > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------- | -| **RETURNS** | bool | Whether the span has a vector data attached. | +| Name | Description | +| ----------- | ----------------------------------------------------- | +| **RETURNS** | Whether the span has a vector data attached. ~~bool~~ | ## Span.vector {#vector tag="property" model="vectors"} @@ -437,9 +437,9 @@ vectors. > assert doc[1:].vector.shape == (300,) > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------- | --------------------------------------------------- | -| **RETURNS** | `numpy.ndarray[ndim=1, dtype="float32"]` | A 1D numpy array representing the span's semantics. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| **RETURNS** | A 1-dimensional array representing the span's vector. ~~`numpy.ndarray[ndim=1, dtype=float32]~~ | ## Span.vector_norm {#vector_norm tag="property" model="vectors"} @@ -454,31 +454,31 @@ The L2 norm of the span's vector representation. > assert doc[1:].vector_norm != doc[2:].vector_norm > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------------------------- | -| **RETURNS** | float | The L2 norm of the vector representation. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| **RETURNS** | The L2 norm of the vector representation. ~~float~~ | ## Attributes {#attributes} -| Name | Type | Description | -| --------------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The parent document. | -| `tensor` 2.1.7 | `ndarray` | The span's slice of the parent `Doc`'s tensor. | -| `sent` | `Span` | The sentence span that this span is a part of. | -| `start` | int | The token offset for the start of the span. | -| `end` | int | The token offset for the end of the span. | -| `start_char` | int | The character offset for the start of the span. | -| `end_char` | int | The character offset for the end of the span. | -| `text` | str | A string representation of the span text. | -| `text_with_ws` | str | The text content of the span with a trailing whitespace character if the last token has one. | -| `orth` | int | ID of the verbatim text content. | -| `orth_` | str | Verbatim text content (identical to `Span.text`). Exists mostly for consistency with the other attributes. | -| `label` | int | The hash value of the span's label. | -| `label_` | str | The span's label. | -| `lemma_` | str | The span's lemma. | -| `kb_id` | int | The hash value of the knowledge base ID referred to by the span. | -| `kb_id_` | str | The knowledge base ID referred to by the span. | -| `ent_id` | int | The hash value of the named entity the token is an instance of. | -| `ent_id_` | str | The string ID of the named entity the token is an instance of. | -| `sentiment` | float | A scalar value indicating the positivity or negativity of the span. | -| `_` | `Underscore` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). | +| Name | Description | +| --------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `doc` | The parent document. ~~Doc~~ | +| `tensor` 2.1.7 | The span's slice of the parent `Doc`'s tensor. ~~numpy.ndarray~~ | +| `sent` | The sentence span that this span is a part of. ~~Span~~ | +| `start` | The token offset for the start of the span. ~~int~~ | +| `end` | The token offset for the end of the span. ~~int~~ | +| `start_char` | The character offset for the start of the span. ~~int~~ | +| `end_char` | The character offset for the end of the span. ~~int~~ | +| `text` | A string representation of the span text. ~~str~~ | +| `text_with_ws` | The text content of the span with a trailing whitespace character if the last token has one. ~~str~~ | +| `orth` | ID of the verbatim text content. ~~int~~ | +| `orth_` | Verbatim text content (identical to `Span.text`). Exists mostly for consistency with the other attributes. ~~str~~ | +| `label` | The hash value of the span's label. ~~int~~ | +| `label_` | The span's label. ~~str~~ | +| `lemma_` | The span's lemma. Equivalent to `"".join(token.text_with_ws for token in span)`. ~~str~~ | +| `kb_id` | The hash value of the knowledge base ID referred to by the span. ~~int~~ | +| `kb_id_` | The knowledge base ID referred to by the span. ~~str~~ | +| `ent_id` | The hash value of the named entity the token is an instance of. ~~int~~ | +| `ent_id_` | The string ID of the named entity the token is an instance of. ~~str~~ | +| `sentiment` | A scalar value indicating the positivity or negativity of the span. ~~float~~ | +| `_` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). ~~Underscore~~ | diff --git a/website/docs/api/stringstore.md b/website/docs/api/stringstore.md index b66d755ed..d5f78dbab 100644 --- a/website/docs/api/stringstore.md +++ b/website/docs/api/stringstore.md @@ -19,9 +19,9 @@ Create the `StringStore`. > stringstore = StringStore(["apple", "orange"]) > ``` -| Name | Type | Description | -| --------- | -------- | ------------------------------------------ | -| `strings` | iterable | A sequence of strings to add to the store. | +| Name | Description | +| --------- | ---------------------------------------------------------------------- | +| `strings` | A sequence of strings to add to the store. ~~Optional[Iterable[str]]~~ | ## StringStore.\_\_len\_\_ {#len tag="method"} @@ -34,9 +34,9 @@ Get the number of strings in the store. > assert len(stringstore) == 2 > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------- | -| **RETURNS** | int | The number of strings in the store. | +| Name | Description | +| ----------- | ------------------------------------------- | +| **RETURNS** | The number of strings in the store. ~~int~~ | ## StringStore.\_\_getitem\_\_ {#getitem tag="method"} @@ -51,10 +51,10 @@ Retrieve a string from a given hash, or vice versa. > assert stringstore[apple_hash] == "apple" > ``` -| Name | Type | Description | -| -------------- | -------------------- | -------------------------- | -| `string_or_id` | bytes, str or uint64 | The value to encode. | -| **RETURNS** | str or int | The value to be retrieved. | +| Name | Description | +| -------------- | ----------------------------------------------- | +| `string_or_id` | The value to encode. ~~Union[bytes, str, int]~~ | +| **RETURNS** | The value to be retrieved. ~~Union[str, int]~~ | ## StringStore.\_\_contains\_\_ {#contains tag="method"} @@ -68,15 +68,15 @@ Check whether a string is in the store. > assert not "cherry" in stringstore > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------- | -| `string` | str | The string to check. | -| **RETURNS** | bool | Whether the store contains the string. | +| Name | Description | +| ----------- | ----------------------------------------------- | +| `string` | The string to check. ~~str~~ | +| **RETURNS** | Whether the store contains the string. ~~bool~~ | ## StringStore.\_\_iter\_\_ {#iter tag="method"} Iterate over the strings in the store, in order. Note that a newly initialized -store will always include an empty string `''` at position `0`. +store will always include an empty string `""` at position `0`. > #### Example > @@ -86,9 +86,9 @@ store will always include an empty string `''` at position `0`. > assert all_strings == ["apple", "orange"] > ``` -| Name | Type | Description | -| ---------- | ---- | ---------------------- | -| **YIELDS** | str | A string in the store. | +| Name | Description | +| ---------- | ------------------------------ | +| **YIELDS** | A string in the store. ~~str~~ | ## StringStore.add {#add tag="method" new="2"} @@ -105,10 +105,10 @@ Add a string to the `StringStore`. > assert stringstore["banana"] == banana_hash > ``` -| Name | Type | Description | -| ----------- | ------ | ------------------------ | -| `string` | str | The string to add. | -| **RETURNS** | uint64 | The string's hash value. | +| Name | Description | +| ----------- | -------------------------------- | +| `string` | The string to add. ~~str~~ | +| **RETURNS** | The string's hash value. ~~int~~ | ## StringStore.to_disk {#to_disk tag="method" new="2"} @@ -120,9 +120,9 @@ Save the current state to a directory. > stringstore.to_disk("/path/to/strings") > ``` -| Name | Type | Description | -| ------ | ------------ | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## StringStore.from_disk {#from_disk tag="method" new="2"} @@ -135,10 +135,10 @@ Loads state from a directory. Modifies the object in place and returns it. > stringstore = StringStore().from_disk("/path/to/strings") > ``` -| Name | Type | Description | -| ----------- | ------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `StringStore` | The modified `StringStore` object. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The modified `StringStore` object. ~~StringStore~~ | ## StringStore.to_bytes {#to_bytes tag="method"} @@ -150,9 +150,9 @@ Serialize the current state to a binary string. > store_bytes = stringstore.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------ | -| **RETURNS** | bytes | The serialized form of the `StringStore` object. | +| Name | Description | +| ----------- | ---------------------------------------------------------- | +| **RETURNS** | The serialized form of the `StringStore` object. ~~bytes~~ | ## StringStore.from_bytes {#from_bytes tag="method"} @@ -166,10 +166,10 @@ Load state from a binary string. > new_store = StringStore().from_bytes(store_bytes) > ``` -| Name | Type | Description | -| ------------ | ------------- | ------------------------- | -| `bytes_data` | bytes | The data to load from. | -| **RETURNS** | `StringStore` | The `StringStore` object. | +| Name | Description | +| ------------ | ----------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| **RETURNS** | The `StringStore` object. ~~StringStore~~ | ## Utilities {#util} @@ -184,7 +184,7 @@ Get a 64-bit hash for a given string. > assert hash_string("apple") == 8566208034543834098 > ``` -| Name | Type | Description | -| ----------- | ------ | ------------------- | -| `string` | str | The string to hash. | -| **RETURNS** | uint64 | The hash. | +| Name | Description | +| ----------- | --------------------------- | +| `string` | The string to hash. ~~str~~ | +| **RETURNS** | The hash. ~~int~~ | diff --git a/website/docs/api/tagger.md b/website/docs/api/tagger.md index 9761dea15..b255b2261 100644 --- a/website/docs/api/tagger.md +++ b/website/docs/api/tagger.md @@ -28,10 +28,10 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("tagger", config=config) > ``` -| Setting | Type | Description | Default | -| ---------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------- | -| `set_morphology` | bool | Whether to set morphological features. | `False` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | A model instance that predicts the tag probabilities. The output vectors should match the number of tags in size, and be normalized as probabilities (all scores between 0 and 1, with the rows summing to `1`). | [Tagger](/api/architectures#Tagger) | +| Setting | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `set_morphology` | Whether to set morphological features. Defaults to `False`. ~~bool~~ | +| `model` | A model instance that predicts the tag probabilities. The output vectors should match the number of tags in size, and be normalized as probabilities (all scores between 0 and 1, with the rows summing to `1`). Defaults to [Tagger](/api/architectures#Tagger). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/tagger.pyx @@ -58,13 +58,13 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#add_pipe). -| Name | Type | Description | -| ---------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | A model instance that predicts the tag probabilities. The output vectors should match the number of tags in size, and be normalized as probabilities (all scores between 0 and 1, with the rows summing to `1`). | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| _keyword-only_ | | | -| `set_morphology` | bool | Whether to set morphological features. | +| Name | Description | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | A model instance that predicts the tag probabilities. The output vectors should match the number of tags in size, and be normalized as probabilities (all scores between 0 and 1, with the rows summing to `1`). ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | +| `set_morphology` | Whether to set morphological features. ~~bool~~ | ## Tagger.\_\_call\_\_ {#call tag="method"} @@ -84,10 +84,10 @@ and all pipeline components are applied to the `Doc` in order. Both > processed = tagger(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Tagger.pipe {#pipe tag="method"} @@ -105,12 +105,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/tagger#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------ | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of texts to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | Processed documents in the order of the original text. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Tagger.begin_training {#begin_training tag="method"} @@ -130,13 +130,13 @@ setting up the label scheme based on the data. > optimizer = tagger.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/tagger#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Tagger.predict {#predict tag="method"} @@ -150,10 +150,10 @@ modifying them. > scores = tagger.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## Tagger.set_annotations {#set_annotations tag="method"} @@ -167,10 +167,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > tagger.set_annotations([doc1, doc2], scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ------------------------------------------------ | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `Tagger.predict`. | +| Name | Description | +| -------- | ------------------------------------------------ | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `Tagger.predict`. | ## Tagger.update {#update tag="method"} @@ -187,15 +187,15 @@ Delegates to [`predict`](/api/tagger#predict) and > losses = tagger.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/tagger#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. The value keyed by the model's name is updated. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Tagger.rehearse {#rehearse tag="method,experimental" new="3"} @@ -211,14 +211,14 @@ the "catastrophic forgetting" problem. This feature is experimental. > losses = tagger.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Tagger.get_loss {#get_loss tag="method"} @@ -233,11 +233,11 @@ predicted scores. > loss, d_loss = tagger.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | - | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## Tagger.score {#score tag="method" new="3"} @@ -249,10 +249,10 @@ Score a batch of examples. > scores = tagger.score(examples) > ``` -| Name | Type | Description | -| ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -| `examples` | `Iterable[Example]` | The examples to score. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_token_attr`](/api/scorer#score_token_attr) for the attributes `"pos"`, `"tag"` and `"lemma"`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_token_attr`](/api/scorer#score_token_attr) for the attributes `"pos"`, `"tag"` and `"lemma"`. ~~Dict[str, float]~~ | ## Tagger.create_optimizer {#create_optimizer tag="method"} @@ -265,9 +265,9 @@ Create an optimizer for the pipeline component. > optimizer = tagger.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Tagger.use_params {#use_params tag="method, contextmanager"} @@ -282,9 +282,9 @@ context, the original parameters are restored. > tagger.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## Tagger.add_label {#add_label tag="method"} @@ -297,10 +297,10 @@ Add a new label to the pipe. > tagger.add_label("MY_LABEL") > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## Tagger.to_disk {#to_disk tag="method"} @@ -313,11 +313,11 @@ Serialize the pipe to disk. > tagger.to_disk("/path/to/tagger") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Tagger.from_disk {#from_disk tag="method"} @@ -330,12 +330,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > tagger.from_disk("/path/to/tagger") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tagger` | The modified `Tagger` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Tagger` object. ~~Tagger~~ | ## Tagger.to_bytes {#to_bytes tag="method"} @@ -348,11 +348,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Tagger` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Tagger` object. ~~bytes~~ | ## Tagger.from_bytes {#from_bytes tag="method"} @@ -366,12 +366,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > tagger.from_bytes(tagger_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tagger` | The `Tagger` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Tagger` object. ~~Tagger~~ | ## Tagger.labels {#labels tag="property"} @@ -384,9 +384,9 @@ The labels currently added to the component. > assert "MY_LABEL" in tagger.labels > ``` -| Name | Type | Description | -| ----------- | ------------ | ---------------------------------- | -| **RETURNS** | `Tuple[str]` | The labels added to the component. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The labels added to the component. ~~Tuple[str, ...]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/textcategorizer.md b/website/docs/api/textcategorizer.md index 73b50b865..927ac5411 100644 --- a/website/docs/api/textcategorizer.md +++ b/website/docs/api/textcategorizer.md @@ -35,10 +35,10 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("textcat", config=config) > ``` -| Setting | Type | Description | Default | -| -------- | ------------------------------------------ | --------------------------------------------------------------------------------------- | ----------------------------------------------------- | -| `labels` | `List[str]` | A list of categories to learn. If empty, the model infers the categories from the data. | `[]` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | A model instance that predicts scores for each category. | [TextCatEnsemble](/api/architectures#TextCatEnsemble) | +| Setting | Description | +| -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `labels` | A list of categories to learn. If empty, the model infers the categories from the data. Defaults to `[]`. ~~Iterable[str]~~ | +| `model` | A model instance that predicts scores for each category. Defaults to [TextCatEnsemble](/api/architectures#TextCatEnsemble). ~~Model[List[Doc], List[Floats2d]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/textcat.py @@ -65,13 +65,13 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#create_pipe). -| Name | Type | Description | -| -------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| _keyword-only_ | | | -| `labels` | `Iterable[str]` | The labels to use. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | +| `labels` | The labels to use. ~~Iterable[str]~~ | ## TextCategorizer.\_\_call\_\_ {#call tag="method"} @@ -91,10 +91,10 @@ delegate to the [`predict`](/api/textcategorizer#predict) and > processed = textcat(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## TextCategorizer.pipe {#pipe tag="method"} @@ -113,12 +113,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/textcategorizer#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------- | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of documents to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | The processed documents in order. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## TextCategorizer.begin_training {#begin_training tag="method"} @@ -138,13 +138,13 @@ setting up the label scheme based on the data. > optimizer = textcat.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/textcategorizer#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## TextCategorizer.predict {#predict tag="method"} @@ -158,10 +158,10 @@ modifying them. > scores = textcat.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## TextCategorizer.set_annotations {#set_annotations tag="method"} @@ -175,10 +175,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > textcat.set_annotations(docs, scores) > ``` -| Name | Type | Description | -| -------- | --------------- | --------------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `TextCategorizer.predict`. | +| Name | Description | +| -------- | --------------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `TextCategorizer.predict`. | ## TextCategorizer.update {#update tag="method"} @@ -195,15 +195,15 @@ Delegates to [`predict`](/api/textcategorizer#predict) and > losses = textcat.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/textcategorizer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## TextCategorizer.rehearse {#rehearse tag="method,experimental" new="3"} @@ -219,14 +219,14 @@ the "catastrophic forgetting" problem. This feature is experimental. > losses = textcat.rehearse(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------ | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## TextCategorizer.get_loss {#get_loss tag="method"} @@ -241,11 +241,11 @@ predicted scores. > loss, d_loss = textcat.get_loss(examples, scores) > ``` -| Name | Type | Description | -| ----------- | --------------------- | --------------------------------------------------- | -| `examples` | `Iterable[Example]` | The batch of examples. | -| `scores` | - | Scores representing the model's predictions. | -| **RETURNS** | `Tuple[float, float]` | The loss and the gradient, i.e. `(loss, gradient)`. | +| Name | Description | +| ----------- | --------------------------------------------------------------------------- | +| `examples` | The batch of examples. ~~Iterable[Example]~~ | +| `scores` | Scores representing the model's predictions. | +| **RETURNS** | The loss and the gradient, i.e. `(loss, gradient)`. ~~Tuple[float, float]~~ | ## TextCategorizer.score {#score tag="method" new="3"} @@ -257,12 +257,12 @@ Score a batch of examples. > scores = textcat.score(examples) > ``` -| Name | Type | Description | -| ---------------- | ------------------- | ---------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | The examples to score. | -| _keyword-only_ | | | -| `positive_label` | str | Optional positive label. | -| **RETURNS** | `Dict[str, Any]` | The scores, produced by [`Scorer.score_cats`](/api/scorer#score_cats). | +| Name | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------- | +| `examples` | The examples to score. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `positive_label` | Optional positive label. ~~Optional[str]~~ | +| **RETURNS** | The scores, produced by [`Scorer.score_cats`](/api/scorer#score_cats). ~~Dict[str, Union[float, Dict[str, float]]]~~ | ## TextCategorizer.create_optimizer {#create_optimizer tag="method"} @@ -275,25 +275,9 @@ Create an optimizer for the pipeline component. > optimizer = textcat.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | - -## TextCategorizer.add_label {#add_label tag="method"} - -Add a new label to the pipe. - -> #### Example -> -> ```python -> textcat = nlp.add_pipe("textcat") -> textcat.add_label("MY_LABEL") -> ``` - -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `label` | str | The label to add. | -| **RETURNS** | int | `0` if the label is already present, otherwise `1`. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## TextCategorizer.use_params {#use_params tag="method, contextmanager"} @@ -307,9 +291,25 @@ Modify the pipe's model, to use the given parameter values. > textcat.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | + +## TextCategorizer.add_label {#add_label tag="method"} + +Add a new label to the pipe. + +> #### Example +> +> ```python +> textcat = nlp.add_pipe("textcat") +> textcat.add_label("MY_LABEL") +> ``` + +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `label` | The label to add. ~~str~~ | +| **RETURNS** | `0` if the label is already present, otherwise `1`. ~~int~~ | ## TextCategorizer.to_disk {#to_disk tag="method"} @@ -322,11 +322,11 @@ Serialize the pipe to disk. > textcat.to_disk("/path/to/textcat") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## TextCategorizer.from_disk {#from_disk tag="method"} @@ -339,12 +339,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > textcat.from_disk("/path/to/textcat") > ``` -| Name | Type | Description | -| -------------- | ----------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `TextCategorizer` | The modified `TextCategorizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `TextCategorizer` object. ~~TextCategorizer~~ | ## TextCategorizer.to_bytes {#to_bytes tag="method"} @@ -357,11 +357,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `TextCategorizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `TextCategorizer` object. ~~bytes~~ | ## TextCategorizer.from_bytes {#from_bytes tag="method"} @@ -375,12 +375,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > textcat.from_bytes(textcat_bytes) > ``` -| Name | Type | Description | -| -------------- | ----------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `TextCategorizer` | The `TextCategorizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `TextCategorizer` object. ~~TextCategorizer~~ | ## TextCategorizer.labels {#labels tag="property"} @@ -393,9 +393,9 @@ The labels currently added to the component. > assert "MY_LABEL" in textcat.labels > ``` -| Name | Type | Description | -| ----------- | ----- | ---------------------------------- | -| **RETURNS** | tuple | The labels added to the component. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The labels added to the component. ~~Tuple[str, ...]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/tok2vec.md b/website/docs/api/tok2vec.md index 4c820c07c..833a50b33 100644 --- a/website/docs/api/tok2vec.md +++ b/website/docs/api/tok2vec.md @@ -40,9 +40,9 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("tok2vec", config=config) > ``` -| Setting | Type | Description | Default | -| ------- | ------------------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------- | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** `List[Floats2d]`. The model to use. | [HashEmbedCNN](/api/architectures#HashEmbedCNN) | +| Setting | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------ | +| `model` | The model to use. Defaults to [HashEmbedCNN](/api/architectures#HashEmbedCNN). ~~Model[List[Doc], List[Floats2d]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/tok2vec.py @@ -69,11 +69,11 @@ Create a new pipeline instance. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#create_pipe). -| Name | Type | Description | -| ------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | +| Name | Description | +| ------- | ------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model[List[Doc], List[Floats2d]~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | ## Tok2Vec.\_\_call\_\_ {#call tag="method"} @@ -95,10 +95,10 @@ pipeline components are applied to the `Doc` in order. Both > processed = tok2vec(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Tok2Vec.pipe {#pipe tag="method"} @@ -116,12 +116,12 @@ and [`set_annotations`](/api/tok2vec#set_annotations) methods. > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------- | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of documents to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | The processed documents in order. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Tok2Vec.begin_training {#begin_training tag="method"} @@ -141,13 +141,13 @@ setting up the label scheme based on the data. > optimizer = tok2vec.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/tok2vec#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Tok2Vec.predict {#predict tag="method"} @@ -161,10 +161,10 @@ modifying them. > scores = tok2vec.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## Tok2Vec.set_annotations {#set_annotations tag="method"} @@ -178,10 +178,10 @@ Modify a batch of [`Doc`](/api/doc) objects, using pre-computed scores. > tok2vec.set_annotations(docs, scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `Tok2Vec.predict`. | +| Name | Description | +| -------- | ------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `Tok2Vec.predict`. | ## Tok2Vec.update {#update tag="method"} @@ -197,15 +197,15 @@ Delegates to [`predict`](/api/tok2vec#predict). > losses = tok2vec.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects to learn from. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/tok2vec#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects to learn from. ~~Iterable[Example]~~ | +| _keyword-only_ | | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Tok2Vec.create_optimizer {#create_optimizer tag="method"} @@ -218,9 +218,9 @@ Create an optimizer for the pipeline component. > optimizer = tok2vec.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Tok2Vec.use_params {#use_params tag="method, contextmanager"} @@ -235,9 +235,9 @@ context, the original parameters are restored. > tok2vec.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## Tok2Vec.to_disk {#to_disk tag="method"} @@ -250,11 +250,11 @@ Serialize the pipe to disk. > tok2vec.to_disk("/path/to/tok2vec") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Tok2Vec.from_disk {#from_disk tag="method"} @@ -267,12 +267,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > tok2vec.from_disk("/path/to/tok2vec") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tok2Vec` | The modified `Tok2Vec` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Tok2Vec` object. ~~Tok2Vec~~ | ## Tok2Vec.to_bytes {#to_bytes tag="method"} @@ -285,11 +285,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Tok2Vec` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Tok2Vec` object. ~~bytes~~ | ## Tok2Vec.from_bytes {#from_bytes tag="method"} @@ -303,12 +303,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > tok2vec.from_bytes(tok2vec_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tok2Vec` | The `Tok2Vec` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Tok2Vec` object. ~~Tok2Vec~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/token.md b/website/docs/api/token.md index 6390ab975..4a8e6eba7 100644 --- a/website/docs/api/token.md +++ b/website/docs/api/token.md @@ -17,11 +17,11 @@ Construct a `Token` object. > assert token.text == "Give" > ``` -| Name | Type | Description | -| -------- | ------- | ------------------------------------------- | -| `vocab` | `Vocab` | A storage container for lexical types. | -| `doc` | `Doc` | The parent document. | -| `offset` | int | The index of the token within the document. | +| Name | Description | +| -------- | --------------------------------------------------- | +| `vocab` | A storage container for lexical types. ~~Vocab~~ | +| `doc` | The parent document. ~~Doc~~ | +| `offset` | The index of the token within the document. ~~int~~ | ## Token.\_\_len\_\_ {#len tag="method"} @@ -35,9 +35,9 @@ The number of unicode characters in the token, i.e. `token.text`. > assert len(token) == 4 > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------------------------------------- | -| **RETURNS** | int | The number of unicode characters in the token. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The number of unicode characters in the token. ~~int~~ | ## Token.set_extension {#set_extension tag="classmethod" new="2"} @@ -55,14 +55,14 @@ For details, see the documentation on > assert doc[3]._.is_fruit > ``` -| Name | Type | Description | -| --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `token._.my_attr`. | -| `default` | - | Optional default value of the attribute if no getter or method is defined. | -| `method` | callable | Set a custom method on the object, for example `token._.compare(other_token)`. | -| `getter` | callable | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. | -| `setter` | callable | Setter function that takes the `Token` and a value, and modifies the object. Is called when the user writes to the `Token._` attribute. | -| `force` | bool | Force overwriting existing attribute. | +| Name | Description | +| --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the attribute to set by the extension. For example, `"my_attr"` will be available as `token._.my_attr`. ~~str~~ | +| `default` | Optional default value of the attribute if no getter or method is defined. ~~Optional[Any]~~ | +| `method` | Set a custom method on the object, for example `token._.compare(other_token)`. ~~Optional[Callable[[Token, ...], Any]]~~ | +| `getter` | Getter function that takes the object and returns an attribute value. Is called when the user accesses the `._` attribute. ~~Optional[Callable[[Token], Any]]~~ | +| `setter` | Setter function that takes the `Token` and a value, and modifies the object. Is called when the user writes to the `Token._` attribute. ~~Optional[Callable[[Token, Any], None]]~~ | +| `force` | Force overwriting existing attribute. ~~bool~~ | ## Token.get_extension {#get_extension tag="classmethod" new="2"} @@ -79,10 +79,10 @@ Look up a previously registered extension by name. Returns a 4-tuple > assert extension == (False, None, None, None) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the extension. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Token.has_extension {#has_extension tag="classmethod" new="2"} @@ -96,10 +96,10 @@ Check whether an extension has been registered on the `Token` class. > assert Token.has_extension("is_fruit") > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------ | -| `name` | str | Name of the extension to check. | -| **RETURNS** | bool | Whether the extension has been registered. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| `name` | Name of the extension to check. ~~str~~ | +| **RETURNS** | Whether the extension has been registered. ~~bool~~ | ## Token.remove_extension {#remove_extension tag="classmethod" new=""2.0.11""} @@ -114,10 +114,10 @@ Remove a previously registered extension. > assert not Token.has_extension("is_fruit") > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------------------------------------- | -| `name` | str | Name of the extension. | -| **RETURNS** | tuple | A `(default, method, getter, setter)` tuple of the removed extension. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Name of the extension. ~~str~~ | +| **RETURNS** | A `(default, method, getter, setter)` tuple of the removed extension. ~~Tuple[Optional[Any], Optional[Callable], Optional[Callable], Optional[Callable]]~~ | ## Token.check_flag {#check_flag tag="method"} @@ -132,10 +132,10 @@ Check the value of a boolean flag. > assert token.check_flag(IS_TITLE) == True > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------- | -| `flag_id` | int | The attribute ID of the flag to check. | -| **RETURNS** | bool | Whether the flag is set. | +| Name | Description | +| ----------- | ---------------------------------------------- | +| `flag_id` | The attribute ID of the flag to check. ~~int~~ | +| **RETURNS** | Whether the flag is set. ~~bool~~ | ## Token.similarity {#similarity tag="method" model="vectors"} @@ -150,10 +150,10 @@ Compute a semantic similarity estimate. Defaults to cosine over vectors. > assert apples_oranges == oranges_apples > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------------------------------------- | -| other | - | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. | -| **RETURNS** | float | A scalar similarity score. Higher is more similar. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------- | +| other | The object to compare with. By default, accepts `Doc`, `Span`, `Token` and `Lexeme` objects. ~~Union[Doc, Span, Token, Lexeme]~~ | +| **RETURNS** | A scalar similarity score. Higher is more similar. ~~float~~ | ## Token.nbor {#nbor tag="method"} @@ -167,10 +167,10 @@ Get a neighboring token. > assert give_nbor.text == "it" > ``` -| Name | Type | Description | -| ----------- | ------- | ----------------------------------------------------------- | -| `i` | int | The relative position of the token to get. Defaults to `1`. | -| **RETURNS** | `Token` | The token at position `self.doc[self.i+i]`. | +| Name | Description | +| ----------- | ------------------------------------------------------------------- | +| `i` | The relative position of the token to get. Defaults to `1`. ~~int~~ | +| **RETURNS** | The token at position `self.doc[self.i+i]`. ~~Token~~ | ## Token.is_ancestor {#is_ancestor tag="method" model="parser"} @@ -186,10 +186,10 @@ dependency tree. > assert give.is_ancestor(it) > ``` -| Name | Type | Description | -| ----------- | ------- | ----------------------------------------------------- | -| descendant | `Token` | Another token. | -| **RETURNS** | bool | Whether this token is the ancestor of the descendant. | +| Name | Description | +| ----------- | -------------------------------------------------------------- | +| descendant | Another token. ~~Token~~ | +| **RETURNS** | Whether this token is the ancestor of the descendant. ~~bool~~ | ## Token.ancestors {#ancestors tag="property" model="parser"} @@ -205,9 +205,9 @@ The rightmost token of this token's syntactic descendants. > assert [t.text for t in he_ancestors] == ["pleaded"] > ``` -| Name | Type | Description | -| ---------- | ------- | --------------------------------------------------------------------- | -| **YIELDS** | `Token` | A sequence of ancestor tokens such that `ancestor.is_ancestor(self)`. | +| Name | Description | +| ---------- | ------------------------------------------------------------------------------- | +| **YIELDS** | A sequence of ancestor tokens such that `ancestor.is_ancestor(self)`. ~~Token~~ | ## Token.conjuncts {#conjuncts tag="property" model="parser"} @@ -221,9 +221,9 @@ A tuple of coordinated tokens, not including the token itself. > assert [t.text for t in apples_conjuncts] == ["oranges"] > ``` -| Name | Type | Description | -| ----------- | ------- | ----------------------- | -| **RETURNS** | `tuple` | The coordinated tokens. | +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | The coordinated tokens. ~~Tuple[Token, ...]~~ | ## Token.children {#children tag="property" model="parser"} @@ -237,9 +237,9 @@ A sequence of the token's immediate syntactic children. > assert [t.text for t in give_children] == ["it", "back", "!"] > ``` -| Name | Type | Description | -| ---------- | ------- | ------------------------------------------- | -| **YIELDS** | `Token` | A child token such that `child.head==self`. | +| Name | Description | +| ---------- | ------------------------------------------------------- | +| **YIELDS** | A child token such that `child.head == self`. ~~Token~~ | ## Token.lefts {#lefts tag="property" model="parser"} @@ -253,9 +253,9 @@ The leftward immediate children of the word, in the syntactic dependency parse. > assert lefts == ["New"] > ``` -| Name | Type | Description | -| ---------- | ------- | -------------------------- | -| **YIELDS** | `Token` | A left-child of the token. | +| Name | Description | +| ---------- | ------------------------------------ | +| **YIELDS** | A left-child of the token. ~~Token~~ | ## Token.rights {#rights tag="property" model="parser"} @@ -269,9 +269,9 @@ The rightward immediate children of the word, in the syntactic dependency parse. > assert rights == ["in"] > ``` -| Name | Type | Description | -| ---------- | ------- | --------------------------- | -| **YIELDS** | `Token` | A right-child of the token. | +| Name | Description | +| ---------- | ------------------------------------- | +| **YIELDS** | A right-child of the token. ~~Token~~ | ## Token.n_lefts {#n_lefts tag="property" model="parser"} @@ -285,9 +285,9 @@ dependency parse. > assert doc[3].n_lefts == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------- | -| **RETURNS** | int | The number of left-child tokens. | +| Name | Description | +| ----------- | ---------------------------------------- | +| **RETURNS** | The number of left-child tokens. ~~int~~ | ## Token.n_rights {#n_rights tag="property" model="parser"} @@ -301,9 +301,9 @@ dependency parse. > assert doc[3].n_rights == 1 > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------- | -| **RETURNS** | int | The number of right-child tokens. | +| Name | Description | +| ----------- | ----------------------------------------- | +| **RETURNS** | The number of right-child tokens. ~~int~~ | ## Token.subtree {#subtree tag="property" model="parser"} @@ -317,9 +317,9 @@ A sequence containing the token and all the token's syntactic descendants. > assert [t.text for t in give_subtree] == ["Give", "it", "back", "!"] > ``` -| Name | Type | Description | -| ---------- | ------- | -------------------------------------------------------------------------- | -| **YIELDS** | `Token` | A descendant token such that `self.is_ancestor(token)` or `token == self`. | +| Name | Description | +| ---------- | ------------------------------------------------------------------------------------ | +| **YIELDS** | A descendant token such that `self.is_ancestor(token)` or `token == self`. ~~Token~~ | ## Token.is_sent_start {#is_sent_start tag="property" new="2"} @@ -334,9 +334,9 @@ unknown. Defaults to `True` for the first token in the `Doc`. > assert not doc[5].is_sent_start > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------ | -| **RETURNS** | bool | Whether the token starts a sentence. | +| Name | Description | +| ----------- | --------------------------------------------- | +| **RETURNS** | Whether the token starts a sentence. ~~bool~~ | ## Token.has_vector {#has_vector tag="property" model="vectors"} @@ -350,9 +350,9 @@ A boolean value indicating whether a word vector is associated with the token. > assert apples.has_vector > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------- | -| **RETURNS** | bool | Whether the token has a vector data attached. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | Whether the token has a vector data attached. ~~bool~~ | ## Token.vector {#vector tag="property" model="vectors"} @@ -367,9 +367,9 @@ A real-valued meaning representation. > assert apples.vector.shape == (300,) > ``` -| Name | Type | Description | -| ----------- | ---------------------------------------- | ---------------------------------------------------- | -| **RETURNS** | `numpy.ndarray[ndim=1, dtype="float32"]` | A 1D numpy array representing the token's semantics. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| **RETURNS** | A 1-dimensional array representing the token's vector. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Token.vector_norm {#vector_norm tag="property" model="vectors"} @@ -386,80 +386,80 @@ The L2 norm of the token's vector representation. > assert apples.vector_norm != pasta.vector_norm > ``` -| Name | Type | Description | -| ----------- | ----- | ----------------------------------------- | -| **RETURNS** | float | The L2 norm of the vector representation. | +| Name | Description | +| ----------- | --------------------------------------------------- | +| **RETURNS** | The L2 norm of the vector representation. ~~float~~ | ## Attributes {#attributes} -| Name | Type | Description | -| -------------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The parent document. | -| `lex` 3 | [`Lexeme`](/api/lexeme) | The underlying lexeme. | -| `sent` 2.0.12 | [`Span`](/api/span) | The sentence span that this token is a part of. | -| `text` | str | Verbatim text content. | -| `text_with_ws` | str | Text content, with trailing space character if present. | -| `whitespace_` | str | Trailing space character if present. | -| `orth` | int | ID of the verbatim text content. | -| `orth_` | str | Verbatim text content (identical to `Token.text`). Exists mostly for consistency with the other attributes. | -| `vocab` | `Vocab` | The vocab object of the parent `Doc`. | -| `tensor` 2.1.7 | `ndarray` | The tokens's slice of the parent `Doc`'s tensor. | -| `head` | `Token` | The syntactic parent, or "governor", of this token. | -| `left_edge` | `Token` | The leftmost token of this token's syntactic descendants. | -| `right_edge` | `Token` | The rightmost token of this token's syntactic descendants. | -| `i` | int | The index of the token within the parent document. | -| `ent_type` | int | Named entity type. | -| `ent_type_` | str | Named entity type. | -| `ent_iob` | int | IOB code of named entity tag. `3` means the token begins an entity, `2` means it is outside an entity, `1` means it is inside an entity, and `0` means no entity tag is set. | -| `ent_iob_` | str | IOB code of named entity tag. "B" means the token begins an entity, "I" means it is inside an entity, "O" means it is outside an entity, and "" means no entity tag is set. | -| `ent_kb_id` 2.2 | int | Knowledge base ID that refers to the named entity this token is a part of, if any. | -| `ent_kb_id_` 2.2 | str | Knowledge base ID that refers to the named entity this token is a part of, if any. | -| `ent_id` | int | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. | -| `ent_id_` | str | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. | -| `lemma` | int | Base form of the token, with no inflectional suffixes. | -| `lemma_` | str | Base form of the token, with no inflectional suffixes. | -| `norm` | int | The token's norm, i.e. a normalized form of the token text. Usually set in the language's [tokenizer exceptions](/usage/adding-languages#tokenizer-exceptions) or [norm exceptions](/usage/adding-languages#norm-exceptions). | -| `norm_` | str | The token's norm, i.e. a normalized form of the token text. Usually set in the language's [tokenizer exceptions](/usage/adding-languages#tokenizer-exceptions) or [norm exceptions](/usage/adding-languages#norm-exceptions). | -| `lower` | int | Lowercase form of the token. | -| `lower_` | str | Lowercase form of the token text. Equivalent to `Token.text.lower()`. | -| `shape` | int | Transform of the tokens's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by `d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. | -| `shape_` | str | Transform of the tokens's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by `d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. | -| `prefix` | int | Hash value of a length-N substring from the start of the token. Defaults to `N=1`. | -| `prefix_` | str | A length-N substring from the start of the token. Defaults to `N=1`. | -| `suffix` | int | Hash value of a length-N substring from the end of the token. Defaults to `N=3`. | -| `suffix_` | str | Length-N substring from the end of the token. Defaults to `N=3`. | -| `is_alpha` | bool | Does the token consist of alphabetic characters? Equivalent to `token.text.isalpha()`. | -| `is_ascii` | bool | Does the token consist of ASCII characters? Equivalent to `all(ord(c) < 128 for c in token.text)`. | -| `is_digit` | bool | Does the token consist of digits? Equivalent to `token.text.isdigit()`. | -| `is_lower` | bool | Is the token in lowercase? Equivalent to `token.text.islower()`. | -| `is_upper` | bool | Is the token in uppercase? Equivalent to `token.text.isupper()`. | -| `is_title` | bool | Is the token in titlecase? Equivalent to `token.text.istitle()`. | -| `is_punct` | bool | Is the token punctuation? | -| `is_left_punct` | bool | Is the token a left punctuation mark, e.g. `"("` ? | -| `is_right_punct` | bool | Is the token a right punctuation mark, e.g. `")"` ? | -| `is_space` | bool | Does the token consist of whitespace characters? Equivalent to `token.text.isspace()`. | -| `is_bracket` | bool | Is the token a bracket? | -| `is_quote` | bool | Is the token a quotation mark? | -| `is_currency` 2.0.8 | bool | Is the token a currency symbol? | -| `like_url` | bool | Does the token resemble a URL? | -| `like_num` | bool | Does the token represent a number? e.g. "10.9", "10", "ten", etc. | -| `like_email` | bool | Does the token resemble an email address? | -| `is_oov` | bool | Does the token have a word vector? | -| `is_stop` | bool | Is the token part of a "stop list"? | -| `pos` | int | Coarse-grained part-of-speech from the [Universal POS tag set](https://universaldependencies.org/docs/u/pos/). | -| `pos_` | str | Coarse-grained part-of-speech from the [Universal POS tag set](https://universaldependencies.org/docs/u/pos/). | -| `tag` | int | Fine-grained part-of-speech. | -| `tag_` | str | Fine-grained part-of-speech. | -| `morph` | `MorphAnalysis` | Morphological analysis. | -| `morph_` | str | Morphological analysis in UD FEATS format. | -| `dep` | int | Syntactic dependency relation. | -| `dep_` | str | Syntactic dependency relation. | -| `lang` | int | Language of the parent document's vocabulary. | -| `lang_` | str | Language of the parent document's vocabulary. | -| `prob` | float | Smoothed log probability estimate of token's word type (context-independent entry in the vocabulary). | -| `idx` | int | The character offset of the token within the parent document. | -| `sentiment` | float | A scalar value indicating the positivity or negativity of the token. | -| `lex_id` | int | Sequential ID of the token's lexical type, used to index into tables, e.g. for word vectors. | -| `rank` | int | Sequential ID of the token's lexical type, used to index into tables, e.g. for word vectors. | -| `cluster` | int | Brown cluster ID. | -| `_` | `Underscore` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). | +| Name | Description | +| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `doc` | The parent document. ~~Doc~~ | +| `lex` 3 | The underlying lexeme. ~~Lexeme~~ | +| `sent` 2.0.12 | The sentence span that this token is a part of. ~~Span~~ | +| `text` | Verbatim text content. ~~str~~ | +| `text_with_ws` | Text content, with trailing space character if present. ~~str~~ | +| `whitespace_` | Trailing space character if present. ~~str~~ | +| `orth` | ID of the verbatim text content. ~~int~~ | +| `orth_` | Verbatim text content (identical to `Token.text`). Exists mostly for consistency with the other attributes. ~~str~~ | +| `vocab` | The vocab object of the parent `Doc`. ~~vocab~~ | +| `tensor` 2.1.7 | The tokens's slice of the parent `Doc`'s tensor. ~~numpy.ndarray~~ | +| `head` | The syntactic parent, or "governor", of this token. ~~Token~~ | +| `left_edge` | The leftmost token of this token's syntactic descendants. ~~Token~~ | +| `right_edge` | The rightmost token of this token's syntactic descendants. ~~Token~~ | +| `i` | The index of the token within the parent document. ~~int~~ | +| `ent_type` | Named entity type. ~~int~~ | +| `ent_type_` | Named entity type. ~~str~~ | +| `ent_iob` | IOB code of named entity tag. `3` means the token begins an entity, `2` means it is outside an entity, `1` means it is inside an entity, and `0` means no entity tag is set. ~~int~~ | +| `ent_iob_` | IOB code of named entity tag. "B" means the token begins an entity, "I" means it is inside an entity, "O" means it is outside an entity, and "" means no entity tag is set. ~~str~~ | +| `ent_kb_id` 2.2 | Knowledge base ID that refers to the named entity this token is a part of, if any. ~~int~~ | +| `ent_kb_id_` 2.2 | Knowledge base ID that refers to the named entity this token is a part of, if any. ~~str~~ | +| `ent_id` | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. ~~int~~ | +| `ent_id_` | ID of the entity the token is an instance of, if any. Currently not used, but potentially for coreference resolution. ~~str~~ | +| `lemma` | Base form of the token, with no inflectional suffixes. ~~int~~ | +| `lemma_` | Base form of the token, with no inflectional suffixes. ~~str~~ | +| `norm` | The token's norm, i.e. a normalized form of the token text. Can be set in the language's [tokenizer exceptions](/usage/adding-languages#tokenizer-exceptions). ~~int~~ | +| `norm_` | The token's norm, i.e. a normalized form of the token text. Can be set in the language's [tokenizer exceptions](/usage/adding-languages#tokenizer-exceptions). ~~str~~ | +| `lower` | Lowercase form of the token. ~~int~~ | +| `lower_` | Lowercase form of the token text. Equivalent to `Token.text.lower()`. ~~str~~ | +| `shape` | Transform of the tokens's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by `d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. ~~int~~ | +| `shape_` | Transform of the tokens's string, to show orthographic features. Alphabetic characters are replaced by `x` or `X`, and numeric characters are replaced by `d`, and sequences of the same character are truncated after length 4. For example,`"Xxxx"`or`"dd"`. ~~str~~ | +| `prefix` | Hash value of a length-N substring from the start of the token. Defaults to `N=1`. ~~int~~ | +| `prefix_` | A length-N substring from the start of the token. Defaults to `N=1`. ~~str~~ | +| `suffix` | Hash value of a length-N substring from the end of the token. Defaults to `N=3`. ~~int~~ | +| `suffix_` | Length-N substring from the end of the token. Defaults to `N=3`. ~~str~~ | +| `is_alpha` | Does the token consist of alphabetic characters? Equivalent to `token.text.isalpha()`. ~~bool~~ | +| `is_ascii` | Does the token consist of ASCII characters? Equivalent to `all(ord(c) < 128 for c in token.text)`. ~~bool~~ | +| `is_digit` | Does the token consist of digits? Equivalent to `token.text.isdigit()`. ~~bool~~ | +| `is_lower` | Is the token in lowercase? Equivalent to `token.text.islower()`. ~~bool~~ | +| `is_upper` | Is the token in uppercase? Equivalent to `token.text.isupper()`. ~~bool~~ | +| `is_title` | Is the token in titlecase? Equivalent to `token.text.istitle()`. ~~bool~~ | +| `is_punct` | Is the token punctuation? ~~bool~~ | +| `is_left_punct` | Is the token a left punctuation mark, e.g. `"("` ? ~~bool~~ | +| `is_right_punct` | Is the token a right punctuation mark, e.g. `")"` ? ~~bool~~ | +| `is_space` | Does the token consist of whitespace characters? Equivalent to `token.text.isspace()`. ~~bool~~ | +| `is_bracket` | Is the token a bracket? ~~bool~~ | +| `is_quote` | Is the token a quotation mark? ~~bool~~ | +| `is_currency` 2.0.8 | Is the token a currency symbol? ~~bool~~ | +| `like_url` | Does the token resemble a URL? ~~bool~~ | +| `like_num` | Does the token represent a number? e.g. "10.9", "10", "ten", etc. ~~bool~~ | +| `like_email` | Does the token resemble an email address? ~~bool~~ | +| `is_oov` | Does the token have a word vector? ~~bool~~ | +| `is_stop` | Is the token part of a "stop list"? ~~bool~~ | +| `pos` | Coarse-grained part-of-speech from the [Universal POS tag set](https://universaldependencies.org/docs/u/pos/). ~~int~~ | +| `pos_` | Coarse-grained part-of-speech from the [Universal POS tag set](https://universaldependencies.org/docs/u/pos/). ~~str~~ | +| `tag` | Fine-grained part-of-speech. ~~int~~ | +| `tag_` | Fine-grained part-of-speech. ~~str~~ | +| `morph` | Morphological analysis. ~~MorphAnalysis~~ | +| `morph_` | Morphological analysis in the Universal Dependencies [FEATS]https://universaldependencies.org/format.html#morphological-annotation format. ~~str~~ | +| `dep` | Syntactic dependency relation. ~~int~~ | +| `dep_` | Syntactic dependency relation. ~~str~~ | +| `lang` | Language of the parent document's vocabulary. ~~int~~ | +| `lang_` | Language of the parent document's vocabulary. ~~str~~ | +| `prob` | Smoothed log probability estimate of token's word type (context-independent entry in the vocabulary). ~~float~~ | +| `idx` | The character offset of the token within the parent document. ~~int~~ | +| `sentiment` | A scalar value indicating the positivity or negativity of the token. ~~float~~ | +| `lex_id` | Sequential ID of the token's lexical type, used to index into tables, e.g. for word vectors. ~~int~~ | +| `rank` | Sequential ID of the token's lexical type, used to index into tables, e.g. for word vectors. ~~int~~ | +| `cluster` | Brown cluster ID. ~~int~~ | +| `_` | User space for adding custom [attribute extensions](/usage/processing-pipelines#custom-components-attributes). ~~Underscore~~ | diff --git a/website/docs/api/tokenizer.md b/website/docs/api/tokenizer.md index 12edf0033..0158c5589 100644 --- a/website/docs/api/tokenizer.md +++ b/website/docs/api/tokenizer.md @@ -45,15 +45,15 @@ the > tokenizer = nlp.tokenizer > ``` -| Name | Type | Description | -| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------ | -| `vocab` | `Vocab` | A storage container for lexical types. | -| `rules` | dict | Exceptions and special-cases for the tokenizer. | -| `prefix_search` | callable | A function matching the signature of `re.compile(string).search` to match prefixes. | -| `suffix_search` | callable | A function matching the signature of `re.compile(string).search` to match suffixes. | -| `infix_finditer` | callable | A function matching the signature of `re.compile(string).finditer` to find infixes. | -| `token_match` | callable | A function matching the signature of `re.compile(string).match` to find token matches. | -| `url_match` | callable | A function matching the signature of `re.compile(string).match` to find token matches after considering prefixes and suffixes. | +| Name | Description | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | A storage container for lexical types. ~~Vocab~~ | +| `rules` | Exceptions and special-cases for the tokenizer. ~~Optional[Dict[str, List[Dict[int, str]]]]~~ | +| `prefix_search` | A function matching the signature of `re.compile(string).search` to match prefixes. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `suffix_search` | A function matching the signature of `re.compile(string).search` to match suffixes. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `infix_finditer` | A function matching the signature of `re.compile(string).finditer` to find infixes. ~~Optional[Callable[[str], Iterator[Match]]]~~ | +| `token_match` | A function matching the signature of `re.compile(string).match` to find token matches. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `url_match` | A function matching the signature of `re.compile(string).match` to find token matches after considering prefixes and suffixes. ~~Optional[Callable[[str], Optional[Match]]]~~ | ## Tokenizer.\_\_call\_\_ {#call tag="method"} @@ -66,10 +66,10 @@ Tokenize a string. > assert len(tokens) == 4 > ``` -| Name | Type | Description | -| ----------- | ----- | --------------------------------------- | -| `string` | str | The string to tokenize. | -| **RETURNS** | `Doc` | A container for linguistic annotations. | +| Name | Description | +| ----------- | ----------------------------------------------- | +| `string` | The string to tokenize. ~~str~~ | +| **RETURNS** | A container for linguistic annotations. ~~Doc~~ | ## Tokenizer.pipe {#pipe tag="method"} @@ -83,40 +83,40 @@ Tokenize a stream of texts. > pass > ``` -| Name | Type | Description | -| ------------ | ----- | ---------------------------------------------------------------------------- | -| `texts` | - | A sequence of unicode texts. | -| `batch_size` | int | The number of texts to accumulate in an internal buffer. Defaults to `1000`. | -| **YIELDS** | `Doc` | A sequence of Doc objects, in order. | +| Name | Description | +| ------------ | ------------------------------------------------------------------------------------ | +| `texts` | A sequence of unicode texts. ~~Iterable[str]~~ | +| `batch_size` | The number of texts to accumulate in an internal buffer. Defaults to `1000`. ~~int~~ | +| **YIELDS** | The tokenized Doc objects, in order. ~~Doc~~ | ## Tokenizer.find_infix {#find_infix tag="method"} Find internal split points of the string. -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `string` | str | The string to split. | -| **RETURNS** | list | A list of `re.MatchObject` objects that have `.start()` and `.end()` methods, denoting the placement of internal segment separators, e.g. hyphens. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `string` | The string to split. ~~str~~ | +| **RETURNS** | A list of `re.MatchObject` objects that have `.start()` and `.end()` methods, denoting the placement of internal segment separators, e.g. hyphens. ~~List[Match]~~ | ## Tokenizer.find_prefix {#find_prefix tag="method"} Find the length of a prefix that should be segmented from the string, or `None` if no prefix rules match. -| Name | Type | Description | -| ----------- | ---- | ------------------------------------------------------ | -| `string` | str | The string to segment. | -| **RETURNS** | int | The length of the prefix if present, otherwise `None`. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------ | +| `string` | The string to segment. ~~str~~ | +| **RETURNS** | The length of the prefix if present, otherwise `None`. ~~Optional[int]~~ | ## Tokenizer.find_suffix {#find_suffix tag="method"} Find the length of a suffix that should be segmented from the string, or `None` if no suffix rules match. -| Name | Type | Description | -| ----------- | ------------ | ------------------------------------------------------ | -| `string` | str | The string to segment. | -| **RETURNS** | int / `None` | The length of the suffix if present, otherwise `None`. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------ | +| `string` | The string to segment. ~~str~~ | +| **RETURNS** | The length of the suffix if present, otherwise `None`. ~~Optional[int]~~ | ## Tokenizer.add_special_case {#add_special_case tag="method"} @@ -134,10 +134,10 @@ and examples. > tokenizer.add_special_case("don't", case) > ``` -| Name | Type | Description | -| ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `string` | str | The string to specially tokenize. | -| `token_attrs` | iterable | A sequence of dicts, where each dict describes a token and its attributes. The `ORTH` fields of the attributes must exactly match the string when they are concatenated. | +| Name | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `string` | The string to specially tokenize. ~~str~~ | +| `token_attrs` | A sequence of dicts, where each dict describes a token and its attributes. The `ORTH` fields of the attributes must exactly match the string when they are concatenated. ~~Iterable[Dict[int, str]]~~ | ## Tokenizer.explain {#explain tag="method"} @@ -153,10 +153,10 @@ produced are identical to `Tokenizer.__call__` except for whitespace tokens. > assert [t[1] for t in tok_exp] == ["(", "do", "n't", ")"] > ``` -| Name | Type | Description | -| ----------- | ---- | --------------------------------------------------- | -| `string` | str | The string to tokenize with the debugging tokenizer | -| **RETURNS** | list | A list of `(pattern_string, token_string)` tuples | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------- | +| `string` | The string to tokenize with the debugging tokenizer. ~~str~~ | +| **RETURNS** | A list of `(pattern_string, token_string)` tuples. ~~List[Tuple[str, str]]~~ | ## Tokenizer.to_disk {#to_disk tag="method"} @@ -169,11 +169,11 @@ Serialize the tokenizer to disk. > tokenizer.to_disk("/path/to/tokenizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Tokenizer.from_disk {#from_disk tag="method"} @@ -186,12 +186,12 @@ Load the tokenizer from disk. Modifies the object in place and returns it. > tokenizer.from_disk("/path/to/tokenizer") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tokenizer` | The modified `Tokenizer` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Tokenizer` object. ~~Tokenizer~~ | ## Tokenizer.to_bytes {#to_bytes tag="method"} @@ -204,11 +204,11 @@ Load the tokenizer from disk. Modifies the object in place and returns it. Serialize the tokenizer to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Tokenizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Tokenizer` object. ~~bytes~~ | ## Tokenizer.from_bytes {#from_bytes tag="method"} @@ -223,23 +223,23 @@ it. > tokenizer.from_bytes(tokenizer_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tokenizer` | The `Tokenizer` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Tokenizer` object. ~~Tokenizer~~ | ## Attributes {#attributes} -| Name | Type | Description | -| ---------------- | ------- | -------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The vocab object of the parent `Doc`. | -| `prefix_search` | - | A function to find segment boundaries from the start of a string. Returns the length of the segment, or `None`. | -| `suffix_search` | - | A function to find segment boundaries from the end of a string. Returns the length of the segment, or `None`. | -| `infix_finditer` | - | A function to find internal segment separators, e.g. hyphens. Returns a (possibly empty) list of `re.MatchObject` objects. | -| `token_match` | - | A function matching the signature of `re.compile(string).match to find token matches. Returns an`re.MatchObject`or`None. | -| `rules` | dict | A dictionary of tokenizer exceptions and special cases. | +| Name | Description | +| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The vocab object of the parent `Doc`. ~~Vocab~~ | +| `prefix_search` | A function to find segment boundaries from the start of a string. Returns the length of the segment, or `None`. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `suffix_search` | A function to find segment boundaries from the end of a string. Returns the length of the segment, or `None`. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `infix_finditer` | A function to find internal segment separators, e.g. hyphens. Returns a (possibly empty) sequence of `re.MatchObject` objects. ~~Optional[Callable[[str], Iterator[Match]]]~~ | +| `token_match` | A function matching the signature of `re.compile(string).match` to find token matches. Returns an `re.MatchObject` or `None`. ~~Optional[Callable[[str], Optional[Match]]]~~ | +| `rules` | A dictionary of tokenizer exceptions and special cases. ~~Optional[Dict[str, List[Dict[int, str]]]]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 60885f246..3bce3db93 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -32,13 +32,13 @@ loaded in via [`Language.from_disk`](/api/language#from_disk). > nlp = spacy.load("en_core_web_sm", disable=["parser", "tagger"]) > ``` -| Name | Type | Description | -| ----------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | -| `name` | str / `Path` | Model to load, i.e. package name or path. | -| _keyword-only_ | | | -| `disable` | `List[str]` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). | -| `config` 3 | `Dict[str, Any]` / [`Config`](https://thinc.ai/docs/api-config#config) | Optional config overrides, either as nested dict or dict keyed by section value in dot notation, e.g. `"components.name.value"`. | -| **RETURNS** | `Language` | A `Language` object with the loaded model. | +| Name | Description | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `name` | Model to load, i.e. package name or path. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `disable` | Names of pipeline components to [disable](/usage/processing-pipelines#disabling). ~~List[str]~~ | +| `config` 3 | Optional config overrides, either as nested dict or dict keyed by section value in dot notation, e.g. `"components.name.value"`. ~~Union[Dict[str, Any], Config]~~ | +| **RETURNS** | A `Language` object with the loaded model. ~~Language~~ | Essentially, `spacy.load()` is a convenience wrapper that reads the language ID and pipeline components from a model's `meta.json`, initializes the `Language` @@ -65,10 +65,10 @@ Create a blank model of a given language class. This function is the twin of > nlp_de = spacy.blank("de") # equivalent to German() > ``` -| Name | Type | Description | -| ----------- | ---------- | ------------------------------------------------------------------------------------------------ | -| `name` | str | [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the language class to load. | -| **RETURNS** | `Language` | An empty `Language` object of the appropriate subclass. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------- | +| `name` | [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the language class to load. ~~str~~ | +| **RETURNS** | An empty `Language` object of the appropriate subclass. ~~Language~~ | #### spacy.info {#spacy.info tag="function"} @@ -85,12 +85,12 @@ meta data as a dictionary instead, you can use the `meta` attribute on your > markdown = spacy.info(markdown=True, silent=True) > ``` -| Name | Type | Description | -| -------------- | ---- | ------------------------------------------------ | -| `model` | str | A model, i.e. a package name or path (optional). | -| _keyword-only_ | | | -| `markdown` | bool | Print information as Markdown. | -| `silent` | bool | Don't print anything, just return. | +| Name | Description | +| -------------- | ------------------------------------------------------------------ | +| `model` | A model, i.e. a package name or path (optional). ~~Optional[str]~~ | +| _keyword-only_ | | +| `markdown` | Print information as Markdown. ~~bool~~ | +| `silent` | Don't print anything, just return. ~~bool~~ | ### spacy.explain {#spacy.explain tag="function"} @@ -111,10 +111,10 @@ list of available terms, see > # world NN noun, singular or mass > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------------- | -| `term` | str | Term to explain. | -| **RETURNS** | str | The explanation, or `None` if not found in the glossary. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------- | +| `term` | Term to explain. ~~str~~ | +| **RETURNS** | The explanation, or `None` if not found in the glossary. ~~Optional[str]~~ | ### spacy.prefer_gpu {#spacy.prefer_gpu tag="function" new="2.0.14"} @@ -131,9 +131,9 @@ models. > nlp = spacy.load("en_core_web_sm") > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------ | -| **RETURNS** | bool | Whether the GPU was activated. | +| Name | Description | +| ----------- | --------------------------------------- | +| **RETURNS** | Whether the GPU was activated. ~~bool~~ | ### spacy.require_gpu {#spacy.require_gpu tag="function" new="2.0.14"} @@ -150,9 +150,9 @@ and _before_ loading any models. > nlp = spacy.load("en_core_web_sm") > ``` -| Name | Type | Description | -| ----------- | ---- | ----------- | -| **RETURNS** | bool | `True` | +| Name | Description | +| ----------- | --------------- | +| **RETURNS** | `True` ~~bool~~ | ## displaCy {#displacy source="spacy/displacy"} @@ -175,16 +175,16 @@ browser. Will run a simple web server. > displacy.serve([doc1, doc2], style="dep") > ``` -| Name | Type | Description | Default | -| --------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------- | -| `docs` | list, `Doc`, `Span` | Document(s) to visualize. | -| `style` | str | Visualization style, `'dep'` or `'ent'`. | `'dep'` | -| `page` | bool | Render markup as full HTML page. | `True` | -| `minify` | bool | Minify HTML markup. | `False` | -| `options` | dict | [Visualizer-specific options](#displacy_options), e.g. colors. | `{}` | -| `manual` | bool | Don't parse `Doc` and instead, expect a dict or list of dicts. [See here](/usage/visualizers#manual-usage) for formats and examples. | `False` | -| `port` | int | Port to serve visualization. | `5000` | -| `host` | str | Host to serve visualization. | `'0.0.0.0'` | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `docs` | Document(s) or span(s) to visualize. ~~Union[Iterable[Union[Doc, Span]], Doc, Span]~~ | +| `style` | Visualization style, `"dep"` or `"ent"`. Defaults to `"dep"`. ~~str~~ | +| `page` | Render markup as full HTML page. Defaults to `True`. ~~bool~~ | +| `minify` | Minify HTML markup. Defaults to `False`. ~~bool~~ | +| `options` | [Visualizer-specific options](#displacy_options), e.g. colors. ~~Dict[str, Any]~~ | +| `manual` | Don't parse `Doc` and instead, expect a dict or list of dicts. [See here](/usage/visualizers#manual-usage) for formats and examples. Defaults to `False`. ~~bool~~ | +| `port` | Port to serve visualization. Defaults to `5000`. ~~int~~ | +| `host` | Host to serve visualization. Defaults to `"0.0.0.0"`. ~~str~~ | ### displacy.render {#displacy.render tag="method" new="2"} @@ -200,16 +200,16 @@ Render a dependency parse tree or named entity visualization. > html = displacy.render(doc, style="dep") > ``` -| Name | Type | Description | Default | -| ----------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -| `docs` | list, `Doc`, `Span` | Document(s) to visualize. | -| `style` | str | Visualization style, `'dep'` or `'ent'`. | `'dep'` | -| `page` | bool | Render markup as full HTML page. | `False` | -| `minify` | bool | Minify HTML markup. | `False` | -| `jupyter` | bool | Explicitly enable or disable "[Jupyter](http://jupyter.org/) mode" to return markup ready to be rendered in a notebook. Detected automatically if `None`. | `None` | -| `options` | dict | [Visualizer-specific options](#displacy_options), e.g. colors. | `{}` | -| `manual` | bool | Don't parse `Doc` and instead, expect a dict or list of dicts. [See here](/usage/visualizers#manual-usage) for formats and examples. | `False` | -| **RETURNS** | str | Rendered HTML markup. | +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `docs` | Document(s) or span(s) to visualize. ~~Union[Iterable[Union[Doc, Span]], Doc, Span]~~ | +| `style` | Visualization style, `"dep"` or `"ent"`. Defaults to `"dep"`. ~~str~~ | +| `page` | Render markup as full HTML page. Defaults to `True`. ~~bool~~ | +| `minify` | Minify HTML markup. Defaults to `False`. ~~bool~~ | +| `options` | [Visualizer-specific options](#displacy_options), e.g. colors. ~~Dict[str, Any]~~ | +| `manual` | Don't parse `Doc` and instead, expect a dict or list of dicts. [See here](/usage/visualizers#manual-usage) for formats and examples. Defaults to `False`. ~~bool~~ | +| `jupyter` | Explicitly enable or disable "[Jupyter](http://jupyter.org/) mode" to return markup ready to be rendered in a notebook. Detected automatically if `None` (default). ~~Optional[bool]~~ | +| **RETURNS** | The rendered HTML markup. ~~str~~ | ### Visualizer options {#displacy_options} @@ -225,22 +225,22 @@ If a setting is not present in the options, the default value will be used. > displacy.serve(doc, style="dep", options=options) > ``` -| Name | Type | Description | Default | -| ------------------------------------------ | ---- | --------------------------------------------------------------------------------------------------------------- | ----------------------- | -| `fine_grained` | bool | Use fine-grained part-of-speech tags (`Token.tag_`) instead of coarse-grained tags (`Token.pos_`). | `False` | -| `add_lemma` 2.2.4 | bool | Print the lemma's in a separate row below the token texts. | `False` | -| `collapse_punct` | bool | Attach punctuation to tokens. Can make the parse more readable, as it prevents long arcs to attach punctuation. | `True` | -| `collapse_phrases` | bool | Merge noun phrases into one token. | `False` | -| `compact` | bool | "Compact mode" with square arrows that takes up less space. | `False` | -| `color` | str | Text color (HEX, RGB or color names). | `'#000000'` | -| `bg` | str | Background color (HEX, RGB or color names). | `'#ffffff'` | -| `font` | str | Font name or font family for all text. | `'Arial'` | -| `offset_x` | int | Spacing on left side of the SVG in px. | `50` | -| `arrow_stroke` | int | Width of arrow path in px. | `2` | -| `arrow_width` | int | Width of arrow head in px. | `10` / `8` (compact) | -| `arrow_spacing` | int | Spacing between arrows in px to avoid overlaps. | `20` / `12` (compact) | -| `word_spacing` | int | Vertical spacing between words and arcs in px. | `45` | -| `distance` | int | Distance between words in px. | `175` / `150` (compact) | +| Name | Description | +| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | +| `fine_grained` | Use fine-grained part-of-speech tags (`Token.tag_`) instead of coarse-grained tags (`Token.pos_`). Defaults to `False`. ~~bool~~ | +| `add_lemma` 2.2.4 | Print the lemma's in a separate row below the token texts. Defaults to `False`. ~~bool~~ | +| `collapse_punct` | Attach punctuation to tokens. Can make the parse more readable, as it prevents long arcs to attach punctuation. Defaults to `True`. ~~bool~~ | +| `collapse_phrases` | Merge noun phrases into one token. Defaults to `False`. ~~bool~~ | +| `compact` | "Compact mode" with square arrows that takes up less space. Defaults to `False`. ~~bool~~ | +| `color` | Text color (HEX, RGB or color names). Defaults to `"#000000"`. ~~str~~ | +| `bg` | Background color (HEX, RGB or color names). Defaults to `"#ffffff"`. ~~str~~ | +| `font` | Font name or font family for all text. Defaults to `"Arial"`. ~~str~~ | +| `offset_x` | Spacing on left side of the SVG in px. Defaults to `50`. ~~int~~ | +| `arrow_stroke` | Width of arrow path in px. Defaults to `2`. ~~int~~ | +| `arrow_width` | Width of arrow head in px. Defaults to `10` in regular mode and `8` in compact mode. ~~int~~ | +| `arrow_spacing` | Spacing between arrows in px to avoid overlaps. Defaults to `20` in regular mode and `12` in compact mode. ~~int~~ | +| `word_spacing` | Vertical spacing between words and arcs in px. Defaults to `45`. ~~int~~ | +| `distance` | Distance between words in px. Defaults to `175` in regular mode and `150` in compact mode. ~~int~~ | #### Named Entity Visualizer options {#displacy_options-ent} @@ -252,11 +252,11 @@ If a setting is not present in the options, the default value will be used. > displacy.serve(doc, style="ent", options=options) > ``` -| Name | Type | Description | Default | -| --------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | -| `ents` | list | Entity types to highlight (`None` for all types). | `None` | -| `colors` | dict | Color overrides. Entity types in uppercase should be mapped to color names or values. | `{}` | -| `template` 2.2 | str | Optional template to overwrite the HTML used to render entity spans. Should be a format string and can use `{bg}`, `{text}` and `{label}`. | see [`templates.py`](https://github.com/explosion/spaCy/blob/master/spacy/displacy/templates.py) | +| Name | Description | +| --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ents` | Entity types to highlight or `None` for all types (default). ~~Optional[List[str]]~~ | +| `colors` | Color overrides. Entity types in uppercase should be mapped to color names or values. ~~Dict[str, str]~~ | +| `template` 2.2 | Optional template to overwrite the HTML used to render entity spans. Should be a format string and can use `{bg}`, `{text}` and `{label}`. See [`templates.py`](https://github.com/explosion/spaCy/blob/master/spacy/displacy/templates.py) for examples. ~~Optional[str]~~ | By default, displaCy comes with colors for all entity types used by [spaCy models](/models). If you're using custom entity types, you can use the @@ -359,13 +359,13 @@ themselves, or be discarded if `discard_oversize` is set to `True`. The argument > get_length = null > ``` -| Name | Type | Description | -| ------------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `seqs` | `Iterable[Any]` | The sequences to minibatch. | -| `size` | `Iterable[int]` / int | The target number of words per batch. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). | -| `tolerance` | float | What percentage of the size to allow batches to exceed. | -| `discard_oversize` | bool | Whether to discard sequences that by themselves exceed the tolerated size. | -| `get_length` | `Callable[[Any], int]` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. | +| Name | Description | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `seqs` | The sequences to minibatch. ~~Iterable[Any]~~ | +| `size` | The target number of words per batch. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). ~~Union[int, Sequence[int]]~~ | +| `tolerance` | What percentage of the size to allow batches to exceed. ~~float~~ | +| `discard_oversize` | Whether to discard sequences that by themselves exceed the tolerated size. ~~bool~~ | +| `get_length` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. ~~Optional[Callable[[Any], int]]~~ | #### batch_by_sequence.v1 {#batch_by_sequence tag="registered function"} @@ -380,10 +380,10 @@ themselves, or be discarded if `discard_oversize` is set to `True`. The argument Create a batcher that creates batches of the specified size. -| Name | Type | Description | -| ------------ | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `size` | `Iterable[int]` / int | The target number of items per batch. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). | -| `get_length` | `Callable[[Any], int]` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. | +| Name | Description | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `size` | The target number of items per batch. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). ~~Union[int, Sequence[int]]~~ | +| `get_length` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. ~~Optional[Callable[[Any], int]]~~ | #### batch_by_padded.v1 {#batch_by_padded tag="registered function"} @@ -403,12 +403,12 @@ sequences binned by length within a window. The padded size is defined as the maximum length of sequences within the batch multiplied by the number of sequences in the batch. -| Name | Type | Description | -| ------------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `size` | `Iterable[int]` / int | The largest padded size to batch sequences into. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). | -| `buffer` | int | The number of sequences to accumulate before sorting by length. A larger buffer will result in more even sizing, but if the buffer is very large, the iteration order will be less random, which can result in suboptimal training. | -| `discard_oversize` | bool | Whether to discard sequences that are by themselves longer than the largest padded batch size. | -| `get_length` | `Callable[[Any], int]` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. | +| Name | Description | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `size` | The largest padded size to batch sequences into. Can also be a block referencing a schedule, e.g. [`compounding`](https://thinc.ai/docs/api-schedules/#compounding). ~~Union[int, Sequence[int]]~~ | +| `buffer` | The number of sequences to accumulate before sorting by length. A larger buffer will result in more even sizing, but if the buffer is very large, the iteration order will be less random, which can result in suboptimal training. ~~int~~ | +| `discard_oversize` | Whether to discard sequences that are by themselves longer than the largest padded batch size. ~~bool~~ | +| `get_length` | Optional function that receives a sequence item and returns its length. Defaults to the built-in `len()` if not set. ~~Optional[Callable[[Any], int]]~~ | ## Training data and alignment {#gold source="spacy/gold"} @@ -436,11 +436,11 @@ single-token entity. > assert tags == ["O", "O", "U-LOC", "O"] > ``` -| Name | Type | Description | -| ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The document that the entity offsets refer to. The output tags will refer to the token boundaries within the document. | -| `entities` | iterable | A sequence of `(start, end, label)` triples. `start` and `end` should be character-offset integers denoting the slice into the original string. | -| **RETURNS** | list | str strings, describing the [BILUO](/usage/linguistic-features#accessing-ner) tags. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `doc` | The document that the entity offsets refer to. The output tags will refer to the token boundaries within the document. ~~Doc~~ | +| `entities` | A sequence of `(start, end, label)` triples. `start` and `end` should be character-offset integers denoting the slice into the original string. ~~List[Tuple[int, int, Union[str, int]]]~~ | +| **RETURNS** | A list of strings, describing the [BILUO](/usage/linguistic-features#accessing-ner) tags. ~~List[str]~~ | ### gold.offsets_from_biluo_tags {#offsets_from_biluo_tags tag="function"} @@ -458,11 +458,11 @@ Encode per-token tags following the > assert entities == [(7, 13, "LOC")] > ``` -| Name | Type | Description | -| ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The document that the BILUO tags refer to. | -| `entities` | iterable | A sequence of [BILUO](/usage/linguistic-features#accessing-ner) tags with each tag describing one token. Each tag string will be of the form of either `""`, `"O"` or `"{action}-{label}"`, where action is one of `"B"`, `"I"`, `"L"`, `"U"`. | -| **RETURNS** | list | A sequence of `(start, end, label)` triples. `start` and `end` will be character-offset integers denoting the slice into the original string. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `doc` | The document that the BILUO tags refer to. ~~Doc~~ | +| `entities` | A sequence of [BILUO](/usage/linguistic-features#accessing-ner) tags with each tag describing one token. Each tag string will be of the form of either `""`, `"O"` or `"{action}-{label}"`, where action is one of `"B"`, `"I"`, `"L"`, `"U"`. ~~List[str]~~ | +| **RETURNS** | A sequence of `(start, end, label)` triples. `start` and `end` will be character-offset integers denoting the slice into the original string. ~~List[Tuple[int, int, str]]~~ | ### gold.spans_from_biluo_tags {#spans_from_biluo_tags tag="function" new="2.1"} @@ -481,11 +481,11 @@ token-based tags, e.g. to overwrite the `doc.ents`. > doc.ents = spans_from_biluo_tags(doc, tags) > ``` -| Name | Type | Description | -| ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `doc` | `Doc` | The document that the BILUO tags refer to. | -| `entities` | iterable | A sequence of [BILUO](/usage/linguistic-features#accessing-ner) tags with each tag describing one token. Each tag string will be of the form of either `""`, `"O"` or `"{action}-{label}"`, where action is one of `"B"`, `"I"`, `"L"`, `"U"`. | -| **RETURNS** | list | A sequence of `Span` objects with added entity labels. | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `doc` | The document that the BILUO tags refer to. ~~Doc~~ | +| `entities` | A sequence of [BILUO](/usage/linguistic-features#accessing-ner) tags with each tag describing one token. Each tag string will be of the form of either `""`, `"O"` or `"{action}-{label}"`, where action is one of `"B"`, `"I"`, `"L"`, `"U"`. ~~List[str]~~ | +| **RETURNS** | A sequence of `Span` objects with added entity labels. ~~List[Span]~~ | ## Utility functions {#util source="spacy/util.py"} @@ -504,7 +504,8 @@ depends on any of spaCy's utilities. Import and load a `Language` class. Allows lazy-loading [language data](/usage/adding-languages) and importing languages using the two-letter language code. To add a language code for a custom language class, -you can use the [`set_lang_class`](/api/top-level#util.set_lang_class) helper. +you can register it using the [`@registry.languages`](/api/top-level#registry) +decorator. > #### Example > @@ -514,36 +515,14 @@ you can use the [`set_lang_class`](/api/top-level#util.set_lang_class) helper. > lang = lang_class() > ``` -| Name | Type | Description | -| ----------- | ---------- | -------------------------------------- | -| `lang` | str | Two-letter language code, e.g. `'en'`. | -| **RETURNS** | `Language` | Language class. | - -### util.set_lang_class {#util.set_lang_class tag="function"} - -Set a custom `Language` class name that can be loaded via -[`get_lang_class`](/api/top-level#util.get_lang_class). If your model uses a -custom language, this is required so that spaCy can load the correct class from -the two-letter language code. - -> #### Example -> -> ```python -> from spacy.lang.xy import CustomLanguage -> -> util.set_lang_class('xy', CustomLanguage) -> lang_class = util.get_lang_class('xy') -> nlp = lang_class() -> ``` - -| Name | Type | Description | -| ------ | ---------- | -------------------------------------- | -| `name` | str | Two-letter language code, e.g. `'en'`. | -| `cls` | `Language` | The language class, e.g. `English`. | +| Name | Description | +| ----------- | ---------------------------------------------- | +| `lang` | Two-letter language code, e.g. `"en"`. ~~str~~ | +| **RETURNS** | The respective subclass. ~~Language~~ | ### util.lang_class_is_loaded {#util.lang_class_is_loaded tag="function" new="2.1"} -Check whether a `Language` class is already loaded. `Language` classes are +Check whether a `Language` subclass is already loaded. `Language` subclasses are loaded lazily, to avoid expensive setup code associated with the language data. > #### Example @@ -554,10 +533,10 @@ loaded lazily, to avoid expensive setup code associated with the language data. > assert util.lang_class_is_loaded("de") is False > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------- | -| `name` | str | Two-letter language code, e.g. `'en'`. | -| **RETURNS** | bool | Whether the class has been loaded. | +| Name | Description | +| ----------- | ---------------------------------------------- | +| `name` | Two-letter language code, e.g. `"en"`. ~~str~~ | +| **RETURNS** | Whether the class has been loaded. ~~bool~~ | ### util.load_model {#util.load_model tag="function" new="2"} @@ -566,7 +545,7 @@ will assume the model is a Python package and import and call its `load()` method. If called with a path, spaCy will assume it's a data directory, read the language and pipeline settings from the meta.json and initialize a `Language` class. The model data will then be loaded in via -[`Language.from_disk()`](/api/language#from_disk). +[`Language.from_disk`](/api/language#from_disk). > #### Example > @@ -576,31 +555,13 @@ class. The model data will then be loaded in via > nlp = util.load_model("/path/to/data") > ``` -| Name | Type | Description | -| ------------- | ---------- | -------------------------------------------------------- | -| `name` | str | Package name or model path. | -| `**overrides` | - | Specific overrides, like pipeline components to disable. | -| **RETURNS** | `Language` | `Language` class with the loaded model. | - -### util.load_model_from_path {#util.load_model_from_path tag="function" new="2"} - -Load a model from a data directory path. Creates the [`Language`](/api/language) -class and pipeline based on the directory's meta.json and then calls -[`from_disk()`](/api/language#from_disk) with the path. This function also makes -it easy to test a new model that you haven't packaged yet. - -> #### Example -> -> ```python -> nlp = load_model_from_path("/path/to/data") -> ``` - -| Name | Type | Description | -| ------------- | ---------- | ---------------------------------------------------------------------------------------------------- | -| `model_path` | str | Path to model data directory. | -| `meta` | dict | Model meta data. If `False`, spaCy will try to load the meta from a meta.json in the same directory. | -| `**overrides` | - | Specific overrides, like pipeline components to disable. | -| **RETURNS** | `Language` | `Language` class with the loaded model. | +| Name | Description | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `name` | Package name or model path. ~~str~~ | +| `vocab` 3 | Optional shared vocab to pass in on initialization. If `True` (default), a new `Vocab` object will be created. ~~Union[Vocab, bool]~~. | +| `disable` | Names of pipeline components to disable. ~~Iterable[str]~~ | +| `config` 3 | Config overrides as nested dict or flat dict keyed by section values in dot notation, e.g. `"nlp.pipeline"`. ~~Union[Dict[str, Any], Config]~~ | +| **RETURNS** | `Language` class with the loaded model. ~~Language~~ | ### util.load_model_from_init_py {#util.load_model_from_init_py tag="function" new="2"} @@ -616,11 +577,13 @@ A helper function to use in the `load()` method of a model package's > return load_model_from_init_py(__file__, **overrides) > ``` -| Name | Type | Description | -| ------------- | ---------- | -------------------------------------------------------- | -| `init_file` | str | Path to model's `__init__.py`, i.e. `__file__`. | -| `**overrides` | - | Specific overrides, like pipeline components to disable. | -| **RETURNS** | `Language` | `Language` class with the loaded model. | +| Name | Description | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `init_file` | Path to model's `__init__.py`, i.e. `__file__`. ~~Union[str, Path]~~ | +| `vocab` 3 | Optional shared vocab to pass in on initialization. If `True` (default), a new `Vocab` object will be created. ~~Union[Vocab, bool]~~. | +| `disable` | Names of pipeline components to disable. ~~Iterable[str]~~ | +| `config` 3 | Config overrides as nested dict or flat dict keyed by section values in dot notation, e.g. `"nlp.pipeline"`. ~~Union[Dict[str, Any], Config]~~ | +| **RETURNS** | `Language` class with the loaded model. ~~Language~~ | ### util.get_model_meta {#util.get_model_meta tag="function" new="2"} @@ -632,10 +595,10 @@ Get a model's meta.json from a directory path and validate its contents. > meta = util.get_model_meta("/path/to/model") > ``` -| Name | Type | Description | -| ----------- | ------------ | ------------------------ | -| `path` | str / `Path` | Path to model directory. | -| **RETURNS** | dict | The model's meta data. | +| Name | Description | +| ----------- | --------------------------------------------- | +| `path` | Path to model directory. ~~Union[str, Path]~~ | +| **RETURNS** | The model's meta data. ~~Dict[str, Any]~~ | ### util.is_package {#util.is_package tag="function"} @@ -649,10 +612,10 @@ Check if string maps to a package installed via pip. Mainly used to validate > util.is_package("xyz") # False > ``` -| Name | Type | Description | -| ----------- | ------ | -------------------------------------------- | -| `name` | str | Name of package. | -| **RETURNS** | `bool` | `True` if installed package, `False` if not. | +| Name | Description | +| ----------- | ----------------------------------------------------- | +| `name` | Name of package. ~~str~~ | +| **RETURNS** | `True` if installed package, `False` if not. ~~bool~~ | ### util.get_package_path {#util.get_package_path tag="function" new="2"} @@ -666,10 +629,10 @@ Get path to an installed package. Mainly used to resolve the location of > # /usr/lib/python3.6/site-packages/en_core_web_sm > ``` -| Name | Type | Description | -| -------------- | ------ | -------------------------------- | -| `package_name` | str | Name of installed package. | -| **RETURNS** | `Path` | Path to model package directory. | +| Name | Description | +| -------------- | ----------------------------------------- | +| `package_name` | Name of installed package. ~~str~~ | +| **RETURNS** | Path to model package directory. ~~Path~~ | ### util.is_in_jupyter {#util.is_in_jupyter tag="function" new="2"} @@ -686,9 +649,9 @@ detecting the IPython kernel. Mainly used for the > display(HTML(html)) > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------- | -| **RETURNS** | bool | `True` if in Jupyter, `False` if not. | +| Name | Description | +| ----------- | ---------------------------------------------- | +| **RETURNS** | `True` if in Jupyter, `False` if not. ~~bool~~ | ### util.compile_prefix_regex {#util.compile_prefix_regex tag="function"} @@ -702,10 +665,10 @@ Compile a sequence of prefix rules into a regex object. > nlp.tokenizer.prefix_search = prefix_regex.search > ``` -| Name | Type | Description | -| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `entries` | tuple | The prefix rules, e.g. [`lang.punctuation.TOKENIZER_PREFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). | -| **RETURNS** | [regex](https://docs.python.org/3/library/re.html#re-objects) | The regex object. to be used for [`Tokenizer.prefix_search`](/api/tokenizer#attributes). | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `entries` | The prefix rules, e.g. [`lang.punctuation.TOKENIZER_PREFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). ~~Iterable[Union[str, Pattern]]~~ | +| **RETURNS** | The regex object. to be used for [`Tokenizer.prefix_search`](/api/tokenizer#attributes). ~~Pattern~~ | ### util.compile_suffix_regex {#util.compile_suffix_regex tag="function"} @@ -719,10 +682,10 @@ Compile a sequence of suffix rules into a regex object. > nlp.tokenizer.suffix_search = suffix_regex.search > ``` -| Name | Type | Description | -| ----------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| `entries` | tuple | The suffix rules, e.g. [`lang.punctuation.TOKENIZER_SUFFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). | -| **RETURNS** | [regex](https://docs.python.org/3/library/re.html#re-objects) | The regex object. to be used for [`Tokenizer.suffix_search`](/api/tokenizer#attributes). | +| Name | Description | +| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `entries` | The suffix rules, e.g. [`lang.punctuation.TOKENIZER_SUFFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). ~~Iterable[Union[str, Pattern]]~~ | +| **RETURNS** | The regex object. to be used for [`Tokenizer.suffix_search`](/api/tokenizer#attributes). ~~Pattern~~ | ### util.compile_infix_regex {#util.compile_infix_regex tag="function"} @@ -736,10 +699,10 @@ Compile a sequence of infix rules into a regex object. > nlp.tokenizer.infix_finditer = infix_regex.finditer > ``` -| Name | Type | Description | -| ----------- | ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | -| `entries` | tuple | The infix rules, e.g. [`lang.punctuation.TOKENIZER_INFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). | -| **RETURNS** | [regex](https://docs.python.org/3/library/re.html#re-objects) | The regex object. to be used for [`Tokenizer.infix_finditer`](/api/tokenizer#attributes). | +| Name | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `entries` | The infix rules, e.g. [`lang.punctuation.TOKENIZER_INFIXES`](https://github.com/explosion/spaCy/tree/master/spacy/lang/punctuation.py). ~~Iterable[Union[str, Pattern]]~~ | +| **RETURNS** | The regex object. to be used for [`Tokenizer.infix_finditer`](/api/tokenizer#attributes). ~~Pattern~~ | ### util.minibatch {#util.minibatch tag="function" new="2"} @@ -754,11 +717,11 @@ vary on each step. > nlp.update(batch) > ``` -| Name | Type | Description | -| ---------- | -------------- | ---------------------- | -| `items` | iterable | The items to batch up. | -| `size` | int / iterable | The batch size(s). | -| **YIELDS** | list | The batches. | +| Name | Description | +| ---------- | ---------------------------------------- | +| `items` | The items to batch up. ~~Iterable[Any]~~ | +| `size` | int / iterable | The batch size(s). ~~Union[int, Sequence[int]]~~ | +| **YIELDS** | The batches. | ### util.filter_spans {#util.filter_spans tag="function" new="2.1.4"} @@ -776,17 +739,30 @@ of one entity) or when merging spans with > filtered = filter_spans(spans) > ``` -| Name | Type | Description | -| ----------- | -------- | -------------------- | -| `spans` | iterable | The spans to filter. | -| **RETURNS** | list | The filtered spans. | +| Name | Description | +| ----------- | --------------------------------------- | +| `spans` | The spans to filter. ~~Iterable[Span]~~ | +| **RETURNS** | The filtered spans. ~~List[Span]~~ | ### util.get_words_and_spaces {#get_words_and_spaces tag="function" new="3"} - +Given a list of words and a text, reconstruct the original tokens and return a +list of words and spaces that can be used to create a [`Doc`](/api/doc#init). +This can help recover destructive tokenization that didn't preserve any +whitespace information. -| Name | Type | Description | -| ----------- | ----- | ----------- | -| `words` | list | | -| `text` | str | | -| **RETURNS** | tuple | | +> #### Example +> +> ```python +> orig_words = ["Hey", ",", "what", "'s", "up", "?"] +> orig_text = "Hey, what's up?" +> words, spaces = get_words_and_spaces(orig_words, orig_text) +> # ['Hey', ',', 'what', "'s", 'up', '?'] +> # [False, True, False, True, False, False] +> ``` + +| Name | Description | +| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `words` | The list of words. ~~Iterable[str]~~ | +| `text` | The original text. ~~str~~ | +| **RETURNS** | A list of words and a list of boolean values indicating whether the word at this position is followed by a space. ~~Tuple[List[str], List[bool]]~~ | diff --git a/website/docs/api/transformer.md b/website/docs/api/transformer.md index 0e4b066ed..19cb4daa2 100644 --- a/website/docs/api/transformer.md +++ b/website/docs/api/transformer.md @@ -60,11 +60,11 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("transformer", config=DEFAULT_CONFIG) > ``` -| Setting | Type | Description | Default | -| ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- | -| `max_batch_items` | int | Maximum size of a padded batch. | `4096` | -| `annotation_setter` | Callable | Function that takes a batch of `Doc` objects and a [`FullTransformerBatch`](/api/transformer#fulltransformerbatch) and can set additional annotations on the `Doc`. The `Doc._.transformer_data` attribute is set prior to calling the callback. By default, no additional annotations are set. | `null_annotation_setter` | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** [`FullTransformerBatch`](/api/transformer#fulltransformerbatch). The Thinc [`Model`](https://thinc.ai/docs/api-model) wrapping the transformer. | [TransformerModel](/api/architectures#TransformerModel) | +| Setting | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `max_batch_items` | Maximum size of a padded batch. Defaults to `4096`. ~~int~~ | +| `annotation_setter` | Function that takes a batch of `Doc` objects and transformer outputs can set additional annotations on the `Doc`. The `Doc._.transformer_data` attribute is set prior to calling the callback. Defaults to `null_annotation_setter` (no additional annotations). ~~Callable[[List[Doc], FullTransformerBatch], None]~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) wrapping the transformer. Defaults to [TransformerModel](/api/architectures#TransformerModel). ~~Model[List[Doc], FullTransformerBatch]~~ | ```python https://github.com/explosion/spacy-transformers/blob/master/spacy_transformers/pipeline_component.py @@ -101,14 +101,14 @@ attribute. You can also provide a callback to set additional annotations. In your application, you would normally use a shortcut for this and instantiate the component using its string name and [`nlp.add_pipe`](/api/language#create_pipe). -| Name | Type | Description | -| ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `vocab` | `Vocab` | The shared vocabulary. | -| `model` | [`Model`](https://thinc.ai/docs/api-model) | **Input:** `List[Doc]`. **Output:** [`FullTransformerBatch`](/api/transformer#fulltransformerbatch). The Thinc [`Model`](https://thinc.ai/docs/api-model) wrapping the transformer. Usually you will want to use the [TransformerModel](/api/architectures#TransformerModel) layer for this. | -| `annotation_setter` | `Callable` | Function that takes a batch of `Doc` objects and a [`FullTransformerBatch`](/api/transformer#fulltransformerbatch) and can set additional annotations on the `Doc`. The `Doc._.transformer_data` attribute is set prior to calling the callback. By default, no additional annotations are set. | -| _keyword-only_ | | | -| `name` | str | String name of the component instance. Used to add entries to the `losses` during training. | -| `max_batch_items` | int | Maximum size of a padded batch. Defaults to `128*32`. | +| Name | Description | +| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The Thinc [`Model`](https://thinc.ai/docs/api-model) wrapping the transformer. Usually you will want to use the [TransformerModel](/api/architectures#TransformerModel) layer for this. ~~Model[List[Doc], FullTransformerBatch]~~ | +| `annotation_setter` | Function that takes a batch of `Doc` objects and transformer outputs can set additional annotations on the `Doc`. The `Doc._.transformer_data` attribute is set prior to calling the callback. By default, no annotations are set. ~~Callable[[List[Doc], FullTransformerBatch], None]~~ | +| _keyword-only_ | | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| `max_batch_items` | Maximum size of a padded batch. Defaults to `128*32`. ~~int~~ | ## Transformer.\_\_call\_\_ {#call tag="method"} @@ -128,10 +128,10 @@ to the [`predict`](/api/transformer#predict) and > processed = transformer(doc) > ``` -| Name | Type | Description | -| ----------- | ----- | ------------------------ | -| `doc` | `Doc` | The document to process. | -| **RETURNS** | `Doc` | The processed document. | +| Name | Description | +| ----------- | -------------------------------- | +| `doc` | The document to process. ~~Doc~~ | +| **RETURNS** | The processed document. ~~Doc~~ | ## Transformer.pipe {#pipe tag="method"} @@ -150,12 +150,12 @@ applied to the `Doc` in order. Both [`__call__`](/api/transformer#call) and > pass > ``` -| Name | Type | Description | -| -------------- | --------------- | ----------------------------------------------------- | -| `stream` | `Iterable[Doc]` | A stream of documents. | -| _keyword-only_ | | | -| `batch_size` | int | The number of documents to buffer. Defaults to `128`. | -| **YIELDS** | `Doc` | The processed documents in order. | +| Name | Description | +| -------------- | ------------------------------------------------------------- | +| `stream` | A stream of documents. ~~Iterable[Doc]~~ | +| _keyword-only_ | | +| `batch_size` | The number of documents to buffer. Defaults to `128`. ~~int~~ | +| **YIELDS** | The processed documents in order. ~~Doc~~ | ## Transformer.begin_training {#begin_training tag="method"} @@ -175,13 +175,13 @@ setting up the label scheme based on the data. > optimizer = trf.begin_training(lambda: [], pipeline=nlp.pipeline) > ``` -| Name | Type | Description | -| -------------- | --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| `get_examples` | `Callable[[], Iterable[Example]]` | Optional function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. | -| _keyword-only_ | | | -| `pipeline` | `List[Tuple[str, Callable]]` | Optional list of pipeline components that this component is part of. | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | An optional optimizer. Will be created via [`create_optimizer`](/api/transformer#create_optimizer) if not set. | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------- | +| `get_examples` | Function that returns gold-standard annotations in the form of [`Example`](/api/example) objects. ~~Callable[[], Iterable[Example]]~~ | +| _keyword-only_ | | +| `pipeline` | Optional list of pipeline components that this component is part of. ~~Optional[List[Tuple[str, Callable[[Doc], Doc]]]]~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Transformer.predict {#predict tag="method"} @@ -195,10 +195,10 @@ modifying them. > scores = trf.predict([doc1, doc2]) > ``` -| Name | Type | Description | -| ----------- | --------------- | ----------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to predict. | -| **RETURNS** | - | The model's prediction for each document. | +| Name | Description | +| ----------- | ------------------------------------------- | +| `docs` | The documents to predict. ~~Iterable[Doc]~~ | +| **RETURNS** | The model's prediction for each document. | ## Transformer.set_annotations {#set_annotations tag="method"} @@ -215,10 +215,10 @@ callback is then called, if provided. > trf.set_annotations(docs, scores) > ``` -| Name | Type | Description | -| -------- | --------------- | ----------------------------------------------------- | -| `docs` | `Iterable[Doc]` | The documents to modify. | -| `scores` | - | The scores to set, produced by `Transformer.predict`. | +| Name | Description | +| -------- | ----------------------------------------------------- | +| `docs` | The documents to modify. ~~Iterable[Doc]~~ | +| `scores` | The scores to set, produced by `Transformer.predict`. | ## Transformer.update {#update tag="method"} @@ -244,15 +244,15 @@ and call the optimizer, while the others simply increment the gradients. > losses = trf.update(examples, sgd=optimizer) > ``` -| Name | Type | Description | -| ----------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `examples` | `Iterable[Example]` | A batch of [`Example`](/api/example) objects. Only the [`Example.predicted`](/api/example#predicted) `Doc` object is used, the reference `Doc` is ignored. | -| _keyword-only_ | | | -| `drop` | float | The dropout rate. | -| `set_annotations` | bool | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](/api/transformer#set_annotations). | -| `sgd` | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | -| `losses` | `Dict[str, float]` | Optional record of the loss during training. Updated using the component name as the key. | -| **RETURNS** | `Dict[str, float]` | The updated `losses` dictionary. | +| Name | Description | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `examples` | A batch of [`Example`](/api/example) objects. Only the [`Example.predicted`](/api/example#predicted) `Doc` object is used, the reference `Doc` is ignored. ~~Iterable[Example]~~ | +| _keyword-only_ | | +| `drop` | The dropout rate. ~~float~~ | +| `set_annotations` | Whether or not to update the `Example` objects with the predictions, delegating to [`set_annotations`](#set_annotations). ~~bool~~ | +| `sgd` | An optimizer. Will be created via [`create_optimizer`](#create_optimizer) if not set. ~~Optional[Optimizer]~~ | +| `losses` | Optional record of the loss during training. Updated using the component name as the key. ~~Optional[Dict[str, float]]~~ | +| **RETURNS** | The updated `losses` dictionary. ~~Dict[str, float]~~ | ## Transformer.create_optimizer {#create_optimizer tag="method"} @@ -265,9 +265,9 @@ Create an optimizer for the pipeline component. > optimizer = trf.create_optimizer() > ``` -| Name | Type | Description | -| ----------- | --------------------------------------------------- | -------------- | -| **RETURNS** | [`Optimizer`](https://thinc.ai/docs/api-optimizers) | The optimizer. | +| Name | Description | +| ----------- | ---------------------------- | +| **RETURNS** | The optimizer. ~~Optimizer~~ | ## Transformer.use_params {#use_params tag="method, contextmanager"} @@ -282,9 +282,9 @@ context, the original parameters are restored. > trf.to_disk("/best_model") > ``` -| Name | Type | Description | -| -------- | ---- | ----------------------------------------- | -| `params` | dict | The parameter values to use in the model. | +| Name | Description | +| -------- | -------------------------------------------------- | +| `params` | The parameter values to use in the model. ~~dict~~ | ## Transformer.to_disk {#to_disk tag="method"} @@ -297,11 +297,11 @@ Serialize the pipe to disk. > trf.to_disk("/path/to/transformer") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Transformer.from_disk {#from_disk tag="method"} @@ -314,12 +314,12 @@ Load the pipe from disk. Modifies the object in place and returns it. > trf.from_disk("/path/to/transformer") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tok2Vec` | The modified `Tok2Vec` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Transformer` object. ~~Transformer~~ | ## Transformer.to_bytes {#to_bytes tag="method"} @@ -332,11 +332,11 @@ Load the pipe from disk. Modifies the object in place and returns it. Serialize the pipe to a bytestring. -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Tok2Vec` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Transformer` object. ~~bytes~~ | ## Transformer.from_bytes {#from_bytes tag="method"} @@ -350,12 +350,12 @@ Load the pipe from a bytestring. Modifies the object in place and returns it. > trf.from_bytes(trf_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Tok2Vec` | The `Tok2Vec` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Transformer` object. ~~Transformer~~ | ## Serialization fields {#serialization-fields} @@ -386,20 +386,20 @@ by this class. Instances of this class are`typically assigned to the [Doc._.trf_data`](/api/transformer#custom-attributes) extension attribute. -| Name | Type | Description | -| --------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `tokens` | `Dict` | A slice of the tokens data produced by the tokenizer. This may have several fields, including the token IDs, the texts, and the attention mask. See the [`transformers.BatchEncoding`](https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.BatchEncoding) object for details. | -| `tensors` | `List[FloatsXd]` | The activations for the Doc from the transformer. Usually the last tensor that is 3-dimensional will be the most important, as that will provide the final hidden state. Generally activations that are 2-dimensional will be attention weights. Details of this variable will differ depending on the underlying transformer model. | -| `align` | [`Ragged`](https://thinc.ai/docs/api-types#ragged) | Alignment from the `Doc`'s tokenization to the wordpieces. This is a ragged array, where `align.lengths[i]` indicates the number of wordpiece tokens that token `i` aligns against. The actual indices are provided at `align[i].dataXd`. | -| `width` | int | The width of the last hidden layer. | +| Name | Description | +| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `tokens` | A slice of the tokens data produced by the tokenizer. This may have several fields, including the token IDs, the texts, and the attention mask. See the [`transformers.BatchEncoding`](https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.BatchEncoding) object for details. ~~dict~~ | +| `tensors` | The activations for the Doc from the transformer. Usually the last tensor that is 3-dimensional will be the most important, as that will provide the final hidden state. Generally activations that are 2-dimensional will be attention weights. Details of this variable will differ depending on the underlying transformer model. ~~List[FloatsXd]~~ | +| `align` | Alignment from the `Doc`'s tokenization to the wordpieces. This is a ragged array, where `align.lengths[i]` indicates the number of wordpiece tokens that token `i` aligns against. The actual indices are provided at `align[i].dataXd`. ~~Ragged~~ | +| `width` | The width of the last hidden layer. ~~int~~ | ### TransformerData.empty {#transformerdata-emoty tag="classmethod"} Create an empty `TransformerData` container. -| Name | Type | Description | -| ----------- | ----------------- | -------------- | -| **RETURNS** | `TransformerData` | The container. | +| Name | Description | +| ----------- | ---------------------------------- | +| **RETURNS** | The container. ~~TransformerData~~ | ## FullTransformerBatch {#fulltransformerbatch tag="dataclass"} @@ -407,13 +407,13 @@ Holds a batch of input and output objects for a transformer model. The data can then be split to a list of [`TransformerData`](/api/transformer#transformerdata) objects to associate the outputs to each [`Doc`](/api/doc) in the batch. -| Name | Type | Description | -| ---------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `spans` | `List[List[Span]]` | The batch of input spans. The outer list refers to the Doc objects in the batch, and the inner list are the spans for that `Doc`. Note that spans are allowed to overlap or exclude tokens, but each Span can only refer to one `Doc` (by definition). This means that within a `Doc`, the regions of the output tensors that correspond to each Span may overlap or have gaps, but for each `Doc`, there is a non-overlapping contiguous slice of the outputs. | -| `tokens` | [`transformers.BatchEncoding`](https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.BatchEncoding) | The output of the tokenizer. | -| `tensors` | `List[torch.Tensor]` | The output of the transformer model. | -| `align` | [`Ragged`](https://thinc.ai/docs/api-types#ragged) | Alignment from the spaCy tokenization to the wordpieces. This is a ragged array, where `align.lengths[i]` indicates the number of wordpiece tokens that token `i` aligns against. The actual indices are provided at `align[i].dataXd`. | -| `doc_data` | `List[TransformerData]` | The outputs, split per `Doc` object. | +| Name | Description | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `spans` | The batch of input spans. The outer list refers to the Doc objects in the batch, and the inner list are the spans for that `Doc`. Note that spans are allowed to overlap or exclude tokens, but each Span can only refer to one `Doc` (by definition). This means that within a `Doc`, the regions of the output tensors that correspond to each Span may overlap or have gaps, but for each `Doc`, there is a non-overlapping contiguous slice of the outputs. ~~List[List[Span]]~~ | +| `tokens` | The output of the tokenizer. ~~transformers.BatchEncoding~~ | +| `tensors` | The output of the transformer model. ~~List[torch.Tensor]~~ | +| `align` | Alignment from the spaCy tokenization to the wordpieces. This is a ragged array, where `align.lengths[i]` indicates the number of wordpiece tokens that token `i` aligns against. The actual indices are provided at `align[i].dataXd`. ~~Ragged~~ | +| `doc_data` | The outputs, split per `Doc` object. ~~List[TransformerData]~~ | ### FullTransformerBatch.unsplit_by_doc {#fulltransformerbatch-unsplit_by_doc tag="method"} @@ -422,19 +422,19 @@ current object's spans, tokens and alignment. This is used during the backward pass, in order to construct the gradients to pass back into the transformer model. -| Name | Type | Description | -| ----------- | ---------------------- | ------------------------------- | -| `arrays` | `List[List[Floats3d]]` | The split batch of activations. | -| **RETURNS** | `FullTransformerBatch` | The transformer batch. | +| Name | Description | +| ----------- | -------------------------------------------------------- | +| `arrays` | The split batch of activations. ~~List[List[Floats3d]]~~ | +| **RETURNS** | The transformer batch. ~~FullTransformerBatch~~ | ### FullTransformerBatch.split_by_doc {#fulltransformerbatch-split_by_doc tag="method"} Split a `TransformerData` object that represents a batch into a list with one `TransformerData` per `Doc`. -| Name | Type | Description | -| ----------- | ----------------------- | ---------------- | -| **RETURNS** | `List[TransformerData]` | The split batch. | +| Name | Description | +| ----------- | ------------------------------------------ | +| **RETURNS** | The split batch. ~~List[TransformerData]~~ | ## Span getters {#span_getters source="github.com/explosion/spacy-transformers/blob/master/spacy_transformers/span_getters.py"} @@ -460,10 +460,10 @@ decorator. > return get_sent_spans > ``` -| Name | Type | Description | -| ----------- | ------------------ | ---------------------------------------- | -| `docs` | `Iterable[Doc]` | A batch of `Doc` objects. | -| **RETURNS** | `List[List[Span]]` | The spans to process by the transformer. | +| Name | Description | +| ----------- | ------------------------------------------------------------- | +| `docs` | A batch of `Doc` objects. ~~Iterable[Doc]~~ | +| **RETURNS** | The spans to process by the transformer. ~~List[List[Span]]~~ | ### doc_spans.v1 {#doc_spans tag="registered function"} @@ -510,10 +510,10 @@ than `window` will allow for an overlap, so that some tokens are counted twice. This can be desirable, because it allows all tokens to have both a left and right context. -| Name | Type | Description | -| --------- | ---- | ---------------- | -|  `window` | int | The window size. | -| `stride` | int | The stride size. | +| Name | Description | +| -------- | ------------------------ | +| `window` | The window size. ~~int~~ | +| `stride` | The stride size. ~~int~~ | ## Annotation setters {#annotation_setters tag="registered functions" source="github.com/explosion/spacy-transformers/blob/master/spacy_transformers/annotation_setters.py"} @@ -534,10 +534,10 @@ You can register custom annotation setters using the > return setter > ``` -| Name | Type | Description | -| ---------- | ---------------------- | ------------------------------------ | -| `docs` | `List[Doc]` | A batch of `Doc` objects. | -| `trf_data` | `FullTransformerBatch` | The transformers data for the batch. | +| Name | Description | +| ---------- | ------------------------------------------------------------- | +| `docs` | A batch of `Doc` objects. ~~List[Doc]~~ | +| `trf_data` | The transformers data for the batch. ~~FullTransformerBatch~~ | The following built-in functions are available: @@ -550,6 +550,6 @@ The following built-in functions are available: The component sets the following [custom extension attributes](/usage/processing-pipeline#custom-components-attributes): -| Name | Type | Description | -| -------------- | ----------------------------------------------------- | ---------------------------------------------------- | -| `Doc.trf_data` | [`TransformerData`](/api/transformer#transformerdata) | Transformer tokens and outputs for the `Doc` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------ | +| `Doc.trf_data` | Transformer tokens and outputs for the `Doc` object. ~~TransformerData~~ | diff --git a/website/docs/api/vectors.md b/website/docs/api/vectors.md index bfb49e9a2..7e97b4ca3 100644 --- a/website/docs/api/vectors.md +++ b/website/docs/api/vectors.md @@ -30,13 +30,13 @@ you can add vectors to later. > vectors = Vectors(data=data, keys=keys) > ``` -| Name | Type | Description | -| -------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| _keyword-only_ | | | -| `shape` | tuple | Size of the table as `(n_entries, n_columns)`, the number of entries and number of columns. Not required if you're initializing the object with `data` and `keys`. | -| `data` | `ndarray[ndim=1, dtype='float32']` | The vector data. | -| `keys` | iterable | A sequence of keys aligned with the data. | -| `name` | str | A name to identify the vectors table. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `shape` | Size of the table as `(n_entries, n_columns)`, the number of entries and number of columns. Not required if you're initializing the object with `data` and `keys`. ~~Tuple[int, int]~~ | +| `data` | The vector data. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | +| `keys` | A sequence of keys aligned with the data. ~~Iterable[Union[str, int]]~~ | +| `name` | A name to identify the vectors table. ~~str~~ | ## Vectors.\_\_getitem\_\_ {#getitem tag="method"} @@ -51,10 +51,10 @@ raised. > assert cat_vector == nlp.vocab["cat"].vector > ``` -| Name | Type | Description | -| ------- | ---------------------------------- | ------------------------------ | -| `key` | int | The key to get the vector for. | -| returns | `ndarray[ndim=1, dtype='float32']` | The vector for the key. | +| Name | Description | +| ----------- | ---------------------------------------------------------------- | +| `key` | The key to get the vector for. ~~int~~ | +| **RETURNS** | The vector for the key. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Vectors.\_\_setitem\_\_ {#setitem tag="method"} @@ -68,10 +68,10 @@ Set a vector for the given key. > nlp.vocab.vectors[cat_id] = vector > ``` -| Name | Type | Description | -| -------- | ---------------------------------- | ------------------------------ | -| `key` | int | The key to set the vector for. | -| `vector` | `ndarray[ndim=1, dtype='float32']` | The vector to set. | +| Name | Description | +| -------- | ----------------------------------------------------------- | +| `key` | The key to set the vector for. ~~int~~ | +| `vector` | The vector to set. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Vectors.\_\_iter\_\_ {#iter tag="method"} @@ -84,9 +84,9 @@ Iterate over the keys in the table. > print(key, nlp.vocab.strings[key]) > ``` -| Name | Type | Description | -| ---------- | ---- | ------------------- | -| **YIELDS** | int | A key in the table. | +| Name | Description | +| ---------- | --------------------------- | +| **YIELDS** | A key in the table. ~~int~~ | ## Vectors.\_\_len\_\_ {#len tag="method"} @@ -99,9 +99,9 @@ Return the number of vectors in the table. > assert len(vectors) == 3 > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------- | -| **RETURNS** | int | The number of vectors in the table. | +| Name | Description | +| ----------- | ------------------------------------------- | +| **RETURNS** | The number of vectors in the table. ~~int~~ | ## Vectors.\_\_contains\_\_ {#contains tag="method"} @@ -115,10 +115,10 @@ Check whether a key has been mapped to a vector entry in the table. > assert cat_id in vectors > ``` -| Name | Type | Description | -| ----------- | ---- | ----------------------------------- | -| `key` | int | The key to check. | -| **RETURNS** | bool | Whether the key has a vector entry. | +| Name | Description | +| ----------- | -------------------------------------------- | +| `key` | The key to check. ~~int~~ | +| **RETURNS** | Whether the key has a vector entry. ~~bool~~ | ## Vectors.add {#add tag="method"} @@ -138,13 +138,13 @@ mapping separately. If you need to manage the strings, you should use the > nlp.vocab.vectors.add("dog", row=0) > ``` -| Name | Type | Description | -| -------------- | ---------------------------------- | ----------------------------------------------------- | -| `key` | str / int | The key to add. | -| _keyword-only_ | | | -| `vector` | `ndarray[ndim=1, dtype='float32']` | An optional vector to add for the key. | -| `row` | int | An optional row number of a vector to map the key to. | -| **RETURNS** | int | The row the vector was added to. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------- | +| `key` | The key to add. ~~Union[str, int]~~ | +| _keyword-only_ | | +| `vector` | An optional vector to add for the key. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | +| `row` | An optional row number of a vector to map the key to. ~~int~~ | +| **RETURNS** | The row the vector was added to. ~~int~~ | ## Vectors.resize {#resize tag="method"} @@ -160,11 +160,11 @@ These removed items are returned as a list of `(key, row)` tuples. > removed = nlp.vocab.vectors.resize((10000, 300)) > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------------------------------- | -| `shape` | tuple | A `(rows, dims)` tuple describing the number of rows and dimensions. | -| `inplace` | bool | Reallocate the memory. | -| **RETURNS** | list | The removed items as a list of `(key, row)` tuples. | +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------------- | +| `shape` | A `(rows, dims)` tuple describing the number of rows and dimensions. ~~Tuple[int, int]~~ | +| `inplace` | Reallocate the memory. ~~bool~~ | +| **RETURNS** | The removed items as a list of `(key, row)` tuples. ~~List[Tuple[int, int]]~~ | ## Vectors.keys {#keys tag="method"} @@ -177,9 +177,9 @@ A sequence of the keys in the table. > print(key, nlp.vocab.strings[key]) > ``` -| Name | Type | Description | -| ----------- | -------- | ----------- | -| **RETURNS** | iterable | The keys. | +| Name | Description | +| ----------- | --------------------------- | +| **RETURNS** | The keys. ~~Iterable[int]~~ | ## Vectors.values {#values tag="method"} @@ -194,9 +194,9 @@ the length of the vectors table. > print(vector) > ``` -| Name | Type | Description | -| ---------- | ---------------------------------- | ---------------------- | -| **YIELDS** | `ndarray[ndim=1, dtype='float32']` | A vector in the table. | +| Name | Description | +| ---------- | --------------------------------------------------------------- | +| **YIELDS** | A vector in the table. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Vectors.items {#items tag="method"} @@ -209,9 +209,9 @@ Iterate over `(key, vector)` pairs, in order. > print(key, nlp.vocab.strings[key], vector) > ``` -| Name | Type | Description | -| ---------- | ----- | -------------------------------- | -| **YIELDS** | tuple | `(key, vector)` pairs, in order. | +| Name | Description | +| ---------- | ------------------------------------------------------------------------------------- | +| **YIELDS** | `(key, vector)` pairs, in order. ~~Tuple[int, numpy.ndarray[ndim=1, dtype=float32]]~~ | ## Vectors.find {#find tag="method"} @@ -226,14 +226,14 @@ Look up one or more keys by row, or vice versa. > keys = nlp.vocab.vectors.find(rows=[18, 256, 985]) > ``` -| Name | Type | Description | -| -------------- | ------------------------------------- | ------------------------------------------------------------------------ | -| _keyword-only_ | | | -| `key` | str / int | Find the row that the given key points to. Returns int, `-1` if missing. | -| `keys` | iterable | Find rows that the keys point to. Returns `ndarray`. | -| `row` | int | Find the first key that points to the row. Returns int. | -| `rows` | iterable | Find the keys that point to the rows. Returns ndarray. | -| **RETURNS** | The requested key, keys, row or rows. | +| Name | Description | +| -------------- | -------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `key` | Find the row that the given key points to. Returns int, `-1` if missing. ~~Union[str, int]~~ | +| `keys` | Find rows that the keys point to. Returns `numpy.ndarray`. ~~Iterable[Union[str, int]]~~ | +| `row` | Find the first key that points to the row. Returns integer. ~~int~~ | +| `rows` | Find the keys that point to the rows. Returns `numpy.ndarray`. ~~Iterable[int]~~ | +| **RETURNS** | The requested key, keys, row or rows. ~~Union[int, numpy.ndarray[ndim=1, dtype=float32]]~~ | ## Vectors.shape {#shape tag="property"} @@ -250,9 +250,9 @@ vector table. > assert dims == 300 > ``` -| Name | Type | Description | -| ----------- | ----- | ---------------------- | -| **RETURNS** | tuple | A `(rows, dims)` pair. | +| Name | Description | +| ----------- | ------------------------------------------ | +| **RETURNS** | A `(rows, dims)` pair. ~~Tuple[int, int]~~ | ## Vectors.size {#size tag="property"} @@ -265,9 +265,9 @@ The vector size, i.e. `rows * dims`. > assert vectors.size == 150000 > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------- | -| **RETURNS** | int | The vector size. | +| Name | Description | +| ----------- | ------------------------ | +| **RETURNS** | The vector size. ~~int~~ | ## Vectors.is_full {#is_full tag="property"} @@ -283,9 +283,9 @@ If a table is full, it can be resized using > assert vectors.is_full > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------------------------- | -| **RETURNS** | bool | Whether the vectors table is full. | +| Name | Description | +| ----------- | ------------------------------------------- | +| **RETURNS** | Whether the vectors table is full. ~~bool~~ | ## Vectors.n_keys {#n_keys tag="property"} @@ -301,9 +301,9 @@ vectors, they will be counted individually. > assert vectors.n_keys == 0 > ``` -| Name | Type | Description | -| ----------- | ---- | ------------------------------------ | -| **RETURNS** | int | The number of all keys in the table. | +| Name | Description | +| ----------- | -------------------------------------------- | +| **RETURNS** | The number of all keys in the table. ~~int~~ | ## Vectors.most_similar {#most_similar tag="method"} @@ -320,14 +320,14 @@ performed in chunks, to avoid consuming too much memory. You can set the > most_similar = nlp.vocab.vectors.most_similar(queries, n=10) > ``` -| Name | Type | Description | -| -------------- | --------- | ------------------------------------------------------------------ | -| `queries` | `ndarray` | An array with one or more vectors. | -| _keyword-only_ | | | -| `batch_size` | int | The batch size to use. Default to `1024`. | -| `n` | int | The number of entries to return for each query. Defaults to `1`. | -| `sort` | bool | Whether to sort the entries returned by score. Defaults to `True`. | -| **RETURNS** | tuple | The most similar entries as a `(keys, best_rows, scores)` tuple. | +| Name | Description | +| -------------- | --------------------------------------------------------------------------- | +| `queries` | An array with one or more vectors. ~~numpy.ndarray~~ | +| _keyword-only_ | | +| `batch_size` | The batch size to use. Default to `1024`. ~~int~~ | +| `n` | The number of entries to return for each query. Defaults to `1`. ~~int~~ | +| `sort` | Whether to sort the entries returned by score. Defaults to `True`. ~~bool~~ | +| **RETURNS** | tuple | The most similar entries as a `(keys, best_rows, scores)` tuple. ~~Tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]~~ | ## Vectors.to_disk {#to_disk tag="method"} @@ -340,9 +340,9 @@ Save the current state to a directory. > > ``` -| Name | Type | Description | -| ------ | ------------ | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | +| Name | Description | +| ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | ## Vectors.from_disk {#from_disk tag="method"} @@ -355,10 +355,10 @@ Loads state from a directory. Modifies the object in place and returns it. > vectors.from_disk("/path/to/vectors") > ``` -| Name | Type | Description | -| ----------- | ------------ | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| **RETURNS** | `Vectors` | The modified `Vectors` object. | +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| **RETURNS** | The modified `Vectors` object. ~~Vectors~~ | ## Vectors.to_bytes {#to_bytes tag="method"} @@ -370,9 +370,9 @@ Serialize the current state to a binary string. > vectors_bytes = vectors.to_bytes() > ``` -| Name | Type | Description | -| ----------- | ----- | -------------------------------------------- | -| **RETURNS** | bytes | The serialized form of the `Vectors` object. | +| Name | Description | +| ----------- | ------------------------------------------------------ | +| **RETURNS** | The serialized form of the `Vectors` object. ~~bytes~~ | ## Vectors.from_bytes {#from_bytes tag="method"} @@ -387,15 +387,15 @@ Load state from a binary string. > new_vectors.from_bytes(vectors_bytes) > ``` -| Name | Type | Description | -| ----------- | --------- | ---------------------- | -| `data` | bytes | The data to load from. | -| **RETURNS** | `Vectors` | The `Vectors` object. | +| Name | Description | +| ----------- | --------------------------------- | +| `data` | The data to load from. ~~bytes~~ | +| **RETURNS** | The `Vectors` object. ~~Vectors~~ | ## Attributes {#attributes} -| Name | Type | Description | -| --------- | ---------------------------------- | ------------------------------------------------------------------------------- | -| `data` | `ndarray[ndim=1, dtype='float32']` | Stored vectors data. `numpy` is used for CPU vectors, `cupy` for GPU vectors. | -| `key2row` | dict | Dictionary mapping word hashes to rows in the `Vectors.data` table. | -| `keys` | `ndarray[ndim=1, dtype='float32']` | Array keeping the keys in order, such that `keys[vectors.key2row[key]] == key`. | +| Name | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | Stored vectors data. `numpy` is used for CPU vectors, `cupy` for GPU vectors. ~~Union[numpy.ndarray[ndim=1, dtype=float32], cupy.ndarray[ndim=1, dtype=float32]]~~ | +| `key2row` | Dictionary mapping word hashes to rows in the `Vectors.data` table. ~~Dict[int, int]~~ | +| `keys` | Array keeping the keys in order, such that `keys[vectors.key2row[key]] == key`. ~~Union[numpy.ndarray[ndim=1, dtype=float32], cupy.ndarray[ndim=1, dtype=float32]]~~ | diff --git a/website/docs/api/vocab.md b/website/docs/api/vocab.md index 7e77762bb..71a678cb3 100644 --- a/website/docs/api/vocab.md +++ b/website/docs/api/vocab.md @@ -21,14 +21,15 @@ Create the vocabulary. > vocab = Vocab(strings=["hello", "world"]) > ``` -| Name | Type | Description | -| -------------------------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `lex_attr_getters` | dict | A dictionary mapping attribute IDs to functions to compute them. Defaults to `None`. | -| `strings` | `StringStore` / list | A [`StringStore`](/api/stringstore) that maps strings to hash values, and vice versa, or a list of strings. | -| `lookups` | `Lookups` | A [`Lookups`](/api/lookups) that stores the `lemma_\*`, `lexeme_norm` and other large lookup tables. Defaults to `None`. | -| `lookups_extra` 2.3 | `Lookups` | A [`Lookups`](/api/lookups) that stores the optional `lexeme_cluster`/`lexeme_prob`/`lexeme_sentiment`/`lexeme_settings` lookup tables. Defaults to `None`. | -| `oov_prob` | float | The default OOV probability. Defaults to `-20.0`. | -| `vectors_name` 2.2 | str | A name to identify the vectors table. | +| Name | Description | +| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `lex_attr_getters` | A dictionary mapping attribute IDs to functions to compute them. Defaults to `None`. ~~Optional[Dict[str, Callable[[str], Any]]]~~ | +| `strings` | A [`StringStore`](/api/stringstore) that maps strings to hash values, and vice versa, or a list of strings. ~~Union[List[str], StringStore]~~ | +| `lookups` | A [`Lookups`](/api/lookups) that stores the `lexeme_norm` and other large lookup tables. Defaults to `None`. ~~Optional[Lookups]~~ | +| `oov_prob` | The default OOV probability. Defaults to `-20.0`. ~~float~~ | +| `vectors_name` 2.2 | A name to identify the vectors table. ~~str~~ | +| `writing_system` | A dictionary describing the language's writing system. Typically provided by [`Language.Defaults`](/api/language#defaults). ~~Dict[str, Any]~~ | +| `get_noun_chunks` | A function that yields base noun phrases, used for [`Doc.noun_chunks`](/ap/doc#noun_chunks). ~~Optional[Callable[[Union[Doc, Span], Iterator[Span]]]]~~ | ## Vocab.\_\_len\_\_ {#len tag="method"} @@ -41,9 +42,9 @@ Get the current number of lexemes in the vocabulary. > assert len(nlp.vocab) > 0 > ``` -| Name | Type | Description | -| ----------- | ---- | ---------------------------------------- | -| **RETURNS** | int | The number of lexemes in the vocabulary. | +| Name | Description | +| ----------- | ------------------------------------------------ | +| **RETURNS** | The number of lexemes in the vocabulary. ~~int~~ | ## Vocab.\_\_getitem\_\_ {#getitem tag="method"} @@ -57,10 +58,10 @@ given, a new lexeme is created and stored. > assert nlp.vocab[apple] == nlp.vocab["apple"] > ``` -| Name | Type | Description | -| -------------- | --------- | ---------------------------------------- | -| `id_or_string` | int / str | The hash value of a word, or its string. | -| **RETURNS** | `Lexeme` | The lexeme indicated by the given ID. | +| Name | Description | +| -------------- | ------------------------------------------------------------ | +| `id_or_string` | The hash value of a word, or its string. ~~Union[int, str]~~ | +| **RETURNS** | The lexeme indicated by the given ID. ~~Lexeme~~ | ## Vocab.\_\_iter\_\_ {#iter tag="method"} @@ -72,9 +73,9 @@ Iterate over the lexemes in the vocabulary. > stop_words = (lex for lex in nlp.vocab if lex.is_stop) > ``` -| Name | Type | Description | -| ---------- | -------- | --------------------------- | -| **YIELDS** | `Lexeme` | An entry in the vocabulary. | +| Name | Description | +| ---------- | -------------------------------------- | +| **YIELDS** | An entry in the vocabulary. ~~Lexeme~~ | ## Vocab.\_\_contains\_\_ {#contains tag="method"} @@ -91,10 +92,10 @@ given string, you need to look it up in > assert oov not in nlp.vocab > ``` -| Name | Type | Description | -| ----------- | ---- | -------------------------------------------------- | -| `string` | str | The ID string. | -| **RETURNS** | bool | Whether the string has an entry in the vocabulary. | +| Name | Description | +| ----------- | ----------------------------------------------------------- | +| `string` | The ID string. ~~str~~ | +| **RETURNS** | Whether the string has an entry in the vocabulary. ~~bool~~ | ## Vocab.add_flag {#add_flag tag="method"} @@ -115,11 +116,11 @@ using `token.check_flag(flag_id)`. > assert doc[2].check_flag(MY_PRODUCT) == True > ``` -| Name | Type | Description | -| ------------- | ---- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| `flag_getter` | dict | A function `f(str) -> bool`, to get the flag value. | -| `flag_id` | int | An integer between 1 and 63 (inclusive), specifying the bit at which the flag will be stored. If `-1`, the lowest available bit will be chosen. | -| **RETURNS** | int | The integer ID by which the flag value can be checked. | +| Name | Description | +| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `flag_getter` | A function that takes the lexeme text and returns the boolean flag value. ~~Callable[[str], bool]~~ | +| `flag_id` | An integer between `1` and `63` (inclusive), specifying the bit at which the flag will be stored. If `-1`, the lowest available bit will be chosen. ~~int~~ | +| **RETURNS** | The integer ID by which the flag value can be checked. ~~int~~ | ## Vocab.reset_vectors {#reset_vectors tag="method" new="2"} @@ -133,11 +134,11 @@ have to call this to change the size of the vectors. Only one of the `width` and > nlp.vocab.reset_vectors(width=300) > ``` -| Name | Type | Description | -| -------------- | ---- | -------------------------------------- | -| _keyword-only_ | | | -| `width` | int | The new width (keyword argument only). | -| `shape` | int | The new shape (keyword argument only). | +| Name | Description | +| -------------- | ---------------------- | +| _keyword-only_ | | +| `width` | The new width. ~~int~~ | +| `shape` | The new shape. ~~int~~ | ## Vocab.prune_vectors {#prune_vectors tag="method" new="2"} @@ -158,11 +159,11 @@ cosines are calculated in minibatches, to reduce memory usage. > assert len(nlp.vocab.vectors) <= 1000 > ``` -| Name | Type | Description | -| ------------ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nr_row` | int | The number of rows to keep in the vector table. | -| `batch_size` | int | Batch of vectors for calculating the similarities. Larger batch sizes might be faster, while temporarily requiring more memory. | -| **RETURNS** | dict | A dictionary keyed by removed words mapped to `(string, score)` tuples, where `string` is the entry the removed word was mapped to, and `score` the similarity score between the two words. | +| Name | Description | +| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `nr_row` | The number of rows to keep in the vector table. ~~int~~ | +| `batch_size` | Batch of vectors for calculating the similarities. Larger batch sizes might be faster, while temporarily requiring more memory. ~~int~~ | +| **RETURNS** | A dictionary keyed by removed words mapped to `(string, score)` tuples, where `string` is the entry the removed word was mapped to, and `score` the similarity score between the two words. ~~Dict[str, Tuple[str, float]]~~ | ## Vocab.get_vector {#get_vector tag="method" new="2"} @@ -178,12 +179,12 @@ subword features by average over ngrams of `orth` (introduced in spaCy `v2.1`). > nlp.vocab.get_vector("apple", minn=1, maxn=5) > ``` -| Name | Type | Description | -| ----------------------------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------- | -| `orth` | int / str | The hash value of a word, or its unicode string. | -| `minn` 2.1 | int | Minimum n-gram length used for FastText's ngram computation. Defaults to the length of `orth`. | -| `maxn` 2.1 | int | Maximum n-gram length used for FastText's ngram computation. Defaults to the length of `orth`. | -| **RETURNS** | `numpy.ndarray[ndim=1, dtype='float32']` | A word vector. Size and shape are determined by the `Vocab.vectors` instance. | +| Name | Description | +| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `orth` | The hash value of a word, or its unicode string. ~~Union[int, str]~~ | +| `minn` 2.1 | Minimum n-gram length used for FastText's ngram computation. Defaults to the length of `orth`. ~~int~~ | +| `maxn` 2.1 | Maximum n-gram length used for FastText's ngram computation. Defaults to the length of `orth`. ~~int~~ | +| **RETURNS** | A word vector. Size and shape are determined by the `Vocab.vectors` instance. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Vocab.set_vector {#set_vector tag="method" new="2"} @@ -196,10 +197,10 @@ or hash value. > nlp.vocab.set_vector("apple", array([...])) > ``` -| Name | Type | Description | -| -------- | ---------------------------------------- | ------------------------------------------------ | -| `orth` | int / str | The hash value of a word, or its unicode string. | -| `vector` | `numpy.ndarray[ndim=1, dtype='float32']` | The vector to set. | +| Name | Description | +| -------- | -------------------------------------------------------------------- | +| `orth` | The hash value of a word, or its unicode string. ~~Union[int, str]~~ | +| `vector` | The vector to set. ~~numpy.ndarray[ndim=1, dtype=float32]~~ | ## Vocab.has_vector {#has_vector tag="method" new="2"} @@ -213,10 +214,10 @@ Words can be looked up by string or hash value. > vector = nlp.vocab.get_vector("apple") > ``` -| Name | Type | Description | -| ----------- | --------- | ------------------------------------------------ | -| `orth` | int / str | The hash value of a word, or its unicode string. | -| **RETURNS** | bool | Whether the word has a vector. | +| Name | Description | +| ----------- | -------------------------------------------------------------------- | +| `orth` | The hash value of a word, or its unicode string. ~~Union[int, str]~~ | +| **RETURNS** | Whether the word has a vector. ~~bool~~ | ## Vocab.to_disk {#to_disk tag="method" new="2"} @@ -228,11 +229,11 @@ Save the current state to a directory. > nlp.vocab.to_disk("/path/to/vocab") > ``` -| Name | Type | Description | -| -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | +| `path` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | ## Vocab.from_disk {#from_disk tag="method" new="2"} @@ -245,12 +246,12 @@ Loads state from a directory. Modifies the object in place and returns it. > vocab = Vocab().from_disk("/path/to/vocab") > ``` -| Name | Type | Description | -| -------------- | --------------- | -------------------------------------------------------------------------- | -| `path` | str / `Path` | A path to a directory. Paths may be either strings or `Path`-like objects. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Vocab` | The modified `Vocab` object. | +| Name | Description | +| -------------- | ----------------------------------------------------------------------------------------------- | +| `path` | A path to a directory. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The modified `Vocab` object. ~~Vocab~~ | ## Vocab.to_bytes {#to_bytes tag="method"} @@ -262,11 +263,11 @@ Serialize the current state to a binary string. > vocab_bytes = nlp.vocab.to_bytes() > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | bytes | The serialized form of the `Vocab` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The serialized form of the `Vocab` object. ~~Vocab~~ | ## Vocab.from_bytes {#from_bytes tag="method"} @@ -281,12 +282,12 @@ Load state from a binary string. > vocab.from_bytes(vocab_bytes) > ``` -| Name | Type | Description | -| -------------- | --------------- | ------------------------------------------------------------------------- | -| `bytes_data` | bytes | The data to load from. | -| _keyword-only_ | | | -| `exclude` | `Iterable[str]` | String names of [serialization fields](#serialization-fields) to exclude. | -| **RETURNS** | `Vocab` | The `Vocab` object. | +| Name | Description | +| -------------- | ------------------------------------------------------------------------------------------- | +| `bytes_data` | The data to load from. ~~bytes~~ | +| _keyword-only_ | | +| `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | +| **RETURNS** | The `Vocab` object. ~~Vocab~~ | ## Attributes {#attributes} @@ -299,13 +300,13 @@ Load state from a binary string. > assert type(PERSON) == int > ``` -| Name | Type | Description | -| --------------------------------------------- | ------------- | ------------------------------------------------------------ | -| `strings` | `StringStore` | A table managing the string-to-int mapping. | -| `vectors` 2 | `Vectors` | A table associating word IDs to word vectors. | -| `vectors_length` | int | Number of dimensions for each word vector. | -| `lookups` | `Lookups` | The available lookup tables in this vocab. | -| `writing_system` 2.1 | dict | A dict with information about the language's writing system. | +| Name | Description | +| --------------------------------------------- | ------------------------------------------------------------------------------- | +| `strings` | A table managing the string-to-int mapping. ~~StringStore~~ | +| `vectors` 2 | A table associating word IDs to word vectors. ~~Vectors~~ | +| `vectors_length` | Number of dimensions for each word vector. ~~int~~ | +| `lookups` | The available lookup tables in this vocab. ~~Lookups~~ | +| `writing_system` 2.1 | A dict with information about the language's writing system. ~~Dict[str, Any]~~ | ## Serialization fields {#serialization-fields} diff --git a/website/docs/usage/101/_architecture.md b/website/docs/usage/101/_architecture.md index 2a389cd87..98011f173 100644 --- a/website/docs/usage/101/_architecture.md +++ b/website/docs/usage/101/_architecture.md @@ -73,14 +73,14 @@ operates on a `Doc` and gives you access to the matched tokens **in context**. ### Other classes {#architecture-other} -| Name | Description | -| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -| [`Vocab`](/api/vocab) | The shared vocabulary that stores strings and gives you access to [`Lexeme`](/api/lexeme) objects. | -| [`StringStore`](/api/stringstore) | Map strings to and from hash values. | -| [`Vectors`](/api/vectors) | Container class for vector data keyed by string. | -| [`Lookups`](/api/lookups) | Container for convenient access to large lookup tables and dictionaries. | -| [`Morphology`](/api/morphology) | Assign linguistic features like lemmas, noun case, verb tense etc. based on the word and its part-of-speech tag. | -| [`MorphAnalysis`](/api/morphanalysis) | A morphological analysis. | -| [`KnowledgeBase`](/api/kb) | Storage for entities and aliases of a knowledge base for entity linking. | -| [`Scorer`](/api/scorer) | Compute evaluation scores. | -| [`Corpus`](/api/corpus) | Class for managing annotated corpora for training and evaluation data. | +| Name | Description | +| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | +| [`Vocab`](/api/vocab) | The shared vocabulary that stores strings and gives you access to [`Lexeme`](/api/lexeme) objects. | +| [`StringStore`](/api/stringstore) | Map strings to and from hash values. | +| [`Vectors`](/api/vectors) | Container class for vector data keyed by string. | +| [`Lookups`](/api/lookups) | Container for convenient access to large lookup tables and dictionaries. | +| [`Morphology`](/api/morphology) | Assign linguistic features like lemmas, noun case, verb tense etc. based on the word and its part-of-speech tag. | +| [`MorphAnalysis`](/api/morphology#morphanalysis) | A morphological analysis. | +| [`KnowledgeBase`](/api/kb) | Storage for entities and aliases of a knowledge base for entity linking. | +| [`Scorer`](/api/scorer) | Compute evaluation scores. | +| [`Corpus`](/api/corpus) | Class for managing annotated corpora for training and evaluation data. | diff --git a/website/docs/usage/linguistic-features.md b/website/docs/usage/linguistic-features.md index 589cef44c..ac922c4fa 100644 --- a/website/docs/usage/linguistic-features.md +++ b/website/docs/usage/linguistic-features.md @@ -980,7 +980,7 @@ nlp.tokenizer = my_tokenizer | Argument | Type | Description | | ----------- | ----------------- | ------------------------- | -| `text` | str | The raw text to tokenize. | +| `text` | `str` | The raw text to tokenize. | | **RETURNS** | [`Doc`](/api/doc) | The tokenized document. | #### Example 1: Basic whitespace tokenizer {#custom-tokenizer-example} diff --git a/website/docs/usage/models.md b/website/docs/usage/models.md index 5e58d126d..1ea39fa83 100644 --- a/website/docs/usage/models.md +++ b/website/docs/usage/models.md @@ -139,25 +139,25 @@ $ pip install https://github.com/honnibal/pkuseg-python/archive/master.zip The `meta` argument of the `Chinese` language class supports the following following tokenizer config settings: -| Name | Type | Description | -| ------------------ | ---- | ------------------------------------------------------------------------------------------------------- | -| `segmenter` | str | Word segmenter: `char`, `jieba` or `pkuseg`. Defaults to `char`. | -| `pkuseg_model` | str | **Required for `pkuseg`:** Name of a model provided by `pkuseg` or the path to a local model directory. | -| `pkuseg_user_dict` | str | Optional path to a file with one word per line which overrides the default `pkuseg` user dictionary. | +| Name | Description | +| ------------------ | --------------------------------------------------------------------------------------------------------------- | +| `segmenter` | Word segmenter: `char`, `jieba` or `pkuseg`. Defaults to `char`. ~~str~~ | +| `pkuseg_model` | **Required for `pkuseg`:** Name of a model provided by `pkuseg` or the path to a local model directory. ~~str~~ | +| `pkuseg_user_dict` | Optional path to a file with one word per line which overrides the default `pkuseg` user dictionary. ~~str~~ | ```python ### Examples # Load "default" model cfg = {"segmenter": "pkuseg", "pkuseg_model": "default"} -nlp = Chinese(meta={"tokenizer": {"config": cfg}}) +nlp = Chinese(config={"tokenizer": {"config": cfg}}) # Load local model cfg = {"segmenter": "pkuseg", "pkuseg_model": "/path/to/pkuseg_model"} -nlp = Chinese(meta={"tokenizer": {"config": cfg}}) +nlp = Chinese(config={"tokenizer": {"config": cfg}}) # Override the user directory cfg = {"segmenter": "pkuseg", "pkuseg_model": "default", "pkuseg_user_dict": "/path"} -nlp = Chinese(meta={"tokenizer": {"config": cfg}}) +nlp = Chinese(config={"tokenizer": {"config": cfg}}) ``` You can also modify the user dictionary on-the-fly: diff --git a/website/docs/usage/processing-pipelines.md b/website/docs/usage/processing-pipelines.md index 00348065c..8df4b200d 100644 --- a/website/docs/usage/processing-pipelines.md +++ b/website/docs/usage/processing-pipelines.md @@ -477,10 +477,10 @@ only being able to modify it afterwards. > return doc > ``` -| Argument | Type | Description | -| ----------- | ----- | ------------------------------------------------------ | -| `doc` | `Doc` | The `Doc` object processed by the previous component. | -| **RETURNS** | `Doc` | The `Doc` object processed by this pipeline component. | +| Argument | Type | Description | +| ----------- | ----------------- | ------------------------------------------------------ | +| `doc` | [`Doc`](/api/doc) | The `Doc` object processed by the previous component. | +| **RETURNS** | [`Doc`](/api/doc) | The `Doc` object processed by this pipeline component. | The [`@Language.component`](/api/language#component) decorator lets you turn a simple function into a pipeline component. It takes at least one argument, the @@ -502,12 +502,12 @@ last** in the pipeline, or define a **custom name**. If no name is set and no > nlp.add_pipe("my_component", before="parser") > ``` -| Argument | Type | Description | -| -------- | --------- | ------------------------------------------------------------------------ | -| `last` | bool | If set to `True`, component is added **last** in the pipeline (default). | -| `first` | bool | If set to `True`, component is added **first** in the pipeline. | -| `before` | str / int | String name or index to add the new component **before**. | -| `after` | str / int | String name or index to add the new component **after**. | +| Argument | Description | +| -------- | --------------------------------------------------------------------------------- | +| `last` | If set to `True`, component is added **last** in the pipeline (default). ~~bool~~ | +| `first` | If set to `True`, component is added **first** in the pipeline. ~~bool~~ | +| `before` | String name or index to add the new component **before**. ~~Union[str, int]~~ | +| `after` | String name or index to add the new component **after**. ~~Union[str, int]~~ | @@ -626,10 +626,10 @@ added to the pipeline: > return MyComponent() > ``` -| Argument | Type | Description | -| -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `nlp` | [`Language`](/api/language) | The current `nlp` object. Can be used to access the | -| `name` | str | The **instance name** of the component in the pipeline. This lets you identify different instances of the same component. | +| Argument | Description | +| -------- | --------------------------------------------------------------------------------------------------------------------------------- | +| `nlp` | The current `nlp` object. Can be used to access the shared vocab. ~~Language~~ | +| `name` | The **instance name** of the component in the pipeline. This lets you identify different instances of the same component. ~~str~~ | All other settings can be passed in by the user via the `config` argument on [`nlp.add_pipe`](/api/language). The @@ -1332,12 +1332,11 @@ function that takes a `Doc`, modifies it and returns it. - If you're looking to publish a model that depends on a custom pipeline component, you can either **require it** in the model package's dependencies, or – if the component is specific and lightweight – choose to **ship it with - your model package** and add it to the `Language` instance returned by the - model's `load()` method. For examples of this, check out the implementations - of spaCy's - [`load_model_from_init_py`](/api/top-level#util.load_model_from_init_py) - [`load_model_from_path`](/api/top-level#util.load_model_from_path) utility - functions. + your model package**. Just make sure the + [`@Language.component`](/api/language#component) or + [`@Language.factory`](/api/language#factory) decorator that registers the + custom component runs in your model's `__init__.py` or is exposed via an + [entry point](/usage/saving-loading#entry-points). - Once you're ready to share your extension with others, make sure to **add docs and installation instructions** (you can always link to this page for more diff --git a/website/docs/usage/rule-based-matching.md b/website/docs/usage/rule-based-matching.md index d7c3d49f8..66a3daf6e 100644 --- a/website/docs/usage/rule-based-matching.md +++ b/website/docs/usage/rule-based-matching.md @@ -157,19 +157,20 @@ The available token pattern keys correspond to a number of [`Token` attributes](/api/token#attributes). The supported attributes for rule-based matching are: -| Attribute | Type |  Description | -| -------------------------------------- | ---- | ------------------------------------------------------------------------------------------------------ | -| `ORTH` | str | The exact verbatim text of a token. | -| `TEXT` 2.1 | str | The exact verbatim text of a token. | -| `LOWER` | str | The lowercase form of the token text. | -|  `LENGTH` | int | The length of the token text. | -|  `IS_ALPHA`, `IS_ASCII`, `IS_DIGIT` | bool | Token text consists of alphabetic characters, ASCII characters, digits. | -|  `IS_LOWER`, `IS_UPPER`, `IS_TITLE` | bool | Token text is in lowercase, uppercase, titlecase. | -|  `IS_PUNCT`, `IS_SPACE`, `IS_STOP` | bool | Token is punctuation, whitespace, stop word. | -|  `LIKE_NUM`, `LIKE_URL`, `LIKE_EMAIL` | bool | Token text resembles a number, URL, email. | -|  `POS`, `TAG`, `DEP`, `LEMMA`, `SHAPE` | str | The token's simple and extended part-of-speech tag, dependency label, lemma, shape. | -| `ENT_TYPE` | str | The token's entity label. | -| `_` 2.1 | dict | Properties in [custom extension attributes](/usage/processing-pipelines#custom-components-attributes). | +| Attribute |  Description | +| -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | +| `ORTH` | The exact verbatim text of a token. ~~str~~ | +| `TEXT` 2.1 | The exact verbatim text of a token. ~~str~~ | +| `LOWER` | The lowercase form of the token text. ~~str~~ | +|  `LENGTH` | The length of the token text. ~~int~~ | +|  `IS_ALPHA`, `IS_ASCII`, `IS_DIGIT` | Token text consists of alphabetic characters, ASCII characters, digits. ~~bool~~ | +|  `IS_LOWER`, `IS_UPPER`, `IS_TITLE` | Token text is in lowercase, uppercase, titlecase. ~~bool~~ | +|  `IS_PUNCT`, `IS_SPACE`, `IS_STOP` | Token is punctuation, whitespace, stop word. ~~bool~~ | +|  `LIKE_NUM`, `LIKE_URL`, `LIKE_EMAIL` | Token text resembles a number, URL, email. ~~bool~~ | +|  `POS`, `TAG`, `DEP`, `LEMMA`, `SHAPE` | The token's simple and extended part-of-speech tag, dependency label, lemma, shape. ~~str~~ | +| `ENT_TYPE` | The token's entity label. ~~str~~ | +| `_` 2.1 | Properties in [custom extension attributes](/usage/processing-pipelines#custom-components-attributes). ~~Dict[str, Any]~~ | +| `OP` | [Operator or quantifier](#quantifiers) to determine how often to match a token pattern. ~~str~~ | @@ -231,11 +232,11 @@ following rich comparison attributes are available: > pattern2 = [{"LENGTH": {">=": 10}}] > ``` -| Attribute | Value Type | Description | -| -------------------------- | ---------- | --------------------------------------------------------------------------------- | -| `IN` | any | Attribute value is member of a list. | -| `NOT_IN` | any | Attribute value is _not_ member of a list. | -| `==`, `>=`, `<=`, `>`, `<` | int, float | Attribute value is equal, greater or equal, smaller or equal, greater or smaller. | +| Attribute | Description | +| -------------------------- | ------------------------------------------------------------------------------------------------------- | +| `IN` | Attribute value is member of a list. ~~Any~~ | +| `NOT_IN` | Attribute value is _not_ member of a list. ~~Any~~ | +| `==`, `>=`, `<=`, `>`, `<` | Attribute value is equal, greater or equal, smaller or equal, greater or smaller. ~~Union[int, float]~~ | #### Regular expressions {#regex new="2.1"} @@ -485,12 +486,12 @@ This allows you to write callbacks that consider the entire set of matched phrases, so that you can resolve overlaps and other conflicts in whatever way you prefer. -| Argument | Type | Description | -| --------- | --------- | -------------------------------------------------------------------------------------------------------------------- | -| `matcher` | `Matcher` | The matcher instance. | -| `doc` | `Doc` | The document the matcher was used on. | -| `i` | int | Index of the current match (`matches[i`]). | -| `matches` | list |  A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. | +| Argument | Description | +| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `matcher` | The matcher instance. ~~Matcher~~ | +| `doc` | The document the matcher was used on. ~~Doc~~ | +| `i` | Index of the current match (`matches[i`]). ~~int~~ | +| `matches` | A list of `(match_id, start, end)` tuples, describing the matches. A match tuple describes a span `doc[start:end`]. ~~ List[Tuple[int, int int]]~~ | ### Using custom pipeline components {#matcher-pipeline} diff --git a/website/docs/usage/transformers.md b/website/docs/usage/transformers.md index b672d5612..79ac8177f 100644 --- a/website/docs/usage/transformers.md +++ b/website/docs/usage/transformers.md @@ -10,45 +10,48 @@ next: /usage/training ## Installation {#install hidden="true"} -Transformers are a family of neural network architectures that compute dense, -context-sensitive representations for the tokens in your documents. Downstream +Transformers are a family of neural network architectures that compute **dense, +context-sensitive representations** for the tokens in your documents. Downstream models in your pipeline can then use these representations as input features to -improve their predictions. You can connect multiple components to a single +**improve their predictions**. You can connect multiple components to a single transformer model, with any or all of those components giving feedback to the transformer to fine-tune it to your tasks. spaCy's transformer support -interoperates with PyTorch and the [Huggingface transformers](https://huggingface.co/transformers/) -library, giving you access to thousands of pretrained models for your pipelines. -There are many [great guides](http://jalammar.github.io/illustrated-transformer/) -to transformer models, but for practical purposes, you can simply think of them -as a drop-in replacement that let you achieve higher accuracy in exchange for -higher training and runtime costs. +interoperates with [PyTorch](https://pytorch.org) and the +[HuggingFace `transformers`](https://huggingface.co/transformers/) library, +giving you access to thousands of pretrained models for your pipelines. There +are many [great guides](http://jalammar.github.io/illustrated-transformer/) to +transformer models, but for practical purposes, you can simply think of them as +a drop-in replacement that let you achieve **higher accuracy** in exchange for +**higher training and runtime costs**. -## System requirements +### System requirements We recommend an NVIDIA GPU with at least 10GB of memory in order to work with transformer models. The exact requirements will depend on the transformer you model you choose and whether you're training the pipeline or simply running it. Training a transformer-based model without a GPU will be too slow for most -practical purposes. You'll also need to make sure your GPU drivers are up-to-date -and v9+ of the CUDA runtime is installed. +practical purposes. You'll also need to make sure your GPU drivers are +up-to-date and v9+ of the CUDA runtime is installed. -Once you have CUDA installed, you'll need to install two pip packages, `cupy` -and `spacy-transformers`. [CuPy](https://docs.cupy.dev/en/stable/install.html) +Once you have CUDA installed, you'll need to install two pip packages, +[`cupy`](https://docs.cupy.dev/en/stable/install.html) and +[`spacy-transformers`](https://github.com/explosion/spacy-transformers). `cupy` is just like `numpy`, but for GPU. The best way to install it is to choose a -wheel that matches the version of CUDA you're using. You may also need to set the -`CUDA_PATH` environment variable if your CUDA runtime is installed in -a non-standard location. Putting it all together, if you had installed CUDA 10.2 +wheel that matches the version of CUDA you're using. You may also need to set +the `CUDA_PATH` environment variable if your CUDA runtime is installed in a +non-standard location. Putting it all together, if you had installed CUDA 10.2 in `/opt/nvidia/cuda`, you would run: -``` +```bash +### Installation with CUDA export CUDA_PATH="/opt/nvidia/cuda" pip install cupy-cuda102 pip install spacy-transformers ``` -Provisioning a new machine will require about 5GB of data to be downloaded in total: -3GB for the CUDA runtime, 800MB for PyTorch, 400MB for CuPy, 500MB for the transformer -weights, and about 200MB for spaCy and its various requirements. +Provisioning a new machine will require about 5GB of data to be downloaded in +total: 3GB for the CUDA runtime, 800MB for PyTorch, 400MB for CuPy, 500MB for +the transformer weights, and about 200MB for spaCy and its various requirements. ## Runtime usage {#runtime} @@ -237,23 +240,22 @@ The [`Transformer`](/api/transformer) component expects a Thinc [`Model`](https://thinc.ai/docs/api-model) object to be passed in as its `model` argument. You're not limited to the implementation provided by `spacy-transformers` – the only requirement is that your registered function -must return an object of type `Model[List[Doc], FullTransformerBatch]`: that is, -a Thinc model that takes a list of [`Doc`](/api/doc) objects, and returns a +must return an object of type ~~Model[List[Doc], FullTransformerBatch]~~: that +is, a Thinc model that takes a list of [`Doc`](/api/doc) objects, and returns a [`FullTransformerBatch`](/api/transformer#fulltransformerbatch) object with the transformer data. > #### Model type annotations > > In the documentation and code base, you may come across type annotations and -> descriptions of [Thinc](https://thinc.ai) model types, like -> `Model[List[Doc], List[Floats2d]]`. This so-called generic type describes the -> layer and its input and output type – in this case, it takes a list of `Doc` -> objects as the input and list of 2-dimensional arrays of floats as the output. -> You can read more about defining Thinc -> models [here](https://thinc.ai/docs/usage-models). Also see the -> [type checking](https://thinc.ai/docs/usage-type-checking) for how to enable -> linting in your editor to see live feedback if your inputs and outputs don't -> match. +> descriptions of [Thinc](https://thinc.ai) model types, like ~~Model[List[Doc], +> List[Floats2d]]~~. This so-called generic type describes the layer and its +> input and output type – in this case, it takes a list of `Doc` objects as the +> input and list of 2-dimensional arrays of floats as the output. You can read +> more about defining Thinc models [here](https://thinc.ai/docs/usage-models). +> Also see the [type checking](https://thinc.ai/docs/usage-type-checking) for +> how to enable linting in your editor to see live feedback if your inputs and +> outputs don't match. The same idea applies to task models that power the **downstream components**. Most of spaCy's built-in model creation functions support a `tok2vec` argument, @@ -288,7 +290,7 @@ The [Tok2VecListener](/api/architectures#Tok2VecListener) layer expects a determines how the vector for each spaCy token will be computed from the zero or more source rows the token is aligned against. Here we use the [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean) layer, which -averages the wordpiece rows. We could instead use `reduce_last`, +averages the wordpiece rows. We could instead use [`reduce_max`](https://thinc.ai/docs/api-layers#reduce_max), or a custom function you write yourself. diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index a32f9cd86..7213adf4a 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -231,6 +231,7 @@ on them. | `Language.tagger`, `Language.parser`, `Language.entity` | [`Language.get_pipe`](/api/language#get_pipe) | | keyword-arguments like `vocab=False` on `to_disk`, `from_disk`, `to_bytes`, `from_bytes` | `exclude=["vocab"]` | | `n_threads` argument on [`Tokenizer`](/api/tokenizer), [`Matcher`](/api/matcher), [`PhraseMatcher`](/api/phrasematcher) | `n_process` | +| `verbose` argument on [`Language.evaluate`] | logging | | `SentenceSegmenter` hook, `SimilarityHook` | [user hooks](/usage/processing-pipelines#custom-components-user-hooks), [`Sentencizer`](/api/sentencizer), [`SentenceRecognizer`](/api/sentenceregognizer) | ## Migrating from v2.x {#migrating} diff --git a/website/docs/usage/visualizers.md b/website/docs/usage/visualizers.md index 5db741d52..f33340063 100644 --- a/website/docs/usage/visualizers.md +++ b/website/docs/usage/visualizers.md @@ -58,12 +58,12 @@ arcs. -| Argument | Type | Description | Default | -| --------- | ---- | ----------------------------------------------------------- | ----------- | -| `compact` | bool | "Compact mode" with square arrows that takes up less space. | `False` | -| `color` | str | Text color (HEX, RGB or color names). | `"#000000"` | -| `bg` | str | Background color (HEX, RGB or color names). | `"#ffffff"` | -| `font` | str | Font name or font family for all text. | `"Arial"` | +| Argument | Description | +| --------- | ----------------------------------------------------------------------------------------- | +| `compact` | "Compact mode" with square arrows that takes up less space. Defaults to `False`. ~~bool~~ | +| `color` | Text color (HEX, RGB or color names). Defaults to `"#000000"`. ~~str~~ | +| `bg` | Background color (HEX, RGB or color names). Defaults to `"#ffffff"`. ~~str~~ | +| `font` | Font name or font family for all text. Defaults to `"Arial"`. ~~str~~ | For a list of all available options, see the [`displacy` API documentation](/api/top-level#displacy_options). @@ -121,10 +121,10 @@ import DisplacyEntHtml from 'images/displacy-ent2.html' The entity visualizer lets you customize the following `options`: -| Argument | Type | Description | Default | -| -------- | ---- | ------------------------------------------------------------------------------------- | ------- | -| `ents` | list |  Entity types to highlight (`None` for all types). | `None` | -| `colors` | dict | Color overrides. Entity types in uppercase should be mapped to color names or values. | `{}` | +| Argument | Description | +| -------- | -------------------------------------------------------------------------------------------------------------------------- | +| `ents` | Entity types to highlight (`None` for all types). Defaults to `None`. ~~Optional[List[str]]~~ | `None` | +| `colors` | Color overrides. Entity types in uppercase should be mapped to color names or values. Defaults to `{}`. ~~Dict[str, str]~~ | If you specify a list of `ents`, only those entity types will be rendered – for example, you can choose to display `PERSON` entities. Internally, the visualizer diff --git a/website/meta/sidebars.json b/website/meta/sidebars.json index 230900dcd..6f8763955 100644 --- a/website/meta/sidebars.json +++ b/website/meta/sidebars.json @@ -113,7 +113,6 @@ { "text": "Vectors", "url": "/api/vectors" }, { "text": "Lookups", "url": "/api/lookups" }, { "text": "Morphology", "url": "/api/morphology" }, - { "text": "MorphAnalysis", "url": "/api/morphanalysis" }, { "text": "KnowledgeBase", "url": "/api/kb" }, { "text": "Scorer", "url": "/api/scorer" }, { "text": "Corpus", "url": "/api/corpus" } diff --git a/website/meta/type-annotations.json b/website/meta/type-annotations.json new file mode 100644 index 000000000..9bb1abbf4 --- /dev/null +++ b/website/meta/type-annotations.json @@ -0,0 +1,43 @@ +{ + "Doc": "/api/doc", + "Token": "/api/token", + "Span": "/api/span", + "Lexeme": "/api/lexeme", + "Example": "/api/example", + "Alignment": "/api/example#alignment-object", + "Vocab": "/api/vocab", + "StringStore": "/api/stringstore", + "Lookups": "/api/lookups", + "Table": "/api/lookups#table", + "Vectors": "/api/vectors", + "Language": "/api/language", + "Defaults": "/api/language#defaults", + "Scorer": "/api/scorer", + "DocBin": "/api/docbin", + "FactoryMeta": "/api/language#factorymeta", + "Tokenizer": "/api/tokenizer", + "MorphAnalysis": "/api/morphology#morphanalysis", + "KnowledgeBase": "/api/kb", + "Candidate": "/api/kb#candidate", + "Matcher": "/api/matcher", + "PhraseMatcher": "/api/phrasematcher", + "TransformerData": "/api/transformer#transformerdata", + "FullTransformerBatch": "/api/transformer#fulltransformerbatch", + "LexemeC": "/api/cython-structs#lexemec", + "TokenC": "/api/cython-structs#tokenc", + "Config": "https://thinc.ai/docs/api-config#config", + "Optimizer": "https://thinc.ai/docs/api-optimizers", + "Model": "https://thinc.ai/docs/api-model", + "Ragged": "https://thinc.ai/docs/api-types#ragged", + "Floats2d": "https://thinc.ai/docs/api-types#types", + "Floats3d": "https://thinc.ai/docs/api-types#types", + "FloatsXd": "https://thinc.ai/docs/api-types#types", + "cymem.Pool": "https://github.com/explosion/cymem", + "preshed.BloomFilter": "https://github.com/explosion/preshed", + "transformers.BatchEncoding": "https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.BatchEncoding", + "torch.Tensor": "https://pytorch.org/docs/stable/tensors.html", + "numpy.ndarray": "https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html", + "Match": "https://docs.python.org/3/library/re.html#match-objects", + "Pattern": "https://docs.python.org/3/library/re.html#regular-expression-objects", + "Path": "https://docs.python.org/3/library/pathlib.html" +} diff --git a/website/src/components/code.js b/website/src/components/code.js index 952014ed5..46a55bfc4 100644 --- a/website/src/components/code.js +++ b/website/src/components/code.js @@ -1,4 +1,4 @@ -import React from 'react' +import React, { Fragment } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import highlightCode from 'gatsby-remark-prismjs/highlight-code.js' @@ -6,12 +6,13 @@ import rangeParser from 'parse-numeric-range' import { StaticQuery, graphql } from 'gatsby' import { window } from 'browser-monads' +import CUSTOM_TYPES from '../../meta/type-annotations.json' import { isString, htmlToReact } from './util' import Link from './link' import GitHubCode from './github' import classes from '../styles/code.module.sass' -const WRAP_THRESHOLD = 16 +const WRAP_THRESHOLD = 30 export default props => (
@@ -40,6 +41,52 @@ InlineCode.propTypes = {
     children: PropTypes.node,
 }
 
+function linkType(el, showLink = true) {
+    if (!isString(el) || !el.length) return el
+    const elStr = el.trim()
+    if (!elStr) return el
+    const typeUrl = CUSTOM_TYPES[elStr]
+    const url = typeUrl == true ? DEFAULT_TYPE_URL : typeUrl
+    const ws = el[0] == ' '
+    return url && showLink ? (
+        
+            {ws && ' '}
+            
+                {elStr}
+            
+        
+    ) : (
+        el
+    )
+}
+
+export const TypeAnnotation = ({ lang = 'python', link = true, children }) => {
+    // Hacky, but we're temporarily replacing a dot to prevent it from being split during highlighting
+    const TMP_DOT = '•'
+    const code = Array.isArray(children) ? children.join('') : children || ''
+    const rawStr = code.replace('.', TMP_DOT)
+    const rawHtml = lang === 'none' || !code ? code : highlightCode(lang, rawStr)
+    const html = rawHtml.replace(TMP_DOT, '.').replace(/\n/g, ' ')
+    const result = htmlToReact(html)
+    const elements = Array.isArray(result) ? result : [result]
+    const annotClassNames = classNames(
+        'type-annotation',
+        `language-${lang}`,
+        classes.inlineCode,
+        classes.typeAnnotation,
+        {
+            [classes.wrap]: code.length >= WRAP_THRESHOLD,
+        }
+    )
+    return (
+        
+            {elements.map((el, i) => (
+                {linkType(el, !!link)}
+            ))}
+        
+    )
+}
+
 export class Code extends React.Component {
     state = { Juniper: null }
 
diff --git a/website/src/styles/code.module.sass b/website/src/styles/code.module.sass
index e3a27cbba..bed62d824 100644
--- a/website/src/styles/code.module.sass
+++ b/website/src/styles/code.module.sass
@@ -56,6 +56,38 @@
     --color-inline-code-text: var(--color-back)
     --color-inline-code-bg: var(--color-dark-secondary)
 
+.type-annotation,
+    white-space: pre-wrap
+    font-family: var(--font-code)
+
+    &.wrap
+        word-wrap: break-word
+
+    a
+        border: 0
+
+    // Special style for types in API tables
+    td > &:last-child
+        display: block
+        border-top: 1px dotted var(--color-subtle)
+        border-radius: 0
+        background: none
+        width: calc(100% + 2rem)
+        margin-left: -1rem
+        padding-left: 1rem
+        padding-top: 5px
+        margin-top: 5px
+        margin-bottom: -5px
+
+        &:before
+            content: "Type: "
+            opacity: 0.75
+            font-family: var(--font-primary)
+            color: var(--color-dark-secondary)
+            font-weight: bold
+            text-transform: uppercase
+            margin-right: 5px
+
 .wrap
     white-space: pre-wrap
     word-wrap: anywhere
diff --git a/website/src/styles/layout.sass b/website/src/styles/layout.sass
index 3591fb005..82612c103 100644
--- a/website/src/styles/layout.sass
+++ b/website/src/styles/layout.sass
@@ -358,6 +358,15 @@ body [id]:target
     &.italic
         font-style: italic
 
+
+[class*="language-"].type-annotation .token
+    &.builtin, &.boolean, &.number
+        color: var(--color-inline-code-text)
+
+    &.operator
+        color: var(--syntax-comment)
+
+
 // Settings for ini syntax (config files)
 [class*="language-ini"]
     color: var(--syntax-comment)
diff --git a/website/src/styles/table.module.sass b/website/src/styles/table.module.sass
index 6306e2a15..b46c1af92 100644
--- a/website/src/styles/table.module.sass
+++ b/website/src/styles/table.module.sass
@@ -29,7 +29,8 @@
         border: 0
 
 .td
-    padding: 1rem
+    padding: 0.9rem 1rem
+    font-size: 95%
 
     &:not(:last-child)
         border-right: 1px solid var(--color-subtle)
diff --git a/website/src/templates/index.js b/website/src/templates/index.js
index c97663317..027241d97 100644
--- a/website/src/templates/index.js
+++ b/website/src/templates/index.js
@@ -20,7 +20,7 @@ import SEO from '../components/seo'
 import Link from '../components/link'
 import Section, { Hr } from '../components/section'
 import { Table, Tr, Th, Td } from '../components/table'
-import { Pre, Code, InlineCode } from '../components/code'
+import { Pre, Code, InlineCode, TypeAnnotation } from '../components/code'
 import { Ol, Ul, Li } from '../components/list'
 import { H2, H3, H4, H5, P, Abbr, Help } from '../components/typography'
 import Accordion from '../components/accordion'
@@ -41,6 +41,7 @@ const mdxComponents = {
     pre: Pre,
     code: Code,
     inlineCode: InlineCode,
+    del: TypeAnnotation,
     table: Table,
     img: Image,
     tr: Tr,

From 4fe4bab1c9c9c3ab60c75fe11006f1cac29f4b41 Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Mon, 17 Aug 2020 17:10:15 +0200
Subject: [PATCH 08/92] typo fixes

---
 website/docs/api/data-formats.md | 2 +-
 website/docs/usage/training.md   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md
index 4577d7ef3..8ed3232ec 100644
--- a/website/docs/api/data-formats.md
+++ b/website/docs/api/data-formats.md
@@ -161,7 +161,7 @@ run [`spacy pretrain`](/api/cli#pretrain).
 | `dropout`                    | The dropout rate. ~~float~~                                                                                 | `0.2`                                               |
 | `n_save_every`               | Saving frequency. ~~int~~                                                                                   | `null`                                              |
 | `batch_size`                 | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). ~~Union[int, Sequence[int]]~~ | `3000`                                              |
-| `seed`                       | The random seed. ~~int~~                                                                                    | `${system.seed}`                                    |
+| `seed`                       | The random seed. ~~int~~                                                                                    | `${system:seed}`                                    |
 | `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. ~~bool~~                                                                       | `${system:use_pytorch_for_gpu_memory}`              |
 | `tok2vec_model`              | tok2vec model section in the config. ~~str~~                                                                | `"components.tok2vec.model"`                        |
 | `objective`                  | The pretraining objective. ~~Dict[str, Any]~~                                                               | `{"type": "characters", "n_characters": 4}`         |
diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index f605750f6..94c5ea1cb 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -144,7 +144,7 @@ https://github.com/explosion/spaCy/blob/develop/spacy/default_config.cfg
 
 Under the hood, the config is parsed into a dictionary. It's divided into
 sections and subsections, indicated by the square brackets and dot notation. For
-example, `[training]` is a section and `[training.batch_size]` a subsections.
+example, `[training]` is a section and `[training.batch_size]` a subsection.
 Subsections can define values, just like a dictionary, or use the `@` syntax to
 refer to [registered functions](#config-functions). This allows the config to
 not just define static settings, but also construct objects like architectures,
@@ -156,7 +156,7 @@ sections of a config file are:
 | `nlp`         | Definition of the `nlp` object, its tokenizer and [processing pipeline](/usage/processing-pipelines) component names.                                           |
 | `components`  | Definitions of the [pipeline components](/usage/processing-pipelines) and their models.                                                                         |
 | `paths`       | Paths to data and other assets. Re-used across the config as variables, e.g. `${paths:train}`, and can be [overwritten](#config-overrides) on the CLI.          |
-| `system`      | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system.seed}`, and can be [overwritten](#config-overrides) on the CLI. |
+| `system`      | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system:seed}`, and can be [overwritten](#config-overrides) on the CLI. |
 | `training`    | Settings and controls for the training and evaluation process.                                                                                                  |
 | `pretraining` | Optional settings and controls for the [language model pretraining](#pretraining).                                                                              |
 

From 990c6b4c323c49317bf62dcd6a6a681fd013e5e0 Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Mon, 17 Aug 2020 21:38:20 +0200
Subject: [PATCH 09/92] Update docs and CLI [ci skip]

---
 spacy/cli/debug_data.py             |  18 +-
 website/docs/api/cli.md             | 408 ++++++++++++++--------------
 website/src/components/code.js      |   4 +-
 website/src/components/table.js     |   2 +-
 website/src/styles/code.module.sass |   4 +
 5 files changed, 214 insertions(+), 222 deletions(-)

diff --git a/spacy/cli/debug_data.py b/spacy/cli/debug_data.py
index 27cf033c4..b23705311 100644
--- a/spacy/cli/debug_data.py
+++ b/spacy/cli/debug_data.py
@@ -3,7 +3,7 @@ from pathlib import Path
 from collections import Counter
 import sys
 import srsly
-from wasabi import Printer, MESSAGES, msg, diff_strings
+from wasabi import Printer, MESSAGES, msg
 import typer
 
 from ._util import app, Arg, Opt, show_validation_error, parse_config_overrides
@@ -32,8 +32,6 @@ def debug_config_cli(
     ctx: typer.Context,  # This is only used to read additional arguments
     config_path: Path = Arg(..., help="Path to config file", exists=True),
     code_path: Optional[Path] = Opt(None, "--code-path", "-c", help="Path to Python file with additional code (registered functions) to be imported"),
-    auto_fill: bool = Opt(False, "--auto-fill", "-F", help="Whether or not to auto-fill the config with built-in defaults if possible"),
-    diff: bool = Opt(False, "--diff", "-D", help="Show a visual diff if config was auto-filled")
     # fmt: on
 ):
     """Debug a config.cfg file and show validation errors. The command will
@@ -49,18 +47,8 @@ def debug_config_cli(
     import_code(code_path)
     with show_validation_error(config_path):
         config = util.load_config(config_path, overrides=overrides)
-        nlp, _ = util.load_model_from_config(config, auto_fill=auto_fill)
-    if auto_fill:
-        orig_config = config.to_str()
-        filled_config = nlp.config.to_str()
-        if orig_config == filled_config:
-            msg.good("Original config is valid, no values were auto-filled")
-        else:
-            msg.good("Auto-filled config is valid")
-            if diff:
-                print(diff_strings(config.to_str(), nlp.config.to_str()))
-    else:
-        msg.good("Original config is valid")
+        nlp, _ = util.load_model_from_config(config)
+    msg.good("Original config is valid")
 
 
 @debug_cli.command(
diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md
index c9e2a28c1..ec61eb0b5 100644
--- a/website/docs/api/cli.md
+++ b/website/docs/api/cli.md
@@ -3,17 +3,17 @@ title: Command Line Interface
 teaser: Download, train and package models, and debug spaCy
 source: spacy/cli
 menu:
-  - ['Download', 'download']
-  - ['Info', 'info']
-  - ['Validate', 'validate']
-  - ['Init', 'init']
-  - ['Convert', 'convert']
-  - ['Debug', 'debug']
-  - ['Train', 'train']
-  - ['Pretrain', 'pretrain']
-  - ['Evaluate', 'evaluate']
-  - ['Package', 'package']
-  - ['Project', 'project']
+  - ['download', 'download']
+  - ['info', 'info']
+  - ['validate', 'validate']
+  - ['init', 'init']
+  - ['convert', 'convert']
+  - ['debug', 'debug']
+  - ['train', 'train']
+  - ['pretrain', 'pretrain']
+  - ['evaluate', 'evaluate']
+  - ['package', 'package']
+  - ['project', 'project']
 ---
 
 spaCy's CLI provides a range of helpful commands for downloading and training
@@ -22,7 +22,7 @@ list of available commands, you can type `python -m spacy --help`. You can also
 add the `--help` flag to any command or subcommand to see the description,
 available arguments and usage.
 
-## Download {#download}
+## download {#download tag="command"}
 
 Download [models](/usage/models) for spaCy. The downloader finds the
 best-matching compatible version and uses `pip install` to download the model as
@@ -43,15 +43,15 @@ the model name to be specified with its version (e.g. `en_core_web_sm-2.2.0`).
 $ python -m spacy download [model] [--direct] [pip args]
 ```
 
-| Argument                              | Type          | Description                                                                                                                                                                                                    |
-| ------------------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `model`                               | positional    | Model name, e.g. [`en_core_web_sm`](/models/en#en_core_web_sm).                                                                                                                                                |
-| `--direct`, `-d`                      | flag          | Force direct download of exact model version.                                                                                                                                                                  |
-| `--help`, `-h`                        | flag          | Show help message and available arguments.                                                                                                                                                                     |
-| pip args 2.1 | option / flag | Additional installation options to be passed to `pip install` when installing the model package. For example, `--user` to install to the user home directory or `--no-deps` to not install model dependencies. |
-| **CREATES**                           | directory     | The installed model package in your `site-packages` directory.                                                                                                                                                 |
+| Name                                  | Description                                                                                                                                                                                                                          |
+| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `model`                               | Model name, e.g. [`en_core_web_sm`](/models/en#en_core_web_sm). ~~str (positional)~~                                                                                                                                                 |
+| `--direct`, `-d`                      | Force direct download of exact model version. ~~bool (flag)~~                                                                                                                                                                        |
+| `--help`, `-h`                        | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                                                           |
+| pip args 2.1 | Additional installation options to be passed to `pip install` when installing the model package. For example, `--user` to install to the user home directory or `--no-deps` to not install model dependencies. ~~Any (option/flag)~~ |
+| **CREATES**                           | The installed model package in your `site-packages` directory.                                                                                                                                                                       |
 
-## Info {#info}
+## info {#info tag="command"}
 
 Print information about your spaCy installation, models and local setup, and
 generate [Markdown](https://en.wikipedia.org/wiki/Markdown)-formatted markup to
@@ -65,15 +65,15 @@ $ python -m spacy info [--markdown] [--silent]
 $ python -m spacy info [model] [--markdown] [--silent]
 ```
 
-| Argument                                         | Type       | Description                                    |
-| ------------------------------------------------ | ---------- | ---------------------------------------------- |
-| `model`                                          | positional | A model, i.e. package name or path (optional). |
-| `--markdown`, `-md`                              | flag       | Print information as Markdown.                 |
-| `--silent`, `-s` 2.0.12 | flag       | Don't print anything, just return the values.  |
-| `--help`, `-h`                                   | flag       | Show help message and available arguments.     |
-| **PRINTS**                                       | `stdout`   | Information about your spaCy installation.     |
+| Name                                             | Description                                                                    |
+| ------------------------------------------------ | ------------------------------------------------------------------------------ |
+| `model`                                          | A model, i.e. package name or path (optional). ~~Optional[str] \(positional)~~ |
+| `--markdown`, `-md`                              | Print information as Markdown. ~~bool (flag)~~                                 |
+| `--silent`, `-s` 2.0.12 | Don't print anything, just return the values. ~~bool (flag)~~                  |
+| `--help`, `-h`                                   | Show help message and available arguments. ~~bool (flag)~~                     |
+| **PRINTS**                                       | Information about your spaCy installation.                                     |
 
-## Validate {#validate new="2"}
+## validate {#validate new="2" tag="command"}
 
 Find all models installed in the current environment and check whether they are
 compatible with the currently installed version of spaCy. Should be run after
@@ -92,16 +92,16 @@ and command for updating are shown.
 $ python -m spacy validate
 ```
 
-| Argument   | Type     | Description                                               |
-| ---------- | -------- | --------------------------------------------------------- |
-| **PRINTS** | `stdout` | Details about the compatibility of your installed models. |
+| Name       | Description                                               |
+| ---------- | --------------------------------------------------------- |
+| **PRINTS** | Details about the compatibility of your installed models. |
 
-## Init {#init new="3"}
+## init {#init new="3"}
 
 The `spacy init` CLI includes helpful commands for initializing training config
 files and model directories.
 
-### init config {#init-config new="3"}
+### init config {#init-config new="3" tag="command"}
 
 Initialize and save a [`config.cfg` file](/usage/training#config) using the
 **recommended settings** for your use case. It works just like the
@@ -121,15 +121,15 @@ $ python -m spacy init config [output_file] [--lang] [--pipeline]
 [--optimize] [--cpu]
 ```
 
-| Argument           | Type       | Description                                                                                                                                                                                                                                                                                                       |
-| ------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `output_file`      | positional | Path to output `.cfg` file. If not set, the config is written to stdout so you can pipe it forward to a file.                                                                                                                                                                                                     |
-| `--lang`, `-l`     | option     | Optional code of the [language](/usage/models#languages) to use. Defaults to `"en"`.                                                                                                                                                                                                                              |
-| `--pipeline`, `-p` | option     | Comma-separated list of trainable [pipeline components](/usage/processing-pipelines#built-in) to include in the model. Defaults to `"tagger,parser,ner"`.                                                                                                                                                         |
-| `--optimize`, `-o` | option     | `"efficiency"` or `"accuracy"`. Whether to optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger and slower model). This will impact the choice of architecture, pretrained weights and related hyperparameters. Defaults to `"efficiency"`. |
-| `--cpu`, `-C`      | flag       | Whether the model needs to run on CPU. This will impact the choice of architecture, pretrained weights and related hyperparameters.                                                                                                                                                                               |
-| `--help`, `-h`     | flag       | Show help message and available arguments.                                                                                                                                                                                                                                                                        |
-| **CREATES**        | file       | The config file for training.                                                                                                                                                                                                                                                                                     |
+| Name               | Description                                                                                                                                                                                                                                                                                                                        |
+| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `output_file`      | Path to output `.cfg` file. If not set, the config is written to stdout so you can pipe it forward to a file. ~~Path (positional)~~                                                                                                                                                                                                |
+| `--lang`, `-l`     | Optional code of the [language](/usage/models#languages) to use. Defaults to `"en"`. ~~str (option)~~                                                                                                                                                                                                                              |
+| `--pipeline`, `-p` | Comma-separated list of trainable [pipeline components](/usage/processing-pipelines#built-in) to include in the model. Defaults to `"tagger,parser,ner"`. ~~str (option)~~                                                                                                                                                         |
+| `--optimize`, `-o` | `"efficiency"` or `"accuracy"`. Whether to optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger and slower model). This will impact the choice of architecture, pretrained weights and related hyperparameters. Defaults to `"efficiency"`. ~~str (option)~~ |
+| `--cpu`, `-C`      | Whether the model needs to run on CPU. This will impact the choice of architecture, pretrained weights and related hyperparameters. ~~bool (flag)~~                                                                                                                                                                                |
+| `--help`, `-h`     | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                                                                                                                                                         |
+| **CREATES**        | The config file for training.                                                                                                                                                                                                                                                                                                      |
 
 ### init fill-config {#init-fill-config new="3"}
 
@@ -152,15 +152,15 @@ validation error with more details.
 $ python -m spacy init fill-config [base_path] [output_file] [--diff]
 ```
 
-| Argument       | Type       | Description                                                                                                   |
-| -------------- | ---------- | ------------------------------------------------------------------------------------------------------------- |
-| `base_path`    | positional | Path to base config to fill, e.g. generated by the [quickstart widget](/usage/training#quickstart).           |
-| `output_file`  | positional | Path to output `.cfg` file. If not set, the config is written to stdout so you can pipe it forward to a file. |
-| `--diff`, `-D` | flag       | Print a visual diff highlighting the changes.                                                                 |
-| `--help`, `-h` | flag       | Show help message and available arguments.                                                                    |
-| **CREATES**    | file       | Complete and auto-filled config file for training.                                                            |
+| Name           | Description                                                                                                                         |
+| -------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `base_path`    | Path to base config to fill, e.g. generated by the [quickstart widget](/usage/training#quickstart). ~~Path (positional)~~           |
+| `output_file`  | Path to output `.cfg` file. If not set, the config is written to stdout so you can pipe it forward to a file. ~~Path (positional)~~ |
+| `--diff`, `-D` | Print a visual diff highlighting the changes. ~~bool (flag)~~                                                                       |
+| `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~                                                                          |
+| **CREATES**    | Complete and auto-filled config file for training.                                                                                  |
 
-### init model {#init-model new="2"}
+### init model {#init-model new="2" tag="command"}
 
 
 
@@ -182,19 +182,19 @@ $ python -m spacy init model [lang] [output_dir] [--jsonl-loc] [--vectors-loc]
 [--prune-vectors]
 ```
 
-| Argument                                                | Type       | Description                                                                                                                                                                                                                                            |
-| ------------------------------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `lang`                                                  | positional | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), e.g. `en`.                                                                                                                                                           |
-| `output_dir`                                            | positional | Model output directory. Will be created if it doesn't exist.                                                                                                                                                                                           |
-| `--jsonl-loc`, `-j`                                     | option     | Optional location of JSONL-formatted [vocabulary file](/api/data-formats#vocab-jsonl) with lexical attributes.                                                                                                                                         |
-| `--vectors-loc`, `-v`                                   | option     | Optional location of vectors. Should be a file where the first row contains the dimensions of the vectors, followed by a space-separated Word2Vec table. File can be provided in `.txt` format or as a zipped text file in `.zip` or `.tar.gz` format. |
-| `--truncate-vectors`, `-t` 2.3 | option     | Number of vectors to truncate to when reading in vectors file. Defaults to `0` for no truncation.                                                                                                                                                      |
-| `--prune-vectors`, `-V`                                 | option     | Number of vectors to prune the vocabulary to. Defaults to `-1` for no pruning.                                                                                                                                                                         |
-| `--vectors-name`, `-vn`                                 | option     | Name to assign to the word vectors in the `meta.json`, e.g. `en_core_web_md.vectors`.                                                                                                                                                                  |
-| `--help`, `-h`                                          | flag       | Show help message and available arguments.                                                                                                                                                                                                             |
-| **CREATES**                                             | model      | A spaCy model containing the vocab and vectors.                                                                                                                                                                                                        |
+| Name                                                    | Description                                                                                                                                                                                                                                                                         |
+| ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `lang`                                                  | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes), e.g. `en`. ~~str (positional)~~                                                                                                                                                                   |
+| `output_dir`                                            | Model output directory. Will be created if it doesn't exist. ~~Path (positional)~~                                                                                                                                                                                                  |
+| `--jsonl-loc`, `-j`                                     | Optional location of JSONL-formatted [vocabulary file](/api/data-formats#vocab-jsonl) with lexical attributes. ~~Optional[Path] \(option)~~                                                                                                                                         |
+| `--vectors-loc`, `-v`                                   | Optional location of vectors. Should be a file where the first row contains the dimensions of the vectors, followed by a space-separated Word2Vec table. File can be provided in `.txt` format or as a zipped text file in `.zip` or `.tar.gz` format. ~~Optional[Path] \(option)~~ |
+| `--truncate-vectors`, `-t` 2.3 | Number of vectors to truncate to when reading in vectors file. Defaults to `0` for no truncation. ~~int (option)~~                                                                                                                                                                  |
+| `--prune-vectors`, `-V`                                 | Number of vectors to prune the vocabulary to. Defaults to `-1` for no pruning. ~~int (option)~~                                                                                                                                                                                     |
+| `--vectors-name`, `-vn`                                 | Name to assign to the word vectors in the `meta.json`, e.g. `en_core_web_md.vectors`. ~~str (option)~~                                                                                                                                                                              |
+| `--help`, `-h`                                          | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                                                                                                          |
+| **CREATES**                                             | A spaCy model containing the vocab and vectors.                                                                                                                                                                                                                                     |
 
-## Convert {#convert}
+## convert {#convert tag="command"}
 
 Convert files into spaCy's
 [binary training data format](/api/data-formats#binary-training), a serialized
@@ -208,22 +208,22 @@ $ python -m spacy convert [input_file] [output_dir] [--converter]
 [--merge-subtokens] [--ner-map] [--lang]
 ```
 
-| Argument                                         | Type       | Description                                                                                                              |
-| ------------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------ |
-| `input_file`                                     | positional | Input file.                                                                                                              |
-| `output_dir`                                     | positional | Output directory for converted file. Defaults to `"-"`, meaning data will be written to `stdout`.                        |
-| `--converter`, `-c` 2   | option     | Name of converter to use (see below).                                                                                    |
-| `--file-type`, `-t` 2.1 | option     | Type of file to create. Either `spacy` (default) for binary [`DocBin`](/api/docbin) data or `json` for v2.x JSON format. |
-| `--n-sents`, `-n`                                | option     | Number of sentences per document.                                                                                        |
-| `--seg-sents`, `-s` 2.2 | flag       | Segment sentences (for `-c ner`)                                                                                         |
-| `--model`, `-b` 2.2     | option     | Model for parser-based sentence segmentation (for `-s`)                                                                  |
-| `--morphology`, `-m`                             | option     | Enable appending morphology to tags.                                                                                     |
-| `--ner-map`, `-nm`                               | option     | NER tag mapping (as JSON-encoded dict of entity types).                                                                  |
-| `--lang`, `-l` 2.1      | option     | Language code (if tokenizer required).                                                                                   |
-| `--help`, `-h`                                   | flag       | Show help message and available arguments.                                                                               |
-| **CREATES**                                      | binary     | Binary [`DocBin`](/api/docbin) training data that can be used with [`spacy train`](/api/cli#train).                      |
+| Name                                             | Description                                                                                                                               |
+| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- |
+| `input_file`                                     | Input file. ~~Path (positional)~~                                                                                                         |
+| `output_dir`                                     | Output directory for converted file. Defaults to `"-"`, meaning data will be written to `stdout`. ~~Optional[Path] \(positional)~~        |
+| `--converter`, `-c` 2   | Name of converter to use (see below). ~~str (option)~~                                                                                    |
+| `--file-type`, `-t` 2.1 | Type of file to create. Either `spacy` (default) for binary [`DocBin`](/api/docbin) data or `json` for v2.x JSON format. ~~str (option)~~ |
+| `--n-sents`, `-n`                                | Number of sentences per document. ~~int (option)~~                                                                                        |
+| `--seg-sents`, `-s` 2.2 | Segment sentences (for `--converter ner`). ~~bool (flag)~~                                                                                |
+| `--model`, `-b` 2.2     | Model for parser-based sentence segmentation (for `--seg-sents`). ~~Optional[str](option)~~                                               |
+| `--morphology`, `-m`                             | Enable appending morphology to tags. ~~bool (flag)~~                                                                                      |
+| `--ner-map`, `-nm`                               | NER tag mapping (as JSON-encoded dict of entity types). ~~Optional[Path](option)~~                                                        |
+| `--lang`, `-l` 2.1      | Language code (if tokenizer required). ~~Optional[str] \(option)~~                                                                        |
+| `--help`, `-h`                                   | Show help message and available arguments. ~~bool (flag)~~                                                                                |
+| **CREATES**                                      | Binary [`DocBin`](/api/docbin) training data that can be used with [`spacy train`](/api/cli#train).                                       |
 
-### Converters
+### Converters {#converters}
 
 | ID      | Description                                                                                                                                                                                                                                                                                                                                                                                    |
 | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@@ -233,12 +233,12 @@ $ python -m spacy convert [input_file] [output_dir] [--converter]
 | `ner`   | NER with IOB/IOB2 tags, one token per line with columns separated by whitespace. The first column is the token and the final column is the IOB tag. Sentences are separated by blank lines and documents are separated by the line `-DOCSTART- -X- O O`. Supports CoNLL 2003 NER format. See [sample data](https://github.com/explosion/spaCy/tree/master/examples/training/ner_example_data). |
 | `iob`   | NER with IOB/IOB2 tags, one sentence per line with tokens separated by whitespace and annotation separated by `|`, either `word|B-ENT` or `word|POS|B-ENT`. See [sample data](https://github.com/explosion/spaCy/tree/master/examples/training/ner_example_data).                                                                                                                              |
 
-## Debug {#debug new="3"}
+## debug {#debug new="3"}
 
 The `spacy debug` CLI includes helpful commands for debugging and profiling your
 configs, data and implementations.
 
-### debug config {#debug-config}
+### debug config {#debug-config new="3" tag="command"}
 
 Debug a [`config.cfg` file](/usage/training#config) and show validation errors.
 The command will create all objects in the tree and validate them. Note that
@@ -246,10 +246,10 @@ some config validation errors are blocking and will prevent the rest of the
 config from being resolved. This means that you may not see all validation
 errors at once and some issues are only shown once previous errors have been
 fixed. To auto-fill a partial config and save the result, you can use the
-[`init config`](/api/cli#init-config) command.
+[`init fillconfig`](/api/cli#init-fill-config) command.
 
 ```bash
-$ python -m spacy debug config [config_path] [--code_path] [--output] [--auto_fill] [--diff] [overrides]
+$ python -m spacy debug config [config_path] [--code_path] [overrides]
 ```
 
 > #### Example
@@ -277,18 +277,15 @@ python -m spacy init fill-config tmp/starter-config_invalid.cfg --base tmp/start
 
 
 
-| Argument              | Type          | Default                                                                                                                                                              | Description |
-| --------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
-| `config_path`         | positional    | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters.                                                                |
-| `--code_path`, `-c`   | option        | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures.                 |
-| `--auto_fill`, `-F`   | option        | Whether or not to auto-fill the config with built-in defaults if possible. If `False`, the provided config needs to be complete.                                     |
-| `--output_path`, `-o` | option        | Output path where the filled config can be stored. Use '-' for standard output.                                                                                      |
-| `--diff`, `-D`        | option        | `Show a visual diff if config was auto-filled.                                                                                                                       |
-| `--help`, `-h`        | flag          | Show help message and available arguments.                                                                                                                           |
-| overrides             | option / flag | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. |
-| **PRINTS**            | stdout        | Config validation errors, if available.                                                                                                                              |
+| Name                | Description                                                                                                                                                                                |
+| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `config_path`       | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~                                                                |
+| `--code_path`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~          |
+| `--help`, `-h`      | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                 |
+| overrides           | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. ~~Any (option/flag)~~ |
+| **PRINTS**          | Config validation errors, if available.                                                                                                                                                    |
 
-### debug data {#debug-data}
+### debug data {#debug-data tag="command"}
 
 Analyze, debug, and validate your training and development data. Get useful
 stats, and find problems like invalid entity annotations, cyclic dependencies,
@@ -453,18 +450,18 @@ will not be available.
 
 
 
-| Argument                   | Type          | Description                                                                                                                                                          |
-| -------------------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `config_path`              | positional    | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters.                                                                |
-| `--code`, `-c`             | option        | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures.                 |
-| `--ignore-warnings`, `-IW` | flag          | Ignore warnings, only show stats and errors.                                                                                                                         |
-| `--verbose`, `-V`          | flag          | Print additional information and explanations.                                                                                                                       |
-| `--no-format`, `-NF`       | flag          | Don't pretty-print the results. Use this if you want to write to a file.                                                                                             |
-| `--help`, `-h`             | flag          | Show help message and available arguments.                                                                                                                           |
-| overrides                  | option / flag | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. |
-| **PRINTS**                 | stdout        | Debugging information.                                                                                                                                               |
+| Name                       | Description                                                                                                                                                                                |
+| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `config_path`              | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~                                                                |
+| `--code`, `-c`             | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~          |
+| `--ignore-warnings`, `-IW` | Ignore warnings, only show stats and errors. ~~bool (flag)~~                                                                                                                               |
+| `--verbose`, `-V`          | Print additional information and explanations. ~~bool (flag)~~                                                                                                                             |
+| `--no-format`, `-NF`       | Don't pretty-print the results. Use this if you want to write to a file. ~~bool (flag)~~                                                                                                   |
+| `--help`, `-h`             | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                 |
+| overrides                  | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. ~~Any (option/flag)~~ |
+| **PRINTS**                 | Debugging information.                                                                                                                                                                     |
 
-### debug profile {#debug-profile}
+### debug profile {#debug-profile tag="command"}
 
 Profile which functions take the most time in a spaCy pipeline. Input should be
 formatted as one JSON object per line with a key `"text"`. It can either be
@@ -482,15 +479,15 @@ The `profile` command is now available as a subcommand of `spacy debug`.
 $ python -m spacy debug profile [model] [inputs] [--n-texts]
 ```
 
-| Argument          | Type       | Description                                                       |
-| ----------------- | ---------- | ----------------------------------------------------------------- |
-| `model`           | positional | A loadable spaCy model.                                           |
-| `inputs`          | positional | Optional path to input file, or `-` for standard input.           |
-| `--n-texts`, `-n` | option     | Maximum number of texts to use if available. Defaults to `10000`. |
-| `--help`, `-h`    | flag       | Show help message and available arguments.                        |
-| **PRINTS**        | stdout     | Profiling information for the model.                              |
+| Name              | Description                                                                        |
+| ----------------- | ---------------------------------------------------------------------------------- |
+| `model`           | A loadable spaCy model. ~~str (positional)~~                                       |
+| `inputs`          | Optional path to input file, or `-` for standard input. ~~Path (positional)~~      |
+| `--n-texts`, `-n` | Maximum number of texts to use if available. Defaults to `10000`. ~~int (option)~~ |
+| `--help`, `-h`    | Show help message and available arguments. ~~bool (flag)~~                         |
+| **PRINTS**        | Profiling information for the model.                                               |
 
-### debug model {#debug-model}
+### debug model {#debug-model new="3" tag="command"}
 
 Debug a Thinc [`Model`](https://thinc.ai/docs/api-model) by running it on a
 sample text and checking how it updates its internal weights and parameters.
@@ -596,23 +593,24 @@ $ python -m spacy debug model ./config.cfg tagger -l "5,15" -DIM -PAR -P0 -P1 -P
 
 
 
-| Argument                | Type       | Description                                                                                           |
-| ----------------------- | ---------- | ----------------------------------------------------------------------------------------------------- |
-| `config_path`           | positional | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. |
-| `component`             | positional | Name of the pipeline component of which the model should be analyzed.                                 |
-| `--layers`, `-l`        | option     | Comma-separated names of layer IDs to print.                                                          |
-| `--dimensions`, `-DIM`  | option     | Show dimensions of each layer.                                                                        |
-| `--parameters`, `-PAR`  | option     | Show parameters of each layer.                                                                        |
-| `--gradients`, `-GRAD`  | option     | Show gradients of each layer.                                                                         |
-| `--attributes`, `-ATTR` | option     | Show attributes of each layer.                                                                        |
-| `--print-step0`, `-P0`  | option     | Print model before training.                                                                          |
-| `--print-step1`, `-P1`  | option     | Print model after initialization.                                                                     |
-| `--print-step2`, `-P2`  | option     | Print model after training.                                                                           |
-| `--print-step3`, `-P3`  | option     | Print final predictions.                                                                              |
-| `--help`, `-h`          | flag       | Show help message and available arguments.                                                            |
-| **PRINTS**              | stdout     | Debugging information.                                                                                |
+| Name                    | Description                                                                                                                 |
+| ----------------------- | --------------------------------------------------------------------------------------------------------------------------- |
+| `config_path`           | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~ |
+| `component`             | Name of the pipeline component of which the model should be analyzed. ~~str (positional)~~                                  |
+| `--layers`, `-l`        | Comma-separated names of layer IDs to print. ~~str (option)~~                                                               |
+| `--dimensions`, `-DIM`  | Show dimensions of each layer. ~~bool (flag)~~                                                                              |
+| `--parameters`, `-PAR`  | Show parameters of each layer. ~~bool (flag)~~                                                                              |
+| `--gradients`, `-GRAD`  | Show gradients of each layer. ~~bool (flag)~~                                                                               |
+| `--attributes`, `-ATTR` | Show attributes of each layer. ~~bool (flag)~~                                                                              |
+| `--print-step0`, `-P0`  | Print model before training. ~~bool (flag)~~                                                                                |
+| `--print-step1`, `-P1`  | Print model after initialization. ~~bool (flag)~~                                                                           |
+| `--print-step2`, `-P2`  | Print model after training. ~~bool (flag)~~                                                                                 |
+| `--print-step3`, `-P3`  | Print final predictions. ~~bool (flag)~~                                                                                    |
+| `--gpu-id`, `-g`        | GPU ID or `-1` for CPU. Defaults to `-1`. ~~int (option)~~                                                                  |
+| `--help`, `-h`          | Show help message and available arguments. ~~bool (flag)~~                                                                  |
+| **PRINTS**              | Debugging information.                                                                                                      |
 
-## Train {#train}
+## train {#train tag="command"}
 
 Train a model. Expects data in spaCy's
 [binary format](/api/data-formats#training) and a
@@ -640,17 +638,17 @@ in the section `[paths]`.
 $ python -m spacy train [config_path] [--output] [--code] [--verbose] [overrides]
 ```
 
-| Argument          | Type          | Description                                                                                                                                                          |
-| ----------------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `config_path`     | positional    | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters.                                                                |
-| `--output`, `-o`  | positional    | Directory to store model in. Will be created if it doesn't exist.                                                                                                    |
-| `--code`, `-c`    | option        | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures.                 |
-| `--verbose`, `-V` | flag          | Show more detailed messages during training.                                                                                                                         |
-| `--help`, `-h`    | flag          | Show help message and available arguments.                                                                                                                           |
-| overrides         | option / flag | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. |
-| **CREATES**       | model         | The final model and the best model.                                                                                                                                  |
+| Name              | Description                                                                                                                                                                                |
+| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `config_path`     | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~                                                                |
+| `--output`, `-o`  | Directory to store model in. Will be created if it doesn't exist. ~~Optional[Path] \(positional)~~                                                                                         |
+| `--code`, `-c`    | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~          |
+| `--verbose`, `-V` | Show more detailed messages during training. ~~bool (flag)~~                                                                                                                               |
+| `--help`, `-h`    | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                 |
+| overrides         | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. ~~Any (option/flag)~~ |
+| **CREATES**       | The final model and the best model.                                                                                                                                                        |
 
-## Pretrain {#pretrain new="2.1" tag="experimental"}
+## pretrain {#pretrain new="2.1" tag="command,experimental"}
 
 Pretrain the "token to vector" ([`Tok2vec`](/api/tok2vec)) layer of pipeline
 components on [raw text](/api/data-formats#pretrain), using an approximate
@@ -678,19 +676,19 @@ $ python -m spacy pretrain [texts_loc] [output_dir] [config_path]
 [--code] [--resume-path] [--epoch-resume] [overrides]
 ```
 
-| Argument                | Type          | Description                                                                                                                                                                  |
-| ----------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `texts_loc`             | positional    | Path to JSONL file with raw texts to learn from, with text provided as the key `"text"` or tokens as the key `"tokens"`. [See here](/api/data-formats#pretrain) for details. |
-| `output_dir`            | positional    | Directory to write models to on each epoch.                                                                                                                                  |
-| `config_path`           | positional    | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters.                                                                        |
-| `--code`, `-c`          | option        | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures.                         |
-| `--resume-path`, `-r`   | option        | Path to pretrained weights from which to resume pretraining.                                                                                                                 |
-| `--epoch-resume`, `-er` | option        | The epoch to resume counting from when using `--resume-path`. Prevents unintended overwriting of existing weight files.                                                      |
-| `--help`, `-h`          | flag          | Show help message and available arguments.                                                                                                                                   |
-| overrides               | option / flag | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--training.use_gpu 1`.                |
-| **CREATES**             | weights       | The pretrained weights that can be used to initialize `spacy train`.                                                                                                         |
+| Name                    | Description                                                                                                                                                                                        |
+| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `texts_loc`             | Path to JSONL file with raw texts to learn from, with text provided as the key `"text"` or tokens as the key `"tokens"`. [See here](/api/data-formats#pretrain) for details. ~~Path (positional)~~ |
+| `output_dir`            | Directory to write models to on each epoch. ~~Path (positional)~~                                                                                                                                  |
+| `config_path`           | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~                                                                        |
+| `--code`, `-c`          | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~                  |
+| `--resume-path`, `-r`   | Path to pretrained weights from which to resume pretraining. ~~Optional[Path] \(option)~~                                                                                                          |
+| `--epoch-resume`, `-er` | The epoch to resume counting from when using `--resume-path`. Prevents unintended overwriting of existing weight files. ~~Optional[int] \(option)~~                                                |
+| `--help`, `-h`          | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                         |
+| overrides               | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--training.dropout 0.2`. ~~Any (option/flag)~~              |
+| **CREATES**             | The pretrained weights that can be used to initialize `spacy train`.                                                                                                                               |
 
-## Evaluate {#evaluate new="2"}
+## evaluate {#evaluate new="2" tag="command"}
 
 Evaluate a model. Expects a loadable spaCy model and evaluation data in the
 [binary `.spacy` format](/api/data-formats#binary-training). The
@@ -707,19 +705,19 @@ $ python -m spacy evaluate [model] [data_path] [--output] [--gold-preproc]
 [--gpu-id] [--displacy-path] [--displacy-limit]
 ```
 
-| Argument                  | Type                 | Description                                                                                                                                              |
-| ------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `model`                   | positional           | Model to evaluate. Can be a package or a path to a model data directory.                                                                                 |
-| `data_path`               | positional           | Location of evaluation data in spaCy's [binary format](/api/data-formats#training).                                                                      |
-| `--output`, `-o`          | option               | Output JSON file for metrics. If not set, no metrics will be exported.                                                                                   |
-| `--gold-preproc`, `-G`    | flag                 | Use gold preprocessing.                                                                                                                                  |
-| `--gpu-id`, `-g`          | option               | GPU to use, if any. Defaults to `-1` for CPU.                                                                                                            |
-| `--displacy-path`, `-dp`  | option               | Directory to output rendered parses as HTML. If not set, no visualizations will be generated.                                                            |
-| `--displacy-limit`, `-dl` | option               | Number of parses to generate per file. Defaults to `25`. Keep in mind that a significantly higher number might cause the `.html` files to render slowly. |
-| `--help`, `-h`            | flag                 | Show help message and available arguments.                                                                                                               |
-| **CREATES**               | `stdout`, JSON, HTML | Training results and optional metrics and visualizations.                                                                                                |
+| Name                      | Description                                                                                                                                                               |
+| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `model`                   | Model to evaluate. Can be a package or a path to a model data directory. ~~str (positional)~~                                                                             |
+| `data_path`               | Location of evaluation data in spaCy's [binary format](/api/data-formats#training). ~~Path (positional)~~                                                                 |
+| `--output`, `-o`          | Output JSON file for metrics. If not set, no metrics will be exported. ~~Optional[Path] \(option)~~                                                                       |
+| `--gold-preproc`, `-G`    | Use gold preprocessing. ~~bool (flag)~~                                                                                                                                   |
+| `--gpu-id`, `-g`          | GPU to use, if any. Defaults to `-1` for CPU. ~~int (option)~~                                                                                                            |
+| `--displacy-path`, `-dp`  | Directory to output rendered parses as HTML. If not set, no visualizations will be generated. ~~Optional[Path] \(option)~~                                                |
+| `--displacy-limit`, `-dl` | Number of parses to generate per file. Defaults to `25`. Keep in mind that a significantly higher number might cause the `.html` files to render slowly. ~~int (option)~~ |
+| `--help`, `-h`            | Show help message and available arguments. ~~bool (flag)~~                                                                                                                |
+| **CREATES**               | Training results and optional metrics and visualizations.                                                                                                                 |
 
-## Package {#package}
+## package {#package tag="command"}
 
 Generate an installable
 [model Python package](/usage/training#models-generating) from an existing model
@@ -750,25 +748,25 @@ $ python -m spacy package [input_dir] [output_dir] [--meta-path] [--create-meta]
 > pip install dist/en_model-0.0.0.tar.gz
 > ```
 
-| Argument                                         | Type       | Description                                                                                                                                                                                     |
-| ------------------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `input_dir`                                      | positional | Path to directory containing model data.                                                                                                                                                        |
-| `output_dir`                                     | positional | Directory to create package folder in.                                                                                                                                                          |
-| `--meta-path`, `-m` 2   | option     | Path to `meta.json` file (optional).                                                                                                                                                            |
-| `--create-meta`, `-C` 2 | flag       | Create a `meta.json` file on the command line, even if one already exists in the directory. If an existing file is found, its entries will be shown as the defaults in the command line prompt. |
-| `--no-sdist`, `-NS`,                             | flag       | Don't build the `.tar.gz` sdist automatically. Can be set if you want to run this step manually.                                                                                                |
-| `--version`, `-v` 3     | option     | Package version to override in meta. Useful when training new versions, as it doesn't require editing the meta template.                                                                        |
-| `--force`, `-f`                                  | flag       | Force overwriting of existing folder in output directory.                                                                                                                                       |
-| `--help`, `-h`                                   | flag       | Show help message and available arguments.                                                                                                                                                      |
-| **CREATES**                                      | directory  | A Python package containing the spaCy model.                                                                                                                                                    |
+| Name                                             | Description                                                                                                                                                                                                     |
+| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `input_dir`                                      | Path to directory containing model data. ~~Path (positional)~~                                                                                                                                                  |
+| `output_dir`                                     | Directory to create package folder in. ~~Path (positional)~~                                                                                                                                                    |
+| `--meta-path`, `-m` 2   | Path to `meta.json` file (optional). ~~Optional[Path] \(option)~~                                                                                                                                               |
+| `--create-meta`, `-C` 2 | Create a `meta.json` file on the command line, even if one already exists in the directory. If an existing file is found, its entries will be shown as the defaults in the command line prompt. ~~bool (flag)~~ |
+| `--no-sdist`, `-NS`,                             | Don't build the `.tar.gz` sdist automatically. Can be set if you want to run this step manually. ~~bool (flag)~~                                                                                                |
+| `--version`, `-v` 3     | Package version to override in meta. Useful when training new versions, as it doesn't require editing the meta template. ~~Optional[str] \(option)~~                                                            |
+| `--force`, `-f`                                  | Force overwriting of existing folder in output directory. ~~bool (flag)~~                                                                                                                                       |
+| `--help`, `-h`                                   | Show help message and available arguments. ~~bool (flag)~~                                                                                                                                                      |
+| **CREATES**                                      | A Python package containing the spaCy model.                                                                                                                                                                    |
 
-## Project {#project new="3"}
+## project {#project new="3"}
 
 The `spacy project` CLI includes subcommands for working with
 [spaCy projects](/usage/projects), end-to-end workflows for building and
 deploying custom spaCy models.
 
-### project clone {#project-clone}
+### project clone {#project-clone tag="command"}
 
 Clone a project template from a Git repository. Calls into `git` under the hood
 and uses the sparse checkout feature, so you're only downloading what you need.
@@ -795,15 +793,15 @@ $ python -m spacy project clone [name] [dest] [--repo]
 > $ python -m spacy project clone template --repo https://github.com/your_org/your_repo
 > ```
 
-| Argument       | Type       | Description                                                                                                                  |
-| -------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------- |
-| `name`         | positional | The name of the template to clone, relative to the repo. Can be a top-level directory or a subdirectory like `dir/template`. |
-| `dest`         | positional | Where to clone the project. Defaults to current working directory.                                                           |
-| `--repo`, `-r` | option     | The repository to clone from. Can be any public or private Git repo you have access to.                                      |
-| `--help`, `-h` | flag       | Show help message and available arguments.                                                                                   |
-| **CREATES**    | directory  | The cloned [project directory](/usage/projects#project-files).                                                               |
+| Name           | Description                                                                                                                                       |
+| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name`         | The name of the template to clone, relative to the repo. Can be a top-level directory or a subdirectory like `dir/template`. ~~str (positional)~~ |
+| `dest`         | Where to clone the project. Defaults to current working directory. ~~Path (positional)~~                                                          |
+| `--repo`, `-r` | The repository to clone from. Can be any public or private Git repo you have access to. ~~str (option)~~                                          |
+| `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~                                                                                        |
+| **CREATES**    | The cloned [project directory](/usage/projects#project-files).                                                                                    |
 
-### project assets {#project-assets}
+### project assets {#project-assets tag="command"}
 
 Fetch project assets like datasets and pretrained weights. Assets are defined in
 the `assets` section of the [`project.yml`](/usage/projects#project-yml). If a
@@ -824,13 +822,13 @@ $ python -m spacy project assets [project_dir]
 > $ python -m spacy project assets
 > ```
 
-| Argument       | Type       | Description                                                       |
-| -------------- | ---------- | ----------------------------------------------------------------- |
-| `project_dir`  | positional | Path to project directory. Defaults to current working directory. |
-| `--help`, `-h` | flag       | Show help message and available arguments.                        |
-| **CREATES**    | files      | Downloaded or copied assets defined in the `project.yml`.         |
+| Name           | Description                                                                             |
+| -------------- | --------------------------------------------------------------------------------------- |
+| `project_dir`  | Path to project directory. Defaults to current working directory. ~~Path (positional)~~ |
+| `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~                              |
+| **CREATES**    | Downloaded or copied assets defined in the `project.yml`.                               |
 
-### project run {#project-run}
+### project run {#project-run tag="command"}
 
 Run a named command or workflow defined in the
 [`project.yml`](/usage/projects#project-yml). If a workflow name is specified,
@@ -849,16 +847,16 @@ $ python -m spacy project run [subcommand] [project_dir] [--force] [--dry]
 > $ python -m spacy project run train
 > ```
 
-| Argument        | Type       | Description                                                       |
-| --------------- | ---------- | ----------------------------------------------------------------- |
-| `subcommand`    | positional | Name of the command or workflow to run.                           |
-| `project_dir`   | positional | Path to project directory. Defaults to current working directory. |
-| `--force`, `-F` | flag       | Force re-running steps, even if nothing changed.                  |
-| `--dry`, `-D`   | flag       |  Perform a dry run and don't execute scripts.                     |
-| `--help`, `-h`  | flag       | Show help message and available arguments.                        |
-| **EXECUTES**    | script     | The command defined in the `project.yml`.                         |
+| Name            | Description                                                                             |
+| --------------- | --------------------------------------------------------------------------------------- |
+| `subcommand`    | Name of the command or workflow to run. ~~str (positional)~~                            |
+| `project_dir`   | Path to project directory. Defaults to current working directory. ~~Path (positional)~~ |
+| `--force`, `-F` | Force re-running steps, even if nothing changed. ~~bool (flag)~~                        |
+| `--dry`, `-D`   |  Perform a dry run and don't execute scripts. ~~bool (flag)~~                           |
+| `--help`, `-h`  | Show help message and available arguments. ~~bool (flag)~~                              |
+| **EXECUTES**    | The command defined in the `project.yml`.                                               |
 
-### project dvc {#project-dvc}
+### project dvc {#project-dvc tag="command"}
 
 Auto-generate [Data Version Control](https://dvc.org) (DVC) config file. Calls
 [`dvc run`](https://dvc.org/doc/command-reference/run) with `--no-exec` under
@@ -890,11 +888,11 @@ $ python -m spacy project dvc [project_dir] [workflow] [--force] [--verbose]
 > python -m spacy project dvc all
 > ```
 
-| Argument          | Type       | Description                                                                                   |
-| ----------------- | ---------- | --------------------------------------------------------------------------------------------- |
-| `project_dir`     | positional | Path to project directory. Defaults to current working directory.                             |
-| `workflow`        | positional | Name of workflow defined in `project.yml`. Defaults to first workflow if not set.             |
-| `--force`, `-F`   | flag       | Force-updating config file.                                                                   |
-| `--verbose`, `-V` | flag       |  Print more output generated by DVC.                                                          |
-| `--help`, `-h`    | flag       | Show help message and available arguments.                                                    |
-| **CREATES**       | file       | A `dvc.yaml` file in the project directory, based on the steps defined in the given workflow. |
+| Name              | Description                                                                                                       |
+| ----------------- | ----------------------------------------------------------------------------------------------------------------- |
+| `project_dir`     | Path to project directory. Defaults to current working directory. ~~Path (positional)~~                           |
+| `workflow`        | Name of workflow defined in `project.yml`. Defaults to first workflow if not set. ~~Optional[str] \(positional)~~ |
+| `--force`, `-F`   | Force-updating config file. ~~bool (flag)~~                                                                       |
+| `--verbose`, `-V` |  Print more output generated by DVC. ~~bool (flag)~~                                                              |
+| `--help`, `-h`    | Show help message and available arguments. ~~bool (flag)~~                                                        |
+| **CREATES**       | A `dvc.yaml` file in the project directory, based on the steps defined in the given workflow.                     |
diff --git a/website/src/components/code.js b/website/src/components/code.js
index 46a55bfc4..f514c752d 100644
--- a/website/src/components/code.js
+++ b/website/src/components/code.js
@@ -64,7 +64,8 @@ export const TypeAnnotation = ({ lang = 'python', link = true, children }) => {
     // Hacky, but we're temporarily replacing a dot to prevent it from being split during highlighting
     const TMP_DOT = '•'
     const code = Array.isArray(children) ? children.join('') : children || ''
-    const rawStr = code.replace('.', TMP_DOT)
+    const [rawText, meta] = code.split(/(?= \(.+\)$)/)
+    const rawStr = rawText.replace('.', TMP_DOT)
     const rawHtml = lang === 'none' || !code ? code : highlightCode(lang, rawStr)
     const html = rawHtml.replace(TMP_DOT, '.').replace(/\n/g, ' ')
     const result = htmlToReact(html)
@@ -83,6 +84,7 @@ export const TypeAnnotation = ({ lang = 'python', link = true, children }) => {
             {elements.map((el, i) => (
                 {linkType(el, !!link)}
             ))}
+            {meta && {meta}}
         
     )
 }
diff --git a/website/src/components/table.js b/website/src/components/table.js
index 1a7d460d0..3d442cde7 100644
--- a/website/src/components/table.js
+++ b/website/src/components/table.js
@@ -37,7 +37,7 @@ function isDividerRow(children) {
 }
 
 function isFootRow(children) {
-    const rowRegex = /^(RETURNS|YIELDS|CREATES|PRINTS)/
+    const rowRegex = /^(RETURNS|YIELDS|CREATES|PRINTS|EXECUTES)/
     if (children.length && children[0].props.name === 'td') {
         const cellChildren = children[0].props.children
         if (
diff --git a/website/src/styles/code.module.sass b/website/src/styles/code.module.sass
index bed62d824..3ff1fae6b 100644
--- a/website/src/styles/code.module.sass
+++ b/website/src/styles/code.module.sass
@@ -88,6 +88,10 @@
             text-transform: uppercase
             margin-right: 5px
 
+.type-annotation-meta
+    font-size: 90%
+    color: var(--color-subtle-dark)
+
 .wrap
     white-space: pre-wrap
     word-wrap: anywhere

From 728fec0194224ef2d58172511dc087c274b57e4e Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Tue, 18 Aug 2020 00:49:19 +0200
Subject: [PATCH 10/92] Update docs [ci skip]

---
 netlify.toml                                  |   4 +-
 website/docs/api/architectures.md             |   6 +-
 website/docs/api/cli.md                       |  12 +-
 website/docs/api/top-level.md                 |   2 +-
 website/docs/api/transformer.md               |   3 +-
 website/docs/usage/101/_vectors-similarity.md |  14 +-
 website/docs/usage/embeddings-transformers.md | 459 ++++++++++++++++++
 website/docs/usage/linguistic-features.md     | 157 +++++-
 website/docs/usage/processing-pipelines.md    |  10 +-
 website/docs/usage/spacy-101.md               |   2 +-
 website/docs/usage/training.md                |   6 +-
 website/docs/usage/v2.md                      |   2 +-
 website/docs/usage/v3.md                      |  16 +-
 website/docs/usage/vectors-embeddings.md      | 340 -------------
 website/meta/sidebars.json                    |   7 +-
 website/src/components/typography.js          |  10 +-
 16 files changed, 665 insertions(+), 385 deletions(-)
 create mode 100644 website/docs/usage/embeddings-transformers.md
 delete mode 100644 website/docs/usage/vectors-embeddings.md

diff --git a/netlify.toml b/netlify.toml
index 6afa5ed7e..2f3e350e6 100644
--- a/netlify.toml
+++ b/netlify.toml
@@ -36,11 +36,11 @@ redirects = [
     {from = "/docs/api/features", to = "/models/#architecture", force = true},
     {from = "/docs/api/philosophy", to = "/usage/spacy-101", force = true},
     {from = "/docs/usage/showcase", to = "/universe", force = true},
-    {from = "/tutorials/load-new-word-vectors", to = "/usage/vectors-similarity#custom", force = true},
+    {from = "/tutorials/load-new-word-vectors", to = "/usage/linguistic-features", force = true},
     {from = "/tutorials", to = "/usage/examples", force = true},
     # Old documentation pages (v2.x)
     {from = "/usage/adding-languages", to = "/usage/linguistic-features", force = true},
-    {from = "/usage/vectors-similarity", to = "/usage/vectors-embeddings", force = true},
+    {from = "/usage/vectors-similarity", to = "/usage/linguistic-features#vectors-similarity", force = true},
     {from = "/api/goldparse", to = "/api/top-level", force = true},
     {from = "/api/goldcorpus", to = "/api/corpus", force = true},
     {from = "/api/annotation", to = "/api/data-formats", force = true},
diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md
index 3bc2ab578..8bb5cdeea 100644
--- a/website/docs/api/architectures.md
+++ b/website/docs/api/architectures.md
@@ -243,11 +243,15 @@ Encode context using bidirectional LSTM layers. Requires
 | `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~                                                                                           |
 | `depth`       | The number of convolutional layers. Recommended value is `4`. ~~int~~                                                                                                                                          |
 
+### spacy.StaticVectors.v1 {#StaticVectors}
+
+
+
 ## Transformer architectures {#transformers source="github.com/explosion/spacy-transformers/blob/master/spacy_transformers/architectures.py"}
 
 The following architectures are provided by the package
 [`spacy-transformers`](https://github.com/explosion/spacy-transformers). See the
-[usage documentation](/usage/transformers) for how to integrate the
+[usage documentation](/usage/embeddings-transformers) for how to integrate the
 architectures into your training config.
 
 ### spacy-transformers.TransformerModel.v1 {#TransformerModel}
diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md
index ec61eb0b5..b614898df 100644
--- a/website/docs/api/cli.md
+++ b/website/docs/api/cli.md
@@ -162,14 +162,12 @@ $ python -m spacy init fill-config [base_path] [output_file] [--diff]
 
 ### init model {#init-model new="2" tag="command"}
 
-
-
 Create a new model directory from raw data, like word frequencies, Brown
-clusters and word vectors. This command is similar to the `spacy model` command
-in v1.x. Note that in order to populate the model's vocab, you need to pass in a
-JSONL-formatted [vocabulary file](/api/data-formats#vocab-jsonl) as
-`--jsonl-loc` with optional `id` values that correspond to the vectors table.
-Just loading in vectors will not automatically populate the vocab.
+clusters and word vectors. Note that in order to populate the model's vocab, you
+need to pass in a JSONL-formatted
+[vocabulary file](/api/data-formats#vocab-jsonl) as `--jsonl-loc` with optional
+`id` values that correspond to the vectors table. Just loading in vectors will
+not automatically populate the vocab.
 
 
 
diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md
index 3bce3db93..0f87b8fd0 100644
--- a/website/docs/api/top-level.md
+++ b/website/docs/api/top-level.md
@@ -316,7 +316,7 @@ factories.
 The following registries are added by the
 [`spacy-transformers`](https://github.com/explosion/spacy-transformers) package.
 See the [`Transformer`](/api/transformer) API reference and
-[usage docs](/usage/transformers) for details.
+[usage docs](/usage/embeddings-transformers) for details.
 
 > #### Example
 >
diff --git a/website/docs/api/transformer.md b/website/docs/api/transformer.md
index 19cb4daa2..d4d7de161 100644
--- a/website/docs/api/transformer.md
+++ b/website/docs/api/transformer.md
@@ -41,7 +41,8 @@ token, the spaCy token receives the sum of their values. To access the values,
 you can use the custom [`Doc._.trf_data`](#custom-attributes) attribute. The
 package also adds the function registries [`@span_getters`](#span_getters) and
 [`@annotation_setters`](#annotation_setters) with several built-in registered
-functions. For more details, see the [usage documentation](/usage/transformers).
+functions. For more details, see the
+[usage documentation](/usage/embeddings-transformers).
 
 ## Config and implementation {#config}
 
diff --git a/website/docs/usage/101/_vectors-similarity.md b/website/docs/usage/101/_vectors-similarity.md
index 9ff55f815..a04c96236 100644
--- a/website/docs/usage/101/_vectors-similarity.md
+++ b/website/docs/usage/101/_vectors-similarity.md
@@ -77,12 +77,14 @@ or flagging duplicates. For example, you can suggest a user content that's
 similar to what they're currently looking at, or label a support ticket as a
 duplicate if it's very similar to an already existing one.
 
-Each `Doc`, `Span` and `Token` comes with a
-[`.similarity()`](/api/token#similarity) method that lets you compare it with
-another object, and determine the similarity. Of course similarity is always
-subjective – whether "dog" and "cat" are similar really depends on how you're
-looking at it. spaCy's similarity model usually assumes a pretty general-purpose
-definition of similarity.
+Each [`Doc`](/api/doc), [`Span`](/api/span), [`Token`](/api/token) and
+[`Lexeme`](/api/lexeme) comes with a [`.similarity`](/api/token#similarity)
+method that lets you compare it with another object, and determine the
+similarity. Of course similarity is always subjective – whether "dog" and "cat"
+are similar really depends on how you're looking at it. spaCy's similarity model
+usually assumes a pretty general-purpose definition of similarity.
+
+
 
 ```python
 ### {executable="true"}
diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md
new file mode 100644
index 000000000..23037a3ab
--- /dev/null
+++ b/website/docs/usage/embeddings-transformers.md
@@ -0,0 +1,459 @@
+---
+title: Embeddings, Transformers and Transfer Learning
+teaser: Using transformer embeddings like BERT in spaCy
+menu:
+  - ['Embedding Layers', 'embedding-layers']
+  - ['Transformers', 'transformers']
+  - ['Static Vectors', 'static-vectors']
+  - ['Pretraining', 'pretraining']
+next: /usage/training
+---
+
+
+
+## Shared embedding layers {#embedding-layers}
+
+
+
+
+
+The key difference between [word vectors](#word-vectors) and contextual language
+models such as [transformers](#transformers) is that word vectors model
+**lexical types**, rather than _tokens_. If you have a list of terms with no
+context around them, a transformer model like BERT can't really help you. BERT
+is designed to understand language **in context**, which isn't what you have. A
+word vectors table will be a much better fit for your task. However, if you do
+have words in context — whole sentences or paragraphs of running text — word
+vectors will only provide a very rough approximation of what the text is about.
+
+Word vectors are also very computationally efficient, as they map a word to a
+vector with a single indexing operation. Word vectors are therefore useful as a
+way to **improve the accuracy** of neural network models, especially models that
+are small or have received little or no pretraining. In spaCy, word vector
+tables are only used as **static features**. spaCy does not backpropagate
+gradients to the pretrained word vectors table. The static vectors table is
+usually used in combination with a smaller table of learned task-specific
+embeddings.
+
+
+
+
+
+Word vectors are not compatible with most [transformer models](#transformers),
+but if you're training another type of NLP network, it's almost always worth
+adding word vectors to your model. As well as improving your final accuracy,
+word vectors often make experiments more consistent, as the accuracy you reach
+will be less sensitive to how the network is randomly initialized. High variance
+due to random chance can slow down your progress significantly, as you need to
+run many experiments to filter the signal from the noise.
+
+Word vector features need to be enabled prior to training, and the same word
+vectors table will need to be available at runtime as well. You cannot add word
+vector features once the model has already been trained, and you usually cannot
+replace one word vectors table with another without causing a significant loss
+of performance.
+
+
+
+## Using transformer models {#transformers}
+
+Transformers are a family of neural network architectures that compute **dense,
+context-sensitive representations** for the tokens in your documents. Downstream
+models in your pipeline can then use these representations as input features to
+**improve their predictions**. You can connect multiple components to a single
+transformer model, with any or all of those components giving feedback to the
+transformer to fine-tune it to your tasks. spaCy's transformer support
+interoperates with [PyTorch](https://pytorch.org) and the
+[HuggingFace `transformers`](https://huggingface.co/transformers/) library,
+giving you access to thousands of pretrained models for your pipelines. There
+are many [great guides](http://jalammar.github.io/illustrated-transformer/) to
+transformer models, but for practical purposes, you can simply think of them as
+a drop-in replacement that let you achieve **higher accuracy** in exchange for
+**higher training and runtime costs**.
+
+### Setup and installation {#transformers-installation}
+
+> #### System requirements
+>
+> We recommend an NVIDIA **GPU** with at least **10GB of memory** in order to
+> work with transformer models. Make sure your GPU drivers are up to date and
+> you have **CUDA v9+** installed.
+
+> The exact requirements will depend on the transformer model. Training a
+> transformer-based model without a GPU will be too slow for most practical
+> purposes.
+>
+> Provisioning a new machine will require about **5GB** of data to be
+> downloaded: 3GB CUDA runtime, 800MB PyTorch, 400MB CuPy, 500MB weights, 200MB
+> spaCy and dependencies.
+
+Once you have CUDA installed, you'll need to install two pip packages,
+[`cupy`](https://docs.cupy.dev/en/stable/install.html) and
+[`spacy-transformers`](https://github.com/explosion/spacy-transformers). `cupy`
+is just like `numpy`, but for GPU. The best way to install it is to choose a
+wheel that matches the version of CUDA you're using. You may also need to set
+the `CUDA_PATH` environment variable if your CUDA runtime is installed in a
+non-standard location. Putting it all together, if you had installed CUDA 10.2
+in `/opt/nvidia/cuda`, you would run:
+
+```bash
+### Installation with CUDA
+export CUDA_PATH="/opt/nvidia/cuda"
+pip install cupy-cuda102
+pip install spacy-transformers
+```
+
+### Runtime usage {#transformers-runtime}
+
+Transformer models can be used as **drop-in replacements** for other types of
+neural networks, so your spaCy pipeline can include them in a way that's
+completely invisible to the user. Users will download, load and use the model in
+the standard way, like any other spaCy pipeline. Instead of using the
+transformers as subnetworks directly, you can also use them via the
+[`Transformer`](/api/transformer) pipeline component.
+
+![The processing pipeline with the transformer component](../images/pipeline_transformer.svg)
+
+The `Transformer` component sets the
+[`Doc._.trf_data`](/api/transformer#custom_attributes) extension attribute,
+which lets you access the transformers outputs at runtime.
+
+```bash
+$ python -m spacy download en_core_trf_lg
+```
+
+```python
+### Example
+import spacy
+from thinc.api import use_pytorch_for_gpu_memory, require_gpu
+
+# Use the GPU, with memory allocations directed via PyTorch.
+# This prevents out-of-memory errors that would otherwise occur from competing
+# memory pools.
+use_pytorch_for_gpu_memory()
+require_gpu(0)
+
+nlp = spacy.load("en_core_trf_lg")
+for doc in nlp.pipe(["some text", "some other text"]):
+    tokvecs = doc._.trf_data.tensors[-1]
+```
+
+You can also customize how the [`Transformer`](/api/transformer) component sets
+annotations onto the [`Doc`](/api/doc), by customizing the `annotation_setter`.
+This callback will be called with the raw input and output data for the whole
+batch, along with the batch of `Doc` objects, allowing you to implement whatever
+you need. The annotation setter is called with a batch of [`Doc`](/api/doc)
+objects and a [`FullTransformerBatch`](/api/transformer#fulltransformerbatch)
+containing the transformers data for the batch.
+
+```python
+def custom_annotation_setter(docs, trf_data):
+    # TODO:
+    ...
+
+nlp = spacy.load("en_core_trf_lg")
+nlp.get_pipe("transformer").annotation_setter = custom_annotation_setter
+doc = nlp("This is a text")
+print()  # TODO:
+```
+
+### Training usage {#transformers-training}
+
+The recommended workflow for training is to use spaCy's
+[config system](/usage/training#config), usually via the
+[`spacy train`](/api/cli#train) command. The training config defines all
+component settings and hyperparameters in one place and lets you describe a tree
+of objects by referring to creation functions, including functions you register
+yourself. For details on how to get started with training your own model, check
+out the [training quickstart](/usage/training#quickstart).
+
+
+
+The easiest way to get started is to clone a transformers-based project
+template. Swap in your data, edit the settings and hyperparameters and train,
+evaluate, package and visualize your model.
+
+
+
+The `[components]` section in the [`config.cfg`](/api/data-formats#config)
+describes the pipeline components and the settings used to construct them,
+including their model implementation. Here's a config snippet for the
+[`Transformer`](/api/transformer) component, along with matching Python code. In
+this case, the `[components.transformer]` block describes the `transformer`
+component:
+
+> #### Python equivalent
+>
+> ```python
+> from spacy_transformers import Transformer, TransformerModel
+> from spacy_transformers.annotation_setters import null_annotation_setter
+> from spacy_transformers.span_getters import get_doc_spans
+>
+> trf = Transformer(
+>     nlp.vocab,
+>     TransformerModel(
+>         "bert-base-cased",
+>         get_spans=get_doc_spans,
+>         tokenizer_config={"use_fast": True},
+>     ),
+>     annotation_setter=null_annotation_setter,
+>     max_batch_items=4096,
+> )
+> ```
+
+```ini
+### config.cfg (excerpt)
+[components.transformer]
+factory = "transformer"
+max_batch_items = 4096
+
+[components.transformer.model]
+@architectures = "spacy-transformers.TransformerModel.v1"
+name = "bert-base-cased"
+tokenizer_config = {"use_fast": true}
+
+[components.transformer.model.get_spans]
+@span_getters = "doc_spans.v1"
+
+[components.transformer.annotation_setter]
+@annotation_setters = "spacy-transformer.null_annotation_setter.v1"
+
+```
+
+The `[components.transformer.model]` block describes the `model` argument passed
+to the transformer component. It's a Thinc
+[`Model`](https://thinc.ai/docs/api-model) object that will be passed into the
+component. Here, it references the function
+[spacy-transformers.TransformerModel.v1](/api/architectures#TransformerModel)
+registered in the [`architectures` registry](/api/top-level#registry). If a key
+in a block starts with `@`, it's **resolved to a function** and all other
+settings are passed to the function as arguments. In this case, `name`,
+`tokenizer_config` and `get_spans`.
+
+`get_spans` is a function that takes a batch of `Doc` object and returns lists
+of potentially overlapping `Span` objects to process by the transformer. Several
+[built-in functions](/api/transformer#span-getters) are available – for example,
+to process the whole document or individual sentences. When the config is
+resolved, the function is created and passed into the model as an argument.
+
+
+
+Remember that the `config.cfg` used for training should contain **no missing
+values** and requires all settings to be defined. You don't want any hidden
+defaults creeping in and changing your results! spaCy will tell you if settings
+are missing, and you can run
+[`spacy init fill-config`](/api/cli#init-fill-config) to automatically fill in
+all defaults.
+
+
+
+### Customizing the settings {#transformers-training-custom-settings}
+
+To change any of the settings, you can edit the `config.cfg` and re-run the
+training. To change any of the functions, like the span getter, you can replace
+the name of the referenced function – e.g. `@span_getters = "sent_spans.v1"` to
+process sentences. You can also register your own functions using the
+`span_getters` registry:
+
+> #### config.cfg
+>
+> ```ini
+> [components.transformer.model.get_spans]
+> @span_getters = "custom_sent_spans"
+> ```
+
+```python
+### code.py
+import spacy_transformers
+
+@spacy_transformers.registry.span_getters("custom_sent_spans")
+def configure_custom_sent_spans():
+    # TODO: write custom example
+    def get_sent_spans(docs):
+        return [list(doc.sents) for doc in docs]
+
+    return get_sent_spans
+```
+
+To resolve the config during training, spaCy needs to know about your custom
+function. You can make it available via the `--code` argument that can point to
+a Python file. For more details on training with custom code, see the
+[training documentation](/usage/training#custom-code).
+
+```bash
+$ python -m spacy train ./config.cfg --code ./code.py
+```
+
+### Customizing the model implementations {#training-custom-model}
+
+The [`Transformer`](/api/transformer) component expects a Thinc
+[`Model`](https://thinc.ai/docs/api-model) object to be passed in as its `model`
+argument. You're not limited to the implementation provided by
+`spacy-transformers` – the only requirement is that your registered function
+must return an object of type ~~Model[List[Doc], FullTransformerBatch]~~: that
+is, a Thinc model that takes a list of [`Doc`](/api/doc) objects, and returns a
+[`FullTransformerBatch`](/api/transformer#fulltransformerbatch) object with the
+transformer data.
+
+> #### Model type annotations
+>
+> In the documentation and code base, you may come across type annotations and
+> descriptions of [Thinc](https://thinc.ai) model types, like ~~Model[List[Doc],
+> List[Floats2d]]~~. This so-called generic type describes the layer and its
+> input and output type – in this case, it takes a list of `Doc` objects as the
+> input and list of 2-dimensional arrays of floats as the output. You can read
+> more about defining Thinc models [here](https://thinc.ai/docs/usage-models).
+> Also see the [type checking](https://thinc.ai/docs/usage-type-checking) for
+> how to enable linting in your editor to see live feedback if your inputs and
+> outputs don't match.
+
+The same idea applies to task models that power the **downstream components**.
+Most of spaCy's built-in model creation functions support a `tok2vec` argument,
+which should be a Thinc layer of type ~~Model[List[Doc], List[Floats2d]]~~. This
+is where we'll plug in our transformer model, using the
+[Tok2VecListener](/api/architectures#Tok2VecListener) layer, which sneakily
+delegates to the `Transformer` pipeline component.
+
+```ini
+### config.cfg (excerpt) {highlight="12"}
+[components.ner]
+factory = "ner"
+
+[nlp.pipeline.ner.model]
+@architectures = "spacy.TransitionBasedParser.v1"
+nr_feature_tokens = 3
+hidden_width = 128
+maxout_pieces = 3
+use_upper = false
+
+[nlp.pipeline.ner.model.tok2vec]
+@architectures = "spacy-transformers.Tok2VecListener.v1"
+grad_factor = 1.0
+
+[nlp.pipeline.ner.model.tok2vec.pooling]
+@layers = "reduce_mean.v1"
+```
+
+The [Tok2VecListener](/api/architectures#Tok2VecListener) layer expects a
+[pooling layer](https://thinc.ai/docs/api-layers#reduction-ops) as the argument
+`pooling`, which needs to be of type ~~Model[Ragged, Floats2d]~~. This layer
+determines how the vector for each spaCy token will be computed from the zero or
+more source rows the token is aligned against. Here we use the
+[`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean) layer, which
+averages the wordpiece rows. We could instead use
+[`reduce_max`](https://thinc.ai/docs/api-layers#reduce_max), or a custom
+function you write yourself.
+
+You can have multiple components all listening to the same transformer model,
+and all passing gradients back to it. By default, all of the gradients will be
+**equally weighted**. You can control this with the `grad_factor` setting, which
+lets you reweight the gradients from the different listeners. For instance,
+setting `grad_factor = 0` would disable gradients from one of the listeners,
+while `grad_factor = 2.0` would multiply them by 2. This is similar to having a
+custom learning rate for each component. Instead of a constant, you can also
+provide a schedule, allowing you to freeze the shared parameters at the start of
+training.
+
+## Static vectors {#static-vectors}
+
+
+
+### Using word vectors in your models {#word-vectors-models}
+
+Many neural network models are able to use word vector tables as additional
+features, which sometimes results in significant improvements in accuracy.
+spaCy's built-in embedding layer,
+[MultiHashEmbed](/api/architectures#MultiHashEmbed), can be configured to use
+word vector tables using the `also_use_static_vectors` flag. This setting is
+also available on the [MultiHashEmbedCNN](/api/architectures#MultiHashEmbedCNN)
+layer, which builds the default token-to-vector encoding architecture.
+
+```ini
+[tagger.model.tok2vec.embed]
+@architectures = "spacy.MultiHashEmbed.v1"
+width = 128
+rows = 7000
+also_embed_subwords = true
+also_use_static_vectors = true
+```
+
+
+
+The configuration system will look up the string `"spacy.MultiHashEmbed.v1"` in
+the `architectures` [registry](/api/top-level#registry), and call the returned
+object with the rest of the arguments from the block. This will result in a call
+to the
+[`MultiHashEmbed`](https://github.com/explosion/spacy/tree/develop/spacy/ml/models/tok2vec.py)
+function, which will return a [Thinc](https://thinc.ai) model object with the
+type signature ~~Model[List[Doc], List[Floats2d]]~~. Because the embedding layer
+takes a list of `Doc` objects as input, it does not need to store a copy of the
+vectors table. The vectors will be retrieved from the `Doc` objects that are
+passed in, via the `doc.vocab.vectors` attribute. This part of the process is
+handled by the [StaticVectors](/api/architectures#StaticVectors) layer.
+
+
+
+#### Creating a custom embedding layer {#custom-embedding-layer}
+
+The [MultiHashEmbed](/api/architectures#StaticVectors) layer is spaCy's
+recommended strategy for constructing initial word representations for your
+neural network models, but you can also implement your own. You can register any
+function to a string name, and then reference that function within your config
+(see the [training docs](/usage/training) for more details). To try this out,
+you can save the following little example to a new Python file:
+
+```python
+from spacy.ml.staticvectors import StaticVectors
+from spacy.util import registry
+
+print("I was imported!")
+
+@registry.architectures("my_example.MyEmbedding.v1")
+def MyEmbedding(output_width: int) -> Model[List[Doc], List[Floats2d]]:
+    print("I was called!")
+    return StaticVectors(nO=output_width)
+```
+
+If you pass the path to your file to the [`spacy train`](/api/cli#train) command
+using the `--code` argument, your file will be imported, which means the
+decorator registering the function will be run. Your function is now on equal
+footing with any of spaCy's built-ins, so you can drop it in instead of any
+other model with the same input and output signature. For instance, you could
+use it in the tagger model as follows:
+
+```ini
+[tagger.model.tok2vec.embed]
+@architectures = "my_example.MyEmbedding.v1"
+output_width = 128
+```
+
+Now that you have a custom function wired into the network, you can start
+implementing the logic you're interested in. For example, let's say you want to
+try a relatively simple embedding strategy that makes use of static word
+vectors, but combines them via summation with a smaller table of learned
+embeddings.
+
+```python
+from thinc.api import add, chain, remap_ids, Embed
+from spacy.ml.staticvectors import StaticVectors
+
+@registry.architectures("my_example.MyEmbedding.v1")
+def MyCustomVectors(
+    output_width: int,
+    vector_width: int,
+    embed_rows: int,
+    key2row: Dict[int, int]
+) -> Model[List[Doc], List[Floats2d]]:
+    return add(
+        StaticVectors(nO=output_width),
+        chain(
+           FeatureExtractor(["ORTH"]),
+           remap_ids(key2row),
+           Embed(nO=output_width, nV=embed_rows)
+        )
+    )
+```
+
+## Pretraining {#pretraining}
+
+
diff --git a/website/docs/usage/linguistic-features.md b/website/docs/usage/linguistic-features.md
index ac922c4fa..325063e58 100644
--- a/website/docs/usage/linguistic-features.md
+++ b/website/docs/usage/linguistic-features.md
@@ -9,6 +9,7 @@ menu:
   - ['Tokenization', 'tokenization']
   - ['Merging & Splitting', 'retokenization']
   - ['Sentence Segmentation', 'sbd']
+  - ['Vectors & Similarity', 'vectors-similarity']
   - ['Language data', 'language-data']
 ---
 
@@ -1024,10 +1025,10 @@ produced by the tokenizer.
 >
 > If you're working with transformer models like BERT, check out the
 > [`spacy-transformers`](https://github.com/explosion/spacy-transformers)
-> extension package and [documentation](/usage/transformers). It includes a
-> pipeline component for using pretrained transformer weights and **training
-> transformer models** in spaCy, as well as helpful utilities for aligning word
-> pieces to linguistic tokenization.
+> extension package and [documentation](/usage/embeddings-transformers). It
+> includes a pipeline component for using pretrained transformer weights and
+> **training transformer models** in spaCy, as well as helpful utilities for
+> aligning word pieces to linguistic tokenization.
 
 ```python
 ### Custom BERT word piece tokenizer
@@ -1510,7 +1511,7 @@ adding it to the pipeline using [`nlp.add_pipe`](/api/language#add_pipe).
 
 
 Here's an example of a component that implements a pre-processing rule for
-splitting on `'...'` tokens. The component is added before the parser, which is
+splitting on `"..."` tokens. The component is added before the parser, which is
 then used to further segment the text. That's possible, because `is_sent_start`
 is only set to `True` for some of the tokens – all others still specify `None`
 for unset sentence boundaries. This approach can be useful if you want to
@@ -1540,6 +1541,152 @@ doc = nlp(text)
 print("After:", [sent.text for sent in doc.sents])
 ```
 
+## Word vectors and semantic similarity {#vectors-similarity}
+
+import Vectors101 from 'usage/101/\_vectors-similarity.md'
+
+
+
+
+
+Computing similarity scores can be helpful in many situations, but it's also
+important to maintain **realistic expectations** about what information it can
+provide. Words can be related to each over in many ways, so a single
+"similarity" score will always be a **mix of different signals**, and vectors
+trained on different data can produce very different results that may not be
+useful for your purpose.
+
+Also note that the similarity of `Doc` or `Span` objects defaults to the
+**average** of the token vectors. This means it's insensitive to the order of
+the words. Two documents expressing the same meaning with dissimilar wording
+will return a lower similarity score than two documents that happen to contain
+the same words while expressing different meanings.
+
+
+
+### Adding word vectors {#adding-vectors}
+
+Custom word vectors can be trained using a number of open-source libraries, such
+as [Gensim](https://radimrehurek.com/gensim), [Fast Text](https://fasttext.cc),
+or Tomas Mikolov's original
+[Word2vec implementation](https://code.google.com/archive/p/word2vec/). Most
+word vector libraries output an easy-to-read text-based format, where each line
+consists of the word followed by its vector. For everyday use, we want to
+convert the vectors model into a binary format that loads faster and takes up
+less space on disk. The easiest way to do this is the
+[`init model`](/api/cli#init-model) command-line utility. This will output a
+spaCy model in the directory `/tmp/la_vectors_wiki_lg`, giving you access to
+some nice Latin vectors. You can then pass the directory path to
+[`spacy.load`](/api/top-level#spacy.load).
+
+> #### Usage example
+>
+> ```python
+> nlp_latin = spacy.load("/tmp/la_vectors_wiki_lg")
+> doc1 = nlp_latin("Caecilius est in horto")
+> doc2 = nlp_latin("servus est in atrio")
+> doc1.similarity(doc2)
+> ```
+
+```bash
+wget https://s3-us-west-1.amazonaws.com/fasttext-vectors/word-vectors-v2/cc.la.300.vec.gz
+python -m spacy init model en /tmp/la_vectors_wiki_lg --vectors-loc cc.la.300.vec.gz
+```
+
+
+
+To help you strike a good balance between coverage and memory usage, spaCy's
+[`Vectors`](/api/vectors) class lets you map **multiple keys** to the **same
+row** of the table. If you're using the
+[`spacy init model`](/api/cli#init-model) command to create a vocabulary,
+pruning the vectors will be taken care of automatically if you set the
+`--prune-vectors` flag. You can also do it manually in the following steps:
+
+1. Start with a **word vectors model** that covers a huge vocabulary. For
+   instance, the [`en_vectors_web_lg`](/models/en-starters#en_vectors_web_lg)
+   model provides 300-dimensional GloVe vectors for over 1 million terms of
+   English.
+2. If your vocabulary has values set for the `Lexeme.prob` attribute, the
+   lexemes will be sorted by descending probability to determine which vectors
+   to prune. Otherwise, lexemes will be sorted by their order in the `Vocab`.
+3. Call [`Vocab.prune_vectors`](/api/vocab#prune_vectors) with the number of
+   vectors you want to keep.
+
+```python
+nlp = spacy.load('en_vectors_web_lg')
+n_vectors = 105000  # number of vectors to keep
+removed_words = nlp.vocab.prune_vectors(n_vectors)
+
+assert len(nlp.vocab.vectors) <= n_vectors  # unique vectors have been pruned
+assert nlp.vocab.vectors.n_keys > n_vectors  # but not the total entries
+```
+
+[`Vocab.prune_vectors`](/api/vocab#prune_vectors) reduces the current vector
+table to a given number of unique entries, and returns a dictionary containing
+the removed words, mapped to `(string, score)` tuples, where `string` is the
+entry the removed word was mapped to, and `score` the similarity score between
+the two words.
+
+```python
+### Removed words
+{
+    "Shore": ("coast", 0.732257),
+    "Precautionary": ("caution", 0.490973),
+    "hopelessness": ("sadness", 0.742366),
+    "Continous": ("continuous", 0.732549),
+    "Disemboweled": ("corpse", 0.499432),
+    "biostatistician": ("scientist", 0.339724),
+    "somewheres": ("somewheres", 0.402736),
+    "observing": ("observe", 0.823096),
+    "Leaving": ("leaving", 1.0),
+}
+```
+
+In the example above, the vector for "Shore" was removed and remapped to the
+vector of "coast", which is deemed about 73% similar. "Leaving" was remapped to
+the vector of "leaving", which is identical. If you're using the
+[`init model`](/api/cli#init-model) command, you can set the `--prune-vectors`
+option to easily reduce the size of the vectors as you add them to a spaCy
+model:
+
+```bash
+$ python -m spacy init model /tmp/la_vectors_web_md --vectors-loc la.300d.vec.tgz --prune-vectors 10000
+```
+
+This will create a spaCy model with vectors for the first 10,000 words in the
+vectors model. All other words in the vectors model are mapped to the closest
+vector among those retained.
+
+
+
+### Adding vectors individually {#adding-individual-vectors}
+
+The `vector` attribute is a **read-only** numpy or cupy array (depending on
+whether you've configured spaCy to use GPU memory), with dtype `float32`. The
+array is read-only so that spaCy can avoid unnecessary copy operations where
+possible. You can modify the vectors via the [`Vocab`](/api/vocab) or
+[`Vectors`](/api/vectors) table. Using the
+[`Vocab.set_vector`](/api/vocab#set_vector) method is often the easiest approach
+if you have vectors in an arbitrary format, as you can read in the vectors with
+your own logic, and just set them with a simple loop. This method is likely to
+be slower than approaches that work with the whole vectors table at once, but
+it's a great approach for once-off conversions before you save out your model to
+disk.
+
+```python
+### Adding vectors
+from spacy.vocab import Vocab
+
+vector_data = {
+    "dog": numpy.random.uniform(-1, 1, (300,)),
+    "cat": numpy.random.uniform(-1, 1, (300,)),
+    "orange": numpy.random.uniform(-1, 1, (300,))
+}
+vocab = Vocab()
+for word, vector in vector_data.items():
+    vocab.set_vector(word, vector)
+```
+
 ## Language data {#language-data}
 
 import LanguageData101 from 'usage/101/\_language-data.md'
diff --git a/website/docs/usage/processing-pipelines.md b/website/docs/usage/processing-pipelines.md
index 8df4b200d..2b040a832 100644
--- a/website/docs/usage/processing-pipelines.md
+++ b/website/docs/usage/processing-pipelines.md
@@ -1,6 +1,6 @@
 ---
 title: Language Processing Pipelines
-next: /usage/vectors-embeddings
+next: /usage/embeddings-transformers
 menu:
   - ['Processing Text', 'processing']
   - ['How Pipelines Work', 'pipelines']
@@ -324,9 +324,9 @@ pretrained components and new components trained on your data.
 
 When reusing components across models, keep in mind that the **vocabulary**,
 **vectors** and model settings **must match**. If a pretrained model includes
-[word vectors](/usage/vectors-embeddings) and the component uses them as
-features, the model you copy it to needs to have the _same_ vectors available –
-otherwise, it won't be able to make the same predictions.
+[word vectors](/usage/linguistic-features#vectors-similarity) and the component
+uses them as features, the model you copy it to needs to have the _same_ vectors
+available – otherwise, it won't be able to make the same predictions.
 
 
 
@@ -1202,7 +1202,7 @@ document similarity method.
 Hooks let you customize some of the behaviors of the `Doc`, `Span` or `Token`
 objects by adding a component to the pipeline. For instance, to customize the
 [`Doc.similarity`](/api/doc#similarity) method, you can add a component that
-sets a custom function to `doc.user_hooks['similarity']`. The built-in
+sets a custom function to `doc.user_hooks["similarity"]`. The built-in
 `Doc.similarity` method will check the `user_hooks` dict, and delegate to your
 function if you've set one. Similar results can be achieved by setting functions
 to `Doc.user_span_hooks` and `Doc.user_token_hooks`.
diff --git a/website/docs/usage/spacy-101.md b/website/docs/usage/spacy-101.md
index 49cdd96ea..df08e0320 100644
--- a/website/docs/usage/spacy-101.md
+++ b/website/docs/usage/spacy-101.md
@@ -247,7 +247,7 @@ import Vectors101 from 'usage/101/\_vectors-similarity.md'
 
 To learn more about word vectors, how to **customize them** and how to load
 **your own vectors** into spaCy, see the usage guide on
-[using word vectors and semantic similarities](/usage/vectors-embeddings).
+[using word vectors and semantic similarities](/usage/linguistic-features#vectors-similarity).
 
 
 
diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index fc1624ec1..9f74cafac 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -30,7 +30,7 @@ ready-to-use spaCy models.
 
 
 
-## Quickstart {#quickstart}
+## Quickstart {#quickstart tag="new"}
 
 The recommended way to train your spaCy models is via the
 [`spacy train`](/api/cli#train) command on the command line. It only needs a
@@ -131,7 +131,7 @@ Some of the main advantages and features of spaCy's training config are:
   multiple components, define them once and reference them as
   [variables](#config-interpolation).
 - **Reproducibility with no hidden defaults.** The config file is the "single
-  source of truth" and includes all settings. 
+  source of truth" and includes all settings.
 - **Automated checks and validation.** When you load a config, spaCy checks if
   the settings are complete and if all values have the correct types. This lets
   you catch potential mistakes early. In your custom architectures, you can use
@@ -667,7 +667,7 @@ visualize your model.
 
 For more details on how to integrate transformer models into your training
 config and customize the implementations, see the usage guide on
-[training transformers](/usage/transformers#training).
+[training transformers](/usage/embeddings-transformers#transformers-training).
 
 ### Pretraining with spaCy {#pretraining}
 
diff --git a/website/docs/usage/v2.md b/website/docs/usage/v2.md
index 59a842968..f7bcc17d3 100644
--- a/website/docs/usage/v2.md
+++ b/website/docs/usage/v2.md
@@ -218,7 +218,7 @@ available via `token.orth`.
 
 The new [`Vectors`](/api/vectors) class helps the `Vocab` manage the vectors
 assigned to strings, and lets you assign vectors individually, or
-[load in GloVe vectors](/usage/vectors-embeddings#custom-loading-glove) from a
+[load in GloVe vectors](/usage/linguistic-features#adding-vectors) from a
 directory. To help you strike a good balance between coverage and memory usage,
 the `Vectors` class lets you map **multiple keys** to the **same row** of the
 table. If you're using the [`spacy init-model`](/api/cli#init-model) command to
diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md
index 7213adf4a..fda5393a4 100644
--- a/website/docs/usage/v3.md
+++ b/website/docs/usage/v3.md
@@ -30,7 +30,7 @@ menu:
 
 
 
-- **Usage:** [Transformers](/usage/transformers),
+- **Usage:** [Embeddings & Transformers](/usage/embeddings-transformers),
   [Training models](/usage/training)
 - **API:** [`Transformer`](/api/transformer),
   [`TransformerData`](/api/transformer#transformerdata),
@@ -59,13 +59,13 @@ menu:
 
 ### New built-in pipeline components {#features-pipeline-components}
 
-| Name                                            | Description                                                                                                                                                                                                  |
-| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| [`SentenceRecognizer`](/api/sentencerecognizer) | Trainable component for sentence segmentation.                                                                                                                                                               |
-| [`Morphologizer`](/api/morphologizer)           | Trainable component to predict morphological features.                                                                                                                                                       |
-| [`Lemmatizer`](/api/lemmatizer)                 | Standalone component for rule-based and lookup lemmatization.                                                                                                                                                |
-| [`AttributeRuler`](/api/attributeruler)         | Component for setting token attributes using match patterns.                                                                                                                                                 |
-| [`Transformer`](/api/transformer)               | Component for using [transformer models](/usage/transformers) in your pipeline, accessing outputs and aligning tokens. Provided via [`spacy-transformers`](https://github.com/explosion/spacy-transformers). |
+| Name                                            | Description                                                                                                                                                                                                             |
+| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [`SentenceRecognizer`](/api/sentencerecognizer) | Trainable component for sentence segmentation.                                                                                                                                                                          |
+| [`Morphologizer`](/api/morphologizer)           | Trainable component to predict morphological features.                                                                                                                                                                  |
+| [`Lemmatizer`](/api/lemmatizer)                 | Standalone component for rule-based and lookup lemmatization.                                                                                                                                                           |
+| [`AttributeRuler`](/api/attributeruler)         | Component for setting token attributes using match patterns.                                                                                                                                                            |
+| [`Transformer`](/api/transformer)               | Component for using [transformer models](/usage/embeddings-transformers) in your pipeline, accessing outputs and aligning tokens. Provided via [`spacy-transformers`](https://github.com/explosion/spacy-transformers). |
 
 
 
diff --git a/website/docs/usage/vectors-embeddings.md b/website/docs/usage/vectors-embeddings.md
deleted file mode 100644
index 184436d12..000000000
--- a/website/docs/usage/vectors-embeddings.md
+++ /dev/null
@@ -1,340 +0,0 @@
----
-title: Vectors and Embeddings
-menu:
-  - ["What's a Word Vector?", 'whats-a-vector']
-  - ['Using Word Vectors', 'usage']
-  - ['Converting and Importing', 'converting']
-next: /usage/transformers
----
-
-Word vector tables (or "embeddings") let you find similar terms, and can improve
-the accuracy of some of your components. You can even use word vectors as a
-quick-and-dirty text-classification solution when you don't have any training data.
-Word vector tables are included in some of the spaCy [model packages](/models)
-we distribute, and you can easily create your own model packages with word
-vectors you train or download yourself.
-
-## What's a word vector? {#whats-a-vector}
-
-For spaCy's purposes, a "word vector" is a 1-dimensional slice from a
-2-dimensional **vectors table**, with a deterministic mapping from word types to
-rows in the table.
-
-```python
-def what_is_a_word_vector(
-    word_id: int,
-    key2row: Dict[int, int],
-    vectors_table: Floats2d,
-    *,
-    default_row: int=0
-) -> Floats1d:
-    return vectors_table[key2row.get(word_id, default_row)]
-```
-
-An old idea in linguistics is that you can "know a word by the company it
-keeps": that is, word meanings can be understood relationally, based on their
-patterns of usage. This idea inspired a branch of NLP research known as
-"distributional semantics" that has aimed to compute databases of lexical
-knowledge automatically. The [Word2vec](https://en.wikipedia.org/wiki/Word2vec)
-family of algorithms are a key milestone in this line of research. For
-simplicity, we will refer to a distributional word representation as a "word
-vector", and algorithms that computes word vectors (such as
-[GloVe](https://nlp.stanford.edu/projects/glove/),
-[FastText](https://fasttext.cc), etc.) as "Word2vec algorithms".
-
-Word2vec algorithms try to produce vectors tables that let you estimate useful
-relationships between words using simple linear algebra operations. For
-instance, you can often find close synonyms of a word by finding the vectors
-closest to it by cosine distance, and then finding the words that are mapped to
-those neighboring vectors. Word vectors can also be useful as features in
-statistical models.
-
-### Word vectors vs. contextual language models {#vectors-vs-language-models}
-
-The key difference between word vectors and contextual language models such
-as [transformers](/usage/transformers)
-is that word vectors model **lexical types**, rather than
-_tokens_. If you have a list of terms with no context around them,
-a transformer model like BERT can't really help you. BERT is designed to understand
-language **in context**, which isn't what you have. A word vectors table will be
-a much better fit for your task. However, if you do have words in context — whole
-sentences or paragraphs of running text — word vectors will only provide a very
-rough approximation of what the text is about.
-
-Word vectors are also very computationally efficient, as they map a word to a
-vector with a single indexing operation. Word vectors are therefore useful as a
-way to **improve the accuracy** of neural network models, especially models that
-are small or have received little or no pretraining. In spaCy, word vector
-tables are only used as **static features**. spaCy does not backpropagate
-gradients to the pretrained word vectors table. The static vectors table is
-usually used in combination with a smaller table of learned task-specific
-embeddings.
-
-## Using word vectors {#usage}
-
-spaCy stores word vector information in the
-[`Vocab.vectors`](/api/vocab#attributes) attribute, so you can access the whole
-vectors table from most spaCy objects. You can also access the vector for a
-[`Doc`](/api/doc), [`Span`](/api/span), [`Token`](/api/token) or
-[`Lexeme`](/api/lexeme) instance via the `vector` attribute. If your `Doc` or
-`Span` has multiple tokens, the average of the word vectors will be returned,
-excluding any "out of vocabulary" entries that have no vector available. If none
-of the words have a vector, a zeroed vector will be returned.
-
-The `vector` attribute is a **read-only** numpy or cupy array (depending on
-whether you've configured spaCy to use GPU memory), with dtype `float32`. The
-array is read-only so that spaCy can avoid unnecessary copy operations where
-possible. You can modify the vectors via the `Vocab` or `Vectors` table.
-
-### Word vectors and similarity
-
-A common use-case of word vectors is to answer _similarity questions_. You can
-ask how similar a `token`, `span`, `doc` or `lexeme` is to another object using
-the `.similarity()` method. You can even check the similarity of mismatched
-types, asking how similar a whole document is to a particular word, how similar
-a span is to a document, etc. By default, the `.similarity()` method will use
-return the cosine of the `.vector` attribute of the two objects being compared.
-You can customize this behavior by setting one or more
-[user hooks](/usage/processing-pipelines#custom-components-user-hooks) for the
-types you want to customize.
-
-Word vector similarity is a practical technique for many situations, especially
-since it's easy to use and relatively efficient to compute. However, it's
-important to maintain realistic expectations about what information it can
-provide. Words can be related to each over in many ways, so a single
-"similarity" score will always be a mix of different signals. The word vectors
-model is also not trained for your specific use-case, so you have no way of
-telling it which results are more or less useful for your purpose. These
-problems are even more accute when you go from measuring the similarity of
-single words to the similarity of spans or documents. The vector averaging
-process is insensitive to the order of the words, so `doc1.similarity(doc2)`
-will mostly be based on the overlap in lexical items between the two documents
-objects. Two documents expressing the same meaning with dissimilar wording will
-return a lower similarity score than two documents that happen to contain the
-same words while expressing different meanings.
-
-### Using word vectors in your models
-
-Many neural network models are able to use word vector tables as additional
-features, which sometimes results in significant improvements in accuracy.
-spaCy's built-in embedding layer, `spacy.MultiHashEmbed.v1`, can be configured
-to use word vector tables using the `also_use_static_vectors` flag. This
-setting is also available on the `spacy.MultiHashEmbedCNN.v1` layer, which
-builds the default token-to-vector encoding architecture.
-
-```
-[tagger.model.tok2vec.embed]
-@architectures = "spacy.MultiHashEmbed.v1"
-width = 128
-rows = 7000
-also_embed_subwords = true
-also_use_static_vectors = true
-```
-
-
-The configuration system will look up the string `spacy.MultiHashEmbed.v1`
-in the `architectures` registry, and call the returned object with the
-rest of the arguments from the block. This will result in a call to the
-`spacy.ml.models.tok2vec.MultiHashEmbed` function, which will return
-a Thinc model object with the type signature `Model[List[Doc],
-List[Floats2d]]`. Because the embedding layer takes a list of `Doc` objects as
-input, it does not need to store a copy of the vectors table. The vectors will
-be retrieved from the `Doc` objects that are passed in, via the
-`doc.vocab.vectors` attribute. This part of the process is handled by the
-`spacy.ml.staticvectors.StaticVectors` layer.
-
-
-#### Creating a custom embedding layer
-
-The `MultiHashEmbed` layer is spaCy's recommended strategy for constructing
-initial word representations for your neural network models, but you can also
-implement your own. You can register any function to a string name, and then
-reference that function within your config (see the [training]("/usage/training")
-section for more details). To try this out, you can save the following little
-example to a new Python file:
-
-```
-from spacy.ml.staticvectors import StaticVectors
-from spacy.util import registry
-
-print("I was imported!")
-
-@registry.architectures("my_example.MyEmbedding.v1")
-def MyEmbedding(output_width: int) -> Model[List[Doc], List[Floats2d]]:
-    print("I was called!")
-    return StaticVectors(nO=output_width)
-```
-
-If you pass the path to your file to the `spacy train` command using the `-c`
-argument, your file will be imported, which means the decorator registering the
-function will be run. Your function is now on equal footing with any of spaCy's
-built-ins, so you can drop it in instead of any other model with the same input
-and output signature. For instance, you could use it in the tagger model as
-follows:
-
-```
-[tagger.model.tok2vec.embed]
-@architectures = "my_example.MyEmbedding.v1"
-output_width = 128
-```
-
-Now that you have a custom function wired into the network, you can start
-implementing the logic you're interested in. For example, let's say you want to
-try a relatively simple embedding strategy that makes use of static word vectors,
-but combines them via summation with a smaller table of learned embeddings.
-
-```python
-from thinc.api import add, chain, remap_ids, Embed
-from spacy.ml.staticvectors import StaticVectors
-
-@registry.architectures("my_example.MyEmbedding.v1")
-def MyCustomVectors(
-    output_width: int,
-    vector_width: int,
-    embed_rows: int,
-    key2row: Dict[int, int]
-) -> Model[List[Doc], List[Floats2d]]:
-    return add(
-        StaticVectors(nO=output_width),
-        chain(
-           FeatureExtractor(["ORTH"]),
-           remap_ids(key2row),
-           Embed(nO=output_width, nV=embed_rows)
-        )
-    )
-```
-
-#### When should you add word vectors to your model?
-        
-Word vectors are not compatible with most [transformer models](/usage/transformers),
-but if you're training another type of NLP network, it's almost always worth
-adding word vectors to your model. As well as improving your final accuracy,
-word vectors often make experiments more consistent, as the accuracy you
-reach will be less sensitive to how the network is randomly initialized. High
-variance due to random chance can slow down your progress significantly, as you
-need to run many experiments to filter the signal from the noise.
-
-Word vector features need to be enabled prior to training, and the same word vectors
-table will need to be available at runtime as well. You cannot add word vector
-features once the model has already been trained, and you usually cannot
-replace one word vectors table with another without causing a significant loss
-of performance.
-
-## Converting word vectors for use in spaCy {#converting}
-
-Custom word vectors can be trained using a number of open-source libraries, such
-as [Gensim](https://radimrehurek.com/gensim), [Fast Text](https://fasttext.cc),
-or Tomas Mikolov's original
-[Word2vec implementation](https://code.google.com/archive/p/word2vec/). Most
-word vector libraries output an easy-to-read text-based format, where each line
-consists of the word followed by its vector. For everyday use, we want to
-convert the vectors model into a binary format that loads faster and takes up
-less space on disk. The easiest way to do this is the
-[`init-model`](/api/cli#init-model) command-line utility:
-
-```bash
-wget https://s3-us-west-1.amazonaws.com/fasttext-vectors/word-vectors-v2/cc.la.300.vec.gz
-python -m spacy init-model en /tmp/la_vectors_wiki_lg --vectors-loc cc.la.300.vec.gz
-```
-
-This will output a spaCy model in the directory `/tmp/la_vectors_wiki_lg`,
-giving you access to some nice Latin vectors 😉 You can then pass the directory
-path to [`spacy.load()`](/api/top-level#spacy.load).
-
-```python
-nlp_latin = spacy.load("/tmp/la_vectors_wiki_lg")
-doc1 = nlp_latin("Caecilius est in horto")
-doc2 = nlp_latin("servus est in atrio")
-doc1.similarity(doc2)
-```
-
-The model directory will have a `/vocab` directory with the strings, lexical
-entries and word vectors from the input vectors model. The
-[`init-model`](/api/cli#init-model) command supports a number of archive formats
-for the word vectors: the vectors can be in plain text (`.txt`), zipped
-(`.zip`), or tarred and zipped (`.tgz`).
-
-### Optimizing vector coverage {#custom-vectors-coverage new="2"}
-
-To help you strike a good balance between coverage and memory usage, spaCy's
-[`Vectors`](/api/vectors) class lets you map **multiple keys** to the **same
-row** of the table. If you're using the
-[`spacy init-model`](/api/cli#init-model) command to create a vocabulary,
-pruning the vectors will be taken care of automatically if you set the
-`--prune-vectors` flag. You can also do it manually in the following steps:
-
-1. Start with a **word vectors model** that covers a huge vocabulary. For
-   instance, the [`en_vectors_web_lg`](/models/en-starters#en_vectors_web_lg)
-   model provides 300-dimensional GloVe vectors for over 1 million terms of
-   English.
-2. If your vocabulary has values set for the `Lexeme.prob` attribute, the
-   lexemes will be sorted by descending probability to determine which vectors
-   to prune. Otherwise, lexemes will be sorted by their order in the `Vocab`.
-3. Call [`Vocab.prune_vectors`](/api/vocab#prune_vectors) with the number of
-   vectors you want to keep.
-
-```python
-nlp = spacy.load('en_vectors_web_lg')
-n_vectors = 105000  # number of vectors to keep
-removed_words = nlp.vocab.prune_vectors(n_vectors)
-
-assert len(nlp.vocab.vectors) <= n_vectors  # unique vectors have been pruned
-assert nlp.vocab.vectors.n_keys > n_vectors  # but not the total entries
-```
-
-[`Vocab.prune_vectors`](/api/vocab#prune_vectors) reduces the current vector
-table to a given number of unique entries, and returns a dictionary containing
-the removed words, mapped to `(string, score)` tuples, where `string` is the
-entry the removed word was mapped to, and `score` the similarity score between
-the two words.
-
-```python
-### Removed words
-{
-    "Shore": ("coast", 0.732257),
-    "Precautionary": ("caution", 0.490973),
-    "hopelessness": ("sadness", 0.742366),
-    "Continous": ("continuous", 0.732549),
-    "Disemboweled": ("corpse", 0.499432),
-    "biostatistician": ("scientist", 0.339724),
-    "somewheres": ("somewheres", 0.402736),
-    "observing": ("observe", 0.823096),
-    "Leaving": ("leaving", 1.0),
-}
-```
-
-In the example above, the vector for "Shore" was removed and remapped to the
-vector of "coast", which is deemed about 73% similar. "Leaving" was remapped to
-the vector of "leaving", which is identical. If you're using the
-[`init-model`](/api/cli#init-model) command, you can set the `--prune-vectors`
-option to easily reduce the size of the vectors as you add them to a spaCy
-model:
-
-```bash
-$ python -m spacy init-model /tmp/la_vectors_web_md --vectors-loc la.300d.vec.tgz --prune-vectors 10000
-```
-
-This will create a spaCy model with vectors for the first 10,000 words in the
-vectors model. All other words in the vectors model are mapped to the closest
-vector among those retained.
-
-### Adding vectors {#adding-vectors}
-
-You can also add word vectors individually, using the method `vocab.set_vector`.
-This is often the easiest approach if you have vectors in an arbitrary format,
-as you can read in the vectors with your own logic, and just set them with
-a simple loop. This method is likely to be slower than approaches that work
-with the whole vectors table at once, but it's a great approach for once-off
-conversions before you save out your model to disk.
-
-```python
-### Adding vectors
-from spacy.vocab import Vocab
-
-vector_data = {"dog": numpy.random.uniform(-1, 1, (300,)),
-               "cat": numpy.random.uniform(-1, 1, (300,)),
-               "orange": numpy.random.uniform(-1, 1, (300,))}
-vocab = Vocab()
-for word, vector in vector_data.items():
-    vocab.set_vector(word, vector)
-```
diff --git a/website/meta/sidebars.json b/website/meta/sidebars.json
index 6f8763955..c830619c5 100644
--- a/website/meta/sidebars.json
+++ b/website/meta/sidebars.json
@@ -18,8 +18,11 @@
                     { "text": "Linguistic Features", "url": "/usage/linguistic-features" },
                     { "text": "Rule-based Matching", "url": "/usage/rule-based-matching" },
                     { "text": "Processing Pipelines", "url": "/usage/processing-pipelines" },
-                    { "text": "Vectors & Embeddings", "url": "/usage/vectors-embeddings" },
-                    { "text": "Transformers", "url": "/usage/transformers", "tag": "new" },
+                    {
+                        "text": "Embeddings & Transformers",
+                        "url": "/usage/embeddings-transformers",
+                        "tag": "new"
+                    },
                     { "text": "Training Models", "url": "/usage/training", "tag": "new" },
                     { "text": "spaCy Projects", "url": "/usage/projects", "tag": "new" },
                     { "text": "Saving & Loading", "url": "/usage/saving-loading" },
diff --git a/website/src/components/typography.js b/website/src/components/typography.js
index 41464473f..d37c345b9 100644
--- a/website/src/components/typography.js
+++ b/website/src/components/typography.js
@@ -9,7 +9,12 @@ import { isString, github, headingTextClassName } from './util'
 import classes from '../styles/typography.module.sass'
 
 export const H1 = ({ Component = 'h1', className, ...props }) => (
-    
+    
 )
 export const H2 = ({ className, ...props }) => (
     
@@ -90,6 +95,7 @@ const Headline = ({
     source,
     hidden,
     action,
+    permalink = true,
     className,
     children,
 }) => {
@@ -102,7 +108,7 @@ const Headline = ({
     const tags = tag ? tag.split(',').map(t => t.trim()) : []
     return (
         
-            {children} 
+            {children} 
             {tags.map((tag, i) => (
                 
                     {tag}

From 1c3bcfb4880a4ecb20e514ac3b73460ac286948b Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Tue, 18 Aug 2020 01:22:59 +0200
Subject: [PATCH 11/92] Update docs and util consistency

---
 spacy/util.py                  | 86 ++++++++++++++++++++++++++++------
 website/docs/api/language.md   |  2 +-
 website/docs/api/top-level.md  | 36 ++++++++++----
 website/src/components/code.js |  6 +--
 4 files changed, 103 insertions(+), 27 deletions(-)

diff --git a/spacy/util.py b/spacy/util.py
index e8f78f2f2..5eff82866 100644
--- a/spacy/util.py
+++ b/spacy/util.py
@@ -249,7 +249,16 @@ def load_model_from_package(
     disable: Iterable[str] = tuple(),
     config: Union[Dict[str, Any], Config] = SimpleFrozenDict(),
 ) -> "Language":
-    """Load a model from an installed package."""
+    """Load a model from an installed package.
+
+    name (str): The package name.
+    vocab (Vocab / True): Optional vocab to pass in on initialization. If True,
+        a new Vocab object will be created.
+    disable (Iterable[str]): Names of pipeline components to disable.
+    config (Dict[str, Any] / Config): Config overrides as nested dict or dict
+        keyed by section values in dot notation.
+    RETURNS (Language): The loaded nlp object.
+    """
     cls = importlib.import_module(name)
     return cls.load(vocab=vocab, disable=disable, config=config)
 
@@ -263,7 +272,17 @@ def load_model_from_path(
     config: Union[Dict[str, Any], Config] = SimpleFrozenDict(),
 ) -> "Language":
     """Load a model from a data directory path. Creates Language class with
-    pipeline from config.cfg and then calls from_disk() with path."""
+    pipeline from config.cfg and then calls from_disk() with path.
+
+    name (str): Package name or model path.
+    meta (Dict[str, Any]): Optional model meta.
+    vocab (Vocab / True): Optional vocab to pass in on initialization. If True,
+        a new Vocab object will be created.
+    disable (Iterable[str]): Names of pipeline components to disable.
+    config (Dict[str, Any] / Config): Config overrides as nested dict or dict
+        keyed by section values in dot notation.
+    RETURNS (Language): The loaded nlp object.
+    """
     if not model_path.exists():
         raise IOError(Errors.E052.format(path=model_path))
     if not meta:
@@ -284,6 +303,15 @@ def load_model_from_config(
 ) -> Tuple["Language", Config]:
     """Create an nlp object from a config. Expects the full config file including
     a section "nlp" containing the settings for the nlp object.
+
+    name (str): Package name or model path.
+    meta (Dict[str, Any]): Optional model meta.
+    vocab (Vocab / True): Optional vocab to pass in on initialization. If True,
+        a new Vocab object will be created.
+    disable (Iterable[str]): Names of pipeline components to disable.
+    auto_fill (bool): Whether to auto-fill config with missing defaults.
+    validate (bool): Whether to show config validation errors.
+    RETURNS (Language): The loaded nlp object.
     """
     if "nlp" not in config:
         raise ValueError(Errors.E985.format(config=config))
@@ -308,6 +336,13 @@ def load_model_from_init_py(
 ) -> "Language":
     """Helper function to use in the `load()` method of a model package's
     __init__.py.
+
+    vocab (Vocab / True): Optional vocab to pass in on initialization. If True,
+        a new Vocab object will be created.
+    disable (Iterable[str]): Names of pipeline components to disable.
+    config (Dict[str, Any] / Config): Config overrides as nested dict or dict
+        keyed by section values in dot notation.
+    RETURNS (Language): The loaded nlp object.
     """
     model_path = Path(init_file).parent
     meta = get_model_meta(model_path)
@@ -325,7 +360,14 @@ def load_config(
     overrides: Dict[str, Any] = SimpleFrozenDict(),
     interpolate: bool = False,
 ) -> Config:
-    """Load a config file. Takes care of path validation and section order."""
+    """Load a config file. Takes care of path validation and section order.
+
+    path (Union[str, Path]): Path to the config file.
+    overrides: (Dict[str, Any]): Config overrides as nested dict or
+        dict keyed by section values in dot notation.
+    interpolate (bool): Whether to interpolate and resolve variables.
+    RETURNS (Config): The loaded config.
+    """
     config_path = ensure_path(path)
     if not config_path.exists() or not config_path.is_file():
         raise IOError(Errors.E053.format(path=config_path, name="config.cfg"))
@@ -337,7 +379,12 @@ def load_config(
 def load_config_from_str(
     text: str, overrides: Dict[str, Any] = SimpleFrozenDict(), interpolate: bool = False
 ):
-    """Load a full config from a string."""
+    """Load a full config from a string. Wrapper around Thinc's Config.from_str.
+
+    text (str): The string config to load.
+    interpolate (bool): Whether to interpolate and resolve variables.
+    RETURNS (Config): The loaded config.
+    """
     return Config(section_order=CONFIG_SECTION_ORDER).from_str(
         text, overrides=overrides, interpolate=interpolate,
     )
@@ -435,19 +482,18 @@ def get_base_version(version: str) -> str:
     return Version(version).base_version
 
 
-def get_model_meta(path: Union[str, Path]) -> Dict[str, Any]:
-    """Get model meta.json from a directory path and validate its contents.
+def load_meta(path: Union[str, Path]) -> Dict[str, Any]:
+    """Load a model meta.json from a path and validate its contents.
 
-    path (str / Path): Path to model directory.
-    RETURNS (Dict[str, Any]): The model's meta data.
+    path (Union[str, Path]): Path to meta.json.
+    RETURNS (Dict[str, Any]): The loaded meta.
     """
-    model_path = ensure_path(path)
-    if not model_path.exists():
-        raise IOError(Errors.E052.format(path=model_path))
-    meta_path = model_path / "meta.json"
-    if not meta_path.is_file():
-        raise IOError(Errors.E053.format(path=meta_path, name="meta.json"))
-    meta = srsly.read_json(meta_path)
+    path = ensure_path(path)
+    if not path.parent.exists():
+        raise IOError(Errors.E052.format(path=path.parent))
+    if not path.exists() or not path.is_file():
+        raise IOError(Errors.E053.format(path=path, name="meta.json"))
+    meta = srsly.read_json(path)
     for setting in ["lang", "name", "version"]:
         if setting not in meta or not meta[setting]:
             raise ValueError(Errors.E054.format(setting=setting))
@@ -471,6 +517,16 @@ def get_model_meta(path: Union[str, Path]) -> Dict[str, Any]:
     return meta
 
 
+def get_model_meta(path: Union[str, Path]) -> Dict[str, Any]:
+    """Get model meta.json from a directory path and validate its contents.
+
+    path (str / Path): Path to model directory.
+    RETURNS (Dict[str, Any]): The model's meta data.
+    """
+    model_path = ensure_path(path)
+    return load_meta(model_path / "meta.json")
+
+
 def is_package(name: str) -> bool:
     """Check if string maps to a package installed via pip.
 
diff --git a/website/docs/api/language.md b/website/docs/api/language.md
index 7d44d47d9..871adc0f2 100644
--- a/website/docs/api/language.md
+++ b/website/docs/api/language.md
@@ -40,7 +40,7 @@ Initialize a `Language` object.
 | `meta`             | Custom meta data for the `Language` class. Is written to by models to add model meta data. ~~dict~~                      |
 | `create_tokenizer` | Optional function that receives the `nlp` object and returns a tokenizer. ~~Callable[[Language], Callable[[str], Doc]]~~ |
 
-## Language.from_config {#from_config tag="classmethod"}
+## Language.from_config {#from_config tag="classmethod" new="3"}
 
 Create a `Language` object from a loaded config. Will set up the tokenizer and
 language data, add pipeline components based on the pipeline and components
diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md
index 0f87b8fd0..c44e4e5b4 100644
--- a/website/docs/api/top-level.md
+++ b/website/docs/api/top-level.md
@@ -70,7 +70,7 @@ Create a blank model of a given language class. This function is the twin of
 | `name`      | [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) of the language class to load. ~~str~~ |
 | **RETURNS** | An empty `Language` object of the appropriate subclass. ~~Language~~                                     |
 
-#### spacy.info {#spacy.info tag="function"}
+### spacy.info {#spacy.info tag="function"}
 
 The same as the [`info` command](/api/cli#info). Pretty-print information about
 your installation, models and local setup from within spaCy. To get the model
@@ -585,20 +585,40 @@ A helper function to use in the `load()` method of a model package's
 | `config` 3 | Config overrides as nested dict or flat dict keyed by section values in dot notation, e.g. `"nlp.pipeline"`. ~~Union[Dict[str, Any], Config]~~ |
 | **RETURNS**                         | `Language` class with the loaded model. ~~Language~~                                                                                           |
 
-### util.get_model_meta {#util.get_model_meta tag="function" new="2"}
+### util.load_config {#util.load_config tag="function" new="3"}
 
-Get a model's meta.json from a directory path and validate its contents.
+Load a model's [`config.cfg`](/api/data-formats#config) from a file path. The
+config typically includes details about the model pipeline and how its
+components are created, as well as all training settings and hyperparameters.
 
 > #### Example
 >
 > ```python
-> meta = util.get_model_meta("/path/to/model")
+> config = util.load_config("/path/to/model/config.cfg")
+> print(config.to_str())
 > ```
 
-| Name        | Description                                   |
-| ----------- | --------------------------------------------- |
-| `path`      | Path to model directory. ~~Union[str, Path]~~ |
-| **RETURNS** | The model's meta data. ~~Dict[str, Any]~~     |
+| Name          | Description                                                                                                                                                                 |
+| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `path`        | Path to the model's `config.cfg`. ~~Union[str, Path]~~                                                                                                                      |
+| `overrides`   | Optional config overrides to replace in loaded config. Can be provided as nested dict, or as flat dict with keys in dot notation, e.g. `"nlp.pipeline"`. ~~Dict[str, Any]~~ |
+| `interpolate` | Whether to interpolate the config and replace variables like `${paths:train}` with their values. Defaults to `False`. ~~bool~~                                              |
+| **RETURNS**   | The model's config. ~~Config~~                                                                                                                                              |
+
+### util.load_meta {#util.load_meta tag="function" new="3"}
+
+Get a model's `meta.json` from a file path and validate its contents.
+
+> #### Example
+>
+> ```python
+> meta = util.load_meta("/path/to/model/meta.json")
+> ```
+
+| Name        | Description                                           |
+| ----------- | ----------------------------------------------------- |
+| `path`      | Path to the model's `meta.json`. ~~Union[str, Path]~~ |
+| **RETURNS** | The model's meta data. ~~Dict[str, Any]~~             |
 
 ### util.is_package {#util.is_package tag="function"}
 
diff --git a/website/src/components/code.js b/website/src/components/code.js
index f514c752d..0d1d214ae 100644
--- a/website/src/components/code.js
+++ b/website/src/components/code.js
@@ -62,12 +62,12 @@ function linkType(el, showLink = true) {
 
 export const TypeAnnotation = ({ lang = 'python', link = true, children }) => {
     // Hacky, but we're temporarily replacing a dot to prevent it from being split during highlighting
-    const TMP_DOT = '•'
+    const TMP_DOT = '۔'
     const code = Array.isArray(children) ? children.join('') : children || ''
     const [rawText, meta] = code.split(/(?= \(.+\)$)/)
-    const rawStr = rawText.replace('.', TMP_DOT)
+    const rawStr = rawText.replace(/\./g, TMP_DOT)
     const rawHtml = lang === 'none' || !code ? code : highlightCode(lang, rawStr)
-    const html = rawHtml.replace(TMP_DOT, '.').replace(/\n/g, ' ')
+    const html = rawHtml.replace(new RegExp(TMP_DOT, 'g'), '.').replace(/\n/g, ' ')
     const result = htmlToReact(html)
     const elements = Array.isArray(result) ? result : [result]
     const annotClassNames = classNames(

From ef6cf3b27646992233b3f2415fffad366a12ecdc Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Tue, 18 Aug 2020 01:29:34 +0200
Subject: [PATCH 12/92] Update docs [ci skip]

---
 website/docs/usage/v3.md | 45 +++++++++++++++++++++-------------------
 1 file changed, 24 insertions(+), 21 deletions(-)

diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md
index fda5393a4..a72baa7fa 100644
--- a/website/docs/usage/v3.md
+++ b/website/docs/usage/v3.md
@@ -140,22 +140,20 @@ in your config and see validation errors if the argument values don't match.
 
 The following methods, attributes and commands are new in spaCy v3.0.
 
-| Name                                                                                                                     | Description                                                                                                                                                                                      |
-| ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| [`Token.lex`](/api/token#attributes)                                                                                     | Access a token's [`Lexeme`](/api/lexeme).                                                                                                                                                        |
-| [`Language.select_pipes`](/api/language#select_pipes)                                                                    | Contextmanager for enabling or disabling specific pipeline components for a block.                                                                                                               |
-| [`Language.analyze_pipes`](/api/language#analyze_pipes)                                                                  | [Analyze](/usage/processing-pipelines#analysis) components and their interdependencies.                                                                                                          |
-| [`Language.resume_training`](/api/language#resume_training)                                                              | Experimental: continue training a pretrained model and initialize "rehearsal" for components that implement a `rehearse` method to prevent catastrophic forgetting.                              |
-| [`@Language.factory`](/api/language#factory) [`@Language.component`](/api/language#component)                            | Decorators for [registering](/usage/processing-pipelines#custom-components) pipeline component factories and simple stateless component functions.                                               |
-| [`Language.has_factory`](/api/language#has_factory)                                                                      | Check whether a component factory is registered on a language class.s                                                                                                                            |
-| [`Language.get_factory_meta`](/api/language#get_factory_meta) [`Language.get_pipe_meta`](/api/language#get_factory_meta) | Get the [`FactoryMeta`](/api/language#factorymeta) with component metadata for a factory or instance name.                                                                                       |
-| [`Language.config`](/api/language#config)                                                                                | The [config](/usage/training#config) used to create the current `nlp` object. An instance of [`Config`](https://thinc.ai/docs/api-config#config) and can be saved to disk and used for training. |
-| [`Pipe.score`](/api/pipe#score)                                                                                          | Method on trainable pipeline components that returns a dictionary of evaluation scores.                                                                                                          |
-| [`registry`](/api/top-level#registry)                                                                                    | Function registry to map functions to string names that can be referenced in [configs](/usage/training#config).                                                                                  |
-| [`init config`](/api/cli#init-config)                                                                                    | CLI command for initializing a [training config](/usage/training) file with the recommended settings.                                                                                            |
-| [`init fill-config`](/api/cli#init-fill-config)                                                                          | CLI command for auto-filling a partial config with all defaults and missing values.                                                                                                              |
-| [`debug config`](/api/cli#debug-config)                                                                                  | CLI command for debugging a [training config](/usage/training) file and showing validation errors.                                                                                               |
-| [`project`](/api/cli#project)                                                                                            | Suite of CLI commands for cloning, running and managing [spaCy projects](/usage/projects).                                                                                                       |
+| Name                                                                                                                          | Description                                                                                                                                                                                      |
+| ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| [`Token.lex`](/api/token#attributes)                                                                                          | Access a token's [`Lexeme`](/api/lexeme).                                                                                                                                                        |
+| [`Language.select_pipes`](/api/language#select_pipes)                                                                         | Contextmanager for enabling or disabling specific pipeline components for a block.                                                                                                               |
+| [`Language.analyze_pipes`](/api/language#analyze_pipes)                                                                       | [Analyze](/usage/processing-pipelines#analysis) components and their interdependencies.                                                                                                          |
+| [`Language.resume_training`](/api/language#resume_training)                                                                   | Experimental: continue training a pretrained model and initialize "rehearsal" for components that implement a `rehearse` method to prevent catastrophic forgetting.                              |
+| [`@Language.factory`](/api/language#factory) [`@Language.component`](/api/language#component)                                 | Decorators for [registering](/usage/processing-pipelines#custom-components) pipeline component factories and simple stateless component functions.                                               |
+| [`Language.has_factory`](/api/language#has_factory)                                                                           | Check whether a component factory is registered on a language class.s                                                                                                                            |
+| [`Language.get_factory_meta`](/api/language#get_factory_meta) [`Language.get_pipe_meta`](/api/language#get_factory_meta)      | Get the [`FactoryMeta`](/api/language#factorymeta) with component metadata for a factory or instance name.                                                                                       |
+| [`Language.config`](/api/language#config)                                                                                     | The [config](/usage/training#config) used to create the current `nlp` object. An instance of [`Config`](https://thinc.ai/docs/api-config#config) and can be saved to disk and used for training. |
+| [`Pipe.score`](/api/pipe#score)                                                                                               | Method on trainable pipeline components that returns a dictionary of evaluation scores.                                                                                                          |
+| [`registry`](/api/top-level#registry)                                                                                         | Function registry to map functions to string names that can be referenced in [configs](/usage/training#config).                                                                                  |
+| [`init config`](/api/cli#init-config) [`init fill-config`](/api/cli#init-fill-config) [`debug config`](/api/cli#debug-config) | CLI commands for initializing, auto-filling and debugging [training configs](/usage/training).                                                                                                   |
+| [`project`](/api/cli#project)                                                                                                 | Suite of CLI commands for cloning, running and managing [spaCy projects](/usage/projects).                                                                                                       |
 
 ## Backwards Incompatibilities {#incompat}
 
@@ -420,15 +418,20 @@ $ python -m spacy convert ./training.json ./output
 #### Training config {#migrating-training-config}
 
 The easiest way to get started with a training config is to use the
-[`init config`](/api/cli#init-config) command. You can start off with a blank
-config for a new model, copy the config from an existing model, or auto-fill a
-partial config like a starter config generated by our
-[quickstart widget](/usage/training#quickstart).
+[`init config`](/api/cli#init-config) command or the
+[quickstart widget](/usage/training#quickstart). You can define your
+requirements, and it will auto-generate a starter config with the best-matching
+default settings.
 
 ```bash
-python -m spacy init-config ./config.cfg --lang en --pipeline tagger,parser
+$ python -m spacy init config ./config.cfg --lang en --pipeline tagger,parser
 ```
 
+If you've exported a starter config from our
+[quickstart widget](/usage/training#quickstart), you can use the
+[`init fill-config`](/api/cli#init-fill-config) to fill it with all default
+values. You can then use the auto-generated `config.cfg` for training:
+
 ```diff
 ### {wrap="true"}
 - python -m spacy train en ./output ./train.json ./dev.json --pipeline tagger,parser --cnn-window 1 --bilstm-depth 0

From 8dcda351ec63d921761865d22bd93bbb469c6296 Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 10:23:27 +0200
Subject: [PATCH 13/92] typo's and quick note on default values

---
 website/docs/usage/training.md | 22 ++++++++++++----------
 1 file changed, 12 insertions(+), 10 deletions(-)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 94c5ea1cb..38c40088b 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -433,8 +433,8 @@ components are weighted equally.
 | Name                       | Description                                                                                                             |
 | -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
 | **Loss**                   | The training loss representing the amount of work left for the optimizer. Should decrease, but usually not to `0`.      |
-| **Precision** (P)          | The percentage of generated predictions that are correct. Should increase.                                                                                                        |
-| **Recall** (R)             | The percentage of gold-standard annotations that are in fact predicted. Should increase.                                                                                                        |
+| **Precision** (P)          | The percentage of generated predictions that are correct. Should increase.                                              |
+| **Recall** (R)             | The percentage of gold-standard annotations that are in fact predicted. Should increase.                                |
 | **F-Score** (F)            | The weighted average of precision and recall. Should increase.                                                          |
 | **UAS** / **LAS**          | Unlabeled and labeled attachment score for the dependency parser, i.e. the percentage of correct arcs. Should increase. |
 | **Words per second** (WPS) | Prediction speed in words per second. Should stay stable.                                                               |
@@ -483,11 +483,11 @@ language class and `nlp` object at different points of the lifecycle:
 | `after_creation`          | Called right after the `nlp` object is created, but before the pipeline components are added to the pipeline and receives the `nlp` object. Useful for modifying the tokenizer.          |
 | `after_pipeline_creation` | Called right after the pipeline components are created and added and receives the `nlp` object. Useful for modifying pipeline components.                                                |
 
-The `@spacy.registry.callbacks` decorator lets you register your custom function in the
-`callbacks` [registry](/api/top-level#registry) under a given name. You can then
-reference the function in a config block using the `@callbacks` key. If a block
-contains a key starting with an `@`, it's interpreted as a reference to a
-function. Because you've registered the function, spaCy knows how to create it
+The `@spacy.registry.callbacks` decorator lets you register your custom function
+in the `callbacks` [registry](/api/top-level#registry) under a given name. You
+can then reference the function in a config block using the `@callbacks` key. If
+a block contains a key starting with an `@`, it's interpreted as a reference to
+a function. Because you've registered the function, spaCy knows how to create it
 when you reference `"customize_language_data"` in your config. Here's an example
 of a callback that runs before the `nlp` object is created and adds a few custom
 tokenization rules to the defaults:
@@ -562,9 +562,9 @@ spaCy's configs are powered by our machine learning library Thinc's
 using [`pydantic`](https://github.com/samuelcolvin/pydantic). If your registered
 function provides type hints, the values that are passed in will be checked
 against the expected types. For example, `debug: bool` in the example above will
-ensure that the value received as the argument `debug` is an boolean. If the
+ensure that the value received as the argument `debug` is a boolean. If the
 value can't be coerced into a boolean, spaCy will raise an error.
-`start: pydantic.StrictBool` will force the value to be an boolean and raise an
+`debug: pydantic.StrictBool` will force the value to be a boolean and raise an
 error if it's not – for instance, if your config defines `1` instead of `true`.
 
 
@@ -612,7 +612,9 @@ settings in the block will be passed to the function as keyword arguments. Keep
 in mind that the config shouldn't have any hidden defaults and all arguments on
 the functions need to be represented in the config. If your function defines
 **default argument values**, spaCy is able to auto-fill your config when you run
-[`init fill-config`](/api/cli#init-fill-config).
+[`init fill-config`](/api/cli#init-fill-config). If you want to make sure that a
+given parameter is always explicitely set in the config, avoid setting a default
+value for it.
 
 ```ini
 ### config.cfg (excerpt)

From 705e1cb06c1f1d5298e9a41b3f1bfb01376f3001 Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 12:04:05 +0200
Subject: [PATCH 14/92] typo in link

---
 website/docs/api/corpus.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/website/docs/api/corpus.md b/website/docs/api/corpus.md
index 3631e201e..8c530ab6d 100644
--- a/website/docs/api/corpus.md
+++ b/website/docs/api/corpus.md
@@ -17,7 +17,7 @@ customize the data loading during training, you can register your own
 or evaluation data. It takes the same arguments as the `Corpus` class and
 returns a callable that yields [`Example`](/api/example) objects. You can
 replace it with your own registered function in the
-[`@readers` registry](/api/top-level#regsitry) to customize the data loading and
+[`@readers` registry](/api/top-level#registry) to customize the data loading and
 streaming.
 
 > #### Example config

From 10e67b400c91467b7eaf03bd8b7f49359b2c47ca Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 13:38:43 +0200
Subject: [PATCH 15/92] output_file required, spacy-transformers prefered
 instead of required

---
 spacy/cli/init_config.py | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/spacy/cli/init_config.py b/spacy/cli/init_config.py
index 7d80eb289..79c4acd11 100644
--- a/spacy/cli/init_config.py
+++ b/spacy/cli/init_config.py
@@ -39,7 +39,7 @@ class RecommendationSchema(BaseModel):
 @init_cli.command("config")
 def init_config_cli(
     # fmt: off
-    output_file: Path = Arg("-", help="File to save config.cfg to (or - for stdout)", allow_dash=True),
+    output_file: Path = Arg(..., help="File to save config.cfg to (or - for stdout, disabling logging)", allow_dash=True),
     lang: Optional[str] = Opt("en", "--lang", "-l", help="Two-letter code of the language to use"),
     pipeline: Optional[str] = Opt("tagger,parser,ner", "--pipeline", "-p", help="Comma-separated names of trainable pipeline components to include in the model (without 'tok2vec' or 'transformer')"),
     optimize: Optimizations = Opt(Optimizations.efficiency.value, "--optimize", "-o", help="Whether to optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger and slower model). This will impact the choice of architecture, pretrained weights and related hyperparameters."),
@@ -128,6 +128,8 @@ def init_config(
         "word_vectors": reco["word_vectors"],
         "has_letters": has_letters,
     }
+    if variables["transformer_data"] and not cpu:
+        variables["transformer_data"] = prefer_spacy_transformers(msg)
     base_template = template.render(variables).strip()
     # Giving up on getting the newlines right in jinja for now
     base_template = re.sub(r"\n\n\n+", "\n\n", base_template)
@@ -144,8 +146,6 @@ def init_config(
     for label, value in use_case.items():
         msg.text(f"- {label}: {value}")
     use_transformer = bool(template_vars.use_transformer)
-    if use_transformer:
-        require_spacy_transformers(msg)
     with show_validation_error(hint_fill=False):
         config = util.load_config_from_str(base_template)
         nlp, _ = util.load_model_from_config(config, auto_fill=True)
@@ -167,12 +167,13 @@ def save_config(config: Config, output_file: Path, is_stdout: bool = False) -> N
         print(f"{COMMAND} train {output_file.parts[-1]} {' '.join(variables)}")
 
 
-def require_spacy_transformers(msg: Printer) -> None:
+def prefer_spacy_transformers(msg: Printer) -> bool:
     try:
         import spacy_transformers  # noqa: F401
     except ImportError:
-        msg.fail(
-            "Using a transformer-based pipeline requires spacy-transformers "
-            "to be installed.",
-            exits=1,
+        msg.info(
+            "Recommend to install 'spacy-transformers' to create a more efficient "
+            "transformer-based pipeline."
         )
+        return False
+    return True

From 96a9c65f97059d33fa19f9fe0ff0d89abb739259 Mon Sep 17 00:00:00 2001
From: Matthew Honnibal 
Date: Tue, 18 Aug 2020 13:50:55 +0200
Subject: [PATCH 16/92] Add model architectures intro

---
 website/docs/usage/training.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 9f74cafac..fc96a76c1 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -385,6 +385,19 @@ that reference this variable.
 ### Model architectures {#model-architectures}
 
 
+A **model architecture** is a function that wires up a Thinc `Model` instance,
+which you can then use in a component or as a layer of a larger network. You
+can use Thinc as a thin wrapper around frameworks such as PyTorch, Tensorflow
+or MXNet, or you can implement your logic in Thinc directly.
+
+spaCy's built-in components will never construct their `Model` instances
+themselves, so you won't have to subclass the component to change its model
+architecture. You can just update the config so that it refers
+to a different registered function. Once the component has been created, its
+model instance has already been assigned, so you cannot change its model
+architecture. The architecture is like a recipe for the network, and you can't
+change the recipe once the dish has already been prepared. You have to make
+a new one.
 
 ### Metrics, training output and weighted scores {#metrics}
 

From 574fd53289c80d43676643c5be985658b1ee48a3 Mon Sep 17 00:00:00 2001
From: Matthew Honnibal 
Date: Tue, 18 Aug 2020 13:51:08 +0200
Subject: [PATCH 17/92] Add precision/recall description

---
 website/docs/usage/training.md | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index fc96a76c1..acfa4afa8 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -454,7 +454,22 @@ components are weighted equally.
 | **UAS** / **LAS**          | Unlabeled and labeled attachment score for the dependency parser, i.e. the percentage of correct arcs. Should increase. |
 | **Words per second** (WPS) | Prediction speed in words per second. Should stay stable.                                                               |
 
+Precision and recall are two common measurements of a model's accuracy. You
+need precision and recall statistics whenever your model can return a variable
+number of predictions, as in this situation there are two different ways your
+model can be "accurate".
+
+Precision refers to the percentage of predicted annotations that were correct,
+while recall refers to the percentage of reference annotations recovered.
+A model that only returns one entity for a document will have precision 1.0 if
+that entity is correct, but might have low recall if it has missed lots of
+other correct entities. F-score is the harmonic mean of precision and recall.
+The harmonic mean is used instead of the arithmetic mean so that systems with
+very low precision or very low recall will score lower than systems that
+achieve a balance of the two.
+
 
+
 
 Note that if the development data has raw text, some of the gold-standard
 entities might not align to the predicted tokenization. These tokenization

From b72bd1767f46a6fc82a682a03ba5e28363285e02 Mon Sep 17 00:00:00 2001
From: Matthew Honnibal 
Date: Tue, 18 Aug 2020 13:52:22 +0200
Subject: [PATCH 18/92] Remove todo

---
 website/docs/usage/training.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index acfa4afa8..3943cf061 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -384,7 +384,6 @@ that reference this variable.
 
 ### Model architectures {#model-architectures}
 
-
 A **model architecture** is a function that wires up a Thinc `Model` instance,
 which you can then use in a component or as a layer of a larger network. You
 can use Thinc as a thin wrapper around frameworks such as PyTorch, Tensorflow

From 82f0e20318dc43cb5463b53d046694c11fb069cc Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Tue, 18 Aug 2020 14:39:40 +0200
Subject: [PATCH 19/92] Update docs and consistency [ci skip]

---
 spacy/cli/package.py                          |   1 +
 spacy/schemas.py                              |  26 +-
 website/docs/api/architectures.md             | 134 +++++---
 website/docs/api/cli.md                       |  12 +-
 website/docs/api/data-formats.md              | 163 +++++++---
 website/docs/api/language.md                  |  14 +-
 website/docs/api/tok2vec.md                   |   2 +-
 website/docs/api/top-level.md                 |  21 +-
 website/docs/images/tok2vec-listener.svg      |  41 +++
 website/docs/images/tok2vec.svg               |  17 +
 website/docs/usage/embeddings-transformers.md |  34 +-
 website/docs/usage/models.md                  |  17 +-
 website/docs/usage/processing-pipelines.md    |   2 +-
 website/docs/usage/rule-based-matching.md     |  11 +-
 website/docs/usage/saving-loading.md          |  50 ++-
 website/docs/usage/training.md                |  66 ++--
 website/docs/usage/transformers.md            | 305 ------------------
 website/docs/usage/v3.md                      |   6 +
 website/meta/type-annotations.json            |   1 +
 website/src/components/icon.js                |  11 +-
 website/src/components/table.js               |  28 +-
 website/src/widgets/quickstart-training.js    |  13 +-
 22 files changed, 468 insertions(+), 507 deletions(-)
 create mode 100644 website/docs/images/tok2vec-listener.svg
 create mode 100644 website/docs/images/tok2vec.svg
 delete mode 100644 website/docs/usage/transformers.md

diff --git a/spacy/cli/package.py b/spacy/cli/package.py
index a1162f3e1..523e8a99a 100644
--- a/spacy/cli/package.py
+++ b/spacy/cli/package.py
@@ -229,6 +229,7 @@ if __name__ == '__main__':
 
 TEMPLATE_MANIFEST = """
 include meta.json
+include config.cfg
 """.strip()
 
 
diff --git a/spacy/schemas.py b/spacy/schemas.py
index 0f2a35c60..e219c2009 100644
--- a/spacy/schemas.py
+++ b/spacy/schemas.py
@@ -167,18 +167,20 @@ class ModelMetaSchema(BaseModel):
     lang: StrictStr = Field(..., title="Two-letter language code, e.g. 'en'")
     name: StrictStr = Field(..., title="Model name")
     version: StrictStr = Field(..., title="Model version")
-    spacy_version: Optional[StrictStr] = Field(None, title="Compatible spaCy version identifier")
-    parent_package: Optional[StrictStr] = Field("spacy", title="Name of parent spaCy package, e.g. spacy or spacy-nightly")
-    pipeline: Optional[List[StrictStr]] = Field([], title="Names of pipeline components")
-    description: Optional[StrictStr] = Field(None, title="Model description")
-    license: Optional[StrictStr] = Field(None, title="Model license")
-    author: Optional[StrictStr] = Field(None, title="Model author name")
-    email: Optional[StrictStr] = Field(None, title="Model author email")
-    url: Optional[StrictStr] = Field(None, title="Model author URL")
-    sources: Optional[Union[List[StrictStr], Dict[str, str]]] = Field(None, title="Training data sources")
-    vectors: Optional[Dict[str, Any]] = Field(None, title="Included word vectors")
-    accuracy: Optional[Dict[str, Union[float, int]]] = Field(None, title="Accuracy numbers")
-    speed: Optional[Dict[str, Union[float, int]]] = Field(None, title="Speed evaluation numbers")
+    spacy_version: StrictStr = Field("", title="Compatible spaCy version identifier")
+    parent_package: StrictStr = Field("spacy", title="Name of parent spaCy package, e.g. spacy or spacy-nightly")
+    pipeline: List[StrictStr] = Field([], title="Names of pipeline components")
+    description: StrictStr = Field("", title="Model description")
+    license: StrictStr = Field("", title="Model license")
+    author: StrictStr = Field("", title="Model author name")
+    email: StrictStr = Field("", title="Model author email")
+    url: StrictStr = Field("", title="Model author URL")
+    sources: Optional[Union[List[StrictStr], List[Dict[str, str]]]] = Field(None, title="Training data sources")
+    vectors: Dict[str, Any] = Field({}, title="Included word vectors")
+    labels: Dict[str, Dict[str, List[str]]] = Field({}, title="Component labels, keyed by component name")
+    accuracy: Dict[str, Union[float, Dict[str, float]]] = Field({}, title="Accuracy numbers")
+    speed: Dict[str, Union[float, int]] = Field({}, title="Speed evaluation numbers")
+    spacy_git_version: StrictStr = Field("", title="Commit of spaCy version used")
     # fmt: on
 
 
diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md
index 8bb5cdeea..737ca2fa2 100644
--- a/website/docs/api/architectures.md
+++ b/website/docs/api/architectures.md
@@ -33,7 +33,7 @@ TODO: intro and how architectures work, link to
 > subword_features = true
 > ```
 
-Build spaCy's "standard" tok2vec layer, which uses hash embedding with subword
+Build spaCy's "standard" embedding layer, which uses hash embedding with subword
 features and a CNN with layer-normalized maxout.
 
 | Name                 | Description                                                                                                                                                                                                                                                                   |
@@ -45,6 +45,7 @@ features and a CNN with layer-normalized maxout.
 | `maxout_pieces`      | The number of pieces to use in the maxout non-linearity. If `1`, the [`Mish`](https://thinc.ai/docs/api-layers#mish) non-linearity is used instead. Recommended values are `1`-`3`. ~~int~~                                                                                   |
 | `subword_features`   | Whether to also embed subword features, specifically the prefix, suffix and word shape. This is recommended for alphabetic languages like English, but not if single-character tokens are used for a language such as Chinese. ~~bool~~                                       |
 | `pretrained_vectors` | Whether to also use static vectors. ~~bool~~                                                                                                                                                                                                                                  |
+| **CREATES**          | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                                                                                                                                                                                                        |
 
 ### spacy.Tok2Vec.v1 {#Tok2Vec}
 
@@ -67,10 +68,11 @@ Construct a tok2vec model out of embedding and encoding subnetworks. See the
 ["Embed, Encode, Attend, Predict"](https://explosion.ai/blog/deep-learning-formula-nlp)
 blog post for background.
 
-| Name     | Description                                                                                                                                                                                                                      |
-| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `embed`  | Embed tokens into context-independent word vector representations. For example, [CharacterEmbed](/api/architectures#CharacterEmbed) or [MultiHashEmbed](/api/architectures#MultiHashEmbed). ~~Model[List[Doc], List[Floats2d]]~~ |
-| `encode` | Encode context into the embeddings, using an architecture such as a CNN, BiLSTM or transformer. For example, [MaxoutWindowEncoder](/api/architectures#MaxoutWindowEncoder). ~~Model[List[Floats2d], List[Floats2d]]~~            |
+| Name        | Description                                                                                                                                                                                                                      |
+| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `embed`     | Embed tokens into context-independent word vector representations. For example, [CharacterEmbed](/api/architectures#CharacterEmbed) or [MultiHashEmbed](/api/architectures#MultiHashEmbed). ~~Model[List[Doc], List[Floats2d]]~~ |
+| `encode`    | Encode context into the embeddings, using an architecture such as a CNN, BiLSTM or transformer. For example, [MaxoutWindowEncoder](/api/architectures#MaxoutWindowEncoder). ~~Model[List[Floats2d], List[Floats2d]]~~            |
+| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                                                                                                                                                           |
 
 ### spacy.Tok2VecListener.v1 {#Tok2VecListener}
 
@@ -108,10 +110,13 @@ Instead of defining its own `Tok2Vec` instance, a model architecture like
 [Tagger](/api/architectures#tagger) can define a listener as its `tok2vec`
 argument that connects to the shared `tok2vec` component in the pipeline.
 
-| Name       | Description                                                                                                                                                                                                                                                                                                    |
-| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `width`    | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. ~~int~~                                                                                                                                                                                                               |
-| `upstream` | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. ~~str~~ |
+
+
+| Name        | Description                                                                                                                                                                                                                                                                                                    |
+| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `width`     | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. ~~int~~                                                                                                                                                                                                               |
+| `upstream`  | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. ~~str~~ |
+| **CREATES** | The model using the architecture. ~~Model~~                                                                                                                                                                                                                                                                    |
 
 ### spacy.MultiHashEmbed.v1 {#MultiHashEmbed}
 
@@ -134,12 +139,15 @@ definitions depending on the `Vocab` of the `Doc` object passed in. Vectors from
 pretrained static vectors can also be incorporated into the concatenated
 representation.
 
+
+
 | Name                      | Description                                                                                                                                                                                                       |
 | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `width`                   | The output width. Also used as the width of the embedding tables. Recommended values are between `64` and `300`. ~~int~~                                                                                          |
 | `rows`                    | The number of rows for the embedding tables. Can be low, due to the hashing trick. Embeddings for prefix, suffix and word shape use half as many rows. Recommended values are between `2000` and `10000`. ~~int~~ |
 | `also_embed_subwords`     | Whether to use the `PREFIX`, `SUFFIX` and `SHAPE` features in the embeddings. If not using these, you may need more rows in your hash embeddings, as there will be increased chance of collisions. ~~bool~~       |
 | `also_use_static_vectors` | Whether to also use static word vectors. Requires a vectors table to be loaded in the [Doc](/api/doc) objects' vocab. ~~bool~~                                                                                    |
+| **CREATES**               | The model using the architecture. ~~Model~~                                                                                                                                                                       |
 
 ### spacy.CharacterEmbed.v1 {#CharacterEmbed}
 
@@ -170,12 +178,15 @@ concatenated. A hash-embedded vector of the `NORM` of the word is also
 concatenated on, and the result is then passed through a feed-forward network to
 construct a single vector to represent the information.
 
-| Name    | Description                                                                                                                                                     |
-| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `width` | The width of the output vector and the `NORM` hash embedding. ~~int~~                                                                                           |
-| `rows`  | The number of rows in the `NORM` hash embedding table. ~~int~~                                                                                                  |
-| `nM`    | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. ~~int~~                                                           |
-| `nC`    | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. ~~int~~ |
+
+
+| Name        | Description                                                                                                                                                     |
+| ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `width`     | The width of the output vector and the `NORM` hash embedding. ~~int~~                                                                                           |
+| `rows`      | The number of rows in the `NORM` hash embedding table. ~~int~~                                                                                                  |
+| `nM`        | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. ~~int~~                                                           |
+| `nC`        | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. ~~int~~ |
+| **CREATES** | The model using the architecture. ~~Model~~                                                                                                                     |
 
 ### spacy.MaxoutWindowEncoder.v1 {#MaxoutWindowEncoder}
 
@@ -199,6 +210,7 @@ and residual connections.
 | `window_size`   | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~                                                                                           |
 | `maxout_pieces` | The number of maxout pieces to use. Recommended values are `2` or `3`. ~~int~~                                                                                                                                 |
 | `depth`         | The number of convolutional layers. Recommended value is `4`. ~~int~~                                                                                                                                          |
+| **CREATES**     | The model using the architecture. ~~Model[List[Floats2d], List[Floats2d]]~~                                                                                                                                    |
 
 ### spacy.MishWindowEncoder.v1 {#MishWindowEncoder}
 
@@ -221,6 +233,7 @@ and residual connections.
 | `width`       | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. ~~int~~ |
 | `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~                                                                                           |
 | `depth`       | The number of convolutional layers. Recommended value is `4`. ~~int~~                                                                                                                                          |
+| **CREATES**   | The model using the architecture. ~~Model[List[Floats2d], List[Floats2d]]~~                                                                                                                                    |
 
 ### spacy.TorchBiLSTMEncoder.v1 {#TorchBiLSTMEncoder}
 
@@ -242,10 +255,38 @@ Encode context using bidirectional LSTM layers. Requires
 | `width`       | The input and output width. These are required to be the same, to allow residual connections. This value will be determined by the width of the inputs. Recommended values are between `64` and `300`. ~~int~~ |
 | `window_size` | The number of words to concatenate around each token to construct the convolution. Recommended value is `1`. ~~int~~                                                                                           |
 | `depth`       | The number of convolutional layers. Recommended value is `4`. ~~int~~                                                                                                                                          |
+| **CREATES**   | The model using the architecture. ~~Model[List[Floats2d], List[Floats2d]]~~                                                                                                                                    |
 
 ### spacy.StaticVectors.v1 {#StaticVectors}
 
-
+> #### Example config
+>
+> ```ini
+> [model]
+> @architectures = "spacy.StaticVectors.v1"
+> nO = null
+> nM = null
+> dropout = 0.2
+> key_attr = "ORTH"
+>
+> [model.init_W]
+> @initializers = "glorot_uniform_init.v1"
+> ```
+
+Embed [`Doc`](/api/doc) objects with their vocab's vectors table, applying a
+learned linear projection to control the dimensionality. See the documentation
+on [static vectors](/usage/embeddings-transformers#static-vectors) for details.
+
+
+
+| Name        |  Description                                                                                                                                                                                                            |
+| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `nO`        | Defaults to `None`. ~~Optional[int]~~                                                                                                                                                                                   |
+| `nM`        | Defaults to `None`. ~~Optional[int]~~                                                                                                                                                                                   |
+| `dropout`   | Optional dropout rate. If set, it's applied per dimension over the whole batch. Defaults to `None`. ~~Optional[float]~~                                                                                                 |
+| `init_W`    | The [initialization function](https://thinc.ai/docs/api-initializers). Defaults to [`glorot_uniform_init`](https://thinc.ai/docs/api-initializers#glorot_uniform_init). ~~Callable[[Ops, Tuple[int, ...]]], FloatsXd]~~ |
+| `key_attr`  | Defaults to `"ORTH"`. ~~str~~                                                                                                                                                                                           |
+| **CREATES** | The model using the architecture. ~~Model[List[Doc], Ragged]~~                                                                                                                                                          |
 
 ## Transformer architectures {#transformers source="github.com/explosion/spacy-transformers/blob/master/spacy_transformers/architectures.py"}
 
@@ -277,6 +318,7 @@ architectures into your training config.
 | `name`             | Any model name that can be loaded by [`transformers.AutoModel`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoModel). ~~str~~                                                                                              |
 | `get_spans`        | Function that takes a batch of [`Doc`](/api/doc) object and returns lists of [`Span`](/api) objects to process by the transformer. [See here](/api/transformer#span_getters) for built-in options and examples. ~~Callable[[List[Doc]], List[Span]]~~ |
 | `tokenizer_config` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). ~~Dict[str, Any]~~                                                                                   |
+| **CREATES**        | The model using the architecture. ~~Model[List[Doc], FullTransformerBatch]~~                                                                                                                                                                          |
 
 ### spacy-transformers.Tok2VecListener.v1 {#transformers-Tok2VecListener}
 
@@ -305,6 +347,7 @@ a single token vector given zero or more wordpiece vectors.
 | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `pooling`     | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. ~~Model[Ragged, Floats2d]~~                            |
 | `grad_factor` | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. ~~float~~ |
+| **CREATES**   | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                                                                                                                                                                                                        |
 
 ### spacy-transformers.Tok2VecTransformer.v1 {#Tok2VecTransformer}
 
@@ -330,6 +373,7 @@ one component.
 | `tokenizer_config` | Tokenizer settings passed to [`transformers.AutoTokenizer`](https://huggingface.co/transformers/model_doc/auto.html#transformers.AutoTokenizer). ~~Dict[str, Any]~~                                                                                                           |
 | `pooling`          | A reduction layer used to calculate the token vectors based on zero or more wordpiece vectors. If in doubt, mean pooling (see [`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean)) is usually a good choice. ~~Model[Ragged, Floats2d]~~                            |
 | `grad_factor`      | Reweight gradients from the component before passing them upstream. You can set this to `0` to "freeze" the transformer weights with respect to the component, or use it to make some components more significant than others. Leaving it at `1.0` is usually fine. ~~float~~ |
+| **CREATES**        | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                                                                                                                                                                                                        |
 
 ## Parser & NER architectures {#parser}
 
@@ -372,6 +416,8 @@ consists of either two or three subnetworks:
   state representation. If not present, the output from the lower model is used
   as action scores directly.
 
+
+
 | Name                | Description                                                                                                                                                                                                                                                                                                                                                             |
 | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `tok2vec`           | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~                                                                                                                                                                                                                                                                              |
@@ -380,6 +426,7 @@ consists of either two or three subnetworks:
 | `maxout_pieces`     | How many pieces to use in the state prediction layer. Recommended values are `1`, `2` or `3`. If `1`, the maxout non-linearity is replaced with a [`Relu`](https://thinc.ai/docs/api-layers#relu) non-linearity if `use_upper` is `True`, and no non-linearity if `False`. ~~int~~                                                                                      |
 | `use_upper`         | Whether to use an additional hidden layer after the state vector in order to predict the action scores. It is recommended to set this to `False` for large pretrained models such as transformers, and `True` for smaller networks. The upper layer is computed on CPU, which becomes a bottleneck on larger GPU-based models, where it's also less necessary. ~~bool~~ |
 | `nO`                | The number of actions the model will predict between. Usually inferred from data at the beginning of training, or loaded from disk. ~~int~~                                                                                                                                                                                                                             |
+| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                                                                                                                                                                                             |
 
 ### spacy.BILUOTagger.v1 {#BILUOTagger source="spacy/ml/models/simple_ner.py"}
 
@@ -406,9 +453,10 @@ generally results in better linear separation between classes, especially for
 non-CRF models, because there are more distinct classes for the different
 situations ([Ratinov et al., 2009](https://www.aclweb.org/anthology/W09-1119/)).
 
-| Name      | Description                                                                                |
-| --------- | ------------------------------------------------------------------------------------------ |
-| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
+| Name        | Description                                                                                |
+| ----------- | ------------------------------------------------------------------------------------------ |
+| `tok2vec`   | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
+| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                     |
 
 ### spacy.IOBTagger.v1 {#IOBTagger source="spacy/ml/models/simple_ner.py"}
 
@@ -431,9 +479,10 @@ spans into tags assigned to each token. The first token of a span is given the
 tag B-LABEL, and subsequent tokens are given the tag I-LABEL. All other tokens
 are assigned the tag O.
 
-| Name      | Description                                                                                |
-| --------- | ------------------------------------------------------------------------------------------ |
-| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
+| Name        | Description                                                                                |
+| ----------- | ------------------------------------------------------------------------------------------ |
+| `tok2vec`   | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
+| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                     |
 
 ## Tagging architectures {#tagger source="spacy/ml/models/tagger.py"}
 
@@ -454,10 +503,11 @@ Build a tagger model, using a provided token-to-vector component. The tagger
 model simply adds a linear layer with softmax activation to predict scores given
 the token vectors.
 
-| Name      | Description                                                                                |
-| --------- | ------------------------------------------------------------------------------------------ |
-| `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
-| `nO`      | The number of tags to output. Inferred from the data if `None`. ~~Optional[int]~~          |
+| Name        | Description                                                                                |
+| ----------- | ------------------------------------------------------------------------------------------ |
+| `tok2vec`   | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ |
+| `nO`        | The number of tags to output. Inferred from the data if `None`. ~~Optional[int]~~          |
+| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~                     |
 
 ## Text classification architectures {#textcat source="spacy/ml/models/textcat.py"}
 
@@ -474,9 +524,6 @@ specific data and challenge.
 
 ### spacy.TextCatEnsemble.v1 {#TextCatEnsemble}
 
-Stacked ensemble of a bag-of-words model and a neural network model. The neural
-network has an internal CNN Tok2Vec layer and uses attention.
-
 > #### Example Config
 >
 > ```ini
@@ -493,6 +540,11 @@ network has an internal CNN Tok2Vec layer and uses attention.
 > nO = null
 > ```
 
+Stacked ensemble of a bag-of-words model and a neural network model. The neural
+network has an internal CNN Tok2Vec layer and uses attention.
+
+
+
 | Name                 | Description                                                                                                                                                                                            |
 | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | `exclusive_classes`  | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
@@ -504,6 +556,7 @@ network has an internal CNN Tok2Vec layer and uses attention.
 | `ngram_size`         | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                    |
 | `dropout`            | The dropout rate. ~~float~~                                                                                                                                                                            |
 | `nO`                 | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
+| **CREATES**          | The model using the architecture. ~~Model~~                                                                                                                                                            |
 
 ### spacy.TextCatCNN.v1 {#TextCatCNN}
 
@@ -530,11 +583,14 @@ A neural network model where token vectors are calculated using a CNN. The
 vectors are mean pooled and used as features in a feed-forward network. This
 architecture is usually less accurate than the ensemble, but runs faster.
 
+
+
 | Name                | Description                                                                                                                                                                                            |
 | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
 | `tok2vec`           | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~                                                                                                                                                |
 | `nO`                | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
+| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                            |
 
 ### spacy.TextCatBOW.v1 {#TextCatBOW}
 
@@ -552,12 +608,15 @@ architecture is usually less accurate than the ensemble, but runs faster.
 An ngram "bag-of-words" model. This architecture should run much faster than the
 others, but may not be as accurate, especially if texts are short.
 
+
+
 | Name                | Description                                                                                                                                                                                            |
 | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
 | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
 | `ngram_size`        | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                    |
 | `no_output_layer`   | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~                                                                   |
 | `nO`                | Output dimension, determined by the number of different labels. If not set, the the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
+| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                            |
 
 ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"}
 
@@ -574,9 +633,6 @@ into the "real world". This requires 3 main component
 
 ### spacy.EntityLinker.v1 {#EntityLinker}
 
-The `EntityLinker` model architecture is a Thinc `Model` with a
-[`Linear`](https://thinc.ai/api-layers#linear) output layer.
-
 > #### Example Config
 >
 > ```ini
@@ -602,10 +658,16 @@ The `EntityLinker` model architecture is a Thinc `Model` with a
 > @assets = "spacy.CandidateGenerator.v1"
 > ```
 
-| Name      | Description                                                                                                                                                                                                             |
-| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~                                                                                                                                                                 |
-| `nO`      | Output dimension, determined by the length of the vectors encoding each entity in the KB. If the `nO` dimension is not set, the entity linking component will set it when `begin_training` is called. ~~Optional[int]~~ |
+The `EntityLinker` model architecture is a Thinc `Model` with a
+[`Linear`](https://thinc.ai/api-layers#linear) output layer.
+
+
+
+| Name        | Description                                                                                                                                                                                                             |
+| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `tok2vec`   | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~                                                                                                                                                                 |
+| `nO`        | Output dimension, determined by the length of the vectors encoding each entity in the KB. If the `nO` dimension is not set, the entity linking component will set it when `begin_training` is called. ~~Optional[int]~~ |
+| **CREATES** | The model using the architecture. ~~Model~~                                                                                                                                                                             |
 
 ### spacy.EmptyKB.v1 {#EmptyKB}
 
diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md
index b614898df..c7a1c3f06 100644
--- a/website/docs/api/cli.md
+++ b/website/docs/api/cli.md
@@ -719,11 +719,11 @@ $ python -m spacy evaluate [model] [data_path] [--output] [--gold-preproc]
 
 Generate an installable
 [model Python package](/usage/training#models-generating) from an existing model
-data directory. All data files are copied over. If the path to a `meta.json` is
-supplied, or a `meta.json` is found in the input directory, this file is used.
-Otherwise, the data can be entered directly from the command line. spaCy will
-then create a `.tar.gz` archive file that you can distribute and install with
-`pip install`.
+data directory. All data files are copied over. If the path to a
+[`meta.json`](/api/data-formats#meta) is supplied, or a `meta.json` is found in
+the input directory, this file is used. Otherwise, the data can be entered
+directly from the command line. spaCy will then create a `.tar.gz` archive file
+that you can distribute and install with `pip install`.
 
 
 
@@ -750,7 +750,7 @@ $ python -m spacy package [input_dir] [output_dir] [--meta-path] [--create-meta]
 | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
 | `input_dir`                                      | Path to directory containing model data. ~~Path (positional)~~                                                                                                                                                  |
 | `output_dir`                                     | Directory to create package folder in. ~~Path (positional)~~                                                                                                                                                    |
-| `--meta-path`, `-m` 2   | Path to `meta.json` file (optional). ~~Optional[Path] \(option)~~                                                                                                                                               |
+| `--meta-path`, `-m` 2   | Path to [`meta.json`](/api/data-formats#meta) file (optional). ~~Optional[Path] \(option)~~                                                                                                                     |
 | `--create-meta`, `-C` 2 | Create a `meta.json` file on the command line, even if one already exists in the directory. If an existing file is found, its entries will be shown as the defaults in the command line prompt. ~~bool (flag)~~ |
 | `--no-sdist`, `-NS`,                             | Don't build the `.tar.gz` sdist automatically. Can be set if you want to run this step manually. ~~bool (flag)~~                                                                                                |
 | `--version`, `-v` 3     | Package version to override in meta. Useful when training new versions, as it doesn't require editing the meta template. ~~Optional[str] \(option)~~                                                            |
diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md
index 4577d7ef3..56528de43 100644
--- a/website/docs/api/data-formats.md
+++ b/website/docs/api/data-formats.md
@@ -6,6 +6,7 @@ menu:
   - ['Training Data', 'training']
   - ['Pretraining Data', 'pretraining']
   - ['Vocabulary', 'vocab']
+  - ['Model Meta', 'meta']
 ---
 
 This section documents input and output formats of data used by spaCy, including
@@ -73,15 +74,15 @@ your config and check that it's valid, you can run the
 Defines the `nlp` object, its tokenizer and
 [processing pipeline](/usage/processing-pipelines) component names.
 
-| Name                      | Description                                                                                                                                                                                                    | Default                       |
-| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
-| `lang`                    | The language code to use. ~~str~~                                                                                                                                                                              | `null`                        |
-| `pipeline`                | Names of pipeline components in order. Should correspond to sections in the `[components]` block, e.g. `[components.ner]`. See docs on [defining components](/usage/training#config-components). ~~List[str]~~ | `[]`                          |
-| `load_vocab_data`         | Whether to load additional lexeme and vocab data from [`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data) if available. ~~bool~~                                                           | `true`                        |
-| `before_creation`         | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `Language` subclass before it's initialized. ~~Optional[Callable[[Type[Language]], Type[Language]]]~~                                 | `null`                        |
-| `after_creation`          | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object right after it's initialized. ~~Optional[Callable[[Language], Language]]~~                                               | `null`                        |
-| `after_pipeline_creation` | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object after the pipeline components have been added. ~~Optional[Callable[[Language], Language]]~~                              | `null`                        |
-| `tokenizer`               | The tokenizer to use. ~~Callable[[str], Doc]~~                                                                                                                                                                 | [`Tokenizer`](/api/tokenizer) |
+| Name                      | Description                                                                                                                                                                                                                      |
+| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `lang`                    | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Defaults to `null`. ~~str~~                                                                                                                    |
+| `pipeline`                | Names of pipeline components in order. Should correspond to sections in the `[components]` block, e.g. `[components.ner]`. See docs on [defining components](/usage/training#config-components). Defaults to `[]`. ~~List[str]~~ |
+| `load_vocab_data`         | Whether to load additional lexeme and vocab data from [`spacy-lookups-data`](https://github.com/explosion/spacy-lookups-data) if available. Defaults to `true`. ~~bool~~                                                         |
+| `before_creation`         | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `Language` subclass before it's initialized. Defaults to `null`. ~~Optional[Callable[[Type[Language]], Type[Language]]]~~                               |
+| `after_creation`          | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object right after it's initialized. Defaults to `null`. ~~Optional[Callable[[Language], Language]]~~                                             |
+| `after_pipeline_creation` | Optional [callback](/usage/training#custom-code-nlp-callbacks) to modify `nlp` object after the pipeline components have been added. Defaults to `null`. ~~Optional[Callable[[Language], Language]]~~                            |
+| `tokenizer`               | The tokenizer to use. Defaults to [`Tokenizer`](/api/tokenizer). ~~Callable[[str], Doc]~~                                                                                                                                        |
 
 ### components {#config-components tag="section"}
 
@@ -128,24 +129,24 @@ process that are used when you run [`spacy train`](/api/cli#train).
 
 
 
-| Name                  | Description                                                                                                                                                   | Default                                             |
-| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
-| `seed`                | The random seed. ~~int~~                                                                                                                                      | `${system:seed}`                                    |
-| `dropout`             | The dropout rate. ~~float~~                                                                                                                                   | `0.1`                                               |
-| `accumulate_gradient` | Whether to divide the batch up into substeps. ~~int~~                                                                                                         | `1`                                                 |
-| `init_tok2vec`        | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). ~~Optional[str]~~                                             | `${paths:init_tok2vec}`                             |
-| `raw_text`            | ~~Optional[str]~~                                                                                                                                             | `${paths:raw}`                                      |
-| `vectors`             | ~~Optional[str]~~                                                                                                                                             | `null`                                              |
-| `patience`            | How many steps to continue without improvement in evaluation score. ~~int~~                                                                                   | `1600`                                              |
-| `max_epochs`          | Maximum number of epochs to train for. ~~int~~                                                                                                                | `0`                                                 |
-| `max_steps`           | Maximum number of update steps to train for. ~~int~~                                                                                                          | `20000`                                             |
-| `eval_frequency`      | How often to evaluate during training (steps). ~~int~~                                                                                                        | `200`                                               |
-| `score_weights`       | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. ~~Dict[str, float]~~   | `{}`                                                |
-| `frozen_components`   | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. ~~List[str]~~ | `[]`                                                |
-| `train_corpus`        | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. ~~Callable[[Language], Iterator[Example]]~~                        | [`Corpus`](/api/corpus)                             |
-| `dev_corpus`          | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. ~~Callable[[Language], Iterator[Example]]~~                        | [`Corpus`](/api/corpus)                             |
-| `batcher`             | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. ~~Callable[[Iterator[Doc], Iterator[List[Doc]]]]~~                 | [`batch_by_words`](/api/top-level#batch_by_words)   |
-| `optimizer`           | The optimizer. The learning rate schedule and other settings can be configured as part of the optimizer. ~~Optimizer~~                                        | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) |
+| Name                  | Description                                                                                                                                                                                                  |
+| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| `seed`                | The random seed. Defaults to variable `${system:seed}`. ~~int~~                                                                                                                                              |
+| `dropout`             | The dropout rate. Defaults to `0.1`. ~~float~~                                                                                                                                                               |
+| `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~                                                                                                                                       |
+| `init_tok2vec`        | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths:init_tok2vec}`. ~~Optional[str]~~                                              |
+| `raw_text`            | TODO: ... Defaults to variable `${paths:raw}`. ~~Optional[str]~~                                                                                                                                             |
+| `vectors`             | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~                                          |
+| `patience`            | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~                                                                                                              |
+| `max_epochs`          | Maximum number of epochs to train for. Defaults to `0`. ~~int~~                                                                                                                                              |
+| `max_steps`           | Maximum number of update steps to train for. Defaults to `20000`. ~~int~~                                                                                                                                    |
+| `eval_frequency`      | How often to evaluate during training (steps). Defaults to `200`. ~~int~~                                                                                                                                    |
+| `score_weights`       | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. Defaults to `{}`. ~~Dict[str, float]~~                                |
+| `frozen_components`   | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. Defaults to `[]`. ~~List[str]~~                              |
+| `train_corpus`        | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~                                  |
+| `dev_corpus`          | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~                                  |
+| `batcher`             | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. Defaults to [`batch_by_words`](/api/top-level#batch_by_words). ~~Callable[[Iterator[Doc], Iterator[List[Doc]]]]~~ |
+| `optimizer`           | The optimizer. The learning rate schedule and other settings can be configured as part of the optimizer. Defaults to [`Adam`](https://thinc.ai/docs/api-optimizers#adam). ~~Optimizer~~                      |
 
 ### pretraining {#config-pretraining tag="section,optional"}
 
@@ -153,19 +154,19 @@ This section is optional and defines settings and controls for
 [language model pretraining](/usage/training#pretraining). It's used when you
 run [`spacy pretrain`](/api/cli#pretrain).
 
-| Name                         | Description                                                                                                 | Default                                             |
-| ---------------------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
-| `max_epochs`                 | Maximum number of epochs. ~~int~~                                                                           | `1000`                                              |
-| `min_length`                 | Minimum length of examples. ~~int~~                                                                         | `5`                                                 |
-| `max_length`                 | Maximum length of examples. ~~int~~                                                                         | `500`                                               |
-| `dropout`                    | The dropout rate. ~~float~~                                                                                 | `0.2`                                               |
-| `n_save_every`               | Saving frequency. ~~int~~                                                                                   | `null`                                              |
-| `batch_size`                 | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). ~~Union[int, Sequence[int]]~~ | `3000`                                              |
-| `seed`                       | The random seed. ~~int~~                                                                                    | `${system.seed}`                                    |
-| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. ~~bool~~                                                                       | `${system:use_pytorch_for_gpu_memory}`              |
-| `tok2vec_model`              | tok2vec model section in the config. ~~str~~                                                                | `"components.tok2vec.model"`                        |
-| `objective`                  | The pretraining objective. ~~Dict[str, Any]~~                                                               | `{"type": "characters", "n_characters": 4}`         |
-| `optimizer`                  | The optimizer. ~~Optimizer~~                                                                                | [`Adam`](https://thinc.ai/docs/api-optimizers#adam) |
+| Name                         | Description                                                                                                                     |
+| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
+| `max_epochs`                 | Maximum number of epochs. Defaults to `1000`. ~~int~~                                                                           |
+| `min_length`                 | Minimum length of examples. Defaults to `5`. ~~int~~                                                                            |
+| `max_length`                 | Maximum length of examples. Defaults to `500`. ~~int~~                                                                          |
+| `dropout`                    | The dropout rate. Defaults to `0.2`. ~~float~~                                                                                  |
+| `n_save_every`               | Saving frequency. Defaults to `null`. ~~Optional[int]~~                                                                         |
+| `batch_size`                 | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). Defaults to `3000`. ~~Union[int, Sequence[int]]~~ |
+| `seed`                       | The random seed. Defaults to variable `${system.seed}`. ~~int~~                                                                 |
+| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. Defaults to variable `${system:use_pytorch_for_gpu_memory}`. ~~bool~~                              |
+| `tok2vec_model`              | The model section of the embedding component in the config. Defaults to `"components.tok2vec.model"`. ~~str~~                   |
+| `objective`                  | The pretraining objective. Defaults to `{"type": "characters", "n_characters": 4}`. ~~Dict[str, Any]~~                          |
+| `optimizer`                  | The optimizer. Defaults to [`Adam`](https://thinc.ai/docs/api-optimizers#adam). ~~Optimizer~~                                   |
 
 ## Training data {#training}
 
@@ -372,11 +373,11 @@ example = Example.from_dict(doc, gold_dict)
 
 ## Pretraining data {#pretraining}
 
-The [`spacy pretrain`](/api/cli#pretrain) command lets you pretrain the tok2vec
-layer of pipeline components from raw text. Raw text can be provided as a
-`.jsonl` (newline-delimited JSON) file containing one input text per line
-(roughly paragraph length is good). Optionally, custom tokenization can be
-provided.
+The [`spacy pretrain`](/api/cli#pretrain) command lets you pretrain the
+"token-to-vector" embedding layer of pipeline components from raw text. Raw text
+can be provided as a `.jsonl` (newline-delimited JSON) file containing one input
+text per line (roughly paragraph length is good). Optionally, custom
+tokenization can be provided.
 
 > #### Tip: Writing JSONL
 >
@@ -457,3 +458,75 @@ Here's an example of the 20 most frequent lexemes in the English training data:
 ```json
 https://github.com/explosion/spaCy/tree/master/examples/training/vocab-data.jsonl
 ```
+
+## Model meta {#meta}
+
+The model meta is available as the file `meta.json` and exported automatically
+when you save an `nlp` object to disk. Its contents are available as
+[`nlp.meta`](/api/language#meta).
+
+
+
+As of spaCy v3.0, the `meta.json` **isn't** used to construct the language class
+and pipeline anymore and only contains meta information for reference and for
+creating a Python package with [`spacy package`](/api/cli#package). How to set
+up the `nlp` object is now defined in the
+[`config.cfg`](/api/data-formats#config), which includes detailed information
+about the pipeline components and their model architectures, and all other
+settings and hyperparameters used to train the model. It's the **single source
+of truth** used for loading a model.
+
+
+
+> #### Example
+>
+> ```json
+> {
+>   "name": "example_model",
+>   "lang": "en",
+>   "version": "1.0.0",
+>   "spacy_version": ">=3.0.0,<3.1.0",
+>   "parent_package": "spacy",
+>   "description": "Example model for spaCy",
+>   "author": "You",
+>   "email": "you@example.com",
+>   "url": "https://example.com",
+>   "license": "CC BY-SA 3.0",
+>   "sources": [{ "name": "My Corpus", "license": "MIT" }],
+>   "vectors": { "width": 0, "vectors": 0, "keys": 0, "name": null },
+>   "pipeline": ["tok2vec", "ner", "textcat"],
+>   "labels": {
+>     "ner": ["PERSON", "ORG", "PRODUCT"],
+>     "textcat": ["POSITIVE", "NEGATIVE"]
+>   },
+>   "accuracy": {
+>     "ents_f": 82.7300930714,
+>     "ents_p": 82.135523614,
+>     "ents_r": 83.3333333333,
+>     "textcat_score": 88.364323811
+>   },
+>   "speed": { "cpu": 7667.8, "gpu": null, "nwords": 10329 },
+>   "spacy_git_version": "61dfdd9fb"
+> }
+> ```
+
+| Name                                           | Description                                                                                                                                                                                                                                                                                                          |
+| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `lang`                                         | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Defaults to `"en"`. ~~str~~                                                                                                                                                                                                        |
+| `name`                                         | Model name, e.g. `"core_web_sm"`. The final model package name will be `{lang}_{name}`. Defaults to `"model"`. ~~str~~                                                                                                                                                                                               |
+| `version`                                      | Model version. Will be used to version a Python package created with [`spacy package`](/api/cli#package). Defaults to `"0.0.0"`. ~~str~~                                                                                                                                                                             |
+| `spacy_version`                                | spaCy version range the model is compatible with. Defaults to spaCy version used to create the model, up to next minor version, which is the default compatibility for the available [pretrained models](/models). For instance, a model trained with v3.0.0 will have the version range `">=3.0.0,<3.1.0"`. ~~str~~ |
+| `parent_package`                               | Name of the spaCy package. Typically `"spacy"` or `"spacy_nightly"`. Defaults to `"spacy"`. ~~str~~                                                                                                                                                                                                                  |
+| `description`                                  | Model description. Also used for Python package. Defaults to `""`. ~~str~~                                                                                                                                                                                                                                           |
+| `author`                                       | Model author name. Also used for Python package. Defaults to `""`. ~~str~~                                                                                                                                                                                                                                           |
+| `email`                                        | Model author email. Also used for Python package. Defaults to `""`. ~~str~~                                                                                                                                                                                                                                          |
+| `url`                                          | Model author URL. Also used for Python package. Defaults to `""`. ~~str~~                                                                                                                                                                                                                                            |
+| `license`                                      | Model license. Also used for Python package. Defaults to `""`. ~~str~~                                                                                                                                                                                                                                               |
+| `sources`                                      | Data sources used to train the model. Typically a list of dicts with the keys `"name"`, `"url"`, `"author"` and `"license"`. [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `None`. ~~Optional[List[Dict[str, str]]]~~                                             |
+| `vectors`                                      | Information about the word vectors included with the model. Typically a dict with the keys `"width"`, `"vectors"` (number of vectors), `"keys"` and `"name"`. ~~Dict[str, Any]~~                                                                                                                                     |
+| `pipeline`                                     | Names of pipeline component names in the model, in order. Corresponds to [`nlp.pipe_names`](/api/language#pipe_names). Only exists for reference and is not used to create the components. This information is defined in the [`config.cfg`](/api/data-formats#config). Defaults to `[]`. ~~List[str]~~              |
+| `labels`                                       | Label schemes of the trained pipeline components, keyed by component name. Corresponds to [`nlp.pipe_labels`](/api/language#pipe_labels). [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `{}`. ~~Dict[str, Dict[str, List[str]]]~~                                 |
+| `accuracy`                                     | Training accuracy, added automatically by [`spacy train`](/api/cli#train). Dictionary of [score names](/usage/training#metrics) mapped to scores. Defaults to `{}`. ~~Dict[str, Union[float, Dict[str, float]]]~~                                                                                                    |
+| `speed`                                        | Model speed, added automatically by [`spacy train`](/api/cli#train). Typically a dictionary with the keys `"cpu"`, `"gpu"` and `"nwords"` (words per second). Defaults to `{}`. ~~Dict[str, Optional[Union[float, str]]]~~                                                                                           |
+| `spacy_git_version` 3 | Git commit of [`spacy`](https://github.com/explosion/spaCy) used to create model. ~~str~~                                                                                                                                                                                                                            |
+| other                                          | Any other custom meta information you want to add. The data is preserved in [`nlp.meta`](/api/language#meta). ~~Any~~                                                                                                                                                                                                |
diff --git a/website/docs/api/language.md b/website/docs/api/language.md
index 871adc0f2..34e3569a7 100644
--- a/website/docs/api/language.md
+++ b/website/docs/api/language.md
@@ -742,7 +742,7 @@ token.ent_iob, token.ent_type
 
 Custom meta data for the Language class. If a model is loaded, contains meta
 data of the model. The `Language.meta` is also what's serialized as the
-`meta.json` when you save an `nlp` object to disk.
+[`meta.json`](/api/data-formats#meta) when you save an `nlp` object to disk.
 
 > #### Example
 >
@@ -954,12 +954,12 @@ serialization by passing in the string names via the `exclude` argument.
 > nlp.from_disk("./model-data", exclude=["ner"])
 > ```
 
-| Name        | Description                                        |
-| ----------- | -------------------------------------------------- |
-| `vocab`     | The shared [`Vocab`](/api/vocab).                  |
-| `tokenizer` | Tokenization rules and exceptions.                 |
-| `meta`      | The meta data, available as `Language.meta`.       |
-| ...         | String names of pipeline components, e.g. `"ner"`. |
+| Name        | Description                                                        |
+| ----------- | ------------------------------------------------------------------ |
+| `vocab`     | The shared [`Vocab`](/api/vocab).                                  |
+| `tokenizer` | Tokenization rules and exceptions.                                 |
+| `meta`      | The meta data, available as [`Language.meta`](/api/language#meta). |
+| ...         | String names of pipeline components, e.g. `"ner"`.                 |
 
 ## FactoryMeta {#factorymeta new="3" tag="dataclass"}
 
diff --git a/website/docs/api/tok2vec.md b/website/docs/api/tok2vec.md
index 833a50b33..deb8369ab 100644
--- a/website/docs/api/tok2vec.md
+++ b/website/docs/api/tok2vec.md
@@ -15,7 +15,7 @@ multiple components, e.g. to have one embedding and CNN network shared between a
 [`EntityRecognizer`](/api/entityrecognizer).
 
 In order to use the `Tok2Vec` predictions, subsequent components should use the
-[Tok2VecListener](/api/architectures#Tok2VecListener) layer as the tok2vec
+[Tok2VecListener](/api/architectures#Tok2VecListener) layer as the `tok2vec`
 subnetwork of their model. This layer will read data from the `doc.tensor`
 attribute during prediction. During training, the `Tok2Vec` component will save
 its prediction and backprop callback for each batch, so that the subsequent
diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md
index c44e4e5b4..de0b3d36c 100644
--- a/website/docs/api/top-level.md
+++ b/website/docs/api/top-level.md
@@ -18,9 +18,10 @@ Load a model using the name of an installed
 `Path`-like object. spaCy will try resolving the load argument in this order. If
 a model is loaded from a model name, spaCy will assume it's a Python package and
 import it and call the model's own `load()` method. If a model is loaded from a
-path, spaCy will assume it's a data directory, read the language and pipeline
-settings off the meta.json and initialize the `Language` class. The data will be
-loaded in via [`Language.from_disk`](/api/language#from_disk).
+path, spaCy will assume it's a data directory, load its
+[`config.cfg`](/api/data-formats#config) and use the language and pipeline
+information to construct the `Language` class. The data will be loaded in via
+[`Language.from_disk`](/api/language#from_disk).
 
 > #### Example
 >
@@ -40,9 +41,10 @@ loaded in via [`Language.from_disk`](/api/language#from_disk).
 | `config` 3 | Optional config overrides, either as nested dict or dict keyed by section value in dot notation, e.g. `"components.name.value"`. ~~Union[Dict[str, Any], Config]~~ |
 | **RETURNS**                         | A `Language` object with the loaded model. ~~Language~~                                                                                                            |
 
-Essentially, `spacy.load()` is a convenience wrapper that reads the language ID
-and pipeline components from a model's `meta.json`, initializes the `Language`
-class, loads in the model data and returns it.
+Essentially, `spacy.load()` is a convenience wrapper that reads the model's
+[`config.cfg`](/api/data-formats#config), uses the language and pipeline
+information to construct a `Language` object, loads in the model data and
+returns it.
 
 ```python
 ### Abstract example
@@ -543,8 +545,8 @@ loaded lazily, to avoid expensive setup code associated with the language data.
 Load a model from a package or data path. If called with a package name, spaCy
 will assume the model is a Python package and import and call its `load()`
 method. If called with a path, spaCy will assume it's a data directory, read the
-language and pipeline settings from the meta.json and initialize a `Language`
-class. The model data will then be loaded in via
+language and pipeline settings from the [`config.cfg`](/api/data-formats#config)
+and create a `Language` object. The model data will then be loaded in via
 [`Language.from_disk`](/api/language#from_disk).
 
 > #### Example
@@ -607,7 +609,8 @@ components are created, as well as all training settings and hyperparameters.
 
 ### util.load_meta {#util.load_meta tag="function" new="3"}
 
-Get a model's `meta.json` from a file path and validate its contents.
+Get a model's [`meta.json`](/api/data-formats#meta) from a file path and
+validate its contents.
 
 > #### Example
 >
diff --git a/website/docs/images/tok2vec-listener.svg b/website/docs/images/tok2vec-listener.svg
new file mode 100644
index 000000000..bb67d2186
--- /dev/null
+++ b/website/docs/images/tok2vec-listener.svg
@@ -0,0 +1,41 @@
+
+  
+    
+      
+      
+    
+    
+      
+      
+    
+    
+      
+      
+    
+  
+  
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+      
+    
+    
+      
+      
+      
+    
+    
+      
+      
+    
+  
+
diff --git a/website/docs/images/tok2vec.svg b/website/docs/images/tok2vec.svg
new file mode 100644
index 000000000..5338b6280
--- /dev/null
+++ b/website/docs/images/tok2vec.svg
@@ -0,0 +1,17 @@
+
+  
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+      
+      
+    
+  
+
diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md
index 23037a3ab..df9e68282 100644
--- a/website/docs/usage/embeddings-transformers.md
+++ b/website/docs/usage/embeddings-transformers.md
@@ -9,11 +9,7 @@ menu:
 next: /usage/training
 ---
 
-
-
-## Shared embedding layers {#embedding-layers}
-
-
+
 
 
 
@@ -55,6 +51,22 @@ of performance.
 
 
 
+## Shared embedding layers {#embedding-layers}
+
+
+
+![Pipeline components using a shared embedding component vs. independent embedding layers](../images/tok2vec.svg)
+
+| Shared                                                                                      | Independent                                                             |
+| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
+| ✅ **smaller:** models only need to include a single copy of the embeddings                 | ❌ **larger:** models need to include the embeddings for each component |
+| ✅ **faster:**                                                                              | ❌ **slower:**                                                          |
+| ❌ **less composable:** all components require the same embedding component in the pipeline | ✅ **modular:** components can be moved and swapped freely              |
+
+![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg)
+
+
+
 ## Using transformer models {#transformers}
 
 Transformers are a family of neural network architectures that compute **dense,
@@ -295,18 +307,6 @@ is, a Thinc model that takes a list of [`Doc`](/api/doc) objects, and returns a
 [`FullTransformerBatch`](/api/transformer#fulltransformerbatch) object with the
 transformer data.
 
-> #### Model type annotations
->
-> In the documentation and code base, you may come across type annotations and
-> descriptions of [Thinc](https://thinc.ai) model types, like ~~Model[List[Doc],
-> List[Floats2d]]~~. This so-called generic type describes the layer and its
-> input and output type – in this case, it takes a list of `Doc` objects as the
-> input and list of 2-dimensional arrays of floats as the output. You can read
-> more about defining Thinc models [here](https://thinc.ai/docs/usage-models).
-> Also see the [type checking](https://thinc.ai/docs/usage-type-checking) for
-> how to enable linting in your editor to see live feedback if your inputs and
-> outputs don't match.
-
 The same idea applies to task models that power the **downstream components**.
 Most of spaCy's built-in model creation functions support a `tok2vec` argument,
 which should be a Thinc layer of type ~~Model[List[Doc], List[Floats2d]]~~. This
diff --git a/website/docs/usage/models.md b/website/docs/usage/models.md
index 1ea39fa83..be98cd36c 100644
--- a/website/docs/usage/models.md
+++ b/website/docs/usage/models.md
@@ -70,8 +70,7 @@ import Languages from 'widgets/languages.js'
 > nlp = MultiLanguage()
 >
 > # With lazy-loading
-> from spacy.util import get_lang_class
-> nlp = get_lang_class('xx')
+> nlp = spacy.blank("xx")
 > ```
 
 spaCy also supports models trained on more than one language. This is especially
@@ -80,10 +79,10 @@ language-neutral models is `xx`. The language class, a generic subclass
 containing only the base language data, can be found in
 [`lang/xx`](https://github.com/explosion/spaCy/tree/master/spacy/lang/xx).
 
-To load your model with the neutral, multi-language class, simply set
-`"language": "xx"` in your [model package](/usage/training#models-generating)'s
-`meta.json`. You can also import the class directly, or call
-[`util.get_lang_class()`](/api/top-level#util.get_lang_class) for lazy-loading.
+To train a model using the neutral multi-language class, you can set
+`lang = "xx"` in your [training config](/usage/training#config). You can also
+import the `MultiLanguage` class directly, or call
+[`spacy.blank("xx")`](/api/top-level#spacy.blank) for lazy-loading.
 
 ### Chinese language support {#chinese new=2.3}
 
@@ -308,12 +307,14 @@ model data.
 ```yaml
 ### Directory structure {highlight="7"}
 └── en_core_web_md-3.0.0.tar.gz       # downloaded archive
-    ├── meta.json                     # model meta data
     ├── setup.py                      # setup file for pip installation
+    ├── meta.json                     # copy of model meta
     └── en_core_web_md                # 📦 model package
         ├── __init__.py               # init for pip installation
-        ├── meta.json                 # model meta data
         └── en_core_web_md-3.0.0      # model data
+            ├── config.cfg            # model config
+            ├── meta.json             # model meta
+            └── ...                   # directories with component data
 ```
 
 You can place the **model package directory** anywhere on your local file
diff --git a/website/docs/usage/processing-pipelines.md b/website/docs/usage/processing-pipelines.md
index 2b040a832..73ad88bcc 100644
--- a/website/docs/usage/processing-pipelines.md
+++ b/website/docs/usage/processing-pipelines.md
@@ -232,7 +232,7 @@ available pipeline components and component functions.
 | `morphologizer` | [`Morphologizer`](/api/morphologizer)           | Assign morphological features and coarse-grained POS tags.                                |
 | `senter`        | [`SentenceRecognizer`](/api/sentencerecognizer) | Assign sentence boundaries.                                                               |
 | `sentencizer`   | [`Sentencizer`](/api/sentencizer)               | Add rule-based sentence segmentation without the dependency parse.                        |
-| `tok2vec`       | [`Tok2Vec`](/api/tok2vec)                       |                                                                                           |
+| `tok2vec`       | [`Tok2Vec`](/api/tok2vec)                       | Assign token-to-vector embeddings.                                                        |
 | `transformer`   | [`Transformer`](/api/transformer)               | Assign the tokens and outputs of a transformer model.                                     |
 
 ### Disabling and modifying pipeline components {#disabling}
diff --git a/website/docs/usage/rule-based-matching.md b/website/docs/usage/rule-based-matching.md
index 66a3daf6e..ce6625897 100644
--- a/website/docs/usage/rule-based-matching.md
+++ b/website/docs/usage/rule-based-matching.md
@@ -1096,11 +1096,12 @@ ruler.add_patterns([{"label": "ORG", "pattern": "Apple"}])
 nlp.to_disk("/path/to/model")
 ```
 
-The saved model now includes the `"entity_ruler"` in its `"pipeline"` setting in
-the `meta.json`, and the model directory contains a file `entityruler.jsonl`
-with the patterns. When you load the model back in, all pipeline components will
-be restored and deserialized – including the entity ruler. This lets you ship
-powerful model packages with binary weights _and_ rules included!
+The saved model now includes the `"entity_ruler"` in its
+[`config.cfg`](/api/data-formats#config) and the model directory contains a file
+`entityruler.jsonl` with the patterns. When you load the model back in, all
+pipeline components will be restored and deserialized – including the entity
+ruler. This lets you ship powerful model packages with binary weights _and_
+rules included!
 
 ### Using a large number of phrase patterns {#entityruler-large-phrase-patterns new="2.2.4"}
 
diff --git a/website/docs/usage/saving-loading.md b/website/docs/usage/saving-loading.md
index 904477733..f8bb1bfa9 100644
--- a/website/docs/usage/saving-loading.md
+++ b/website/docs/usage/saving-loading.md
@@ -569,9 +569,32 @@ back later. You can do this with the
 nlp.to_disk('/home/me/data/en_example_model')
 ```
 
-The directory will be created if it doesn't exist, and the whole pipeline will
-be written out. To make the model more convenient to deploy, we recommend
-wrapping it as a Python package.
+The directory will be created if it doesn't exist, and the whole pipeline data,
+model meta and model configuration will be written out. To make the model more
+convenient to deploy, we recommend wrapping it as a
+[Python package](/api/cli#package).
+
+
+
+When you save a model in spaCy v3.0+, two files will be exported: a
+[`config.cfg`](/api/data-formats#config) based on
+[`nlp.config`](/api/language#config) and a [`meta.json`](/api/data-formats#meta)
+based on [`nlp.meta`](/api/language#meta).
+
+- **config**: Configuration used to create the current `nlp` object, its
+  pipeline components and models, as well as training settings and
+  hyperparameters. Can include references to registered functions like
+  [pipeline components](/usage/processing-pipelines#custom-components) or
+  [model architectures](/api/architectures). Given a config, spaCy is able
+  reconstruct the whole tree of objects and the `nlp` object. An exported config
+  can also be used to [train a model](/usage/training#conig) with the same
+  settings.
+- **meta**: Meta information about the model and the Python package, such as the
+  author information, license, version, data sources and label scheme. This is
+  mostly used for documentation purposes and for packaging models. It has no
+  impact on the functionality of the `nlp` object.
+
+
 
 ### Generating a model package {#models-generating}
 
@@ -623,6 +646,9 @@ model package that can be installed using `pip install`.
     ├── en_example_model                   # model directory
     │    ├── __init__.py                   # init for pip installation
     │    └── en_example_model-1.0.0        # model data
+    │        ├── config.cfg                # model config
+    │        ├── meta.json                 # model meta
+    │        └── ...                       # directories with component data
     └── dist
         └── en_example_model-1.0.0.tar.gz  # installable package
 ```
@@ -644,13 +670,25 @@ you can also **ship the code with your model** and include it in the
 [pipeline components](/usage/processing-pipelines#custom-components) before the
 `nlp` object is created.
 
+
+
+While it's no problem to edit the package code or meta information, avoid making
+edits to the `config.cfg` **after** training, as this can easily lead to data
+incompatibility. For instance, changing an architecture or hyperparameter can
+mean that the trained weights are now incompatible. If you want to make
+adjustments, you can do so before training. Otherwise, you should always trust
+spaCy to export the current state of its `nlp` objects via
+[`nlp.config`](/api/language#config).
+
+
+
 ### Loading a custom model package {#loading}
 
 To load a model from a data directory, you can use
 [`spacy.load()`](/api/top-level#spacy.load) with the local path. This will look
-for a meta.json in the directory and use the `lang` and `pipeline` settings to
-initialize a `Language` class with a processing pipeline and load in the model
-data.
+for a `config.cfg` in the directory and use the `lang` and `pipeline` settings
+to initialize a `Language` class with a processing pipeline and load in the
+model data.
 
 ```python
 nlp = spacy.load("/path/to/model")
diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 3943cf061..31ba902b0 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -384,19 +384,40 @@ that reference this variable.
 
 ### Model architectures {#model-architectures}
 
-A **model architecture** is a function that wires up a Thinc `Model` instance,
-which you can then use in a component or as a layer of a larger network. You
-can use Thinc as a thin wrapper around frameworks such as PyTorch, Tensorflow
-or MXNet, or you can implement your logic in Thinc directly.
+> #### 💡 Model type annotations
+>
+> In the documentation and code base, you may come across type annotations and
+> descriptions of [Thinc](https://thinc.ai) model types, like ~~Model[List[Doc],
+> List[Floats2d]]~~. This so-called generic type describes the layer and its
+> input and output type – in this case, it takes a list of `Doc` objects as the
+> input and list of 2-dimensional arrays of floats as the output. You can read
+> more about defining Thinc models [here](https://thinc.ai/docs/usage-models).
+> Also see the [type checking](https://thinc.ai/docs/usage-type-checking) for
+> how to enable linting in your editor to see live feedback if your inputs and
+> outputs don't match.
+
+A **model architecture** is a function that wires up a Thinc
+[`Model`](https://thinc.ai/docs/api-model) instance, which you can then use in a
+component or as a layer of a larger network. You can use Thinc as a thin
+[wrapper around frameworks](https://thinc.ai/docs/usage-frameworks) such as
+PyTorch, TensorFlow or MXNet, or you can implement your logic in Thinc
+[directly](https://thinc.ai/docs/usage-models).
 
 spaCy's built-in components will never construct their `Model` instances
 themselves, so you won't have to subclass the component to change its model
-architecture. You can just update the config so that it refers
-to a different registered function. Once the component has been created, its
-model instance has already been assigned, so you cannot change its model
-architecture. The architecture is like a recipe for the network, and you can't
-change the recipe once the dish has already been prepared. You have to make
-a new one.
+architecture. You can just **update the config** so that it refers to a
+different registered function. Once the component has been created, its `Model`
+instance has already been assigned, so you cannot change its model architecture.
+The architecture is like a recipe for the network, and you can't change the
+recipe once the dish has already been prepared. You have to make a new one.
+spaCy includes a variety of built-in [architectures](/api/architectures) for
+different tasks. For example:
+
+
+
+| Architecture                                    | Description                                                                                                                                                            |
+| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [HashEmbedCNN](/api/architectures#HashEmbedCNN) | Build spaCy’s “standard” embedding layer, which uses hash embedding with subword features and a CNN with layer-normalized maxout. ~~Model[List[Doc], List[Floats2d]]~~ |
 
 ### Metrics, training output and weighted scores {#metrics}
 
@@ -442,34 +463,15 @@ components are weighted equally.
 
 
 
-
-
 | Name                       | Description                                                                                                             |
 | -------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
 | **Loss**                   | The training loss representing the amount of work left for the optimizer. Should decrease, but usually not to `0`.      |
-| **Precision** (P)          | Should increase.                                                                                                        |
-| **Recall** (R)             | Should increase.                                                                                                        |
-| **F-Score** (F)            | The weighted average of precision and recall. Should increase.                                                          |
+| **Precision** (P)          | Percentage of predicted annotations that were correct. Should increase.                                                 |
+| **Recall** (R)             | Percentage of reference annotations recovered. Should increase.                                                         |
+| **F-Score** (F)            | Harmonic mean of precision and recall. Should increase.                                                                 |
 | **UAS** / **LAS**          | Unlabeled and labeled attachment score for the dependency parser, i.e. the percentage of correct arcs. Should increase. |
 | **Words per second** (WPS) | Prediction speed in words per second. Should stay stable.                                                               |
 
-Precision and recall are two common measurements of a model's accuracy. You
-need precision and recall statistics whenever your model can return a variable
-number of predictions, as in this situation there are two different ways your
-model can be "accurate".
-
-Precision refers to the percentage of predicted annotations that were correct,
-while recall refers to the percentage of reference annotations recovered.
-A model that only returns one entity for a document will have precision 1.0 if
-that entity is correct, but might have low recall if it has missed lots of
-other correct entities. F-score is the harmonic mean of precision and recall.
-The harmonic mean is used instead of the arithmetic mean so that systems with
-very low precision or very low recall will score lower than systems that
-achieve a balance of the two.
-
-
-
-
 Note that if the development data has raw text, some of the gold-standard
 entities might not align to the predicted tokenization. These tokenization
 errors are **excluded from the NER evaluation**. If your tokenization makes it
diff --git a/website/docs/usage/transformers.md b/website/docs/usage/transformers.md
deleted file mode 100644
index 79ac8177f..000000000
--- a/website/docs/usage/transformers.md
+++ /dev/null
@@ -1,305 +0,0 @@
----
-title: Transformers
-teaser: Using transformer models like BERT in spaCy
-menu:
-  - ['Installation', 'install']
-  - ['Runtime Usage', 'runtime']
-  - ['Training Usage', 'training']
-next: /usage/training
----
-
-## Installation {#install hidden="true"}
-
-Transformers are a family of neural network architectures that compute **dense,
-context-sensitive representations** for the tokens in your documents. Downstream
-models in your pipeline can then use these representations as input features to
-**improve their predictions**. You can connect multiple components to a single
-transformer model, with any or all of those components giving feedback to the
-transformer to fine-tune it to your tasks. spaCy's transformer support
-interoperates with [PyTorch](https://pytorch.org) and the
-[HuggingFace `transformers`](https://huggingface.co/transformers/) library,
-giving you access to thousands of pretrained models for your pipelines. There
-are many [great guides](http://jalammar.github.io/illustrated-transformer/) to
-transformer models, but for practical purposes, you can simply think of them as
-a drop-in replacement that let you achieve **higher accuracy** in exchange for
-**higher training and runtime costs**.
-
-### System requirements
-
-We recommend an NVIDIA GPU with at least 10GB of memory in order to work with
-transformer models. The exact requirements will depend on the transformer you
-model you choose and whether you're training the pipeline or simply running it.
-Training a transformer-based model without a GPU will be too slow for most
-practical purposes. You'll also need to make sure your GPU drivers are
-up-to-date and v9+ of the CUDA runtime is installed.
-
-Once you have CUDA installed, you'll need to install two pip packages,
-[`cupy`](https://docs.cupy.dev/en/stable/install.html) and
-[`spacy-transformers`](https://github.com/explosion/spacy-transformers). `cupy`
-is just like `numpy`, but for GPU. The best way to install it is to choose a
-wheel that matches the version of CUDA you're using. You may also need to set
-the `CUDA_PATH` environment variable if your CUDA runtime is installed in a
-non-standard location. Putting it all together, if you had installed CUDA 10.2
-in `/opt/nvidia/cuda`, you would run:
-
-```bash
-### Installation with CUDA
-export CUDA_PATH="/opt/nvidia/cuda"
-pip install cupy-cuda102
-pip install spacy-transformers
-```
-
-Provisioning a new machine will require about 5GB of data to be downloaded in
-total: 3GB for the CUDA runtime, 800MB for PyTorch, 400MB for CuPy, 500MB for
-the transformer weights, and about 200MB for spaCy and its various requirements.
-
-## Runtime usage {#runtime}
-
-Transformer models can be used as **drop-in replacements** for other types of
-neural networks, so your spaCy pipeline can include them in a way that's
-completely invisible to the user. Users will download, load and use the model in
-the standard way, like any other spaCy pipeline. Instead of using the
-transformers as subnetworks directly, you can also use them via the
-[`Transformer`](/api/transformer) pipeline component.
-
-![The processing pipeline with the transformer component](../images/pipeline_transformer.svg)
-
-The `Transformer` component sets the
-[`Doc._.trf_data`](/api/transformer#custom_attributes) extension attribute,
-which lets you access the transformers outputs at runtime.
-
-```bash
-$ python -m spacy download en_core_trf_lg
-```
-
-```python
-### Example
-import spacy
-from thinc.api import use_pytorch_for_gpu_memory, require_gpu
-
-# Use the GPU, with memory allocations directed via PyTorch.
-# This prevents out-of-memory errors that would otherwise occur from competing
-# memory pools.
-use_pytorch_for_gpu_memory()
-require_gpu(0)
-
-nlp = spacy.load("en_core_trf_lg")
-for doc in nlp.pipe(["some text", "some other text"]):
-    tokvecs = doc._.trf_data.tensors[-1]
-```
-
-You can also customize how the [`Transformer`](/api/transformer) component sets
-annotations onto the [`Doc`](/api/doc), by customizing the `annotation_setter`.
-This callback will be called with the raw input and output data for the whole
-batch, along with the batch of `Doc` objects, allowing you to implement whatever
-you need. The annotation setter is called with a batch of [`Doc`](/api/doc)
-objects and a [`FullTransformerBatch`](/api/transformer#fulltransformerbatch)
-containing the transformers data for the batch.
-
-```python
-def custom_annotation_setter(docs, trf_data):
-    # TODO:
-    ...
-
-nlp = spacy.load("en_core_trf_lg")
-nlp.get_pipe("transformer").annotation_setter = custom_annotation_setter
-doc = nlp("This is a text")
-print()  # TODO:
-```
-
-## Training usage {#training}
-
-The recommended workflow for training is to use spaCy's
-[config system](/usage/training#config), usually via the
-[`spacy train`](/api/cli#train) command. The training config defines all
-component settings and hyperparameters in one place and lets you describe a tree
-of objects by referring to creation functions, including functions you register
-yourself. For details on how to get started with training your own model, check
-out the [training quickstart](/usage/training#quickstart).
-
-
-
-The easiest way to get started is to clone a transformers-based project
-template. Swap in your data, edit the settings and hyperparameters and train,
-evaluate, package and visualize your model.
-
-
-
-The `[components]` section in the [`config.cfg`](/api/data-formats#config)
-describes the pipeline components and the settings used to construct them,
-including their model implementation. Here's a config snippet for the
-[`Transformer`](/api/transformer) component, along with matching Python code. In
-this case, the `[components.transformer]` block describes the `transformer`
-component:
-
-> #### Python equivalent
->
-> ```python
-> from spacy_transformers import Transformer, TransformerModel
-> from spacy_transformers.annotation_setters import null_annotation_setter
-> from spacy_transformers.span_getters import get_doc_spans
->
-> trf = Transformer(
->     nlp.vocab,
->     TransformerModel(
->         "bert-base-cased",
->         get_spans=get_doc_spans,
->         tokenizer_config={"use_fast": True},
->     ),
->     annotation_setter=null_annotation_setter,
->     max_batch_items=4096,
-> )
-> ```
-
-```ini
-### config.cfg (excerpt)
-[components.transformer]
-factory = "transformer"
-max_batch_items = 4096
-
-[components.transformer.model]
-@architectures = "spacy-transformers.TransformerModel.v1"
-name = "bert-base-cased"
-tokenizer_config = {"use_fast": true}
-
-[components.transformer.model.get_spans]
-@span_getters = "doc_spans.v1"
-
-[components.transformer.annotation_setter]
-@annotation_setters = "spacy-transformer.null_annotation_setter.v1"
-
-```
-
-The `[components.transformer.model]` block describes the `model` argument passed
-to the transformer component. It's a Thinc
-[`Model`](https://thinc.ai/docs/api-model) object that will be passed into the
-component. Here, it references the function
-[spacy-transformers.TransformerModel.v1](/api/architectures#TransformerModel)
-registered in the [`architectures` registry](/api/top-level#registry). If a key
-in a block starts with `@`, it's **resolved to a function** and all other
-settings are passed to the function as arguments. In this case, `name`,
-`tokenizer_config` and `get_spans`.
-
-`get_spans` is a function that takes a batch of `Doc` object and returns lists
-of potentially overlapping `Span` objects to process by the transformer. Several
-[built-in functions](/api/transformer#span-getters) are available – for example,
-to process the whole document or individual sentences. When the config is
-resolved, the function is created and passed into the model as an argument.
-
-
-
-Remember that the `config.cfg` used for training should contain **no missing
-values** and requires all settings to be defined. You don't want any hidden
-defaults creeping in and changing your results! spaCy will tell you if settings
-are missing, and you can run
-[`spacy init fill-config`](/api/cli#init-fill-config) to automatically fill in
-all defaults.
-
-
-
-### Customizing the settings {#training-custom-settings}
-
-To change any of the settings, you can edit the `config.cfg` and re-run the
-training. To change any of the functions, like the span getter, you can replace
-the name of the referenced function – e.g. `@span_getters = "sent_spans.v1"` to
-process sentences. You can also register your own functions using the
-`span_getters` registry:
-
-> #### config.cfg
->
-> ```ini
-> [components.transformer.model.get_spans]
-> @span_getters = "custom_sent_spans"
-> ```
-
-```python
-### code.py
-import spacy_transformers
-
-@spacy_transformers.registry.span_getters("custom_sent_spans")
-def configure_custom_sent_spans():
-    # TODO: write custom example
-    def get_sent_spans(docs):
-        return [list(doc.sents) for doc in docs]
-
-    return get_sent_spans
-```
-
-To resolve the config during training, spaCy needs to know about your custom
-function. You can make it available via the `--code` argument that can point to
-a Python file. For more details on training with custom code, see the
-[training documentation](/usage/training#custom-code).
-
-```bash
-$ python -m spacy train ./config.cfg --code ./code.py
-```
-
-### Customizing the model implementations {#training-custom-model}
-
-The [`Transformer`](/api/transformer) component expects a Thinc
-[`Model`](https://thinc.ai/docs/api-model) object to be passed in as its `model`
-argument. You're not limited to the implementation provided by
-`spacy-transformers` – the only requirement is that your registered function
-must return an object of type ~~Model[List[Doc], FullTransformerBatch]~~: that
-is, a Thinc model that takes a list of [`Doc`](/api/doc) objects, and returns a
-[`FullTransformerBatch`](/api/transformer#fulltransformerbatch) object with the
-transformer data.
-
-> #### Model type annotations
->
-> In the documentation and code base, you may come across type annotations and
-> descriptions of [Thinc](https://thinc.ai) model types, like ~~Model[List[Doc],
-> List[Floats2d]]~~. This so-called generic type describes the layer and its
-> input and output type – in this case, it takes a list of `Doc` objects as the
-> input and list of 2-dimensional arrays of floats as the output. You can read
-> more about defining Thinc models [here](https://thinc.ai/docs/usage-models).
-> Also see the [type checking](https://thinc.ai/docs/usage-type-checking) for
-> how to enable linting in your editor to see live feedback if your inputs and
-> outputs don't match.
-
-The same idea applies to task models that power the **downstream components**.
-Most of spaCy's built-in model creation functions support a `tok2vec` argument,
-which should be a Thinc layer of type `Model[List[Doc], List[Floats2d]]`. This
-is where we'll plug in our transformer model, using the
-[Tok2VecListener](/api/architectures#Tok2VecListener) layer, which sneakily
-delegates to the `Transformer` pipeline component.
-
-```ini
-### config.cfg (excerpt) {highlight="12"}
-[components.ner]
-factory = "ner"
-
-[nlp.pipeline.ner.model]
-@architectures = "spacy.TransitionBasedParser.v1"
-nr_feature_tokens = 3
-hidden_width = 128
-maxout_pieces = 3
-use_upper = false
-
-[nlp.pipeline.ner.model.tok2vec]
-@architectures = "spacy-transformers.Tok2VecListener.v1"
-grad_factor = 1.0
-
-[nlp.pipeline.ner.model.tok2vec.pooling]
-@layers = "reduce_mean.v1"
-```
-
-The [Tok2VecListener](/api/architectures#Tok2VecListener) layer expects a
-[pooling layer](https://thinc.ai/docs/api-layers#reduction-ops) as the argument
-`pooling`, which needs to be of type `Model[Ragged, Floats2d]`. This layer
-determines how the vector for each spaCy token will be computed from the zero or
-more source rows the token is aligned against. Here we use the
-[`reduce_mean`](https://thinc.ai/docs/api-layers#reduce_mean) layer, which
-averages the wordpiece rows. We could instead use
-[`reduce_max`](https://thinc.ai/docs/api-layers#reduce_max), or a custom
-function you write yourself.
-
-You can have multiple components all listening to the same transformer model,
-and all passing gradients back to it. By default, all of the gradients will be
-**equally weighted**. You can control this with the `grad_factor` setting, which
-lets you reweight the gradients from the different listeners. For instance,
-setting `grad_factor = 0` would disable gradients from one of the listeners,
-while `grad_factor = 2.0` would multiply them by 2. This is similar to having a
-custom learning rate for each component. Instead of a constant, you can also
-provide a schedule, allowing you to freeze the shared parameters at the start of
-training.
diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md
index a72baa7fa..ee90b6cc5 100644
--- a/website/docs/usage/v3.md
+++ b/website/docs/usage/v3.md
@@ -152,6 +152,7 @@ The following methods, attributes and commands are new in spaCy v3.0.
 | [`Language.config`](/api/language#config)                                                                                     | The [config](/usage/training#config) used to create the current `nlp` object. An instance of [`Config`](https://thinc.ai/docs/api-config#config) and can be saved to disk and used for training. |
 | [`Pipe.score`](/api/pipe#score)                                                                                               | Method on trainable pipeline components that returns a dictionary of evaluation scores.                                                                                                          |
 | [`registry`](/api/top-level#registry)                                                                                         | Function registry to map functions to string names that can be referenced in [configs](/usage/training#config).                                                                                  |
+| [`util.load_meta`](/api/top-level#util.load_meta) [`util.load_config`](/api/top-level#util.load_config)                       | Updated helpers for loading a model's [`meta.json`](/api/data-formats#meta) and [`config.cfg`](/api/data-formats#config).                                                                        |
 | [`init config`](/api/cli#init-config) [`init fill-config`](/api/cli#init-fill-config) [`debug config`](/api/cli#debug-config) | CLI commands for initializing, auto-filling and debugging [training configs](/usage/training).                                                                                                   |
 | [`project`](/api/cli#project)                                                                                                 | Suite of CLI commands for cloning, running and managing [spaCy projects](/usage/projects).                                                                                                       |
 
@@ -175,6 +176,11 @@ Note that spaCy v3.0 now requires **Python 3.6+**.
   There can be many [different models](/models) and not just one "English
   model", so you should always use the full model name like
   [`en_core_web_sm`](/models/en) explicitly.
+- A model's [`meta.json`](/api/data-formats#meta) is now only used to provide
+  meta information like the model name, author, license and labels. It's **not**
+  used to construct the processing pipeline anymore. This is all defined in the
+  [`config.cfg`](/api/data-formats#config), which also includes all settings
+  used to train the model.
 - The [`train`](/api/cli#train) and [`pretrain`](/api/cli#pretrain) commands now
   only take a `config.cfg` file containing the full
   [training config](/usage/training#config).
diff --git a/website/meta/type-annotations.json b/website/meta/type-annotations.json
index 9bb1abbf4..3cfcf5f75 100644
--- a/website/meta/type-annotations.json
+++ b/website/meta/type-annotations.json
@@ -32,6 +32,7 @@
     "Floats2d": "https://thinc.ai/docs/api-types#types",
     "Floats3d": "https://thinc.ai/docs/api-types#types",
     "FloatsXd": "https://thinc.ai/docs/api-types#types",
+    "Ops": "https://thinc.ai/docs/api-backends#ops",
     "cymem.Pool": "https://github.com/explosion/cymem",
     "preshed.BloomFilter": "https://github.com/explosion/preshed",
     "transformers.BatchEncoding": "https://huggingface.co/transformers/main_classes/tokenizer.html#transformers.BatchEncoding",
diff --git a/website/src/components/icon.js b/website/src/components/icon.js
index 322337955..8dfba7426 100644
--- a/website/src/components/icon.js
+++ b/website/src/components/icon.js
@@ -53,7 +53,15 @@ const icons = {
     package: PackageIcon,
 }
 
-export default function Icon({ name, width = 20, height, inline = false, variant, className }) {
+export default function Icon({
+    name,
+    width = 20,
+    height,
+    inline = false,
+    variant,
+    className,
+    ...props
+}) {
     const IconComponent = icons[name]
     const iconClassNames = classNames(classes.root, className, {
         [classes.inline]: inline,
@@ -67,6 +75,7 @@ export default function Icon({ name, width = 20, height, inline = false, variant
             aria-hidden="true"
             width={width}
             height={height || width}
+            {...props}
         />
     )
 }
diff --git a/website/src/components/table.js b/website/src/components/table.js
index 3d442cde7..3f41a587b 100644
--- a/website/src/components/table.js
+++ b/website/src/components/table.js
@@ -9,19 +9,25 @@ function isNum(children) {
     return isString(children) && /^\d+[.,]?[\dx]+?(|x|ms|mb|gb|k|m)?$/i.test(children)
 }
 
-function getCellContent(children) {
+function getCellContent(cellChildren) {
     const icons = {
-        '✅': { name: 'yes', variant: 'success' },
-        '❌': { name: 'no', variant: 'error' },
+        '✅': { name: 'yes', variant: 'success', 'aria-label': 'positive' },
+        '❌': { name: 'no', variant: 'error', 'aria-label': 'negative' },
     }
-
-    if (isString(children) && icons[children.trim()]) {
-        const iconProps = icons[children.trim()]
-        return 
-    }
-    // Work around prettier auto-escape
-    if (isString(children) && children.startsWith('\\')) {
-        return children.slice(1)
+    let children = isString(cellChildren) ? [cellChildren] : cellChildren
+    if (Array.isArray(children)) {
+        return children.map((child, i) => {
+            if (isString(child)) {
+                const icon = icons[child.trim()]
+                if (icon) {
+                    const props = { ...icon, inline: i < children.length, 'aria-hidden': undefined }
+                    return 
+                }
+                // Work around prettier auto-escape
+                if (child.startsWith('\\')) return child.slice(1)
+            }
+            return child
+        })
     }
     return children
 }
diff --git a/website/src/widgets/quickstart-training.js b/website/src/widgets/quickstart-training.js
index 4e379e5ec..1a77cc338 100644
--- a/website/src/widgets/quickstart-training.js
+++ b/website/src/widgets/quickstart-training.js
@@ -38,7 +38,8 @@ const DATA = [
     {
         id: 'optimize',
         title: 'Optimize for',
-        help: '...',
+        help:
+            'Optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger & slower model). Will impact the choice of architecture, pretrained weights and hyperparameters.',
         options: [
             { id: 'efficiency', title: 'efficiency', checked: DEFAULT_OPT === 'efficiency' },
             { id: 'accuracy', title: 'accuracy', checked: DEFAULT_OPT === 'accuracy' },
@@ -84,10 +85,12 @@ export default function QuickstartTraining({ id, title, download = 'config.cfg'
             query={query}
             render={({ site }) => {
                 const langs = site.siteMetadata.languages
-                DATA[0].dropdown = langs.map(({ name, code }) => ({
-                    id: code,
-                    title: name,
-                }))
+                DATA[0].dropdown = langs
+                    .map(({ name, code }) => ({
+                        id: code,
+                        title: name,
+                    }))
+                    .sort((a, b) => a.id.localeCompare(b.id))
                 return (
                     
Date: Tue, 18 Aug 2020 16:06:37 +0200
Subject: [PATCH 20/92] Train CLI script fixes (#5931)

* fix dash replacement in overrides arguments

* perform interpolation on training config

* make sure only .spacy files are read
---
 spacy/cli/_util.py   |  3 ++-
 spacy/cli/train.py   |  2 +-
 spacy/errors.py      |  6 ++++--
 spacy/gold/corpus.py | 13 ++++++++++---
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/spacy/cli/_util.py b/spacy/cli/_util.py
index 5613fa317..9d3ae0913 100644
--- a/spacy/cli/_util.py
+++ b/spacy/cli/_util.py
@@ -68,11 +68,12 @@ def parse_config_overrides(args: List[str]) -> Dict[str, Any]:
         opt = args.pop(0)
         err = f"Invalid CLI argument '{opt}'"
         if opt.startswith("--"):  # new argument
-            opt = opt.replace("--", "").replace("-", "_")
+            opt = opt.replace("--", "")
             if "." not in opt:
                 msg.fail(f"{err}: can't override top-level section", exits=1)
             if "=" in opt:  # we have --opt=value
                 opt, value = opt.split("=", 1)
+                opt = opt.replace("-", "_")
             else:
                 if not args or args[0].startswith("--"):  # flag with no value
                     value = "true"
diff --git a/spacy/cli/train.py b/spacy/cli/train.py
index 375e64ffd..202a8555c 100644
--- a/spacy/cli/train.py
+++ b/spacy/cli/train.py
@@ -75,7 +75,7 @@ def train(
         msg.info("Using CPU")
     msg.info(f"Loading config and nlp from: {config_path}")
     with show_validation_error(config_path):
-        config = util.load_config(config_path, overrides=config_overrides)
+        config = util.load_config(config_path, overrides=config_overrides, interpolate=True)
     if config.get("training", {}).get("seed") is not None:
         fix_random_seed(config["training"]["seed"])
     # Use original config here before it's resolved to functions
diff --git a/spacy/errors.py b/spacy/errors.py
index 26c0dba29..1ad5197f7 100644
--- a/spacy/errors.py
+++ b/spacy/errors.py
@@ -78,10 +78,11 @@ class Warnings:
             "are currently: {langs}")
 
     # TODO: fix numbering after merging develop into master
+    W090 = ("Could not locate any binary .spacy files in path '{path}'.")
     W091 = ("Could not clean/remove the temp directory at {dir}: {msg}.")
     W092 = ("Ignoring annotations for sentence starts, as dependency heads are set.")
     W093 = ("Could not find any data to train the {name} on. Is your "
-            "input data correctly formatted ?")
+            "input data correctly formatted?")
     W094 = ("Model '{model}' ({model_version}) specifies an under-constrained "
             "spaCy version requirement: {version}. This can lead to compatibility "
             "problems with older versions, or as new spaCy versions are "
@@ -600,7 +601,8 @@ class Errors:
             "\"en_core_web_sm\" will copy the component from that model.\n\n{config}")
     E985 = ("Can't load model from config file: no 'nlp' section found.\n\n{config}")
     E986 = ("Could not create any training batches: check your input. "
-            "Perhaps discard_oversize should be set to False ?")
+            "Are the train and dev paths defined? "
+            "Is 'discard_oversize' set appropriately? ")
     E987 = ("The text of an example training instance is either a Doc or "
             "a string, but found {type} instead.")
     E988 = ("Could not parse any training examples. Ensure the data is "
diff --git a/spacy/gold/corpus.py b/spacy/gold/corpus.py
index 774c3b840..1046da1e6 100644
--- a/spacy/gold/corpus.py
+++ b/spacy/gold/corpus.py
@@ -1,8 +1,10 @@
+import warnings
 from typing import Union, List, Iterable, Iterator, TYPE_CHECKING, Callable
 from pathlib import Path
 
 from .. import util
 from .example import Example
+from ..errors import Warnings
 from ..tokens import DocBin, Doc
 from ..vocab import Vocab
 
@@ -10,6 +12,8 @@ if TYPE_CHECKING:
     # This lets us add type hints for mypy etc. without causing circular imports
     from ..language import Language  # noqa: F401
 
+FILE_TYPE = ".spacy"
+
 
 @util.registry.readers("spacy.Corpus.v1")
 def create_docbin_reader(
@@ -53,8 +57,9 @@ class Corpus:
     @staticmethod
     def walk_corpus(path: Union[str, Path]) -> List[Path]:
         path = util.ensure_path(path)
-        if not path.is_dir():
+        if not path.is_dir() and path.parts[-1].endswith(FILE_TYPE):
             return [path]
+        orig_path = path
         paths = [path]
         locs = []
         seen = set()
@@ -66,8 +71,10 @@ class Corpus:
                 continue
             elif path.is_dir():
                 paths.extend(path.iterdir())
-            elif path.parts[-1].endswith(".spacy"):
+            elif path.parts[-1].endswith(FILE_TYPE):
                 locs.append(path)
+        if len(locs) == 0:
+            warnings.warn(Warnings.W090.format(path=orig_path))
         return locs
 
     def __call__(self, nlp: "Language") -> Iterator[Example]:
@@ -135,7 +142,7 @@ class Corpus:
         i = 0
         for loc in locs:
             loc = util.ensure_path(loc)
-            if loc.parts[-1].endswith(".spacy"):
+            if loc.parts[-1].endswith(FILE_TYPE):
                 doc_bin = DocBin().from_disk(loc)
                 docs = doc_bin.get_docs(vocab)
                 for doc in docs:

From 358cbb21e391afff49739cf47c50554bfd4641e1 Mon Sep 17 00:00:00 2001
From: Sofie Van Landeghem 
Date: Tue, 18 Aug 2020 16:10:36 +0200
Subject: [PATCH 21/92] Define candidate generator in EL config (#5876)

* candidate generator as separate part of EL config

* update comment

* ent instead of str as input for candidate generation

* Span instead of str: correct type indication

* fix types

* unit test to create new candidate generator

* fix replace_pipe argument passing

* move error message, general cleanup

* add vocab back to KB constructor

* provide KB as callable from Vocab arg

* rename to kb_loader, fix KB serialization as part of the EL pipe

* fix typo

* reformatting

* cleanup

* fix comment

* fix wrongly duplicated code from merge conflict

* rename dump to to_disk

* from_disk instead of load_bulk

* update test after recent removal of set_morphology in tagger

* remove old doc
---
 bin/ud/ud_train.py                            |   3 +-
 examples/training/create_kb.py                |  10 +-
 examples/training/train_entity_linker.py      |   2 +-
 spacy/errors.py                               |   8 +-
 spacy/kb.pxd                                  |   2 +-
 spacy/kb.pyx                                  |  54 +++---
 spacy/language.py                             |   4 +-
 spacy/ml/models/entity_linker.py              |  28 +--
 spacy/pipeline/entity_linker.py               |  53 ++----
 spacy/pipeline/nn_parser.pyx                  |   0
 spacy/pipeline/tagger.pyx                     |   1 -
 spacy/tests/pipeline/test_entity_linker.py    | 173 ++++++++++++------
 spacy/tests/pipeline/test_pipe_methods.py     |   8 +
 spacy/tests/regression/test_issue4501-5000.py |  10 +-
 spacy/tests/regression/test_issue5230.py      |  24 +--
 spacy/tests/serialize/test_serialize_kb.py    |  64 ++++++-
 website/docs/api/kb.md                        |   8 +-
 17 files changed, 272 insertions(+), 180 deletions(-)
 delete mode 100644 spacy/pipeline/nn_parser.pyx

diff --git a/bin/ud/ud_train.py b/bin/ud/ud_train.py
index 11ad564ec..362057b37 100644
--- a/bin/ud/ud_train.py
+++ b/bin/ud/ud_train.py
@@ -15,7 +15,8 @@ import spacy.util
 from bin.ud import conll17_ud_eval
 from spacy.tokens import Token, Doc
 from spacy.gold import Example
-from spacy.util import compounding, minibatch, minibatch_by_words
+from spacy.util import compounding, minibatch
+from spacy.gold.batchers import minibatch_by_words
 from spacy.pipeline._parser_internals.nonproj import projectivize
 from spacy.matcher import Matcher
 from spacy import displacy
diff --git a/examples/training/create_kb.py b/examples/training/create_kb.py
index 0c6e29226..a455c8d7e 100644
--- a/examples/training/create_kb.py
+++ b/examples/training/create_kb.py
@@ -48,8 +48,7 @@ def main(model, output_dir=None):
     # You can change the dimension of vectors in your KB by using an encoder that changes the dimensionality.
     # For simplicity, we'll just use the original vector dimension here instead.
     vectors_dim = nlp.vocab.vectors.shape[1]
-    kb = KnowledgeBase(entity_vector_length=vectors_dim)
-    kb.initialize(nlp.vocab)
+    kb = KnowledgeBase(nlp.vocab, entity_vector_length=vectors_dim)
 
     # set up the data
     entity_ids = []
@@ -81,7 +80,7 @@ def main(model, output_dir=None):
         if not output_dir.exists():
             output_dir.mkdir()
         kb_path = str(output_dir / "kb")
-        kb.dump(kb_path)
+        kb.to_disk(kb_path)
         print()
         print("Saved KB to", kb_path)
 
@@ -96,9 +95,8 @@ def main(model, output_dir=None):
         print("Loading vocab from", vocab_path)
         print("Loading KB from", kb_path)
         vocab2 = Vocab().from_disk(vocab_path)
-        kb2 = KnowledgeBase(entity_vector_length=1)
-        kb.initialize(vocab2)
-        kb2.load_bulk(kb_path)
+        kb2 = KnowledgeBase(vocab2, entity_vector_length=1)
+        kb2.from_disk(kb_path)
         print()
         _print_kb(kb2)
 
diff --git a/examples/training/train_entity_linker.py b/examples/training/train_entity_linker.py
index 8a69ae39c..d2bd61e5b 100644
--- a/examples/training/train_entity_linker.py
+++ b/examples/training/train_entity_linker.py
@@ -83,7 +83,7 @@ def main(kb_path, vocab_path, output_dir=None, n_iter=50):
     if "entity_linker" not in nlp.pipe_names:
         print("Loading Knowledge Base from '%s'" % kb_path)
         cfg = {
-            "kb": {
+            "kb_loader": {
                 "@assets": "spacy.KBFromFile.v1",
                 "vocab_path": vocab_path,
                 "kb_path": kb_path,
diff --git a/spacy/errors.py b/spacy/errors.py
index 1ad5197f7..d1e9489d1 100644
--- a/spacy/errors.py
+++ b/spacy/errors.py
@@ -477,6 +477,10 @@ class Errors:
     E199 = ("Unable to merge 0-length span at doc[{start}:{end}].")
 
     # TODO: fix numbering after merging develop into master
+    E928 = ("A 'KnowledgeBase' should be written to / read from a file, but the "
+            "provided argument {loc} is an existing directory.")
+    E929 = ("A 'KnowledgeBase' could not be read from {loc} - the path does "
+            "not seem to exist.")
     E930 = ("Received invalid get_examples callback in {name}.begin_training. "
             "Expected function that returns an iterable of Example objects but "
             "got: {obj}")
@@ -504,8 +508,6 @@ class Errors:
             "not found in pipeline. Available components: {opts}")
     E945 = ("Can't copy pipeline component '{name}' from source. Expected loaded "
             "nlp object, but got: {source}")
-    E946 = ("The Vocab for the knowledge base is not initialized. Did you forget to "
-            "call kb.initialize()?")
     E947 = ("Matcher.add received invalid 'greedy' argument: expected "
             "a string value from {expected} but got: '{arg}'")
     E948 = ("Matcher.add received invalid 'patterns' argument: expected "
@@ -612,8 +614,6 @@ class Errors:
             "of the training data in spaCy 3.0 onwards. The 'update' "
             "function should now be called with a batch of 'Example' "
             "objects, instead of (text, annotation) tuples. ")
-    E990 = ("An entity linking component needs to be initialized with a "
-            "KnowledgeBase object, but found {type} instead.")
     E991 = ("The function 'select_pipes' should be called with either a "
             "'disable' argument to list the names of the pipe components "
             "that should be disabled, or with an 'enable' argument that "
diff --git a/spacy/kb.pxd b/spacy/kb.pxd
index 53038b5db..695693666 100644
--- a/spacy/kb.pxd
+++ b/spacy/kb.pxd
@@ -140,7 +140,7 @@ cdef class KnowledgeBase:
         self._entries.push_back(entry)
         self._aliases_table.push_back(alias)
 
-    cpdef load_bulk(self, loc)
+    cpdef from_disk(self, loc)
     cpdef set_entities(self, entity_list, freq_list, vector_list)
 
 
diff --git a/spacy/kb.pyx b/spacy/kb.pyx
index 9035f7e6a..3b8017a0c 100644
--- a/spacy/kb.pyx
+++ b/spacy/kb.pyx
@@ -1,4 +1,5 @@
 # cython: infer_types=True, profile=True
+from typing import Iterator
 from cymem.cymem cimport Pool
 from preshed.maps cimport PreshMap
 from cpython.exc cimport PyErr_SetFromErrno
@@ -64,6 +65,16 @@ cdef class Candidate:
         return self.prior_prob
 
 
+def get_candidates(KnowledgeBase kb, span) -> Iterator[Candidate]:
+    """
+    Return candidate entities for a given span by using the text of the span as the alias
+    and fetching appropriate entries from the index.
+    This particular function is optimized to work with the built-in KB functionality,
+    but any other custom candidate generation method can be used in combination with the KB as well.
+    """
+    return kb.get_alias_candidates(span.text)
+
+
 cdef class KnowledgeBase:
     """A `KnowledgeBase` instance stores unique identifiers for entities and their textual aliases,
     to support entity linking of named entities to real-world concepts.
@@ -71,25 +82,16 @@ cdef class KnowledgeBase:
     DOCS: https://spacy.io/api/kb
     """
 
-    def __init__(self, entity_vector_length):
-        """Create a KnowledgeBase. Make sure to call kb.initialize() before using it."""
+    def __init__(self, Vocab vocab, entity_vector_length):
+        """Create a KnowledgeBase."""
         self.mem = Pool()
         self.entity_vector_length = entity_vector_length
-
         self._entry_index = PreshMap()
         self._alias_index = PreshMap()
-        self.vocab = None
-
-
-    def initialize(self, Vocab vocab):
         self.vocab = vocab
         self.vocab.strings.add("")
         self._create_empty_vectors(dummy_hash=self.vocab.strings[""])
 
-    def require_vocab(self):
-        if self.vocab is None:
-            raise ValueError(Errors.E946)
-
     @property
     def entity_vector_length(self):
         """RETURNS (uint64): length of the entity vectors"""
@@ -102,14 +104,12 @@ cdef class KnowledgeBase:
         return len(self._entry_index)
 
     def get_entity_strings(self):
-        self.require_vocab()
         return [self.vocab.strings[x] for x in self._entry_index]
 
     def get_size_aliases(self):
         return len(self._alias_index)
 
     def get_alias_strings(self):
-        self.require_vocab()
         return [self.vocab.strings[x] for x in self._alias_index]
 
     def add_entity(self, unicode entity, float freq, vector[float] entity_vector):
@@ -117,7 +117,6 @@ cdef class KnowledgeBase:
         Add an entity to the KB, optionally specifying its log probability based on corpus frequency
         Return the hash of the entity ID/name at the end.
         """
-        self.require_vocab()
         cdef hash_t entity_hash = self.vocab.strings.add(entity)
 
         # Return if this entity was added before
@@ -140,7 +139,6 @@ cdef class KnowledgeBase:
         return entity_hash
 
     cpdef set_entities(self, entity_list, freq_list, vector_list):
-        self.require_vocab()
         if len(entity_list) != len(freq_list) or len(entity_list) != len(vector_list):
             raise ValueError(Errors.E140)
 
@@ -176,12 +174,10 @@ cdef class KnowledgeBase:
             i += 1
 
     def contains_entity(self, unicode entity):
-        self.require_vocab()
         cdef hash_t entity_hash = self.vocab.strings.add(entity)
         return entity_hash in self._entry_index
 
     def contains_alias(self, unicode alias):
-        self.require_vocab()
         cdef hash_t alias_hash = self.vocab.strings.add(alias)
         return alias_hash in self._alias_index
 
@@ -190,7 +186,6 @@ cdef class KnowledgeBase:
         For a given alias, add its potential entities and prior probabilies to the KB.
         Return the alias_hash at the end
         """
-        self.require_vocab()
         # Throw an error if the length of entities and probabilities are not the same
         if not len(entities) == len(probabilities):
             raise ValueError(Errors.E132.format(alias=alias,
@@ -234,7 +229,6 @@ cdef class KnowledgeBase:
         Throw an error if this entity+prior prob would exceed the sum of 1.
         For efficiency, it's best to use the method `add_alias` as much as possible instead of this one.
         """
-        self.require_vocab()
         # Check if the alias exists in the KB
         cdef hash_t alias_hash = self.vocab.strings[alias]
         if not alias_hash in self._alias_index:
@@ -274,14 +268,12 @@ cdef class KnowledgeBase:
             alias_entry.probs = probs
             self._aliases_table[alias_index] = alias_entry
 
-
-    def get_candidates(self, unicode alias):
+    def get_alias_candidates(self, unicode alias) -> Iterator[Candidate]:
         """
         Return candidate entities for an alias. Each candidate defines the entity, the original alias,
         and the prior probability of that alias resolving to that entity.
         If the alias is not known in the KB, and empty list is returned.
         """
-        self.require_vocab()
         cdef hash_t alias_hash = self.vocab.strings[alias]
         if not alias_hash in self._alias_index:
             return []
@@ -298,7 +290,6 @@ cdef class KnowledgeBase:
                 if entry_index != 0]
 
     def get_vector(self, unicode entity):
-        self.require_vocab()
         cdef hash_t entity_hash = self.vocab.strings[entity]
 
         # Return an empty list if this entity is unknown in this KB
@@ -311,7 +302,6 @@ cdef class KnowledgeBase:
     def get_prior_prob(self, unicode entity, unicode alias):
         """ Return the prior probability of a given alias being linked to a given entity,
         or return 0.0 when this combination is not known in the knowledge base"""
-        self.require_vocab()
         cdef hash_t alias_hash = self.vocab.strings[alias]
         cdef hash_t entity_hash = self.vocab.strings[entity]
 
@@ -329,8 +319,7 @@ cdef class KnowledgeBase:
         return 0.0
 
 
-    def dump(self, loc):
-        self.require_vocab()
+    def to_disk(self, loc):
         cdef Writer writer = Writer(loc)
         writer.write_header(self.get_size_entities(), self.entity_vector_length)
 
@@ -370,7 +359,7 @@ cdef class KnowledgeBase:
 
         writer.close()
 
-    cpdef load_bulk(self, loc):
+    cpdef from_disk(self, loc):
         cdef hash_t entity_hash
         cdef hash_t alias_hash
         cdef int64_t entry_index
@@ -462,12 +451,11 @@ cdef class KnowledgeBase:
 
 cdef class Writer:
     def __init__(self, object loc):
-        if path.exists(loc):
-            assert not path.isdir(loc), f"{loc} is directory"
         if isinstance(loc, Path):
             loc = bytes(loc)
         if path.exists(loc):
-            assert not path.isdir(loc), "%s is directory." % loc
+            if path.isdir(loc):
+                raise ValueError(Errors.E928.format(loc=loc))
         cdef bytes bytes_loc = loc.encode('utf8') if type(loc) == unicode else loc
         self._fp = fopen(bytes_loc, 'wb')
         if not self._fp:
@@ -511,8 +499,10 @@ cdef class Reader:
     def __init__(self, object loc):
         if isinstance(loc, Path):
             loc = bytes(loc)
-        assert path.exists(loc)
-        assert not path.isdir(loc)
+        if not path.exists(loc):
+            raise ValueError(Errors.E929.format(loc=loc))
+        if path.isdir(loc):
+            raise ValueError(Errors.E928.format(loc=loc))
         cdef bytes bytes_loc = loc.encode('utf8') if type(loc) == unicode else loc
         self._fp = fopen(bytes_loc, 'rb')
         if not self._fp:
diff --git a/spacy/language.py b/spacy/language.py
index bf3bdb9aa..6fc780f3e 100644
--- a/spacy/language.py
+++ b/spacy/language.py
@@ -772,9 +772,9 @@ class Language:
         self.remove_pipe(name)
         if not len(self.pipeline) or pipe_index == len(self.pipeline):
             # we have no components to insert before/after, or we're replacing the last component
-            self.add_pipe(factory_name, name=name)
+            self.add_pipe(factory_name, name=name, config=config, validate=validate)
         else:
-            self.add_pipe(factory_name, name=name, before=pipe_index)
+            self.add_pipe(factory_name, name=name, before=pipe_index, config=config, validate=validate)
 
     def rename_pipe(self, old_name: str, new_name: str) -> None:
         """Rename a pipeline component.
diff --git a/spacy/ml/models/entity_linker.py b/spacy/ml/models/entity_linker.py
index f96d50a7b..55d8614e1 100644
--- a/spacy/ml/models/entity_linker.py
+++ b/spacy/ml/models/entity_linker.py
@@ -1,9 +1,9 @@
-from typing import Optional
+from typing import Optional, Callable, Iterable
 from thinc.api import chain, clone, list2ragged, reduce_mean, residual
 from thinc.api import Model, Maxout, Linear
 
 from ...util import registry
-from ...kb import KnowledgeBase
+from ...kb import KnowledgeBase, Candidate, get_candidates
 from ...vocab import Vocab
 
 
@@ -25,15 +25,21 @@ def build_nel_encoder(tok2vec: Model, nO: Optional[int] = None) -> Model:
 
 
 @registry.assets.register("spacy.KBFromFile.v1")
-def load_kb(vocab_path: str, kb_path: str) -> KnowledgeBase:
-    vocab = Vocab().from_disk(vocab_path)
-    kb = KnowledgeBase(entity_vector_length=1)
-    kb.initialize(vocab)
-    kb.load_bulk(kb_path)
-    return kb
+def load_kb(kb_path: str) -> Callable[[Vocab], KnowledgeBase]:
+    def kb_from_file(vocab):
+        kb = KnowledgeBase(vocab, entity_vector_length=1)
+        kb.from_disk(kb_path)
+        return kb
+    return kb_from_file
 
 
 @registry.assets.register("spacy.EmptyKB.v1")
-def empty_kb(entity_vector_length: int) -> KnowledgeBase:
-    kb = KnowledgeBase(entity_vector_length=entity_vector_length)
-    return kb
+def empty_kb(entity_vector_length: int) -> Callable[[Vocab], KnowledgeBase]:
+    def empty_kb_factory(vocab):
+        return KnowledgeBase(vocab=vocab, entity_vector_length=entity_vector_length)
+    return empty_kb_factory
+
+
+@registry.assets.register("spacy.CandidateGenerator.v1")
+def create_candidates() -> Callable[[KnowledgeBase, "Span"], Iterable[Candidate]]:
+    return get_candidates
diff --git a/spacy/pipeline/entity_linker.py b/spacy/pipeline/entity_linker.py
index 35bf2906e..d92c700ba 100644
--- a/spacy/pipeline/entity_linker.py
+++ b/spacy/pipeline/entity_linker.py
@@ -6,7 +6,7 @@ from thinc.api import CosineDistance, get_array_module, Model, Optimizer, Config
 from thinc.api import set_dropout_rate
 import warnings
 
-from ..kb import KnowledgeBase
+from ..kb import KnowledgeBase, Candidate
 from ..tokens import Doc
 from .pipe import Pipe, deserialize_config
 from ..language import Language
@@ -32,35 +32,30 @@ subword_features = true
 """
 DEFAULT_NEL_MODEL = Config().from_str(default_model_config)["model"]
 
-default_kb_config = """
-[kb]
-@assets = "spacy.EmptyKB.v1"
-entity_vector_length = 64
-"""
-DEFAULT_NEL_KB = Config().from_str(default_kb_config)["kb"]
-
 
 @Language.factory(
     "entity_linker",
     requires=["doc.ents", "doc.sents", "token.ent_iob", "token.ent_type"],
     assigns=["token.ent_kb_id"],
     default_config={
-        "kb": DEFAULT_NEL_KB,
+        "kb_loader": {"@assets": "spacy.EmptyKB.v1", "entity_vector_length": 64},
         "model": DEFAULT_NEL_MODEL,
         "labels_discard": [],
         "incl_prior": True,
         "incl_context": True,
+        "get_candidates": {"@assets": "spacy.CandidateGenerator.v1"},
     },
 )
 def make_entity_linker(
     nlp: Language,
     name: str,
     model: Model,
-    kb: KnowledgeBase,
+    kb_loader: Callable[[Vocab], KnowledgeBase],
     *,
     labels_discard: Iterable[str],
     incl_prior: bool,
     incl_context: bool,
+    get_candidates: Callable[[KnowledgeBase, "Span"], Iterable[Candidate]],
 ):
     """Construct an EntityLinker component.
 
@@ -76,10 +71,11 @@ def make_entity_linker(
         nlp.vocab,
         model,
         name,
-        kb=kb,
+        kb_loader=kb_loader,
         labels_discard=labels_discard,
         incl_prior=incl_prior,
         incl_context=incl_context,
+        get_candidates=get_candidates,
     )
 
 
@@ -97,10 +93,11 @@ class EntityLinker(Pipe):
         model: Model,
         name: str = "entity_linker",
         *,
-        kb: KnowledgeBase,
+        kb_loader: Callable[[Vocab], KnowledgeBase],
         labels_discard: Iterable[str],
         incl_prior: bool,
         incl_context: bool,
+        get_candidates: Callable[[KnowledgeBase, "Span"], Iterable[Candidate]],
     ) -> None:
         """Initialize an entity linker.
 
@@ -108,7 +105,7 @@ class EntityLinker(Pipe):
         model (thinc.api.Model): The Thinc Model powering the pipeline component.
         name (str): The component instance name, used to add entries to the
             losses during training.
-        kb (KnowledgeBase): The KnowledgeBase holding all entities and their aliases.
+        kb_loader (Callable[[Vocab], KnowledgeBase]): A function that creates a KnowledgeBase from a Vocab instance.
         labels_discard (Iterable[str]): NER labels that will automatically get a "NIL" prediction.
         incl_prior (bool): Whether or not to include prior probabilities from the KB in the model.
         incl_context (bool): Whether or not to include the local context in the model.
@@ -119,17 +116,12 @@ class EntityLinker(Pipe):
         self.model = model
         self.name = name
         cfg = {
-            "kb": kb,
             "labels_discard": list(labels_discard),
             "incl_prior": incl_prior,
             "incl_context": incl_context,
         }
-        if not isinstance(kb, KnowledgeBase):
-            raise ValueError(Errors.E990.format(type=type(self.kb)))
-        kb.initialize(vocab)
-        self.kb = kb
-        if "kb" in cfg:
-            del cfg["kb"]  # we don't want to duplicate its serialization
+        self.kb = kb_loader(self.vocab)
+        self.get_candidates = get_candidates
         self.cfg = dict(cfg)
         self.distance = CosineDistance(normalize=False)
         # how many neightbour sentences to take into account
@@ -326,10 +318,11 @@ class EntityLinker(Pipe):
                         end_token = sentences[end_sentence].end
                         sent_doc = doc[start_token:end_token].as_doc()
                         # currently, the context is the same for each entity in a sentence (should be refined)
-                        sentence_encoding = self.model.predict([sent_doc])[0]
-                        xp = get_array_module(sentence_encoding)
-                        sentence_encoding_t = sentence_encoding.T
-                        sentence_norm = xp.linalg.norm(sentence_encoding_t)
+                        xp = self.model.ops.xp
+                        if self.cfg.get("incl_context"):
+                            sentence_encoding = self.model.predict([sent_doc])[0]
+                            sentence_encoding_t = sentence_encoding.T
+                            sentence_norm = xp.linalg.norm(sentence_encoding_t)
                         for ent in sent.ents:
                             entity_count += 1
                             to_discard = self.cfg.get("labels_discard", [])
@@ -337,7 +330,7 @@ class EntityLinker(Pipe):
                                 # ignoring this entity - setting to NIL
                                 final_kb_ids.append(self.NIL)
                             else:
-                                candidates = self.kb.get_candidates(ent.text)
+                                candidates = self.get_candidates(self.kb, ent)
                                 if not candidates:
                                     # no prediction possible for this entity - setting to NIL
                                     final_kb_ids.append(self.NIL)
@@ -421,10 +414,9 @@ class EntityLinker(Pipe):
         DOCS: https://spacy.io/api/entitylinker#to_disk
         """
         serialize = {}
-        self.cfg["entity_width"] = self.kb.entity_vector_length
         serialize["cfg"] = lambda p: srsly.write_json(p, self.cfg)
         serialize["vocab"] = lambda p: self.vocab.to_disk(p)
-        serialize["kb"] = lambda p: self.kb.dump(p)
+        serialize["kb"] = lambda p: self.kb.to_disk(p)
         serialize["model"] = lambda p: self.model.to_disk(p)
         util.to_disk(path, serialize, exclude)
 
@@ -446,15 +438,10 @@ class EntityLinker(Pipe):
             except AttributeError:
                 raise ValueError(Errors.E149) from None
 
-        def load_kb(p):
-            self.kb = KnowledgeBase(entity_vector_length=self.cfg["entity_width"])
-            self.kb.initialize(self.vocab)
-            self.kb.load_bulk(p)
-
         deserialize = {}
         deserialize["vocab"] = lambda p: self.vocab.from_disk(p)
         deserialize["cfg"] = lambda p: self.cfg.update(deserialize_config(p))
-        deserialize["kb"] = load_kb
+        deserialize["kb"] = lambda p: self.kb.from_disk(p)
         deserialize["model"] = load_model
         util.from_disk(path, deserialize, exclude)
         return self
diff --git a/spacy/pipeline/nn_parser.pyx b/spacy/pipeline/nn_parser.pyx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/spacy/pipeline/tagger.pyx b/spacy/pipeline/tagger.pyx
index 9070329e8..2a4274597 100644
--- a/spacy/pipeline/tagger.pyx
+++ b/spacy/pipeline/tagger.pyx
@@ -68,7 +68,6 @@ class Tagger(Pipe):
         name (str): The component instance name, used to add entries to the
             losses during training.
         labels (List): The set of labels. Defaults to None.
-        set_morphology (bool): Whether to set morphological features.
 
         DOCS: https://spacy.io/api/tagger#init
         """
diff --git a/spacy/tests/pipeline/test_entity_linker.py b/spacy/tests/pipeline/test_entity_linker.py
index b3fb6d0fc..4385d2bf9 100644
--- a/spacy/tests/pipeline/test_entity_linker.py
+++ b/spacy/tests/pipeline/test_entity_linker.py
@@ -1,6 +1,7 @@
+from typing import Callable, Iterable
 import pytest
 
-from spacy.kb import KnowledgeBase
+from spacy.kb import KnowledgeBase, get_candidates, Candidate
 
 from spacy import util, registry
 from spacy.gold import Example
@@ -21,8 +22,7 @@ def assert_almost_equal(a, b):
 
 def test_kb_valid_entities(nlp):
     """Test the valid construction of a KB with 3 entities and two aliases"""
-    mykb = KnowledgeBase(entity_vector_length=3)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=3)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=19, entity_vector=[8, 4, 3])
@@ -51,8 +51,7 @@ def test_kb_valid_entities(nlp):
 
 def test_kb_invalid_entities(nlp):
     """Test the invalid construction of a KB with an alias linked to a non-existing entity"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=19, entity_vector=[1])
@@ -68,8 +67,7 @@ def test_kb_invalid_entities(nlp):
 
 def test_kb_invalid_probabilities(nlp):
     """Test the invalid construction of a KB with wrong prior probabilities"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=19, entity_vector=[1])
@@ -83,8 +81,7 @@ def test_kb_invalid_probabilities(nlp):
 
 def test_kb_invalid_combination(nlp):
     """Test the invalid construction of a KB with non-matching entity and probability lists"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=19, entity_vector=[1])
@@ -100,8 +97,7 @@ def test_kb_invalid_combination(nlp):
 
 def test_kb_invalid_entity_vector(nlp):
     """Test the invalid construction of a KB with non-matching entity vector lengths"""
-    mykb = KnowledgeBase(entity_vector_length=3)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=3)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=19, entity_vector=[1, 2, 3])
@@ -117,14 +113,14 @@ def test_kb_default(nlp):
     assert len(entity_linker.kb) == 0
     assert entity_linker.kb.get_size_entities() == 0
     assert entity_linker.kb.get_size_aliases() == 0
-    # default value from pipeline.entity_linker
+    # 64 is the default value from pipeline.entity_linker
     assert entity_linker.kb.entity_vector_length == 64
 
 
 def test_kb_custom_length(nlp):
     """Test that the default (empty) KB can be configured with a custom entity length"""
     entity_linker = nlp.add_pipe(
-        "entity_linker", config={"kb": {"entity_vector_length": 35}}
+        "entity_linker", config={"kb_loader": {"entity_vector_length": 35}}
     )
     assert len(entity_linker.kb) == 0
     assert entity_linker.kb.get_size_entities() == 0
@@ -141,7 +137,7 @@ def test_kb_undefined(nlp):
 
 def test_kb_empty(nlp):
     """Test that the EL can't train with an empty KB"""
-    config = {"kb": {"@assets": "spacy.EmptyKB.v1", "entity_vector_length": 342}}
+    config = {"kb_loader": {"@assets": "spacy.EmptyKB.v1", "entity_vector_length": 342}}
     entity_linker = nlp.add_pipe("entity_linker", config=config)
     assert len(entity_linker.kb) == 0
     with pytest.raises(ValueError):
@@ -150,8 +146,13 @@ def test_kb_empty(nlp):
 
 def test_candidate_generation(nlp):
     """Test correct candidate generation"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
+    doc = nlp("douglas adam Adam shrubbery")
+
+    douglas_ent = doc[0:1]
+    adam_ent = doc[1:2]
+    Adam_ent = doc[2:3]
+    shrubbery_ent = doc[3:4]
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=27, entity_vector=[1])
@@ -163,21 +164,76 @@ def test_candidate_generation(nlp):
     mykb.add_alias(alias="adam", entities=["Q2"], probabilities=[0.9])
 
     # test the size of the relevant candidates
-    assert len(mykb.get_candidates("douglas")) == 2
-    assert len(mykb.get_candidates("adam")) == 1
-    assert len(mykb.get_candidates("shrubbery")) == 0
+    assert len(get_candidates(mykb, douglas_ent)) == 2
+    assert len(get_candidates(mykb, adam_ent)) == 1
+    assert len(get_candidates(mykb, Adam_ent)) == 0  # default case sensitive
+    assert len(get_candidates(mykb, shrubbery_ent)) == 0
 
     # test the content of the candidates
-    assert mykb.get_candidates("adam")[0].entity_ == "Q2"
-    assert mykb.get_candidates("adam")[0].alias_ == "adam"
-    assert_almost_equal(mykb.get_candidates("adam")[0].entity_freq, 12)
-    assert_almost_equal(mykb.get_candidates("adam")[0].prior_prob, 0.9)
+    assert get_candidates(mykb, adam_ent)[0].entity_ == "Q2"
+    assert get_candidates(mykb, adam_ent)[0].alias_ == "adam"
+    assert_almost_equal(get_candidates(mykb, adam_ent)[0].entity_freq, 12)
+    assert_almost_equal(get_candidates(mykb, adam_ent)[0].prior_prob, 0.9)
+
+
+def test_el_pipe_configuration(nlp):
+    """Test correct candidate generation as part of the EL pipe"""
+    nlp.add_pipe("sentencizer")
+    pattern = {"label": "PERSON", "pattern": [{"LOWER": "douglas"}]}
+    ruler = nlp.add_pipe("entity_ruler")
+    ruler.add_patterns([pattern])
+
+    @registry.assets.register("myAdamKB.v1")
+    def mykb() -> Callable[["Vocab"], KnowledgeBase]:
+        def create_kb(vocab):
+            kb = KnowledgeBase(vocab, entity_vector_length=1)
+            kb.add_entity(entity="Q2", freq=12, entity_vector=[2])
+            kb.add_entity(entity="Q3", freq=5, entity_vector=[3])
+            kb.add_alias(
+                alias="douglas", entities=["Q2", "Q3"], probabilities=[0.8, 0.1]
+            )
+            return kb
+
+        return create_kb
+
+    # run an EL pipe without a trained context encoder, to check the candidate generation step only
+    nlp.add_pipe(
+        "entity_linker",
+        config={"kb_loader": {"@assets": "myAdamKB.v1"}, "incl_context": False},
+    )
+    # With the default get_candidates function, matching is case-sensitive
+    text = "Douglas and douglas are not the same."
+    doc = nlp(text)
+    assert doc[0].ent_kb_id_ == "NIL"
+    assert doc[1].ent_kb_id_ == ""
+    assert doc[2].ent_kb_id_ == "Q2"
+
+    def get_lowercased_candidates(kb, span):
+        return kb.get_alias_candidates(span.text.lower())
+
+    @registry.assets.register("spacy.LowercaseCandidateGenerator.v1")
+    def create_candidates() -> Callable[[KnowledgeBase, "Span"], Iterable[Candidate]]:
+        return get_lowercased_candidates
+
+    # replace the pipe with a new one with with a different candidate generator
+    nlp.replace_pipe(
+        "entity_linker",
+        "entity_linker",
+        config={
+            "kb_loader": {"@assets": "myAdamKB.v1"},
+            "incl_context": False,
+            "get_candidates": {"@assets": "spacy.LowercaseCandidateGenerator.v1"},
+        },
+    )
+    doc = nlp(text)
+    assert doc[0].ent_kb_id_ == "Q2"
+    assert doc[1].ent_kb_id_ == ""
+    assert doc[2].ent_kb_id_ == "Q2"
 
 
 def test_append_alias(nlp):
     """Test that we can append additional alias-entity pairs"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=27, entity_vector=[1])
@@ -189,26 +245,25 @@ def test_append_alias(nlp):
     mykb.add_alias(alias="adam", entities=["Q2"], probabilities=[0.9])
 
     # test the size of the relevant candidates
-    assert len(mykb.get_candidates("douglas")) == 2
+    assert len(mykb.get_alias_candidates("douglas")) == 2
 
     # append an alias
     mykb.append_alias(alias="douglas", entity="Q1", prior_prob=0.2)
 
     # test the size of the relevant candidates has been incremented
-    assert len(mykb.get_candidates("douglas")) == 3
+    assert len(mykb.get_alias_candidates("douglas")) == 3
 
     # append the same alias-entity pair again should not work (will throw a warning)
     with pytest.warns(UserWarning):
         mykb.append_alias(alias="douglas", entity="Q1", prior_prob=0.3)
 
     # test the size of the relevant candidates remained unchanged
-    assert len(mykb.get_candidates("douglas")) == 3
+    assert len(mykb.get_alias_candidates("douglas")) == 3
 
 
 def test_append_invalid_alias(nlp):
     """Test that append an alias will throw an error if prior probs are exceeding 1"""
-    mykb = KnowledgeBase(entity_vector_length=1)
-    mykb.initialize(nlp.vocab)
+    mykb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
 
     # adding entities
     mykb.add_entity(entity="Q1", freq=27, entity_vector=[1])
@@ -228,16 +283,18 @@ def test_preserving_links_asdoc(nlp):
     """Test that Span.as_doc preserves the existing entity links"""
 
     @registry.assets.register("myLocationsKB.v1")
-    def dummy_kb() -> KnowledgeBase:
-        mykb = KnowledgeBase(entity_vector_length=1)
-        mykb.initialize(nlp.vocab)
-        # adding entities
-        mykb.add_entity(entity="Q1", freq=19, entity_vector=[1])
-        mykb.add_entity(entity="Q2", freq=8, entity_vector=[1])
-        # adding aliases
-        mykb.add_alias(alias="Boston", entities=["Q1"], probabilities=[0.7])
-        mykb.add_alias(alias="Denver", entities=["Q2"], probabilities=[0.6])
-        return mykb
+    def dummy_kb() -> Callable[["Vocab"], KnowledgeBase]:
+        def create_kb(vocab):
+            mykb = KnowledgeBase(vocab, entity_vector_length=1)
+            # adding entities
+            mykb.add_entity(entity="Q1", freq=19, entity_vector=[1])
+            mykb.add_entity(entity="Q2", freq=8, entity_vector=[1])
+            # adding aliases
+            mykb.add_alias(alias="Boston", entities=["Q1"], probabilities=[0.7])
+            mykb.add_alias(alias="Denver", entities=["Q2"], probabilities=[0.6])
+            return mykb
+
+        return create_kb
 
     # set up pipeline with NER (Entity Ruler) and NEL (prior probability only, model not trained)
     nlp.add_pipe("sentencizer")
@@ -247,7 +304,7 @@ def test_preserving_links_asdoc(nlp):
     ]
     ruler = nlp.add_pipe("entity_ruler")
     ruler.add_patterns(patterns)
-    el_config = {"kb": {"@assets": "myLocationsKB.v1"}, "incl_prior": False}
+    el_config = {"kb_loader": {"@assets": "myLocationsKB.v1"}, "incl_prior": False}
     el_pipe = nlp.add_pipe("entity_linker", config=el_config, last=True)
     el_pipe.begin_training(lambda: [])
     el_pipe.incl_context = False
@@ -331,24 +388,28 @@ def test_overfitting_IO():
         train_examples.append(Example.from_dict(doc, annotation))
 
     @registry.assets.register("myOverfittingKB.v1")
-    def dummy_kb() -> KnowledgeBase:
-        # create artificial KB - assign same prior weight to the two russ cochran's
-        # Q2146908 (Russ Cochran): American golfer
-        # Q7381115 (Russ Cochran): publisher
-        mykb = KnowledgeBase(entity_vector_length=3)
-        mykb.initialize(nlp.vocab)
-        mykb.add_entity(entity="Q2146908", freq=12, entity_vector=[6, -4, 3])
-        mykb.add_entity(entity="Q7381115", freq=12, entity_vector=[9, 1, -7])
-        mykb.add_alias(
-            alias="Russ Cochran",
-            entities=["Q2146908", "Q7381115"],
-            probabilities=[0.5, 0.5],
-        )
-        return mykb
+    def dummy_kb() -> Callable[["Vocab"], KnowledgeBase]:
+        def create_kb(vocab):
+            # create artificial KB - assign same prior weight to the two russ cochran's
+            # Q2146908 (Russ Cochran): American golfer
+            # Q7381115 (Russ Cochran): publisher
+            mykb = KnowledgeBase(vocab, entity_vector_length=3)
+            mykb.add_entity(entity="Q2146908", freq=12, entity_vector=[6, -4, 3])
+            mykb.add_entity(entity="Q7381115", freq=12, entity_vector=[9, 1, -7])
+            mykb.add_alias(
+                alias="Russ Cochran",
+                entities=["Q2146908", "Q7381115"],
+                probabilities=[0.5, 0.5],
+            )
+            return mykb
+
+        return create_kb
 
     # Create the Entity Linker component and add it to the pipeline
     nlp.add_pipe(
-        "entity_linker", config={"kb": {"@assets": "myOverfittingKB.v1"}}, last=True
+        "entity_linker",
+        config={"kb_loader": {"@assets": "myOverfittingKB.v1"}},
+        last=True,
     )
 
     # train the NEL pipe
diff --git a/spacy/tests/pipeline/test_pipe_methods.py b/spacy/tests/pipeline/test_pipe_methods.py
index 0141708b4..feb11cabc 100644
--- a/spacy/tests/pipeline/test_pipe_methods.py
+++ b/spacy/tests/pipeline/test_pipe_methods.py
@@ -78,6 +78,14 @@ def test_replace_last_pipe(nlp):
     assert nlp.pipe_names == ["sentencizer", "ner"]
 
 
+def test_replace_pipe_config(nlp):
+    nlp.add_pipe("entity_linker")
+    nlp.add_pipe("sentencizer")
+    assert nlp.get_pipe("entity_linker").cfg["incl_prior"] == True
+    nlp.replace_pipe("entity_linker", "entity_linker", config={"incl_prior": False})
+    assert nlp.get_pipe("entity_linker").cfg["incl_prior"] == False
+
+
 @pytest.mark.parametrize("old_name,new_name", [("old_pipe", "new_pipe")])
 def test_rename_pipe(nlp, old_name, new_name):
     with pytest.raises(ValueError):
diff --git a/spacy/tests/regression/test_issue4501-5000.py b/spacy/tests/regression/test_issue4501-5000.py
index 1e655851f..0d4ce9a30 100644
--- a/spacy/tests/regression/test_issue4501-5000.py
+++ b/spacy/tests/regression/test_issue4501-5000.py
@@ -139,8 +139,7 @@ def test_issue4665():
 def test_issue4674():
     """Test that setting entities with overlapping identifiers does not mess up IO"""
     nlp = English()
-    kb = KnowledgeBase(entity_vector_length=3)
-    kb.initialize(nlp.vocab)
+    kb = KnowledgeBase(nlp.vocab, entity_vector_length=3)
     vector1 = [0.9, 1.1, 1.01]
     vector2 = [1.8, 2.25, 2.01]
     with pytest.warns(UserWarning):
@@ -156,10 +155,9 @@ def test_issue4674():
         if not dir_path.exists():
             dir_path.mkdir()
         file_path = dir_path / "kb"
-        kb.dump(str(file_path))
-        kb2 = KnowledgeBase(entity_vector_length=3)
-        kb2.initialize(nlp.vocab)
-        kb2.load_bulk(str(file_path))
+        kb.to_disk(str(file_path))
+        kb2 = KnowledgeBase(nlp.vocab, entity_vector_length=3)
+        kb2.from_disk(str(file_path))
     assert kb2.get_size_entities() == 1
 
 
diff --git a/spacy/tests/regression/test_issue5230.py b/spacy/tests/regression/test_issue5230.py
index 93069d9a3..58d03ca8b 100644
--- a/spacy/tests/regression/test_issue5230.py
+++ b/spacy/tests/regression/test_issue5230.py
@@ -1,3 +1,4 @@
+from typing import Callable
 import warnings
 from unittest import TestCase
 import pytest
@@ -70,13 +71,14 @@ def entity_linker():
     nlp = Language()
 
     @registry.assets.register("TestIssue5230KB.v1")
-    def dummy_kb() -> KnowledgeBase:
-        kb = KnowledgeBase(entity_vector_length=1)
-        kb.initialize(nlp.vocab)
-        kb.add_entity("test", 0.0, zeros((1, 1), dtype="f"))
-        return kb
+    def dummy_kb() -> Callable[["Vocab"], KnowledgeBase]:
+        def create_kb(vocab):
+            kb = KnowledgeBase(vocab, entity_vector_length=1)
+            kb.add_entity("test", 0.0, zeros((1, 1), dtype="f"))
+            return kb
+        return create_kb
 
-    config = {"kb": {"@assets": "TestIssue5230KB.v1"}}
+    config = {"kb_loader": {"@assets": "TestIssue5230KB.v1"}}
     entity_linker = nlp.add_pipe("entity_linker", config=config)
     # need to add model for two reasons:
     # 1. no model leads to error in serialization,
@@ -121,19 +123,17 @@ def test_writer_with_path_py35():
 
 def test_save_and_load_knowledge_base():
     nlp = Language()
-    kb = KnowledgeBase(entity_vector_length=1)
-    kb.initialize(nlp.vocab)
+    kb = KnowledgeBase(nlp.vocab, entity_vector_length=1)
     with make_tempdir() as d:
         path = d / "kb"
         try:
-            kb.dump(path)
+            kb.to_disk(path)
         except Exception as e:
             pytest.fail(str(e))
 
         try:
-            kb_loaded = KnowledgeBase(entity_vector_length=1)
-            kb_loaded.initialize(nlp.vocab)
-            kb_loaded.load_bulk(path)
+            kb_loaded = KnowledgeBase(nlp.vocab, entity_vector_length=1)
+            kb_loaded.from_disk(path)
         except Exception as e:
             pytest.fail(str(e))
 
diff --git a/spacy/tests/serialize/test_serialize_kb.py b/spacy/tests/serialize/test_serialize_kb.py
index 3f33c6f06..3cf5485d7 100644
--- a/spacy/tests/serialize/test_serialize_kb.py
+++ b/spacy/tests/serialize/test_serialize_kb.py
@@ -1,4 +1,8 @@
-from spacy.util import ensure_path
+from typing import Callable
+
+from spacy import util
+from spacy.lang.en import English
+from spacy.util import ensure_path, registry
 from spacy.kb import KnowledgeBase
 
 from ..util import make_tempdir
@@ -15,20 +19,16 @@ def test_serialize_kb_disk(en_vocab):
         if not dir_path.exists():
             dir_path.mkdir()
         file_path = dir_path / "kb"
-        kb1.dump(str(file_path))
-
-        kb2 = KnowledgeBase(entity_vector_length=3)
-        kb2.initialize(en_vocab)
-        kb2.load_bulk(str(file_path))
+        kb1.to_disk(str(file_path))
+        kb2 = KnowledgeBase(vocab=en_vocab, entity_vector_length=3)
+        kb2.from_disk(str(file_path))
 
     # final assertions
     _check_kb(kb2)
 
 
 def _get_dummy_kb(vocab):
-    kb = KnowledgeBase(entity_vector_length=3)
-    kb.initialize(vocab)
-
+    kb = KnowledgeBase(vocab, entity_vector_length=3)
     kb.add_entity(entity="Q53", freq=33, entity_vector=[0, 5, 3])
     kb.add_entity(entity="Q17", freq=2, entity_vector=[7, 1, 0])
     kb.add_entity(entity="Q007", freq=7, entity_vector=[0, 0, 7])
@@ -61,7 +61,7 @@ def _check_kb(kb):
         assert alias_string not in kb.get_alias_strings()
 
     # check candidates & probabilities
-    candidates = sorted(kb.get_candidates("double07"), key=lambda x: x.entity_)
+    candidates = sorted(kb.get_alias_candidates("double07"), key=lambda x: x.entity_)
     assert len(candidates) == 2
 
     assert candidates[0].entity_ == "Q007"
@@ -75,3 +75,47 @@ def _check_kb(kb):
     assert candidates[1].entity_vector == [7, 1, 0]
     assert candidates[1].alias_ == "double07"
     assert 0.099 < candidates[1].prior_prob < 0.101
+
+
+def test_serialize_subclassed_kb():
+    """Check that IO of a custom KB works fine as part of an EL pipe."""
+
+    class SubKnowledgeBase(KnowledgeBase):
+        def __init__(self, vocab, entity_vector_length, custom_field):
+            super().__init__(vocab, entity_vector_length)
+            self.custom_field = custom_field
+
+    @registry.assets.register("spacy.CustomKB.v1")
+    def custom_kb(
+        entity_vector_length: int, custom_field: int
+    ) -> Callable[["Vocab"], KnowledgeBase]:
+        def custom_kb_factory(vocab):
+            return SubKnowledgeBase(
+                vocab=vocab,
+                entity_vector_length=entity_vector_length,
+                custom_field=custom_field,
+            )
+
+        return custom_kb_factory
+
+    nlp = English()
+    config = {
+        "kb_loader": {
+            "@assets": "spacy.CustomKB.v1",
+            "entity_vector_length": 342,
+            "custom_field": 666,
+        }
+    }
+    entity_linker = nlp.add_pipe("entity_linker", config=config)
+    assert type(entity_linker.kb) == SubKnowledgeBase
+    assert entity_linker.kb.entity_vector_length == 342
+    assert entity_linker.kb.custom_field == 666
+
+    # Make sure the custom KB is serialized correctly
+    with make_tempdir() as tmp_dir:
+        nlp.to_disk(tmp_dir)
+        nlp2 = util.load_model_from_path(tmp_dir)
+        entity_linker2 = nlp2.get_pipe("entity_linker")
+        assert type(entity_linker2.kb) == SubKnowledgeBase
+        assert entity_linker2.kb.entity_vector_length == 342
+        assert entity_linker2.kb.custom_field == 666
diff --git a/website/docs/api/kb.md b/website/docs/api/kb.md
index 3db5d6bac..855dead27 100644
--- a/website/docs/api/kb.md
+++ b/website/docs/api/kb.md
@@ -200,21 +200,21 @@ probability of the fact that the mention links to the entity ID.
 | `alias`     | The textual mention or alias. ~~str~~                                     |
 | **RETURNS** | The prior probability of the `alias` referring to the `entity`. ~~float~~ |
 
-## KnowledgeBase.dump {#dump tag="method"}
+## KnowledgeBase.to_disk {#to_disk tag="method"}
 
 Save the current state of the knowledge base to a directory.
 
 > #### Example
 >
 > ```python
-> kb.dump(loc)
+> kb.to_disk(loc)
 > ```
 
 | Name  | Description                                                                                                                                |
 | ----- | ------------------------------------------------------------------------------------------------------------------------------------------ |
 | `loc` | A path to a directory, which will be created if it doesn't exist. Paths may be either strings or `Path`-like objects. ~~Union[str, Path]~~ |
 
-## KnowledgeBase.load_bulk {#load_bulk tag="method"}
+## KnowledgeBase.from_disk {#from_disk tag="method"}
 
 Restore the state of the knowledge base from a given directory. Note that the
 [`Vocab`](/api/vocab) should also be the same as the one used to create the KB.
@@ -226,7 +226,7 @@ Restore the state of the knowledge base from a given directory. Note that the
 > from spacy.vocab import Vocab
 > vocab = Vocab().from_disk("/path/to/vocab")
 > kb = KnowledgeBase(vocab=vocab, entity_vector_length=64)
-> kb.load_bulk("/path/to/kb")
+> kb.from_disk("/path/to/kb")
 > ```
 
 | Name        | Description                                                                                     |

From 0d55b6ebb45ed66dbd9ef60ec0f79b983ddf093b Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 18:55:56 +0200
Subject: [PATCH 22/92] formatting

---
 website/docs/api/architectures.md | 44 +++++++++++++++----------------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md
index b74f0275a..446e6c7c3 100644
--- a/website/docs/api/architectures.md
+++ b/website/docs/api/architectures.md
@@ -545,18 +545,18 @@ network has an internal CNN Tok2Vec layer and uses attention.
 
 
 
-| Name                 | Description                                                                                                                                                                                            |
-| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `exclusive_classes`  | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
-| `pretrained_vectors` | Whether or not pretrained vectors will be used in addition to the feature vectors. ~~bool~~                                                                                                            |
-| `width`              | Output dimension of the feature encoding step. ~~int~~                                                                                                                                                 |
-| `embed_size`         | Input dimension of the feature encoding step. ~~int~~                                                                                                                                                  |
-| `conv_depth`         | Depth of the tok2vec layer. ~~int~~                                                                                                                                                                    |
-| `window_size`        | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. ~~int~~                                                            |
-| `ngram_size`         | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                    |
-| `dropout`            | The dropout rate. ~~float~~                                                                                                                                                                            |
+| Name                 | Description                                                                                                                                                                                        |
+| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `exclusive_classes`  | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                         |
+| `pretrained_vectors` | Whether or not pretrained vectors will be used in addition to the feature vectors. ~~bool~~                                                                                                        |
+| `width`              | Output dimension of the feature encoding step. ~~int~~                                                                                                                                             |
+| `embed_size`         | Input dimension of the feature encoding step. ~~int~~                                                                                                                                              |
+| `conv_depth`         | Depth of the tok2vec layer. ~~int~~                                                                                                                                                                |
+| `window_size`        | The number of contextual vectors to [concatenate](https://thinc.ai/docs/api-layers#expand_window) from the left and from the right. ~~int~~                                                        |
+| `ngram_size`         | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                |
+| `dropout`            | The dropout rate. ~~float~~                                                                                                                                                                        |
 | `nO`                 | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
-| **CREATES**          | The model using the architecture. ~~Model~~                                                                                                                                                            |
+| **CREATES**          | The model using the architecture. ~~Model~~                                                                                                                                                        |
 
 ### spacy.TextCatCNN.v1 {#TextCatCNN}
 
@@ -585,12 +585,12 @@ architecture is usually less accurate than the ensemble, but runs faster.
 
 
 
-| Name                | Description                                                                                                                                                                                            |
-| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
-| `tok2vec`           | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~                                                                                                                                                |
+| Name                | Description                                                                                                                                                                                        |
+| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                         |
+| `tok2vec`           | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~                                                                                                                                            |
 | `nO`                | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
-| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                            |
+| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                        |
 
 ### spacy.TextCatBOW.v1 {#TextCatBOW}
 
@@ -610,13 +610,13 @@ others, but may not be as accurate, especially if texts are short.
 
 
 
-| Name                | Description                                                                                                                                                                                            |
-| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                             |
-| `ngram_size`        | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                    |
-| `no_output_layer`   | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~                                                                   |
+| Name                | Description                                                                                                                                                                                        |
+| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~                                                                                                                                         |
+| `ngram_size`        | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~                                                |
+| `no_output_layer`   | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~                                                               |
 | `nO`                | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ |
-| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                            |
+| **CREATES**         | The model using the architecture. ~~Model~~                                                                                                                                                        |
 
 ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"}
 

From a8acedd4baa2dcce742c3e6ded160f11cbcf048d Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 19:15:16 +0200
Subject: [PATCH 23/92] example of custom reader and batcher

---
 spacy/cli/train.py             |  2 +-
 website/docs/usage/training.md | 60 +++++++++++++++++++++++++++++++++-
 2 files changed, 60 insertions(+), 2 deletions(-)

diff --git a/spacy/cli/train.py b/spacy/cli/train.py
index 202a8555c..43866e4b3 100644
--- a/spacy/cli/train.py
+++ b/spacy/cli/train.py
@@ -235,7 +235,7 @@ def train_while_improving(
     with each iteration yielding a tuple `(batch, info, is_best_checkpoint)`,
     where info is a dict, and is_best_checkpoint is in [True, False, None] --
     None indicating that the iteration was not evaluated as a checkpoint.
-    The evaluation is conducted by calling the evaluate callback, which should
+    The evaluation is conducted by calling the evaluate callback.
 
     Positional arguments:
         nlp: The spaCy pipeline to evaluate.
diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index d84635177..911046965 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -657,7 +657,65 @@ factor = 1.005
 
 #### Example: Custom data reading and batching {#custom-code-readers-batchers}
 
-
+Some use-cases require streaming in data or manipulating datasets on the fly,
+rather than generating all data beforehand and storing it to file. Instead of
+using the built-in reader `"spacy.Corpus.v1"`, which uses static file paths, you
+can create and register a custom function that generates
+[`Example`](/api/example) objects. The resulting generator can be infinite. When
+using this dataset for training, other stopping criteria can be used such as
+maximum number of steps, or stopping when the loss does not decrease further.
+
+For instance, in this example we assume a custom function `read_custom_data()`
+which loads or generates texts with relevant textcat annotations. Then, small lexical
+variations of the input text are created before generating the final `Example`
+objects.
+
+```python
+### functions.py
+from typing import Callable, Iterable
+import spacy
+from spacy.gold import Example
+import random
+
+@spacy.registry.readers("corpus_variants.v1")
+def stream_data() -> Callable[["Language"], Iterable[Example]]:
+    def generate_stream(nlp):
+        for text, cats in read_custom_data():
+            random_index = random.randint(0, len(text) - 1)
+            output_list = list(text)
+            output_list[random_index] = output_list[random_index].upper()
+            doc = nlp.make_doc("".join(output_list))
+            example = Example.from_dict(doc, {"cats": cats})
+            yield example
+    return generate_stream
+```
+
+We can also customize the batching strategy by registering a new "batcher" which
+turns a stream of items into a stream of batches. spaCy has several useful builtin
+batching strategies with customizable sizes , but it's also
+easy to implement your own. For instance, the following function takes the stream 
+of generated Example objects, and removes those which have the exact same underlying 
+raw text, to avoid duplicates in the final training data. Note that in a more realistic 
+implementation, you'd also want to check whether the annotations are exactly the same.
+
+```python
+### functions.py
+from typing import Callable, Iterable, List
+import spacy
+from spacy.gold import Example
+
+@spacy.registry.batchers("filtering_batch.v1")
+def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterable[List[Example]]]:
+    def create_filtered_batches(examples: Iterable[Example]) -> Iterable[List[Example]]:
+        batch = []
+        for eg in examples:
+            if eg.text not in [x.text for x in batch]:
+                batch.append(eg)
+            if len(batch) == size:
+                yield batch
+                batch = []
+    return create_filtered_batches
+```
 
 ### Wrapping PyTorch and TensorFlow {#custom-frameworks}
 

From f9fe5eb3230940250e25f32f6905167bda004e27 Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 19:35:23 +0200
Subject: [PATCH 24/92] clean up example

---
 website/docs/usage/training.md | 51 ++++++++++++++++++----------------
 1 file changed, 27 insertions(+), 24 deletions(-)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 911046965..4ee17ee21 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -662,17 +662,35 @@ rather than generating all data beforehand and storing it to file. Instead of
 using the built-in reader `"spacy.Corpus.v1"`, which uses static file paths, you
 can create and register a custom function that generates
 [`Example`](/api/example) objects. The resulting generator can be infinite. When
-using this dataset for training, other stopping criteria can be used such as
-maximum number of steps, or stopping when the loss does not decrease further.
+using this dataset for training, stopping criteria such as maximum number of
+steps, or stopping when the loss does not decrease further, can be used.
 
-For instance, in this example we assume a custom function `read_custom_data()`
-which loads or generates texts with relevant textcat annotations. Then, small lexical
-variations of the input text are created before generating the final `Example`
-objects.
+In this example we assume a custom function `read_custom_data()`
+which loads or generates texts with relevant textcat annotations. Then, small
+lexical variations of the input text are created before generating the final
+`Example` objects.
+
+We can also customize the batching strategy by registering a new "batcher" which
+turns a stream of items into a stream of batches. spaCy has several useful
+built-in batching strategies with customizable sizes, but
+it's also easy to implement your own. For instance, the following function takes
+the stream of generated `Example` objects, and removes those which have the exact
+same underlying raw text, to avoid duplicates in the final training data. Note
+that in a more realistic implementation, you'd also want to check whether the
+annotations are exactly the same.
+
+> ```ini
+> [training.train_corpus]
+> @readers = "corpus_variants.v1"
+>
+> [training.batcher]
+> @batchers = "filtering_batch.v1"
+> size = 150
+> ```
 
 ```python
 ### functions.py
-from typing import Callable, Iterable
+from typing import Callable, Iterable, List
 import spacy
 from spacy.gold import Example
 import random
@@ -682,27 +700,12 @@ def stream_data() -> Callable[["Language"], Iterable[Example]]:
     def generate_stream(nlp):
         for text, cats in read_custom_data():
             random_index = random.randint(0, len(text) - 1)
-            output_list = list(text)
-            output_list[random_index] = output_list[random_index].upper()
-            doc = nlp.make_doc("".join(output_list))
+            variant = text[:random_index] + text[random_index].upper() + text[random_index + 1:]
+            doc = nlp.make_doc(variant)
             example = Example.from_dict(doc, {"cats": cats})
             yield example
     return generate_stream
-```
 
-We can also customize the batching strategy by registering a new "batcher" which
-turns a stream of items into a stream of batches. spaCy has several useful builtin
-batching strategies with customizable sizes , but it's also
-easy to implement your own. For instance, the following function takes the stream 
-of generated Example objects, and removes those which have the exact same underlying 
-raw text, to avoid duplicates in the final training data. Note that in a more realistic 
-implementation, you'd also want to check whether the annotations are exactly the same.
-
-```python
-### functions.py
-from typing import Callable, Iterable, List
-import spacy
-from spacy.gold import Example
 
 @spacy.registry.batchers("filtering_batch.v1")
 def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterable[List[Example]]]:

From 6ed67d495a5bc42b9e7a8d7e1932363488b728af Mon Sep 17 00:00:00 2001
From: svlandeg 
Date: Tue, 18 Aug 2020 19:43:20 +0200
Subject: [PATCH 25/92] format

---
 website/docs/usage/training.md | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 4ee17ee21..adafcac68 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -665,18 +665,18 @@ can create and register a custom function that generates
 using this dataset for training, stopping criteria such as maximum number of
 steps, or stopping when the loss does not decrease further, can be used.
 
-In this example we assume a custom function `read_custom_data()`
-which loads or generates texts with relevant textcat annotations. Then, small
-lexical variations of the input text are created before generating the final
-`Example` objects.
+In this example we assume a custom function `read_custom_data()` which loads or
+generates texts with relevant textcat annotations. Then, small lexical
+variations of the input text are created before generating the final `Example`
+objects.
 
 We can also customize the batching strategy by registering a new "batcher" which
 turns a stream of items into a stream of batches. spaCy has several useful
 built-in batching strategies with customizable sizes, but
 it's also easy to implement your own. For instance, the following function takes
-the stream of generated `Example` objects, and removes those which have the exact
-same underlying raw text, to avoid duplicates in the final training data. Note
-that in a more realistic implementation, you'd also want to check whether the
+the stream of generated `Example` objects, and removes those which have the
+exact same underlying raw text, to avoid duplicates within each batch. Note that
+in a more realistic implementation, you'd also want to check whether the
 annotations are exactly the same.
 
 > ```ini

From c0f6e77a414f22f263a8c8616d90f068441ae61d Mon Sep 17 00:00:00 2001
From: Matthew Honnibal 
Date: Tue, 18 Aug 2020 23:29:00 +0200
Subject: [PATCH 26/92] Set version to v3.0.0a8

---
 spacy/about.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spacy/about.py b/spacy/about.py
index 5ed46bbe4..77b00eb48 100644
--- a/spacy/about.py
+++ b/spacy/about.py
@@ -1,6 +1,6 @@
 # fmt: off
 __title__ = "spacy-nightly"
-__version__ = "3.0.0a7"
+__version__ = "3.0.0a8"
 __release__ = True
 __download_url__ = "https://github.com/explosion/spacy-models/releases/download"
 __compatibility__ = "https://raw.githubusercontent.com/explosion/spacy-models/master/compatibility.json"

From 13291e97ba8bcf96394d3934a40d30fd1e00aa70 Mon Sep 17 00:00:00 2001
From: Ines Montani 
Date: Wed, 19 Aug 2020 00:28:37 +0200
Subject: [PATCH 27/92] Update docs [ci skip]

---
 website/docs/api/cli.md                       | 109 ++++++++----------
 website/docs/api/data-formats.md              |   8 +-
 website/docs/usage/embeddings-transformers.md |  12 +-
 website/docs/usage/index.md                   |  50 ++++----
 website/docs/usage/linguistic-features.md     |  15 ++-
 website/docs/usage/models.md                  |  35 +++---
 website/docs/usage/projects.md                |  36 +++---
 website/docs/usage/saving-loading.md          |  10 +-
 website/docs/usage/spacy-101.md               |   2 +-
 website/docs/usage/training.md                |  23 ++--
 website/docs/usage/v3.md                      |  28 ++---
 website/src/components/code.js                | 100 ++++++++++++++--
 website/src/components/quickstart.js          |   4 +-
 website/src/fonts/jetBrainsmono-italic.woff   | Bin 0 -> 69120 bytes
 website/src/fonts/jetbrainsmono-italic.woff2  | Bin 0 -> 54448 bytes
 website/src/styles/aside.module.sass          |   2 +-
 website/src/styles/code.module.sass           |  30 ++++-
 website/src/styles/layout.sass                |  14 +++
 18 files changed, 295 insertions(+), 183 deletions(-)
 create mode 100644 website/src/fonts/jetBrainsmono-italic.woff
 create mode 100644 website/src/fonts/jetbrainsmono-italic.woff2

diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md
index c7a1c3f06..a86c920ad 100644
--- a/website/docs/api/cli.md
+++ b/website/docs/api/cli.md
@@ -39,8 +39,8 @@ the model name to be specified with its version (e.g. `en_core_web_sm-2.2.0`).
 > to a local PyPi installation and fetching it straight from there. This will
 > also allow you to add it as a versioned package dependency to your project.
 
-```bash
-$ python -m spacy download [model] [--direct] [pip args]
+```cli
+$ python -m spacy download [model] [--direct] [pip_args]
 ```
 
 | Name                                  | Description                                                                                                                                                                                                                          |
@@ -57,11 +57,11 @@ Print information about your spaCy installation, models and local setup, and
 generate [Markdown](https://en.wikipedia.org/wiki/Markdown)-formatted markup to
 copy-paste into [GitHub issues](https://github.com/explosion/spaCy/issues).
 
-```bash
+```cli
 $ python -m spacy info [--markdown] [--silent]
 ```
 
-```bash
+```cli
 $ python -m spacy info [model] [--markdown] [--silent]
 ```
 
@@ -88,7 +88,7 @@ and command for updating are shown.
 > suite, to ensure all models are up to date before proceeding. If incompatible
 > models are found, it will return `1`.
 
-```bash
+```cli
 $ python -m spacy validate
 ```
 
@@ -111,14 +111,14 @@ config. The settings you specify will impact the suggested model architectures
 and pipeline setup, as well as the hyperparameters. You can also adjust and
 customize those settings in your config file later.
 
-> ```bash
-> ### Example {wrap="true"}
+> #### Example
+>
+> ```cli
 > $ python -m spacy init config config.cfg --lang en --pipeline ner,textcat --optimize accuracy
 > ```
 
-```bash
-$ python -m spacy init config [output_file] [--lang] [--pipeline]
-[--optimize] [--cpu]
+```cli
+$ python -m spacy init config [output_file] [--lang] [--pipeline] [--optimize] [--cpu]
 ```
 
 | Name               | Description                                                                                                                                                                                                                                                                                                                        |
@@ -143,12 +143,13 @@ be created, and their signatures are used to find the defaults. If your config
 contains a problem that can't be resolved automatically, spaCy will show you a
 validation error with more details.
 
-> ```bash
-> ### Example {wrap="true"}
+> #### Example
+>
+> ```cli
 > $ python -m spacy init fill-config base.cfg config.cfg
 > ```
 
-```bash
+```cli
 $ python -m spacy init fill-config [base_path] [output_file] [--diff]
 ```
 
@@ -175,9 +176,8 @@ The `init-model` command is now available as a subcommand of `spacy init`.
 
 
 
-```bash
-$ python -m spacy init model [lang] [output_dir] [--jsonl-loc] [--vectors-loc]
-[--prune-vectors]
+```cli
+$ python -m spacy init model [lang] [output_dir] [--jsonl-loc] [--vectors-loc] [--prune-vectors]
 ```
 
 | Name                                                    | Description                                                                                                                                                                                                                                                                         |
@@ -200,10 +200,8 @@ Convert files into spaCy's
 management functions. The converter can be specified on the command line, or
 chosen based on the file extension of the input file.
 
-```bash
-$ python -m spacy convert [input_file] [output_dir] [--converter]
-[--file-type] [--n-sents] [--seg-sents] [--model] [--morphology]
-[--merge-subtokens] [--ner-map] [--lang]
+```cli
+$ python -m spacy convert [input_file] [output_dir] [--converter] [--file-type] [--n-sents] [--seg-sents] [--model] [--morphology] [--merge-subtokens] [--ner-map] [--lang]
 ```
 
 | Name                                             | Description                                                                                                                               |
@@ -246,13 +244,13 @@ errors at once and some issues are only shown once previous errors have been
 fixed. To auto-fill a partial config and save the result, you can use the
 [`init fillconfig`](/api/cli#init-fill-config) command.
 
-```bash
+```cli
 $ python -m spacy debug config [config_path] [--code_path] [overrides]
 ```
 
 > #### Example
 >
-> ```bash
+> ```cli
 > $ python -m spacy debug config ./config.cfg
 > ```
 
@@ -298,14 +296,13 @@ takes the same arguments as `train` and reads settings off the
 
 
 
-```bash
-$ python -m spacy debug data [config_path] [--code] [--ignore-warnings]
-[--verbose] [--no-format] [overrides]
+```cli
+$ python -m spacy debug data [config_path] [--code] [--ignore-warnings] [--verbose] [--no-format] [overrides]
 ```
 
 > #### Example
 >
-> ```bash
+> ```cli
 > $ python -m spacy debug data ./config.cfg
 > ```
 
@@ -473,7 +470,7 @@ The `profile` command is now available as a subcommand of `spacy debug`.
 
 
 
-```bash
+```cli
 $ python -m spacy debug profile [model] [inputs] [--n-texts]
 ```
 
@@ -490,9 +487,8 @@ $ python -m spacy debug profile [model] [inputs] [--n-texts]
 Debug a Thinc [`Model`](https://thinc.ai/docs/api-model) by running it on a
 sample text and checking how it updates its internal weights and parameters.
 
-```bash
-$ python -m spacy debug model [config_path] [component] [--layers] [-DIM]
-[-PAR] [-GRAD] [-ATTR] [-P0] [-P1] [-P2] [P3] [--gpu-id]
+```cli
+$ python -m spacy debug model [config_path] [component] [--layers] [-DIM] [-PAR] [-GRAD] [-ATTR] [-P0] [-P1] [-P2] [P3] [--gpu-id]
 ```
 
 
@@ -502,7 +498,7 @@ model ("Step 0"), which helps us to understand the internal structure of the
 Neural Network, and to focus on specific layers that we want to inspect further
 (see next example).
 
-```bash
+```cli
 $ python -m spacy debug model ./config.cfg tagger -P0
 ```
 
@@ -548,7 +544,7 @@ an all-zero matrix determined by the `nO` and `nI` dimensions. After a first
 training step (Step 2), this matrix has clearly updated its values through the
 training feedback loop.
 
-```bash
+```cli
 $ python -m spacy debug model ./config.cfg tagger -l "5,15" -DIM -PAR -P0 -P1 -P2
 ```
 
@@ -632,7 +628,7 @@ in the section `[paths]`.
 
 
 
-```bash
+```cli
 $ python -m spacy train [config_path] [--output] [--code] [--verbose] [overrides]
 ```
 
@@ -669,9 +665,8 @@ the [data format](/api/data-formats#config) for details.
 
 
 
-```bash
-$ python -m spacy pretrain [texts_loc] [output_dir] [config_path]
-[--code] [--resume-path] [--epoch-resume] [overrides]
+```cli
+$ python -m spacy pretrain [texts_loc] [output_dir] [config_path] [--code] [--resume-path] [--epoch-resume] [overrides]
 ```
 
 | Name                    | Description                                                                                                                                                                                        |
@@ -698,9 +693,8 @@ skew. To render a sample of dependency parses in a HTML file using the
 [displaCy visualizations](/usage/visualizers), set as output directory as the
 `--displacy-path` argument.
 
-```bash
-$ python -m spacy evaluate [model] [data_path] [--output] [--gold-preproc]
-[--gpu-id] [--displacy-path] [--displacy-limit]
+```cli
+$ python -m spacy evaluate [model] [data_path] [--output] [--gold-preproc] [--gpu-id] [--displacy-path] [--displacy-limit]
 ```
 
 | Name                      | Description                                                                                                                                                               |
@@ -733,17 +727,16 @@ this, you can set the `--no-sdist` flag.
 
 
 
-```bash
-$ python -m spacy package [input_dir] [output_dir] [--meta-path] [--create-meta]
-[--no-sdist] [--version] [--force]
+```cli
+$ python -m spacy package [input_dir] [output_dir] [--meta-path] [--create-meta] [--no-sdist] [--version] [--force]
 ```
 
 > #### Example
 >
-> ```bash
-> python -m spacy package /input /output
-> cd /output/en_model-0.0.0
-> pip install dist/en_model-0.0.0.tar.gz
+> ```cli
+> $ python -m spacy package /input /output
+> $ cd /output/en_model-0.0.0
+> $ pip install dist/en_model-0.0.0.tar.gz
 > ```
 
 | Name                                             | Description                                                                                                                                                                                                     |
@@ -775,19 +768,19 @@ can provide any other repo (public or private) that you have access to using the
 
 
 
-```bash
+```cli
 $ python -m spacy project clone [name] [dest] [--repo]
 ```
 
 > #### Example
 >
-> ```bash
+> ```cli
 > $ python -m spacy project clone some_example
 > ```
 >
 > Clone from custom repo:
 >
-> ```bash
+> ```cli
 > $ python -m spacy project clone template --repo https://github.com/your_org/your_repo
 > ```
 
@@ -810,13 +803,13 @@ considered "private" and you have to take care of putting them into the
 destination directory yourself. If a local path is provided, the asset is copied
 into the current project.
 
-```bash
+```cli
 $ python -m spacy project assets [project_dir]
 ```
 
 > #### Example
 >
-> ```bash
+> ```cli
 > $ python -m spacy project assets
 > ```
 
@@ -835,13 +828,13 @@ all commands in the workflow are run, in order. If commands define
 re-run if state has changed. For example, if the input dataset changes, a
 preprocessing command that depends on those files will be re-run.
 
-```bash
+```cli
 $ python -m spacy project run [subcommand] [project_dir] [--force] [--dry]
 ```
 
 > #### Example
 >
-> ```bash
+> ```cli
 > $ python -m spacy project run train
 > ```
 
@@ -874,16 +867,16 @@ You'll also need to add the assets you want to track with
 
 
 
-```bash
+```cli
 $ python -m spacy project dvc [project_dir] [workflow] [--force] [--verbose]
 ```
 
 > #### Example
 >
-> ```bash
-> git init
-> dvc init
-> python -m spacy project dvc all
+> ```cli
+> $ git init
+> $ dvc init
+> $ python -m spacy project dvc all
 > ```
 
 | Name              | Description                                                                                                       |
diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md
index 56528de43..701e16b1e 100644
--- a/website/docs/api/data-formats.md
+++ b/website/docs/api/data-formats.md
@@ -118,8 +118,8 @@ need paths, you can define them here. All config values can also be
 [`spacy train`](/api/cli#train), which is especially relevant for data paths
 that you don't want to hard-code in your config file.
 
-```bash
-$ python -m spacy train ./config.cfg --paths.train ./corpus/train.spacy
+```cli
+$ python -m spacy train config.cfg --paths.train ./corpus/train.spacy
 ```
 
 ### training {#config-training tag="section"}
@@ -209,8 +209,8 @@ objects to JSON, you can now serialize them directly using the
 [`spacy convert`](/api/cli) lets you convert your JSON data to the new `.spacy`
 format:
 
-```bash
-$ python -m spacy convert ./data.json ./output
+```cli
+$ python -m spacy convert ./data.json ./output.spacy
 ```
 
 
diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md
index df9e68282..5a3189ecb 100644
--- a/website/docs/usage/embeddings-transformers.md
+++ b/website/docs/usage/embeddings-transformers.md
@@ -110,9 +110,9 @@ in `/opt/nvidia/cuda`, you would run:
 
 ```bash
 ### Installation with CUDA
-export CUDA_PATH="/opt/nvidia/cuda"
-pip install cupy-cuda102
-pip install spacy-transformers
+$ export CUDA_PATH="/opt/nvidia/cuda"
+$ pip install cupy-cuda102
+$ pip install spacy-transformers
 ```
 
 ### Runtime usage {#transformers-runtime}
@@ -130,7 +130,7 @@ The `Transformer` component sets the
 [`Doc._.trf_data`](/api/transformer#custom_attributes) extension attribute,
 which lets you access the transformers outputs at runtime.
 
-```bash
+```cli
 $ python -m spacy download en_core_trf_lg
 ```
 
@@ -292,8 +292,8 @@ function. You can make it available via the `--code` argument that can point to
 a Python file. For more details on training with custom code, see the
 [training documentation](/usage/training#custom-code).
 
-```bash
-$ python -m spacy train ./config.cfg --code ./code.py
+```cli
+python -m spacy train ./config.cfg --code ./code.py
 ```
 
 ### Customizing the model implementations {#training-custom-model}
diff --git a/website/docs/usage/index.md b/website/docs/usage/index.md
index bda9f76d6..90e02aef7 100644
--- a/website/docs/usage/index.md
+++ b/website/docs/usage/index.md
@@ -40,7 +40,7 @@ $ pip install -U spacy
 > After installation you need to download a language model. For more info and
 > available models, see the [docs on models](/models).
 >
-> ```bash
+> ```cli
 > $ python -m spacy download en_core_web_sm
 >
 > >>> import spacy
@@ -62,9 +62,9 @@ When using pip it is generally recommended to install packages in a virtual
 environment to avoid modifying system state:
 
 ```bash
-python -m venv .env
-source .env/bin/activate
-pip install spacy
+$ python -m venv .env
+$ source .env/bin/activate
+$ pip install spacy
 ```
 
 ### conda {#conda}
@@ -106,9 +106,9 @@ links created in different virtual environments. It's recommended to run the
 command with `python -m` to make sure you're executing the correct version of
 spaCy.
 
-```bash
-pip install -U spacy
-python -m spacy validate
+```cli
+$ pip install -U spacy
+$ python -m spacy validate
 ```
 
 ### Run spaCy with GPU {#gpu new="2.0.14"}
@@ -156,15 +156,15 @@ system. See notes on [Ubuntu](#source-ubuntu), [macOS / OS X](#source-osx) and
 [Windows](#source-windows) for details.
 
 ```bash
-python -m pip install -U pip                   # update pip
-git clone https://github.com/explosion/spaCy   # clone spaCy
-cd spaCy                                       # navigate into directory
+$ python -m pip install -U pip                  # update pip
+$ git clone https://github.com/explosion/spaCy  # clone spaCy
+$ cd spaCy                                      # navigate into dir
 
-python -m venv .env                            # create environment in .env
-source .env/bin/activate                       # activate virtual environment
-\export PYTHONPATH=`pwd`                        # set Python path to spaCy directory
-pip install -r requirements.txt                # install all requirements
-python setup.py build_ext --inplace            # compile spaCy
+$ python -m venv .env                           # create environment in .env
+$ source .env/bin/activate                      # activate virtual env
+$ export PYTHONPATH=`pwd`                       # set Python path to spaCy dir
+$ pip install -r requirements.txt               # install all requirements
+$ python setup.py build_ext --inplace           # compile spaCy
 ```
 
 Compared to regular install via pip, the
@@ -209,20 +209,18 @@ that directory. Don't forget to also install the test utilities via spaCy's
 [`requirements.txt`](https://github.com/explosion/spaCy/tree/master/requirements.txt):
 
 ```bash
-python -c "import os; import spacy; print(os.path.dirname(spacy.__file__))"
-pip install -r path/to/requirements.txt
-python -m pytest [spacy directory]
+$ python -c "import os; import spacy; print(os.path.dirname(spacy.__file__))"
+$ pip install -r path/to/requirements.txt
+$ python -m pytest [spacy directory]
 ```
 
 Calling `pytest` on the spaCy directory will run only the basic tests. The flag
 `--slow` is optional and enables additional tests that take longer.
 
 ```bash
-# make sure you are using recent pytest version
-python -m pip install -U pytest
-
-python -m pytest [spacy directory]                 # basic tests
-python -m pytest [spacy directory] --slow          # basic and slow tests
+$ python -m pip install -U pytest               # update pytest
+$ python -m pytest [spacy directory]            # basic tests
+$ python -m pytest [spacy directory] --slow     # basic and slow tests
 ```
 
 ## Troubleshooting guide {#troubleshooting}
@@ -283,7 +281,7 @@ only 65535 in a narrow unicode build. You can check this by running the
 following command:
 
 ```bash
-python -c "import sys; print(sys.maxunicode)"
+$ python -c "import sys; print(sys.maxunicode)"
 ```
 
 If you're running a narrow unicode build, reinstall Python and use a wide
@@ -305,8 +303,8 @@ run `source ~/.bash_profile` or `source ~/.zshrc`. Make sure to add **both
 lines** for `LC_ALL` and `LANG`.
 
 ```bash
-\export LC_ALL=en_US.UTF-8
-\export LANG=en_US.UTF-8
+$ export LC_ALL=en_US.UTF-8
+$ export LANG=en_US.UTF-8
 ```
 
 
diff --git a/website/docs/usage/linguistic-features.md b/website/docs/usage/linguistic-features.md
index 325063e58..10efcf875 100644
--- a/website/docs/usage/linguistic-features.md
+++ b/website/docs/usage/linguistic-features.md
@@ -1588,9 +1588,9 @@ some nice Latin vectors. You can then pass the directory path to
 > doc1.similarity(doc2)
 > ```
 
-```bash
-wget https://s3-us-west-1.amazonaws.com/fasttext-vectors/word-vectors-v2/cc.la.300.vec.gz
-python -m spacy init model en /tmp/la_vectors_wiki_lg --vectors-loc cc.la.300.vec.gz
+```cli
+$ wget https://s3-us-west-1.amazonaws.com/fasttext-vectors/word-vectors-v2/cc.la.300.vec.gz
+$ python -m spacy init model en /tmp/la_vectors_wiki_lg --vectors-loc cc.la.300.vec.gz
 ```
 
 
@@ -1649,8 +1649,8 @@ the vector of "leaving", which is identical. If you're using the
 option to easily reduce the size of the vectors as you add them to a spaCy
 model:
 
-```bash
-$ python -m spacy init model /tmp/la_vectors_web_md --vectors-loc la.300d.vec.tgz --prune-vectors 10000
+```cli
+$ python -m spacy init model en /tmp/la_vectors_web_md --vectors-loc la.300d.vec.tgz --prune-vectors 10000
 ```
 
 This will create a spaCy model with vectors for the first 10,000 words in the
@@ -1741,9 +1741,8 @@ language name, and even train models with it and refer to it in your
 > needs to be available during training. You can load a Python file containing
 > the code using the `--code` argument:
 >
-> ```bash
-> ### {wrap="true"}
-> $ python -m spacy train config.cfg --code code.py
+> ```cli
+> python -m spacy train config.cfg --code code.py
 > ```
 
 ```python
diff --git a/website/docs/usage/models.md b/website/docs/usage/models.md
index be98cd36c..ec0e02297 100644
--- a/website/docs/usage/models.md
+++ b/website/docs/usage/models.md
@@ -116,15 +116,10 @@ The Chinese language class supports three word segmentation options:
 
 
 
-In spaCy v3, the default Chinese word segmenter has switched from Jieba to
-character segmentation.
-
-
-
-
-
-Note that [`pkuseg`](https://github.com/lancopku/pkuseg-python) doesn't yet ship
-with pre-compiled wheels for Python 3.8. If you're running Python 3.8, you can
+In spaCy v3.0, the default Chinese word segmenter has switched from Jieba to
+character segmentation. Also note that
+[`pkuseg`](https://github.com/lancopku/pkuseg-python) doesn't yet ship with
+pre-compiled wheels for Python 3.8. If you're running Python 3.8, you can
 install it from our fork and compile it locally:
 
 ```bash
@@ -174,7 +169,7 @@ nlp.tokenizer.pkuseg_update_user_dict([], reset=True)
 
 
 
-
+
 
 The [Chinese models](/models/zh) provided by spaCy include a custom `pkuseg`
 model trained only on
@@ -247,20 +242,20 @@ best-matching model compatible with your spaCy installation.
 > + nlp = spacy.load("en_core_web_sm")
 > ```
 
-```bash
-# Download best-matching version of specific model for your spaCy installation
-python -m spacy download en_core_web_sm
+```cli
+# Download best-matching version of a model for your spaCy installation
+$ python -m spacy download en_core_web_sm
 
 # Download exact model version
-python -m spacy download en_core_web_sm-2.2.0 --direct
+$ python -m spacy download en_core_web_sm-3.0.0 --direct
 ```
 
 The download command will [install the model](/usage/models#download-pip) via
 pip and place the package in your `site-packages` directory.
 
-```bash
-pip install spacy
-python -m spacy download en_core_web_sm
+```cli
+$ pip install -U spacy
+$ python -m spacy download en_core_web_sm
 ```
 
 ```python
@@ -279,10 +274,10 @@ click on the archive link and copy it to your clipboard.
 
 ```bash
 # With external URL
-pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.0.0/en_core_web_sm-3.0.0.tar.gz
+$ pip install https://github.com/explosion/spacy-models/releases/download/en_core_web_sm-3.0.0/en_core_web_sm-3.0.0.tar.gz
 
 # With local file
-pip install /Users/you/en_core_web_sm-3.0.0.tar.gz
+$ pip install /Users/you/en_core_web_sm-3.0.0.tar.gz
 ```
 
 By default, this will install the model into your `site-packages` directory. You
@@ -305,7 +300,7 @@ archive consists of a model directory that contains another directory with the
 model data.
 
 ```yaml
-### Directory structure {highlight="7"}
+### Directory structure {highlight="6"}
 └── en_core_web_md-3.0.0.tar.gz       # downloaded archive
     ├── setup.py                      # setup file for pip installation
     ├── meta.json                     # copy of model meta
diff --git a/website/docs/usage/projects.md b/website/docs/usage/projects.md
index ccf8ec49f..ab8101477 100644
--- a/website/docs/usage/projects.md
+++ b/website/docs/usage/projects.md
@@ -67,8 +67,8 @@ project template and copies the files to a local directory. You can then run the
 project, e.g. to train a model and edit the commands and scripts to build fully
 custom workflows.
 
-```bash
-$ python -m spacy clone some_example_project
+```cli
+python -m spacy project clone some_example_project
 ```
 
 By default, the project will be cloned into the current working directory. You
@@ -95,9 +95,9 @@ to download and where to put them. The
 [`spacy project assets`](/api/cli#project-assets) will fetch the project assets
 for you:
 
-```bash
-cd some_example_project
-python -m spacy project assets
+```
+$ cd some_example_project
+$ python -m spacy project assets
 ```
 
 ### 3. Run a command {#run}
@@ -123,7 +123,7 @@ Commands consist of one or more steps and can be run with
 [`spacy project run`](/api/cli#project-run). The following will run the command
 `preprocess` defined in the `project.yml`:
 
-```bash
+```cli
 $ python -m spacy project run preprocess
 ```
 
@@ -156,7 +156,7 @@ to turn the best model artifact into an installable Python package. The
 following command run the workflow named `all` defined in the `project.yml`, and
 execute the commands it specifies, in order:
 
-```bash
+```cli
 $ python -m spacy project run all
 ```
 
@@ -379,8 +379,8 @@ The [`spacy project clone`](/api/cli#project-clone) command lets you customize
 the repo to clone from using the `--repo` option. It calls into `git`, so you'll
 be able to clone from any repo that you have access to, including private repos.
 
-```bash
-$ python -m spacy project your_project --repo https://github.com/you/repo
+```cli
+python -m spacy project clone your_project --repo https://github.com/you/repo
 ```
 
 At a minimum, a valid project template needs to contain a
@@ -445,9 +445,9 @@ to include support for remote storage like Google Cloud Storage, S3, Azure, SSH
 and more.
 
 ```bash
-pip install dvc   # Install DVC
-git init          # Initialize a Git repo
-dvc init          # Initialize a DVC project
+$ pip install dvc   # Install DVC
+$ git init          # Initialize a Git repo
+$ dvc init          # Initialize a DVC project
 ```
 
 
@@ -466,8 +466,8 @@ can then manage your spaCy project like any other DVC project, run
 and [`dvc repro`](https://dvc.org/doc/command-reference/repro) to reproduce the
 workflow or individual commands.
 
-```bash
-$ python -m spacy project dvc [workflow name]
+```cli
+$ python -m spacy project dvc [workflow_name]
 ```
 
 
@@ -508,7 +508,7 @@ and evaluation set.
 
 > #### Example usage
 >
-> ```bash
+> ```cli
 > $ python -m spacy project run annotate
 > ```
 
@@ -595,7 +595,7 @@ spacy_streamlit.visualize(MODELS, DEFAULT_TEXT, visualizers=["ner"])
 
 > #### Example usage
 >
-> ```bash
+> ```cli
 > $ python -m spacy project run visualize
 > ```
 
@@ -636,8 +636,8 @@ API.
 
 > #### Example usage
 >
-> ```bash
-> $ python -m spacy project run visualize
+> ```cli
+> $ python -m spacy project run serve
 > ```
 
 
diff --git a/website/docs/usage/saving-loading.md b/website/docs/usage/saving-loading.md
index f8bb1bfa9..5fb8fc98b 100644
--- a/website/docs/usage/saving-loading.md
+++ b/website/docs/usage/saving-loading.md
@@ -562,11 +562,11 @@ import DisplaCyEntSnekHtml from 'images/displacy-ent-snek.html'
 ## Saving, loading and distributing models {#models}
 
 After training your model, you'll usually want to save its state, and load it
-back later. You can do this with the
-[`Language.to_disk()`](/api/language#to_disk) method:
+back later. You can do this with the [`Language.to_disk`](/api/language#to_disk)
+method:
 
 ```python
-nlp.to_disk('/home/me/data/en_example_model')
+nlp.to_disk("./en_example_model")
 ```
 
 The directory will be created if it doesn't exist, and the whole pipeline data,
@@ -629,8 +629,8 @@ docs.
 > }
 > ```
 
-```bash
-$ python -m spacy package /home/me/data/en_example_model /home/me/my_models
+```cli
+$ python -m spacy package ./en_example_model ./my_models
 ```
 
 This command will create a model package directory and will run
diff --git a/website/docs/usage/spacy-101.md b/website/docs/usage/spacy-101.md
index df08e0320..8ea6a6ca0 100644
--- a/website/docs/usage/spacy-101.md
+++ b/website/docs/usage/spacy-101.md
@@ -160,7 +160,7 @@ the website or company in a specific context.
 
 > #### Loading models
 >
-> ```bash
+> ```cli
 > $ python -m spacy download en_core_web_sm
 >
 > >>> import spacy
diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md
index 31ba902b0..6829d38c0 100644
--- a/website/docs/usage/training.md
+++ b/website/docs/usage/training.md
@@ -66,7 +66,7 @@ the [`init fill-config`](/api/cli#init-fill-config) command to fill in the
 remaining defaults. Training configs should always be **complete and without
 hidden defaults**, to keep your experiments reproducible.
 
-```bash
+```cli
 $ python -m spacy init fill-config base_config.cfg config.cfg
 ```
 
@@ -76,8 +76,8 @@ $ python -m spacy init fill-config base_config.cfg config.cfg
 > your training and development data, get useful stats, and find problems like
 > invalid entity annotations, cyclic dependencies, low data labels and more.
 >
-> ```bash
-> $ python -m spacy debug data config.cfg --verbose
+> ```cli
+> $ python -m spacy debug data config.cfg
 > ```
 
 Instead of exporting your starter config from the quickstart widget and
@@ -88,7 +88,7 @@ add your data and run [`train`](/api/cli#train) with your config. See the
 spaCy's binary `.spacy` format. You can either include the data paths in the
 `[paths]` section of your config, or pass them in via the command line.
 
-```bash
+```cli
 $ python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy
 ```
 
@@ -186,9 +186,8 @@ For cases like this, you can set additional command-line options starting with
 `--paths.train ./corpus/train.spacy` sets the `train` value in the `[paths]`
 block.
 
-```bash
-$ python -m spacy train config.cfg --paths.train ./corpus/train.spacy
---paths.dev ./corpus/dev.spacy --training.batch_size 128
+```cli
+$ python -m spacy train config.cfg --paths.train ./corpus/train.spacy --paths.dev ./corpus/dev.spacy --training.batch_size 128
 ```
 
 Only existing sections and values in the config can be overwritten. At the end
@@ -486,8 +485,9 @@ still look good.
 
 ### Training with custom code {#custom-code}
 
-> ```bash
-> ### Example {wrap="true"}
+> #### Example
+>
+> ```cli
 > $ python -m spacy train config.cfg --code functions.py
 > ```
 
@@ -605,9 +605,8 @@ you can now run [`spacy train`](/api/cli#train) and point the argument `--code`
 to your Python file. Before loading the config, spaCy will import the
 `functions.py` module and your custom functions will be registered.
 
-```bash
-### Training with custom code {wrap="true"}
-python -m spacy train config.cfg --output ./output --code ./functions.py
+```cli
+$ python -m spacy train config.cfg --output ./output --code ./functions.py
 ```
 
 #### Example: Custom batch size schedule {#custom-code-schedule}
diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md
index ee90b6cc5..47110609e 100644
--- a/website/docs/usage/v3.md
+++ b/website/docs/usage/v3.md
@@ -212,14 +212,15 @@ Note that spaCy v3.0 now requires **Python 3.6+**.
 
 ### Removed or renamed API {#incompat-removed}
 
-| Removed                                                  | Replacement                                           |
-| -------------------------------------------------------- | ----------------------------------------------------- |
-| `Language.disable_pipes`                                 | [`Language.select_pipes`](/api/language#select_pipes) |
-| `GoldParse`                                              | [`Example`](/api/example)                             |
-| `GoldCorpus`                                             | [`Corpus`](/api/corpus)                               |
-| `spacy debug-data`                                       | [`spacy debug data`](/api/cli#debug-data)             |
-| `spacy profile`                                          | [`spacy debug profile`](/api/cli#debug-profile)       |
-| `spacy link`, `util.set_data_path`, `util.get_data_path` | not needed, model symlinks are deprecated             |
+| Removed                                                | Replacement                                                                               |
+| ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
+| `Language.disable_pipes`                               | [`Language.select_pipes`](/api/language#select_pipes)                                     |
+| `GoldParse`                                            | [`Example`](/api/example)                                                                 |
+| `GoldCorpus`                                           | [`Corpus`](/api/corpus)                                                                   |
+| `KnowledgeBase.load_bulk` `KnowledgeBase.dump`         | [`KnowledgeBase.from_disk`](/api/kb#from_disk) [`KnowledgeBase.to_disk`](/api/kb#to_disk) |
+| `spacy debug-data`                                     | [`spacy debug data`](/api/cli#debug-data)                                                 |
+| `spacy profile`                                        | [`spacy debug profile`](/api/cli#debug-profile)                                           |
+| `spacy link` `util.set_data_path` `util.get_data_path` | not needed, model symlinks are deprecated                                                 |
 
 The following deprecated methods, attributes and arguments were removed in v3.0.
 Most of them have been **deprecated for a while** and many would previously
@@ -412,12 +413,11 @@ spaCy v3.0 uses a new
 serializing a [`DocBin`](/api/docbin), which represents a collection of `Doc`
 objects. This means that you can train spaCy models using the same format it
 outputs: annotated `Doc` objects. The binary format is extremely **efficient in
-storage**, especially when packing multiple documents together.
+storage**, especially when packing multiple documents together. You can convert
+your existing JSON-formatted data using the [`spacy convert`](/api/cli#convert)
+command, which outputs `.spacy` files:
 
-You can convert your existing JSON-formatted data using the
-[`spacy convert`](/api/cli#convert) command, which outputs `.spacy` files:
-
-```bash
+```cli
 $ python -m spacy convert ./training.json ./output
 ```
 
@@ -429,7 +429,7 @@ The easiest way to get started with a training config is to use the
 requirements, and it will auto-generate a starter config with the best-matching
 default settings.
 
-```bash
+```cli
 $ python -m spacy init config ./config.cfg --lang en --pipeline tagger,parser
 ```
 
diff --git a/website/src/components/code.js b/website/src/components/code.js
index 0d1d214ae..740544f43 100644
--- a/website/src/components/code.js
+++ b/website/src/components/code.js
@@ -8,7 +8,7 @@ import { window } from 'browser-monads'
 
 import CUSTOM_TYPES from '../../meta/type-annotations.json'
 import { isString, htmlToReact } from './util'
-import Link from './link'
+import Link, { OptionalLink } from './link'
 import GitHubCode from './github'
 import classes from '../styles/code.module.sass'
 
@@ -89,6 +89,91 @@ export const TypeAnnotation = ({ lang = 'python', link = true, children }) => {
     )
 }
 
+function replacePrompt(line, prompt, isFirst = false) {
+    let result = line
+    const hasPrompt = result.startsWith(`${prompt} `)
+    const showPrompt = hasPrompt || isFirst
+    if (hasPrompt) result = result.slice(2)
+    return result && showPrompt ? `${result}` : result
+}
+
+function parseArgs(raw) {
+    const commandGroups = ['init', 'debug', 'project']
+    let args = raw.split(' ').filter(arg => arg)
+    const result = {}
+    while (args.length) {
+        let opt = args.shift()
+        if (opt.length > 1 && opt.startsWith('-')) {
+            const isFlag = !args.length || (args[0].length > 1 && args[0].startsWith('-'))
+            result[opt] = isFlag ? true : args.shift()
+        } else {
+            const key = commandGroups.includes(opt) ? `${opt} ${args.shift()}` : opt
+            result[key] = null
+        }
+    }
+    return result
+}
+
+function formatCode(html, lang, prompt) {
+    if (lang === 'cli') {
+        const cliRegex = /^(\$ )?python -m spacy/
+        const lines = html
+            .trim()
+            .split('\n')
+            .map((line, i) => {
+                if (cliRegex.test(line)) {
+                    const text = line.replace(cliRegex, '')
+                    const args = parseArgs(text)
+                    const cmd = Object.keys(args).map((key, i) => {
+                        const value = args[key]
+                        return value === null || value === true || i === 0 ? key : `${key} ${value}`
+                    })
+                    return (
+                        
+                            
+                                python -m
+                            {' '}
+                            spacy{' '}
+                            {cmd.map((item, j) => {
+                                const isCmd = j === 0
+                                const url = isCmd ? `/api/cli#${item.replace(' ', '-')}` : null
+                                const isAbstract = isString(item) && /^\[(.+)\]$/.test(item)
+                                const itemClassNames = classNames(classes.cliArg, {
+                                    [classes.cliArgHighlight]: isCmd,
+                                    [classes.cliArgEmphasis]: isAbstract,
+                                })
+                                const text = isAbstract ? item.slice(1, -1) : item
+                                return (
+                                    
+                                        {j !== 0 && ' '}
+                                        
+                                            
+                                        
+                                    
+                                )
+                            })}
+                        
+                    )
+                }
+                const htmlLine = replacePrompt(highlightCode('bash', line), '$')
+                return htmlToReact(htmlLine)
+            })
+        return lines.map((line, i) => (
+            
+                {i !== 0 && 
} + {line} +
+ )) + } + const result = html + .split('\n') + .map((line, i) => (prompt ? replacePrompt(line, prompt, i === 0) : line)) + .join('\n') + return htmlToReact(result) +} + export class Code extends React.Component { state = { Juniper: null } @@ -136,7 +221,8 @@ export class Code extends React.Component { children, } = this.props const codeClassNames = classNames(classes.code, className, `language-${lang}`, { - [classes.wrap]: !!highlight || !!wrap, + [classes.wrap]: !!highlight || !!wrap || lang === 'cli', + [classes.cli]: lang === 'cli', }) const ghClassNames = classNames(codeClassNames, classes.maxHeight) const { Juniper } = this.state @@ -154,14 +240,14 @@ export class Code extends React.Component { const codeText = Array.isArray(children) ? children.join('') : children || '' const highlightRange = highlight ? rangeParser.parse(highlight).filter(n => n > 0) : [] - const html = lang === 'none' ? codeText : highlightCode(lang, codeText, highlightRange) - + const rawHtml = ['none', 'cli'].includes(lang) + ? codeText + : highlightCode(lang, codeText, highlightRange) + const html = formatCode(rawHtml, lang, prompt) return ( <> {title &&

{title}

} - - {htmlToReact(html)} - + {html} ) } diff --git a/website/src/components/quickstart.js b/website/src/components/quickstart.js index f7ab11fa4..6a335d4a0 100644 --- a/website/src/components/quickstart.js +++ b/website/src/components/quickstart.js @@ -117,7 +117,7 @@ const Quickstart = ({ {help && ( {' '} - + )} @@ -201,7 +201,7 @@ const Quickstart = ({ className={classes.help} > {' '} - + )} diff --git a/website/src/fonts/jetBrainsmono-italic.woff b/website/src/fonts/jetBrainsmono-italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..f3ddf4db5f385bfbd08e7a745e51faefa54345a4 GIT binary patch literal 69120 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 literal 0 HcmV?d00001 diff --git a/website/src/fonts/jetbrainsmono-italic.woff2 b/website/src/fonts/jetbrainsmono-italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..828c42961422928231579cb0f3271791a4e20ce4 GIT binary patch literal 54448 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 d$A-=3xg8AMqVJPcKm5VYKUw)(N$&sv008!WK Date: Wed, 19 Aug 2020 12:14:41 +0200 Subject: [PATCH 28/92] Update docs [ci skip] --- website/docs/usage/embeddings-transformers.md | 2 + website/docs/usage/projects.md | 2 + website/docs/usage/training.md | 116 ++++++++++++------ website/docs/usage/v3.md | 4 + website/src/styles/layout.sass | 2 +- 5 files changed, 88 insertions(+), 38 deletions(-) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index 5a3189ecb..e097ae02a 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -179,6 +179,7 @@ of objects by referring to creation functions, including functions you register yourself. For details on how to get started with training your own model, check out the [training quickstart](/usage/training#quickstart). + The `[components]` section in the [`config.cfg`](/api/data-formats#config) describes the pipeline components and the settings used to construct them, diff --git a/website/docs/usage/projects.md b/website/docs/usage/projects.md index ab8101477..61367fb0e 100644 --- a/website/docs/usage/projects.md +++ b/website/docs/usage/projects.md @@ -33,6 +33,7 @@ and prototypes and ship your models into production. + spaCy projects make it easy to integrate with many other **awesome tools** in the data science and machine learning ecosystem to track and manage your data diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 30537b8d8..7ce9457f9 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -92,6 +92,7 @@ spaCy's binary `.spacy` format. You can either include the data paths in the $ python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy ``` + ## Training config {#config} @@ -656,32 +658,74 @@ factor = 1.005 #### Example: Custom data reading and batching {#custom-code-readers-batchers} -Some use-cases require streaming in data or manipulating datasets on the fly, -rather than generating all data beforehand and storing it to file. Instead of -using the built-in reader `"spacy.Corpus.v1"`, which uses static file paths, you -can create and register a custom function that generates +Some use-cases require **streaming in data** or manipulating datasets on the +fly, rather than generating all data beforehand and storing it to file. Instead +of using the built-in [`Corpus`](/api/corpus) reader, which uses static file +paths, you can create and register a custom function that generates [`Example`](/api/example) objects. The resulting generator can be infinite. When using this dataset for training, stopping criteria such as maximum number of steps, or stopping when the loss does not decrease further, can be used. -In this example we assume a custom function `read_custom_data()` which loads or -generates texts with relevant textcat annotations. Then, small lexical -variations of the input text are created before generating the final `Example` -objects. - -We can also customize the batching strategy by registering a new "batcher" which -turns a stream of items into a stream of batches. spaCy has several useful -built-in batching strategies with customizable sizes, but -it's also easy to implement your own. For instance, the following function takes -the stream of generated `Example` objects, and removes those which have the -exact same underlying raw text, to avoid duplicates within each batch. Note that -in a more realistic implementation, you'd also want to check whether the -annotations are exactly the same. +In this example we assume a custom function `read_custom_data` which loads or +generates texts with relevant text classification annotations. Then, small +lexical variations of the input text are created before generating the final +[`Example`](/api/example) objects. The `@spacy.registry.readers` decorator lets +you register the function creating the custom reader in the `readers` +[registry](/api/top-level#registry) and assign it a string name, so it can be +used in your config. All arguments on the registered function become available +as **config settings** – in this case, `source`. +> #### config.cfg +> > ```ini > [training.train_corpus] > @readers = "corpus_variants.v1" +> source = "s3://your_bucket/path/data.csv" +> ``` + +```python +### functions.py {highlight="7-8"} +from typing import Callable, Iterator, List +import spacy +from spacy.gold import Example +from spacy.language import Language +import random + +@spacy.registry.readers("corpus_variants.v1") +def stream_data(source: str) -> Callable[[Language], Iterator[Example]]: + def generate_stream(nlp): + for text, cats in read_custom_data(source): + # Create a random variant of the example text + i = random.randint(0, len(text) - 1) + variant = text[:i] + text[i].upper() + text[i + 1:] + doc = nlp.make_doc(variant) + example = Example.from_dict(doc, {"cats": cats}) + yield example + + return generate_stream +``` + + + +Remember that a registered function should always be a function that spaCy +**calls to create something**. In this case, it **creates the reader function** +– it's not the reader itself. + + + +We can also customize the **batching strategy** by registering a new batcher +function in the `batchers` [registry](/api/top-level#registry). A batcher turns +a stream of items into a stream of batches. spaCy has several useful built-in +[batching strategies](/api/top-level#batchers) with customizable sizes, but it's +also easy to implement your own. For instance, the following function takes the +stream of generated [`Example`](/api/example) objects, and removes those which +have the exact same underlying raw text, to avoid duplicates within each batch. +Note that in a more realistic implementation, you'd also want to check whether +the annotations are exactly the same. + +> #### config.cfg > +> ```ini > [training.batcher] > @batchers = "filtering_batch.v1" > size = 150 @@ -689,39 +733,26 @@ annotations are exactly the same. ```python ### functions.py -from typing import Callable, Iterable, List +from typing import Callable, Iterable, Iterator import spacy from spacy.gold import Example -import random - -@spacy.registry.readers("corpus_variants.v1") -def stream_data() -> Callable[["Language"], Iterable[Example]]: - def generate_stream(nlp): - for text, cats in read_custom_data(): - random_index = random.randint(0, len(text) - 1) - variant = text[:random_index] + text[random_index].upper() + text[random_index + 1:] - doc = nlp.make_doc(variant) - example = Example.from_dict(doc, {"cats": cats}) - yield example - return generate_stream - @spacy.registry.batchers("filtering_batch.v1") -def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterable[List[Example]]]: - def create_filtered_batches(examples: Iterable[Example]) -> Iterable[List[Example]]: +def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterator[List[Example]]]: + def create_filtered_batches(examples): batch = [] for eg in examples: + # Remove duplicate examples with the same text from batch if eg.text not in [x.text for x in batch]: batch.append(eg) if len(batch) == size: yield batch batch = [] + return create_filtered_batches ``` -### Wrapping PyTorch and TensorFlow {#custom-frameworks} - - + + ### Defining custom architectures {#custom-architectures} + ## Transfer learning {#transfer-learning} + + ### Using transformer models like BERT {#transformers} spaCy v3.0 lets you use almost any statistical model to power your pipeline. You @@ -748,6 +784,8 @@ do the required plumbing. It also provides a pipeline component, [`Transformer`](/api/transformer), that lets you do multi-task learning and lets you save the transformer outputs for later use. + For more details on how to integrate transformer models into your training config and customize the implementations, see the usage guide on @@ -766,7 +805,8 @@ config and customize the implementations, see the usage guide on ## Parallel Training with Ray {#parallel-training} - + + ## Internal training API {#api} diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index 47110609e..ffed1c89f 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -444,6 +444,8 @@ values. You can then use the auto-generated `config.cfg` for training: + python -m spacy train ./config.cfg --output ./output ``` + + #### Training via the Python API {#migrating-training-python} For most use cases, you **shouldn't** have to write your own training scripts diff --git a/website/src/styles/layout.sass b/website/src/styles/layout.sass index 03011bf4e..775523190 100644 --- a/website/src/styles/layout.sass +++ b/website/src/styles/layout.sass @@ -396,7 +396,7 @@ body [id]:target margin-right: -1.5em margin-left: -1.5em padding-right: 1.5em - padding-left: 1.25em + padding-left: 1.2em &:empty:before // Fix issue where empty lines would disappear From 225f8866a19df151fed5dbae612780a09233be3a Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 12:47:57 +0200 Subject: [PATCH 29/92] Fix consistency --- website/docs/api/transformer.md | 8 ++++---- website/docs/usage/embeddings-transformers.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/docs/api/transformer.md b/website/docs/api/transformer.md index d4d7de161..c32651e02 100644 --- a/website/docs/api/transformer.md +++ b/website/docs/api/transformer.md @@ -527,7 +527,7 @@ You can register custom annotation setters using the > #### Example > > ```python -> @registry.annotation_setters("spacy-transformer.null_annotation_setter.v1") +> @registry.annotation_setters("spacy-transformers.null_annotation_setter.v1") > def configure_null_annotation_setter() -> Callable: > def setter(docs: List[Doc], trf_data: FullTransformerBatch) -> None: > pass @@ -542,9 +542,9 @@ You can register custom annotation setters using the The following built-in functions are available: -| Name | Description | -| --------------------------------------------- | ------------------------------------- | -| `spacy-transformer.null_annotation_setter.v1` | Don't set any additional annotations. | +| Name | Description | +| ---------------------------------------------- | ------------------------------------- | +| `spacy-transformers.null_annotation_setter.v1` | Don't set any additional annotations. | ## Custom attributes {#custom-attributes} diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index e097ae02a..c2727f5b1 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -230,7 +230,7 @@ tokenizer_config = {"use_fast": true} @span_getters = "doc_spans.v1" [components.transformer.annotation_setter] -@annotation_setters = "spacy-transformer.null_annotation_setter.v1" +@annotation_setters = "spacy-transformers.null_annotation_setter.v1" ``` From e2f2ef3a5a7d5113c34c44205bf4ac3263b0dd2a Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 13:33:15 +0200 Subject: [PATCH 30/92] Update init config and recommendations - As much as I dislike YAML, it seemed like a better format here because it allows us to add comments if we want to explain the different recommendations - Don't include the generated JS in the repo by default and build it on the fly when running or deploying the site. This ensures it's always up to date. - Simplify jinja_to_js script and use fewer dependencies --- .gitignore | 1 + spacy/cli/init_config.py | 34 ++---- .../quickstart_training_recommendations.json | 13 --- .../quickstart_training_recommendations.yml | 103 ++++++++++++++++++ spacy/schemas.py | 19 ++++ spacy/tests/test_cli.py | 9 +- website/package.json | 2 +- website/setup/jinja_to_js.py | 38 ++++--- website/setup/requirements.txt | 2 +- website/setup/setup.sh | 2 +- .../widgets/quickstart-training-generator.js | 2 +- website/src/widgets/quickstart-training.js | 12 +- 12 files changed, 166 insertions(+), 71 deletions(-) delete mode 100644 spacy/cli/templates/quickstart_training_recommendations.json create mode 100644 spacy/cli/templates/quickstart_training_recommendations.yml diff --git a/.gitignore b/.gitignore index 087163761..136a8f26d 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ website/logs npm-debug.log* website/www/ website/_deploy.sh +quickstart-training-generator.js # Cython / C extensions cythonize.json diff --git a/spacy/cli/init_config.py b/spacy/cli/init_config.py index 7d80eb289..9b47dea14 100644 --- a/spacy/cli/init_config.py +++ b/spacy/cli/init_config.py @@ -3,17 +3,17 @@ from enum import Enum from pathlib import Path from wasabi import Printer, diff_strings from thinc.api import Config -from pydantic import BaseModel import srsly import re from .. import util +from ..schemas import RecommendationSchema from ._util import init_cli, Arg, Opt, show_validation_error, COMMAND -TEMPLATE_ROOT = Path(__file__).parent / "templates" -TEMPLATE_PATH = TEMPLATE_ROOT / "quickstart_training.jinja" -RECOMMENDATIONS_PATH = TEMPLATE_ROOT / "quickstart_training_recommendations.json" +ROOT = Path(__file__).parent / "templates" +TEMPLATE_PATH = ROOT / "quickstart_training.jinja" +RECOMMENDATIONS = srsly.read_yaml(ROOT / "quickstart_training_recommendations.yml") class Optimizations(str, Enum): @@ -21,21 +21,6 @@ class Optimizations(str, Enum): accuracy = "accuracy" -class RecommendationsTrfItem(BaseModel): - name: str - size_factor: int - - -class RecommendationsTrf(BaseModel): - efficiency: RecommendationsTrfItem - accuracy: RecommendationsTrfItem - - -class RecommendationSchema(BaseModel): - word_vectors: Optional[str] = None - transformer: Optional[RecommendationsTrf] = None - - @init_cli.command("config") def init_config_cli( # fmt: off @@ -111,14 +96,11 @@ def init_config( from jinja2 import Template except ImportError: msg.fail("This command requires jinja2", "pip install jinja2", exits=1) - recommendations = srsly.read_json(RECOMMENDATIONS_PATH) - lang_defaults = util.get_lang_class(lang).Defaults - has_letters = lang_defaults.writing_system.get("has_letters", True) - # Filter out duplicates since tok2vec and transformer are added by template - pipeline = [pipe for pipe in pipeline if pipe not in ("tok2vec", "transformer")] - reco = RecommendationSchema(**recommendations.get(lang, {})).dict() with TEMPLATE_PATH.open("r") as f: template = Template(f.read()) + # Filter out duplicates since tok2vec and transformer are added by template + pipeline = [pipe for pipe in pipeline if pipe not in ("tok2vec", "transformer")] + reco = RecommendationSchema(**RECOMMENDATIONS.get(lang, {})).dict() variables = { "lang": lang, "components": pipeline, @@ -126,7 +108,7 @@ def init_config( "hardware": "cpu" if cpu else "gpu", "transformer_data": reco["transformer"], "word_vectors": reco["word_vectors"], - "has_letters": has_letters, + "has_letters": reco["has_letters"], } base_template = template.render(variables).strip() # Giving up on getting the newlines right in jinja for now diff --git a/spacy/cli/templates/quickstart_training_recommendations.json b/spacy/cli/templates/quickstart_training_recommendations.json deleted file mode 100644 index 8a3acc438..000000000 --- a/spacy/cli/templates/quickstart_training_recommendations.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "en": { - "word_vectors": "en_vectors_web_lg", - "transformer": { - "efficiency": { "name": "roberta-base", "size_factor": 3 }, - "accuracy": { "name": "roberta-base", "size_factor": 3 } - } - }, - "de": { - "word_vectors": null, - "transformer": null - } -} diff --git a/spacy/cli/templates/quickstart_training_recommendations.yml b/spacy/cli/templates/quickstart_training_recommendations.yml new file mode 100644 index 000000000..efb6da2be --- /dev/null +++ b/spacy/cli/templates/quickstart_training_recommendations.yml @@ -0,0 +1,103 @@ +# Recommended settings and available resources for each language, if available. +# Not all languages have recommended word vecotrs or transformers and for some, +# the recommended transformer for efficiency and accuracy may be the same. +en: + word_vectors: en_vectors_web_lg + transformer: + efficiency: + name: roberta-base + size_factor: 3 + accuracy: + name: roberta-base + size_factor: 3 +de: + word_vectors: null + transformer: + efficiency: + name: bert-base-german-cased + size_factor: 3 + accuracy: + name: bert-base-german-cased + size_factor: 3 +fr: + word_vectors: null + transformer: + efficiency: + name: camembert-base + size_factor: 3 + accuracy: + name: camembert-base + size_factor: 3 +es: + word_vectors: null + transformer: + efficiency: + name: mrm8488/RuPERTa-base + size_factor: 3 + accuracy: + name: mrm8488/RuPERTa-base + size_factor: 3 +sv: + word_vectors: null + transformer: + efficiency: + name: KB/bert-base-swedish-cased + size_factor: 3 + accuracy: + name: KB/bert-base-swedish-cased + size_factor: 3 +fi: + word_vectors: null + transformer: + efficiency: + name: TurkuNLP/bert-base-finnish-cased-v1 + size_factor: 3 + accuracy: + name: TurkuNLP/bert-base-finnish-cased-v1 + size_factor: 3 +el: + word_vectors: null + transformer: + efficiency: + name: nlpaueb/bert-base-greek-uncased-v1 + size_factor: 3 + accuracy: + name: nlpaueb/bert-base-greek-uncased-v1 + size_factor: 3 +tr: + word_vectors: null + transformer: + efficiency: + name: dbmdz/bert-base-turkish-cased + size_factor: 3 + accuracy: + name: dbmdz/bert-base-turkish-cased + size_factor: 3 +zh: + word_vectors: null + transformer: + efficiency: + name: bert-base-chinese + size_factor: 3 + accuracy: + name: bert-base-chinese + size_factor: 3 + has_letters: false +ar: + word_vectors: null + transformer: + efficiency: + name: asafaya/bert-base-arabic + size_factor: 3 + accuracy: + name: asafaya/bert-base-arabic + size_factor: 3 +pl: + word_vectors: null + transformer: + efficiency: + name: dkleczek/bert-base-polish-cased-v1 + size_factor: 3 + accuracy: + name: dkleczek/bert-base-polish-cased-v1 + size_factor: 3 diff --git a/spacy/schemas.py b/spacy/schemas.py index e219c2009..3eef814c6 100644 --- a/spacy/schemas.py +++ b/spacy/schemas.py @@ -311,3 +311,22 @@ class ProjectConfigSchema(BaseModel): class Config: title = "Schema for project configuration file" + + +# Recommendations for init config workflows + + +class RecommendationTrfItem(BaseModel): + name: str + size_factor: int + + +class RecommendationTrf(BaseModel): + efficiency: RecommendationTrfItem + accuracy: RecommendationTrfItem + + +class RecommendationSchema(BaseModel): + word_vectors: Optional[str] = None + transformer: Optional[RecommendationTrf] = None + has_letters: bool = True diff --git a/spacy/tests/test_cli.py b/spacy/tests/test_cli.py index 1da257fd5..89ce740e0 100644 --- a/spacy/tests/test_cli.py +++ b/spacy/tests/test_cli.py @@ -2,10 +2,9 @@ import pytest from spacy.gold import docs_to_json, biluo_tags_from_offsets from spacy.gold.converters import iob2docs, conll_ner2docs, conllu2docs from spacy.lang.en import English -from spacy.schemas import ProjectConfigSchema, validate +from spacy.schemas import ProjectConfigSchema, RecommendationSchema, validate from spacy.cli.pretrain import make_docs -from spacy.cli.init_config import init_config, RECOMMENDATIONS_PATH -from spacy.cli.init_config import RecommendationSchema +from spacy.cli.init_config import init_config, RECOMMENDATIONS from spacy.cli._util import validate_project_commands, parse_config_overrides from spacy.util import get_lang_class import srsly @@ -335,7 +334,5 @@ def test_init_config(lang, pipeline, optimize): def test_model_recommendations(): - recommendations = srsly.read_json(RECOMMENDATIONS_PATH) - for lang, data in recommendations.items(): - assert get_lang_class(lang) + for lang, data in RECOMMENDATIONS.items(): assert RecommendationSchema(**data) diff --git a/website/package.json b/website/package.json index 441d996fe..9e02cda82 100644 --- a/website/package.json +++ b/website/package.json @@ -53,7 +53,7 @@ "remark-react": "^5.0.1" }, "scripts": { - "build": "npm run python:setup && gatsby build", + "build": "npm run python:install && npm run python:setup && gatsby build", "dev": "npm run python:setup && gatsby develop", "dev:nightly": "BRANCH=nightly.spacy.io npm run dev", "lint": "eslint **", diff --git a/website/setup/jinja_to_js.py b/website/setup/jinja_to_js.py index 0d363375e..114d0e172 100644 --- a/website/setup/jinja_to_js.py +++ b/website/setup/jinja_to_js.py @@ -11,7 +11,8 @@ from os import path from io import StringIO from jinja2 import Environment, FileSystemLoader, nodes from pathlib import Path -import typer +import srsly +import sys OPERANDS = { @@ -437,7 +438,8 @@ class JinjaToJS(object): with self._interpolation(): with self._python_bool_wrapper(**kwargs): if node.items: - raise ValueError(f"Can't process non-empty dict in epxression: {node}") + err = f"Can't process non-empty dict in expression: {node}" + raise ValueError(err) self.output.write("{}") def _process_getattr(self, node, **kwargs): @@ -1232,18 +1234,22 @@ class JinjaToJS(object): self.output.write(")") -def main( - # fmt: off - template_path: Path = typer.Argument(..., exists=True, dir_okay=False, help="Path to .jinja file"), - output: Path = typer.Argument(None, help="Path to output module (stdout if unset)"), - data_path: Path = typer.Option(None, "--data", help="Optional JSON file with additional data to be included as DATA") - # fmt: on -): - """Convert a jinja2 template to a JavaScript module.""" +def main(template_path, output=None, data_path=None): + """Convert a jinja2 template to a JavaScript module. + + template_path (Path): Path to .jijna file. + output (Optional[Path]): Path to output .js module (stdout if unset). + data_path (Optional[Path]): Optional JSON or YAML file with additional data + to be included in the JS module as the exported variable DATA. + """ data = "{}" if data_path is not None: - with data_path.open("r", encoding="utf8") as f: - data = json.dumps(json.loads(f.read())) # dump and load for compactness + if data_path.suffix in (".yml", ".yaml"): + data = srsly.read_yaml(data_path) + else: + data = srsly.read_json(data_path) + data = srsly.json_dumps(data) # dump and load for compactness + template_path = Path(template_path) tpl_file = template_path.parts[-1] compiler = JinjaToJS(template_path.parent, tpl_file, js_module_format="es6") header = f"// This file was auto-generated by {__file__} based on {tpl_file}" @@ -1258,4 +1264,10 @@ def main( if __name__ == "__main__": - typer.run(main) + args = sys.argv[1:] + if not len(args): + raise ValueError("Need at least one argument: path to .jinja template") + template_path = Path(args[0]) + output = Path(args[1]) if len(args) > 1 else None + data_path = Path(args[2]) if len(args) > 2 else None + main(template_path, output, data_path) diff --git a/website/setup/requirements.txt b/website/setup/requirements.txt index 7ffb6df0b..e7a8e65a7 100644 --- a/website/setup/requirements.txt +++ b/website/setup/requirements.txt @@ -1,3 +1,3 @@ # These are used to compile the training quickstart config jinja2 -typer +srsly diff --git a/website/setup/setup.sh b/website/setup/setup.sh index a6bbd3294..674b25674 100755 --- a/website/setup/setup.sh +++ b/website/setup/setup.sh @@ -1 +1 @@ -python jinja_to_js.py ../../spacy/cli/templates/quickstart_training.jinja ../src/widgets/quickstart-training-generator.js --data ../../spacy/cli/templates/quickstart_training_recommendations.json +python jinja_to_js.py ../../spacy/cli/templates/quickstart_training.jinja ../src/widgets/quickstart-training-generator.js ../../spacy/cli/templates/quickstart_training_recommendations.yml diff --git a/website/src/widgets/quickstart-training-generator.js b/website/src/widgets/quickstart-training-generator.js index c7f856073..b5389d4d7 100644 --- a/website/src/widgets/quickstart-training-generator.js +++ b/website/src/widgets/quickstart-training-generator.js @@ -9,4 +9,4 @@ import jinjaToJS from "jinja-to-js";export default function templateQuickstartTr var use_transformer = context.transformer_data && context.hardware!=="cpu";var transformer = (use_transformer ? context.transformer_data[context.optimize] : {});__result += "[paths]\ntrain = \"\"\ndev = \"\"\n\n[system]\nuse_pytorch_for_gpu_memory = ";__result += "" + __runtime.escape((__tmp = ((use_transformer ? "true" : "false"))) == null ? "" : __tmp);__result += "\n\n[nlp]\nlang = \"";__result += "" + __runtime.escape((__tmp = (context.lang)) == null ? "" : __tmp);__result += "\"";var full_pipeline = [(use_transformer ? "transformer" : "tok2vec")].concat(context.components);__result += "\npipeline = ";__result += "" + ((__tmp = (JSON.stringify(full_pipeline).split("'").join("\""))) == null ? "" : __tmp);__result += "\ntokenizer = {\"@tokenizers\": \"spacy.Tokenizer.v1\"}\n\n[components]\n\n";if(__runtime.boolean(use_transformer)){__result += "[components.transformer]\nfactory = \"transformer\"\n\n[components.transformer.model]\n@architectures = \"spacy-transformers.TransformerModel.v1\"\nname = \"";__result += "" + __runtime.escape((__tmp = (transformer["name"])) == null ? "" : __tmp);__result += "\"\ntokenizer_config = {\"use_fast\": true}\n\n[components.transformer.model.get_spans]\n@span_getters = \"strided_spans.v1\"\nwindow = 128\nstride = 96\n\n";if(context.components.includes("tagger")){__result += "\n[components.tagger]\nfactory = \"tagger\"\n\n[components.tagger.model]\n@architectures = \"spacy.Tagger.v1\"\nnO = null\n\n[components.tagger.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.tagger.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"";}__result += "\n\n";if(context.components.includes("parser")){__result += "[components.parser]\nfactory = \"parser\"\n\n[components.parser.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 8\nhidden_width = 128\nmaxout_pieces = 3\nuse_upper = false\nnO = null\n\n[components.parser.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.parser.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"";}__result += "\n\n";if(context.components.includes("ner")){__result += "[components.ner]\nfactory = \"ner\"\n\n[components.ner.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 3\nhidden_width = 64\nmaxout_pieces = 2\nuse_upper = false\nnO = null\n\n[components.ner.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.ner.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"\n";}__result += "\n";} else {if(context.hardware==="gpu"){__result += "# There are no recommended transformer weights available for language '";__result += "" + __runtime.escape((__tmp = (context.lang)) == null ? "" : __tmp);__result += "'\n# yet, so the pipeline described here is not transformer-based.";}__result += "\n\n[components.tok2vec]\nfactory = \"tok2vec\"\n\n[components.tok2vec.model]\n@architectures = \"spacy.Tok2Vec.v1\"\n\n[components.tok2vec.model.embed]\n@architectures = \"spacy.MultiHashEmbed.v1\"\nwidth = ${components.tok2vec.model.encode:width}\nrows = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 2000 : 7000))) == null ? "" : __tmp);__result += "\nalso_embed_subwords = ";__result += "" + __runtime.escape((__tmp = ((context.has_letters ? true : false))) == null ? "" : __tmp);__result += "\nalso_use_static_vectors = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="accuracy" ? true : false))) == null ? "" : __tmp);__result += "\n\n[components.tok2vec.model.encode]\n@architectures = \"spacy.MaxoutWindowEncoder.v1\"\nwidth = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 96 : 256))) == null ? "" : __tmp);__result += "\ndepth = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 4 : 8))) == null ? "" : __tmp);__result += "\nwindow_size = 1\nmaxout_pieces = 3\n\n";if(context.components.includes("tagger")){__result += "\n[components.tagger]\nfactory = \"tagger\"\n\n[components.tagger.model]\n@architectures = \"spacy.Tagger.v1\"\nnO = null\n\n[components.tagger.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}";}__result += "\n\n";if(context.components.includes("parser")){__result += "[components.parser]\nfactory = \"parser\"\n\n[components.parser.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 8\nhidden_width = 128\nmaxout_pieces = 3\nuse_upper = true\nnO = null\n\n[components.parser.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}";}__result += "\n\n";if(context.components.includes("ner")){__result += "\n[components.ner]\nfactory = \"ner\"\n\n[components.ner.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 6\nhidden_width = 64\nmaxout_pieces = 2\nuse_upper = true\nnO = null\n\n[components.ner.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}\n";}__result += "\n";}__result += "\n\n";__runtime.each(context.components,function(pipe){var __$0 = context.pipe;context.pipe = pipe;__result += "\n";if(!["tagger","parser","ner"].includes(pipe)){__result += "\n";__result += "\n[components.";__result += "" + __runtime.escape((__tmp = (pipe)) == null ? "" : __tmp);__result += "]\nfactory = \"";__result += "" + __runtime.escape((__tmp = (pipe)) == null ? "" : __tmp);__result += "\"\n";}__result += "\n";context.pipe = __$0;});__result += "\n\n[training]\n";if(__runtime.boolean(use_transformer) || context.optimize==="efficiency" || !__runtime.boolean(context.word_vectors)){__result += "vectors = null\n";} else {__result += "vectors = \"";__result += "" + __runtime.escape((__tmp = (context.word_vectors)) == null ? "" : __tmp);__result += "\"\n";}if(__runtime.boolean(use_transformer)){__result += "accumulate_gradient = ";__result += "" + __runtime.escape((__tmp = (transformer["size_factor"])) == null ? "" : __tmp);__result += "\n";}__result += "\n\n[training.optimizer]\n@optimizers = \"Adam.v1\"\n\n[training.optimizer.learn_rate]\n@schedules = \"warmup_linear.v1\"\nwarmup_steps = 250\ntotal_steps = 20000\ninitial_rate = 5e-5\n\n[training.train_corpus]\n@readers = \"spacy.Corpus.v1\"\npath = ${paths:train}\nmax_length = ";__result += "" + __runtime.escape((__tmp = ((context.hardware==="gpu" ? 500 : 0))) == null ? "" : __tmp);__result += "\n\n[training.dev_corpus]\n@readers = \"spacy.Corpus.v1\"\npath = ${paths:dev}\nmax_length = 0\n\n";if(__runtime.boolean(use_transformer)){__result += "\n[training.batcher]\n@batchers = \"batch_by_padded.v1\"\ndiscard_oversize = true\nsize = 2000\nbuffer = 256";} else {__result += "\n[training.batcher]\n@batchers = \"batch_by_words.v1\"\ndiscard_oversize = false\ntolerance = 0.2\n\n[training.batcher.size]\n@schedules = \"compounding.v1\"\nstart = 100\nstop = 1000\ncompound = 1.001\n";}__result += "\n\n[training.score_weights]";if(context.components.includes("tagger")){__result += "\ntag_acc = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);}if(context.components.includes("parser")){__result += "\ndep_uas = 0.0\ndep_las = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);__result += "\nsents_f = 0.0";}if(context.components.includes("ner")){__result += "\nents_f = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);__result += "\nents_p = 0.0\nents_r = 0.0";} return __result; } -export const DATA = {"en": {"word_vectors": "en_vectors_web_lg", "transformer": {"efficiency": {"name": "roberta-base", "size_factor": 3}, "accuracy": {"name": "roberta-base", "size_factor": 3}}}, "de": {"word_vectors": null, "transformer": null}} \ No newline at end of file +export const DATA = {"en":{"word_vectors":"en_vectors_web_lg","transformer":{"efficiency":{"name":"roberta-base","size_factor":3},"accuracy":{"name":"roberta-base","size_factor":3}}},"de":{"word_vectors":null,"transformer":{"efficiency":{"name":"bert-base-german-cased","size_factor":3},"accuracy":{"name":"bert-base-german-cased","size_factor":3}}},"fr":{"word_vectors":null,"transformer":{"efficiency":{"name":"camembert-base","size_factor":3},"accuracy":{"name":"camembert-base","size_factor":3}}},"es":{"word_vectors":null,"transformer":{"efficiency":{"name":"mrm8488/RuPERTa-base","size_factor":3},"accuracy":{"name":"mrm8488/RuPERTa-base","size_factor":3}}},"sv":{"word_vectors":null,"transformer":{"efficiency":{"name":"KB/bert-base-swedish-cased","size_factor":3},"accuracy":{"name":"KB/bert-base-swedish-cased","size_factor":3}}},"fi":{"word_vectors":null,"transformer":{"efficiency":{"name":"TurkuNLP/bert-base-finnish-cased-v1","size_factor":3},"accuracy":{"name":"TurkuNLP/bert-base-finnish-cased-v1","size_factor":3}}},"el":{"word_vectors":null,"transformer":{"efficiency":{"name":"nlpaueb/bert-base-greek-uncased-v1","size_factor":3},"accuracy":{"name":"nlpaueb/bert-base-greek-uncased-v1","size_factor":3}}},"tr":{"word_vectors":null,"transformer":{"efficiency":{"name":"dbmdz/bert-base-turkish-cased","size_factor":3},"accuracy":{"name":"dbmdz/bert-base-turkish-cased","size_factor":3}}},"zh":{"word_vectors":null,"transformer":{"efficiency":{"name":"bert-base-chinese","size_factor":3},"accuracy":{"name":"bert-base-chinese","size_factor":3}},"has_letters":false},"ar":{"word_vectors":null,"transformer":{"efficiency":{"name":"asafaya/bert-base-arabic","size_factor":3},"accuracy":{"name":"asafaya/bert-base-arabic","size_factor":3}}},"pl":{"word_vectors":null,"transformer":{"efficiency":{"name":"dkleczek/bert-base-polish-cased-v1","size_factor":3},"accuracy":{"name":"dkleczek/bert-base-polish-cased-v1","size_factor":3}}}} \ No newline at end of file diff --git a/website/src/widgets/quickstart-training.js b/website/src/widgets/quickstart-training.js index 1a77cc338..ae8d41b64 100644 --- a/website/src/widgets/quickstart-training.js +++ b/website/src/widgets/quickstart-training.js @@ -4,7 +4,7 @@ import highlightCode from 'gatsby-remark-prismjs/highlight-code.js' import { Quickstart } from '../components/quickstart' import generator, { DATA as GENERATOR_DATA } from './quickstart-training-generator' -import { isString, htmlToReact } from '../components/util' +import { htmlToReact } from '../components/util' const DEFAULT_LANG = 'en' const DEFAULT_HARDWARE = 'gpu' @@ -47,13 +47,6 @@ const DATA = [ }, ] -function stringify(value) { - if (isString(value) && value.startsWith('${')) return value - const string = JSON.stringify(value) - if (Array.isArray(value)) return string.replace(/,/g, ', ') - return string -} - export default function QuickstartTraining({ id, title, download = 'config.cfg' }) { const [lang, setLang] = useState(DEFAULT_LANG) const [components, setComponents] = useState([]) @@ -73,6 +66,7 @@ export default function QuickstartTraining({ id, title, download = 'config.cfg' hardware, transformer_data: reco.transformer, word_vectors: reco.word_vectors, + has_letters: reco.has_letters, }) const rawStr = content.trim().replace(/\n\n\n+/g, '\n\n') const rawContent = `${COMMENT}\n${rawStr}` @@ -90,7 +84,7 @@ export default function QuickstartTraining({ id, title, download = 'config.cfg' id: code, title: name, })) - .sort((a, b) => a.id.localeCompare(b.id)) + .sort((a, b) => a.title.localeCompare(b.title)) return ( Date: Wed, 19 Aug 2020 14:52:49 +0200 Subject: [PATCH 31/92] add kb_loader and get_candidates back to EL API --- website/docs/api/architectures.md | 2 +- website/docs/api/entitylinker.md | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index 446e6c7c3..da1a64068 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -622,7 +622,7 @@ others, but may not be as accurate, especially if texts are short. An [`EntityLinker`](/api/entitylinker) component disambiguates textual mentions (tagged as named entities) to unique identifiers, grounding the named entities -into the "real world". This requires 3 main component +into the "real world". This requires 3 main components: - A [`KnowledgeBase`](/api/kb) (KB) holding the unique identifiers, potential synonyms and prior probabilities. diff --git a/website/docs/api/entitylinker.md b/website/docs/api/entitylinker.md index a1bc52199..8b3875f9d 100644 --- a/website/docs/api/entitylinker.md +++ b/website/docs/api/entitylinker.md @@ -40,13 +40,14 @@ architectures and their arguments and hyperparameters. > nlp.add_pipe("entity_linker", config=config) > ``` -| Setting | Description | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `labels_discard` | NER labels that will automatically get a "NIL" prediction. Defaults to `[]`. ~~Iterable[str]~~ | -| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. Defaults to `True`. ~~bool~~ | -| `incl_context` | Whether or not to include the local context in the model. Defaults to `True`. ~~bool~~ | -| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [EntityLinker](/api/architectures#EntityLinker). ~~Model~~ | -| `kb` | The [`KnowledgeBase`](/api/kb). Defaults to [EmptyKB](/api/architectures#EmptyKB), a function returning an empty `KnowledgeBase` with an `entity_vector_length` of `64`. ~~KnowledgeBase~~ | +| Setting | Description | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `labels_discard` | NER labels that will automatically get a "NIL" prediction. Defaults to `[]`. ~~Iterable[str]~~ | +| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. Defaults to `True`. ~~bool~~ | +| `incl_context` | Whether or not to include the local context in the model. Defaults to `True`. ~~bool~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. Defaults to [EntityLinker](/api/architectures#EntityLinker). ~~Model~~ | +| `kb_loader` | Function that creates a [`KnowledgeBase`](/api/kb) from a `Vocab` instance. Defaults to [EmptyKB](/api/architectures#EmptyKB), a function returning an empty `KnowledgeBase` with an `entity_vector_length` of `64`. ~~Callable[[Vocab], KnowledgeBase]~~ | +| `get_candidates` | Function that generates plausible candidates for a given `Span` object. Defaults to [CandidateGenerator](/api/architectures#CandidateGenerator), a function looking up exact, case-dependent aliases in the KB. ~~Callable[[KnowledgeBase, Span], Iterable[Candidate]]~~ | ```python https://github.com/explosion/spaCy/blob/develop/spacy/pipeline/entity_linker.py From 60fedb8518d56f8c263ec2bca48036dc0d84ada8 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 14:55:32 +0200 Subject: [PATCH 32/92] fix 2 more API lines --- website/docs/api/entitylinker.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/website/docs/api/entitylinker.md b/website/docs/api/entitylinker.md index 8b3875f9d..679c3c0c2 100644 --- a/website/docs/api/entitylinker.md +++ b/website/docs/api/entitylinker.md @@ -80,16 +80,17 @@ shortcut for this and instantiate the component using its string name and `KnowledgeBase` as well as the Candidate generator can be customized by providing custom registered functions. -| Name | Description | -| ---------------- | --------------------------------------------------------------------------------------------------- | -| `vocab` | The shared vocabulary. ~~Vocab~~ | -| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model~~ | -| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | -| _keyword-only_ | | | -| `kb` | The [`KnowledgeBase`](/api/kb). ~~KnowledgeBase~~ | -| `labels_discard` | NER labels that will automatically get a `"NIL"` prediction. ~~Iterable[str]~~ | -| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. ~~bool~~ | -| `incl_context` | Whether or not to include the local context in the model. ~~bool~~ | +| Name | Description | +| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| `vocab` | The shared vocabulary. ~~Vocab~~ | +| `model` | The [`Model`](https://thinc.ai/docs/api-model) powering the pipeline component. ~~Model~~ | +| `name` | String name of the component instance. Used to add entries to the `losses` during training. ~~str~~ | +| _keyword-only_ | | | +| `kb_loader` | Function that creates a [`KnowledgeBase`](/api/kb) from a `Vocab` instance. ~~Callable[[Vocab], KnowledgeBase]~~ | +| `get_candidates` | Function that generates plausible candidates for a given `Span` object. ~~Callable[[KnowledgeBase, Span], Iterable[Candidate]]~~ | +| `labels_discard` | NER labels that will automatically get a `"NIL"` prediction. ~~Iterable[str]~~ | +| `incl_prior` | Whether or not to include prior probabilities from the KB in the model. ~~bool~~ | +| `incl_context` | Whether or not to include the local context in the model. ~~bool~~ | ## EntityLinker.\_\_call\_\_ {#call tag="method"} From d3a83211721bd04513070c7aeda3bc205a0d86f1 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 15:12:12 +0200 Subject: [PATCH 33/92] fix typos --- website/docs/usage/projects.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/usage/projects.md b/website/docs/usage/projects.md index 61367fb0e..123ef195e 100644 --- a/website/docs/usage/projects.md +++ b/website/docs/usage/projects.md @@ -155,8 +155,8 @@ other. For instance, to generate a packaged model, you might start by converting your data, then run [`spacy train`](/api/cli#train) to train your model on the converted data and if that's successful, run [`spacy package`](/api/cli#package) to turn the best model artifact into an installable Python package. The -following command run the workflow named `all` defined in the `project.yml`, and -execute the commands it specifies, in order: +following command runs the workflow named `all` defined in the `project.yml`, and +executes the commands it specifies, in order: ```cli $ python -m spacy project run all @@ -199,7 +199,7 @@ https://github.com/explosion/spacy-boilerplates/blob/master/ner_fashion/project. ### Dependencies and outputs {#deps-outputs} Each command defined in the `project.yml` can optionally define a list of -dependencies and outputs. These are the files the commands requires and creates. +dependencies and outputs. These are the files the command requires and creates. For example, a command for training a model may depend on a [`config.cfg`](/usage/training#config) and the training and evaluation data, and it will export a directory `model-best`, containing the best model, which you From 63921161c850dd60297cb1c01da1b302bd1e7a6f Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 16:04:21 +0200 Subject: [PATCH 34/92] Update docs [ci skip] --- website/docs/api/top-level.md | 2 - website/docs/usage/processing-pipelines.md | 87 ++++++++++++++++++++-- website/docs/usage/projects.md | 2 +- website/docs/usage/training.md | 16 ++-- 4 files changed, 90 insertions(+), 17 deletions(-) diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index de0b3d36c..646b685f0 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -499,8 +499,6 @@ page should be safe to use and we'll try to ensure backwards compatibility. However, we recommend having additional tests in place if your application depends on any of spaCy's utilities. - - ### util.get_lang_class {#util.get_lang_class tag="function"} Import and load a `Language` class. Allows lazy-loading diff --git a/website/docs/usage/processing-pipelines.md b/website/docs/usage/processing-pipelines.md index 73ad88bcc..bc8c990e8 100644 --- a/website/docs/usage/processing-pipelines.md +++ b/website/docs/usage/processing-pipelines.md @@ -623,7 +623,7 @@ added to the pipeline: > > @Language.factory("my_component") > def my_component(nlp, name): -> return MyComponent() +> return MyComponent() > ``` | Argument | Description | @@ -636,8 +636,6 @@ All other settings can be passed in by the user via the `config` argument on [`@Language.factory`](/api/language#factory) decorator also lets you define a `default_config` that's used as a fallback. - - ```python ### With config {highlight="4,9"} import spacy @@ -688,7 +686,7 @@ make your factory a separate function. That's also how spaCy does it internally.
-### Example: Stateful component with settings +### Example: Stateful component with settings {#example-stateful-components} This example shows a **stateful** pipeline component for handling acronyms: based on a dictionary, it will detect acronyms and their expanded forms in both @@ -757,6 +755,85 @@ doc = nlp("LOL, be right back") print(doc._.acronyms) ``` +Many stateful components depend on **data resources** like dictionaries and +lookup tables that should ideally be **configurable**. For example, it makes +sense to make the `DICTIONARY` and argument of the registered function, so the +`AcronymComponent` can be re-used with different data. One logical solution +would be to make it an argument of the component factory, and allow it to be +initialized with different dictionaries. + +> #### Example +> +> Making the data an argument of the registered function would result in output +> like this in your `config.cfg`, which is typically not what you want (and only +> works for JSON-serializable data). +> +> ```ini +> [components.acronyms.dictionary] +> lol = "laugh out loud" +> brb = "be right back" +> ``` + +However, passing in the dictionary directly is problematic, because it means +that if a component saves out its config and settings, the +[`config.cfg`](/usage/training#config) will include a dump of the entire data, +since that's the config the component was created with. + +```diff +DICTIONARY = {"lol": "laughing out loud", "brb": "be right back"} +- default_config = {"dictionary:" DICTIONARY} +``` + +If what you're passing in isn't JSON-serializable – e.g. a custom object like a +[model](#trainable-components) – saving out the component config becomes +impossible because there's no way for spaCy to know _how_ that object was +created, and what to do to create it again. This makes it much harder to save, +load and train custom models with custom components. A simple solution is to +**register a function** that returns your resources. The +[registry](/api/top-level#registry) lets you **map string names to functions** +that create objects, so given a name and optional arguments, spaCy will know how +to recreate the object. To register a function that returns a custom asset, you +can use the `@spacy.registry.assets` decorator with a single argument, the name: + +```python +### Registered function for assets {highlight="1"} +@spacy.registry.assets("acronyms.slang_dict.v1") +def create_acronyms_slang_dict(): + dictionary = {"lol": "laughing out loud", "brb": "be right back"} + dictionary.update({value: key for key, value in dictionary.items()}) + return dictionary +``` + +In your `default_config` (and later in your +[training config](/usage/training#config)), you can now refer to the function +registered under the name `"acronyms.slang_dict.v1"` using the `@assets` key. +This tells spaCy how to create the value, and when your component is created, +the result of the registered function is passed in as the key `"dictionary"`. + +> #### config.cfg +> +> ```ini +> [components.acronyms] +> factory = "acronyms" +> +> [components.acronyms.dictionary] +> @assets = "acronyms.slang_dict.v1" +> ``` + +```diff +- default_config = {"dictionary:" DICTIONARY} ++ default_config = {"dictionary": {"@assets": "acronyms.slang_dict.v1"}} +``` + +Using a registered function also means that you can easily include your custom +components in models that you [train](/usage/training). To make sure spaCy knows +where to find your custom `@assets` function, you can pass in a Python file via +the argument `--code`. If someone else is using your component, all they have to +do to customize the data is to register their own function and swap out the +name. Registered functions can also take **arguments** by the way that can be +defined in the config as well – you can read more about this in the docs on +[training with custom code](/usage/training#custom-code). + ### Python type hints and pydantic validation {#type-hints new="3"} spaCy's configs are powered by our machine learning library Thinc's @@ -994,7 +1071,7 @@ loss is calculated and to add evaluation scores to the training output. | [`get_loss`](/api/pipe#get_loss) | Return a tuple of the loss and the gradient for a batch of [`Example`](/api/example) objects. | | [`score`](/api/pipe#score) | Score a batch of [`Example`](/api/example) objects and return a dictionary of scores. The [`@Language.factory`](/api/language#factory) decorator can define the `default_socre_weights` of the component to decide which keys of the scores to display during training and how they count towards the final score. | - + ## Extension attributes {#custom-components-attributes new="2"} diff --git a/website/docs/usage/projects.md b/website/docs/usage/projects.md index 61367fb0e..41f0357ca 100644 --- a/website/docs/usage/projects.md +++ b/website/docs/usage/projects.md @@ -97,7 +97,7 @@ to download and where to put them. The [`spacy project assets`](/api/cli#project-assets) will fetch the project assets for you: -``` +```cli $ cd some_example_project $ python -m spacy project assets ``` diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 7ce9457f9..f7a74bbcc 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -414,7 +414,7 @@ recipe once the dish has already been prepared. You have to make a new one. spaCy includes a variety of built-in [architectures](/api/architectures) for different tasks. For example: - + | Architecture | Description | | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -483,7 +483,7 @@ still look good. ## Custom model implementations and architectures {#custom-models} - + ### Training with custom code {#custom-code} @@ -766,12 +766,11 @@ mattis pretium. ### Defining custom architectures {#custom-architectures} - - + ## Transfer learning {#transfer-learning} - + ### Using transformer models like BERT {#transformers} @@ -801,7 +800,7 @@ config and customize the implementations, see the usage guide on ### Pretraining with spaCy {#pretraining} - + ## Parallel Training with Ray {#parallel-training} @@ -826,9 +825,8 @@ spaCy gives you full control over the training loop. However, for most use cases, it's recommended to train your models via the [`spacy train`](/api/cli#train) command with a [`config.cfg`](#config) to keep track of your settings and hyperparameters, instead of writing your own training -scripts from scratch. -[Custom registered functions](/usage/training/#custom-code) should typically -give you everything you need to train fully custom models with +scripts from scratch. [Custom registered functions](#custom-code) should +typically give you everything you need to train fully custom models with [`spacy train`](/api/cli#train). From 648499157a3400aca9edd96c4d385e4e3dc48370 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 16:53:51 +0200 Subject: [PATCH 35/92] rename "custom models" to "custom functions" --- website/docs/api/architectures.md | 2 +- website/docs/api/cli.md | 14 ++++++------- website/docs/api/top-level.md | 34 +++++++++++++++---------------- website/docs/usage/training.md | 8 ++++---- 4 files changed, 29 insertions(+), 29 deletions(-) diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index da1a64068..25a44245d 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -13,7 +13,7 @@ menu: TODO: intro and how architectures work, link to [`registry`](/api/top-level#registry), -[custom models](/usage/training#custom-models) usage etc. +[custom functions](/usage/training#custom-functions) usage etc. ## Tok2Vec architectures {#tok2vec-arch source="spacy/ml/models/tok2vec.py"} diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md index a86c920ad..9cadb2f0f 100644 --- a/website/docs/api/cli.md +++ b/website/docs/api/cli.md @@ -276,7 +276,7 @@ python -m spacy init fill-config tmp/starter-config_invalid.cfg --base tmp/start | Name | Description | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `config_path` | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~ | -| `--code_path`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~ | +| `--code_path`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-functions) for new architectures. ~~Optional[Path] \(option)~~ | | `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | | overrides | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. ~~Any (option/flag)~~ | | **PRINTS** | Config validation errors, if available. | @@ -448,7 +448,7 @@ will not be available. | Name | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `config_path` | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~ | -| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~ | +| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-functions) for new architectures. ~~Optional[Path] \(option)~~ | | `--ignore-warnings`, `-IW` | Ignore warnings, only show stats and errors. ~~bool (flag)~~ | | `--verbose`, `-V` | Print additional information and explanations. ~~bool (flag)~~ | | `--no-format`, `-NF` | Don't pretty-print the results. Use this if you want to write to a file. ~~bool (flag)~~ | @@ -612,9 +612,9 @@ Train a model. Expects data in spaCy's Will save out the best model from all epochs, as well as the final model. The `--code` argument can be used to provide a Python file that's imported before the training process starts. This lets you register -[custom functions](/usage/training#custom-models) and architectures and refer to -them in your config, all while still using spaCy's built-in `train` workflow. If -you need to manage complex multi-step training workflows, check out the new +[custom functions](/usage/training#custom-functions) and architectures and refer +to them in your config, all while still using spaCy's built-in `train` workflow. +If you need to manage complex multi-step training workflows, check out the new [spaCy projects](/usage/projects). @@ -636,7 +636,7 @@ $ python -m spacy train [config_path] [--output] [--code] [--verbose] [overrides | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `config_path` | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~ | | `--output`, `-o` | Directory to store model in. Will be created if it doesn't exist. ~~Optional[Path] \(positional)~~ | -| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~ | +| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-functions) for new architectures. ~~Optional[Path] \(option)~~ | | `--verbose`, `-V` | Show more detailed messages during training. ~~bool (flag)~~ | | `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | | overrides | Config parameters to override. Should be options starting with `--` that correspond to the config section and value to override, e.g. `--paths.train ./train.spacy`. ~~Any (option/flag)~~ | @@ -674,7 +674,7 @@ $ python -m spacy pretrain [texts_loc] [output_dir] [config_path] [--code] [--re | `texts_loc` | Path to JSONL file with raw texts to learn from, with text provided as the key `"text"` or tokens as the key `"tokens"`. [See here](/api/data-formats#pretrain) for details. ~~Path (positional)~~ | | `output_dir` | Directory to write models to on each epoch. ~~Path (positional)~~ | | `config_path` | Path to [training config](/api/data-formats#config) file containing all settings and hyperparameters. ~~Path (positional)~~ | -| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-models) for new architectures. ~~Optional[Path] \(option)~~ | +| `--code`, `-c` | Path to Python file with additional code to be imported. Allows [registering custom functions](/usage/training#custom-functions) for new architectures. ~~Optional[Path] \(option)~~ | | `--resume-path`, `-r` | Path to pretrained weights from which to resume pretraining. ~~Optional[Path] \(option)~~ | | `--epoch-resume`, `-er` | The epoch to resume counting from when using `--resume-path`. Prevents unintended overwriting of existing weight files. ~~Optional[int] \(option)~~ | | `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index de0b3d36c..48496bfd1 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -295,23 +295,23 @@ factories. > return Model("custom", forward, dims={"nO": nO}) > ``` -| Registry name | Description | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `architectures` | Registry for functions that create [model architectures](/api/architectures). Can be used to register custom model architectures and reference them in the `config.cfg`. | -| `factories` | Registry for functions that create [pipeline components](/usage/processing-pipelines#custom-components). Added automatically when you use the `@spacy.component` decorator and also reads from [entry points](/usage/saving-loading#entry-points) | -| `tokenizers` | Registry for tokenizer factories. Registered functions should return a callback that receives the `nlp` object and returns a [`Tokenizer`](/api/tokenizer) or a custom callable. | -| `languages` | Registry for language-specific `Language` subclasses. Automatically reads from [entry points](/usage/saving-loading#entry-points). | -| `lookups` | Registry for large lookup tables available via `vocab.lookups`. | -| `displacy_colors` | Registry for custom color scheme for the [`displacy` NER visualizer](/usage/visualizers). Automatically reads from [entry points](/usage/saving-loading#entry-points). | -| `assets` | Registry for data assets, knowledge bases etc. | -| `callbacks` | Registry for custom callbacks to [modify the `nlp` object](/usage/training#custom-code-nlp-callbacks) before training. | -| `readers` | Registry for training and evaluation data readers like [`Corpus`](/api/corpus). | -| `batchers` | Registry for training and evaluation [data batchers](#batchers). | -| `optimizers` | Registry for functions that create [optimizers](https://thinc.ai/docs/api-optimizers). | -| `schedules` | Registry for functions that create [schedules](https://thinc.ai/docs/api-schedules). | -| `layers` | Registry for functions that create [layers](https://thinc.ai/docs/api-layers). | -| `losses` | Registry for functions that create [losses](https://thinc.ai/docs/api-loss). | -| `initializers` | Registry for functions that create [initializers](https://thinc.ai/docs/api-initializers). | +| Registry name | Description | +| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `architectures` | Registry for functions that create [model architectures](/api/architectures). Can be used to register custom model architectures and reference them in the `config.cfg`. | +| `factories` | Registry for functions that create [pipeline components](/usage/processing-pipelines#custom-components). Added automatically when you use the `@spacy.component` decorator and also reads from [entry points](/usage/saving-loading#entry-points). | +| `tokenizers` | Registry for tokenizer factories. Registered functions should return a callback that receives the `nlp` object and returns a [`Tokenizer`](/api/tokenizer) or a custom callable. | +| `languages` | Registry for language-specific `Language` subclasses. Automatically reads from [entry points](/usage/saving-loading#entry-points). | +| `lookups` | Registry for large lookup tables available via `vocab.lookups`. | +| `displacy_colors` | Registry for custom color scheme for the [`displacy` NER visualizer](/usage/visualizers). Automatically reads from [entry points](/usage/saving-loading#entry-points). | +| `assets` | Registry for data assets, knowledge bases etc. | +| `callbacks` | Registry for custom callbacks to [modify the `nlp` object](/usage/training#custom-code-nlp-callbacks) before training. | +| `readers` | Registry for training and evaluation data readers like [`Corpus`](/api/corpus). | +| `batchers` | Registry for training and evaluation [data batchers](#batchers). | +| `optimizers` | Registry for functions that create [optimizers](https://thinc.ai/docs/api-optimizers). | +| `schedules` | Registry for functions that create [schedules](https://thinc.ai/docs/api-schedules). | +| `layers` | Registry for functions that create [layers](https://thinc.ai/docs/api-layers). | +| `losses` | Registry for functions that create [losses](https://thinc.ai/docs/api-loss). | +| `initializers` | Registry for functions that create [initializers](https://thinc.ai/docs/api-initializers). | ### spacy-transformers registry {#registry-transformers} diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 7ce9457f9..2f71ed729 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -5,7 +5,7 @@ menu: - ['Introduction', 'basics'] - ['Quickstart', 'quickstart'] - ['Config System', 'config'] - - ['Custom Models', 'custom-models'] + - ['Custom Functions', 'custom-functions'] - ['Transfer Learning', 'transfer-learning'] - ['Parallel Training', 'parallel-training'] - ['Internal API', 'api'] @@ -127,7 +127,7 @@ Some of the main advantages and features of spaCy's training config are: [optimizers](https://thinc.ai/docs/api-optimizers) or [schedules](https://thinc.ai/docs/api-schedules) and define arguments that are passed into them. You can also register your own functions to define - [custom architectures](#custom-models), reference them in your config and + [custom architectures](#custom-functions), reference them in your config and tweak their parameters. - **Interpolation.** If you have hyperparameters or other settings used by multiple components, define them once and reference them as @@ -299,7 +299,7 @@ case [`compounding.v1`](https://thinc.ai/docs/api-schedules#compounding) defined in the [function registry](/api/top-level#registry). All other values defined in the block are passed to the function as keyword arguments when it's initialized. You can also use this mechanism to register -[custom implementations and architectures](#custom-models) and reference them +[custom implementations and architectures](#custom-functions) and reference them from your configs. > #### How the config is resolved @@ -481,7 +481,7 @@ still look good.
-## Custom model implementations and architectures {#custom-models} +## Custom model implementations and architectures {#custom-functions} From 7a2e6a96f51efc5a1dcf3671d072930c984c9e78 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 16:54:16 +0200 Subject: [PATCH 36/92] fix typo --- website/docs/usage/v3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index ffed1c89f..837818a83 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -44,7 +44,7 @@ menu: -### Custom models using any framework {#feautres-custom-models} +### Custom models using any framework {#features-custom-models} ### Manage end-to-end workflows with projects {#features-projects} From 4906a2ae6c67757c179556b33d5c6a47d712b455 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 17:32:35 +0200 Subject: [PATCH 37/92] custom functions intro --- website/docs/usage/training.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 2f71ed729..259d3bb39 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -481,9 +481,23 @@ still look good. -## Custom model implementations and architectures {#custom-functions} +## Custom Functions {#custom-functions} - +Registered functions in the training config files can refer to built-in +implementations, but you can also plug in fully custom implementations. To do +so, you first write your own implementation of a custom architectures, data +reader or any other functionality, and then register this function with the +correct [registry](/api/top-level#registry). This allows you to plug in models +defined in PyTorch or Tensorflow, make custom modifications to the `nlp` object, +create custom optimizers or schedules, or write a function that streams in data +and preprocesses it on the fly while training. + +Each custom function can have any numbers of arguments that should be passed +into them through the config similar as with the built-in functions. If your +function defines **default argument values**, spaCy is able to auto-fill your +config when you run [`init fill-config`](/api/cli#init-fill-config). If you want +to make sure that a given parameter is always explicitely set in the config, +avoid setting a default value for it. ### Training with custom code {#custom-code} @@ -642,11 +656,7 @@ In your config, you can now reference the schedule in the starting with an `@`, it's interpreted as a reference to a function. All other settings in the block will be passed to the function as keyword arguments. Keep in mind that the config shouldn't have any hidden defaults and all arguments on -the functions need to be represented in the config. If your function defines -**default argument values**, spaCy is able to auto-fill your config when you run -[`init fill-config`](/api/cli#init-fill-config). If you want to make sure that a -given parameter is always explicitely set in the config, avoid setting a default -value for it. +the functions need to be represented in the config. ```ini ### config.cfg (excerpt) From 7119295a8aa05bc481e53af2e861103793f698fc Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 17:53:22 +0200 Subject: [PATCH 38/92] badgers intro --- website/docs/api/top-level.md | 12 +++++++++++- website/docs/usage/training.md | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 48496bfd1..2398cb632 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -340,7 +340,17 @@ See the [`Transformer`](/api/transformer) API reference and ## Batchers {#batchers source="spacy/gold/batchers.py" new="3"} - +A batcher implements a batching strategy that essentially turns a stream of +items into a stream of batches, with each batch consisting of one item or a list +of items. During training, the models update their weights after processing one +batch at a time. Typical batching strategies include presenting the training +data as a stream of batches with similar sizes, or with increasing batch sizes. +See the Thinc documentation on +[`schedules`](https://thinc.ai/docs/api-schedules) for a few standard examples. + +Instead of using one of the built-in batchers listed here, you can also +[implement your own](/usage/training#custom-code-readers-batchers), which may or +may not use a custom schedule. #### batch_by_words.v1 {#batch_by_words tag="registered function"} diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 259d3bb39..c4eaf1d88 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -743,7 +743,7 @@ the annotations are exactly the same. ```python ### functions.py -from typing import Callable, Iterable, Iterator +from typing import Callable, Iterable, Iterator, List import spacy from spacy.gold import Example From d8f6abdc23525a1ead51e610e3d783759017ad84 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 18:00:35 +0200 Subject: [PATCH 39/92] add linking TODO back in --- website/docs/usage/training.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 739403625..1579e61ea 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -499,6 +499,8 @@ config when you run [`init fill-config`](/api/cli#init-fill-config). If you want to make sure that a given parameter is always explicitely set in the config, avoid setting a default value for it. + + ### Training with custom code {#custom-code} > #### Example From b96cd9fa5e57895c69b94c8862196391ddc6bbf8 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 18:46:08 +0200 Subject: [PATCH 40/92] fix typo --- spacy/cli/templates/quickstart_training_recommendations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spacy/cli/templates/quickstart_training_recommendations.yml b/spacy/cli/templates/quickstart_training_recommendations.yml index efb6da2be..206e69954 100644 --- a/spacy/cli/templates/quickstart_training_recommendations.yml +++ b/spacy/cli/templates/quickstart_training_recommendations.yml @@ -1,5 +1,5 @@ # Recommended settings and available resources for each language, if available. -# Not all languages have recommended word vecotrs or transformers and for some, +# Not all languages have recommended word vectors or transformers and for some, # the recommended transformer for efficiency and accuracy may be the same. en: word_vectors: en_vectors_web_lg From cb9a2402ee9ed911d900378f7cadce848a65cf8c Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Wed, 19 Aug 2020 19:05:31 +0200 Subject: [PATCH 41/92] Include yml files in cli folder --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index ef42138f1..b4887cdb8 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,5 +5,5 @@ include README.md include pyproject.toml recursive-exclude spacy/lang *.json recursive-include spacy/lang *.json.gz -recursive-include spacy/cli *.json +recursive-include spacy/cli *.json *.yml recursive-include licenses * From 85b39639e1ee84b8794249572cf948f2ea500837 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 19:17:36 +0200 Subject: [PATCH 42/92] small fix --- website/docs/api/top-level.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 626c1d858..b33d7f022 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -340,7 +340,7 @@ See the [`Transformer`](/api/transformer) API reference and ## Batchers {#batchers source="spacy/gold/batchers.py" new="3"} -A batcher implements a batching strategy that essentially turns a stream of +A data batcher implements a batching strategy that essentially turns a stream of items into a stream of batches, with each batch consisting of one item or a list of items. During training, the models update their weights after processing one batch at a time. Typical batching strategies include presenting the training From 3dd390b1a117c8e39707e1710c3d0a69e69fb1c0 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 19:46:12 +0200 Subject: [PATCH 43/92] Update Thinc and config variables --- pyproject.toml | 2 +- requirements.txt | 2 +- setup.cfg | 4 ++-- spacy/cli/templates/quickstart_training.jinja | 12 +++++----- spacy/default_config.cfg | 14 +++++------ .../tests/serialize/test_serialize_config.py | 12 +++++----- website/docs/api/architectures.md | 2 +- website/docs/api/corpus.md | 2 +- website/docs/api/data-formats.md | 12 +++++----- website/docs/api/top-level.md | 2 +- website/docs/usage/training.md | 24 ++++++------------- 11 files changed, 39 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1b4972bd5..9a646d0d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ "cymem>=2.0.2,<2.1.0", "preshed>=3.0.2,<3.1.0", "murmurhash>=0.28.0,<1.1.0", - "thinc>=8.0.0a27,<8.0.0a30", + "thinc>=8.0.0a28,<8.0.0a30", "blis>=0.4.0,<0.5.0", "pytokenizations", "smart_open>=2.0.0,<3.0.0" diff --git a/requirements.txt b/requirements.txt index b4901a692..181cb2101 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # Our libraries cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 -thinc>=8.0.0a27,<8.0.0a30 +thinc>=8.0.0a28,<8.0.0a30 blis>=0.4.0,<0.5.0 ml_datasets>=0.1.1 murmurhash>=0.28.0,<1.1.0 diff --git a/setup.cfg b/setup.cfg index a34c34e23..d56eab3a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,13 +34,13 @@ setup_requires = cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 murmurhash>=0.28.0,<1.1.0 - thinc>=8.0.0a27,<8.0.0a30 + thinc>=8.0.0a28,<8.0.0a30 install_requires = # Our libraries murmurhash>=0.28.0,<1.1.0 cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 - thinc>=8.0.0a27,<8.0.0a30 + thinc>=8.0.0a28,<8.0.0a30 blis>=0.4.0,<0.5.0 wasabi>=0.7.1,<1.1.0 srsly>=2.1.0,<3.0.0 diff --git a/spacy/cli/templates/quickstart_training.jinja b/spacy/cli/templates/quickstart_training.jinja index 4f5a2226e..674099abc 100644 --- a/spacy/cli/templates/quickstart_training.jinja +++ b/spacy/cli/templates/quickstart_training.jinja @@ -105,7 +105,7 @@ factory = "tok2vec" [components.tok2vec.model.embed] @architectures = "spacy.MultiHashEmbed.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} rows = {{ 2000 if optimize == "efficiency" else 7000 }} also_embed_subwords = {{ true if has_letters else false }} also_use_static_vectors = {{ true if optimize == "accuracy" else false }} @@ -127,7 +127,7 @@ nO = null [components.tagger.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {%- endif %} {% if "parser" in components -%} @@ -144,7 +144,7 @@ nO = null [components.parser.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {%- endif %} {% if "ner" in components %} @@ -161,7 +161,7 @@ nO = null [components.ner.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {% endif %} {% endif %} @@ -194,12 +194,12 @@ initial_rate = 5e-5 [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} max_length = {{ 500 if hardware == "gpu" else 0 }} [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} max_length = 0 {% if use_transformer %} diff --git a/spacy/default_config.cfg b/spacy/default_config.cfg index 8aadad668..3eab21888 100644 --- a/spacy/default_config.cfg +++ b/spacy/default_config.cfg @@ -23,12 +23,12 @@ after_pipeline_creation = null # Training hyper-parameters and additional features. [training] -seed = ${system:seed} +seed = ${system.seed} dropout = 0.1 accumulate_gradient = 1 # Extra resources for transfer-learning or pseudo-rehearsal -init_tok2vec = ${paths:init_tok2vec} -raw_text = ${paths:raw} +init_tok2vec = ${paths.init_tok2vec} +raw_text = ${paths.raw} vectors = null # Controls early-stopping. 0 or -1 mean unlimited. patience = 1600 @@ -42,7 +42,7 @@ frozen_components = [] [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} # Whether to train on sequences with 'gold standard' sentence boundaries # and tokens. If you set this to true, take care to ensure your run-time # data is passed in sentence-by-sentence via some prior preprocessing. @@ -54,7 +54,7 @@ limit = 0 [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} # Whether to train on sequences with 'gold standard' sentence boundaries # and tokens. If you set this to true, take care to ensure your run-time # data is passed in sentence-by-sentence via some prior preprocessing. @@ -98,8 +98,8 @@ max_length = 500 dropout = 0.2 n_save_every = null batch_size = 3000 -seed = ${system:seed} -use_pytorch_for_gpu_memory = ${system:use_pytorch_for_gpu_memory} +seed = ${system.seed} +use_pytorch_for_gpu_memory = ${system.use_pytorch_for_gpu_memory} tok2vec_model = "components.tok2vec.model" [pretraining.objective] diff --git a/spacy/tests/serialize/test_serialize_config.py b/spacy/tests/serialize/test_serialize_config.py index 1de137e81..f2b496d71 100644 --- a/spacy/tests/serialize/test_serialize_config.py +++ b/spacy/tests/serialize/test_serialize_config.py @@ -20,11 +20,11 @@ dev = "" [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} [training.batcher] @batchers = "batch_by_words.v1" @@ -57,7 +57,7 @@ factory = "tagger" [components.tagger.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model:width} +width = ${components.tok2vec.model.width} """ @@ -284,13 +284,13 @@ def test_config_overrides(): def test_config_interpolation(): config = Config().from_str(nlp_config_string, interpolate=False) - assert config["training"]["train_corpus"]["path"] == "${paths:train}" + assert config["training"]["train_corpus"]["path"] == "${paths.train}" interpolated = config.interpolate() assert interpolated["training"]["train_corpus"]["path"] == "" nlp = English.from_config(config) - assert nlp.config["training"]["train_corpus"]["path"] == "${paths:train}" + assert nlp.config["training"]["train_corpus"]["path"] == "${paths.train}" # Ensure that variables are preserved in nlp config - width = "${components.tok2vec.model:width}" + width = "${components.tok2vec.model.width}" assert config["components"]["tagger"]["model"]["tok2vec"]["width"] == width assert nlp.config["components"]["tagger"]["model"]["tok2vec"]["width"] == width interpolated2 = nlp.config.interpolate() diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index 446e6c7c3..5037c1f99 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -94,7 +94,7 @@ blog post for background. > > [components.tagger.model.tok2vec] > @architectures = "spacy.Tok2VecListener.v1" -> width = ${components.tok2vec.model:width} +> width = ${components.tok2vec.model.width} > ``` A listener is used as a sublayer within a component such as a diff --git a/website/docs/api/corpus.md b/website/docs/api/corpus.md index 8c530ab6d..86cfa9121 100644 --- a/website/docs/api/corpus.md +++ b/website/docs/api/corpus.md @@ -28,7 +28,7 @@ streaming. > > [training.train_corpus] > @readers = "spacy.Corpus.v1" -> path = ${paths:train} +> path = ${paths.train} > gold_preproc = false > max_length = 0 > limit = 0 diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index ff106b229..87f3ecbf2 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -111,7 +111,7 @@ model to copy components from). See the docs on ### paths, system {#config-variables tag="variables"} These sections define variables that can be referenced across the other sections -as variables. For example `${paths:train}` uses the value of `train` defined in +as variables. For example `${paths.train}` uses the value of `train` defined in the block `[paths]`. If your config includes custom registered functions that need paths, you can define them here. All config values can also be [overwritten](/usage/training#config-overrides) on the CLI when you run @@ -131,11 +131,11 @@ process that are used when you run [`spacy train`](/api/cli#train). | Name | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `seed` | The random seed. Defaults to variable `${system:seed}`. ~~int~~ | +| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | | `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | -| `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths:init_tok2vec}`. ~~Optional[str]~~ | -| `raw_text` | TODO: ... Defaults to variable `${paths:raw}`. ~~Optional[str]~~ | +| `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths.init_tok2vec}`. ~~Optional[str]~~ | +| `raw_text` | TODO: ... Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | | `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | | `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | | `max_epochs` | Maximum number of epochs to train for. Defaults to `0`. ~~int~~ | @@ -162,8 +162,8 @@ run [`spacy pretrain`](/api/cli#pretrain). | `dropout` | The dropout rate. Defaults to `0.2`. ~~float~~ | | `n_save_every` | Saving frequency. Defaults to `null`. ~~Optional[int]~~ | | `batch_size` | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). Defaults to `3000`. ~~Union[int, Sequence[int]]~~ | -| `seed` | The random seed. Defaults to variable `${system:seed}`. ~~int~~ | -| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. Defaults to variable `${system:use_pytorch_for_gpu_memory}`. ~~bool~~ | +| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | +| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. Defaults to variable `${system.use_pytorch_for_gpu_memory}`. ~~bool~~ | | `tok2vec_model` | The model section of the embedding component in the config. Defaults to `"components.tok2vec.model"`. ~~str~~ | | `objective` | The pretraining objective. Defaults to `{"type": "characters", "n_characters": 4}`. ~~Dict[str, Any]~~ | | `optimizer` | The optimizer. Defaults to [`Adam`](https://thinc.ai/docs/api-optimizers#adam). ~~Optimizer~~ | diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 646b685f0..daa945724 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -602,7 +602,7 @@ components are created, as well as all training settings and hyperparameters. | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | Path to the model's `config.cfg`. ~~Union[str, Path]~~ | | `overrides` | Optional config overrides to replace in loaded config. Can be provided as nested dict, or as flat dict with keys in dot notation, e.g. `"nlp.pipeline"`. ~~Dict[str, Any]~~ | -| `interpolate` | Whether to interpolate the config and replace variables like `${paths:train}` with their values. Defaults to `False`. ~~bool~~ | +| `interpolate` | Whether to interpolate the config and replace variables like `${paths.train}` with their values. Defaults to `False`. ~~bool~~ | | **RETURNS** | The model's config. ~~Config~~ | ### util.load_meta {#util.load_meta tag="function" new="3"} diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index f7a74bbcc..22153e305 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -157,8 +157,8 @@ sections of a config file are: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nlp` | Definition of the `nlp` object, its tokenizer and [processing pipeline](/usage/processing-pipelines) component names. | | `components` | Definitions of the [pipeline components](/usage/processing-pipelines) and their models. | -| `paths` | Paths to data and other assets. Re-used across the config as variables, e.g. `${paths:train}`, and can be [overwritten](#config-overrides) on the CLI. | -| `system` | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system:seed}`, and can be [overwritten](#config-overrides) on the CLI. | +| `paths` | Paths to data and other assets. Re-used across the config as variables, e.g. `${paths.train}`, and can be [overwritten](#config-overrides) on the CLI. | +| `system` | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system.seed}`, and can be [overwritten](#config-overrides) on the CLI. | | `training` | Settings and controls for the training and evaluation process. | | `pretraining` | Optional settings and controls for the [language model pretraining](#pretraining). | @@ -325,19 +325,9 @@ compound = 1.001 Another very useful feature of the config system is that it supports variable interpolation for both **values and sections**. This means that you only need to define a setting once and can reference it across your config using the -`${section:value}` or `${section.block}` syntax. In this example, the value of -`seed` is reused within the `[training]` block, and the whole block of -`[training.optimizer]` is reused in `[pretraining]` and will become -`pretraining.optimizer`. - -> #### Note on syntax -> -> There are two different ways to format your variables, depending on whether -> you want to reference a single value or a block. Values are specified after a -> `:`, while blocks are specified with a `.`: -> -> 1. `${section:value}`, `${section.subsection:value}` -> 2. `${section.block}`, `${section.subsection.block}` +`${section.value}` syntax. In this example, the value of `seed` is reused within +the `[training]` block, and the whole block of `[training.optimizer]` is reused +in `[pretraining]` and will become `pretraining.optimizer`. ```ini ### config.cfg (excerpt) {highlight="5,18"} @@ -345,7 +335,7 @@ define a setting once and can reference it across your config using the seed = 0 [training] -seed = ${system:seed} +seed = ${system.seed} [training.optimizer] @optimizers = "Adam.v1" @@ -369,7 +359,7 @@ to a string. [paths] version = 5 root = "/Users/you/data" -train = "${paths:root}/train_${paths:version}.spacy" +train = "${paths.root}/train_${paths.version}.spacy" # Result: /Users/you/data/train_5.spacy ``` From 7d9f00bdbf45120345972516693e369b61b5dbf3 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 19:53:00 +0200 Subject: [PATCH 44/92] waltzing schedule --- website/docs/api/top-level.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 626c1d858..91f7e276c 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -282,17 +282,18 @@ concept of function registries. spaCy also uses the function registry for language subclasses, model architecture, lookups and pipeline component factories. - - > #### Example > > ```python +> from typing import Iterator > import spacy -> from thinc.api import Model > -> @spacy.registry.architectures("CustomNER.v1") -> def custom_ner(n0: int) -> Model: -> return Model("custom", forward, dims={"nO": nO}) +> @spacy.registry.schedules("waltzing.v1") +> def waltzing() -> Iterator[float]: +> i = 0 +> while True: +> yield i % 3 + 1 +> i += 1 > ``` | Registry name | Description | From 09f3cfc985f186555d6255950c0970528cd25235 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Wed, 19 Aug 2020 19:58:45 +0200 Subject: [PATCH 45/92] add version --- website/docs/api/doc.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/website/docs/api/doc.md b/website/docs/api/doc.md index e8ce7343d..3c4825f0d 100644 --- a/website/docs/api/doc.md +++ b/website/docs/api/doc.md @@ -317,9 +317,7 @@ array of attributes. | `exclude` | String names of [serialization fields](#serialization-fields) to exclude. ~~Iterable[str]~~ | | **RETURNS** | The `Doc` itself. ~~Doc~~ | -## Doc.from_docs {#from_docs tag="staticmethod"} - - +## Doc.from_docs {#from_docs tag="staticmethod" new="3"} Concatenate multiple `Doc` objects to form a new one. Raises an error if the `Doc` objects do not all share the same `Vocab`. From 15e6feed016966a15774c839d77c74d9a633d264 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 20:37:54 +0200 Subject: [PATCH 46/92] Update docs [ci skip] --- website/docs/usage/training.md | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 1579e61ea..8951b9396 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -484,20 +484,21 @@ still look good. ## Custom Functions {#custom-functions} Registered functions in the training config files can refer to built-in -implementations, but you can also plug in fully custom implementations. To do -so, you first write your own implementation of a custom architectures, data -reader or any other functionality, and then register this function with the -correct [registry](/api/top-level#registry). This allows you to plug in models -defined in PyTorch or Tensorflow, make custom modifications to the `nlp` object, -create custom optimizers or schedules, or write a function that streams in data -and preprocesses it on the fly while training. +implementations, but you can also plug in fully **custom implementations**. All +you need to do is register your function using the `@spacy.registry` decorator +with the name of the respective [registry](/api/top-level#registry), e.g. +`@spacy.registry.architectures`, and a string name to assign to your function. +Registering custom functions allows you to **plug in models** defined in PyTorch +or TensorFlow, make **custom modifications** to the `nlp` object, create custom +optimizers or schedules, or **stream in data** and preprocesses it on the fly +while training. -Each custom function can have any numbers of arguments that should be passed -into them through the config similar as with the built-in functions. If your -function defines **default argument values**, spaCy is able to auto-fill your -config when you run [`init fill-config`](/api/cli#init-fill-config). If you want -to make sure that a given parameter is always explicitely set in the config, -avoid setting a default value for it. +Each custom function can have any numbers of arguments that are passed in via +the [config](#config), just the built-in functions. If your function defines +**default argument values**, spaCy is able to auto-fill your config when you run +[`init fill-config`](/api/cli#init-fill-config). If you want to make sure that a +given parameter is always explicitely set in the config, avoid setting a default +value for it. From 2253d26b8211e1cb09ee2e59d187e03d6e0c5fc7 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Wed, 19 Aug 2020 21:18:26 +0200 Subject: [PATCH 47/92] Update vectors and similarity docs [ci skip] --- website/docs/images/sense2vec.jpg | Bin 0 -> 229159 bytes website/docs/usage/101/_vectors-similarity.md | 72 +++++++++++++++--- website/docs/usage/linguistic-features.md | 17 ----- website/src/components/link.js | 4 +- website/src/components/util.js | 11 +++ 5 files changed, 73 insertions(+), 31 deletions(-) create mode 100644 website/docs/images/sense2vec.jpg diff --git a/website/docs/images/sense2vec.jpg b/website/docs/images/sense2vec.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3a1772582cf90f02b565c71e1d18a4c4b27a4291 GIT binary patch literal 229159 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

Xi%bg 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

w)KLQu{ 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 JKXCqD{Xf_6lcfLv literal 0 HcmV?d00001 diff --git a/website/docs/usage/101/_vectors-similarity.md b/website/docs/usage/101/_vectors-similarity.md index a04c96236..92df1b331 100644 --- a/website/docs/usage/101/_vectors-similarity.md +++ b/website/docs/usage/101/_vectors-similarity.md @@ -80,25 +80,73 @@ duplicate if it's very similar to an already existing one. Each [`Doc`](/api/doc), [`Span`](/api/span), [`Token`](/api/token) and [`Lexeme`](/api/lexeme) comes with a [`.similarity`](/api/token#similarity) method that lets you compare it with another object, and determine the -similarity. Of course similarity is always subjective – whether "dog" and "cat" -are similar really depends on how you're looking at it. spaCy's similarity model -usually assumes a pretty general-purpose definition of similarity. +similarity. Of course similarity is always subjective – whether two words, spans +or documents are similar really depends on how you're looking at it. spaCy's +similarity model usually assumes a pretty general-purpose definition of +similarity. - +> #### 📝 Things to try +> +> 1. Compare two different tokens and try to find the two most _dissimilar_ +> tokens in the texts with the lowest similarity score (according to the +> vectors). +> 2. Compare the similarity of two [`Lexeme`](/api/lexeme) objects, entries in +> the vocabulary. You can get a lexeme via the `.lex` attribute of a token. +> You should see that the similarity results are identical to the token +> similarity. ```python ### {executable="true"} import spacy nlp = spacy.load("en_core_web_md") # make sure to use larger model! -tokens = nlp("dog cat banana") +doc1 = nlp("I like salty fries and hamburgers.") +doc2 = nlp("Fast food tastes very good.") -for token1 in tokens: - for token2 in tokens: - print(token1.text, token2.text, token1.similarity(token2)) +# Similarity of two documents +print(doc1, "<->", doc2, doc1.similarity(doc2)) +# Similarity of tokens and spans +french_fries = doc1[2:4] +burgers = doc1[5] +print(french_fries, "<->", burgers, french_fries.similarity(burgers)) ``` -In this case, the model's predictions are pretty on point. A dog is very similar -to a cat, whereas a banana is not very similar to either of them. Identical -tokens are obviously 100% similar to each other (just not always exactly `1.0`, -because of vector math and floating point imprecisions). +### What to expect from similarity results {#similarity-expectations} + +Computing similarity scores can be helpful in many situations, but it's also +important to maintain **realistic expectations** about what information it can +provide. Words can be related to each over in many ways, so a single +"similarity" score will always be a **mix of different signals**, and vectors +trained on different data can produce very different results that may not be +useful for your purpose. Here are some important considerations to keep in mind: + +- There's no objective definition of similarity. Whether "I like burgers" and "I + like pasta" is similar **depends on your application**. Both talk about food + preferences, which makes them very similar – but if you're analyzing mentions + of food, those sentences are pretty dissimilar, because they talk about very + different foods. +- The similarity of [`Doc`](/api/doc) and [`Span`](/api/span) objects defaults + to the **average** of the token vectors. This means that the vector for "fast + food" is the average of the vectors for "fast" and "food", which isn't + necessarily representative of the phrase "fast food". +- Vector averaging means that the vector of multiple tokens is **insensitive to + the order** of the words. Two documents expressing the same meaning with + dissimilar wording will return a lower similarity score than two documents + that happen to contain the same words while expressing different meanings. + + + +[![](../../images/sense2vec.jpg)](https://github.com/explosion/sense2vec) + +[`sense2vec`](https://github.com/explosion/sense2vec) is a library developed by +us that builds on top of spaCy and lets you train and query more interesting and +detailed word vectors. It combines noun phrases like "fast food" or "fair game" +and includes the part-of-speech tags and entity labels. The library also +includes annotation recipes for our annotation tool [Prodigy](https://prodi.gy) +that let you evaluate vector models and create terminology lists. For more +details, check out +[our blog post](https://explosion.ai/blog/sense2vec-reloaded). To explore the +semantic similarities across all Reddit comments of 2015 and 2019, see the +[interactive demo](https://explosion.ai/demos/sense2vec). + + diff --git a/website/docs/usage/linguistic-features.md b/website/docs/usage/linguistic-features.md index 10efcf875..3aa0df7b4 100644 --- a/website/docs/usage/linguistic-features.md +++ b/website/docs/usage/linguistic-features.md @@ -1547,23 +1547,6 @@ import Vectors101 from 'usage/101/\_vectors-similarity.md' - - -Computing similarity scores can be helpful in many situations, but it's also -important to maintain **realistic expectations** about what information it can -provide. Words can be related to each over in many ways, so a single -"similarity" score will always be a **mix of different signals**, and vectors -trained on different data can produce very different results that may not be -useful for your purpose. - -Also note that the similarity of `Doc` or `Span` objects defaults to the -**average** of the token vectors. This means it's insensitive to the order of -the words. Two documents expressing the same meaning with dissimilar wording -will return a lower similarity score than two documents that happen to contain -the same words while expressing different meanings. - - - ### Adding word vectors {#adding-vectors} Custom word vectors can be trained using a number of open-source libraries, such diff --git a/website/src/components/link.js b/website/src/components/link.js index 3644479c5..acded7d0d 100644 --- a/website/src/components/link.js +++ b/website/src/components/link.js @@ -6,7 +6,7 @@ import classNames from 'classnames' import Icon from './icon' import classes from '../styles/link.module.sass' -import { isString } from './util' +import { isString, isImage } from './util' const internalRegex = /(http(s?)):\/\/(prodi.gy|spacy.io|irl.spacy.io|explosion.ai|course.spacy.io)/gi @@ -39,7 +39,7 @@ export default function Link({ const dest = to || href const external = forceExternal || /(http(s?)):\/\//gi.test(dest) const icon = getIcon(dest) - const withIcon = !hidden && !hideIcon && !!icon + const withIcon = !hidden && !hideIcon && !!icon && !isImage(children) const sourceWithText = withIcon && isString(children) const linkClassNames = classNames(classes.root, className, { [classes.hidden]: hidden, diff --git a/website/src/components/util.js b/website/src/components/util.js index 844f2c133..a9c6efcf5 100644 --- a/website/src/components/util.js +++ b/website/src/components/util.js @@ -46,6 +46,17 @@ export function isString(obj) { return typeof obj === 'string' || obj instanceof String } +/** + * @param obj - The object to check. + * @returns {boolean} – Whether the object is an image + */ +export function isImage(obj) { + if (!obj || !React.isValidElement(obj)) { + return false + } + return obj.props.name == 'img' || obj.props.className == 'gatsby-resp-image-wrapper' +} + /** * @param obj - The object to check. * @returns {boolean} - Whether the object is empty. From 229033831aeeb4a78d9ebaa98ebfe1f06d6f9d28 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 20 Aug 2020 10:00:45 +0200 Subject: [PATCH 48/92] add explanation of raw_text --- website/docs/api/data-formats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index ff106b229..444dc0003 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -135,7 +135,7 @@ process that are used when you run [`spacy train`](/api/cli#train). | `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | | `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths:init_tok2vec}`. ~~Optional[str]~~ | -| `raw_text` | TODO: ... Defaults to variable `${paths:raw}`. ~~Optional[str]~~ | +| `raw_text` | Optional path to a jsonl file with unlabelled text documents for a [rehearsel](/api/language#rehearse) step. Defaults to variable `${paths:raw}`. ~~Optional[str]~~ | | `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | | `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | | `max_epochs` | Maximum number of epochs to train for. Defaults to `0`. ~~int~~ | From ae719b354fad17e42092f60f60b80fe71ce8825e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Thu, 20 Aug 2020 10:20:40 +0200 Subject: [PATCH 49/92] fix typos --- website/docs/api/data-formats.md | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index 0d19c797a..fd527935a 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -5,7 +5,7 @@ menu: - ['Training Config', 'config'] - ['Training Data', 'training'] - ['Pretraining Data', 'pretraining'] - - ['Vocabulary', 'vocab'] + - ['Vocabulary', 'vocab-jsonl'] - ['Model Meta', 'meta'] --- @@ -391,10 +391,10 @@ tokenization can be provided. > srsly.write_jsonl("/path/to/text.jsonl", data) > ``` -| Key | Description | -| -------- | ------------------------------------------------------------------ | -| `text` | The raw input text. Is not required if `tokens` available. ~~str~~ | -| `tokens` | Optional tokenization, one string per token. ~~List[str]~~ | +| Key | Description | +| -------- | --------------------------------------------------------------------- | +| `text` | The raw input text. Is not required if `tokens` is available. ~~str~~ | +| `tokens` | Optional tokenization, one string per token. ~~List[str]~~ | ```json ### Example @@ -407,7 +407,7 @@ tokenization can be provided. ## Lexical data for vocabulary {#vocab-jsonl new="2"} To populate a model's vocabulary, you can use the -[`spacy init-model`](/api/cli#init-model) command and load in a +[`spacy init model`](/api/cli#init-model) command and load in a [newline-delimited JSON](http://jsonlines.org/) (JSONL) file containing one lexical entry per line via the `--jsonl-loc` option. The first line defines the language and vocabulary settings. All other lines are expected to be JSON @@ -510,23 +510,23 @@ of truth** used for loading a model. > } > ``` -| Name | Description | -| ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `lang` | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Defaults to `"en"`. ~~str~~ | -| `name` | Model name, e.g. `"core_web_sm"`. The final model package name will be `{lang}_{name}`. Defaults to `"model"`. ~~str~~ | -| `version` | Model version. Will be used to version a Python package created with [`spacy package`](/api/cli#package). Defaults to `"0.0.0"`. ~~str~~ | -| `spacy_version` | spaCy version range the model is compatible with. Defaults to spaCy version used to create the model, up to next minor version, which is the default compatibility for the available [pretrained models](/models). For instance, a model trained with v3.0.0 will have the version range `">=3.0.0,<3.1.0"`. ~~str~~ | -| `parent_package` | Name of the spaCy package. Typically `"spacy"` or `"spacy_nightly"`. Defaults to `"spacy"`. ~~str~~ | -| `description` | Model description. Also used for Python package. Defaults to `""`. ~~str~~ | -| `author` | Model author name. Also used for Python package. Defaults to `""`. ~~str~~ | -| `email` | Model author email. Also used for Python package. Defaults to `""`. ~~str~~ | -| `url` | Model author URL. Also used for Python package. Defaults to `""`. ~~str~~ | -| `license` | Model license. Also used for Python package. Defaults to `""`. ~~str~~ | -| `sources` | Data sources used to train the model. Typically a list of dicts with the keys `"name"`, `"url"`, `"author"` and `"license"`. [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `None`. ~~Optional[List[Dict[str, str]]]~~ | -| `vectors` | Information about the word vectors included with the model. Typically a dict with the keys `"width"`, `"vectors"` (number of vectors), `"keys"` and `"name"`. ~~Dict[str, Any]~~ | -| `pipeline` | Names of pipeline component names in the model, in order. Corresponds to [`nlp.pipe_names`](/api/language#pipe_names). Only exists for reference and is not used to create the components. This information is defined in the [`config.cfg`](/api/data-formats#config). Defaults to `[]`. ~~List[str]~~ | -| `labels` | Label schemes of the trained pipeline components, keyed by component name. Corresponds to [`nlp.pipe_labels`](/api/language#pipe_labels). [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `{}`. ~~Dict[str, Dict[str, List[str]]]~~ | -| `accuracy` | Training accuracy, added automatically by [`spacy train`](/api/cli#train). Dictionary of [score names](/usage/training#metrics) mapped to scores. Defaults to `{}`. ~~Dict[str, Union[float, Dict[str, float]]]~~ | -| `speed` | Model speed, added automatically by [`spacy train`](/api/cli#train). Typically a dictionary with the keys `"cpu"`, `"gpu"` and `"nwords"` (words per second). Defaults to `{}`. ~~Dict[str, Optional[Union[float, str]]]~~ | -| `spacy_git_version` 3 | Git commit of [`spacy`](https://github.com/explosion/spaCy) used to create model. ~~str~~ | -| other | Any other custom meta information you want to add. The data is preserved in [`nlp.meta`](/api/language#meta). ~~Any~~ | +| Name | Description | +| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `lang` | Model language [ISO code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). Defaults to `"en"`. ~~str~~ | +| `name` | Model name, e.g. `"core_web_sm"`. The final model package name will be `{lang}_{name}`. Defaults to `"model"`. ~~str~~ | +| `version` | Model version. Will be used to version a Python package created with [`spacy package`](/api/cli#package). Defaults to `"0.0.0"`. ~~str~~ | +| `spacy_version` | spaCy version range the model is compatible with. Defaults to the spaCy version used to create the model, up to next minor version, which is the default compatibility for the available [pretrained models](/models). For instance, a model trained with v3.0.0 will have the version range `">=3.0.0,<3.1.0"`. ~~str~~ | +| `parent_package` | Name of the spaCy package. Typically `"spacy"` or `"spacy_nightly"`. Defaults to `"spacy"`. ~~str~~ | +| `description` | Model description. Also used for Python package. Defaults to `""`. ~~str~~ | +| `author` | Model author name. Also used for Python package. Defaults to `""`. ~~str~~ | +| `email` | Model author email. Also used for Python package. Defaults to `""`. ~~str~~ | +| `url` | Model author URL. Also used for Python package. Defaults to `""`. ~~str~~ | +| `license` | Model license. Also used for Python package. Defaults to `""`. ~~str~~ | +| `sources` | Data sources used to train the model. Typically a list of dicts with the keys `"name"`, `"url"`, `"author"` and `"license"`. [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `None`. ~~Optional[List[Dict[str, str]]]~~ | +| `vectors` | Information about the word vectors included with the model. Typically a dict with the keys `"width"`, `"vectors"` (number of vectors), `"keys"` and `"name"`. ~~Dict[str, Any]~~ | +| `pipeline` | Names of pipeline component names in the model, in order. Corresponds to [`nlp.pipe_names`](/api/language#pipe_names). Only exists for reference and is not used to create the components. This information is defined in the [`config.cfg`](/api/data-formats#config). Defaults to `[]`. ~~List[str]~~ | +| `labels` | Label schemes of the trained pipeline components, keyed by component name. Corresponds to [`nlp.pipe_labels`](/api/language#pipe_labels). [See here](https://github.com/explosion/spacy-models/tree/master/meta) for examples. Defaults to `{}`. ~~Dict[str, Dict[str, List[str]]]~~ | +| `accuracy` | Training accuracy, added automatically by [`spacy train`](/api/cli#train). Dictionary of [score names](/usage/training#metrics) mapped to scores. Defaults to `{}`. ~~Dict[str, Union[float, Dict[str, float]]]~~ | +| `speed` | Model speed, added automatically by [`spacy train`](/api/cli#train). Typically a dictionary with the keys `"cpu"`, `"gpu"` and `"nwords"` (words per second). Defaults to `{}`. ~~Dict[str, Optional[Union[float, str]]]~~ | +| `spacy_git_version` 3 | Git commit of [`spacy`](https://github.com/explosion/spaCy) used to create model. ~~str~~ | +| other | Any other custom meta information you want to add. The data is preserved in [`nlp.meta`](/api/language#meta). ~~Any~~ | From 410b54e10e46f120ac0440af02344158373ee634 Mon Sep 17 00:00:00 2001 From: Sofie Van Landeghem Date: Thu, 20 Aug 2020 11:15:34 +0200 Subject: [PATCH 50/92] Update website/docs/api/data-formats.md Co-authored-by: Ines Montani --- website/docs/api/data-formats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index fd527935a..8b67aa263 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -135,7 +135,7 @@ process that are used when you run [`spacy train`](/api/cli#train). | `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | | `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths.init_tok2vec}`. ~~Optional[str]~~ | -| `raw_text` | Optional path to a jsonl file with unlabelled text documents for a [rehearsel](/api/language#rehearse) step. Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | +| `raw_text` | Optional path to a jsonl file with unlabelled text documents for a [rehearsal](/api/language#rehearse) step. Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | | `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | | `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | | `max_epochs` | Maximum number of epochs to train for. Defaults to `0`. ~~int~~ | From fb51b55eb9634de544175ff6cca7c7e6712a1716 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Thu, 20 Aug 2020 11:20:43 +0200 Subject: [PATCH 51/92] Add comment [ci skip] --- website/docs/usage/training.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 8951b9396..d4f380c10 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -104,10 +104,10 @@ workflows, from data preprocessing to training and packaging your model. ## Training config {#config} -> #### Migration from spaCy v2.x + Training config files include all **settings and hyperparameters** for training your model. Instead of providing lots of arguments on the command line, you only From 6ad59d59fe5f923ce23cb66d7fb71ca511fd656b Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Thu, 20 Aug 2020 11:20:58 +0200 Subject: [PATCH 52/92] Merge branch 'develop' of https://github.com/explosion/spaCy into develop [ci skip] --- pyproject.toml | 2 +- requirements.txt | 2 +- setup.cfg | 4 ++-- spacy/cli/templates/quickstart_training.jinja | 12 +++++----- spacy/default_config.cfg | 14 +++++------ .../tests/serialize/test_serialize_config.py | 12 +++++----- website/docs/api/architectures.md | 2 +- website/docs/api/corpus.md | 2 +- website/docs/api/data-formats.md | 12 +++++----- website/docs/api/top-level.md | 2 +- website/docs/usage/training.md | 24 ++++++------------- 11 files changed, 39 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1b4972bd5..9a646d0d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ requires = [ "cymem>=2.0.2,<2.1.0", "preshed>=3.0.2,<3.1.0", "murmurhash>=0.28.0,<1.1.0", - "thinc>=8.0.0a27,<8.0.0a30", + "thinc>=8.0.0a28,<8.0.0a30", "blis>=0.4.0,<0.5.0", "pytokenizations", "smart_open>=2.0.0,<3.0.0" diff --git a/requirements.txt b/requirements.txt index b4901a692..181cb2101 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # Our libraries cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 -thinc>=8.0.0a27,<8.0.0a30 +thinc>=8.0.0a28,<8.0.0a30 blis>=0.4.0,<0.5.0 ml_datasets>=0.1.1 murmurhash>=0.28.0,<1.1.0 diff --git a/setup.cfg b/setup.cfg index a34c34e23..d56eab3a6 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,13 +34,13 @@ setup_requires = cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 murmurhash>=0.28.0,<1.1.0 - thinc>=8.0.0a27,<8.0.0a30 + thinc>=8.0.0a28,<8.0.0a30 install_requires = # Our libraries murmurhash>=0.28.0,<1.1.0 cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 - thinc>=8.0.0a27,<8.0.0a30 + thinc>=8.0.0a28,<8.0.0a30 blis>=0.4.0,<0.5.0 wasabi>=0.7.1,<1.1.0 srsly>=2.1.0,<3.0.0 diff --git a/spacy/cli/templates/quickstart_training.jinja b/spacy/cli/templates/quickstart_training.jinja index 4f5a2226e..674099abc 100644 --- a/spacy/cli/templates/quickstart_training.jinja +++ b/spacy/cli/templates/quickstart_training.jinja @@ -105,7 +105,7 @@ factory = "tok2vec" [components.tok2vec.model.embed] @architectures = "spacy.MultiHashEmbed.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} rows = {{ 2000 if optimize == "efficiency" else 7000 }} also_embed_subwords = {{ true if has_letters else false }} also_use_static_vectors = {{ true if optimize == "accuracy" else false }} @@ -127,7 +127,7 @@ nO = null [components.tagger.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {%- endif %} {% if "parser" in components -%} @@ -144,7 +144,7 @@ nO = null [components.parser.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {%- endif %} {% if "ner" in components %} @@ -161,7 +161,7 @@ nO = null [components.ner.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model.encode:width} +width = ${components.tok2vec.model.encode.width} {% endif %} {% endif %} @@ -194,12 +194,12 @@ initial_rate = 5e-5 [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} max_length = {{ 500 if hardware == "gpu" else 0 }} [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} max_length = 0 {% if use_transformer %} diff --git a/spacy/default_config.cfg b/spacy/default_config.cfg index 8aadad668..3eab21888 100644 --- a/spacy/default_config.cfg +++ b/spacy/default_config.cfg @@ -23,12 +23,12 @@ after_pipeline_creation = null # Training hyper-parameters and additional features. [training] -seed = ${system:seed} +seed = ${system.seed} dropout = 0.1 accumulate_gradient = 1 # Extra resources for transfer-learning or pseudo-rehearsal -init_tok2vec = ${paths:init_tok2vec} -raw_text = ${paths:raw} +init_tok2vec = ${paths.init_tok2vec} +raw_text = ${paths.raw} vectors = null # Controls early-stopping. 0 or -1 mean unlimited. patience = 1600 @@ -42,7 +42,7 @@ frozen_components = [] [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} # Whether to train on sequences with 'gold standard' sentence boundaries # and tokens. If you set this to true, take care to ensure your run-time # data is passed in sentence-by-sentence via some prior preprocessing. @@ -54,7 +54,7 @@ limit = 0 [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} # Whether to train on sequences with 'gold standard' sentence boundaries # and tokens. If you set this to true, take care to ensure your run-time # data is passed in sentence-by-sentence via some prior preprocessing. @@ -98,8 +98,8 @@ max_length = 500 dropout = 0.2 n_save_every = null batch_size = 3000 -seed = ${system:seed} -use_pytorch_for_gpu_memory = ${system:use_pytorch_for_gpu_memory} +seed = ${system.seed} +use_pytorch_for_gpu_memory = ${system.use_pytorch_for_gpu_memory} tok2vec_model = "components.tok2vec.model" [pretraining.objective] diff --git a/spacy/tests/serialize/test_serialize_config.py b/spacy/tests/serialize/test_serialize_config.py index 1de137e81..f2b496d71 100644 --- a/spacy/tests/serialize/test_serialize_config.py +++ b/spacy/tests/serialize/test_serialize_config.py @@ -20,11 +20,11 @@ dev = "" [training.train_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:train} +path = ${paths.train} [training.dev_corpus] @readers = "spacy.Corpus.v1" -path = ${paths:dev} +path = ${paths.dev} [training.batcher] @batchers = "batch_by_words.v1" @@ -57,7 +57,7 @@ factory = "tagger" [components.tagger.model.tok2vec] @architectures = "spacy.Tok2VecListener.v1" -width = ${components.tok2vec.model:width} +width = ${components.tok2vec.model.width} """ @@ -284,13 +284,13 @@ def test_config_overrides(): def test_config_interpolation(): config = Config().from_str(nlp_config_string, interpolate=False) - assert config["training"]["train_corpus"]["path"] == "${paths:train}" + assert config["training"]["train_corpus"]["path"] == "${paths.train}" interpolated = config.interpolate() assert interpolated["training"]["train_corpus"]["path"] == "" nlp = English.from_config(config) - assert nlp.config["training"]["train_corpus"]["path"] == "${paths:train}" + assert nlp.config["training"]["train_corpus"]["path"] == "${paths.train}" # Ensure that variables are preserved in nlp config - width = "${components.tok2vec.model:width}" + width = "${components.tok2vec.model.width}" assert config["components"]["tagger"]["model"]["tok2vec"]["width"] == width assert nlp.config["components"]["tagger"]["model"]["tok2vec"]["width"] == width interpolated2 = nlp.config.interpolate() diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index 25a44245d..acdf4cb19 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -94,7 +94,7 @@ blog post for background. > > [components.tagger.model.tok2vec] > @architectures = "spacy.Tok2VecListener.v1" -> width = ${components.tok2vec.model:width} +> width = ${components.tok2vec.model.width} > ``` A listener is used as a sublayer within a component such as a diff --git a/website/docs/api/corpus.md b/website/docs/api/corpus.md index 8c530ab6d..86cfa9121 100644 --- a/website/docs/api/corpus.md +++ b/website/docs/api/corpus.md @@ -28,7 +28,7 @@ streaming. > > [training.train_corpus] > @readers = "spacy.Corpus.v1" -> path = ${paths:train} +> path = ${paths.train} > gold_preproc = false > max_length = 0 > limit = 0 diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index ff106b229..87f3ecbf2 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -111,7 +111,7 @@ model to copy components from). See the docs on ### paths, system {#config-variables tag="variables"} These sections define variables that can be referenced across the other sections -as variables. For example `${paths:train}` uses the value of `train` defined in +as variables. For example `${paths.train}` uses the value of `train` defined in the block `[paths]`. If your config includes custom registered functions that need paths, you can define them here. All config values can also be [overwritten](/usage/training#config-overrides) on the CLI when you run @@ -131,11 +131,11 @@ process that are used when you run [`spacy train`](/api/cli#train). | Name | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `seed` | The random seed. Defaults to variable `${system:seed}`. ~~int~~ | +| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | | `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | -| `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths:init_tok2vec}`. ~~Optional[str]~~ | -| `raw_text` | TODO: ... Defaults to variable `${paths:raw}`. ~~Optional[str]~~ | +| `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths.init_tok2vec}`. ~~Optional[str]~~ | +| `raw_text` | TODO: ... Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | | `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | | `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | | `max_epochs` | Maximum number of epochs to train for. Defaults to `0`. ~~int~~ | @@ -162,8 +162,8 @@ run [`spacy pretrain`](/api/cli#pretrain). | `dropout` | The dropout rate. Defaults to `0.2`. ~~float~~ | | `n_save_every` | Saving frequency. Defaults to `null`. ~~Optional[int]~~ | | `batch_size` | The batch size or batch size [schedule](https://thinc.ai/docs/api-schedules). Defaults to `3000`. ~~Union[int, Sequence[int]]~~ | -| `seed` | The random seed. Defaults to variable `${system:seed}`. ~~int~~ | -| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. Defaults to variable `${system:use_pytorch_for_gpu_memory}`. ~~bool~~ | +| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | +| `use_pytorch_for_gpu_memory` | Allocate memory via PyTorch. Defaults to variable `${system.use_pytorch_for_gpu_memory}`. ~~bool~~ | | `tok2vec_model` | The model section of the embedding component in the config. Defaults to `"components.tok2vec.model"`. ~~str~~ | | `objective` | The pretraining objective. Defaults to `{"type": "characters", "n_characters": 4}`. ~~Dict[str, Any]~~ | | `optimizer` | The optimizer. Defaults to [`Adam`](https://thinc.ai/docs/api-optimizers#adam). ~~Optimizer~~ | diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index b33d7f022..325a94f5c 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -612,7 +612,7 @@ components are created, as well as all training settings and hyperparameters. | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `path` | Path to the model's `config.cfg`. ~~Union[str, Path]~~ | | `overrides` | Optional config overrides to replace in loaded config. Can be provided as nested dict, or as flat dict with keys in dot notation, e.g. `"nlp.pipeline"`. ~~Dict[str, Any]~~ | -| `interpolate` | Whether to interpolate the config and replace variables like `${paths:train}` with their values. Defaults to `False`. ~~bool~~ | +| `interpolate` | Whether to interpolate the config and replace variables like `${paths.train}` with their values. Defaults to `False`. ~~bool~~ | | **RETURNS** | The model's config. ~~Config~~ | ### util.load_meta {#util.load_meta tag="function" new="3"} diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index d4f380c10..348e42b41 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -157,8 +157,8 @@ sections of a config file are: | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `nlp` | Definition of the `nlp` object, its tokenizer and [processing pipeline](/usage/processing-pipelines) component names. | | `components` | Definitions of the [pipeline components](/usage/processing-pipelines) and their models. | -| `paths` | Paths to data and other assets. Re-used across the config as variables, e.g. `${paths:train}`, and can be [overwritten](#config-overrides) on the CLI. | -| `system` | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system:seed}`, and can be [overwritten](#config-overrides) on the CLI. | +| `paths` | Paths to data and other assets. Re-used across the config as variables, e.g. `${paths.train}`, and can be [overwritten](#config-overrides) on the CLI. | +| `system` | Settings related to system and hardware. Re-used across the config as variables, e.g. `${system.seed}`, and can be [overwritten](#config-overrides) on the CLI. | | `training` | Settings and controls for the training and evaluation process. | | `pretraining` | Optional settings and controls for the [language model pretraining](#pretraining). | @@ -325,19 +325,9 @@ compound = 1.001 Another very useful feature of the config system is that it supports variable interpolation for both **values and sections**. This means that you only need to define a setting once and can reference it across your config using the -`${section:value}` or `${section.block}` syntax. In this example, the value of -`seed` is reused within the `[training]` block, and the whole block of -`[training.optimizer]` is reused in `[pretraining]` and will become -`pretraining.optimizer`. - -> #### Note on syntax -> -> There are two different ways to format your variables, depending on whether -> you want to reference a single value or a block. Values are specified after a -> `:`, while blocks are specified with a `.`: -> -> 1. `${section:value}`, `${section.subsection:value}` -> 2. `${section.block}`, `${section.subsection.block}` +`${section.value}` syntax. In this example, the value of `seed` is reused within +the `[training]` block, and the whole block of `[training.optimizer]` is reused +in `[pretraining]` and will become `pretraining.optimizer`. ```ini ### config.cfg (excerpt) {highlight="5,18"} @@ -345,7 +335,7 @@ define a setting once and can reference it across your config using the seed = 0 [training] -seed = ${system:seed} +seed = ${system.seed} [training.optimizer] @optimizers = "Adam.v1" @@ -369,7 +359,7 @@ to a string. [paths] version = 5 root = "/Users/you/data" -train = "${paths:root}/train_${paths:version}.spacy" +train = "${paths.root}/train_${paths.version}.spacy" # Result: /Users/you/data/train_5.spacy ``` From 04e4d592357c75537717433b7ad785c4dabfd17a Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Thu, 20 Aug 2020 16:17:25 +0200 Subject: [PATCH 53/92] Update docs [ci skip] --- .gitignore | 2 -- website/docs/api/architectures.md | 6 ++---- website/docs/usage/training.md | 12 ++++++++---- website/src/widgets/quickstart-training-generator.js | 12 ------------ 4 files changed, 10 insertions(+), 22 deletions(-) delete mode 100644 website/src/widgets/quickstart-training-generator.js diff --git a/.gitignore b/.gitignore index 136a8f26d..4dbcd67f7 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,6 @@ website/.npm website/logs *.log npm-debug.log* -website/www/ -website/_deploy.sh quickstart-training-generator.js # Cython / C extensions diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index acdf4cb19..835815496 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -399,7 +399,7 @@ one component. > subword_features = true > ``` -Build a transition-based parser model. Can apply to NER or dependency-parsing. +Build a transition-based parser model. Can apply to NER or dependency parsing. Transition-based parsing is an approach to structured prediction where the task of predicting the structure is mapped to a series of state transitions. You might find [this tutorial](https://explosion.ai/blog/parsing-english-in-python) @@ -416,8 +416,6 @@ consists of either two or three subnetworks: state representation. If not present, the output from the lower model is used as action scores directly. - - | Name | Description | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tok2vec` | Subnetwork to map tokens into vector representations. ~~Model[List[Doc], List[Floats2d]]~~ | @@ -426,7 +424,7 @@ consists of either two or three subnetworks: | `maxout_pieces` | How many pieces to use in the state prediction layer. Recommended values are `1`, `2` or `3`. If `1`, the maxout non-linearity is replaced with a [`Relu`](https://thinc.ai/docs/api-layers#relu) non-linearity if `use_upper` is `True`, and no non-linearity if `False`. ~~int~~ | | `use_upper` | Whether to use an additional hidden layer after the state vector in order to predict the action scores. It is recommended to set this to `False` for large pretrained models such as transformers, and `True` for smaller networks. The upper layer is computed on CPU, which becomes a bottleneck on larger GPU-based models, where it's also less necessary. ~~bool~~ | | `nO` | The number of actions the model will predict between. Usually inferred from data at the beginning of training, or loaded from disk. ~~int~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Docs], List[List[Floats2d]]]~~ | ### spacy.BILUOTagger.v1 {#BILUOTagger source="spacy/ml/models/simple_ner.py"} diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 348e42b41..892fb7f48 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -404,11 +404,15 @@ recipe once the dish has already been prepared. You have to make a new one. spaCy includes a variety of built-in [architectures](/api/architectures) for different tasks. For example: - + -| Architecture | Description | -| ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [HashEmbedCNN](/api/architectures#HashEmbedCNN) | Build spaCy’s “standard” embedding layer, which uses hash embedding with subword features and a CNN with layer-normalized maxout. ~~Model[List[Doc], List[Floats2d]]~~ | +| Architecture | Description | +| ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [HashEmbedCNN](/api/architectures#HashEmbedCNN) | Build spaCy’s "standard" embedding layer, which uses hash embedding with subword features and a CNN with layer-normalized maxout. ~~Model[List[Doc], List[Floats2d]]~~ | +| [TransitionBasedParser](/api/architectures#TransitionBasedParser) | Build a [transition-based parser](https://explosion.ai/blog/parsing-english-in-python) model used in the default [`EntityRecognizer`](/api/entityrecognizer) and [`DependencyParser`](/api/dependencyparser). ~~Model[List[Docs], List[List[Floats2d]]]~~ | +| [TextCatEnsemble](/api/architectures#TextCatEnsemble) | Stacked ensemble of a bag-of-words model and a neural network model with an internal CNN embedding layer. Used in the default [`TextCategorizer`](/api/textcategorizer). ~~Model~~ | + + ### Metrics, training output and weighted scores {#metrics} diff --git a/website/src/widgets/quickstart-training-generator.js b/website/src/widgets/quickstart-training-generator.js deleted file mode 100644 index b5389d4d7..000000000 --- a/website/src/widgets/quickstart-training-generator.js +++ /dev/null @@ -1,12 +0,0 @@ -// This file was auto-generated by jinja_to_js.py based on quickstart_training.jinja -import jinjaToJS from "jinja-to-js";export default function templateQuickstartTraining(ctx) { - var __result = ""; - var __tmp; - var __runtime = jinjaToJS.runtime; - var __filters = jinjaToJS.filters; - var __globals = jinjaToJS.globals; - var context = jinjaToJS.createContext(ctx); - var use_transformer = context.transformer_data && context.hardware!=="cpu";var transformer = (use_transformer ? context.transformer_data[context.optimize] : {});__result += "[paths]\ntrain = \"\"\ndev = \"\"\n\n[system]\nuse_pytorch_for_gpu_memory = ";__result += "" + __runtime.escape((__tmp = ((use_transformer ? "true" : "false"))) == null ? "" : __tmp);__result += "\n\n[nlp]\nlang = \"";__result += "" + __runtime.escape((__tmp = (context.lang)) == null ? "" : __tmp);__result += "\"";var full_pipeline = [(use_transformer ? "transformer" : "tok2vec")].concat(context.components);__result += "\npipeline = ";__result += "" + ((__tmp = (JSON.stringify(full_pipeline).split("'").join("\""))) == null ? "" : __tmp);__result += "\ntokenizer = {\"@tokenizers\": \"spacy.Tokenizer.v1\"}\n\n[components]\n\n";if(__runtime.boolean(use_transformer)){__result += "[components.transformer]\nfactory = \"transformer\"\n\n[components.transformer.model]\n@architectures = \"spacy-transformers.TransformerModel.v1\"\nname = \"";__result += "" + __runtime.escape((__tmp = (transformer["name"])) == null ? "" : __tmp);__result += "\"\ntokenizer_config = {\"use_fast\": true}\n\n[components.transformer.model.get_spans]\n@span_getters = \"strided_spans.v1\"\nwindow = 128\nstride = 96\n\n";if(context.components.includes("tagger")){__result += "\n[components.tagger]\nfactory = \"tagger\"\n\n[components.tagger.model]\n@architectures = \"spacy.Tagger.v1\"\nnO = null\n\n[components.tagger.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.tagger.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"";}__result += "\n\n";if(context.components.includes("parser")){__result += "[components.parser]\nfactory = \"parser\"\n\n[components.parser.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 8\nhidden_width = 128\nmaxout_pieces = 3\nuse_upper = false\nnO = null\n\n[components.parser.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.parser.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"";}__result += "\n\n";if(context.components.includes("ner")){__result += "[components.ner]\nfactory = \"ner\"\n\n[components.ner.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 3\nhidden_width = 64\nmaxout_pieces = 2\nuse_upper = false\nnO = null\n\n[components.ner.model.tok2vec]\n@architectures = \"spacy-transformers.Tok2VecListener.v1\"\ngrad_factor = 1.0\n\n[components.ner.model.tok2vec.pooling]\n@layers = \"reduce_mean.v1\"\n";}__result += "\n";} else {if(context.hardware==="gpu"){__result += "# There are no recommended transformer weights available for language '";__result += "" + __runtime.escape((__tmp = (context.lang)) == null ? "" : __tmp);__result += "'\n# yet, so the pipeline described here is not transformer-based.";}__result += "\n\n[components.tok2vec]\nfactory = \"tok2vec\"\n\n[components.tok2vec.model]\n@architectures = \"spacy.Tok2Vec.v1\"\n\n[components.tok2vec.model.embed]\n@architectures = \"spacy.MultiHashEmbed.v1\"\nwidth = ${components.tok2vec.model.encode:width}\nrows = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 2000 : 7000))) == null ? "" : __tmp);__result += "\nalso_embed_subwords = ";__result += "" + __runtime.escape((__tmp = ((context.has_letters ? true : false))) == null ? "" : __tmp);__result += "\nalso_use_static_vectors = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="accuracy" ? true : false))) == null ? "" : __tmp);__result += "\n\n[components.tok2vec.model.encode]\n@architectures = \"spacy.MaxoutWindowEncoder.v1\"\nwidth = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 96 : 256))) == null ? "" : __tmp);__result += "\ndepth = ";__result += "" + __runtime.escape((__tmp = ((context.optimize==="efficiency" ? 4 : 8))) == null ? "" : __tmp);__result += "\nwindow_size = 1\nmaxout_pieces = 3\n\n";if(context.components.includes("tagger")){__result += "\n[components.tagger]\nfactory = \"tagger\"\n\n[components.tagger.model]\n@architectures = \"spacy.Tagger.v1\"\nnO = null\n\n[components.tagger.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}";}__result += "\n\n";if(context.components.includes("parser")){__result += "[components.parser]\nfactory = \"parser\"\n\n[components.parser.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 8\nhidden_width = 128\nmaxout_pieces = 3\nuse_upper = true\nnO = null\n\n[components.parser.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}";}__result += "\n\n";if(context.components.includes("ner")){__result += "\n[components.ner]\nfactory = \"ner\"\n\n[components.ner.model]\n@architectures = \"spacy.TransitionBasedParser.v1\"\nnr_feature_tokens = 6\nhidden_width = 64\nmaxout_pieces = 2\nuse_upper = true\nnO = null\n\n[components.ner.model.tok2vec]\n@architectures = \"spacy.Tok2VecListener.v1\"\nwidth = ${components.tok2vec.model.encode:width}\n";}__result += "\n";}__result += "\n\n";__runtime.each(context.components,function(pipe){var __$0 = context.pipe;context.pipe = pipe;__result += "\n";if(!["tagger","parser","ner"].includes(pipe)){__result += "\n";__result += "\n[components.";__result += "" + __runtime.escape((__tmp = (pipe)) == null ? "" : __tmp);__result += "]\nfactory = \"";__result += "" + __runtime.escape((__tmp = (pipe)) == null ? "" : __tmp);__result += "\"\n";}__result += "\n";context.pipe = __$0;});__result += "\n\n[training]\n";if(__runtime.boolean(use_transformer) || context.optimize==="efficiency" || !__runtime.boolean(context.word_vectors)){__result += "vectors = null\n";} else {__result += "vectors = \"";__result += "" + __runtime.escape((__tmp = (context.word_vectors)) == null ? "" : __tmp);__result += "\"\n";}if(__runtime.boolean(use_transformer)){__result += "accumulate_gradient = ";__result += "" + __runtime.escape((__tmp = (transformer["size_factor"])) == null ? "" : __tmp);__result += "\n";}__result += "\n\n[training.optimizer]\n@optimizers = \"Adam.v1\"\n\n[training.optimizer.learn_rate]\n@schedules = \"warmup_linear.v1\"\nwarmup_steps = 250\ntotal_steps = 20000\ninitial_rate = 5e-5\n\n[training.train_corpus]\n@readers = \"spacy.Corpus.v1\"\npath = ${paths:train}\nmax_length = ";__result += "" + __runtime.escape((__tmp = ((context.hardware==="gpu" ? 500 : 0))) == null ? "" : __tmp);__result += "\n\n[training.dev_corpus]\n@readers = \"spacy.Corpus.v1\"\npath = ${paths:dev}\nmax_length = 0\n\n";if(__runtime.boolean(use_transformer)){__result += "\n[training.batcher]\n@batchers = \"batch_by_padded.v1\"\ndiscard_oversize = true\nsize = 2000\nbuffer = 256";} else {__result += "\n[training.batcher]\n@batchers = \"batch_by_words.v1\"\ndiscard_oversize = false\ntolerance = 0.2\n\n[training.batcher.size]\n@schedules = \"compounding.v1\"\nstart = 100\nstop = 1000\ncompound = 1.001\n";}__result += "\n\n[training.score_weights]";if(context.components.includes("tagger")){__result += "\ntag_acc = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);}if(context.components.includes("parser")){__result += "\ndep_uas = 0.0\ndep_las = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);__result += "\nsents_f = 0.0";}if(context.components.includes("ner")){__result += "\nents_f = ";__result += "" + __runtime.escape((__tmp = (Math.round((1.0 / __filters.size(context.components)+ Number.EPSILON) * 10**2) / 10**2)) == null ? "" : __tmp);__result += "\nents_p = 0.0\nents_r = 0.0";} - return __result; -} -export const DATA = {"en":{"word_vectors":"en_vectors_web_lg","transformer":{"efficiency":{"name":"roberta-base","size_factor":3},"accuracy":{"name":"roberta-base","size_factor":3}}},"de":{"word_vectors":null,"transformer":{"efficiency":{"name":"bert-base-german-cased","size_factor":3},"accuracy":{"name":"bert-base-german-cased","size_factor":3}}},"fr":{"word_vectors":null,"transformer":{"efficiency":{"name":"camembert-base","size_factor":3},"accuracy":{"name":"camembert-base","size_factor":3}}},"es":{"word_vectors":null,"transformer":{"efficiency":{"name":"mrm8488/RuPERTa-base","size_factor":3},"accuracy":{"name":"mrm8488/RuPERTa-base","size_factor":3}}},"sv":{"word_vectors":null,"transformer":{"efficiency":{"name":"KB/bert-base-swedish-cased","size_factor":3},"accuracy":{"name":"KB/bert-base-swedish-cased","size_factor":3}}},"fi":{"word_vectors":null,"transformer":{"efficiency":{"name":"TurkuNLP/bert-base-finnish-cased-v1","size_factor":3},"accuracy":{"name":"TurkuNLP/bert-base-finnish-cased-v1","size_factor":3}}},"el":{"word_vectors":null,"transformer":{"efficiency":{"name":"nlpaueb/bert-base-greek-uncased-v1","size_factor":3},"accuracy":{"name":"nlpaueb/bert-base-greek-uncased-v1","size_factor":3}}},"tr":{"word_vectors":null,"transformer":{"efficiency":{"name":"dbmdz/bert-base-turkish-cased","size_factor":3},"accuracy":{"name":"dbmdz/bert-base-turkish-cased","size_factor":3}}},"zh":{"word_vectors":null,"transformer":{"efficiency":{"name":"bert-base-chinese","size_factor":3},"accuracy":{"name":"bert-base-chinese","size_factor":3}},"has_letters":false},"ar":{"word_vectors":null,"transformer":{"efficiency":{"name":"asafaya/bert-base-arabic","size_factor":3},"accuracy":{"name":"asafaya/bert-base-arabic","size_factor":3}}},"pl":{"word_vectors":null,"transformer":{"efficiency":{"name":"dkleczek/bert-base-polish-cased-v1","size_factor":3},"accuracy":{"name":"dkleczek/bert-base-polish-cased-v1","size_factor":3}}}} \ No newline at end of file From c356e6290872824c669c1dee22e0f068d442b46a Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Fri, 21 Aug 2020 00:10:21 +0200 Subject: [PATCH 54/92] Minor adjustments to quickstart template --- spacy/cli/templates/quickstart_training.jinja | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spacy/cli/templates/quickstart_training.jinja b/spacy/cli/templates/quickstart_training.jinja index 674099abc..0071f1b1a 100644 --- a/spacy/cli/templates/quickstart_training.jinja +++ b/spacy/cli/templates/quickstart_training.jinja @@ -107,8 +107,8 @@ factory = "tok2vec" @architectures = "spacy.MultiHashEmbed.v1" width = ${components.tok2vec.model.encode.width} rows = {{ 2000 if optimize == "efficiency" else 7000 }} -also_embed_subwords = {{ true if has_letters else false }} -also_use_static_vectors = {{ true if optimize == "accuracy" else false }} +also_embed_subwords = {{ "true" if has_letters else "false" }} +also_use_static_vectors = {{ "true" if optimize == "accuracy" else "false" }} [components.tok2vec.model.encode] @architectures = "spacy.MaxoutWindowEncoder.v1" @@ -195,7 +195,7 @@ initial_rate = 5e-5 [training.train_corpus] @readers = "spacy.Corpus.v1" path = ${paths.train} -max_length = {{ 500 if hardware == "gpu" else 0 }} +max_length = {{ 500 if hardware == "gpu" else 2000 }} [training.dev_corpus] @readers = "spacy.Corpus.v1" From e60442d83a0129762959bca92119d79818f760b4 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 11:51:31 +0200 Subject: [PATCH 55/92] Adjust label casing in displaCy NER visualizer (resolves #4866) - Accept any case for label names in ents and colors option, even if actual predicted label uses different casing - Don't text-transform: uppercase visually, if it's important to users that the label is represented as-is in the UI --- spacy/displacy/render.py | 4 +++- spacy/displacy/templates.py | 4 ++-- spacy/tests/test_displacy.py | 16 +++++++++++++++- website/docs/api/top-level.md | 2 +- website/docs/usage/visualizers.md | 8 ++++---- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/spacy/displacy/render.py b/spacy/displacy/render.py index 69f6df8f0..07550f9aa 100644 --- a/spacy/displacy/render.py +++ b/spacy/displacy/render.py @@ -252,8 +252,10 @@ class EntityRenderer: colors.update(user_color) colors.update(options.get("colors", {})) self.default_color = DEFAULT_ENTITY_COLOR - self.colors = colors + self.colors = {label.upper(): color for label, color in colors.items()} self.ents = options.get("ents", None) + if self.ents is not None: + self.ents = [ent.upper() for ent in self.ents] self.direction = DEFAULT_DIR self.lang = DEFAULT_LANG template = options.get("template") diff --git a/spacy/displacy/templates.py b/spacy/displacy/templates.py index ff99000f4..b9cbf717b 100644 --- a/spacy/displacy/templates.py +++ b/spacy/displacy/templates.py @@ -51,14 +51,14 @@ TPL_ENTS = """ TPL_ENT = """ {text} - {label} + {label} """ TPL_ENT_RTL = """ {text} - {label} + {label} """ diff --git a/spacy/tests/test_displacy.py b/spacy/tests/test_displacy.py index adac0f7c3..1fa0eeaa1 100644 --- a/spacy/tests/test_displacy.py +++ b/spacy/tests/test_displacy.py @@ -1,6 +1,6 @@ import pytest from spacy import displacy -from spacy.displacy.render import DependencyRenderer +from spacy.displacy.render import DependencyRenderer, EntityRenderer from spacy.tokens import Span from spacy.lang.fa import Persian @@ -97,3 +97,17 @@ def test_displacy_render_wrapper(en_vocab): assert html.endswith("/div>TEST") # Restore displacy.set_render_wrapper(lambda html: html) + + +def test_displacy_options_case(): + ents = ["foo", "BAR"] + colors = {"FOO": "red", "bar": "green"} + renderer = EntityRenderer({"ents": ents, "colors": colors}) + text = "abcd" + labels = ["foo", "bar", "FOO", "BAR"] + spans = [{"start": i, "end": i + 1, "label": labels[i]} for i in range(len(text))] + result = renderer.render_ents("abcde", spans, None).split("\n\n") + assert "red" in result[0] and "foo" in result[0] + assert "green" in result[1] and "bar" in result[1] + assert "red" in result[2] and "FOO" in result[2] + assert "green" in result[3] and "BAR" in result[3] diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 61fca6ec5..89c53cce3 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -257,7 +257,7 @@ If a setting is not present in the options, the default value will be used. | Name | Description | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ents` | Entity types to highlight or `None` for all types (default). ~~Optional[List[str]]~~ | -| `colors` | Color overrides. Entity types in uppercase should be mapped to color names or values. ~~Dict[str, str]~~ | +| `colors` | Color overrides. Entity types should be mapped to color names or values. ~~Dict[str, str]~~ | | `template` 2.2 | Optional template to overwrite the HTML used to render entity spans. Should be a format string and can use `{bg}`, `{text}` and `{label}`. See [`templates.py`](https://github.com/explosion/spaCy/blob/master/spacy/displacy/templates.py) for examples. ~~Optional[str]~~ | By default, displaCy comes with colors for all entity types used by diff --git a/website/docs/usage/visualizers.md b/website/docs/usage/visualizers.md index f33340063..4ba0112b6 100644 --- a/website/docs/usage/visualizers.md +++ b/website/docs/usage/visualizers.md @@ -121,10 +121,10 @@ import DisplacyEntHtml from 'images/displacy-ent2.html' The entity visualizer lets you customize the following `options`: -| Argument | Description | -| -------- | -------------------------------------------------------------------------------------------------------------------------- | -| `ents` | Entity types to highlight (`None` for all types). Defaults to `None`. ~~Optional[List[str]]~~ | `None` | -| `colors` | Color overrides. Entity types in uppercase should be mapped to color names or values. Defaults to `{}`. ~~Dict[str, str]~~ | +| Argument | Description | +| -------- | ------------------------------------------------------------------------------------------------------------- | +| `ents` | Entity types to highlight (`None` for all types). Defaults to `None`. ~~Optional[List[str]]~~ | `None` | +| `colors` | Color overrides. Entity types should be mapped to color names or values. Defaults to `{}`. ~~Dict[str, str]~~ | If you specify a list of `ents`, only those entity types will be rendered – for example, you can choose to display `PERSON` entities. Internally, the visualizer From 79af7dcd6dfbd5c73c3a667f236c440e85f132fc Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 12:06:19 +0200 Subject: [PATCH 56/92] Small wording adjustments [ci skip] --- spacy/cli/init_config.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/spacy/cli/init_config.py b/spacy/cli/init_config.py index 79c4acd11..273838906 100644 --- a/spacy/cli/init_config.py +++ b/spacy/cli/init_config.py @@ -39,7 +39,7 @@ class RecommendationSchema(BaseModel): @init_cli.command("config") def init_config_cli( # fmt: off - output_file: Path = Arg(..., help="File to save config.cfg to (or - for stdout, disabling logging)", allow_dash=True), + output_file: Path = Arg(..., help="File to save config.cfg to or - for stdout (will only output config and no additional logging info)", allow_dash=True), lang: Optional[str] = Opt("en", "--lang", "-l", help="Two-letter code of the language to use"), pipeline: Optional[str] = Opt("tagger,parser,ner", "--pipeline", "-p", help="Comma-separated names of trainable pipeline components to include in the model (without 'tok2vec' or 'transformer')"), optimize: Optimizations = Opt(Optimizations.efficiency.value, "--optimize", "-o", help="Whether to optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger and slower model). This will impact the choice of architecture, pretrained weights and related hyperparameters."), @@ -128,8 +128,13 @@ def init_config( "word_vectors": reco["word_vectors"], "has_letters": has_letters, } - if variables["transformer_data"] and not cpu: - variables["transformer_data"] = prefer_spacy_transformers(msg) + if variables["transformer_data"] and not has_spacy_transformers(): + msg.warn( + "To generate a more effective transformer-based config (GPU-only), " + "install the spacy-transformers package and re-run this command. " + "The config generated now does not use transformers." + ) + variables["transformer_data"] = None base_template = template.render(variables).strip() # Giving up on getting the newlines right in jinja for now base_template = re.sub(r"\n\n\n+", "\n\n", base_template) @@ -167,13 +172,10 @@ def save_config(config: Config, output_file: Path, is_stdout: bool = False) -> N print(f"{COMMAND} train {output_file.parts[-1]} {' '.join(variables)}") -def prefer_spacy_transformers(msg: Printer) -> bool: +def has_spacy_transformers() -> bool: try: import spacy_transformers # noqa: F401 + + return True except ImportError: - msg.info( - "Recommend to install 'spacy-transformers' to create a more efficient " - "transformer-based pipeline." - ) return False - return True From 52bd3a8b48d354de57c1eb59d78abdb32c3e3a30 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 13:22:59 +0200 Subject: [PATCH 57/92] Update docs [ci skip] --- website/docs/api/morphology.md | 2 +- website/docs/api/token.md | 4 +- website/docs/api/top-level.md | 17 ++ website/docs/usage/embeddings-transformers.md | 4 + website/docs/usage/v3.md | 182 ++++++++++++++++-- 5 files changed, 190 insertions(+), 19 deletions(-) diff --git a/website/docs/api/morphology.md b/website/docs/api/morphology.md index 1b2e159d0..5d5324061 100644 --- a/website/docs/api/morphology.md +++ b/website/docs/api/morphology.md @@ -7,7 +7,7 @@ source: spacy/morphology.pyx Store the possible morphological analyses for a language, and index them by hash. To save space on each token, tokens only know the hash of their morphological analysis, so queries of morphological attributes are delegated to -this class. See [`MorphAnalysis`](/api/morphology#morphansalysis) for the +this class. See [`MorphAnalysis`](/api/morphology#morphanalysis) for the container storing a single morphological analysis. ## Morphology.\_\_init\_\_ {#init tag="method"} diff --git a/website/docs/api/token.md b/website/docs/api/token.md index 4a8e6eba7..0860797aa 100644 --- a/website/docs/api/token.md +++ b/website/docs/api/token.md @@ -450,8 +450,8 @@ The L2 norm of the token's vector representation. | `pos_` | Coarse-grained part-of-speech from the [Universal POS tag set](https://universaldependencies.org/docs/u/pos/). ~~str~~ | | `tag` | Fine-grained part-of-speech. ~~int~~ | | `tag_` | Fine-grained part-of-speech. ~~str~~ | -| `morph` | Morphological analysis. ~~MorphAnalysis~~ | -| `morph_` | Morphological analysis in the Universal Dependencies [FEATS]https://universaldependencies.org/format.html#morphological-annotation format. ~~str~~ | +| `morph` 3 | Morphological analysis. ~~MorphAnalysis~~ | +| `morph_` 3 | Morphological analysis in the Universal Dependencies [FEATS]https://universaldependencies.org/format.html#morphological-annotation format. ~~str~~ | | `dep` | Syntactic dependency relation. ~~int~~ | | `dep_` | Syntactic dependency relation. ~~str~~ | | `lang` | Language of the parent document's vocabulary. ~~int~~ | diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 89c53cce3..9c65b2982 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -632,6 +632,23 @@ validate its contents. | `path` | Path to the model's `meta.json`. ~~Union[str, Path]~~ | | **RETURNS** | The model's meta data. ~~Dict[str, Any]~~ | +### util.get_installed_models {#util.get_installed_models tag="function" new="3"} + +List all model packages installed in the current environment. This will include +any spaCy model that was packaged with [`spacy package`](/api/cli#package). +Under the hood, model packages expose a Python entry point that spaCy can check, +without having to load the model. + +> #### Example +> +> ```python +> model_names = util.get_installed_models() +> ``` + +| Name | Description | +| ----------- | ---------------------------------------------------------------------------------- | +| **RETURNS** | The string names of the models installed in the current environment. ~~List[str]~~ | + ### util.is_package {#util.is_package tag="function"} Check if string maps to a package installed via pip. Mainly used to validate diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index c2727f5b1..70562cf7e 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -11,6 +11,10 @@ next: /usage/training +If you're looking for details on using word vectors and semantic similarity, +check out the +[linguistic features docs](/usage/linguistic-features#vectors-similarity). + The key difference between [word vectors](#word-vectors) and contextual language diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index 837818a83..3111bf38e 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -10,6 +10,32 @@ menu: ## Summary {#summary} + + +

+ +
+ + + +- [Summary](#summary) +- [New features](#features) +- [Training & config system](#features-training) +- [Transformer-based pipelines](#features-transformers) +- [Custom models](#features-custom-models) +- [End-to-end project workflows](#features-projects) +- [New built-in components](#features-pipeline-components) +- [New custom component API](#features-components) +- [Python type hints](#features-types) +- [New methods & attributes](#new-methods) +- [New & updated documentation](#new-docs) +- [Backwards incompatibilities](#incompat) +- [Migrating from spaCy v2.x](#migrating) + + + + + ## New Features {#features} ### New training workflow and config system {#features-training} @@ -28,6 +54,8 @@ menu: ### Transformer-based pipelines {#features-transformers} +![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg) + - **Usage:** [Embeddings & Transformers](/usage/embeddings-transformers), @@ -46,8 +74,53 @@ menu: ### Custom models using any framework {#features-custom-models} + + + + +- **Thinc: ** + [Wrapping PyTorch, TensorFlow & MXNet](https://thinc.ai/docs/usage-frameworks) +- **API:** [Model architectures](/api/architectures), [`Pipe`](/api/pipe) + + + ### Manage end-to-end workflows with projects {#features-projects} + + +> #### Example +> +> ```cli +> # Clone a project template +> $ python -m spacy project clone example +> $ cd example +> # Download data assets +> $ python -m spacy project assets +> # Run a workflow +> $ python -m spacy project run train +> ``` + +spaCy projects let you manage and share **end-to-end spaCy workflows** for +different **use cases and domains**, and orchestrate training, packaging and +serving your custom models. You can start off by cloning a pre-defined project +template, adjust it to fit your needs, load in your data, train a model, export +it as a Python package and share the project templates with your team. spaCy +projects also make it easy to **integrate with other tools** in the data science +and machine learning ecosystem, including [DVC](/usage/projects#dvc) for data +version control, [Prodigy](/usage/projects#prodigy) for creating labelled data, +[Streamlit](/usage/projects#streamlit) for building interactive apps, +[FastAPI](/usage/projects#fastapi) for serving models in production, +[Ray](/usage/projects#ray) for parallel training, +[Weights & Biases](/usage/projects#wandb) for experiment tracking, and more! + + + - **Usage:** [spaCy projects](/usage/projects), @@ -59,6 +132,16 @@ menu: ### New built-in pipeline components {#features-pipeline-components} +spaCy v3.0 includes several new trainable and rule-based components that you can +add to your pipeline and customize for your use case: + +> #### Example +> +> ```python +> nlp = spacy.blank("en") +> nlp.add_pipe("lemmatizer") +> ``` + | Name | Description | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [`SentenceRecognizer`](/api/sentencerecognizer) | Trainable component for sentence segmentation. | @@ -78,15 +161,37 @@ menu: ### New and improved pipeline component APIs {#features-components} -- `Language.factory`, `Language.component` -- `Language.analyze_pipes` -- Adding components from other models +> #### Example +> +> ```python +> @Language.component("my_component") +> def my_component(doc): +> return doc +> +> nlp.add_pipe("my_component") +> nlp.add_pipe("ner", source=other_nlp) +> nlp.analyze_pipes(pretty=True) +> ``` + +Defining, configuring, reusing, training and analyzing pipeline components is +now easier and more convenient. The `@Language.component` and +`@Language.factory` decorators let you register your component, define its +default configuration and meta data, like the attribute values it assigns and +requires. Any custom component can be included during training, and sourcing +components from existing pretrained models lets you **mix and match custom +pipelines**. The `nlp.analyze_pipes` method outputs structured information about +the current pipeline and its components, including the attributes they assign, +the scores they compute during training and whether any required attributes +aren't set. - **Usage:** [Custom components](/usage/processing-pipelines#custom_components), - [Defining components during training](/usage/training#config-components) -- **API:** [`Language`](/api/language) + [Defining components for training](/usage/training#config-components) +- **API:** [`@Language.component`](/api/language#component), + [`@Language.factory`](/api/language#factory), + [`Language.add_pipe`](/api/language#add_pipe), + [`Language.analyze_pipes`](/api/language#analyze_pipes) - **Implementation:** [`spacy/language.py`](https://github.com/explosion/spaCy/tree/develop/spacy/language.py) @@ -136,13 +241,14 @@ in your config and see validation errors if the argument values don't match. -### New methods, attributes and commands +### New methods, attributes and commands {#new-methods} The following methods, attributes and commands are new in spaCy v3.0. | Name | Description | | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`Token.lex`](/api/token#attributes) | Access a token's [`Lexeme`](/api/lexeme). | +| [`Token.morph`](/api/token#attributes) [`Token.morph_`](/api/token#attributes) | Access a token's morphological analysis. | | [`Language.select_pipes`](/api/language#select_pipes) | Contextmanager for enabling or disabling specific pipeline components for a block. | | [`Language.analyze_pipes`](/api/language#analyze_pipes) | [Analyze](/usage/processing-pipelines#analysis) components and their interdependencies. | | [`Language.resume_training`](/api/language#resume_training) | Experimental: continue training a pretrained model and initialize "rehearsal" for components that implement a `rehearse` method to prevent catastrophic forgetting. | @@ -153,9 +259,52 @@ The following methods, attributes and commands are new in spaCy v3.0. | [`Pipe.score`](/api/pipe#score) | Method on trainable pipeline components that returns a dictionary of evaluation scores. | | [`registry`](/api/top-level#registry) | Function registry to map functions to string names that can be referenced in [configs](/usage/training#config). | | [`util.load_meta`](/api/top-level#util.load_meta) [`util.load_config`](/api/top-level#util.load_config) | Updated helpers for loading a model's [`meta.json`](/api/data-formats#meta) and [`config.cfg`](/api/data-formats#config). | +| [`util.get_installed_models`](/api/top-level#util.get_installed_models) | Names of all models installed in the environment. | | [`init config`](/api/cli#init-config) [`init fill-config`](/api/cli#init-fill-config) [`debug config`](/api/cli#debug-config) | CLI commands for initializing, auto-filling and debugging [training configs](/usage/training). | | [`project`](/api/cli#project) | Suite of CLI commands for cloning, running and managing [spaCy projects](/usage/projects). | +### New and updated documentation {#new-docs} + + + +
+ +To help you get started with spaCy v3.0 and the new features, we've added +several new or rewritten documentation pages, including a new usage guide on +[embeddings, transformers and transfer learning](/usage/embeddings-transformers), +a guide on [training models](/usage/training) rewritten from scratch, a page +explaining the new [spaCy projects](/usage/projects) and updated usage +documentation on +[custom pipeline components](/usage/processing-pipelines#custom-components). +We've also added a bunch of new illustrations and new API reference pages +documenting spaCy's machine learning [model architectures](/api/architectures) +and the expected [data formats](/api/data-formats). API pages about +[pipeline components](/api/#architecture-pipeline) now include more information, +like the default config and implementation, and we've adopted a more detailed +format for documenting argument and return types. + +
+ +[![Library architecture](../images/architecture.svg)](/api) + +
+ + + +- **Usage: ** [Embeddings & Transformers](/usage/embeddings-transformers), + [Training models](/usage/training), [Projects](/usage/projects), + [Custom pipeline components](/usage/processing-pipelines#custom-components) +- **API Reference: ** [Library architecture](/api), + [Model architectures](/api/architectures), [Data formats](/api/data-formats) +- **New Classes: ** [`Example`](/api/example), [`Tok2Vec`](/api/tok2vec), + [`Transformer`](/api/transformer), [`Lemmatizer`](/api/lemmatizer), + [`Morphologizer`](/api/morphologizer), + [`AttributeRuler`](/api/attributeruler), + [`SentenceRecognizer`](/api/sentencerecognizer), [`Pipe`](/api/pipe), + [`Corpus`](/api/corpus) + + + ## Backwards Incompatibilities {#incompat} As always, we've tried to keep the breaking changes to a minimum and focus on @@ -212,15 +361,16 @@ Note that spaCy v3.0 now requires **Python 3.6+**. ### Removed or renamed API {#incompat-removed} -| Removed | Replacement | -| ------------------------------------------------------ | ----------------------------------------------------------------------------------------- | -| `Language.disable_pipes` | [`Language.select_pipes`](/api/language#select_pipes) | -| `GoldParse` | [`Example`](/api/example) | -| `GoldCorpus` | [`Corpus`](/api/corpus) | -| `KnowledgeBase.load_bulk` `KnowledgeBase.dump` | [`KnowledgeBase.from_disk`](/api/kb#from_disk) [`KnowledgeBase.to_disk`](/api/kb#to_disk) | -| `spacy debug-data` | [`spacy debug data`](/api/cli#debug-data) | -| `spacy profile` | [`spacy debug profile`](/api/cli#debug-profile) | -| `spacy link` `util.set_data_path` `util.get_data_path` | not needed, model symlinks are deprecated | +| Removed | Replacement | +| -------------------------------------------------------- | ----------------------------------------------------------------------------------------- | +| `Language.disable_pipes` | [`Language.select_pipes`](/api/language#select_pipes) | +| `GoldParse` | [`Example`](/api/example) | +| `GoldCorpus` | [`Corpus`](/api/corpus) | +| `KnowledgeBase.load_bulk` `KnowledgeBase.dump` | [`KnowledgeBase.from_disk`](/api/kb#from_disk) [`KnowledgeBase.to_disk`](/api/kb#to_disk) | +| `spacy init-model` | [`spacy init model`](/api/cli#init-model) | +| `spacy debug-data` | [`spacy debug data`](/api/cli#debug-data) | +| `spacy profile` | [`spacy debug profile`](/api/cli#debug-profile) | +| `spacy link`, `util.set_data_path`, `util.get_data_path` | not needed, model symlinks are deprecated | The following deprecated methods, attributes and arguments were removed in v3.0. Most of them have been **deprecated for a while** and many would previously @@ -236,7 +386,7 @@ on them. | `Language.tagger`, `Language.parser`, `Language.entity` | [`Language.get_pipe`](/api/language#get_pipe) | | keyword-arguments like `vocab=False` on `to_disk`, `from_disk`, `to_bytes`, `from_bytes` | `exclude=["vocab"]` | | `n_threads` argument on [`Tokenizer`](/api/tokenizer), [`Matcher`](/api/matcher), [`PhraseMatcher`](/api/phrasematcher) | `n_process` | -| `verbose` argument on [`Language.evaluate`] | logging | +| `verbose` argument on [`Language.evaluate`](/api/language#evaluate) | logging (`DEBUG`) | | `SentenceSegmenter` hook, `SimilarityHook` | [user hooks](/usage/processing-pipelines#custom-components-user-hooks), [`Sentencizer`](/api/sentencizer), [`SentenceRecognizer`](/api/sentenceregognizer) | ## Migrating from v2.x {#migrating} From aa6a7cd6e72bfd8515b7c3b6ddb4c0951c6513e6 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 13:49:18 +0200 Subject: [PATCH 58/92] Update docs and consistency [ci skip] --- CONTRIBUTING.md | 12 +++++------ README.md | 5 ++--- spacy/tokenizer.pyx | 4 ++-- website/docs/usage/embeddings-transformers.md | 2 +- website/docs/usage/index.md | 4 ++-- website/docs/usage/linguistic-features.md | 14 ++++++------- website/docs/usage/processing-pipelines.md | 20 +++++++++---------- website/docs/usage/rule-based-matching.md | 10 +++++----- website/docs/usage/training.md | 2 +- website/docs/usage/v3.md | 5 +++-- website/src/styles/layout.sass | 5 ++++- 11 files changed, 43 insertions(+), 40 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 81cfbf8cb..0abde2abf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,7 +5,7 @@ Thanks for your interest in contributing to spaCy 🎉 The project is maintained by [@honnibal](https://github.com/honnibal) and [@ines](https://github.com/ines), and we'll do our best to help you get started. This page will give you a quick -overview of how things are organised and most importantly, how to get involved. +overview of how things are organized and most importantly, how to get involved. ## Table of contents @@ -195,7 +195,7 @@ modules in `.py` files, not Cython modules in `.pyx` and `.pxd` files.** ### Code formatting [`black`](https://github.com/ambv/black) is an opinionated Python code -formatter, optimised to produce readable code and small diffs. You can run +formatter, optimized to produce readable code and small diffs. You can run `black` from the command-line, or via your code editor. For example, if you're using [Visual Studio Code](https://code.visualstudio.com/), you can add the following to your `settings.json` to use `black` for formatting and auto-format @@ -286,7 +286,7 @@ Code that interacts with the file-system should accept objects that follow the If the function is user-facing and takes a path as an argument, it should check whether the path is provided as a string. Strings should be converted to `pathlib.Path` objects. Serialization and deserialization functions should always -accept **file-like objects**, as it makes the library io-agnostic. Working on +accept **file-like objects**, as it makes the library IO-agnostic. Working on buffers makes the code more general, easier to test, and compatible with Python 3's asynchronous IO. @@ -384,7 +384,7 @@ of Python and C++, with additional complexity and syntax from numpy. The many "traps for new players". Working in Cython is very rewarding once you're over the initial learning curve. As with C and C++, the first way you write something in Cython will often be the performance-optimal approach. In contrast, -Python optimisation generally requires a lot of experimentation. Is it faster to +Python optimization generally requires a lot of experimentation. Is it faster to have an `if item in my_dict` check, or to use `.get()`? What about `try`/`except`? Does this numpy operation create a copy? There's no way to guess the answers to these questions, and you'll usually be dissatisfied with your results — so @@ -400,7 +400,7 @@ Python. If it's not fast enough the first time, just switch to Cython. - [PEP 8 Style Guide for Python Code](https://www.python.org/dev/peps/pep-0008/) (python.org) - [Official Cython documentation](http://docs.cython.org/en/latest/) (cython.org) - [Writing C in Cython](https://explosion.ai/blog/writing-c-in-cython) (explosion.ai) -- [Multi-threading spaCy’s parser and named entity recogniser](https://explosion.ai/blog/multithreading-with-cython) (explosion.ai) +- [Multi-threading spaCy’s parser and named entity recognizer](https://explosion.ai/blog/multithreading-with-cython) (explosion.ai) ## Adding tests @@ -412,7 +412,7 @@ name. For example, tests for the `Tokenizer` can be found in all test files and test functions need to be prefixed with `test_`. When adding tests, make sure to use descriptive names, keep the code short and -concise and only test for one behaviour at a time. Try to `parametrize` test +concise and only test for one behavior at a time. Try to `parametrize` test cases wherever possible, use our pre-defined fixtures for spaCy components and avoid unnecessary imports. diff --git a/README.md b/README.md index 1fece1e5a..cef2a1fdd 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,8 @@ It's commercial open-source software, released under the MIT license. ## 💬 Where to ask questions -The spaCy project is maintained by [@honnibal](https://github.com/honnibal) and -[@ines](https://github.com/ines), along with core contributors -[@svlandeg](https://github.com/svlandeg) and +The spaCy project is maintained by [@honnibal](https://github.com/honnibal), +[@ines](https://github.com/ines), [@svlandeg](https://github.com/svlandeg) and [@adrianeboyd](https://github.com/adrianeboyd). Please understand that we won't be able to provide individual support via email. We also believe that help is much more valuable if it's shared publicly, so that more people can benefit from diff --git a/spacy/tokenizer.pyx b/spacy/tokenizer.pyx index a13299fff..9fda1800b 100644 --- a/spacy/tokenizer.pyx +++ b/spacy/tokenizer.pyx @@ -47,9 +47,9 @@ cdef class Tokenizer: `infix_finditer` (callable): A function matching the signature of `re.compile(string).finditer` to find infixes. token_match (callable): A boolean function matching strings to be - recognised as tokens. + recognized as tokens. url_match (callable): A boolean function matching strings to be - recognised as tokens after considering prefixes and suffixes. + recognized as tokens after considering prefixes and suffixes. EXAMPLE: >>> tokenizer = Tokenizer(nlp.vocab) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index 70562cf7e..33385ff51 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -184,7 +184,7 @@ yourself. For details on how to get started with training your own model, check out the [training quickstart](/usage/training#quickstart). - | Name | Description | | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `width` | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. ~~int~~ | | `upstream` | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. ~~str~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.MultiHashEmbed.v1 {#MultiHashEmbed} @@ -139,15 +137,13 @@ definitions depending on the `Vocab` of the `Doc` object passed in. Vectors from pretrained static vectors can also be incorporated into the concatenated representation. - - | Name | Description | | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `width` | The output width. Also used as the width of the embedding tables. Recommended values are between `64` and `300`. ~~int~~ | | `rows` | The number of rows for the embedding tables. Can be low, due to the hashing trick. Embeddings for prefix, suffix and word shape use half as many rows. Recommended values are between `2000` and `10000`. ~~int~~ | | `also_embed_subwords` | Whether to use the `PREFIX`, `SUFFIX` and `SHAPE` features in the embeddings. If not using these, you may need more rows in your hash embeddings, as there will be increased chance of collisions. ~~bool~~ | | `also_use_static_vectors` | Whether to also use static word vectors. Requires a vectors table to be loaded in the [Doc](/api/doc) objects' vocab. ~~bool~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.CharacterEmbed.v1 {#CharacterEmbed} @@ -178,15 +174,13 @@ concatenated. A hash-embedded vector of the `NORM` of the word is also concatenated on, and the result is then passed through a feed-forward network to construct a single vector to represent the information. - - | Name | Description | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `width` | The width of the output vector and the `NORM` hash embedding. ~~int~~ | | `rows` | The number of rows in the `NORM` hash embedding table. ~~int~~ | | `nM` | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. ~~int~~ | | `nC` | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. ~~int~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.MaxoutWindowEncoder.v1 {#MaxoutWindowEncoder} @@ -277,12 +271,10 @@ Embed [`Doc`](/api/doc) objects with their vocab's vectors table, applying a learned linear projection to control the dimensionality. See the documentation on [static vectors](/usage/embeddings-transformers#static-vectors) for details. - - | Name |  Description | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nO` | Defaults to `None`. ~~Optional[int]~~ | -| `nM` | Defaults to `None`. ~~Optional[int]~~ | +| `nO` | The output width of the layer, after the linear projection. ~~Optional[int]~~ | +| `nM` | The width of the static vectors. ~~Optional[int]~~ | | `dropout` | Optional dropout rate. If set, it's applied per dimension over the whole batch. Defaults to `None`. ~~Optional[float]~~ | | `init_W` | The [initialization function](https://thinc.ai/docs/api-initializers). Defaults to [`glorot_uniform_init`](https://thinc.ai/docs/api-initializers#glorot_uniform_init). ~~Callable[[Ops, Tuple[int, ...]]], FloatsXd]~~ | | `key_attr` | Defaults to `"ORTH"`. ~~str~~ | @@ -311,7 +303,22 @@ architectures into your training config. > stride = 96 > ``` - +Load and wrap a transformer model from the Huggingface transformers library. +You can any transformer that has pretrained weights and a PyTorch +implementation. The `name` variable is passed through to the underlying +library, so it can be either a string or a path. If it's a string, the +pretrained weights will be downloaded via the transformers library if they are +not already available locally. + +In order to support longer documents, the `TransformerModel` layer allows you +to pass in a `get_spans` function that will divide up the `Doc` objects before +passing them through the transformer. Your spans are allowed to overlap or +exclude tokens. + +This layer outputs a `FullTransformerBatch` dataclass. In order to plug the +layer into most architectures, you'll probably need to map the raw transformer +output to token-aligned vectors using a layer such as `trfs2arrays`. + | Name | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -541,8 +548,6 @@ specific data and challenge. Stacked ensemble of a bag-of-words model and a neural network model. The neural network has an internal CNN Tok2Vec layer and uses attention. - - | Name | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | @@ -554,7 +559,7 @@ network has an internal CNN Tok2Vec layer and uses attention. | `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | | `dropout` | The dropout rate. ~~float~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.TextCatCNN.v1 {#TextCatCNN} @@ -581,14 +586,12 @@ A neural network model where token vectors are calculated using a CNN. The vectors are mean pooled and used as features in a feed-forward network. This architecture is usually less accurate than the ensemble, but runs faster. - - | Name | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | | `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.TextCatBOW.v1 {#TextCatBOW} @@ -606,15 +609,13 @@ architecture is usually less accurate than the ensemble, but runs faster. An ngram "bag-of-words" model. This architecture should run much faster than the others, but may not be as accurate, especially if texts are short. - - | Name | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | | `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | | `no_output_layer` | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"} @@ -659,13 +660,11 @@ into the "real world". This requires 3 main components: The `EntityLinker` model architecture is a Thinc `Model` with a [`Linear`](https://thinc.ai/api-layers#linear) output layer. - - | Name | Description | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | | `nO` | Output dimension, determined by the length of the vectors encoding each entity in the KB. If the `nO` dimension is not set, the entity linking component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.EmptyKB.v1 {#EmptyKB} From f5bcc102686e443e46147b400cec32136a609f75 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Fri, 21 Aug 2020 15:34:54 +0200 Subject: [PATCH 62/92] Update architectures --- website/docs/api/architectures.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index 993f25bbc..c74737b66 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -315,9 +315,9 @@ to pass in a `get_spans` function that will divide up the `Doc` objects before passing them through the transformer. Your spans are allowed to overlap or exclude tokens. -This layer outputs a `FullTransformerBatch` dataclass. In order to plug the -layer into most architectures, you'll probably need to map the raw transformer -output to token-aligned vectors using a layer such as `trfs2arrays`. +This layer is usually used directly by the `Transformer` component, which +allows you to share the transformer weights across your pipeline. For a layer +that's configured for use in other components, see `Tok2VecTransformer`. | Name | Description | From af36d77d01866d310b0258f69d11a23d829dc231 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 15:56:03 +0200 Subject: [PATCH 63/92] fix typo in docstring --- spacy/tests/regression/test_issue4501-5000.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spacy/tests/regression/test_issue4501-5000.py b/spacy/tests/regression/test_issue4501-5000.py index 0d4ce9a30..d16ecc1e6 100644 --- a/spacy/tests/regression/test_issue4501-5000.py +++ b/spacy/tests/regression/test_issue4501-5000.py @@ -65,7 +65,7 @@ def test_issue4590(en_vocab): def test_issue4651_with_phrase_matcher_attr(): - """Test that the EntityRuler PhraseMatcher is deserialize correctly using + """Test that the EntityRuler PhraseMatcher is deserialized correctly using the method from_disk when the EntityRuler argument phrase_matcher_attr is specified. """ @@ -87,7 +87,7 @@ def test_issue4651_with_phrase_matcher_attr(): def test_issue4651_without_phrase_matcher_attr(): - """Test that the EntityRuler PhraseMatcher is deserialize correctly using + """Test that the EntityRuler PhraseMatcher is deserialized correctly using the method from_disk when the EntityRuler argument phrase_matcher_attr is not specified. """ From 74cb6d39d0d1f00af10a9b521aec36206baf457f Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 16:11:38 +0200 Subject: [PATCH 64/92] Update docs [ci skip] --- website/docs/api/architectures.md | 49 +++++++-------- website/docs/usage/architectures.md | 92 +++++++++++++++++++++++++++++ website/meta/sidebars.json | 5 ++ website/meta/type-annotations.json | 2 + website/src/styles/code.module.sass | 2 +- 5 files changed, 125 insertions(+), 25 deletions(-) create mode 100644 website/docs/usage/architectures.md diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index c74737b66..fd88434f1 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -114,7 +114,7 @@ argument that connects to the shared `tok2vec` component in the pipeline. | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `width` | The width of the vectors produced by the "upstream" [`Tok2Vec`](/api/tok2vec) component. ~~int~~ | | `upstream` | A string to identify the "upstream" `Tok2Vec` component to communicate with. The upstream name should either be the wildcard string `"*"`, or the name of the `Tok2Vec` component. You'll almost never have multiple upstream `Tok2Vec` components, so the wildcard string will almost always be fine. ~~str~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.MultiHashEmbed.v1 {#MultiHashEmbed} @@ -143,7 +143,7 @@ representation. | `rows` | The number of rows for the embedding tables. Can be low, due to the hashing trick. Embeddings for prefix, suffix and word shape use half as many rows. Recommended values are between `2000` and `10000`. ~~int~~ | | `also_embed_subwords` | Whether to use the `PREFIX`, `SUFFIX` and `SHAPE` features in the embeddings. If not using these, you may need more rows in your hash embeddings, as there will be increased chance of collisions. ~~bool~~ | | `also_use_static_vectors` | Whether to also use static word vectors. Requires a vectors table to be loaded in the [Doc](/api/doc) objects' vocab. ~~bool~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.CharacterEmbed.v1 {#CharacterEmbed} @@ -180,7 +180,7 @@ construct a single vector to represent the information. | `rows` | The number of rows in the `NORM` hash embedding table. ~~int~~ | | `nM` | The dimensionality of the character embeddings. Recommended values are between `16` and `64`. ~~int~~ | | `nC` | The number of UTF-8 bytes to embed per word. Recommended values are between `3` and `8`, although it may depend on the length of words in the language. ~~int~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], List[Floats2d]]~~ | ### spacy.MaxoutWindowEncoder.v1 {#MaxoutWindowEncoder} @@ -273,8 +273,8 @@ on [static vectors](/usage/embeddings-transformers#static-vectors) for details. | Name |  Description | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `nO` | The output width of the layer, after the linear projection. ~~Optional[int]~~ | -| `nM` | The width of the static vectors. ~~Optional[int]~~ | +| `nO` | The output width of the layer, after the linear projection. ~~Optional[int]~~ | +| `nM` | The width of the static vectors. ~~Optional[int]~~ | | `dropout` | Optional dropout rate. If set, it's applied per dimension over the whole batch. Defaults to `None`. ~~Optional[float]~~ | | `init_W` | The [initialization function](https://thinc.ai/docs/api-initializers). Defaults to [`glorot_uniform_init`](https://thinc.ai/docs/api-initializers#glorot_uniform_init). ~~Callable[[Ops, Tuple[int, ...]]], FloatsXd]~~ | | `key_attr` | Defaults to `"ORTH"`. ~~str~~ | @@ -303,22 +303,23 @@ architectures into your training config. > stride = 96 > ``` -Load and wrap a transformer model from the Huggingface transformers library. -You can any transformer that has pretrained weights and a PyTorch -implementation. The `name` variable is passed through to the underlying -library, so it can be either a string or a path. If it's a string, the -pretrained weights will be downloaded via the transformers library if they are -not already available locally. - -In order to support longer documents, the `TransformerModel` layer allows you -to pass in a `get_spans` function that will divide up the `Doc` objects before -passing them through the transformer. Your spans are allowed to overlap or -exclude tokens. - -This layer is usually used directly by the `Transformer` component, which -allows you to share the transformer weights across your pipeline. For a layer -that's configured for use in other components, see `Tok2VecTransformer`. +Load and wrap a transformer model from the +[HuggingFace `transformers`](https://huggingface.co/transformers) library. You +can any transformer that has pretrained weights and a PyTorch implementation. +The `name` variable is passed through to the underlying library, so it can be +either a string or a path. If it's a string, the pretrained weights will be +downloaded via the transformers library if they are not already available +locally. +In order to support longer documents, the +[TransformerModel](/api/architectures#TransformerModel) layer allows you to pass +in a `get_spans` function that will divide up the [`Doc`](/api/doc) objects +before passing them through the transformer. Your spans are allowed to overlap +or exclude tokens. This layer is usually used directly by the +[`Transformer`](/api/transformer) component, which allows you to share the +transformer weights across your pipeline. For a layer that's configured for use +in other components, see +[Tok2VecTransformer](/api/architectures#Tok2VecTransformer). | Name | Description | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -559,7 +560,7 @@ network has an internal CNN Tok2Vec layer and uses attention. | `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | | `dropout` | The dropout rate. ~~float~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.TextCatCNN.v1 {#TextCatCNN} @@ -591,7 +592,7 @@ architecture is usually less accurate than the ensemble, but runs faster. | `exclusive_classes` | Whether or not categories are mutually exclusive. ~~bool~~ | | `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.TextCatBOW.v1 {#TextCatBOW} @@ -615,7 +616,7 @@ others, but may not be as accurate, especially if texts are short. | `ngram_size` | Determines the maximum length of the n-grams in the BOW model. For instance, `ngram_size=3`would give unigram, trigram and bigram features. ~~int~~ | | `no_output_layer` | Whether or not to add an output layer to the model (`Softmax` activation if `exclusive_classes` is `True`, else `Logistic`. ~~bool~~ | | `nO` | Output dimension, determined by the number of different labels. If not set, the [`TextCategorizer`](/api/textcategorizer) component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ## Entity linking architectures {#entitylinker source="spacy/ml/models/entity_linker.py"} @@ -664,7 +665,7 @@ The `EntityLinker` model architecture is a Thinc `Model` with a | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tok2vec` | The [`tok2vec`](#tok2vec) layer of the model. ~~Model~~ | | `nO` | Output dimension, determined by the length of the vectors encoding each entity in the KB. If the `nO` dimension is not set, the entity linking component will set it when `begin_training` is called. ~~Optional[int]~~ | -| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | +| **CREATES** | The model using the architecture. ~~Model[List[Doc], Floats2d]~~ | ### spacy.EmptyKB.v1 {#EmptyKB} diff --git a/website/docs/usage/architectures.md b/website/docs/usage/architectures.md new file mode 100644 index 000000000..e9dddfed7 --- /dev/null +++ b/website/docs/usage/architectures.md @@ -0,0 +1,92 @@ +--- +title: Layers and Model Architectures +teaser: Power spaCy components with custom neural networks +menu: + - ['Type Signatures', 'type-sigs'] + - ['Defining Sublayers', 'sublayers'] + - ['PyTorch & TensorFlow', 'frameworks'] + - ['Trainable Components', 'components'] +--- + +​ A **model architecture** is a function that wires up a +[Thinc `Model`](https://thinc.ai/docs/api-model) instance, which you can then +use in a component or as a layer of a larger network. You can use Thinc as a +thin wrapper around frameworks such as PyTorch, TensorFlow or MXNet, or you can +implement your logic in Thinc directly. ​ spaCy's built-in components will never +construct their `Model` instances themselves, so you won't have to subclass the +component to change its model architecture. You can just **update the config** +so that it refers to a different registered function. Once the component has +been created, its model instance has already been assigned, so you cannot change +its model architecture. The architecture is like a recipe for the network, and +you can't change the recipe once the dish has already been prepared. You have to +make a new one. ​ + +## Type signatures {#type-sigs} + +​ The Thinc `Model` class is a **generic type** that can specify its input and +output types. Python uses a square-bracket notation for this, so the type +~~Model[List, Dict]~~ says that each batch of inputs to the model will be a +list, and the outputs will be a dictionary. Both `typing.List` and `typing.Dict` +are also generics, allowing you to be more specific about the data. For +instance, you can write ~~Model[List[Doc], Dict[str, float]]~~ to specify that +the model expects a list of [`Doc`](/api/doc) objects as input, and returns a +dictionary mapping strings to floats. Some of the most common types you'll see +are: ​ + +| Type | Description | +| ------------------ | ---------------------------------------------------------------------------------------------------- | +| ~~List[Doc]~~ | A batch of [`Doc`](/api/doc) objects. Most components expect their models to take this as input. | +| ~~Floats2d~~ | A two-dimensional `numpy` or `cupy` array of floats. Usually 32-bit. | +| ~~Ints2d~~ | A two-dimensional `numpy` or `cupy` array of integers. Common dtypes include uint64, int32 and int8. | +| ~~List[Floats2d]~~ | A list of two-dimensional arrays, generally with one array per `Doc` and one row per token. | +| ~~Ragged~~ | A container to handle variable-length sequence data in an unpadded contiguous array. | +| ~~Padded~~ | A container to handle variable-length sequence data in a passed contiguous array. | + +The model type-signatures help you figure out which model architectures and +components can fit together. For instance, the +[`TextCategorizer`](/api/textcaregorizer) class expects a model typed +~~Model[List[Doc], Floats2d]~~, because the model will predict one row of +category probabilities per `Doc`. In contrast, the `Tagger` class expects a +model typed ~~Model[List[Doc], List[Floats2d]]~~, because it needs to predict +one row of probabilities per token. ​ There's no guarantee that two models with +the same type-signature can be used interchangeably. There are many other ways +they could be incompatible. However, if the types don't match, they almost +surely _won't_ be compatible. This little bit of validation goes a long way, +especially if you configure your editor or other tools to highlight these errors +early. Thinc will also verify that your types match correctly when your config +file is processed at the beginning of training. ​ + +## Defining sublayers {#sublayers} + +​ Model architecture functions often accept sublayers as arguments, so that you +can try substituting a different layer into the network. Depending on how the +architecture function is structured, you might be able to define your network +structure entirely through the [config system](/usage/training#config), using +layers that have already been defined. ​The +[transformers documentation](/usage/embeddings-transformers#transformers) +section shows a common example of swapping in a different sublayer. In most NLP +neural network models, the most important parts of the network are what we refer +to as the +[embed and encode](https://explosion.ai/blog/embed-encode-attend-predict) steps. +These steps together compute dense, context-sensitive representations of the +tokens. Most of spaCy's default architectures accept a `tok2vec` layer as an +argument, so you can control this important part of the network separately. This +makes it easy to switch between transformer, CNN, BiLSTM or other feature +extraction approaches. And if you want to define your own solution, all you need +to do is register a ~~Model[List[Doc], List[Floats2d]]~~ architecture function, +and you'll be able to try it out in any of spaCy components. ​ + +### Registering new architectures + +- Recap concept, link to config docs. ​ + +## Wrapping PyTorch, TensorFlow and other frameworks {#frameworks} + +- Explain concept +- Link off to notebook ​ + +## Models for trainable components {#components} + +- Interaction with `predict`, `get_loss` and `set_annotations` +- Initialization life-cycle with `begin_training`. +- Link to relation extraction notebook. diff --git a/website/meta/sidebars.json b/website/meta/sidebars.json index c830619c5..4840eb537 100644 --- a/website/meta/sidebars.json +++ b/website/meta/sidebars.json @@ -24,6 +24,11 @@ "tag": "new" }, { "text": "Training Models", "url": "/usage/training", "tag": "new" }, + { + "text": "Layers & Model Architectures", + "url": "/usage/architectures", + "tag": "new" + }, { "text": "spaCy Projects", "url": "/usage/projects", "tag": "new" }, { "text": "Saving & Loading", "url": "/usage/saving-loading" }, { "text": "Visualizers", "url": "/usage/visualizers" } diff --git a/website/meta/type-annotations.json b/website/meta/type-annotations.json index 3cfcf5f75..b1d94403d 100644 --- a/website/meta/type-annotations.json +++ b/website/meta/type-annotations.json @@ -29,6 +29,8 @@ "Optimizer": "https://thinc.ai/docs/api-optimizers", "Model": "https://thinc.ai/docs/api-model", "Ragged": "https://thinc.ai/docs/api-types#ragged", + "Padded": "https://thinc.ai/docs/api-types#padded", + "Ints2d": "https://thinc.ai/docs/api-types#types", "Floats2d": "https://thinc.ai/docs/api-types#types", "Floats3d": "https://thinc.ai/docs/api-types#types", "FloatsXd": "https://thinc.ai/docs/api-types#types", diff --git a/website/src/styles/code.module.sass b/website/src/styles/code.module.sass index 2d213d001..aa1f499dd 100644 --- a/website/src/styles/code.module.sass +++ b/website/src/styles/code.module.sass @@ -67,7 +67,7 @@ border: 0 // Special style for types in API tables - td > &:last-child + td:not(:first-child) > &:last-child display: block border-top: 1px dotted var(--color-subtle) border-radius: 0 From 2cc4640385523f8b077d374b118cf83de91f7d87 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 16:21:55 +0200 Subject: [PATCH 65/92] Update docs [ci skip] --- .../docs/usage/{architectures.md => layers-architectures.md} | 3 ++- website/docs/usage/training.md | 2 +- website/docs/usage/v3.md | 4 +++- website/meta/sidebars.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) rename website/docs/usage/{architectures.md => layers-architectures.md} (98%) diff --git a/website/docs/usage/architectures.md b/website/docs/usage/layers-architectures.md similarity index 98% rename from website/docs/usage/architectures.md rename to website/docs/usage/layers-architectures.md index e9dddfed7..8d26a4139 100644 --- a/website/docs/usage/architectures.md +++ b/website/docs/usage/layers-architectures.md @@ -6,6 +6,7 @@ menu: - ['Defining Sublayers', 'sublayers'] - ['PyTorch & TensorFlow', 'frameworks'] - ['Trainable Components', 'components'] +next: /usage/projects --- ​ A **model architecture** is a function that wires up a @@ -44,7 +45,7 @@ are: ​ The model type-signatures help you figure out which model architectures and components can fit together. For instance, the -[`TextCategorizer`](/api/textcaregorizer) class expects a model typed +[`TextCategorizer`](/api/textcategorizer) class expects a model typed ~~Model[List[Doc], Floats2d]~~, because the model will predict one row of category probabilities per `Doc`. In contrast, the `Tagger` class expects a model typed ~~Model[List[Doc], List[Floats2d]]~~, because it needs to predict diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 116561cd2..00eb2b882 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -1,6 +1,6 @@ --- title: Training Models -next: /usage/projects +next: /usage/layers-architectures menu: - ['Introduction', 'basics'] - ['Quickstart', 'quickstart'] diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index d71ecba31..b017dcdab 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -292,7 +292,9 @@ format for documenting argument and return types. - **Usage: ** [Embeddings & Transformers](/usage/embeddings-transformers), - [Training models](/usage/training), [Projects](/usage/projects), + [Training models](/usage/training), + [Layers & Architectures](/usage/layers-architectures), + [Projects](/usage/projects), [Custom pipeline components](/usage/processing-pipelines#custom-components), [Custom tokenizers](/usage/linguistic-features#custom-tokenizer) - **API Reference: ** [Library architecture](/api), diff --git a/website/meta/sidebars.json b/website/meta/sidebars.json index 4840eb537..94fbc2492 100644 --- a/website/meta/sidebars.json +++ b/website/meta/sidebars.json @@ -26,7 +26,7 @@ { "text": "Training Models", "url": "/usage/training", "tag": "new" }, { "text": "Layers & Model Architectures", - "url": "/usage/architectures", + "url": "/usage/layers-architectures", "tag": "new" }, { "text": "spaCy Projects", "url": "/usage/projects", "tag": "new" }, From e92bd6e1c1d480214c9bdabfdd869b22f1ac317a Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 17:42:19 +0200 Subject: [PATCH 66/92] alphabetize training lists --- website/docs/api/data-formats.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index 8b67aa263..56ce663ec 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -131,22 +131,22 @@ process that are used when you run [`spacy train`](/api/cli#train). | Name | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | -| `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | +| `batcher` | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. Defaults to [`batch_by_words`](/api/top-level#batch_by_words). ~~Callable[[Iterator[Doc], Iterator[List[Doc]]]]~~ | +| `dev_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~ | +| `dropout` | The dropout rate. Defaults to `0.1`. ~~float~~ | +| `eval_frequency` | How often to evaluate during training (steps). Defaults to `200`. ~~int~~ | +| `frozen_components` | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. Defaults to `[]`. ~~List[str]~~ | | `init_tok2vec` | Optional path to pretrained tok2vec weights created with [`spacy pretrain`](/api/cli#pretrain). Defaults to variable `${paths.init_tok2vec}`. ~~Optional[str]~~ | -| `raw_text` | Optional path to a jsonl file with unlabelled text documents for a [rehearsal](/api/language#rehearse) step. Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | -| `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | -| `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | | `max_epochs` | Maximum number of epochs to train for. Defaults to `0`. ~~int~~ | | `max_steps` | Maximum number of update steps to train for. Defaults to `20000`. ~~int~~ | -| `eval_frequency` | How often to evaluate during training (steps). Defaults to `200`. ~~int~~ | -| `score_weights` | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. Defaults to `{}`. ~~Dict[str, float]~~ | -| `frozen_components` | Pipeline component names that are "frozen" and shouldn't be updated during training. See [here](/usage/training#config-components) for details. Defaults to `[]`. ~~List[str]~~ | -| `train_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~ | -| `dev_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~ | -| `batcher` | Callable that takes an iterator of [`Doc`](/api/doc) objects and yields batches of `Doc`s. Defaults to [`batch_by_words`](/api/top-level#batch_by_words). ~~Callable[[Iterator[Doc], Iterator[List[Doc]]]]~~ | | `optimizer` | The optimizer. The learning rate schedule and other settings can be configured as part of the optimizer. Defaults to [`Adam`](https://thinc.ai/docs/api-optimizers#adam). ~~Optimizer~~ | +| `patience` | How many steps to continue without improvement in evaluation score. Defaults to `1600`. ~~int~~ | +| `raw_text` | Optional path to a jsonl file with unlabelled text documents for a [rehearsal](/api/language#rehearse) step. Defaults to variable `${paths.raw}`. ~~Optional[str]~~ | +| `score_weights` | Score names shown in metrics mapped to their weight towards the final weighted score. See [here](/usage/training#metrics) for details. Defaults to `{}`. ~~Dict[str, float]~~ | +| `seed` | The random seed. Defaults to variable `${system.seed}`. ~~int~~ | +| `train_corpus` | Callable that takes the current `nlp` object and yields [`Example`](/api/example) objects. Defaults to [`Corpus`](/api/corpus). ~~Callable[[Language], Iterator[Example]]~~ | +| `vectors` | Model name or path to model containing pretrained word vectors to use, e.g. created with [`init model`](/api/cli#init-model). Defaults to `null`. ~~Optional[str]~~ | ### pretraining {#config-pretraining tag="section,optional"} From 518a1f97f32ca63615567a50d6716f4ba5885acc Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 17:55:15 +0200 Subject: [PATCH 67/92] remove outdated TODO's --- website/docs/api/data-formats.md | 2 -- website/docs/usage/training.md | 5 ----- 2 files changed, 7 deletions(-) diff --git a/website/docs/api/data-formats.md b/website/docs/api/data-formats.md index 56ce663ec..727c0f35c 100644 --- a/website/docs/api/data-formats.md +++ b/website/docs/api/data-formats.md @@ -127,8 +127,6 @@ $ python -m spacy train config.cfg --paths.train ./corpus/train.spacy This section defines settings and controls for the training and evaluation process that are used when you run [`spacy train`](/api/cli#train). - - | Name | Description | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `accumulate_gradient` | Whether to divide the batch up into substeps. Defaults to `1`. ~~int~~ | diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 116561cd2..5f188a75a 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -104,11 +104,6 @@ workflows, from data preprocessing to training and packaging your model. ## Training config {#config} - - Training config files include all **settings and hyperparameters** for training your model. Instead of providing lots of arguments on the command line, you only need to pass your `config.cfg` file to [`spacy train`](/api/cli#train). Under From c6659e37d8a1d3754c52f2333631e75fcc96720e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:02:20 +0200 Subject: [PATCH 68/92] small fixes --- website/docs/usage/training.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index 5f188a75a..d738cfd04 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -35,8 +35,8 @@ ready-to-use spaCy models. The recommended way to train your spaCy models is via the [`spacy train`](/api/cli#train) command on the command line. It only needs a single [`config.cfg`](#config) **configuration file** that includes all settings -and hyperparameters. You can optionally [overwritten](#config-overrides) -settings on the command line, and load in a Python file to register +and hyperparameters. You can optionally [overwrite](#config-overrides) settings +on the command line, and load in a Python file to register [custom functions](#custom-code) and architectures. This quickstart widget helps you generate a starter config with the **recommended settings** for your specific use case. It's also available in spaCy as the @@ -82,7 +82,7 @@ $ python -m spacy init fill-config base_config.cfg config.cfg Instead of exporting your starter config from the quickstart widget and auto-filling it, you can also use the [`init config`](/api/cli#init-config) -command and specify your requirement and settings and CLI arguments. You can now +command and specify your requirement and settings as CLI arguments. You can now add your data and run [`train`](/api/cli#train) with your config. See the [`convert`](/api/cli#convert) command for details on how to convert your data to spaCy's binary `.spacy` format. You can either include the data paths in the @@ -121,9 +121,10 @@ Some of the main advantages and features of spaCy's training config are: functions like [model architectures](/api/architectures), [optimizers](https://thinc.ai/docs/api-optimizers) or [schedules](https://thinc.ai/docs/api-schedules) and define arguments that are - passed into them. You can also register your own functions to define - [custom architectures](#custom-functions), reference them in your config and - tweak their parameters. + passed into them. You can also + [register your own functions](#custom-functions) to define custom + architectures or methods, reference them in your config and tweak their + parameters. - **Interpolation.** If you have hyperparameters or other settings used by multiple components, define them once and reference them as [variables](#config-interpolation). From dc98f69b57dd5b786dffbbefc9cfc23ea53dc724 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:10:21 +0200 Subject: [PATCH 69/92] alphabetize registries --- website/docs/api/top-level.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/api/top-level.md b/website/docs/api/top-level.md index 9c65b2982..797fa0191 100644 --- a/website/docs/api/top-level.md +++ b/website/docs/api/top-level.md @@ -299,20 +299,20 @@ factories. | Registry name | Description | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `architectures` | Registry for functions that create [model architectures](/api/architectures). Can be used to register custom model architectures and reference them in the `config.cfg`. | -| `factories` | Registry for functions that create [pipeline components](/usage/processing-pipelines#custom-components). Added automatically when you use the `@spacy.component` decorator and also reads from [entry points](/usage/saving-loading#entry-points). | -| `tokenizers` | Registry for tokenizer factories. Registered functions should return a callback that receives the `nlp` object and returns a [`Tokenizer`](/api/tokenizer) or a custom callable. | -| `languages` | Registry for language-specific `Language` subclasses. Automatically reads from [entry points](/usage/saving-loading#entry-points). | -| `lookups` | Registry for large lookup tables available via `vocab.lookups`. | -| `displacy_colors` | Registry for custom color scheme for the [`displacy` NER visualizer](/usage/visualizers). Automatically reads from [entry points](/usage/saving-loading#entry-points). | | `assets` | Registry for data assets, knowledge bases etc. | -| `callbacks` | Registry for custom callbacks to [modify the `nlp` object](/usage/training#custom-code-nlp-callbacks) before training. | -| `readers` | Registry for training and evaluation data readers like [`Corpus`](/api/corpus). | | `batchers` | Registry for training and evaluation [data batchers](#batchers). | -| `optimizers` | Registry for functions that create [optimizers](https://thinc.ai/docs/api-optimizers). | -| `schedules` | Registry for functions that create [schedules](https://thinc.ai/docs/api-schedules). | -| `layers` | Registry for functions that create [layers](https://thinc.ai/docs/api-layers). | -| `losses` | Registry for functions that create [losses](https://thinc.ai/docs/api-loss). | +| `callbacks` | Registry for custom callbacks to [modify the `nlp` object](/usage/training#custom-code-nlp-callbacks) before training. | +| `displacy_colors` | Registry for custom color scheme for the [`displacy` NER visualizer](/usage/visualizers). Automatically reads from [entry points](/usage/saving-loading#entry-points). | +| `factories` | Registry for functions that create [pipeline components](/usage/processing-pipelines#custom-components). Added automatically when you use the `@spacy.component` decorator and also reads from [entry points](/usage/saving-loading#entry-points). | | `initializers` | Registry for functions that create [initializers](https://thinc.ai/docs/api-initializers). | +| `languages` | Registry for language-specific `Language` subclasses. Automatically reads from [entry points](/usage/saving-loading#entry-points). | +| `layers` | Registry for functions that create [layers](https://thinc.ai/docs/api-layers). | +| `lookups` | Registry for large lookup tables available via `vocab.lookups`. | +| `losses` | Registry for functions that create [losses](https://thinc.ai/docs/api-loss). | +| `optimizers` | Registry for functions that create [optimizers](https://thinc.ai/docs/api-optimizers). | +| `readers` | Registry for training and evaluation data readers like [`Corpus`](/api/corpus). | +| `schedules` | Registry for functions that create [schedules](https://thinc.ai/docs/api-schedules). | +| `tokenizers` | Registry for tokenizer factories. Registered functions should return a callback that receives the `nlp` object and returns a [`Tokenizer`](/api/tokenizer) or a custom callable. | ### spacy-transformers registry {#registry-transformers} From ad2332d4b7758aeccc97fbf05fcd03097d51ae2e Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:10:31 +0200 Subject: [PATCH 70/92] alphabetize registries --- website/docs/usage/training.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index d738cfd04..f3d349b56 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -222,21 +222,21 @@ passed to the component factory as arguments. This lets you configure the model settings and hyperparameters. If a component block defines a `source`, the component will be copied over from an existing pretrained model, with its existing weights. This lets you include an already trained component in your -model pipeline, or update a pretrained components with more data specific to +model pipeline, or update a pretrained component with more data specific to your use case. ```ini ### config.cfg (excerpt) [components] -# "parser" and "ner" are sourced from pretrained model +# "parser" and "ner" are sourced from a pretrained model [components.parser] source = "en_core_web_sm" [components.ner] source = "en_core_web_sm" -# "textcat" and "custom" are created blank from built-in / custom factory +# "textcat" and "custom" are created blank from a built-in / custom factory [components.textcat] factory = "textcat" @@ -290,7 +290,7 @@ batch_size = 128 ``` To refer to a function instead, you can make `[training.batch_size]` its own -section and use the `@` syntax specify the function and its arguments – in this +section and use the `@` syntax to specify the function and its arguments – in this case [`compounding.v1`](https://thinc.ai/docs/api-schedules#compounding) defined in the [function registry](/api/top-level#registry). All other values defined in the block are passed to the function as keyword arguments when it's initialized. From da48c6a2a2c67d1087e073cd92530449a03dc904 Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:25:26 +0200 Subject: [PATCH 71/92] several small updates --- website/docs/usage/training.md | 35 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index f3d349b56..fcc1a44d2 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -222,8 +222,8 @@ passed to the component factory as arguments. This lets you configure the model settings and hyperparameters. If a component block defines a `source`, the component will be copied over from an existing pretrained model, with its existing weights. This lets you include an already trained component in your -model pipeline, or update a pretrained component with more data specific to -your use case. +model pipeline, or update a pretrained component with more data specific to your +use case. ```ini ### config.cfg (excerpt) @@ -290,11 +290,11 @@ batch_size = 128 ``` To refer to a function instead, you can make `[training.batch_size]` its own -section and use the `@` syntax to specify the function and its arguments – in this -case [`compounding.v1`](https://thinc.ai/docs/api-schedules#compounding) defined -in the [function registry](/api/top-level#registry). All other values defined in -the block are passed to the function as keyword arguments when it's initialized. -You can also use this mechanism to register +section and use the `@` syntax to specify the function and its arguments – in +this case [`compounding.v1`](https://thinc.ai/docs/api-schedules#compounding) +defined in the [function registry](/api/top-level#registry). All other values +defined in the block are passed to the function as keyword arguments when it's +initialized. You can also use this mechanism to register [custom implementations and architectures](#custom-functions) and reference them from your configs. @@ -722,9 +722,9 @@ a stream of items into a stream of batches. spaCy has several useful built-in [batching strategies](/api/top-level#batchers) with customizable sizes, but it's also easy to implement your own. For instance, the following function takes the stream of generated [`Example`](/api/example) objects, and removes those which -have the exact same underlying raw text, to avoid duplicates within each batch. -Note that in a more realistic implementation, you'd also want to check whether -the annotations are exactly the same. +have the same underlying raw text, to avoid duplicates within each batch. Note +that in a more realistic implementation, you'd also want to check whether the +annotations are the same. > #### config.cfg > @@ -839,8 +839,8 @@ called the **gold standard**. It's initialized with a [`Doc`](/api/doc) object that will hold the predictions, and another `Doc` object that holds the gold-standard annotations. It also includes the **alignment** between those two documents if they differ in tokenization. The `Example` class ensures that spaCy -can rely on one **standardized format** that's passed through the pipeline. -Here's an example of a simple `Example` for part-of-speech tags: +can rely on one **standardized format** that's passed through the pipeline. For +instance, let's say we want to define gold-standard part-of-speech tags: ```python words = ["I", "like", "stuff"] @@ -852,9 +852,10 @@ reference = Doc(vocab, words=words).from_array("TAG", numpy.array(tag_ids, dtype example = Example(predicted, reference) ``` -Alternatively, the `reference` `Doc` with the gold-standard annotations can be -created from a dictionary with keyword arguments specifying the annotations, -like `tags` or `entities`. Using the `Example` object and its gold-standard +As this is quite verbose, there's an alternative way to create the reference +`Doc` with the gold-standard annotations. The function `Example.from_dict` takes +a dictionary with keyword arguments specifying the annotations, like `tags` or +`entities`. Using the resulting `Example` object and its gold-standard annotations, the model can be updated to learn a sentence of three words with their assigned part-of-speech tags. @@ -879,7 +880,7 @@ example = Example.from_dict(predicted, {"tags": tags}) Here's another example that shows how to define gold-standard named entities. The letters added before the labels refer to the tags of the [BILUO scheme](/usage/linguistic-features#updating-biluo) – `O` is a token -outside an entity, `U` an single entity unit, `B` the beginning of an entity, +outside an entity, `U` a single entity unit, `B` the beginning of an entity, `I` a token inside an entity and `L` the last token of an entity. ```python @@ -954,7 +955,7 @@ dictionary of annotations: ```diff text = "Facebook released React in 2014" annotations = {"entities": ["U-ORG", "O", "U-TECHNOLOGY", "O", "U-DATE"]} -+ example = Example.from_dict(nlp.make_doc(text), {"entities": entities}) ++ example = Example.from_dict(nlp.make_doc(text), annotations) - nlp.update([text], [annotations]) + nlp.update([example]) ``` From 262552010d431937c7258d27b3e6a7ee82b8bceb Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:34:02 +0200 Subject: [PATCH 72/92] context manager with space (for consistency) --- website/docs/usage/processing-pipelines.md | 4 ++-- website/docs/usage/v3.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/website/docs/usage/processing-pipelines.md b/website/docs/usage/processing-pipelines.md index a863c6c32..614f113b3 100644 --- a/website/docs/usage/processing-pipelines.md +++ b/website/docs/usage/processing-pipelines.md @@ -265,7 +265,7 @@ for doc in nlp.pipe(texts, disable=["tagger", "parser"]): If you need to **execute more code** with components disabled – e.g. to reset the weights or update only some components during training – you can use the -[`nlp.select_pipes`](/api/language#select_pipes) contextmanager. At the end of +[`nlp.select_pipes`](/api/language#select_pipes) context manager. At the end of the `with` block, the disabled pipeline components will be restored automatically. Alternatively, `select_pipes` returns an object that lets you call its `restore()` method to restore the disabled components when needed. This @@ -274,7 +274,7 @@ blocks. ```python ### Disable for block -# 1. Use as a contextmanager +# 1. Use as a context manager with nlp.select_pipes(disable=["tagger", "parser"]): doc = nlp("I won't be tagged and parsed") doc = nlp("I will be tagged and parsed") diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index d71ecba31..8aad49cd1 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -249,7 +249,7 @@ The following methods, attributes and commands are new in spaCy v3.0. | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`Token.lex`](/api/token#attributes) | Access a token's [`Lexeme`](/api/lexeme). | | [`Token.morph`](/api/token#attributes) [`Token.morph_`](/api/token#attributes) | Access a token's morphological analysis. | -| [`Language.select_pipes`](/api/language#select_pipes) | Contextmanager for enabling or disabling specific pipeline components for a block. | +| [`Language.select_pipes`](/api/language#select_pipes) | Context manager for enabling or disabling specific pipeline components for a block. | | [`Language.analyze_pipes`](/api/language#analyze_pipes) | [Analyze](/usage/processing-pipelines#analysis) components and their interdependencies. | | [`Language.resume_training`](/api/language#resume_training) | Experimental: continue training a pretrained model and initialize "rehearsal" for components that implement a `rehearse` method to prevent catastrophic forgetting. | | [`@Language.factory`](/api/language#factory) [`@Language.component`](/api/language#component) | Decorators for [registering](/usage/processing-pipelines#custom-components) pipeline component factories and simple stateless component functions. | @@ -336,13 +336,13 @@ Note that spaCy v3.0 now requires **Python 3.6+**. [training config](/usage/training#config). - [`Language.add_pipe`](/api/language#add_pipe) now takes the **string name** of the component factory instead of the component function. -- **Custom pipeline components** now needs to be decorated with the +- **Custom pipeline components** now need to be decorated with the [`@Language.component`](/api/language#component) or [`@Language.factory`](/api/language#factory) decorator. - [`Language.update`](/api/language#update) now takes a batch of [`Example`](/api/example) objects instead of raw texts and annotations, or `Doc` and `GoldParse` objects. -- The `Language.disable_pipes` contextmanager has been replaced by +- The `Language.disable_pipes` context manager has been replaced by [`Language.select_pipes`](/api/language#select_pipes), which can explicitly disable or enable components. - The [`Language.update`](/api/language#update), From 942adf0f4d4bd0eeccecfcaa4f74f4794340d52b Mon Sep 17 00:00:00 2001 From: svlandeg Date: Fri, 21 Aug 2020 18:36:02 +0200 Subject: [PATCH 73/92] comma --- website/docs/usage/v3.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index 8aad49cd1..9fec3204f 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -249,7 +249,7 @@ The following methods, attributes and commands are new in spaCy v3.0. | ----------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [`Token.lex`](/api/token#attributes) | Access a token's [`Lexeme`](/api/lexeme). | | [`Token.morph`](/api/token#attributes) [`Token.morph_`](/api/token#attributes) | Access a token's morphological analysis. | -| [`Language.select_pipes`](/api/language#select_pipes) | Context manager for enabling or disabling specific pipeline components for a block. | +| [`Language.select_pipes`](/api/language#select_pipes) | Context manager for enabling or disabling specific pipeline components for a block. | | [`Language.analyze_pipes`](/api/language#analyze_pipes) | [Analyze](/usage/processing-pipelines#analysis) components and their interdependencies. | | [`Language.resume_training`](/api/language#resume_training) | Experimental: continue training a pretrained model and initialize "rehearsal" for components that implement a `rehearse` method to prevent catastrophic forgetting. | | [`@Language.factory`](/api/language#factory) [`@Language.component`](/api/language#component) | Decorators for [registering](/usage/processing-pipelines#custom-components) pipeline component factories and simple stateless component functions. | @@ -362,16 +362,16 @@ Note that spaCy v3.0 now requires **Python 3.6+**. ### Removed or renamed API {#incompat-removed} -| Removed | Replacement | -| -------------------------------------------------------- | ----------------------------------------------------------------------------------------- | -| `Language.disable_pipes` | [`Language.select_pipes`](/api/language#select_pipes) | -| `GoldParse` | [`Example`](/api/example) | -| `GoldCorpus` | [`Corpus`](/api/corpus) | -| `KnowledgeBase.load_bulk` `KnowledgeBase.dump` | [`KnowledgeBase.from_disk`](/api/kb#from_disk) [`KnowledgeBase.to_disk`](/api/kb#to_disk) | -| `spacy init-model` | [`spacy init model`](/api/cli#init-model) | -| `spacy debug-data` | [`spacy debug data`](/api/cli#debug-data) | -| `spacy profile` | [`spacy debug profile`](/api/cli#debug-profile) | -| `spacy link`, `util.set_data_path`, `util.get_data_path` | not needed, model symlinks are deprecated | +| Removed | Replacement | +| -------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `Language.disable_pipes` | [`Language.select_pipes`](/api/language#select_pipes) | +| `GoldParse` | [`Example`](/api/example) | +| `GoldCorpus` | [`Corpus`](/api/corpus) | +| `KnowledgeBase.load_bulk`, `KnowledgeBase.dump` | [`KnowledgeBase.from_disk`](/api/kb#from_disk), [`KnowledgeBase.to_disk`](/api/kb#to_disk) | +| `spacy init-model` | [`spacy init model`](/api/cli#init-model) | +| `spacy debug-data` | [`spacy debug data`](/api/cli#debug-data) | +| `spacy profile` | [`spacy debug profile`](/api/cli#debug-profile) | +| `spacy link`, `util.set_data_path`, `util.get_data_path` | not needed, model symlinks are deprecated | The following deprecated methods, attributes and arguments were removed in v3.0. Most of them have been **deprecated for a while** and many would previously From f102164a1f32c28b1c96f3b46a24d188c2398db1 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 19:34:06 +0200 Subject: [PATCH 74/92] Update docs [ci skip] --- website/docs/api/cli.md | 2 +- website/docs/usage/layers-architectures.md | 78 +++++++++++++++------- 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md index 9cadb2f0f..551147929 100644 --- a/website/docs/api/cli.md +++ b/website/docs/api/cli.md @@ -123,7 +123,7 @@ $ python -m spacy init config [output_file] [--lang] [--pipeline] [--optimize] [ | Name | Description | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `output_file` | Path to output `.cfg` file. If not set, the config is written to stdout so you can pipe it forward to a file. ~~Path (positional)~~ | +| `output_file` | Path to output `.cfg` file or `-` to write the config to stdout (so you can pipe it forward to a file). Note that if you're writing to stdout, no additional logging info is printed. ~~Path (positional)~~ | | `--lang`, `-l` | Optional code of the [language](/usage/models#languages) to use. Defaults to `"en"`. ~~str (option)~~ | | `--pipeline`, `-p` | Comma-separated list of trainable [pipeline components](/usage/processing-pipelines#built-in) to include in the model. Defaults to `"tagger,parser,ner"`. ~~str (option)~~ | | `--optimize`, `-o` | `"efficiency"` or `"accuracy"`. Whether to optimize for efficiency (faster inference, smaller model, lower memory consumption) or higher accuracy (potentially larger and slower model). This will impact the choice of architecture, pretrained weights and related hyperparameters. Defaults to `"efficiency"`. ~~str (option)~~ | diff --git a/website/docs/usage/layers-architectures.md b/website/docs/usage/layers-architectures.md index 8d26a4139..d9f80ce14 100644 --- a/website/docs/usage/layers-architectures.md +++ b/website/docs/usage/layers-architectures.md @@ -22,8 +22,29 @@ its model architecture. The architecture is like a recipe for the network, and you can't change the recipe once the dish has already been prepared. You have to make a new one. ​ + + ## Type signatures {#type-sigs} + + +> #### Example +> +> ```python +> @spacy.registry.architectures.register("spacy.Tagger.v1") +> def build_tagger_model( +> tok2vec: Model[List[Doc], List[Floats2d]], nO: Optional[int] = None +> ) -> Model[List[Doc], List[Floats2d]]: +> t2v_width = tok2vec.get_dim("nO") if tok2vec.has_dim("nO") else None +> output_layer = Softmax(nO, t2v_width, init_W=zero_init) +> softmax = with_array(output_layer) +> model = chain(tok2vec, softmax) +> model.set_ref("tok2vec", tok2vec) +> model.set_ref("softmax", output_layer) +> model.set_ref("output_layer", output_layer) +> return model +> ``` + ​ The Thinc `Model` class is a **generic type** that can specify its input and output types. Python uses a square-bracket notation for this, so the type ~~Model[List, Dict]~~ says that each batch of inputs to the model will be a @@ -43,39 +64,46 @@ are: ​ | ~~Ragged~~ | A container to handle variable-length sequence data in an unpadded contiguous array. | | ~~Padded~~ | A container to handle variable-length sequence data in a passed contiguous array. | -The model type-signatures help you figure out which model architectures and -components can fit together. For instance, the +The model type signatures help you figure out which model architectures and +components can **fit together**. For instance, the [`TextCategorizer`](/api/textcategorizer) class expects a model typed ~~Model[List[Doc], Floats2d]~~, because the model will predict one row of -category probabilities per `Doc`. In contrast, the `Tagger` class expects a -model typed ~~Model[List[Doc], List[Floats2d]]~~, because it needs to predict -one row of probabilities per token. ​ There's no guarantee that two models with -the same type-signature can be used interchangeably. There are many other ways -they could be incompatible. However, if the types don't match, they almost -surely _won't_ be compatible. This little bit of validation goes a long way, -especially if you configure your editor or other tools to highlight these errors -early. Thinc will also verify that your types match correctly when your config -file is processed at the beginning of training. ​ +category probabilities per [`Doc`](/api/doc). In contrast, the +[`Tagger`](/api/tagger) class expects a model typed ~~Model[List[Doc], +List[Floats2d]]~~, because it needs to predict one row of probabilities per +token. + +There's no guarantee that two models with the same type signature can be used +interchangeably. There are many other ways they could be incompatible. However, +if the types don't match, they almost surely _won't_ be compatible. This little +bit of validation goes a long way, especially if you +[configure your editor](https://thinc.ai/docs/usage-type-checking) or other +tools to highlight these errors early. Thinc will also verify that your types +match correctly when your config file is processed at the beginning of training. ## Defining sublayers {#sublayers} -​ Model architecture functions often accept sublayers as arguments, so that you -can try substituting a different layer into the network. Depending on how the -architecture function is structured, you might be able to define your network -structure entirely through the [config system](/usage/training#config), using -layers that have already been defined. ​The +​ Model architecture functions often accept **sublayers as arguments**, so that +you can try **substituting a different layer** into the network. Depending on +how the architecture function is structured, you might be able to define your +network structure entirely through the [config system](/usage/training#config), +using layers that have already been defined. ​The [transformers documentation](/usage/embeddings-transformers#transformers) -section shows a common example of swapping in a different sublayer. In most NLP -neural network models, the most important parts of the network are what we refer -to as the +section shows a common example of swapping in a different sublayer. + +In most neural network models for NLP, the most important parts of the network +are what we refer to as the [embed and encode](https://explosion.ai/blog/embed-encode-attend-predict) steps. These steps together compute dense, context-sensitive representations of the -tokens. Most of spaCy's default architectures accept a `tok2vec` layer as an -argument, so you can control this important part of the network separately. This -makes it easy to switch between transformer, CNN, BiLSTM or other feature -extraction approaches. And if you want to define your own solution, all you need -to do is register a ~~Model[List[Doc], List[Floats2d]]~~ architecture function, -and you'll be able to try it out in any of spaCy components. ​ +tokens. Most of spaCy's default architectures accept a +[`tok2vec` embedding layer](/api/architectures#tok2vec-arch) as an argument, so +you can control this important part of the network separately. This makes it +easy to **switch between** transformer, CNN, BiLSTM or other feature extraction +approaches. And if you want to define your own solution, all you need to do is +register a ~~Model[List[Doc], List[Floats2d]]~~ architecture function, and +you'll be able to try it out in any of spaCy components. ​ + + ### Registering new architectures From 27f81109d605b3f90517e4a05cf56094c0874969 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Fri, 21 Aug 2020 20:02:18 +0200 Subject: [PATCH 75/92] Update docs [ci skip] --- website/docs/images/thinc_mypy.jpg | Bin 0 -> 155643 bytes website/docs/usage/layers-architectures.md | 51 +++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 website/docs/images/thinc_mypy.jpg diff --git a/website/docs/images/thinc_mypy.jpg b/website/docs/images/thinc_mypy.jpg new file mode 100644 index 0000000000000000000000000000000000000000..c0f7ee636a8058fd5cec3711dbbb21e66a818f03 GIT binary patch literal 155643 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 + +If you're using a modern editor like Visual Studio Code, you can +[set up `mypy`](https://thinc.ai/docs/usage-type-checking#install) with the +custom Thinc plugin and get live feedback about mismatched types as you write +code. + +[![](../images/thinc_mypy.jpg)](https://thinc.ai/docs/usage-type-checking#linting) + + + ## Defining sublayers {#sublayers} ​ Model architecture functions often accept **sublayers as arguments**, so that @@ -111,8 +122,48 @@ you'll be able to try it out in any of spaCy components. ​ ## Wrapping PyTorch, TensorFlow and other frameworks {#frameworks} + + +Thinc allows you to wrap models written in other machine learning frameworks +like PyTorch, TensorFlow and MXNet using a unified +[`Model`](https://thinc.ai/docs/api-model) API. As well as **wrapping whole +models**, Thinc lets you call into an external framework for just **part of your +model**: you can have a model where you use PyTorch just for the transformer +layers, using "native" Thinc layers to do fiddly input and output +transformations and add on task-specific "heads", as efficiency is less of a +consideration for those parts of the network. + +Thinc uses a special class, [`Shim`](https://thinc.ai/docs/api-model#shim), to +hold references to external objects. This allows each wrapper space to define a +custom type, with whatever attributes and methods are helpful, to assist in +managing the communication between Thinc and the external library. The +[`Model`](/docs/api-model#model) class holds `shim` instances in a separate +list, and communicates with the shims about updates, serialization, changes of +device, etc. + +The wrapper will receive each batch of inputs, convert them into a suitable form +for the underlying model instance, and pass them over to the shim, which will +**manage the actual communication** with the model. The output is then passed +back into the wrapper, and converted for use in the rest of the network. The +equivalent procedure happens during backpropagation. Array conversion is handled +via the [DLPack](https://github.com/dmlc/dlpack) standard wherever possible, so +that data can be passed between the frameworks **without copying the data back** +to the host device unnecessarily. + +| Framework | Wrapper layer | Shim | DLPack | +| -------------- | ------------------------------------------------------------------------- | --------------------------------------------------------- | --------------- | +| **PyTorch** | [`PyTorchWrapper`](https://thinc.ai/docs/api-layers#pytorchwrapper) | [`PyTorchShim`](https://thinc.ai/docs/api-model#shims) | ✅ | +| **TensorFlow** | [`TensorFlowWrapper`](https://thinc.ai/docs/api-layers#tensorflowwrapper) | [`TensorFlowShim`](https://thinc.ai/docs/api-model#shims) | ❌ 1 | +| **MXNet** | [`MXNetWrapper`](https://thinc.ai/docs/api-layers#mxnetwrapper) | [`MXNetShim`](https://thinc.ai/docs/api-model#shims) | ✅ | + +1. DLPack support in TensorFlow is now + [available](<(https://github.com/tensorflow/tensorflow/issues/24453)>) but + still experimental. + + ## Models for trainable components {#components} From ff5ba14d06e8a3576515a91e07583eba775f19f0 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 12:26:50 +0200 Subject: [PATCH 76/92] Remove font [ci skip] --- website/src/fonts/jetBrainsmono-italic.woff | Bin 69120 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 website/src/fonts/jetBrainsmono-italic.woff diff --git a/website/src/fonts/jetBrainsmono-italic.woff b/website/src/fonts/jetBrainsmono-italic.woff deleted file mode 100644 index f3ddf4db5f385bfbd08e7a745e51faefa54345a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 69120 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 From 9740f1712baf4a9e51e7db7f7efbb61af060de07 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 12:27:02 +0200 Subject: [PATCH 77/92] Re-add font with lowercase name [ci skip] --- website/src/fonts/jetbrainsmono-italic.woff | Bin 0 -> 69120 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 website/src/fonts/jetbrainsmono-italic.woff diff --git a/website/src/fonts/jetbrainsmono-italic.woff b/website/src/fonts/jetbrainsmono-italic.woff new file mode 100644 index 0000000000000000000000000000000000000000..f3ddf4db5f385bfbd08e7a745e51faefa54345a4 GIT binary patch literal 69120 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 literal 0 HcmV?d00001 From c7c9b0451f9b0858d800fcb178d2f457564de986 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 13:52:52 +0200 Subject: [PATCH 78/92] Update docs [ci skip] --- website/docs/api/architectures.md | 28 ++++++-- website/docs/usage/layers-architectures.md | 2 +- website/docs/usage/training.md | 82 ++-------------------- website/src/styles/layout.sass | 5 +- 4 files changed, 29 insertions(+), 88 deletions(-) diff --git a/website/docs/api/architectures.md b/website/docs/api/architectures.md index fd88434f1..3089fa1b3 100644 --- a/website/docs/api/architectures.md +++ b/website/docs/api/architectures.md @@ -11,9 +11,17 @@ menu: - ['Entity Linking', 'entitylinker'] --- -TODO: intro and how architectures work, link to -[`registry`](/api/top-level#registry), -[custom functions](/usage/training#custom-functions) usage etc. +A **model architecture** is a function that wires up a +[`Model`](https://thinc.ai/docs/api-model) instance, which you can then use in a +pipeline component or as a layer of a larger network. This page documents +spaCy's built-in architectures that are used for different NLP tasks. All +trainable [built-in components](/api#architecture-pipeline) expect a `model` +argument defined in the config and document their the default architecture. +Custom architectures can be registered using the +[`@spacy.registry.architectures`](/api/top-level#regsitry) decorator and used as +part of the [training config](/usage/training#custom-functions). Also see the +usage documentation on +[layers and model architectures](/usage/layers-architectures). ## Tok2Vec architectures {#tok2vec-arch source="spacy/ml/models/tok2vec.py"} @@ -284,8 +292,18 @@ on [static vectors](/usage/embeddings-transformers#static-vectors) for details. The following architectures are provided by the package [`spacy-transformers`](https://github.com/explosion/spacy-transformers). See the -[usage documentation](/usage/embeddings-transformers) for how to integrate the -architectures into your training config. +[usage documentation](/usage/embeddings-transformers#transformers) for how to +integrate the architectures into your training config. + + + +Note that in order to use these architectures in your config, you need to +install the +[`spacy-transformers`](https://github.com/explosion/spacy-transformers). See the +[installation docs](/usage/embeddings-transformers#transformers-installation) +for details and system requirements. + + ### spacy-transformers.TransformerModel.v1 {#TransformerModel} diff --git a/website/docs/usage/layers-architectures.md b/website/docs/usage/layers-architectures.md index 1ee0f4fae..eebcc4681 100644 --- a/website/docs/usage/layers-architectures.md +++ b/website/docs/usage/layers-architectures.md @@ -9,7 +9,7 @@ menu: next: /usage/projects --- -​ A **model architecture** is a function that wires up a +​A **model architecture** is a function that wires up a [Thinc `Model`](https://thinc.ai/docs/api-model) instance, which you can then use in a component or as a layer of a larger network. You can use Thinc as a thin wrapper around frameworks such as PyTorch, TensorFlow or MXNet, or you can diff --git a/website/docs/usage/training.md b/website/docs/usage/training.md index c04d3ca77..59766bada 100644 --- a/website/docs/usage/training.md +++ b/website/docs/usage/training.md @@ -6,8 +6,7 @@ menu: - ['Quickstart', 'quickstart'] - ['Config System', 'config'] - ['Custom Functions', 'custom-functions'] - - ['Transfer Learning', 'transfer-learning'] - - ['Parallel Training', 'parallel-training'] + # - ['Parallel Training', 'parallel-training'] - ['Internal API', 'api'] --- @@ -92,16 +91,6 @@ spaCy's binary `.spacy` format. You can either include the data paths in the $ python -m spacy train config.cfg --output ./output --paths.train ./train.spacy --paths.dev ./dev.spacy ``` - - ## Training config {#config} Training config files include all **settings and hyperparameters** for training @@ -400,13 +389,11 @@ recipe once the dish has already been prepared. You have to make a new one. spaCy includes a variety of built-in [architectures](/api/architectures) for different tasks. For example: - - | Architecture | Description | | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [HashEmbedCNN](/api/architectures#HashEmbedCNN) | Build spaCy’s "standard" embedding layer, which uses hash embedding with subword features and a CNN with layer-normalized maxout. ~~Model[List[Doc], List[Floats2d]]~~ | | [TransitionBasedParser](/api/architectures#TransitionBasedParser) | Build a [transition-based parser](https://explosion.ai/blog/parsing-english-in-python) model used in the default [`EntityRecognizer`](/api/entityrecognizer) and [`DependencyParser`](/api/dependencyparser). ~~Model[List[Docs], List[List[Floats2d]]]~~ | -| [TextCatEnsemble](/api/architectures#TextCatEnsemble) | Stacked ensemble of a bag-of-words model and a neural network model with an internal CNN embedding layer. Used in the default [`TextCategorizer`](/api/textcategorizer). ~~Model~~ | +| [TextCatEnsemble](/api/architectures#TextCatEnsemble) | Stacked ensemble of a bag-of-words model and a neural network model with an internal CNN embedding layer. Used in the default [`TextCategorizer`](/api/textcategorizer). ~~Model[List[Doc], Floats2d]~~ | @@ -755,71 +742,10 @@ def filter_batch(size: int) -> Callable[[Iterable[Example]], Iterator[List[Examp return create_filtered_batches ``` - - ### Defining custom architectures {#custom-architectures} -## Transfer learning {#transfer-learning} - - - -### Using transformer models like BERT {#transformers} - -spaCy v3.0 lets you use almost any statistical model to power your pipeline. You -can use models implemented in a variety of frameworks. A transformer model is -just a statistical model, so the -[`spacy-transformers`](https://github.com/explosion/spacy-transformers) package -actually has very little work to do: it just has to provide a few functions that -do the required plumbing. It also provides a pipeline component, -[`Transformer`](/api/transformer), that lets you do multi-task learning and lets -you save the transformer outputs for later use. - - - -For more details on how to integrate transformer models into your training -config and customize the implementations, see the usage guide on -[training transformers](/usage/embeddings-transformers#transformers-training). - -### Pretraining with spaCy {#pretraining} - - - -## Parallel Training with Ray {#parallel-training} - - - ## Internal training API {#api} @@ -880,8 +806,8 @@ example = Example.from_dict(predicted, {"tags": tags}) Here's another example that shows how to define gold-standard named entities. The letters added before the labels refer to the tags of the [BILUO scheme](/usage/linguistic-features#updating-biluo) – `O` is a token -outside an entity, `U` a single entity unit, `B` the beginning of an entity, -`I` a token inside an entity and `L` the last token of an entity. +outside an entity, `U` a single entity unit, `B` the beginning of an entity, `I` +a token inside an entity and `L` the last token of an entity. ```python doc = Doc(nlp.vocab, words=["Facebook", "released", "React", "in", "2014"]) diff --git a/website/src/styles/layout.sass b/website/src/styles/layout.sass index b71eccd80..775523190 100644 --- a/website/src/styles/layout.sass +++ b/website/src/styles/layout.sass @@ -363,7 +363,7 @@ body [id]:target color: var(--color-red-medium) background: var(--color-red-transparent) - &.italic, &.comment + &.italic font-style: italic @@ -384,11 +384,9 @@ body [id]:target // Settings for ini syntax (config files) [class*="language-ini"] color: var(--syntax-comment) - font-style: italic !important .token color: var(--color-subtle) - font-style: normal !important .gatsby-highlight-code-line @@ -426,7 +424,6 @@ body [id]:target .cm-comment color: var(--syntax-comment) - font-style: italic .cm-keyword color: var(--syntax-keyword) From d97695d09d7cce01df5171fc4e6e55ff39cf79f0 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sat, 22 Aug 2020 15:41:35 +0200 Subject: [PATCH 79/92] Update embeddings-transformers.md --- website/docs/usage/embeddings-transformers.md | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index 33385ff51..04b79007c 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -9,11 +9,24 @@ menu: next: /usage/training --- - +spaCy supports a number of transfer and multi-task learning workflows that can +often help improve your pipeline's efficiency or accuracy. Transfer learning +refers to techniques such as word vector tables and language model pretraining. +These techniques can be used to import knowledge from raw text into your +pipeline, so that your models are able to generalize better from your +annotated examples. -If you're looking for details on using word vectors and semantic similarity, -check out the -[linguistic features docs](/usage/linguistic-features#vectors-similarity). +You can convert word vectors from popular tools like FastText and Gensim, or +you can load in any pretrained transformer model if you install our +`spacy-transformers` integration. You can also do your own language model pretraining +via the `spacy pretrain` command. You can even share your transformer or other +contextual embedding model across multiple components, which can make long +pipelines several times more efficient. + +In order to use transfer learning, you'll need to have at least a few annotated +examples for all of the classes you're trying to predict. If you don't, you +could try using a "one-shot learning" approach using +[vectors and similarity](/usage/linguistic-features#vectors-similarity). @@ -57,19 +70,47 @@ of performance. ## Shared embedding layers {#embedding-layers} - +You can share a single token-to-vector embedding model between multiple +components using the `Tok2Vec` component. Other components in +your pipeline can "connect" to the `Tok2Vec` component by including a _listener layer_ +within their model. At the beginning of training, the `Tok2Vec` component will +grab a reference to the relevant listener layers in the rest of your pipeline. +Then, when the `Tok2Vec` component processes a batch of documents, it will pass +forward its predictions to the listeners, allowing the listeners to reuse the +predictions when they are eventually called. A similar mechanism is used to +pass gradients from the listeners back to the `Tok2Vec` model. The +`Transformer` component and `TransformerListener` layer do the same thing for +transformer models, making it easy to share a single transformer model across +your whole pipeline. + +Training a single transformer or other embedding layer for use with multiple +components is termed _multi-task learning_. Multi-task learning is sometimes +less consistent, and the results are generally harder to reason about (as there's +more going on). You'll usually want to compare your accuracy against a single-task +approach to understand whether the weight-sharing is impacting your accuracy, +and whether you can address the problem by adjusting the hyper-parameters. We +are not currently aware of any foolproof recipe. + +The main disadvantage of sharing weights between components is flexibility. +If your components are independent, you can train pipelines separately and +merge them together much more easily. Shared weights also make it more +difficult to resume training of only part of your pipeline. If you train only +part of your pipeline, you risk hurting the accuracy of the other components, +as you'll be changing the shared embedding layer those components are relying +on. + ![Pipeline components using a shared embedding component vs. independent embedding layers](../images/tok2vec.svg) | Shared | Independent | | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | ✅ **smaller:** models only need to include a single copy of the embeddings | ❌ **larger:** models need to include the embeddings for each component | -| ✅ **faster:** | ❌ **slower:** | +| ✅ **faster:** embed the documents once for your whole pipeline | ❌ **slower:** rerun the embedding for each component | | ❌ **less composable:** all components require the same embedding component in the pipeline | ✅ **modular:** components can be moved and swapped freely | +| ?? **accuracy:** weight sharing may increase or decrease accuracy, depending on your task and data, but usually the impact is small | ![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg) - ## Using transformer models {#transformers} From 37ebff6997a749ecd9f91bc0d93aa6031fbd5ed9 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 16:47:03 +0200 Subject: [PATCH 80/92] Update docs [ci skip] --- website/docs/usage/embeddings-transformers.md | 83 +++++++++---------- website/docs/usage/linguistic-features.md | 2 +- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index 04b79007c..d535982d7 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -9,23 +9,23 @@ menu: next: /usage/training --- -spaCy supports a number of transfer and multi-task learning workflows that can -often help improve your pipeline's efficiency or accuracy. Transfer learning +spaCy supports a number of **transfer and multi-task learning** workflows that +can often help improve your pipeline's efficiency or accuracy. Transfer learning refers to techniques such as word vector tables and language model pretraining. These techniques can be used to import knowledge from raw text into your -pipeline, so that your models are able to generalize better from your -annotated examples. +pipeline, so that your models are able to generalize better from your annotated +examples. -You can convert word vectors from popular tools like FastText and Gensim, or -you can load in any pretrained transformer model if you install our -`spacy-transformers` integration. You can also do your own language model pretraining -via the `spacy pretrain` command. You can even share your transformer or other -contextual embedding model across multiple components, which can make long -pipelines several times more efficient. - -In order to use transfer learning, you'll need to have at least a few annotated -examples for all of the classes you're trying to predict. If you don't, you -could try using a "one-shot learning" approach using +You can convert **word vectors** from popular tools like +[FastText](https://fasttext.cc) and [Gensim](https://radimrehurek.com/gensim), +or you can load in any pretrained **transformer model** if you install +[`spacy-transformers`](https://github.com/explosion/spacy-transformers). You can +also do your own language model pretraining via the +[`spacy pretrain`](/api/cli#pretrain) command. You can even **share** your +transformer or other contextual embedding model across multiple components, +which can make long pipelines several times more efficient. To use transfer +learning, you'll need at least a few annotated examples for what you're trying +to predict. Otherwise, you could try using a "one-shot learning" approach using [vectors and similarity](/usage/linguistic-features#vectors-similarity). @@ -70,35 +70,13 @@ of performance. ## Shared embedding layers {#embedding-layers} -You can share a single token-to-vector embedding model between multiple -components using the `Tok2Vec` component. Other components in -your pipeline can "connect" to the `Tok2Vec` component by including a _listener layer_ -within their model. At the beginning of training, the `Tok2Vec` component will -grab a reference to the relevant listener layers in the rest of your pipeline. -Then, when the `Tok2Vec` component processes a batch of documents, it will pass -forward its predictions to the listeners, allowing the listeners to reuse the -predictions when they are eventually called. A similar mechanism is used to -pass gradients from the listeners back to the `Tok2Vec` model. The -`Transformer` component and `TransformerListener` layer do the same thing for -transformer models, making it easy to share a single transformer model across -your whole pipeline. - -Training a single transformer or other embedding layer for use with multiple -components is termed _multi-task learning_. Multi-task learning is sometimes -less consistent, and the results are generally harder to reason about (as there's -more going on). You'll usually want to compare your accuracy against a single-task -approach to understand whether the weight-sharing is impacting your accuracy, -and whether you can address the problem by adjusting the hyper-parameters. We -are not currently aware of any foolproof recipe. - -The main disadvantage of sharing weights between components is flexibility. -If your components are independent, you can train pipelines separately and -merge them together much more easily. Shared weights also make it more -difficult to resume training of only part of your pipeline. If you train only -part of your pipeline, you risk hurting the accuracy of the other components, -as you'll be changing the shared embedding layer those components are relying -on. - +spaCy lets you share a single embedding layer and reuse it across multiple +components. This is also known as **multi-task learning**. Sharing weights +between components can make your pipeline run a lot faster and result in a much +smaller models size, as you only need a single copy of the embeddings. However, +it can make the pipeline less modular and make it more difficult to swap +components or retrain parts of the pipeline, since all components depend on the +same weights. ![Pipeline components using a shared embedding component vs. independent embedding layers](../images/tok2vec.svg) @@ -107,10 +85,27 @@ on. | ✅ **smaller:** models only need to include a single copy of the embeddings | ❌ **larger:** models need to include the embeddings for each component | | ✅ **faster:** embed the documents once for your whole pipeline | ❌ **slower:** rerun the embedding for each component | | ❌ **less composable:** all components require the same embedding component in the pipeline | ✅ **modular:** components can be moved and swapped freely | -| ?? **accuracy:** weight sharing may increase or decrease accuracy, depending on your task and data, but usually the impact is small | + +a single token-to-vector embedding model between multiple components using the +[`Tok2Vec`](/api/tok2vec) component. Other components in your pipeline can +"connect" this component by including a **listener layer** like +[Tok2VecListener](/api/architectures#Tok2VecListener) within their model. ![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg) +At the beginning of training, the [`Tok2Vec`](/api/tok2vec) component will grab +a reference to the relevant listener layers in the rest of your pipeline. When +it processes a batch of documents, it will pass forward its predictions to the +listeners, allowing the listeners to **reuse the predictions** when they are +eventually called. A similar mechanism is used to pass gradients from the +listeners back to the model. The [`Transformer`](/api/transformer) component and +[TransformerListener](/api/architectures#TransformerListener) layer do the same +thing for transformer models, making it easy to share a single transformer model +across your whole pipeline. + + + + ## Using transformer models {#transformers} diff --git a/website/docs/usage/linguistic-features.md b/website/docs/usage/linguistic-features.md index f52c2b2ad..f2ec48d63 100644 --- a/website/docs/usage/linguistic-features.md +++ b/website/docs/usage/linguistic-features.md @@ -1550,7 +1550,7 @@ import Vectors101 from 'usage/101/\_vectors-similarity.md' ### Adding word vectors {#adding-vectors} Custom word vectors can be trained using a number of open-source libraries, such -as [Gensim](https://radimrehurek.com/gensim), [Fast Text](https://fasttext.cc), +as [Gensim](https://radimrehurek.com/gensim), [FastText](https://fasttext.cc), or Tomas Mikolov's original [Word2vec implementation](https://code.google.com/archive/p/word2vec/). Most word vector libraries output an easy-to-read text-based format, where each line From adcf790b96446aabdc8f2bc78491cc52cb35843d Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 17:04:16 +0200 Subject: [PATCH 81/92] Update docs[ci skip] --- website/docs/images/layers-architectures.svg | 97 ++++++++++++++++++++ website/docs/usage/layers-architectures.md | 10 +- website/src/styles/readnext.module.sass | 2 +- 3 files changed, 103 insertions(+), 6 deletions(-) create mode 100644 website/docs/images/layers-architectures.svg diff --git a/website/docs/images/layers-architectures.svg b/website/docs/images/layers-architectures.svg new file mode 100644 index 000000000..22e705ba1 --- /dev/null +++ b/website/docs/images/layers-architectures.svg @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/docs/usage/layers-architectures.md b/website/docs/usage/layers-architectures.md index eebcc4681..4f91b1595 100644 --- a/website/docs/usage/layers-architectures.md +++ b/website/docs/usage/layers-architectures.md @@ -20,9 +20,9 @@ so that it refers to a different registered function. Once the component has been created, its model instance has already been assigned, so you cannot change its model architecture. The architecture is like a recipe for the network, and you can't change the recipe once the dish has already been prepared. You have to -make a new one. ​ +make a new one. - +![Diagram of a pipeline component with its model](../images/layers-architectures.svg) ## Type signatures {#type-sigs} @@ -137,9 +137,9 @@ Thinc uses a special class, [`Shim`](https://thinc.ai/docs/api-model#shim), to hold references to external objects. This allows each wrapper space to define a custom type, with whatever attributes and methods are helpful, to assist in managing the communication between Thinc and the external library. The -[`Model`](/docs/api-model#model) class holds `shim` instances in a separate -list, and communicates with the shims about updates, serialization, changes of -device, etc. +[`Model`](https://thinc.ai/docs/api-model#model) class holds `shim` instances in +a separate list, and communicates with the shims about updates, serialization, +changes of device, etc. The wrapper will receive each batch of inputs, convert them into a suitable form for the underlying model instance, and pass them over to the shim, which will diff --git a/website/src/styles/readnext.module.sass b/website/src/styles/readnext.module.sass index 23aa7f016..aef91c09e 100644 --- a/website/src/styles/readnext.module.sass +++ b/website/src/styles/readnext.module.sass @@ -12,7 +12,7 @@ background: var(--color-subtle-light) color: var(--color-subtle-dark) border-radius: 50% - padding: 0.5rem + padding: 0.5rem 0.65rem 0.5rem 0 transition: color 0.2s ease float: right margin-left: 3rem From 048de64d4c29408a24cbd55c466cbca446068f49 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sat, 22 Aug 2020 17:11:28 +0200 Subject: [PATCH 82/92] Suggest edits --- website/docs/usage/embeddings-transformers.md | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index d535982d7..4f40104c4 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -70,13 +70,14 @@ of performance. ## Shared embedding layers {#embedding-layers} -spaCy lets you share a single embedding layer and reuse it across multiple -components. This is also known as **multi-task learning**. Sharing weights -between components can make your pipeline run a lot faster and result in a much -smaller models size, as you only need a single copy of the embeddings. However, -it can make the pipeline less modular and make it more difficult to swap -components or retrain parts of the pipeline, since all components depend on the -same weights. +spaCy lets you share a single transformer or other token-to-vector ("tok2vec") +embedding layer between multiple components. You can even update the shared layer, +performing **multi-task learning**. Reusing the tok2vec layer between components +can make your pipeline run a lot faster and result in much +smaller models. However, it can make the pipeline less modular and make it more +difficult to swap components or retrain parts of the pipeline. Multi-task +learning can affect your accuracy (either positively or negatively), and may +require some retuning of your hyper-parameters. ![Pipeline components using a shared embedding component vs. independent embedding layers](../images/tok2vec.svg) @@ -86,10 +87,11 @@ same weights. | ✅ **faster:** embed the documents once for your whole pipeline | ❌ **slower:** rerun the embedding for each component | | ❌ **less composable:** all components require the same embedding component in the pipeline | ✅ **modular:** components can be moved and swapped freely | -a single token-to-vector embedding model between multiple components using the -[`Tok2Vec`](/api/tok2vec) component. Other components in your pipeline can -"connect" this component by including a **listener layer** like -[Tok2VecListener](/api/architectures#Tok2VecListener) within their model. +You can share a single transformer or other tok2vec model between multiple components +by adding a [`Transformer`](/api/transformer) or [`Tok2Vec`](/api/tok2vec) component +near the start of your pipeline. Components later in the pipeline can "connect" +to it by including a **listener layer** like [Tok2VecListener](/api/architectures#Tok2VecListener) +within their model. ![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg) @@ -100,8 +102,9 @@ listeners, allowing the listeners to **reuse the predictions** when they are eventually called. A similar mechanism is used to pass gradients from the listeners back to the model. The [`Transformer`](/api/transformer) component and [TransformerListener](/api/architectures#TransformerListener) layer do the same -thing for transformer models, making it easy to share a single transformer model -across your whole pipeline. +thing for transformer models, but the `Transformer` component will also save the +transformer outputs to the `doc._.trf_data` extension attribute, giving you +access to them after the pipeline has finished running. From 98a9e063b695c020fde8b876ec97fe914723fd0b Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sat, 22 Aug 2020 17:15:05 +0200 Subject: [PATCH 83/92] Update docs [ci skip] --- website/docs/usage/embeddings-transformers.md | 25 ++++++++++--------- website/docs/usage/layers-architectures.md | 13 ++++++++++ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/website/docs/usage/embeddings-transformers.md b/website/docs/usage/embeddings-transformers.md index 4f40104c4..7648a5d45 100644 --- a/website/docs/usage/embeddings-transformers.md +++ b/website/docs/usage/embeddings-transformers.md @@ -71,10 +71,10 @@ of performance. ## Shared embedding layers {#embedding-layers} spaCy lets you share a single transformer or other token-to-vector ("tok2vec") -embedding layer between multiple components. You can even update the shared layer, -performing **multi-task learning**. Reusing the tok2vec layer between components -can make your pipeline run a lot faster and result in much -smaller models. However, it can make the pipeline less modular and make it more +embedding layer between multiple components. You can even update the shared +layer, performing **multi-task learning**. Reusing the tok2vec layer between +components can make your pipeline run a lot faster and result in much smaller +models. However, it can make the pipeline less modular and make it more difficult to swap components or retrain parts of the pipeline. Multi-task learning can affect your accuracy (either positively or negatively), and may require some retuning of your hyper-parameters. @@ -87,11 +87,11 @@ require some retuning of your hyper-parameters. | ✅ **faster:** embed the documents once for your whole pipeline | ❌ **slower:** rerun the embedding for each component | | ❌ **less composable:** all components require the same embedding component in the pipeline | ✅ **modular:** components can be moved and swapped freely | -You can share a single transformer or other tok2vec model between multiple components -by adding a [`Transformer`](/api/transformer) or [`Tok2Vec`](/api/tok2vec) component -near the start of your pipeline. Components later in the pipeline can "connect" -to it by including a **listener layer** like [Tok2VecListener](/api/architectures#Tok2VecListener) -within their model. +You can share a single transformer or other tok2vec model between multiple +components by adding a [`Transformer`](/api/transformer) or +[`Tok2Vec`](/api/tok2vec) component near the start of your pipeline. Components +later in the pipeline can "connect" to it by including a **listener layer** like +[Tok2VecListener](/api/architectures#Tok2VecListener) within their model. ![Pipeline components listening to shared embedding component](../images/tok2vec-listener.svg) @@ -102,9 +102,10 @@ listeners, allowing the listeners to **reuse the predictions** when they are eventually called. A similar mechanism is used to pass gradients from the listeners back to the model. The [`Transformer`](/api/transformer) component and [TransformerListener](/api/architectures#TransformerListener) layer do the same -thing for transformer models, but the `Transformer` component will also save the -transformer outputs to the `doc._.trf_data` extension attribute, giving you -access to them after the pipeline has finished running. +thing for transformer models, but the `Transformer` component will also save the +transformer outputs to the +[`Doc._.trf_data`](/api/transformer#custom_attributes) extension attribute, +giving you access to them after the pipeline has finished running. diff --git a/website/docs/usage/layers-architectures.md b/website/docs/usage/layers-architectures.md index 4f91b1595..aa398f752 100644 --- a/website/docs/usage/layers-architectures.md +++ b/website/docs/usage/layers-architectures.md @@ -170,3 +170,16 @@ to the host device unnecessarily. - Interaction with `predict`, `get_loss` and `set_annotations` - Initialization life-cycle with `begin_training`. - Link to relation extraction notebook. + +```python +def update(self, examples): + docs = [ex.predicted for ex in examples] + refs = [ex.reference for ex in examples] + predictions, backprop = self.model.begin_update(docs) + gradient = self.get_loss(predictions, refs) + backprop(gradient) + +def __call__(self, doc): + predictions = self.model([doc]) + self.set_annotations(predictions) +``` From f27aecac1451fd0339def7891be8231efb844978 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sun, 23 Aug 2020 11:57:56 +0200 Subject: [PATCH 84/92] Update formatting [ci skip] --- website/docs/usage/saving-loading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/usage/saving-loading.md b/website/docs/usage/saving-loading.md index b5e0f4370..3f9435f5e 100644 --- a/website/docs/usage/saving-loading.md +++ b/website/docs/usage/saving-loading.md @@ -243,7 +243,7 @@ file `data.json` in its subdirectory: ### Directory structure {highlight="2-3"} └── /path/to/model ├── my_component # data serialized by "my_component" - | └── data.json + │ └── data.json ├── ner # data for "ner" component ├── parser # data for "parser" component ├── tagger # data for "tagger" component From 9bdc9e81f5f8affe464d1ff3c6bc35c08aa2a0d6 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Sun, 23 Aug 2020 12:14:02 +0200 Subject: [PATCH 85/92] Fix error message [ci skip] --- spacy/cli/_util.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spacy/cli/_util.py b/spacy/cli/_util.py index 9d3ae0913..d1686c051 100644 --- a/spacy/cli/_util.py +++ b/spacy/cli/_util.py @@ -110,7 +110,9 @@ def load_project_config(path: Path) -> Dict[str, Any]: msg.fail(invalid_err, e, exits=1) errors = validate(ProjectConfigSchema, config) if errors: - msg.fail(invalid_err, "\n".join(errors), exits=1) + msg.fail(invalid_err) + print("\n".join(errors)) + sys.exit(1) validate_project_commands(config) # Make sure directories defined in config exist for subdir in config.get("directories", []): From fe1cf7e124689adba8e014448be0ee99de98d06d Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 23 Aug 2020 18:31:30 +0200 Subject: [PATCH 86/92] Allow score_weights to list extra scores --- spacy/cli/train.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/spacy/cli/train.py b/spacy/cli/train.py index 43866e4b3..1366e0b38 100644 --- a/spacy/cli/train.py +++ b/spacy/cli/train.py @@ -162,13 +162,14 @@ def train( progress = tqdm.tqdm(total=T_cfg["eval_frequency"], leave=False) except Exception as e: if output_path is not None: + # We don't want to swallow the traceback if we don't have a + # specific error. msg.warn( f"Aborting and saving the final best model. " - f"Encountered exception: {str(e)}", - exits=1, + f"Encountered exception: {str(e)}" ) - else: - raise e + nlp.to_disk(output_path / "model-final") + raise e finally: if output_path is not None: final_model_path = output_path / "model-final" @@ -207,7 +208,7 @@ def create_evaluation_callback( scores = nlp.evaluate(dev_examples) # Calculate a weighted sum based on score_weights for the main score try: - weighted_score = sum(scores[s] * weights.get(s, 0.0) for s in weights) + weighted_score = sum(scores.get(s, 0.0) * weights.get(s, 0.0) for s in weights) except KeyError as e: keys = list(scores.keys()) err = Errors.E983.format(dict="score_weights", key=str(e), keys=keys) @@ -377,7 +378,7 @@ def setup_printer( try: scores = [ - "{0:.2f}".format(float(info["other_scores"][col])) for col in score_cols + "{0:.2f}".format(float(info["other_scores"].get(col, 0.0))) for col in score_cols ] except KeyError as e: raise KeyError( @@ -403,7 +404,7 @@ def update_meta( ) -> None: nlp.meta["performance"] = {} for metric in training["score_weights"]: - nlp.meta["performance"][metric] = info["other_scores"][metric] + nlp.meta["performance"][metric] = info["other_scores"].get(metric, 0.0) for pipe_name in nlp.pipe_names: nlp.meta["performance"][f"{pipe_name}_loss"] = info["losses"][pipe_name] From e5598676053c4fc759a8298cf3532260f371dbf2 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 23 Aug 2020 18:32:09 +0200 Subject: [PATCH 87/92] Allow spacy project to push and pull to/from remote storage (#5949) * Add utils for working with remote storage * WIP add remote_cache for project * WIP add push and pull commands * Use pathy in remote_cache * Updarte util * Update remote_cache * Update util * Update project assets * Update pull script * Update push script * Fix type annotation in util * Work on remote storage * Remove site and env hash * Fix imports * Fix type annotation * Require pathy * Require pathy * Fix import * Add a util to handle project variable substitution * Import push and pull commands * Fix pull command * Fix push command * Fix tarfile in remote_storage * Improve printing * Fiddle with status messages * Set version to v3.0.0a9 * Draft docs for spacy project remote storages * Update docs [ci skip] * Use Thinc config to simplify and unify template variables * Auto-format * Don't import Pathy globally for now Causes slow and annoying Google Cloud warning * Tidy up test * Tidy up and update tests * Update to latest Thinc * Update docs * variables -> vars * Update docs [ci skip] * Update docs [ci skip] Co-authored-by: Ines Montani --- pyproject.toml | 5 +- requirements.txt | 3 +- setup.cfg | 5 +- spacy/about.py | 2 +- spacy/cli/__init__.py | 2 + spacy/cli/_util.py | 62 +++++++++- spacy/cli/project/assets.py | 18 +-- spacy/cli/project/dvc.py | 12 +- spacy/cli/project/pull.py | 36 ++++++ spacy/cli/project/push.py | 48 ++++++++ spacy/cli/project/remote_storage.py | 169 ++++++++++++++++++++++++++ spacy/cli/project/run.py | 50 ++------ spacy/schemas.py | 2 +- spacy/tests/test_cli.py | 23 +++- spacy/util.py | 4 +- website/docs/api/cli.md | 86 +++++++++++++ website/docs/images/projects.svg | 91 ++++++++++++++ website/docs/usage/projects.md | 182 ++++++++++++++++++++++++---- website/docs/usage/v3.md | 14 ++- website/src/components/table.js | 5 +- 20 files changed, 712 insertions(+), 107 deletions(-) create mode 100644 spacy/cli/project/pull.py create mode 100644 spacy/cli/project/push.py create mode 100644 spacy/cli/project/remote_storage.py create mode 100644 website/docs/images/projects.svg diff --git a/pyproject.toml b/pyproject.toml index 9a646d0d7..77deb44b0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,9 +6,10 @@ requires = [ "cymem>=2.0.2,<2.1.0", "preshed>=3.0.2,<3.1.0", "murmurhash>=0.28.0,<1.1.0", - "thinc>=8.0.0a28,<8.0.0a30", + "thinc>=8.0.0a29,<8.0.0a40", "blis>=0.4.0,<0.5.0", "pytokenizations", - "smart_open>=2.0.0,<3.0.0" + "smart_open>=2.0.0,<3.0.0", + "pathy" ] build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 181cb2101..5aafd83dd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ # Our libraries cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 -thinc>=8.0.0a28,<8.0.0a30 +thinc>=8.0.0a29,<8.0.0a40 blis>=0.4.0,<0.5.0 ml_datasets>=0.1.1 murmurhash>=0.28.0,<1.1.0 @@ -9,6 +9,7 @@ wasabi>=0.7.1,<1.1.0 srsly>=2.1.0,<3.0.0 catalogue>=0.0.7,<1.1.0 typer>=0.3.0,<0.4.0 +pathy # Third party dependencies numpy>=1.15.0 requests>=2.13.0,<3.0.0 diff --git a/setup.cfg b/setup.cfg index d56eab3a6..8b4819ed8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -34,18 +34,19 @@ setup_requires = cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 murmurhash>=0.28.0,<1.1.0 - thinc>=8.0.0a28,<8.0.0a30 + thinc>=8.0.0a29,<8.0.0a40 install_requires = # Our libraries murmurhash>=0.28.0,<1.1.0 cymem>=2.0.2,<2.1.0 preshed>=3.0.2,<3.1.0 - thinc>=8.0.0a28,<8.0.0a30 + thinc>=8.0.0a29,<8.0.0a40 blis>=0.4.0,<0.5.0 wasabi>=0.7.1,<1.1.0 srsly>=2.1.0,<3.0.0 catalogue>=0.0.7,<1.1.0 typer>=0.3.0,<0.4.0 + pathy # Third-party dependencies tqdm>=4.38.0,<5.0.0 numpy>=1.15.0 diff --git a/spacy/about.py b/spacy/about.py index 77b00eb48..56bb016c3 100644 --- a/spacy/about.py +++ b/spacy/about.py @@ -1,6 +1,6 @@ # fmt: off __title__ = "spacy-nightly" -__version__ = "3.0.0a8" +__version__ = "3.0.0a9" __release__ = True __download_url__ = "https://github.com/explosion/spacy-models/releases/download" __compatibility__ = "https://raw.githubusercontent.com/explosion/spacy-models/master/compatibility.json" diff --git a/spacy/cli/__init__.py b/spacy/cli/__init__.py index 2b21e2f2b..8aea6ef45 100644 --- a/spacy/cli/__init__.py +++ b/spacy/cli/__init__.py @@ -21,6 +21,8 @@ from .project.clone import project_clone # noqa: F401 from .project.assets import project_assets # noqa: F401 from .project.run import project_run # noqa: F401 from .project.dvc import project_update_dvc # noqa: F401 +from .project.push import project_push # noqa: F401 +from .project.pull import project_pull # noqa: F401 @app.command("link", no_args_is_help=True, deprecated=True, hidden=True) diff --git a/spacy/cli/_util.py b/spacy/cli/_util.py index d1686c051..b527ac2a0 100644 --- a/spacy/cli/_util.py +++ b/spacy/cli/_util.py @@ -1,4 +1,5 @@ -from typing import Dict, Any, Union, List, Optional +from typing import Dict, Any, Union, List, Optional, TYPE_CHECKING +import sys from pathlib import Path from wasabi import msg import srsly @@ -8,11 +9,13 @@ from typer.main import get_command from contextlib import contextmanager from thinc.config import Config, ConfigValidationError from configparser import InterpolationError -import sys from ..schemas import ProjectConfigSchema, validate from ..util import import_file +if TYPE_CHECKING: + from pathy import Pathy # noqa: F401 + PROJECT_FILE = "project.yml" PROJECT_LOCK = "project.lock" @@ -93,11 +96,12 @@ def parse_config_overrides(args: List[str]) -> Dict[str, Any]: return result -def load_project_config(path: Path) -> Dict[str, Any]: +def load_project_config(path: Path, interpolate: bool = True) -> Dict[str, Any]: """Load the project.yml file from a directory and validate it. Also make sure that all directories defined in the config exist. path (Path): The path to the project directory. + interpolate (bool): Whether to substitute project variables. RETURNS (Dict[str, Any]): The loaded project.yml. """ config_path = path / PROJECT_FILE @@ -119,9 +123,25 @@ def load_project_config(path: Path) -> Dict[str, Any]: dir_path = path / subdir if not dir_path.exists(): dir_path.mkdir(parents=True) + if interpolate: + err = "project.yml validation error" + with show_validation_error(title=err, hint_fill=False): + config = substitute_project_variables(config) return config +def substitute_project_variables(config: Dict[str, Any], overrides: Dict = {}): + key = "vars" + config.setdefault(key, {}) + config[key].update(overrides) + # Need to put variables in the top scope again so we can have a top-level + # section "project" (otherwise, a list of commands in the top scope wouldn't) + # be allowed by Thinc's config system + cfg = Config({"project": config, key: config[key]}) + interpolated = cfg.interpolate() + return dict(interpolated["project"]) + + def validate_project_commands(config: Dict[str, Any]) -> None: """Check that project commands and workflows are valid, don't contain duplicates, don't clash and only refer to commands that exist. @@ -232,3 +252,39 @@ def get_sourced_components(config: Union[Dict[str, Any], Config]) -> List[str]: for name, cfg in config.get("components", {}).items() if "factory" not in cfg and "source" in cfg ] + + +def upload_file(src: Path, dest: Union[str, "Pathy"]) -> None: + """Upload a file. + + src (Path): The source path. + url (str): The destination URL to upload to. + """ + dest = ensure_pathy(dest) + with dest.open(mode="wb") as output_file: + with src.open(mode="rb") as input_file: + output_file.write(input_file.read()) + + +def download_file(src: Union[str, "Pathy"], dest: Path, *, force: bool = False) -> None: + """Download a file using smart_open. + + url (str): The URL of the file. + dest (Path): The destination path. + force (bool): Whether to force download even if file exists. + If False, the download will be skipped. + """ + if dest.exists() and not force: + return None + src = ensure_pathy(src) + with src.open(mode="rb") as input_file: + with dest.open(mode="wb") as output_file: + output_file.write(input_file.read()) + + +def ensure_pathy(path): + """Temporary helper to prevent importing Pathy globally (which can cause + slow and annoying Google Cloud warning).""" + from pathy import Pathy # noqa: F811 + + return Pathy(path) diff --git a/spacy/cli/project/assets.py b/spacy/cli/project/assets.py index 3be784e04..60cf95160 100644 --- a/spacy/cli/project/assets.py +++ b/spacy/cli/project/assets.py @@ -4,10 +4,10 @@ from wasabi import msg import re import shutil import requests -import smart_open from ...util import ensure_path, working_dir from .._util import project_cli, Arg, PROJECT_FILE, load_project_config, get_checksum +from .._util import download_file # TODO: find a solution for caches @@ -44,16 +44,14 @@ def project_assets(project_dir: Path) -> None: if not assets: msg.warn(f"No assets specified in {PROJECT_FILE}", exits=0) msg.info(f"Fetching {len(assets)} asset(s)") - variables = config.get("variables", {}) for asset in assets: - dest = asset["dest"].format(**variables) + dest = asset["dest"] url = asset.get("url") checksum = asset.get("checksum") if not url: # project.yml defines asset without URL that the user has to place check_private_asset(dest, checksum) continue - url = url.format(**variables) fetch_asset(project_path, url, dest, checksum) @@ -132,15 +130,3 @@ def convert_asset_url(url: str) -> str: ) return converted return url - - -def download_file(url: str, dest: Path, chunk_size: int = 1024) -> None: - """Download a file using smart_open. - - url (str): The URL of the file. - dest (Path): The destination path. - chunk_size (int): The size of chunks to read/write. - """ - with smart_open.open(url, mode="rb") as input_file: - with dest.open(mode="wb") as output_file: - output_file.write(input_file.read()) diff --git a/spacy/cli/project/dvc.py b/spacy/cli/project/dvc.py index 7386339d9..e0f6cd430 100644 --- a/spacy/cli/project/dvc.py +++ b/spacy/cli/project/dvc.py @@ -99,7 +99,6 @@ def update_dvc_config( if ref_hash == config_hash and not force: return False # Nothing has changed in project.yml, don't need to update dvc_config_path.unlink() - variables = config.get("variables", {}) dvc_commands = [] config_commands = {cmd["name"]: cmd for cmd in config.get("commands", [])} for name in workflows[workflow]: @@ -122,7 +121,7 @@ def update_dvc_config( dvc_commands.append(join_command(full_cmd)) with working_dir(path): dvc_flags = {"--verbose": verbose, "--quiet": silent} - run_dvc_commands(dvc_commands, variables, flags=dvc_flags) + run_dvc_commands(dvc_commands, flags=dvc_flags) with dvc_config_path.open("r+", encoding="utf8") as f: content = f.read() f.seek(0, 0) @@ -131,23 +130,16 @@ def update_dvc_config( def run_dvc_commands( - commands: List[str] = tuple(), - variables: Dict[str, str] = {}, - flags: Dict[str, bool] = {}, + commands: List[str] = tuple(), flags: Dict[str, bool] = {}, ) -> None: """Run a sequence of DVC commands in a subprocess, in order. commands (List[str]): The string commands without the leading "dvc". - variables (Dict[str, str]): Dictionary of variable names, mapped to their - values. Will be used to substitute format string variables in the - commands. flags (Dict[str, bool]): Conditional flags to be added to command. Makes it easier to pass flags like --quiet that depend on a variable or command-line setting while avoiding lots of nested conditionals. """ for command in commands: - # Substitute variables, e.g. "./{NAME}.json" - command = command.format(**variables) command = split_command(command) dvc_command = ["dvc", *command] # Add the flags if they are set to True diff --git a/spacy/cli/project/pull.py b/spacy/cli/project/pull.py new file mode 100644 index 000000000..1bf608c40 --- /dev/null +++ b/spacy/cli/project/pull.py @@ -0,0 +1,36 @@ +from pathlib import Path +from wasabi import msg +from .remote_storage import RemoteStorage +from .remote_storage import get_command_hash +from .._util import project_cli, Arg +from .._util import load_project_config + + +@project_cli.command("pull") +def project_pull_cli( + # fmt: off + remote: str = Arg("default", help="Name or path of remote storage"), + project_dir: Path = Arg(Path.cwd(), help="Location of project directory. Defaults to current working directory.", exists=True, file_okay=False), + # fmt: on +): + """Retrieve any precomputed outputs from a remote storage that are available. + You can alias remotes in your project.yml by mapping them to storage paths. + A storage can be anything that the smart-open library can upload to, e.g. + gcs, aws, ssh, local directories etc + """ + for url, output_path in project_pull(project_dir, remote): + if url is not None: + msg.good(f"Pulled {output_path} from {url}") + + +def project_pull(project_dir: Path, remote: str, *, verbose: bool = False): + config = load_project_config(project_dir) + if remote in config.get("remotes", {}): + remote = config["remotes"][remote] + storage = RemoteStorage(project_dir, remote) + for cmd in config.get("commands", []): + deps = [project_dir / dep for dep in cmd.get("deps", [])] + cmd_hash = get_command_hash("", "", deps, cmd["script"]) + for output_path in cmd.get("outputs", []): + url = storage.pull(output_path, command_hash=cmd_hash) + yield url, output_path diff --git a/spacy/cli/project/push.py b/spacy/cli/project/push.py new file mode 100644 index 000000000..0b070c9d8 --- /dev/null +++ b/spacy/cli/project/push.py @@ -0,0 +1,48 @@ +from pathlib import Path +from wasabi import msg +from .remote_storage import RemoteStorage +from .remote_storage import get_content_hash, get_command_hash +from .._util import load_project_config +from .._util import project_cli, Arg + + +@project_cli.command("push") +def project_push_cli( + # fmt: off + remote: str = Arg("default", help="Name or path of remote storage"), + project_dir: Path = Arg(Path.cwd(), help="Location of project directory. Defaults to current working directory.", exists=True, file_okay=False), + # fmt: on +): + """Persist outputs to a remote storage. You can alias remotes in your project.yml + by mapping them to storage paths. A storage can be anything that the smart-open + library can upload to, e.g. gcs, aws, ssh, local directories etc + """ + for output_path, url in project_push(project_dir, remote): + if url is None: + msg.info(f"Skipping {output_path}") + else: + msg.good(f"Pushed {output_path} to {url}") + + +def project_push(project_dir: Path, remote: str): + """Persist outputs to a remote storage. You can alias remotes in your project.yml + by mapping them to storage paths. A storage can be anything that the smart-open + library can upload to, e.g. gcs, aws, ssh, local directories etc + """ + config = load_project_config(project_dir) + if remote in config.get("remotes", {}): + remote = config["remotes"][remote] + storage = RemoteStorage(project_dir, remote) + for cmd in config.get("commands", []): + cmd_hash = get_command_hash( + "", "", [project_dir / dep for dep in cmd.get("deps", [])], cmd["script"] + ) + for output_path in cmd.get("outputs", []): + output_loc = project_dir / output_path + if output_loc.exists(): + url = storage.push( + output_path, + command_hash=cmd_hash, + content_hash=get_content_hash(output_loc), + ) + yield output_path, url diff --git a/spacy/cli/project/remote_storage.py b/spacy/cli/project/remote_storage.py new file mode 100644 index 000000000..e7e7cbbe8 --- /dev/null +++ b/spacy/cli/project/remote_storage.py @@ -0,0 +1,169 @@ +from typing import Optional, List, Dict, TYPE_CHECKING +import os +import site +import hashlib +import urllib.parse +import tarfile +from pathlib import Path + +from .._util import get_hash, get_checksum, download_file, ensure_pathy +from ...util import make_tempdir + +if TYPE_CHECKING: + from pathy import Pathy # noqa: F401 + + +class RemoteStorage: + """Push and pull outputs to and from a remote file storage. + + Remotes can be anything that `smart-open` can support: AWS, GCS, file system, + ssh, etc. + """ + + def __init__(self, project_root: Path, url: str, *, compression="gz"): + self.root = project_root + self.url = ensure_pathy(url) + self.compression = compression + + def push(self, path: Path, command_hash: str, content_hash: str) -> "Pathy": + """Compress a file or directory within a project and upload it to a remote + storage. If an object exists at the full URL, nothing is done. + + Within the remote storage, files are addressed by their project path + (url encoded) and two user-supplied hashes, representing their creation + context and their file contents. If the URL already exists, the data is + not uploaded. Paths are archived and compressed prior to upload. + """ + loc = self.root / path + if not loc.exists(): + raise IOError(f"Cannot push {loc}: does not exist.") + url = self.make_url(path, command_hash, content_hash) + if url.exists(): + return None + tmp: Path + with make_tempdir() as tmp: + tar_loc = tmp / self.encode_name(str(path)) + mode_string = f"w:{self.compression}" if self.compression else "w" + with tarfile.open(tar_loc, mode=mode_string) as tar_file: + tar_file.add(str(loc), arcname=str(path)) + with tar_loc.open(mode="rb") as input_file: + with url.open(mode="wb") as output_file: + output_file.write(input_file.read()) + return url + + def pull( + self, + path: Path, + *, + command_hash: Optional[str] = None, + content_hash: Optional[str] = None, + ) -> Optional["Pathy"]: + """Retrieve a file from the remote cache. If the file already exists, + nothing is done. + + If the command_hash and/or content_hash are specified, only matching + results are returned. If no results are available, an error is raised. + """ + dest = self.root / path + if dest.exists(): + return None + url = self.find(path, command_hash=command_hash, content_hash=content_hash) + if url is None: + return url + else: + # Make sure the destination exists + if not dest.parent.exists(): + dest.parent.mkdir(parents=True) + tmp: Path + with make_tempdir() as tmp: + tar_loc = tmp / url.parts[-1] + download_file(url, tar_loc) + mode_string = f"r:{self.compression}" if self.compression else "r" + with tarfile.open(tar_loc, mode=mode_string) as tar_file: + # This requires that the path is added correctly, relative + # to root. This is how we set things up in push() + tar_file.extractall(self.root) + return url + + def find( + self, + path: Path, + *, + command_hash: Optional[str] = None, + content_hash: Optional[str] = None, + ) -> Optional["Pathy"]: + """Find the best matching version of a file within the storage, + or `None` if no match can be found. If both the creation and content hash + are specified, only exact matches will be returned. Otherwise, the most + recent matching file is preferred. + """ + name = self.encode_name(str(path)) + if command_hash is not None and content_hash is not None: + url = self.make_url(path, command_hash, content_hash) + urls = [url] if url.exists() else [] + elif command_hash is not None: + urls = list((self.url / name / command_hash).iterdir()) + else: + urls = list((self.url / name).iterdir()) + if content_hash is not None: + urls = [url for url in urls if url.parts[-1] == content_hash] + return urls[-1] if urls else None + + def make_url(self, path: Path, command_hash: str, content_hash: str) -> "Pathy": + """Construct a URL from a subpath, a creation hash and a content hash.""" + return self.url / self.encode_name(str(path)) / command_hash / content_hash + + def encode_name(self, name: str) -> str: + """Encode a subpath into a URL-safe name.""" + return urllib.parse.quote_plus(name) + + +def get_content_hash(loc: Path) -> str: + return get_checksum(loc) + + +def get_command_hash( + site_hash: str, env_hash: str, deps: List[Path], cmd: List[str] +) -> str: + """Create a hash representing the execution of a command. This includes the + currently installed packages, whatever environment variables have been marked + as relevant, and the command. + """ + hashes = [site_hash, env_hash] + [get_checksum(dep) for dep in sorted(deps)] + hashes.extend(cmd) + creation_bytes = "".join(hashes).encode("utf8") + return hashlib.md5(creation_bytes).hexdigest() + + +def get_site_hash(): + """Hash the current Python environment's site-packages contents, including + the name and version of the libraries. The list we're hashing is what + `pip freeze` would output. + """ + site_dirs = site.getsitepackages() + if site.ENABLE_USER_SITE: + site_dirs.extend(site.getusersitepackages()) + packages = set() + for site_dir in site_dirs: + site_dir = Path(site_dir) + for subpath in site_dir.iterdir(): + if subpath.parts[-1].endswith("dist-info"): + packages.add(subpath.parts[-1].replace(".dist-info", "")) + package_bytes = "".join(sorted(packages)).encode("utf8") + return hashlib.md5sum(package_bytes).hexdigest() + + +def get_env_hash(env: Dict[str, str]) -> str: + """Construct a hash of the environment variables that will be passed into + the commands. + + Values in the env dict may be references to the current os.environ, using + the syntax $ENV_VAR to mean os.environ[ENV_VAR] + """ + env_vars = {} + for key, value in env.items(): + if value.startswith("$"): + env_vars[key] = os.environ.get(value[1:], "") + else: + env_vars[key] = value + return get_hash(env_vars) diff --git a/spacy/cli/project/run.py b/spacy/cli/project/run.py index 5c66095aa..6e1deeeee 100644 --- a/spacy/cli/project/run.py +++ b/spacy/cli/project/run.py @@ -44,7 +44,6 @@ def project_run( dry (bool): Perform a dry run and don't execute commands. """ config = load_project_config(project_dir) - variables = config.get("variables", {}) commands = {cmd["name"]: cmd for cmd in config.get("commands", [])} workflows = config.get("workflows", {}) validate_subcommand(commands.keys(), workflows.keys(), subcommand) @@ -54,22 +53,20 @@ def project_run( project_run(project_dir, cmd, force=force, dry=dry) else: cmd = commands[subcommand] - variables = config.get("variables", {}) for dep in cmd.get("deps", []): - dep = dep.format(**variables) if not (project_dir / dep).exists(): err = f"Missing dependency specified by command '{subcommand}': {dep}" err_kwargs = {"exits": 1} if not dry else {} msg.fail(err, **err_kwargs) with working_dir(project_dir) as current_dir: - rerun = check_rerun(current_dir, cmd, variables) + rerun = check_rerun(current_dir, cmd) if not rerun and not force: msg.info(f"Skipping '{cmd['name']}': nothing changed") else: msg.divider(subcommand) - run_commands(cmd["script"], variables, dry=dry) + run_commands(cmd["script"], dry=dry) if not dry: - update_lockfile(current_dir, cmd, variables) + update_lockfile(current_dir, cmd) def print_run_help(project_dir: Path, subcommand: Optional[str] = None) -> None: @@ -115,23 +112,15 @@ def print_run_help(project_dir: Path, subcommand: Optional[str] = None) -> None: def run_commands( - commands: List[str] = tuple(), - variables: Dict[str, Any] = {}, - silent: bool = False, - dry: bool = False, + commands: List[str] = tuple(), silent: bool = False, dry: bool = False, ) -> None: """Run a sequence of commands in a subprocess, in order. commands (List[str]): The string commands. - variables (Dict[str, Any]): Dictionary of variable names, mapped to their - values. Will be used to substitute format string variables in the - commands. silent (bool): Don't print the commands. dry (bool): Perform a dry run and don't execut anything. """ for command in commands: - # Substitute variables, e.g. "./{NAME}.json" - command = command.format(**variables) command = split_command(command) # Not sure if this is needed or a good idea. Motivation: users may often # use commands in their config that reference "python" and we want to @@ -173,15 +162,12 @@ def validate_subcommand( ) -def check_rerun( - project_dir: Path, command: Dict[str, Any], variables: Dict[str, Any] -) -> bool: +def check_rerun(project_dir: Path, command: Dict[str, Any]) -> bool: """Check if a command should be rerun because its settings or inputs/outputs changed. project_dir (Path): The current project directory. command (Dict[str, Any]): The command, as defined in the project.yml. - variables (Dict[str, Any]): The variables defined in the project.yml. RETURNS (bool): Whether to re-run the command. """ lock_path = project_dir / PROJECT_LOCK @@ -197,19 +183,16 @@ def check_rerun( # If the entry in the lockfile matches the lockfile entry that would be # generated from the current command, we don't rerun because it means that # all inputs/outputs, hashes and scripts are the same and nothing changed - return get_hash(get_lock_entry(project_dir, command, variables)) != get_hash(entry) + return get_hash(get_lock_entry(project_dir, command)) != get_hash(entry) -def update_lockfile( - project_dir: Path, command: Dict[str, Any], variables: Dict[str, Any] -) -> None: +def update_lockfile(project_dir: Path, command: Dict[str, Any]) -> None: """Update the lockfile after running a command. Will create a lockfile if it doesn't yet exist and will add an entry for the current command, its script and dependencies/outputs. project_dir (Path): The current project directory. command (Dict[str, Any]): The command, as defined in the project.yml. - variables (Dict[str, Any]): The variables defined in the project.yml. """ lock_path = project_dir / PROJECT_LOCK if not lock_path.exists(): @@ -217,13 +200,11 @@ def update_lockfile( data = {} else: data = srsly.read_yaml(lock_path) - data[command["name"]] = get_lock_entry(project_dir, command, variables) + data[command["name"]] = get_lock_entry(project_dir, command) srsly.write_yaml(lock_path, data) -def get_lock_entry( - project_dir: Path, command: Dict[str, Any], variables: Dict[str, Any] -) -> Dict[str, Any]: +def get_lock_entry(project_dir: Path, command: Dict[str, Any]) -> Dict[str, Any]: """Get a lockfile entry for a given command. An entry includes the command, the script (command steps) and a list of dependencies and outputs with their paths and file hashes, if available. The format is based on the @@ -231,12 +212,11 @@ def get_lock_entry( project_dir (Path): The current project directory. command (Dict[str, Any]): The command, as defined in the project.yml. - variables (Dict[str, Any]): The variables defined in the project.yml. RETURNS (Dict[str, Any]): The lockfile entry. """ - deps = get_fileinfo(project_dir, command.get("deps", []), variables) - outs = get_fileinfo(project_dir, command.get("outputs", []), variables) - outs_nc = get_fileinfo(project_dir, command.get("outputs_no_cache", []), variables) + deps = get_fileinfo(project_dir, command.get("deps", [])) + outs = get_fileinfo(project_dir, command.get("outputs", [])) + outs_nc = get_fileinfo(project_dir, command.get("outputs_no_cache", [])) return { "cmd": f"{COMMAND} run {command['name']}", "script": command["script"], @@ -245,20 +225,16 @@ def get_lock_entry( } -def get_fileinfo( - project_dir: Path, paths: List[str], variables: Dict[str, Any] -) -> List[Dict[str, str]]: +def get_fileinfo(project_dir: Path, paths: List[str]) -> List[Dict[str, str]]: """Generate the file information for a list of paths (dependencies, outputs). Includes the file path and the file's checksum. project_dir (Path): The current project directory. paths (List[str]): The file paths. - variables (Dict[str, Any]): The variables defined in the project.yml. RETURNS (List[Dict[str, str]]): The lockfile entry for a file. """ data = [] for path in paths: - path = path.format(**variables) file_path = project_dir / path md5 = get_checksum(file_path) if file_path.exists() else None data.append({"path": path, "md5": md5}) diff --git a/spacy/schemas.py b/spacy/schemas.py index 3eef814c6..170342b54 100644 --- a/spacy/schemas.py +++ b/spacy/schemas.py @@ -303,7 +303,7 @@ class ProjectConfigCommand(BaseModel): class ProjectConfigSchema(BaseModel): # fmt: off - variables: Dict[StrictStr, Union[str, int, float, bool]] = Field({}, title="Optional variables to substitute in commands") + vars: Dict[StrictStr, Any] = Field({}, title="Optional variables to substitute in commands") assets: List[ProjectConfigAsset] = Field([], title="Data assets") workflows: Dict[StrictStr, List[StrictStr]] = Field({}, title="Named workflows, mapped to list of project commands to run in order") commands: List[ProjectConfigCommand] = Field([], title="Project command shortucts") diff --git a/spacy/tests/test_cli.py b/spacy/tests/test_cli.py index 89ce740e0..104c7c516 100644 --- a/spacy/tests/test_cli.py +++ b/spacy/tests/test_cli.py @@ -6,9 +6,12 @@ from spacy.schemas import ProjectConfigSchema, RecommendationSchema, validate from spacy.cli.pretrain import make_docs from spacy.cli.init_config import init_config, RECOMMENDATIONS from spacy.cli._util import validate_project_commands, parse_config_overrides -from spacy.util import get_lang_class +from spacy.cli._util import load_project_config, substitute_project_variables +from thinc.config import ConfigValidationError import srsly +from .util import make_tempdir + def test_cli_converters_conllu2json(): # from NorNE: https://github.com/ltgoslo/norne/blob/3d23274965f513f23aa48455b28b1878dad23c05/ud/nob/no_bokmaal-ud-dev.conllu @@ -295,6 +298,24 @@ def test_project_config_validation2(config, n_errors): assert len(errors) == n_errors +def test_project_config_interpolation(): + variables = {"a": 10, "b": {"c": "foo", "d": True}} + commands = [ + {"name": "x", "script": ["hello ${vars.a} ${vars.b.c}"]}, + {"name": "y", "script": ["${vars.b.c} ${vars.b.d}"]}, + ] + project = {"commands": commands, "vars": variables} + with make_tempdir() as d: + srsly.write_yaml(d / "project.yml", project) + cfg = load_project_config(d) + assert cfg["commands"][0]["script"][0] == "hello 10 foo" + assert cfg["commands"][1]["script"][0] == "foo true" + commands = [{"name": "x", "script": ["hello ${vars.a} ${vars.b.e}"]}] + project = {"commands": commands, "vars": variables} + with pytest.raises(ConfigValidationError): + substitute_project_variables(project) + + @pytest.mark.parametrize( "args,expected", [ diff --git a/spacy/util.py b/spacy/util.py index 5eff82866..736f4d805 100644 --- a/spacy/util.py +++ b/spacy/util.py @@ -1,5 +1,5 @@ from typing import List, Union, Dict, Any, Optional, Iterable, Callable, Tuple -from typing import Iterator, Type, Pattern, TYPE_CHECKING +from typing import Iterator, Type, Pattern, Generator, TYPE_CHECKING from types import ModuleType import os import importlib @@ -610,7 +610,7 @@ def working_dir(path: Union[str, Path]) -> None: @contextmanager -def make_tempdir() -> None: +def make_tempdir() -> Generator[Path, None, None]: """Execute a block in a temporary directory and remove the directory and its contents at the end of the with block. diff --git a/website/docs/api/cli.md b/website/docs/api/cli.md index 551147929..7ce95c019 100644 --- a/website/docs/api/cli.md +++ b/website/docs/api/cli.md @@ -847,6 +847,92 @@ $ python -m spacy project run [subcommand] [project_dir] [--force] [--dry] | `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | | **EXECUTES** | The command defined in the `project.yml`. | +### project push {#project-push tag="command"} + +Upload all available files or directories listed as in the `outputs` section of +commands to a remote storage. Outputs are archived and compressed prior to +upload, and addressed in the remote storage using the output's relative path +(URL encoded), a hash of its command string and dependencies, and a hash of its +file contents. This means `push` should **never overwrite** a file in your +remote. If all the hashes match, the contents are the same and nothing happens. +If the contents are different, the new version of the file is uploaded. Deleting +obsolete files is left up to you. + +Remotes can be defined in the `remotes` section of the +[`project.yml`](/usage/projects#project-yml). Under the hood, spaCy uses the +[`smart-open`](https://github.com/RaRe-Technologies/smart_open) library to +communicate with the remote storages, so you can use any protocol that +`smart-open` supports, including [S3](https://aws.amazon.com/s3/), +[Google Cloud Storage](https://cloud.google.com/storage), SSH and more, although +you may need to install extra dependencies to use certain protocols. + +```cli +$ python -m spacy project push [remote] [project_dir] +``` + +> #### Example +> +> ```cli +> $ python -m spacy project push my_bucket +> ``` +> +> ```yaml +> ### project.yml +> remotes: +> my_bucket: 's3://my-spacy-bucket' +> ``` + +| Name | Description | +| -------------- | --------------------------------------------------------------------------------------- | +| `remote` | The name of the remote to upload to. Defaults to `"default"`. ~~str (positional)~~ | +| `project_dir` | Path to project directory. Defaults to current working directory. ~~Path (positional)~~ | +| `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | +| **UPLOADS** | All project outputs that exist and are not already stored in the remote. | + +### project pull {#project-pull tag="command"} + +Download all files or directories listed as `outputs` for commands, unless they +are not already present locally. When searching for files in the remote, `pull` +won't just look at the output path, but will also consider the **command +string** and the **hashes of the dependencies**. For instance, let's say you've +previously pushed a model checkpoint to the remote, but now you've changed some +hyper-parameters. Because you've changed the inputs to the command, if you run +`pull`, you won't retrieve the stale result. If you train your model and push +the outputs to the remote, the outputs will be saved alongside the prior +outputs, so if you change the config back, you'll be able to fetch back the +result. + +Remotes can be defined in the `remotes` section of the +[`project.yml`](/usage/projects#project-yml). Under the hood, spaCy uses the +[`smart-open`](https://github.com/RaRe-Technologies/smart_open) library to +communicate with the remote storages, so you can use any protocol that +`smart-open` supports, including [S3](https://aws.amazon.com/s3/), +[Google Cloud Storage](https://cloud.google.com/storage), SSH and more, although +you may need to install extra dependencies to use certain protocols. + +```cli +$ python -m spacy project pull [remote] [project_dir] +``` + +> #### Example +> +> ```cli +> $ python -m spacy project pull my_bucket +> ``` +> +> ```yaml +> ### project.yml +> remotes: +> my_bucket: 's3://my-spacy-bucket' +> ``` + +| Name | Description | +| -------------- | --------------------------------------------------------------------------------------- | +| `remote` | The name of the remote to download from. Defaults to `"default"`. ~~str (positional)~~ | +| `project_dir` | Path to project directory. Defaults to current working directory. ~~Path (positional)~~ | +| `--help`, `-h` | Show help message and available arguments. ~~bool (flag)~~ | +| **DOWNLOADS** | All project outputs that do not exist locally and can be found in the remote. | + ### project dvc {#project-dvc tag="command"} Auto-generate [Data Version Control](https://dvc.org) (DVC) config file. Calls diff --git a/website/docs/images/projects.svg b/website/docs/images/projects.svg new file mode 100644 index 000000000..8de5f9ef6 --- /dev/null +++ b/website/docs/images/projects.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/docs/usage/projects.md b/website/docs/usage/projects.md index 30e4394d1..1aaaeb3af 100644 --- a/website/docs/usage/projects.md +++ b/website/docs/usage/projects.md @@ -5,9 +5,12 @@ menu: - ['Intro & Workflow', 'intro'] - ['Directory & Assets', 'directory'] - ['Custom Projects', 'custom'] + - ['Remote Storage', 'remote'] - ['Integrations', 'integrations'] --- +## Introduction and workflow {#intro hidden="true"} + > #### 🪐 Project templates > > Our [`projects`](https://github.com/explosion/projects) repo includes various @@ -19,20 +22,17 @@ spaCy projects let you manage and share **end-to-end spaCy workflows** for different **use cases and domains**, and orchestrate training, packaging and serving your custom models. You can start off by cloning a pre-defined project template, adjust it to fit your needs, load in your data, train a model, export -it as a Python package and share the project templates with your team. spaCy -projects can be used via the new [`spacy project`](/api/cli#project) command. -For an overview of the available project templates, check out the -[`projects`](https://github.com/explosion/projects) repo. spaCy projects also -[integrate](#integrations) with many other cool machine learning and data -science tools to track and manage your data and experiments, iterate on demos -and prototypes and ship your models into production. +it as a Python package, upload your outputs to a remote storage and share your +results with your team. spaCy projects can be used via the new +[`spacy project`](/api/cli#project) command and we provide templates in our +[`projects`](https://github.com/explosion/projects) repo. -## Introduction and workflow {#intro} - +![Illustration of project workflow and commands](../images/projects.svg) + ```yaml ### project.yml -variables: - BATCH_SIZE: 128 +vars: + batch_size: 128 commands: - name: evaluate script: - - 'python scripts/custom_evaluation.py {BATCH_SIZE} ./training/model-best ./corpus/eval.json' + - 'python scripts/custom_evaluation.py ${batch_size} ./training/model-best ./corpus/eval.json' deps: - 'training/model-best' - 'corpus/eval.json' @@ -421,6 +446,114 @@ assets: checksum: '5113dc04e03f079525edd8df3f4f39e3' ``` +## Remote Storage {#remote} + +You can persist your project outputs to a remote storage using the +[`project push`](/api/cli#project-push) command. This can help you **export** +your model packages, **share** work with your team, or **cache results** to +avoid repeating work. The [`project pull`](/api/cli#project-pull) command will +download any outputs that are in the remote storage and aren't available +locally. + +You can list one or more remotes in the `remotes` section of your +[`project.yml`](#project-yml) by mapping a string name to the URL of the +storage. Under the hood, spaCy uses the +[`smart-open`](https://github.com/RaRe-Technologies/smart_open) library to +communicate with the remote storages, so you can use any protocol that +`smart-open` supports, including [S3](https://aws.amazon.com/s3/), +[Google Cloud Storage](https://cloud.google.com/storage), SSH and more, although +you may need to install extra dependencies to use certain protocols. + +> #### Example +> +> ```cli +> $ python -m spacy project pull local +> ``` + +```yaml +### project.yml +remotes: + default: 's3://my-spacy-bucket' + local: '/mnt/scratch/cache' + stuff: 'ssh://myserver.example.com/whatever' +``` + + + +Inside the remote storage, spaCy uses a clever **directory structure** to avoid +overwriting files. The top level of the directory structure is a URL-encoded +version of the output's path. Within this directory are subdirectories named +according to a hash of the command string and the command's dependencies. +Finally, within those directories are files, named according to an MD5 hash of +their contents. + + + + +```yaml +└── urlencoded_file_path # Path of original file + ├── some_command_hash # Hash of command you ran + │ ├── some_content_hash # Hash of file content + │ └── another_content_hash + └── another_command_hash + └── third_content_hash +``` + + + +For instance, let's say you had the following command in your `project.yml`: + +```yaml +### project.yml +- name: train + help: 'Train a spaCy model using the specified corpus and config' + script: + - 'spacy train ./config.cfg --output training/' + deps: + - 'corpus/train' + - 'corpus/dev' + - 'config.cfg' + outputs: + - 'training/model-best' +``` + +> #### Example +> +> ``` +> └── s3://my-spacy-bucket/training%2Fmodel-best +> └── 1d8cb33a06cc345ad3761c6050934a1b +> └── d8e20c3537a084c5c10d95899fe0b1ff +> ``` + +After you finish training, you run [`project push`](/api/cli#project-push) to +make sure the `training/model-best` output is saved to remote storage. spaCy +will then construct a hash from your command script and the listed dependencies, +`corpus/train`, `corpus/dev` and `config.cfg`, in order to identify the +execution context of your output. It would then compute an MD5 hash of the +`training/model-best` directory, and use those three pieces of information to +construct the storage URL. + +```cli +$ python -m spacy project run train +$ python -m spacy project push +``` + +If you change the command or one of its dependencies (for instance, by editing +the [`config.cfg`](/usage/training#config) file to tune the hyperparameters, a +different creation hash will be calculated, so when you use +[`project push`](/api/cli#project-push) you won't be overwriting your previous +file. The system even supports multiple outputs for the same file and the same +context, which can happen if your training process is not deterministic, or if +you have dependencies that aren't represented in the command. + +In summary, the [`spacy project`](/api/cli#project) remote storages are designed +to make a particular set of trade-offs. Priority is placed on **convenience**, +**correctness** and **avoiding data loss**. You can use +[`project push`](/api/cli#project-push) freely, as you'll never overwrite remote +state, and you don't have to come up with names or version numbers. However, +it's up to you to manage the size of your remote storage, and to remove files +that are no longer relevant to you. + ## Integrations {#integrations} ### Data Version Control (DVC) {#dvc} @@ -517,16 +650,17 @@ and evaluation set. ```yaml ### project.yml -variables: - PRODIGY_DATASET: 'ner_articles' - PRODIGY_LABELS: 'PERSON,ORG,PRODUCT' - PRODIGY_MODEL: 'en_core_web_md' +vars: + prodigy: + dataset: 'ner_articles' + labels: 'PERSON,ORG,PRODUCT' + model: 'en_core_web_md' commands: - name: annotate - script: - - 'python -m prodigy ner.correct {PRODIGY_DATASET} ./assets/raw_data.jsonl {PRODIGY_MODEL} --labels {PRODIGY_LABELS}' - - 'python -m prodigy data-to-spacy ./corpus/train.json ./corpus/eval.json --ner {PRODIGY_DATASET}' + - 'python -m prodigy ner.correct ${vars.prodigy.dataset} ./assets/raw_data.jsonl ${vars.prodigy.model} --labels ${vars.prodigy.labels}' + - 'python -m prodigy data-to-spacy ./corpus/train.json ./corpus/eval.json --ner ${vars.prodigy.dataset}' - 'python -m spacy convert ./corpus/train.json ./corpus/train.spacy' - 'python -m spacy convert ./corpus/eval.json ./corpus/eval.spacy' - deps: diff --git a/website/docs/usage/v3.md b/website/docs/usage/v3.md index aea1d892c..2a47fd264 100644 --- a/website/docs/usage/v3.md +++ b/website/docs/usage/v3.md @@ -104,11 +104,15 @@ spaCy projects let you manage and share **end-to-end spaCy workflows** for different **use cases and domains**, and orchestrate training, packaging and serving your custom models. You can start off by cloning a pre-defined project template, adjust it to fit your needs, load in your data, train a model, export -it as a Python package and share the project templates with your team. spaCy -projects also make it easy to **integrate with other tools** in the data science -and machine learning ecosystem, including [DVC](/usage/projects#dvc) for data -version control, [Prodigy](/usage/projects#prodigy) for creating labelled data, -[Streamlit](/usage/projects#streamlit) for building interactive apps, +it as a Python package, upload your outputs to a remote storage and share your +results with your team. + +![Illustration of project workflow and commands](../images/projects.svg) + +spaCy projects also make it easy to **integrate with other tools** in the data +science and machine learning ecosystem, including [DVC](/usage/projects#dvc) for +data version control, [Prodigy](/usage/projects#prodigy) for creating labelled +data, [Streamlit](/usage/projects#streamlit) for building interactive apps, [FastAPI](/usage/projects#fastapi) for serving models in production, [Ray](/usage/projects#ray) for parallel training, [Weights & Biases](/usage/projects#wandb) for experiment tracking, and more! diff --git a/website/src/components/table.js b/website/src/components/table.js index 3f41a587b..bd3d663f3 100644 --- a/website/src/components/table.js +++ b/website/src/components/table.js @@ -5,6 +5,8 @@ import Icon from './icon' import { isString } from './util' import classes from '../styles/table.module.sass' +const FOOT_ROW_REGEX = /^(RETURNS|YIELDS|CREATES|PRINTS|EXECUTES|UPLOADS|DOWNLOADS)/ + function isNum(children) { return isString(children) && /^\d+[.,]?[\dx]+?(|x|ms|mb|gb|k|m)?$/i.test(children) } @@ -43,7 +45,6 @@ function isDividerRow(children) { } function isFootRow(children) { - const rowRegex = /^(RETURNS|YIELDS|CREATES|PRINTS|EXECUTES)/ if (children.length && children[0].props.name === 'td') { const cellChildren = children[0].props.children if ( @@ -52,7 +53,7 @@ function isFootRow(children) { cellChildren.props.children && isString(cellChildren.props.children) ) { - return rowRegex.test(cellChildren.props.children) + return FOOT_ROW_REGEX.test(cellChildren.props.children) } } return false From 89f5b8abb34d081f48399e86956db2c512b00d3c Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 23 Aug 2020 21:14:44 +0200 Subject: [PATCH 88/92] Fix project push --- spacy/cli/project/push.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/spacy/cli/project/push.py b/spacy/cli/project/push.py index 0b070c9d8..e09ee6e1a 100644 --- a/spacy/cli/project/push.py +++ b/spacy/cli/project/push.py @@ -34,6 +34,9 @@ def project_push(project_dir: Path, remote: str): remote = config["remotes"][remote] storage = RemoteStorage(project_dir, remote) for cmd in config.get("commands", []): + deps = [project_dir / dep for dep in cmd.get("deps", [])] + if any(not dep.exists() for dep in deps): + continue cmd_hash = get_command_hash( "", "", [project_dir / dep for dep in cmd.get("deps", [])], cmd["script"] ) From 160a855246df24d778626c4272e00307b56c4424 Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 23 Aug 2020 21:15:12 +0200 Subject: [PATCH 89/92] Format --- spacy/cli/__init__.py | 4 ++-- spacy/cli/train.py | 11 ++++++++--- spacy/language.py | 8 +++++++- spacy/ml/models/entity_linker.py | 2 ++ spacy/tests/regression/test_issue5230.py | 1 + 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/spacy/cli/__init__.py b/spacy/cli/__init__.py index 8aea6ef45..4c3adc5d3 100644 --- a/spacy/cli/__init__.py +++ b/spacy/cli/__init__.py @@ -21,8 +21,8 @@ from .project.clone import project_clone # noqa: F401 from .project.assets import project_assets # noqa: F401 from .project.run import project_run # noqa: F401 from .project.dvc import project_update_dvc # noqa: F401 -from .project.push import project_push # noqa: F401 -from .project.pull import project_pull # noqa: F401 +from .project.push import project_push # noqa: F401 +from .project.pull import project_pull # noqa: F401 @app.command("link", no_args_is_help=True, deprecated=True, hidden=True) diff --git a/spacy/cli/train.py b/spacy/cli/train.py index 1366e0b38..15188cd4e 100644 --- a/spacy/cli/train.py +++ b/spacy/cli/train.py @@ -75,7 +75,9 @@ def train( msg.info("Using CPU") msg.info(f"Loading config and nlp from: {config_path}") with show_validation_error(config_path): - config = util.load_config(config_path, overrides=config_overrides, interpolate=True) + config = util.load_config( + config_path, overrides=config_overrides, interpolate=True + ) if config.get("training", {}).get("seed") is not None: fix_random_seed(config["training"]["seed"]) # Use original config here before it's resolved to functions @@ -208,7 +210,9 @@ def create_evaluation_callback( scores = nlp.evaluate(dev_examples) # Calculate a weighted sum based on score_weights for the main score try: - weighted_score = sum(scores.get(s, 0.0) * weights.get(s, 0.0) for s in weights) + weighted_score = sum( + scores.get(s, 0.0) * weights.get(s, 0.0) for s in weights + ) except KeyError as e: keys = list(scores.keys()) err = Errors.E983.format(dict="score_weights", key=str(e), keys=keys) @@ -378,7 +382,8 @@ def setup_printer( try: scores = [ - "{0:.2f}".format(float(info["other_scores"].get(col, 0.0))) for col in score_cols + "{0:.2f}".format(float(info["other_scores"].get(col, 0.0))) + for col in score_cols ] except KeyError as e: raise KeyError( diff --git a/spacy/language.py b/spacy/language.py index 6fc780f3e..57abcca0e 100644 --- a/spacy/language.py +++ b/spacy/language.py @@ -774,7 +774,13 @@ class Language: # we have no components to insert before/after, or we're replacing the last component self.add_pipe(factory_name, name=name, config=config, validate=validate) else: - self.add_pipe(factory_name, name=name, before=pipe_index, config=config, validate=validate) + self.add_pipe( + factory_name, + name=name, + before=pipe_index, + config=config, + validate=validate, + ) def rename_pipe(self, old_name: str, new_name: str) -> None: """Rename a pipeline component. diff --git a/spacy/ml/models/entity_linker.py b/spacy/ml/models/entity_linker.py index 55d8614e1..6792f3e59 100644 --- a/spacy/ml/models/entity_linker.py +++ b/spacy/ml/models/entity_linker.py @@ -30,6 +30,7 @@ def load_kb(kb_path: str) -> Callable[[Vocab], KnowledgeBase]: kb = KnowledgeBase(vocab, entity_vector_length=1) kb.from_disk(kb_path) return kb + return kb_from_file @@ -37,6 +38,7 @@ def load_kb(kb_path: str) -> Callable[[Vocab], KnowledgeBase]: def empty_kb(entity_vector_length: int) -> Callable[[Vocab], KnowledgeBase]: def empty_kb_factory(vocab): return KnowledgeBase(vocab=vocab, entity_vector_length=entity_vector_length) + return empty_kb_factory diff --git a/spacy/tests/regression/test_issue5230.py b/spacy/tests/regression/test_issue5230.py index 58d03ca8b..2ac886625 100644 --- a/spacy/tests/regression/test_issue5230.py +++ b/spacy/tests/regression/test_issue5230.py @@ -76,6 +76,7 @@ def entity_linker(): kb = KnowledgeBase(vocab, entity_vector_length=1) kb.add_entity("test", 0.0, zeros((1, 1), dtype="f")) return kb + return create_kb config = {"kb_loader": {"@assets": "TestIssue5230KB.v1"}} From 001546c19e2616cf9740cb78367e4d9b74e1366b Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Sun, 23 Aug 2020 21:15:38 +0200 Subject: [PATCH 90/92] Set version to v3.0.0a10 --- spacy/about.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spacy/about.py b/spacy/about.py index 56bb016c3..da3e32805 100644 --- a/spacy/about.py +++ b/spacy/about.py @@ -1,6 +1,6 @@ # fmt: off __title__ = "spacy-nightly" -__version__ = "3.0.0a9" +__version__ = "3.0.0a10" __release__ = True __download_url__ = "https://github.com/explosion/spacy-models/releases/download" __compatibility__ = "https://raw.githubusercontent.com/explosion/spacy-models/master/compatibility.json" From 588c28fe459d29026c0f153ef1948873ec1dca1b Mon Sep 17 00:00:00 2001 From: Matthew Honnibal Date: Mon, 24 Aug 2020 01:23:36 +0200 Subject: [PATCH 91/92] Fix project pull when deps missing --- spacy/cli/project/pull.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spacy/cli/project/pull.py b/spacy/cli/project/pull.py index 1bf608c40..73cb46bb7 100644 --- a/spacy/cli/project/pull.py +++ b/spacy/cli/project/pull.py @@ -30,6 +30,8 @@ def project_pull(project_dir: Path, remote: str, *, verbose: bool = False): storage = RemoteStorage(project_dir, remote) for cmd in config.get("commands", []): deps = [project_dir / dep for dep in cmd.get("deps", [])] + if any(not dep.exists() for dep in deps): + continue cmd_hash = get_command_hash("", "", deps, cmd["script"]) for output_path in cmd.get("outputs", []): url = storage.pull(output_path, command_hash=cmd_hash) From 26405710e099415aa590c034196b8d87c6397394 Mon Sep 17 00:00:00 2001 From: Ines Montani Date: Mon, 24 Aug 2020 10:28:15 +0200 Subject: [PATCH 92/92] Add icon credit [ci skip] --- website/docs/images/projects.svg | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/images/projects.svg b/website/docs/images/projects.svg index 8de5f9ef6..c7518d445 100644 --- a/website/docs/images/projects.svg +++ b/website/docs/images/projects.svg @@ -1,4 +1,5 @@ +