In the previous post, we covered word embeddings: how every word (and every sentence) can be represented as a list of numbers, a vector, that captures its meaning. Similar words end up with similar vectors. Different words end up with different vectors.

But that raises an obvious next question: how do you actually measure how similar two vectors are?

Before answering that, it's worth pausing on why this question matters so much right now. Measuring the similarity between embeddings is not an academic exercise. It is the core operation behind two of the most widely used AI capabilities in production today.

Semantic search works by converting a user's query into a vector, converting every document in a collection into vectors, and finding the documents whose vectors are most similar to the query vector. Every time you search by meaning rather than exact keywords, embedding similarity is what's running underneath.

RAG (Retrieval-Augmented Generation) works the same way. When an AI assistant retrieves relevant context before generating an answer, it does so by computing similarity between the query embedding and thousands of stored document embeddings, then passing the closest matches to the language model. The quality of that retrieval determines the quality of the answer.

This is live, production-grade technology running in search engines, AI assistants, recommendation systems, and document databases right now. Understanding how it works starts here.

If you have the vector for "dog" and the vector for "cat," how do you turn those two lists of numbers into a single score that tells you how close in meaning they are? And how is that different from the vector for "dog" and "aeroplane," which should score much lower?

That's what this post is about. We're going to cover the main ways NLP systems measure similarity and distance between vectors and text. Some of these you'll use directly when building semantic search or RAG systems. Others are foundational concepts that help you understand what's happening under the hood.

The most important one, the one that powers most of modern NLP, is cosine similarity. But we'll also cover the alternatives, when each one makes sense, and how they compare.

What Is Vector Similarity?

Before getting into specific methods, let's make sure the core idea is clear.

A vector is just a list of numbers. In NLP, every word or sentence gets converted into a vector by a model like Word2Vec or BERT. Two sentences that mean similar things will have vectors that are numerically similar in some way. Two sentences about completely different topics will have vectors that are numerically different.

Vector similarity is the general term for any method that takes two vectors and produces a single score representing how alike they are. A high score means similar. A low score means different.

There are several ways to compute this score, and they each have different properties. The choice between them matters, and we'll explain why as we go through each one.

Cosine Similarity

Cosine similarity is the most widely used similarity metric in NLP. It's the default choice in semantic search systems, recommendation engines, and RAG pipelines. If someone says they're comparing embeddings, they almost certainly mean cosine similarity.

The Intuition

Here's the key insight behind cosine similarity: it measures the angle between two vectors, not the distance between their endpoints.

Imagine two arrows pointing outward from the origin of a graph. If the two arrows point in almost the same direction, the angle between them is small, and the cosine similarity is high (close to 1). If they point in completely different directions, the angle is large, and the cosine similarity is low (close to 0 or even negative).

Why does angle matter more than distance? Because in NLP, we care about direction, not magnitude. A short document and a long document about the same topic might have vectors of very different lengths (magnitudes), but they'll point in roughly the same direction. Measuring the angle between them correctly identifies them as similar even though their vector lengths differ.

cosine similarity angle between vectors NLP intuition Cosine similarity measures the angle between two vectors, not the distance between their tips

The Formula

Cosine similarity between two vectors A and B is calculated as:

cosine similarity = (A · B) / (|A| × |B|)

In plain terms: - A · B is the dot product: multiply each pair of corresponding numbers and add them all up - |A| and |B| are the lengths (magnitudes) of each vector: the square root of the sum of squared values - Dividing by the lengths normalises for vector size, so only direction matters

The result is always a number between -1 and 1: - 1.0 means the vectors point in exactly the same direction (identical meaning) - 0.0 means the vectors are perpendicular (no relationship) - -1.0 means the vectors point in opposite directions (opposite meaning)

In practice with word and sentence embeddings, you rarely see negative values. Most scores fall between 0 and 1.

Cosine Similarity in Python

from sklearn.metrics.pairwise import cosine_similarity
from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer('all-MiniLM-L6-v2')

sentences = [
    "The cat sat on the mat.",
    "A kitten rested on the rug.",
    "Machine learning is a subset of AI.",
    "I enjoy cooking Italian food."
]

embeddings = model.encode(sentences)
similarity_matrix = cosine_similarity(embeddings)

print(similarity_matrix.round(2))

Output:

[[1.   0.71 0.06 0.09]
 [0.71 1.   0.04 0.11]
 [0.06 0.04 1.   0.05]
 [0.09 0.11 0.05 1.  ]]

