What Is a Recommendation System?
A recommendation system predicts items a user is likely to interact with (click, buy, watch, listen to) based on historical interactions and/or item/user attributes.
Core Problem Formulation
Given:
- A set of users
- A set of items
- A sparse interaction matrix where = rating/signal (or 0 if unknown)
Goal: predict for all unknown entries, then recommend top- items per user.
Types of Feedback
| Feedback Type | Examples | Challenge |
|---|---|---|
| Explicit | Star ratings, thumbs up/down | Sparse — users rarely rate things |
| Implicit | Clicks, views, purchases, time spent | Noisy — absence ≠ dislike |
Most production systems work with implicit feedback — it's abundant but requires careful modeling (unobserved ≠ negative).
Three Main Approaches
┌─────────────────────────────────────────────────────────┐
│ Collaborative Filtering │ Content-Based │ Hybrid │
│ (who liked what) │ (what it is) │ (both) │
└─────────────────────────────────────────────────────────┘
Collaborative Filtering
User-Based Collaborative Filtering
Find users similar to the target user, then recommend items they liked:
Common similarity measures:
- Cosine similarity:
- Pearson correlation: accounts for user rating bias (some users always rate high)
Item-Based Collaborative Filtering
Find items similar to items the user already liked:
Item-based CF is preferred in production — item similarities are stable over time, can be precomputed offline. User similarities change frequently and must be recomputed.
Memory-Based vs Model-Based
| Memory-Based (KNN) | Model-Based (Matrix Factorization) | |
|---|---|---|
| How it works | Directly compute similarities on raw data | Learn a compressed model from interaction matrix |
| Scalability | Poor — per query | Good — per query after training |
| Cold start | Fails — needs prior interactions | Same problem but can incorporate side info |
| Examples | User-KNN, Item-KNN | SVD, ALS, Neural CF |
Matrix Factorization
Matrix Factorization (MF) decomposes the sparse interaction matrix into two low-rank dense matrices:
where:
- — user embeddings ( users, latent factors)
- — item embeddings ( items, latent factors)
- — predicted rating = dot product of user and item vectors
The latent factors capture hidden preferences (genre preference, quality sensitivity, etc.) without explicit labels.
Training Objective (with Regularization)
The regularization term prevents overfitting to the sparse observed ratings.
SVD vs ALS vs SGD
| Method | Full Name | Best For |
|---|---|---|
| SVD (Funk SVD) | Stochastic Gradient Descent on MF | Medium datasets, GPU-friendly |
| ALS | Alternating Least Squares | Distributed/large-scale (Spark MLlib), implicit feedback |
| BPR | Bayesian Personalized Ranking | Implicit feedback — optimizes ranking directly |
Bias Terms
Real-world ratings have systematic biases. The full model adds global, user, and item bias terms:
where = global mean, = user bias (harsh/generous rater), = item bias (quality offset).
# ─── Explicit Feedback: SVD Matrix Factorization ─────────────
# pip install scikit-surprise
from surprise import SVD, Dataset, Reader, accuracy
from surprise.model_selection import cross_validate, train_test_split as sv_split
import pandas as pd
import numpy as np
# Load MovieLens 100K (built-in)
data = Dataset.load_builtin("ml-100k")
# SVD matrix factorization
svd = SVD(n_factors=100, n_epochs=20, lr_all=0.005, reg_all=0.02, random_state=42)
results = cross_validate(svd, data, measures=["RMSE", "MAE"], cv=5, verbose=True)
print(f"Mean RMSE: {results['test_rmse'].mean():.4f}")
# Predict a specific user-item pair
trainset, testset = sv_split(data, test_size=0.2)
svd.fit(trainset)
pred = svd.predict(uid="196", iid="302")
print(f"Predicted rating for user 196, item 302: {pred.est:.2f}")
# Top-N recommendations for a user
from collections import defaultdict
def get_top_n(predictions, n=10):
top_n = defaultdict(list)
for uid, iid, true_r, est, _ in predictions:
top_n[uid].append((iid, est))
for uid, user_ratings in top_n.items():
user_ratings.sort(key=lambda x: x[1], reverse=True)
top_n[uid] = user_ratings[:n]
return top_n
predictions = svd.test(testset)
top_n = get_top_n(predictions, n=10)
print(f"Top 10 for user '196': {top_n['196']}")
# ─── Implicit Feedback: ALS Matrix Factorization ─────────────
# pip install implicit
import implicit
from scipy.sparse import csr_matrix
# Simulate user-item click matrix (sparse)
np.random.seed(42)
n_users, n_items = 1000, 500
rows = np.random.randint(0, n_users, size=5000)
cols = np.random.randint(0, n_items, size=5000)
data_vals = np.random.randint(1, 10, size=5000) # interaction counts
user_item = csr_matrix((data_vals, (rows, cols)), shape=(n_users, n_items))
# ALS for implicit feedback (confidence-weighted MF)
model = implicit.als.AlternatingLeastSquares(
factors=64, # latent dimension k
regularization=0.01,
iterations=20,
calculate_training_loss=True,
)
model.fit(user_item) # expects item-user matrix → transpose
# Actually pass item-user:
item_user = user_item.T.tocsr()
model.fit(item_user)
# Recommend top 10 items for user 0
user_id = 0
ids, scores = model.recommend(user_id, user_item[user_id], N=10, filter_already_liked_items=True)
print(f"\nTop 10 items for user {user_id}: {list(zip(ids, scores.round(3)))}")
# Similar items
similar_ids, similar_scores = model.similar_items(item_id=42, N=5)
print(f"Items similar to item 42: {list(zip(similar_ids, similar_scores.round(3)))}")Content-Based Filtering
Content-based filtering recommends items similar to what the user has liked before, based on item attributes — not other users' behavior.
User profile = weighted average of feature vectors of items the user has rated positively.
Feature Representations
| Item Type | Features | Representation |
|---|---|---|
| Movies | Genre, director, cast, plot | TF-IDF on plot, one-hot genre |
| Articles/News | Text content | TF-IDF, sentence embeddings |
| Products | Category, price, attributes | Mixed numerical + categorical |
| Music | Genre, tempo, key, energy | Audio features (Spotify API) |
Cosine Similarity for Recommendations
Pros and Cons
| Pros | Cons |
|---|---|
| No cold start for items (features always available) | Cold start for new users (no history) |
| Explainable ("recommended because you liked X") | Over-specialization — only recommends similar items (filter bubble) |
| Works with single user | Doesn't leverage crowd wisdom |
The Cold-Start Problem
Cold start occurs when the system has insufficient data to make good recommendations:
| Scenario | Challenge | Mitigation |
|---|---|---|
| New user | No interaction history | Onboarding survey, popular/trending items, demographic-based rules |
| New item | No ratings yet | Content-based features, show in exploration slots, business rules |
| New system | No data at all | Editorial picks, A/B test with random + popularity, import external data |
Hybrid Approaches
Combining CF and content-based methods addresses cold start and improves accuracy:
- Weighted hybrid: — tune based on data sparsity
- Feature augmentation: use content features as input to MF (add item metadata as side information)
- Cascade: use content-based for cold users, switch to CF once enough data is collected
- Context-aware: incorporate time, location, device, session context
Exploration vs Exploitation
A recommendation system must balance:
- Exploitation: recommend what we're confident the user will like (maximize immediate relevance)
- Exploration: show diverse or novel items to learn user preferences (maximize long-term value)
ε-greedy: with probability ε, recommend a random/novel item; otherwise recommend the best predicted item. Upper Confidence Bound (UCB): prefer items with high uncertainty — similar to Thompson Sampling (multi-armed bandit).
Deep Learning for Recommendations
Neural Collaborative Filtering (NCF)
Replace the dot-product interaction with a neural network that can learn non-linear user-item interactions:
where = concatenation of user and item embeddings, and the MLP learns complex interactions.
NeuMF (He et al. 2017) combines GMF (dot product) + MLP for complementary signal.
Two-Tower Model (Retrieval + Ranking)
Used at YouTube, Google, Pinterest. Scales to billions of items:
User features → [User Tower ] ──→ user embedding ─┐
├──→ dot product → score
Item features → [Item Tower ] ──→ item embedding ─┘
Stage 1 — Retrieval (Recall): two-tower model retrieves top-K candidates (fast approximate nearest neighbor search with FAISS or ScaNN)
Stage 2 — Ranking: a heavy model (gradient boosting or deep NN) re-ranks the K candidates with full features
Session-Based / Sequential Recommendations
Models order of interactions to predict next item:
- GRU4Rec: GRU applied to click sequences
- BERT4Rec: bidirectional transformer on interaction history
- SASRec: self-attention for sequential recommendation
| Model | Architecture | Best For |
|---|---|---|
| NCF / NeuMF | Embedding + MLP | Rating prediction, dense feedback |
| Two-Tower | Dual encoder | Large-scale retrieval (millions of items) |
| BERT4Rec / SASRec | Transformer | Sequential/session-based recommendations |
| Wide & Deep | Linear + DNN | App store, e-commerce (Google Play) |
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, Dataset
import numpy as np
# ─── Content-Based Filtering (TF-IDF + Cosine Similarity) ────
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Movie descriptions
movies = {
"Movie A": "action thriller spy government agent",
"Movie B": "romantic comedy love story",
"Movie C": "action adventure spy heist thriller",
"Movie D": "romantic drama love loss",
"Movie E": "sci-fi space adventure alien",
}
titles = list(movies.keys())
descriptions = list(movies.values())
# Build TF-IDF matrix
tfidf = TfidfVectorizer()
tfidf_matrix = tfidf.fit_transform(descriptions) # shape: (n_movies, vocab_size)
# Cosine similarity between all pairs
sim_matrix = cosine_similarity(tfidf_matrix)
def get_similar_movies(title, top_n=3):
idx = titles.index(title)
scores = list(enumerate(sim_matrix[idx]))
scores = sorted(scores, key=lambda x: x[1], reverse=True)[1:top_n+1]
return [(titles[i], round(s, 3)) for i, s in scores]
print("Movies similar to 'Movie A':")
print(get_similar_movies("Movie A"))
# ─── Neural Collaborative Filtering (NCF) ────────────────────
class NCF(nn.Module):
def __init__(self, n_users, n_items, emb_dim=32, hidden=[64, 32]):
super().__init__()
# GMF branch
self.user_emb_gmf = nn.Embedding(n_users, emb_dim)
self.item_emb_gmf = nn.Embedding(n_items, emb_dim)
# MLP branch
self.user_emb_mlp = nn.Embedding(n_users, emb_dim)
self.item_emb_mlp = nn.Embedding(n_items, emb_dim)
layers = []
in_dim = emb_dim * 2
for h in hidden:
layers += [nn.Linear(in_dim, h), nn.ReLU()]
in_dim = h
self.mlp = nn.Sequential(*layers)
# Final prediction
self.predict = nn.Linear(emb_dim + hidden[-1], 1)
def forward(self, user, item):
# GMF
gmf = self.user_emb_gmf(user) * self.item_emb_gmf(item)
# MLP
mlp_in = torch.cat([self.user_emb_mlp(user), self.item_emb_mlp(item)], dim=-1)
mlp_out = self.mlp(mlp_in)
# Combine and predict
out = torch.cat([gmf, mlp_out], dim=-1)
return torch.sigmoid(self.predict(out)).squeeze()
# Simulate implicit interaction data (user, item, label)
n_users, n_items = 200, 100
np.random.seed(42)
users = torch.randint(0, n_users, (2000,))
items = torch.randint(0, n_items, (2000,))
labels = torch.randint(0, 2, (2000,)).float() # 1=interacted, 0=not
model = NCF(n_users, n_items, emb_dim=32, hidden=[64, 32])
optimizer = optim.Adam(model.parameters(), lr=1e-3, weight_decay=1e-5)
criterion = nn.BCELoss()
for epoch in range(10):
model.train()
preds = model(users, items)
loss = criterion(preds, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 2 == 0:
print(f"Epoch {epoch+1:2d} Loss: {loss.item():.4f}")
# Get top-10 recommendations for user 0
model.eval()
with torch.no_grad():
all_items = torch.arange(n_items)
u = torch.full((n_items,), 0, dtype=torch.long)
scores = model(u, all_items).numpy()
top10 = np.argsort(scores)[::-1][:10]
print(f"\nTop-10 items for user 0: {top10}")Evaluation Metrics for Recommenders
Standard accuracy metrics (RMSE, MAE) measure rating prediction but not ranking quality — what matters is whether good items appear at the top of the list.
Ranking Metrics (Offline)
| Metric | Formula | What It Measures |
|---|---|---|
| Hit Rate @ K | Did we recommend at least one relevant item? | |
| Precision @ K | How many of the top-K are relevant? | |
| Recall @ K | What fraction of relevant items did we catch? | |
| NDCG @ K | Discounted gain, rewards top positions | Position-aware ranking quality |
| MRR | How high is the first relevant item? |
Beyond Accuracy
Good recommendations also require:
| Property | Definition | Why It Matters |
|---|---|---|
| Coverage | % of item catalog that gets recommended | Prevent popularity bias — tail items also get exposure |
| Diversity | Average dissimilarity within a recommendation list | Avoid filter bubbles; expose users to new types |
| Novelty | Recommending items the user is unlikely to know | Serendipity > restating the obvious |
| Serendipity | Relevant AND surprising recommendations | User delight, long-term engagement |
Online Evaluation
Offline metrics don't always correlate with online business metrics. Use A/B testing to measure:
- Click-through rate (CTR)
- Conversion rate, revenue
- Session length, return rate
- Long-term retention
Knowledge check
A new e-commerce platform has 10,000 products but very few user ratings. Which recommendation approach should they start with?
Summary
Approach Selection Guide
| Scenario | Recommended Approach |
|---|---|
| Dense ratings data, small catalog | SVD Matrix Factorization |
| Implicit feedback, large catalog | ALS (Alternating Least Squares) |
| Rich item metadata, sparse interactions | Content-Based Filtering |
| New platform / cold start | Popularity + Content-Based → CF hybrid |
| Sequential behavior (sessions) | SASRec / BERT4Rec |
| Billion-scale retrieval | Two-Tower + ANN search (FAISS) |
Key takeaways:
- Collaborative filtering leverages crowd wisdom but fails on cold start
- Matrix factorization is the production workhorse for explicit/implicit feedback
- Content-based handles cold items and provides explainability
- Hybrid approaches combine strengths of both families
- Evaluate beyond RMSE — ranking quality (NDCG, Hit Rate) and diversity matter more than rating prediction accuracy
- Online A/B testing is the gold standard — offline metrics often don't predict real-world CTR/conversion
Module Complete: ML Fundamentals
You've now covered all 12 core topics:
- Supervised Learning — regression, classification, time series, Naive Bayes
- Unsupervised Learning — K-Means, DBSCAN, PCA, t-SNE
- Reinforcement Learning — MDPs, Q-learning, PPO
- Semi-Supervised & Self-Supervised Learning
- Ensemble Techniques — Random Forest, XGBoost, stacking
- Deep Neural Networks — backprop, BatchNorm, dropout
- CNNs — convolution, ResNet, transfer learning
- RNN, LSTM & GRU — sequential data and long-range memory
- Model Evaluation — confusion matrix, clustering metrics, ROC, NDCG
- Hyperparameter Tuning — Optuna, LR scheduling
- EDA & AutoML — data exploration and automated pipelines
- Recommendation Systems — CF, MF, content-based, NCF, Two-Tower