Training a language model in spaCy v3 | Nico Lutz
1. Intro
On Feb 1 2020, explosion.ai introduced spaCy v3, a huge upgrade to the previous version, featuring new transformer-based pipelines and workflows. Naturally some projects needed to be migrated to spaCy v3. This article shows in tutorial like steps what needs to be done to create a new language model from scratch.
This article assumes basic knowledge of python, spaCy and standard nlp techniques.
2. Setup
Create virtual env:
python -m venv .venv
source .venv/bin/activate
and install spacy
pip install spacy
3. Language Subclass
If you want to retrain an already existing language class you can skip the following steps, otherwise keep on reading. For educational purposes we choose to train a new english model. To start, create a custom language subclass according to your needs.
Create a folder named custom_en
and add an __init__
defining the Defaults
and Language subclass. In addition we need:
- Punctuation: Punctuation chars (.?!)
- Stopwords: Frequent words that appear often in that language (a, the, about, ...)
- Syntax iterators: Functions that compute views of a Doc object based on its syntax, e.g. Noun chunks
- Tokenizer exception: Special-case rules for the tokenizer, for example, contractions like “can’t” and abbreviations with punctuation, like “U.K.”.
- ...
import spacy
from spacy.language import Language
from .tokenizer_exceptions import TOKENIZER_EXCEPTIONS
from .punctuation import TOKENIZER_PREFIXES, TOKENIZER_INFIXES
from .punctuation import TOKENIZER_SUFFIXES
from .stop_words import STOP_WORDS
from .syntax_iterators import SYNTAX_ITERATORS
class CustomEnglishDefaults(Language.Defaults):
tokenizer_exceptions = TOKENIZER_EXCEPTIONS
prefixes = TOKENIZER_PREFIXES
infixes = TOKENIZER_INFIXES
suffixes = TOKENIZER_SUFFIXES
syntax_iterators = SYNTAX_ITERATORS
stop_words = STOP_WORDS
@spacy.registry.languages("custom_en")
class CustomEnglish(Language):
lang = "custom_en"
Defaults = CustomEnglishDefaults
Here we are using spaCy's new
registery to create the custom
Language subclass. Upon program start the custom_en
language class will be
available.
4. SpaCy's training config and projects
Training Machine Learning models can result in a mess of bash scripts and files
to store all the necessary hyper-parameters. SpaCy introduces two new tools to
reduce complexity during training. The first is their new training
config system. Roughly said one
defines a config.cfg
where each section describes a component and all their
corresponding hyper-parameters. These sections can also refer to registered
function to load model architectures, optimizers, augmenters, etc. This makes it
easy to integrate everything and have all the information in one place.
The second tool is spaCy projects which lets
you manage and share end-to-end spaCy workflows. Such as assets and corpora
management, training and packaging. Basically we define all commands, we need
during our machine learning process, in a project.yaml
file and further reduce
complexity by calling just one and not several commands.
An example config.cfg
could look something like this:
[paths]
train = "corpus/train.spacy"
dev = "corpus/dev.spacy"
vectors = null
vocab_data = null
init_tok2vec = null
[system]
seed = 0
gpu_allocator = null
[nlp]
lang = "custon_en"
pipeline = ["tok2vec","tagger","morphologizer","parser","ner", "senter"]
batch_size = 256
disabled = []
before_creation = null
after_creation = null
after_pipeline_creation = null
tokenizer = {"@tokenizers":"spacy.Tokenizer.v1"}
[components]
[components.tok2vec]
factory = "tok2vec"
[components.tok2vec.model]
@architectures = "spacy.Tok2Vec.v2"
[components.tok2vec.model.embed]
@architectures = "spacy.MultiHashEmbed.v2"
width = ${components.tok2vec.model.encode.width}
attrs = ["NORM","PREFIX","SUFFIX","SHAPE", "LEMMA"]
rows = [5000,2500,2500,2500,2500]
include_static_vectors = false
[components.tok2vec.model.encode]
@architectures = "spacy.MaxoutWindowEncoder.v2"
width = 96
depth = 4
window_size = 1
maxout_pieces = 3
...
As a starting point spaCy provides a widget for creating a default.cfg. Also their extensive documentation on architectures and hyper-parameters helps a lot in defining each component.
In the beginning the project.yaml
can be created by using a provided template
and then add our custom commands as the project goes along.
python -m spacy project clone pipelines/tagger_parser_ud
5. Training data
In reality training data is messy and incomplete. All machine learning engineers
know that here lies the heart of each trained model. This article tries to give
a glimpse into spaCy's training process, so we cheat and take a complete labeled
dataset from the
Universal Treebank.
We can use a helper function to convert these .conllu
files into a trainable
format.
In the project.yaml
:
vars:
config: 'default'
lang: 'custon_en'
treebank: 'UD_English-EWT'
commands:
- name: preprocess
help: "Convert the data to spaCy's format or unzip"
script:
- 'mkdir -p corpus/${vars.norne}'
- 'python -m spacy convert
assets/${vars.treebank}/ud/nno/${vars.train_name}.conllu
corpus/${vars.treebank}/ --converter conllu --n-sents 10
--merge-subtokens'
6. Training
Using spacy project we can run:
python -m spacy project run train
and immediately start seeing losses and accuracies per epochs:
Running command: /Users/nico/Dev/custom_en/.venv/bin/python -m spacy train configs/default.cfg --output training/custom_en --gpu-id -1 --paths.train corpus/train.spacy --paths.dev corpus/dev.spacy --code ./custom_en/loader.py
ℹ Saving to output directory: training/custom_en
ℹ Using CPU
=========================== Initializing pipeline ===========================
[2021-09-08 09:24:54,019] [INFO] Set up nlp object from config
[2021-09-08 09:24:54,037] [INFO] Pipeline: ["tok2vec","tagger","morphologizer","parser","ner", "senter"]
[2021-09-08 09:24:55,612] [INFO] Created vocabulary
[2021-09-08 09:25:26,151] [INFO] Finished initializing nlp object
[2021-09-08 09:26:06,812] [INFO] Initialized pipeline components: ["tok2vec","tagger","morphologizer","parser","ner", "senter"]
✔ Initialized pipeline
============================= Training pipeline =============================
ℹ Pipeline: ["tok2vec","tagger","morphologizer","parser","ner", "senter"]
ℹ Initial learn rate: 0.001
E # LOSS TOK2VEC LOSS TAGGER LOSS MORPH... LOSS PARSER LOSS NER LOSS SENTER TAG_ACC POS_ACC MORPH_ACC DEP_UAS DEP_LAS SENTS_F ENTS_F ENTS_P ENTS_R LEMMA_ACC SCORE
--- ------ ------------ ----------- ------------- ----------- -------- ----------- ------- ------- --------- ------- ------- ------- ------ ------ ------ --------- ------
0 0 0.00 175.06 184.99 361.41 97.53 93.00 30.69 28.60 25.11 18.29 7.28 0.30 0.00 0.00 0.00 59.07 0.25
0 1000 27032.84 31342.60 54016.47 104942.62 10414.39 3219.51 93.91 93.
...
Notice that we use the --code
flag to read in our custom language class and
make it available during the training process. After training package the model
and start using it.
This is just the glimpse of the many capabilities of spaCy, we didn't start talking about loading pre-trained vectors or developing custom components. Nonetheless this article has turned into a love letter to spacy. The library is so well written and documented that it is a blast to use it during development. Every aspect of machine learning is though after and tightly integrated into their architecture.