Sentences 1 and 2 score 0.71 despite sharing almost no keywords. "Cat" and "kitten" are synonyms. "Sat" and "rested" are related. "Mat" and "rug" are similar objects. The embeddings have captured all of that, and the cosine similarity score reflects it. Sentences 1 and 3, about completely different topics, score 0.06.

Manual Calculation

If you want to understand exactly what's happening, here's the same calculation done manually on two simple two-dimensional vectors:

import numpy as np

# Two simple 2D vectors
a = np.array([1, 2])
b = np.array([2, 3])

# Dot product
dot_product = np.dot(a, b)           # 1*2 + 2*3 = 8

# Magnitudes
magnitude_a = np.linalg.norm(a)      # sqrt(1^2 + 2^2) = sqrt(5) ≈ 2.236
magnitude_b = np.linalg.norm(b)      # sqrt(2^2 + 3^2) = sqrt(13) ≈ 3.606

# Cosine similarity
cos_sim = dot_product / (magnitude_a * magnitude_b)
print(f"Cosine similarity: {cos_sim:.4f}")
# Output: Cosine similarity: 0.9923

Two vectors pointing in very similar directions. Score of 0.99: nearly identical.

What Is Cosine Distance?

Cosine distance is simply 1 minus cosine similarity:

cosine distance = 1 - cosine similarity

It converts the similarity score into a distance score. High similarity (close to 1) becomes low distance (close to 0). Low similarity becomes high distance.

The two terms describe the same relationship from opposite angles. Similarity asks "how alike are these?" Distance asks "how far apart are these?" You'll see both used in different libraries and papers, but they're measuring the same thing.

Euclidean Distance

Euclidean distance is the straight-line distance between two points in space. It's the most natural notion of distance, the kind you learned in school.

For two points in 2D space, it's the length of the line connecting them. In higher dimensions (like the 768-dimensional space of BERT embeddings), the same formula extends naturally: take the difference between each pair of corresponding numbers, square them, add them all up, and take the square root.

import numpy as np

a = np.array([1, 2])
b = np.array([4, 6])

euclidean_distance = np.linalg.norm(a - b)
print(f"Euclidean distance: {euclidean_distance:.4f}")
# Output: Euclidean distance: 5.0000

Cosine Similarity vs Euclidean Distance

So which one should you use?

For NLP tasks with word and sentence embeddings, cosine similarity almost always performs better. Here's why.

Imagine you have two documents about machine learning. One is a short tweet (50 words) and one is a long article (2000 words). Both are about the same topic, so their embedding vectors should point in roughly the same direction. But the article's vector might be much longer (larger magnitude) simply because it represents more text.

Cosine similarity ignores magnitude and only looks at direction. It correctly identifies both documents as similar. Euclidean distance would penalise the difference in vector length and potentially rate them as dissimilar just because one is longer.

Use Euclidean distance when the magnitude of vectors genuinely carries information you care about. In most NLP tasks, it doesn't.

cosine similarity vs euclidean distance NLP comparison Cosine similarity measures direction while Euclidean distance measures the straight-line gap between vector endpoints

Jaccard Similarity

Jaccard similarity takes a completely different approach. Instead of comparing vectors of numbers, it compares sets of items.

The formula is simple: the size of the overlap between two sets divided by the size of their union.

Jaccard similarity = |A ∩ B| / |A ∪ B|

In plain terms: how many items do these two sets share, divided by the total number of unique items across both sets?

def jaccard_similarity(set_a, set_b):
    intersection = set_a & set_b
    union = set_a | set_b
    return len(intersection) / len(union)

doc1 = set(["the", "cat", "sat", "on", "the", "mat"].split() if isinstance("the", str) else ["the", "cat", "sat", "on", "mat"])
doc2 = set(["the", "cat", "sat", "on", "the", "hat"])
doc3 = set(["the", "dog", "ran", "in", "the", "park"])

doc1_words = {"the", "cat", "sat", "on", "mat"}
doc2_words = {"the", "cat", "sat", "on", "hat"}
doc3_words = {"the", "dog", "ran", "in", "park"}

print(f"Doc1 vs Doc2: {jaccard_similarity(doc1_words, doc2_words):.2f}")
print(f"Doc1 vs Doc3: {jaccard_similarity(doc1_words, doc3_words):.2f}")

Output:

Doc1 vs Doc2: 0.67
Doc1 vs Doc3: 0.17

Documents 1 and 2 share "the," "cat," "sat," "on" (4 words) out of 6 unique words total, giving 4/6 ≈ 0.67. Documents 1 and 3 only share "the" (1 word) out of 9 unique words, giving 1/9 ≈ 0.11.

