Collaborative filtering recommends items based on what similar users liked. Instead of analyzing item content, it finds patterns in user behavior - "users who bought X also bought Y". This simple idea powers recommendations at Netflix, Spotify, Amazon, and most modern platforms.
Where It Is Used
Collaborative filtering is the backbone of personalization:
- Streaming: Netflix, Spotify, YouTube recommendations
- E-commerce: Amazon "customers also bought", product recommendations
- Social: Facebook friend suggestions, Twitter "who to follow"
- Content: News article recommendations, blog suggestions
- Jobs: LinkedIn job recommendations, candidate matching
- Ads: Personalized ad targeting
Products: Netflix recommendation engine, Spotify Discover Weekly, Amazon recommendations.
Typical Data You Will See
| Aspect | Typical Values |
|---|---|
| Users | 100K to 100M |
| Items | 10K to 10M |
| Interactions | 10M to 10B |
| Sparsity | 99% to 99.99% sparse |
| Embedding dimension | 32 to 256 |
Data types: - Explicit feedback: Ratings (1-5 stars), likes/dislikes - Implicit feedback: Views, clicks, purchases, time spent - User features: Demographics, preferences - Item features: Categories, descriptions, metadata
What You Do in Practice
- Define interactions - What user actions indicate interest?
- Build interaction matrix - User-item sparse matrix
- Handle implicit feedback - Convert actions to signals
- Choose algorithm - Matrix factorization, neural, or hybrid
- Train model - Optimize for ranking or rating prediction
- Generate candidates - Approximate nearest neighbors for items
- Add business rules - Diversity, freshness, inventory
- Evaluate offline - Recall, NDCG on held-out data
- A/B test online - CTR, engagement, conversions
- Handle cold start - New users and items
Baseline First
Start with popularity-based recommendations:
# Popularity baseline
def popularity_recommend(n=10):
"""Recommend most popular items."""
item_popularity = interactions.groupby('item_id').size()
return item_popularity.nlargest(n).index.tolist()
# User-based co-occurrence
def cooccurrence_recommend(user_id, n=10):
"""Items frequently bought with user's items."""
user_items = set(interactions[interactions.user_id == user_id].item_id)
# Find users with similar items
similar_users = interactions[
interactions.item_id.isin(user_items)
].user_id.unique()
# Their other items
candidates = interactions[
(interactions.user_id.isin(similar_users)) &
(~interactions.item_id.isin(user_items))
]
return candidates.item_id.value_counts().head(n).index.tolist()
Why popularity baseline: - Simple and fast - Reasonable for new users - Establishes minimum quality bar - Helps measure personalization lift
Algorithm Approaches
| Approach | Pros | Cons | When to Use |
|---|---|---|---|
| Matrix Factorization | Fast, interpretable | Limited expressiveness | Most cases, good starting point |
| Neural CF | Captures complex patterns | Needs more data, slower | Large scale, complex patterns |
| Item-based CF | Easy to explain | Doesn't personalize deeply | Item similarity focus |
| Graph-based | Captures relationships | Complex to scale | Social networks, knowledge graphs |
Matrix Factorization with ALS:
from implicit.als import AlternatingLeastSquares
from scipy.sparse import csr_matrix
# Build sparse user-item matrix
user_item = csr_matrix((
interactions.rating,
(interactions.user_idx, interactions.item_idx)
))
# Train ALS model
model = AlternatingLeastSquares(
factors=64,
regularization=0.01,
iterations=15
)
model.fit(user_item)
# Get recommendations for user
user_idx = 0
recommendations = model.recommend(
user_idx,
user_item[user_idx],
N=10,
filter_already_liked_items=True
)
Neural Collaborative Filtering:
import torch
import torch.nn as nn
class NCF(nn.Module):
def __init__(self, n_users, n_items, embedding_dim=64):
super().__init__()
self.user_embed = nn.Embedding(n_users, embedding_dim)
self.item_embed = nn.Embedding(n_items, embedding_dim)
self.mlp = nn.Sequential(
nn.Linear(embedding_dim * 2, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid()
)
def forward(self, user_ids, item_ids):
user_emb = self.user_embed(user_ids)
item_emb = self.item_embed(item_ids)
concat = torch.cat([user_emb, item_emb], dim=1)
return self.mlp(concat)
Implicit Feedback Handling
Most production systems use implicit feedback (clicks, views) not explicit ratings:
# Convert implicit to training signal
def prepare_implicit_data(interactions):
"""Convert raw interactions to training pairs."""
# Positive samples: actual interactions
positives = interactions[['user_id', 'item_id']].copy()
positives['label'] = 1
# Negative sampling: items user didn't interact with
all_items = set(interactions.item_id.unique())
negatives = []
for user_id in interactions.user_id.unique():
user_items = set(interactions[
interactions.user_id == user_id
].item_id)
neg_items = random.sample(
list(all_items - user_items),
k=len(user_items) * 4 # 4:1 negative ratio
)
for item_id in neg_items:
negatives.append({
'user_id': user_id,
'item_id': item_id,
'label': 0
})
return pd.concat([positives, pd.DataFrame(negatives)])
Weighting schemes: - Uniform: All interactions equal - Frequency: Weight by interaction count - Recency: Recent interactions weighted higher - Confidence: Weight by interaction strength
Evaluation and Metrics
| Metric | What It Measures | Formula |
|---|---|---|
| Recall@k | Coverage of relevant items | Relevant in top-k / Total relevant |
| Precision@k | Accuracy of top-k | Relevant in top-k / k |
| NDCG@k | Ranking quality | Normalized discounted cumulative gain |
| MAP | Average precision | Mean of precision at each relevant item |
| Hit Rate@k | Any relevant in top-k | Users with hit / Total users |
Evaluation setup:
def evaluate_model(model, test_data, k=10):
"""Evaluate recommendation model."""
metrics = {'recall': [], 'ndcg': [], 'hit_rate': []}
for user_id in test_data.user_id.unique():
# Ground truth: items user interacted with in test
true_items = set(test_data[
test_data.user_id == user_id
].item_id)
# Model predictions
recs = model.recommend(user_id, k)
# Recall@k
hits = len(set(recs) & true_items)
metrics['recall'].append(hits / len(true_items))
# Hit rate
metrics['hit_rate'].append(1 if hits > 0 else 0)
# NDCG@k
metrics['ndcg'].append(compute_ndcg(recs, true_items, k))
return {k: np.mean(v) for k, v in metrics.items()}
Failure Modes and Debugging Checklist
- [ ] Cold start: No recommendations for new users/items
- [ ] Popularity bias: Always recommending popular items
- [ ] Filter bubbles: Recommendations too homogeneous
- [ ] Sparsity: Not enough interactions for meaningful patterns
- [ ] Stale recommendations: Not updating with new behavior
- [ ] Position bias: Training on biased feedback
- [ ] Leakage: Using future data in training
Production Checklist
Before deployment: - [ ] Offline metrics significantly beat popularity baseline - [ ] Cold start strategy defined (content-based fallback) - [ ] Candidate generation pipeline built (ANN index) - [ ] Business rules integrated (diversity, freshness) - [ ] Latency meets SLA (typically < 100ms)
After deployment: - [ ] Online metrics tracked (CTR, engagement, conversions) - [ ] Model refresh schedule established (daily/weekly) - [ ] A/B tests running vs baseline - [ ] Diversity metrics monitored - [ ] Cold start performance tracked separately
Scaling considerations: - [ ] Embedding index (Faiss, ScaNN) for fast retrieval - [ ] Incremental training for new interactions - [ ] Feature caching for user/item features - [ ] Batch vs real-time recommendation trade-offs
Cold Start Solutions
| Problem | Solution |
|---|---|
| New user | Popular items, demographic-based, content-based |
| New item | Content-based similarity, boost in recommendations |
| Both new | Global popular, category popular |
Hybrid approach:
def recommend(user_id, n=10):
"""Hybrid recommendation with cold start handling."""
user_interactions = get_user_interactions(user_id)
if len(user_interactions) == 0:
# Cold start: popularity + content-based
return popularity_recommend(n)
elif len(user_interactions) < 5:
# Warm start: blend CF with content
cf_recs = collaborative_recommend(user_id, n)
content_recs = content_based_recommend(user_id, n)
return blend(cf_recs, content_recs, alpha=0.5)
else:
# Full CF
return collaborative_recommend(user_id, n)
Tools and Stack
| Category | Tools |
|---|---|
| Matrix factorization | Implicit, Surprise, LightFM |
| Deep learning | PyTorch, TensorFlow Recommenders |
| ANN search | Faiss, ScaNN, Annoy, Milvus |
| Feature stores | Feast, Tecton |
| Platforms | AWS Personalize, Google Recommendations AI |
Example Mini-Case
Scenario: E-commerce site with 2M users, 500K products, 50M purchase events wants personalized recommendations.
Approach: 1. Baseline: Popular items - Recall@10: 0.08 2. Data prep: Purchase events with recency weighting 3. Model: ALS matrix factorization (64 factors) 4. Serving: Faiss index for fast candidate retrieval 5. Reranking: Add diversity and freshness rules
Results: - Recall@10: 0.08 → 0.22 (+175%) - A/B test CTR: +18% - Revenue per user: +12% - Latency: 45ms p99
Key learnings: - Recency weighting critical (recent purchases 3x weight) - Cold start users needed category-based fallback - Diversity constraint improved long-term engagement
Related Pages
- Index: ML Field Guide
- Learning to Rank
- Semantic Search Systems
- Feature Stores
Further Reading
- Matrix Factorization for Recommender Systems
- Implicit Feedback Collaborative Filtering
- Neural Collaborative Filtering
- Two-Tower Models for Recommendations
Try it live
Everything above runs in your browser — open Recommendation Systems: Collaborative Filtering, Content-Based & Two-Tower Retrieval and change the parameters while it is running. Nothing is installed, nothing is uploaded, the whole model lives in one tab.
▶ Open Recommendation Systems: Collaborative Filtering, Content-Based & Two-Tower Retrieval simulation