Why Not Just Use Cosine Similarity?
Brute-force cosine similarity is O(nยทd) per query โ fine for 1,000 items, painful for 100,000. FAISS (Facebook AI Similarity Search) uses approximate nearest-neighbor algorithms to achieve sub-millisecond search at scale.
Building the TF-IDF Index
from sklearn.feature_extraction.text import TfidfVectorizer
import faiss
import numpy as np
def build_job_corpus(jobs: list[dict]) -> list[str]:
return [
f"{j['title']} {j['description']} {' '.join(j['required_skills'])}"
for j in jobs
]
corpus = build_job_corpus(jobs)
vectorizer = TfidfVectorizer(max_features=5000, stop_words="english")
tfidf_matrix = vectorizer.fit_transform(corpus).toarray().astype("float32")
# L2-normalize for cosine similarity via inner product
faiss.normalize_L2(tfidf_matrix)
index = faiss.IndexFlatIP(tfidf_matrix.shape[1])
index.add(tfidf_matrix)
Querying for Recommendations
def recommend(user_profile: str, k: int = 10) -> list[int]:
query_vec = vectorizer.transform([user_profile]).toarray().astype("float32")
faiss.normalize_L2(query_vec)
scores, indices = index.search(query_vec, k)
return indices[0].tolist()
The user profile is constructed from:
- Job titles from interaction history (apply, save, view)
- Self-reported skills
- Location preferences
Proximity Sorting
For the "sort by proximity" feature, I combined the FAISS similarity score with a geographic distance score:
combined_score = alpha * relevance_score + (1 - alpha) * proximity_score
with alpha=0.7 as the default (relevance-first).
Performance Results
| Method | 10k jobs | 100k jobs | |--------|----------|-----------| | Brute-force cosine | 18ms | 182ms | | FAISS IVFFlat | 1.2ms | 2.1ms |
~90x speedup at 100k scale with <1% accuracy loss.