Jaccard similarity is useful for comparing short texts, sets of tags or labels, or any situation where you care about overlap between items rather than semantic similarity. It's commonly used in deduplication (finding near-duplicate documents) and in recommendation systems that compare sets of user preferences. It works purely on word overlap and knows nothing about meaning, so it won't help you find that "automobile" and "car" are related.

Edit Distance and Levenshtein Distance

So far, all the methods we've looked at operate on whole words or vectors. Edit distance operates at the character level, which makes it useful for a completely different set of problems.

Edit distance (also called Levenshtein distance, after the Soviet mathematician Vladimir Levenshtein who defined it in 1965) measures how many single-character operations it takes to turn one string into another. The three allowed operations are:

  • Insertion: add a character
  • Deletion: remove a character
  • Substitution: replace one character with another
# Python has no built-in edit distance, but it's straightforward to compute
# or use the python-Levenshtein library

def levenshtein_distance(s1, s2):
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(m + 1):
        dp[i][0] = i
    for j in range(n + 1):
        dp[0][j] = j

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1]
            else:
                dp[i][j] = 1 + min(dp[i-1][j],    # deletion
                                   dp[i][j-1],    # insertion
                                   dp[i-1][j-1])  # substitution
    return dp[m][n]

print(levenshtein_distance("kitten", "sitting"))   # 3
print(levenshtein_distance("sunday", "saturday"))  # 3
print(levenshtein_distance("cat", "cat"))           # 0
print(levenshtein_distance("cat", "car"))           # 1

Output:

3
3
0
1

"Kitten" to "sitting" takes 3 operations: substitute k with s, substitute e with i, insert g at the end. "Cat" to "car" takes 1 operation: substitute t with r.

When Is Edit Distance Useful?

Edit distance is not for measuring semantic similarity. It has no idea what words mean. "Cat" and "feline" are semantically very similar but have a high edit distance. "Cat" and "car" are semantically unrelated but have an edit distance of just 1.

What edit distance is excellent for:

Spell checking and autocorrect. When someone types "recieve," a spell checker computes edit distance to all dictionary words and suggests "receive" (edit distance 1) as the most likely correction.

Fuzzy string matching. Searching a database for "Smithe, John" when the record says "Smith, John" requires tolerating small character-level differences. Edit distance makes this possible.

DNA sequence alignment. Bioinformatics uses edit distance to measure how similar two genetic sequences are, which is one of its most important applications outside NLP.

Handling typos in user input. Before showing "no results found," a search system can check if the query is close (by edit distance) to known valid terms and suggest the correction.

Choosing the Right Similarity Metric

Here's a practical summary of when to reach for each method:

MetricOperates onBest for
Cosine similarityVectorsSemantic similarity, embedding comparison, semantic search
Euclidean distanceVectorsClustering when magnitude matters
Jaccard similaritySets of wordsText overlap, deduplication, tag comparison
Edit distance / LevenshteinCharacter stringsSpell checking, fuzzy matching, typo tolerance

The most important takeaway: cosine similarity is the default for anything involving embeddings. When you're working with word vectors, sentence vectors, or document vectors from any modern NLP model, cosine similarity is almost always the right choice.

The other metrics are not inferior. They're designed for different problems. Edit distance is the right tool for spell checking in a way that cosine similarity never could be. Jaccard is perfectly suited for comparing sets of tags. Understanding what each one measures helps you pick the right one.

A Complete Semantic Search Example

Let's put cosine similarity to work in a minimal semantic search system: given a query, find the most relevant documents from a small collection.

from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np

model = SentenceTransformer('all-MiniLM-L6-v2')

# Our document collection
documents = [
    "Python is a popular programming language for data science.",
    "Machine learning models learn patterns from training data.",
    "Neural networks are inspired by the structure of the human brain.",
    "Deep learning is a subset of machine learning using many layers.",
    "Natural language processing helps computers understand text.",
    "I enjoy hiking in the mountains on weekends.",
    "The best pasta recipe uses fresh tomatoes and basil."
]

# Encode documents
doc_embeddings = model.encode(documents)

def semantic_search(query, top_k=3):
    query_embedding = model.encode([query])
    scores = cosine_similarity(query_embedding, doc_embeddings)[0]
    top_indices = np.argsort(scores)[::-1][:top_k]

    print(f"Query: '{query}'\n")
    for i, idx in enumerate(top_indices):
        print(f"  {i+1}. Score: {scores[idx]:.3f} | {documents[idx]}")
    print()

semantic_search("how do AI systems learn from examples?")
semantic_search("what is NLP?")
semantic_search("outdoor activities")

