Skip to content
  • Recommender Systems · Machine Learning

Recommender Systems

LearnViaNotes @learnvianotes· 5 min read

Recommenders predict which items a user will like from past interactions (ratings, clicks, purchases, watch time) and item information. The core families are content-based filtering, collaborative filtering and matrix factorisation — usually combined.

ApproachIdeaStrengthWeakness
PopularityRecommend what's popular (per segment)Strong baseline, no cold start for usersNot personal; reinforces popularity
Content-basedItems similar to what the user liked, using item features (text, genre, tags)Works for new items; explainableOver-specialised; needs good item features
Collaborative filtering (neighbourhood)Users who agreed before will agree again / items liked by the same users are similarNo item features needed; finds surprising itemsCold start for new users and items; sparse data
Matrix factorisationLearn a latent vector per user and per item; score = their dot productScales, captures hidden tastesCold start; less explainable
Hybrid / two-stageCandidate generation (fast retrieval of hundreds of items) → ranking model with rich featuresIndustry standard at scaleMore components to build and monitor

Content-based: similar items

Python
import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

titles = ["space adventure alien war", "romantic comedy wedding", "alien invasion space thriller",
          "wedding drama romance", "robot space exploration"]
tfidf = TfidfVectorizer().fit_transform(titles)
sim = cosine_similarity(tfidf)

liked = 0                                                # the user liked "space adventure alien war"
ranked = [int(i) for i in np.argsort(sim[liked])[::-1] if i != liked]
print(ranked[:2])                                        # [2, 4] ← the other space/alien films
print(sim[liked].round(2).tolist())                      # [1.0, 0.0, 0.35, 0.0, 0.16]

Collaborative filtering: item–item similarity

Python
import numpy as np

# ratings: rows = users, columns = items A..E; 0 = not rated
R = np.array([[5, 4, 0, 1, 0],
              [4, 5, 1, 0, 1],
              [1, 0, 5, 4, 5],
              [0, 1, 4, 5, 4],
              [5, 5, 0, 0, 1]], dtype=float)

norms = np.linalg.norm(R, axis=0)
item_sim = (R.T @ R) / np.outer(norms, norms)            # cosine similarity between item columns
print(item_sim[0].round(2).tolist())                     # [1.0, 0.97, 0.17, 0.17, 0.26] ← item A is most like item B

user, item = 0, 2                                         # predict user 0's rating of item C
rated = R[user] > 0
w = item_sim[item, rated]
print(round(float(w @ R[user, rated] / w.sum()), 2))     # 1.92 ← similarity-weighted average of their ratings

Matrix factorisation

r̂ᵤᵢ = pᵤ · qᵢ minimise Σ_(u,i observed) (rᵤᵢ − pᵤ·qᵢ)² + λ(‖pᵤ‖² + ‖qᵢ‖²)

Each user and item gets a vector of k latent factors (think “how much action / romance”). Only observed ratings enter the loss — treating missing ratings as 0 (plain SVD) would teach the model that unseen means disliked. Alternating least squares (ALS) fixes the items and solves a small ridge regression per user, then swaps:

Python
import numpy as np

R = np.array([[5, 4, 0, 1, 0],
              [4, 5, 1, 0, 1],
              [1, 0, 5, 4, 5],
              [0, 1, 4, 5, 4],
              [5, 5, 0, 0, 1]], dtype=float)
observed = R > 0
k, lam = 2, 0.1
rng = np.random.default_rng(0)
P = rng.normal(0, 0.1, (5, k))                            # user factors
Q = rng.normal(0, 0.1, (5, k))                            # item factors

for _ in range(50):                                       # alternating least squares
    for u in range(5):
        Qo = Q[observed[u]]
        P[u] = np.linalg.solve(Qo.T @ Qo + lam * np.eye(k), Qo.T @ R[u, observed[u]])
    for i in range(5):
        Po = P[observed[:, i]]
        Q[i] = np.linalg.solve(Po.T @ Po + lam * np.eye(k), Po.T @ R[observed[:, i], i])

pred = P @ Q.T
print(round(float(np.sqrt(np.mean((pred - R)[observed] ** 2))), 3))   # 0.345 ← training RMSE on observed ratings
print(np.clip(pred[0], 1, 5).round(1).tolist())         # [4.4, 4.5, 1.0, 1.1, 1.0] ← user 0, clipped to the 1–5 scale: C and E filled in

User 0 loves A and B and dislikes D; the factors place C and E with D, so both get low predicted ratings — the recommender won't suggest them. (Raw dot products can leave the rating scale, hence the clipping; real systems also add a global mean plus user and item bias terms: r̂ = μ + bᵤ + bᵢ + pᵤ·qᵢ.)

Evaluating recommendations

MetricMeaning
Precision@k / Recall@kShare of the top-k that are relevant / share of relevant items that appear in the top-k
Hit rate@kDid at least one relevant item appear in the top-k?
MRRMean of 1 / rank of the first relevant item
NDCG@kRewards relevant items near the top with a log discount, normalised by the ideal ordering
Coverage, diversity, noveltyIs the system recommending more than the same popular items?

DCG@k = Σᵢ₌₁ᵏ relᵢ / log₂(i + 1) NDCG@k = DCG@k / IDCG@k

Python
import numpy as np

def ndcg_at_k(relevance, k):
    rel = np.asarray(relevance, dtype=float)[:k]
    discounts = 1 / np.log2(np.arange(2, len(rel) + 2))
    ideal = np.sort(np.asarray(relevance, dtype=float))[::-1][:k]
    return float(rel @ discounts / (ideal @ discounts))

print(round(ndcg_at_k([3, 2, 0, 1], 4), 3))    # 0.985 ← good items near the top
print(round(ndcg_at_k([0, 1, 2, 3], 4), 3))    # 0.614 ← same items, worst order
  • Implicit feedback (clicks, views) is far more plentiful than ratings but has no explicit negatives — unobserved ≠ disliked; sample negatives carefully.

  • Cold start: new users → popularity, onboarding questions, context; new items → content features.

  • Split by time for offline evaluation, and confirm with an online A/B test — offline metrics only partly predict online engagement.

  • Watch feedback loops and popularity bias: the model only learns from what it chose to show.

Content-based vs collaborative filtering?

How does matrix factorisation work for recommendations?

How do you solve the cold-start problem?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report