Skip to content
SDB
ML Fundamentals

Chapter 12 · intermediate · 30 min

Recommendation Systems

Collaborative filtering, matrix factorization, content-based, and deep learning approaches

Subhendu Datta BhowmikAI Tutorials

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 U={u1,...,um}U = \{u_1, ..., u_m\}
  • A set of items I={i1,...,in}I = \{i_1, ..., i_n\}
  • A sparse interaction matrix RRm×nR \in \mathbb{R}^{m \times n} where RuiR_{ui} = rating/signal (or 0 if unknown)

Goal: predict R^ui\hat{R}_{ui} for all unknown entries, then recommend top-KK items per user.

Types of Feedback

Feedback TypeExamplesChallenge
ExplicitStar ratings, thumbs up/downSparse — users rarely rate things
ImplicitClicks, views, purchases, time spentNoisy — 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:

r^ui=rˉu+vN(u)sim(u,v)(rvirˉv)vN(u)sim(u,v)\hat{r}_{ui} = \bar{r}_u + \frac{\sum_{v \in N(u)} \text{sim}(u,v) \cdot (r_{vi} - \bar{r}_v)}{\sum_{v \in N(u)} |\text{sim}(u,v)|}

Common similarity measures:

  • Cosine similarity: sim(u,v)=rurvrurv\text{sim}(u,v) = \frac{\mathbf{r}_u \cdot \mathbf{r}_v}{\|\mathbf{r}_u\| \|\mathbf{r}_v\|}
  • 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:

r^ui=jN(i)sim(i,j)rujjN(i)sim(i,j)\hat{r}_{ui} = \frac{\sum_{j \in N(i)} \text{sim}(i,j) \cdot r_{uj}}{\sum_{j \in N(i)} |\text{sim}(i,j)|}

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 worksDirectly compute similarities on raw dataLearn a compressed model from interaction matrix
ScalabilityPoor — O(mn)O(mn) per queryGood — O(kd)O(kd) per query after training
Cold startFails — needs prior interactionsSame problem but can incorporate side info
ExamplesUser-KNN, Item-KNNSVD, ALS, Neural CF

Matrix Factorization

Matrix Factorization (MF) decomposes the sparse interaction matrix RR into two low-rank dense matrices:

RPQTR \approx P \cdot Q^T

where:

  • PRm×kP \in \mathbb{R}^{m \times k} — user embeddings (mm users, kk latent factors)
  • QRn×kQ \in \mathbb{R}^{n \times k} — item embeddings (nn items, kk latent factors)
  • r^ui=puTqi\hat{r}_{ui} = \mathbf{p}_u^T \mathbf{q}_i — predicted rating = dot product of user and item vectors

The latent factors kk capture hidden preferences (genre preference, quality sensitivity, etc.) without explicit labels.

Training Objective (with Regularization)

minP,Q(u,i)observed(ruipuTqi)2+λ(pu2+qi2)\min_{P,Q} \sum_{(u,i) \in \text{observed}} (r_{ui} - \mathbf{p}_u^T \mathbf{q}_i)^2 + \lambda(\|\mathbf{p}_u\|^2 + \|\mathbf{q}_i\|^2)

The regularization term λ\lambda prevents overfitting to the sparse observed ratings.

SVD vs ALS vs SGD

MethodFull NameBest For
SVD (Funk SVD)Stochastic Gradient Descent on MFMedium datasets, GPU-friendly
ALSAlternating Least SquaresDistributed/large-scale (Spark MLlib), implicit feedback
BPRBayesian Personalized RankingImplicit feedback — optimizes ranking directly

Bias Terms

Real-world ratings have systematic biases. The full model adds global, user, and item bias terms:

r^ui=μ+bu+bi+puTqi\hat{r}_{ui} = \mu + b_u + b_i + \mathbf{p}_u^T \mathbf{q}_i

where μ\mu = global mean, bub_u = user bias (harsh/generous rater), bib_i = item bias (quality offset).

Matrix Factorization with Surprise + Implicit Feedback with Implicitpython
# ─── 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.

r^ui=sim(profileu,featuresi)\hat{r}_{ui} = \text{sim}(\text{profile}_u, \text{features}_i)

User profile = weighted average of feature vectors of items the user has rated positively.

Feature Representations

Item TypeFeaturesRepresentation
MoviesGenre, director, cast, plotTF-IDF on plot, one-hot genre
Articles/NewsText contentTF-IDF, sentence embeddings
ProductsCategory, price, attributesMixed numerical + categorical
MusicGenre, tempo, key, energyAudio features (Spotify API)

