04 — Traditional Recommendation Algorithms
“The classics still ship a lot of revenue. Know them cold.”
Introduction
Before deep learning ate the world, recommender systems were powered by collaborative filtering (CF), content-based filtering, and matrix factorization. These methods remain the production backbone in many shops because they are fast, interpretable, robust on small data, and a strong baseline you must beat.
This post walks through CF (memory-based and model-based), content-based, hybrid methods, and matrix factorization with code on MovieLens.
1. Family Tree
flowchart TB
R[Traditional RecSys] --> CF[Collaborative Filtering]
R --> CB[Content-Based]
R --> H[Hybrid]
CF --> M[Memory-Based<br/>kNN]
CF --> MF[Model-Based<br/>Matrix Factorization]
M --> U[User-User]
M --> I[Item-Item]
MF --> SVD[SVD / FunkSVD]
MF --> ALS[ALS for implicit]
MF --> BPR[BPR pairwise]
MF --> NMF[NMF]
CB --> T[TF-IDF on text]
CB --> C[Categorical similarity]
2. Collaborative Filtering: Memory-Based
2.1 User-User CF
Predict user $u$’s rating for item $i$ as a weighted average of similar users’ ratings:
\[\hat r_{ui} = \bar r_u + \frac{\sum_{v \in N_k(u)} \text{sim}(u,v) (r_{vi} - \bar r_v)}{\sum_{v \in N_k(u)} |\text{sim}(u,v)|}\]Similarity is usually cosine or Pearson correlation on overlapping ratings.
2.2 Item-Item CF (Amazon, 2003)
Predict from items the user already rated:
\[\hat r_{ui} = \frac{\sum_{j \in N_k(i)} \text{sim}(i, j) \cdot r_{uj}}{\sum_{j} |\text{sim}(i,j)|}\]Why item-item won: item catalogs are smaller and more stable than user bases, similarities can be precomputed offline.
Pros & Cons of kNN CF
| Pros | Cons |
|---|---|
| Easy to explain (“because you liked X”) | Sparse → noisy similarities |
| No training, just precomputation | Doesn’t scale gracefully past ~10M items |
| Adapts instantly to new ratings | Cold start for new users/items |
| Robust on dense subgraphs | Popularity bias |
Code: Item-Item CF on MovieLens 1M
# pip install pandas scipy scikit-learn
import pandas as pd
from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity
ratings = pd.read_csv("ratings.dat", sep="::",
names=["user", "item", "rating", "ts"],
engine="python")
user_idx = {u: i for i, u in enumerate(ratings["user"].unique())}
item_idx = {it: i for i, it in enumerate(ratings["item"].unique())}
n_u, n_i = len(user_idx), len(item_idx)
mat = csr_matrix(
(ratings["rating"].values,
([user_idx[u] for u in ratings["user"]],
[item_idx[it] for it in ratings["item"]])),
shape=(n_u, n_i)
)
sim = cosine_similarity(mat.T, dense_output=False) # item x item
def recommend(user, k=10):
u = user_idx[user]
user_vec = mat[u].toarray().ravel()
scores = sim.dot(user_vec)
scores[user_vec > 0] = -1 # mask already-rated
return scores.argsort()[::-1][:k]
3. Matrix Factorization
| The big idea: approximate the sparse user-item matrix as the product of two low-rank matrices $P \in \mathbb{R}^{ | U | \times d}$ and $Q \in \mathbb{R}^{ | I | \times d}$: |
3.1 FunkSVD (Netflix Prize, 2006)
Optimize MSE with SGD:
\[\min_{P,Q} \sum_{(u,i) \in \Omega} (r_{ui} - p_u^\top q_i)^2 + \lambda (\|p_u\|^2 + \|q_i\|^2)\]3.2 ALS for Implicit Feedback (Hu, Koren, Volinsky 2008)
For implicit signals (clicks, plays), define confidence $c_{ui} = 1 + \alpha r_{ui}$ and preference $p_{ui} = \mathbb{1}[r_{ui} > 0]$:
\[\min_{P,Q} \sum_{u,i} c_{ui} (p_{ui} - p_u^\top q_i)^2 + \lambda(\|P\|^2+\|Q\|^2)\]Solved by alternating: hold $Q$ fixed, solve closed-form for $P$, swap.
3.3 BPR (Bayesian Personalized Ranking, Rendle 2009)
Pairwise: positive $i$ ranked above negative $j$:
\[\max \sum_{(u, i^+, j^-)} \log \sigma(p_u^\top q_{i^+} - p_u^\top q_{j^-})\]# pip install implicit
import implicit
from scipy.sparse import csr_matrix
# Build user x item matrix of binary or count interactions
model = implicit.als.AlternatingLeastSquares(
factors=64, regularization=0.01, iterations=20, alpha=40.0)
model.fit(mat)
# Top-10 recommendations for user 0
ids, scores = model.recommend(0, mat[0], N=10, filter_already_liked_items=True)
Pros & Cons of MF
| Pros | Cons |
|---|---|
| Captures latent structure | Cold start (no embeddings for new entities) |
| Compact, fast inference | Linear in features only |
| Easy to extend (biases, time decay) | Hard to add side features cleanly |
4. Content-Based Filtering
Build profiles from item content (text, tags, images), then recommend items similar to those the user has liked.
4.1 TF-IDF + Cosine
\[\text{sim}(u, i) = \cos(\bar v_u, v_i), \quad \bar v_u = \frac{1}{|I_u|}\sum_{j \in I_u} v_j\]from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
movies = pd.read_csv("movies.csv") # MovieLens
movies["text"] = movies["title"] + " " + movies["genres"].str.replace("|", " ")
tfidf = TfidfVectorizer(stop_words="english").fit_transform(movies["text"])
sim = cosine_similarity(tfidf)
def content_recs(item_id, k=10):
idx = movies.index[movies["movieId"] == item_id][0]
scores = sim[idx]
top = np.argsort(scores)[::-1][1:k+1]
return movies.iloc[top]
Pros & Cons
| Pros | Cons |
|---|---|
| Handles item cold start | Limited by quality of content features |
| Explainable (“similar to X you liked”) | No serendipity — recs are too similar |
| Independent of user base size | Requires content extraction pipeline |
5. Hybrid Methods
5.1 Weighted hybrid
$\hat s = \alpha \cdot s_{\text{CF}} + (1-\alpha) \cdot s_{\text{CB}}$
5.2 Feature-augmented MF
Treat content vectors as priors for item embeddings (e.g., CTR — Wang & Blei 2011).
5.3 LightFM
Combines MF with feature embeddings — handles cold start.
# pip install lightfm
from lightfm import LightFM
from lightfm.data import Dataset
from lightfm.evaluation import precision_at_k
ds = Dataset()
ds.fit(users=ratings["user"], items=ratings["item"],
item_features=movies["genres"].str.split("|").explode().unique())
(interactions, _) = ds.build_interactions(
ratings[["user", "item"]].itertuples(index=False))
model = LightFM(loss="warp", no_components=64)
model.fit(interactions, epochs=20, num_threads=4)
print("precision@10:", precision_at_k(model, interactions, k=10).mean())
6. When to Use What
| Scenario | Choose |
|---|---|
| <100K interactions, sparse | Item-item CF, TF-IDF CB |
| Implicit signals at scale | ALS (implicit) |
| Ranking objective, pairwise data | BPR (implicit) |
| Need side features, cold start | LightFM, hybrid |
| Latent factor exploration | FunkSVD |
7. Production Tips
- Precompute item-item similarity once a day; serve from KV store.
- For ALS, regularize aggressively on long-tail items.
- Time-decay ratings: $w_{ui} = e^{-\lambda (t_{\text{now}} - t_{ui})}$.
- Mix CF and CB to bridge cold-start without changing UX.
- Keep CF as a fallback when the deep model is degraded.
8. Pitfalls
- Computing similarity on raw counts instead of TF-IDF or BM25 — popularity dominates.
- Using random splits instead of time-based for evaluation.
- Not subtracting user bias before computing similarity (some users rate everything 5).
- Treating implicit clicks as ratings (use ALS for implicit).
- Forgetting to filter already-interacted items at inference.
9. Public Datasets
- MovieLens — https://grouplens.org/datasets/movielens/ (canonical for CF)
- BookCrossing — http://www2.informatik.uni-freiburg.de/~cziegler/BX/
- Amazon Reviews — https://nijianmo.github.io/amazon/ (CB rich content)
- Last.fm — http://ocelma.net/MusicRecommendationDataset/
- Jester Jokes — https://eigentaste.berkeley.edu/dataset/
10. Further Reading
- Koren, Bell, Volinsky, Matrix Factorization Techniques for Recommender Systems (IEEE Computer 2009)
- Sarwar et al., Item-Based Collaborative Filtering Recommendation Algorithms (WWW 2001)
- Rendle, BPR: Bayesian Personalized Ranking (UAI 2009)
- Hu et al., Collaborative Filtering for Implicit Feedback Datasets (ICDM 2008)
- Pazzani & Billsus, Content-Based Recommendation Systems (2007)