Bird
Raised Fist0
NlpProgramBeginner · 3 min read

NLP Program to Find Similar Documents Using Cosine Similarity

Use TF-IDF vectorizer to convert documents into vectors and cosine similarity to find similarity scores; for example, use sklearn.feature_extraction.text.TfidfVectorizer and sklearn.metrics.pairwise.cosine_similarity to find similar documents.
📋

Examples

Input["I love machine learning", "Machine learning is fun", "I enjoy sports"]
Output[[1.0, 0.79, 0.0], [0.79, 1.0, 0.0], [0.0, 0.0, 1.0]]
Input["Cats are great pets", "Dogs are loyal animals", "I have a pet cat"]
Output[[1.0, 0.0, 0.56], [0.0, 1.0, 0.0], [0.56, 0.0, 1.0]]
Input["", "", ""]
Output[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]
🧠

How to Think About It

To find similar documents, first convert each document into a number form that captures its meaning using TF-IDF vectorization. Then, compare these number forms using cosine similarity, which measures how close the documents are in meaning by checking the angle between their vectors.
📐

Algorithm

1
Collect the list of documents to compare.
2
Convert documents into TF-IDF vectors to represent their content numerically.
3
Calculate cosine similarity between each pair of document vectors.
4
Return the similarity scores as a matrix showing how similar each document is to the others.
💻

Code

python
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

documents = [
    "I love machine learning",
    "Machine learning is fun",
    "I enjoy sports"
]

vectorizer = TfidfVectorizer()
tfidf_matrix = vectorizer.fit_transform(documents)
similarity_matrix = cosine_similarity(tfidf_matrix)

print(similarity_matrix)
Output
[[1. 0.78980693 0. ] [0.78980693 1. 0. ] [0. 0. 1. ]]
🔍

Dry Run

Let's trace the example documents through the code

1

Input documents

["I love machine learning", "Machine learning is fun", "I enjoy sports"]

2

TF-IDF vectorization

Convert each document into a vector representing word importance, e.g., doc1 vector might be [0.5, 0.7, 0.0, ...]

3

Calculate cosine similarity

Compute similarity scores between each pair of vectors, resulting in a 3x3 matrix.

4

Output similarity matrix

[[1.0, 0.79, 0.0], [0.79, 1.0, 0.0], [0.0, 0.0, 1.0]]

Document PairSimilarity Score
Doc1 & Doc11.0
Doc1 & Doc20.79
Doc1 & Doc30.0
Doc2 & Doc21.0
Doc2 & Doc30.0
Doc3 & Doc31.0
💡

Why This Works

Step 1: TF-IDF Vectorization

TF-IDF converts text into numbers that show how important each word is in a document compared to all documents, helping capture meaning.

Step 2: Cosine Similarity Calculation

Cosine similarity measures the angle between two vectors; smaller angles mean more similar documents.

Step 3: Similarity Matrix Output

The matrix shows similarity scores between all document pairs, with 1 meaning identical and 0 meaning no similarity.

🔄

Alternative Approaches

Using Word Embeddings and Average Vectors
python
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# Example word vectors
word_vectors = {
    'machine': np.array([1, 0]),
    'learning': np.array([0.9, 0.1]),
    'fun': np.array([0, 1]),
    'love': np.array([0.8, 0.2]),
    'enjoy': np.array([0.7, 0.3]),
    'sports': np.array([0, 0.9])
}

def document_vector(doc):
    words = doc.lower().split()
    vectors = [word_vectors[w] for w in words if w in word_vectors]
    if vectors:
        return np.mean(vectors, axis=0)
    else:
        return np.zeros(2)

docs = ["I love machine learning", "Machine learning is fun", "I enjoy sports"]
vectors = np.array([document_vector(d) for d in docs])
sim_matrix = cosine_similarity(vectors)
print(sim_matrix)
This method uses word embeddings averaged per document; it captures semantic similarity but needs pre-trained vectors and may be less precise than TF-IDF for small datasets.
Using Count Vectorizer and Jaccard Similarity
python
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

docs = ["I love machine learning", "Machine learning is fun", "I enjoy sports"]
vectorizer = CountVectorizer(binary=True)
binary_matrix = vectorizer.fit_transform(docs).toarray()

# Jaccard similarity function
def jaccard_similarity(x, y):
    intersection = np.sum(np.minimum(x, y))
    union = np.sum(np.maximum(x, y))
    return intersection / union if union != 0 else 0

size = len(docs)
sim_matrix = np.zeros((size, size))
for i in range(size):
    for j in range(size):
        sim_matrix[i, j] = jaccard_similarity(binary_matrix[i], binary_matrix[j])

print(sim_matrix)
Jaccard similarity compares shared words presence ignoring frequency; simpler but less sensitive to word importance.

Complexity: O(n^2 * m) time, O(n * m) space

Time Complexity

Calculating TF-IDF vectors takes O(n * m) where n is documents and m is vocabulary size; computing pairwise cosine similarity is O(n^2 * m) because each pair of documents is compared.

Space Complexity

Storing TF-IDF vectors requires O(n * m) space; similarity matrix requires O(n^2) space.

Which Approach is Fastest?

TF-IDF with cosine similarity is efficient for moderate datasets; word embeddings add overhead loading vectors; Jaccard similarity is simpler but less precise.

ApproachTimeSpaceBest For
TF-IDF + Cosine SimilarityO(n^2 * m)O(n * m)Balanced accuracy and speed for text similarity
Word Embeddings Average + CosineO(n * l + n^2 * d)O(n * d)Semantic similarity with pre-trained vectors
Count Vectorizer + JaccardO(n^2 * m)O(n * m)Simple presence-based similarity, less precise
💡
Use TF-IDF with cosine similarity for a simple and effective way to find similar documents.
⚠️
Beginners often forget to preprocess text or use raw text directly without vectorization, which prevents similarity calculation.