Python Spacy
similarity
NLP
machine learning
programming

Python Spacy similarity without loop?

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

Introduction

Computing spaCy similarity in a Python loop is fine for small input sizes but becomes slow for larger corpora. The scalable pattern is to extract vectors once and run cosine operations with vectorized NumPy math. This keeps spaCy for linguistic processing while avoiding Python-level pairwise loops.

Core Sections

1. Baseline similarity and its limits

Doc.similarity is convenient for one-off comparisons.

python
1import spacy
2
3nlp = spacy.load("en_core_web_md")
4
5d1 = nlp("payment failed during checkout")
6d2 = nlp("checkout transaction was declined")
7
8print(d1.similarity(d2))

The problem appears when you compare one query against thousands of documents or build full pairwise matrices.

2. Extract vectors once, then vectorize

Run spaCy pipeline once and keep vectors in a matrix.

python
1import spacy
2import numpy as np
3
4nlp = spacy.load("en_core_web_md")
5texts = [
6    "payment failed",
7    "transaction declined",
8    "weather forecast",
9    "credit card issue",
10]
11
12vecs = np.vstack([nlp(t).vector for t in texts])
13
14# L2 normalize rows for cosine similarity
15norms = np.linalg.norm(vecs, axis=1, keepdims=True)
16vecs = vecs / np.clip(norms, 1e-12, None)
17
18sim_matrix = vecs @ vecs.T
19print(sim_matrix)

No explicit nested loop is required for pairwise comparison.

3. Query-to-corpus ranking pattern

Most retrieval workloads compare one query vector to a precomputed corpus matrix.

python
1import spacy
2import numpy as np
3
4nlp = spacy.load("en_core_web_md")
5
6corpus = [
7    "payment failed at checkout",
8    "new football season starts",
9    "refund requested for duplicate charge",
10    "sunny weather expected tomorrow",
11]
12
13corpus_vecs = np.vstack([nlp(t).vector for t in corpus])
14corpus_vecs /= np.clip(np.linalg.norm(corpus_vecs, axis=1, keepdims=True), 1e-12, None)
15
16query_vec = nlp("card payment declined").vector
17query_vec /= max(np.linalg.norm(query_vec), 1e-12)
18
19scores = corpus_vecs @ query_vec
20best_idx = int(np.argmax(scores))
21print(corpus[best_idx], float(scores[best_idx]))

This is faster than repeated Doc.similarity calls for large sets.

4. Use nlp.pipe for high-throughput vector extraction

nlp.pipe reduces overhead for large text collections.

python
1import spacy
2import numpy as np
3
4nlp = spacy.load("en_core_web_md")
5texts = [f"document {i}" for i in range(20000)]
6
7docs = nlp.pipe(texts, batch_size=256)
8vecs = np.vstack([doc.vector for doc in docs])
9print(vecs.shape)

Batch processing matters when corpus updates are frequent.

5. Cache vectors for repeated queries

If corpus changes rarely, precompute and cache normalized vectors to disk or memory store. Query-time then becomes matrix multiply only.

This shift usually provides the biggest latency improvement in retrieval systems.

6. Quality caveats and model choice

Speed optimization is pointless if embeddings are weak. Check your model:

  • small spaCy models may have limited vectors
  • domain mismatch reduces semantic relevance
  • short noisy texts can degrade similarity quality

Benchmark precision on real labeled pairs before scaling infrastructure.

7. Memory planning for similarity at scale

Full N x N similarity matrices become huge quickly. If you only need top-k results, compute query-wise or chunk-wise instead of materializing full matrix.

Chunked top-k strategy:

  1. split corpus vectors into chunks
  2. score query against each chunk
  3. keep running top-k heap

This controls memory growth while retaining vectorized speed.

8. Numerical stability and normalization

Always protect normalization from zero vectors. Use clipping to avoid divide-by-zero errors.

Also ensure vectors are same dtype to prevent unnecessary casting overhead in hot paths.

9. Validation workflow

After optimization, compare results from vectorized path to baseline Doc.similarity on a fixed sample set. Small numerical differences are normal, but ranking quality should remain consistent.

Keep regression checks in test suite so performance refactors do not silently degrade relevance.

Common Pitfalls

  • Recomputing corpus vectors on every query.
  • Building full pairwise matrices when only query top-k is needed.
  • Skipping vector normalization before cosine operations.
  • Optimizing speed without validating semantic quality.
  • Using tiny models and expecting robust semantic retrieval.

Summary

  • spaCy similarity can be scaled by separating vector extraction from similarity math.
  • Use nlp.pipe and cache normalized vectors for throughput.
  • Perform cosine scoring with vectorized NumPy operations.
  • Plan memory based on query pattern rather than full matrix by default.
  • Validate ranking quality after each optimization step.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

ML System Design practice on Codemia

Design recommenders, ranking systems and training pipelines the way ML interviews actually ask for them, with worked solutions.

Practice ML system design

All Rights Reserved.