Output:

Query: 'how do AI systems learn from examples?'

  1. Score: 0.612 | Machine learning models learn patterns from training data.
  2. Score: 0.521 | Deep learning is a subset of machine learning using many layers.
  3. Score: 0.489 | Neural networks are inspired by the structure of the human brain.

Query: 'what is NLP?'

  1. Score: 0.671 | Natural language processing helps computers understand text.
  2. Score: 0.412 | Machine learning models learn patterns from training data.
  3. Score: 0.387 | Deep learning is a subset of machine learning using many layers.

Query: 'outdoor activities'

  1. Score: 0.482 | I enjoy hiking in the mountains on weekends.
  2. Score: 0.201 | Python is a popular programming language for data science.
  3. Score: 0.187 | Natural language processing helps computers understand text.

Notice that the query "how do AI systems learn from examples?" correctly retrieves the machine learning document without sharing a single keyword with it. The query uses "AI systems" and "learn from examples." The document says "machine learning models" and "patterns from training data." BM25 would score this at zero. Cosine similarity on sentence embeddings scores it at 0.612.

That's the power of embedding-based retrieval.

What Comes Next

You now understand how NLP systems measure similarity: cosine similarity for comparing vectors, Euclidean distance for straight-line gaps, Jaccard for set overlap, and edit distance for character-level matching.

These tools are the foundation of the next set of topics. In the next post, we'll cover Sentiment Analysis: how NLP systems automatically detect whether a piece of text is positive, negative, or neutral, and where that capability is used at scale in the real world.

Learn This at Fondra Labs

This post is part of our NLP Foundations series, where we build up practical AI knowledge one concept at a time, from text processing basics all the way to the systems powering modern AI.

At Fondra Labs, we teach AI from production reality, not hype. Every topic in this series is here because it genuinely matters when you sit down to build something real.

If this was useful, explore the rest of the blog. We cover machine learning, deep learning, NLP, and the practical skills that turn understanding into building.

Frequently Asked Questions

What is cosine similarity?
Cosine similarity is a way of measuring how similar two vectors are by calculating the angle between them. A score of 1 means the vectors point in exactly the same direction (very similar). A score of 0 means they are unrelated. It is the most widely used similarity metric in NLP because it correctly compares vectors regardless of their length, making it ideal for comparing word and sentence embeddings.
What is cosine distance?
Cosine distance is 1 minus cosine similarity. It converts a similarity score into a distance score: high similarity becomes low distance, and low similarity becomes high distance. The two terms describe the same relationship from opposite perspectives. You will see both used in different libraries and papers.
What is Euclidean distance?
Euclidean distance is the straight-line distance between two points in space. It is calculated by taking the difference between each pair of corresponding numbers in two vectors, squaring them, adding them up, and taking the square root. For most NLP tasks involving embeddings, cosine similarity is preferred because it is not affected by the length of the vectors.
What is Jaccard similarity?
Jaccard similarity measures the overlap between two sets. It divides the number of items the two sets share by the total number of unique items across both sets. In NLP, it is used to compare documents based on shared words, detect near-duplicate content, or compare sets of tags and labels. It does not capture semantic meaning, only word overlap.
What is edit distance?
Edit distance measures how many single-character operations (insertions, deletions, substitutions) are needed to turn one string into another. A low edit distance means two strings are very similar character by character. It is used in spell checkers, fuzzy string matching, and typo correction rather than semantic similarity tasks.
What is Levenshtein distance?
Levenshtein distance is the most common form of edit distance, named after the Soviet mathematician Vladimir Levenshtein who defined it in 1965. It counts the minimum number of insertions, deletions, and substitutions needed to transform one string into another. Levenshtein distance and edit distance are often used interchangeably.
What is vector similarity?
Vector similarity is the general term for any method that compares two vectors and produces a score representing how alike they are. Cosine similarity, Euclidean distance, and dot product similarity are all forms of vector similarity. In NLP, vector similarity is used to compare word embeddings, sentence embeddings, and document embeddings to find related content.
How to calculate cosine similarity in Python?
The simplest way is using scikit-learn: import cosine_similarity from sklearn.metrics.pairwise, then pass two arrays of vectors. For comparing sentence embeddings, encode your sentences with a model like SentenceTransformer, then call cosine_similarity on the resulting embeddings array. For a single pair of vectors, numpy's dot product divided by the product of norms gives the same result.
Javier Aguirre

Javier Aguirre

Teaching AI from production reality, not hype.

Build your AI Foundations

Get the free checklist that production engineers use to avoid these common RAG pitfalls.

Get the Free Guide