Last modified: Oct 24, 2026
Text Classification with spaCy
Text classification is one of the most common tasks in natural language processing. You give a model some text. The model gives back a label.
Common examples include spam detection, sentiment analysis, and topic tagging. spaCy makes this task approachable, even for beginners.
In this article, you will learn how to build a custom text classifier with spaCy. We will cover data preparation, training, evaluation, and saving the model.
What Is Text Classification?
Text classification assigns a category to a piece of text. The category can be a single label or multiple labels.
Single-label classification picks exactly one class. For example, an email is either "spam" or "ham".
Multi-label classification allows several labels at once. A news article could be tagged as both "tech" and "business".
spaCy supports both types through its built-in textcat pipeline component.
Why Use spaCy?
spaCy is fast and production-ready. It has a clean API and strong documentation.
You do not need deep machine learning knowledge to get started. A few lines of configuration can train a solid baseline model.
spaCy also handles tokenization, vectors, and serialization for you. That saves a lot of time.
If your pipeline later needs to move data between services, you can use tools like kafka-python to produce and consume Kafka messages after you install kafka-python in your environment.
Installing spaCy
Start by installing spaCy and downloading a base model. The base model gives you tokenization and word vectors.
pip install spacy
python -m spacy download en_core_web_sm
Verify the installation with a quick check.
import spacy
nlp = spacy.load("en_core_web_sm")
print(nlp.pipe_names)
['tok2vec', 'tagger', 'parser', 'attribute_ruler', 'lemmatizer', 'ner']
Preparing Training Data
spaCy expects training data in a simple format. Each example is a text paired with a dictionary of labels.
The value 1.0 means the label applies. The value 0.0 means it does not.
# Each item: (text, {"cats": {label: score}})
TRAIN_DATA = [
("I love this product, it works great!", {"cats": {"POSITIVE": 1.0, "NEGATIVE": 0.0}}),
("This is the best purchase I made.", {"cats": {"POSITIVE": 1.0, "NEGATIVE": 0.0}}),
("Amazing quality and fast delivery.", {"cats": {"POSITIVE": 1.0, "NEGATIVE": 0.0}}),
("Terrible experience, it broke on day one.", {"cats": {"POSITIVE": 0.0, "NEGATIVE": 1.0}}),
("I hate it, total waste of money.", {"cats": {"POSITIVE": 0.0, "NEGATIVE": 1.0}}),
("The item arrived damaged and late.", {"cats": {"POSITIVE": 0.0, "NEGATIVE": 1.0}}),
]
Real projects need hundreds or thousands of examples. A small dataset will overfit quickly.
Keep your labels balanced. If 90 percent of examples are positive, the model may ignore the minority class.
Creating a Blank Pipeline
For a custom classifier, start with a blank English pipeline. Then add the textcat component.
import spacy
# Create a blank pipeline
nlp = spacy.blank("en")
# Add the text classifier with two labels
textcat = nlp.add_pipe("textcat", config={"exclusive_classes": True})
textcat.add_label("POSITIVE")
textcat.add_label("NEGATIVE")
print(nlp.pipe_names)
['textcat']
Set exclusive_classes to True for single-label problems. Use False when a text can have multiple labels.
Training the Model
Training in spaCy uses an Example object. You convert raw data into examples, then update the model in batches.
import spacy
from spacy.training import Example
import random
nlp = spacy.blank("en")
textcat = nlp.add_pipe("textcat", config={"exclusive_classes": True})
textcat.add_label("POSITIVE")
textcat.add_label("NEGATIVE")
# Initialize the model before training
nlp.initialize(lambda: [Example.from_dict(nlp.make_doc(t), a) for t, a in TRAIN_DATA])
optimizer = nlp.create_optimizer()
for epoch in range(20):
random.shuffle(TRAIN_DATA)
losses = {}
for text, annots in TRAIN_DATA:
doc = nlp.make_doc(text)
example = Example.from_dict(doc, annots)
nlp.update([example], sgd=optimizer, losses=losses)
print(f"Epoch {epoch + 1} loss: {losses['textcat']:.4f}")
Epoch 1 loss: 1.3821
Epoch 2 loss: 0.9410
Epoch 3 loss: 0.5127
...
Epoch 20 loss: 0.0031
Falling loss is a good sign, but it does not guarantee good results. Always test on unseen data.
Making Predictions
Once trained, you call the model on new text. The cats attribute holds the scores.
texts = [
"This works perfectly and I am happy.",
"Awful product, it stopped working.",
]
for doc in nlp.pipe(texts):
label = max(doc.cats, key=doc.cats.get)
print(f"{doc.text!r} -> {label} ({doc.cats[label]:.3f})")
'This works perfectly and I am happy.' -> POSITIVE (0.987)
'Awful product, it stopped working.' -> NEGATIVE (0.974)
Evaluating the Model
Evaluation tells you how well the model generalizes. The Scorer class computes precision, recall, and F-score for text categories.
from spacy.scorer import Scorer
scorer = Scorer()
# Build examples from a held-out test set
examples = [Example.from_dict(nlp.make_doc(t), a) for t, a in TEST_DATA]
scores = scorer.score_cats(examples, "cats")
print(scores)
{'POSITIVE': {'p': 0.91, 'r': 0.88, 'f': 0.89}, 'NEGATIVE': {'p': 0.90, 'r': 0.92, 'f': 0.91}}
Aim for a balanced F-score. High precision with low recall means the model misses many true cases.
Saving and Loading the Model
Save the trained pipeline with to_disk. Load it later with spacy.load.
nlp.to_disk("./my_textcat_model")
# Later, in another script
import spacy
nlp = spacy.load("./my_textcat_model")
doc = nlp("Great service, very fast.")
print(doc.cats)
{'POSITIVE': 0.981, 'NEGATIVE': 0.019}
Using a Config File
For larger projects, use spaCy's config system. It makes training reproducible and easier to tune.
python -m spacy init config config.cfg --pipeline textcat --lang en
python -m spacy train config.cfg --paths.train train.spacy --paths.dev dev.spacy --output ./output
This workflow handles batching, evaluation, and model saving automatically. It is the recommended path for production models.
Tips for Better Results
Clean your data. Remove duplicates and fix inconsistent labels before training.
Use enough data. Most classifiers need at least a few hundred examples per class.
Watch for class imbalance. Oversample rare classes or collect more examples.
Use word vectors. Starting from a model with vectors like en_core_web_lg often improves accuracy.
Test on real data. A model trained on reviews may fail on support tickets. Match your training data to your use case.
Where This Fits in a Larger System
Classification is often one step in a bigger pipeline. You might clean text, classify it, and store the result.
If your results go to a database, you may need to install oracledb in Python to connect and write predictions. For containerized deployments, you can manage Kubernetes pods with Python to run your classifier as a service.
Conclusion
Text classification with spaCy is fast to set up and easy to extend. You prepare labeled data, build a blank pipeline, add the textcat component, and train.
Start small. Use a few hundred examples, check your evaluation scores, and iterate. Then save the model and deploy it.
With clean data and a sensible evaluation loop, spaCy gives you a reliable classifier with very little code.