Cosine Similarity for Recommendations

sim(u,i)=profileufiprofileufi\text{sim}(u, i) = \frac{\text{profile}_u \cdot \mathbf{f}_i}{\|\text{profile}_u\| \|\mathbf{f}_i\|}

Pros and Cons

ProsCons
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 userDoesn't leverage crowd wisdom

The Cold-Start Problem

Cold start occurs when the system has insufficient data to make good recommendations:

ScenarioChallengeMitigation
New userNo interaction historyOnboarding survey, popular/trending items, demographic-based rules
New itemNo ratings yetContent-based features, show in exploration slots, business rules
New systemNo data at allEditorial picks, A/B test with random + popularity, import external data

Hybrid Approaches

Combining CF and content-based methods addresses cold start and improves accuracy:

  1. Weighted hybrid: r^=αr^CF+(1α)r^CB\hat{r} = \alpha \cdot \hat{r}_{CF} + (1-\alpha) \cdot \hat{r}_{CB} — tune α\alpha based on data sparsity
  2. Feature augmentation: use content features as input to MF (add item metadata as side information)
  3. Cascade: use content-based for cold users, switch to CF once enough data is collected
  4. 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:

r^ui=σ(hTReLU(WLReLU(W1[puqi])))\hat{r}_{ui} = \sigma\left(\mathbf{h}^T \text{ReLU}(\mathbf{W}_L \cdots \text{ReLU}(\mathbf{W}_1 [\mathbf{p}_u \oplus \mathbf{q}_i]))\right)

where \oplus = 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
ModelArchitectureBest For
NCF / NeuMFEmbedding + MLPRating prediction, dense feedback
Two-TowerDual encoderLarge-scale retrieval (millions of items)
BERT4Rec / SASRecTransformerSequential/session-based recommendations
Wide & DeepLinear + DNNApp store, e-commerce (Google Play)
Content-Based + Simple NCF with PyTorchpython
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)

MetricFormulaWhat It Measures
Hit Rate @ Kusers with ≥1 relevant item in top-Ktotal users\frac{\text{users with ≥1 relevant item in top-}K}{\text{total users}}Did we recommend at least one relevant item?
Precision @ Krelevant items in top-KK\frac{\text{relevant items in top-}K}{K}How many of the top-K are relevant?
Recall @ Krelevant items in top-Ktotal relevant items\frac{\text{relevant items in top-}K}{\text{total relevant items}}What fraction of relevant items did we catch?
NDCG @ KDiscounted gain, rewards top positionsPosition-aware ranking quality
MRR1Q1rank of first hit\frac{1}{Q}\sum \frac{1}{\text{rank of first hit}}How high is the first relevant item?

Beyond Accuracy

Good recommendations also require:

PropertyDefinitionWhy It Matters
Coverage% of item catalog that gets recommendedPrevent popularity bias — tail items also get exposure
DiversityAverage dissimilarity within a recommendation listAvoid filter bubbles; expose users to new types
NoveltyRecommending items the user is unlikely to knowSerendipity > restating the obvious
SerendipityRelevant AND surprising recommendationsUser 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

ScenarioRecommended Approach
Dense ratings data, small catalogSVD Matrix Factorization
Implicit feedback, large catalogALS (Alternating Least Squares)
Rich item metadata, sparse interactionsContent-Based Filtering
New platform / cold startPopularity + Content-Based → CF hybrid
Sequential behavior (sessions)SASRec / BERT4Rec
Billion-scale retrievalTwo-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:

  1. Supervised Learning — regression, classification, time series, Naive Bayes
  2. Unsupervised Learning — K-Means, DBSCAN, PCA, t-SNE
  3. Reinforcement Learning — MDPs, Q-learning, PPO
  4. Semi-Supervised & Self-Supervised Learning
  5. Ensemble Techniques — Random Forest, XGBoost, stacking
  6. Deep Neural Networks — backprop, BatchNorm, dropout
  7. CNNs — convolution, ResNet, transfer learning
  8. RNN, LSTM & GRU — sequential data and long-range memory
  9. Model Evaluation — confusion matrix, clustering metrics, ROC, NDCG
  10. Hyperparameter Tuning — Optuna, LR scheduling
  11. EDA & AutoML — data exploration and automated pipelines
  12. Recommendation Systems — CF, MF, content-based, NCF, Two-Tower

ML Fundamentals