What if a search engine could understand that these two sentences are related?
"I love programming"
"I enjoy coding"
They don't use the exact same words, but their meanings are close.
That's the basic idea behind semantic search.
In this tutorial, we'll build a tiny semantic search engine in Python using:
- Sentence Transformers
- Embeddings
- Cosine Similarity
- scikit-learn
No database.
No RAG.
No complicated architecture.
Just the core idea.
Step 1 — Install the Libraries
pip install sentence-transformers scikit-learn
Then import them:
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
Step 2 — Load an Embedding Model
model = SentenceTransformer("all-MiniLM-L6-v2")
This model converts text into numerical embeddings.
Think of the process as:
"I love programming"
↓
Embedding Model
↓
[0.02, -0.14, 0.08, ...]
The sentence has now been represented by numbers.
Step 3 — Create Some Sentences
sentences = [
"I love programming",
"I enjoy coding",
"The weather is very hot"
]
Generate their embeddings:
embeddings = model.encode(sentences)
print(embeddings.shape)
You'll get something similar to:
(3, 384)
So we have:
3 sentences
384 numbers for each sentence
Step 4 — Compare the Sentences
Now let's calculate their similarity:
similarity = cosine_similarity(embeddings)
print(similarity)
You might see:
[
[1.00, 0.63, 0.12],
[0.63, 1.00, 0.22],
[0.12, 0.22, 1.00]
]
Remember our sentences:
0 → I love programming
1 → I enjoy coding
2 → The weather is very hot
So:
similarity[0][1]
means:
"I love programming"
↕
"I enjoy coding"
and gives approximately:
0.63
That's considerably higher than the similarity between programming and weather.
Step 5 — Let's Build an Actual Search
Now we'll create a few documents:
documents = [
"Python is a programming language.",
"Machine learning is a branch of artificial intelligence.",
"The weather is very hot today.",
"Football is a popular sport."
]
Our user will search for:
query = "I want to learn coding"
The interesting question is:
Which document is closest to this query?
Step 6 — Embed the Documents
document_embeddings = model.encode(documents)
Each document now has its own embedding.
Step 7 — Embed the Query
query_embedding = model.encode([query])
Now we have:
Query → Embedding
Documents → Embeddings
Step 8 — Calculate Similarity
scores = cosine_similarity(
query_embedding,
document_embeddings
)[0]
print(scores)
You might get:
[0.70, 0.55, 0.12, 0.08]
The exact numbers can vary.
But the interpretation is:
Python 0.70
Machine learning 0.55
Weather 0.12
Football 0.08
The Python document is the closest match to our query.
Step 9 — Rank the Search Results
Let's make the output easier to read:
results = sorted(
zip(scores, documents),
reverse=True
)
for score, document in results:
print(f"{score:.3f} - {document}")
Example:
0.700 - Python is a programming language.
0.550 - Machine learning is a branch of artificial intelligence.
0.120 - The weather is very hot today.
0.080 - Football is a popular sport.
Congratulations.
You just built a tiny semantic search engine.
The Complete Code
from sentence_transformers import SentenceTransformer
from sklearn.metrics.pairwise import cosine_similarity
model = SentenceTransformer("all-MiniLM-L6-v2")
documents = [
"Python is a programming language.",
"Machine learning is a branch of artificial intelligence.",
"The weather is very hot today.",
"Football is a popular sport."
]
query = "I want to learn coding"
document_embeddings = model.encode(documents)
query_embedding = model.encode([query])
scores = cosine_similarity(
query_embedding,
document_embeddings
)[0]
results = sorted(
zip(scores, documents),
reverse=True
)
for score, document in results:
print(f"{score:.3f} - {document}")
What Did We Actually Build?
The architecture is surprisingly simple:
Query
↓
Embedding
↓
┌────────┴────────┐
↓ ↓ ↓
Document Document Document
Embedding Embedding Embedding
└────────┬────────┘
↓
Cosine Similarity
↓
Similarity Scores
↓
Ranking
↓
Search Results
Why Not Just Search for Keywords?
Suppose the user searches:
"I want to learn coding"
But the document says:
"Python is a programming language."
There may be no exact match for the word coding.
Semantic search doesn't depend only on exact words.
The embedding model creates representations that allow us to compare the meaning of the texts.
That's why:
coding
and:
programming
can produce relatively similar embeddings.
Three Concepts to Remember
If you're learning NLP or modern AI systems, keep these three concepts separate.
1. Embedding
Turns text into numbers.
Text → Numbers
2. Cosine Similarity
Compares two embeddings.
Embedding A
+
Embedding B
↓
Similarity Score
3. Semantic Search
Uses those similarities to find relevant information.
Query
↓
Embedding
↓
Compare
↓
Rank
↓
Results
Where Do We Go From Here?
This example is intentionally small.
Real applications may contain thousands or millions of documents, so calculating similarity against every document isn't always efficient.
That's where techniques such as vector databases and approximate nearest-neighbor search become useful.
And when retrieved documents are passed to a language model to help answer a user's question, we enter the world of Retrieval-Augmented Generation (RAG).
But before moving there, make sure this basic pipeline is clear:
Text
↓
Embedding
↓
Similarity
↓
Search
Once this clicks, the more advanced systems become much easier to understand.
LinkedIn: https://www.linkedin.com/in/taha-hussein-b0a583425/
Top comments (0)