Skip to main content

    Lesson 45 • Advanced

    Building Recommender Systems 🎬

    Learn how Netflix, Spotify, and Amazon decide what to show you — using cosine similarity, collaborative and content-based filtering, and the maths behind the Netflix Prize — and build a recommender by hand in plain Python.

    What You'll Learn in This Lesson

    • How content-based filtering recommends from item features
    • User-user and item-item collaborative filtering
    • Cosine similarity — and how to compute it by hand
    • The intuition behind matrix factorization
    • What the cold-start problem is and how to dodge it
    • How to evaluate a recommender with precision@k

    🌍 Real-World Analogy: A Friend Who Knows Your Taste

    Picture the one friend who always nails movie night. Over the years they have learned what you love, so when they say "trust me, watch this," they are usually right. How do they do it? Two ways, really — and those two ways are the two big families of recommender systems.

    Sometimes your friend reasons from the movie itself: "you loved the last gritty sci-fi thriller, this one is the same vibe." That is content-based filtering — matching an item's features to what you already liked. Other times they reason from people: "my colleague has the exact same taste as you, and she couldn't stop talking about this one." That is collaborative filtering — borrowing the verdict of users similar to you. The best friend (and the best system, like Netflix or Spotify) blends both.

    1Two Families (and the Hybrid)

    A recommender system predicts which items you will like and shows you the best ones, usually as a ranked list. There are two core strategies, plus a hybrid that mixes them.

    ApproachHow it worksExampleWeakness
    Content-basedMatch item features to your past likes"Because you liked sci-fi…"Filter bubble
    CollaborativeBorrow ratings from similar users/items"Users like you watched…"Cold start
    HybridCombine both signalsNetflix, YouTube, SpotifyComplexity

    2Cosine Similarity — Measuring Taste

    Represent a user as a rating vector — just a list of their scores: [5, 2, 5, 1, 4]. To compare two users, you compare their vectors. The favourite tool is cosine similarity: the angle between the two vectors.

    The formula is the dot product (multiply matching positions and add them up) divided by the product of the two vectors' lengths. The result lands between 0 (nothing in common) and 1 (identical direction of taste).

    📐 Why cosine, not plain distance?

    Cosine measures direction, not magnitude. A generous rater who hands out 5s and a stingy rater who hands out 3s can still be a perfect match if they rank things the same way. Cosine sees that; raw distance would wrongly call them far apart.

    The worked example below computes it from scratch — no NumPy, just math.sqrt and a loop. Read every comment, then run it.

    Worked Example: Cosine Similarity by Hand (plain Python)

    Compute the taste match between two rating vectors with a dot product and two square roots

    Try it Yourself »
    Python
    # === Cosine similarity between two rating vectors, in plain Python ===
    # No libraries. A user's taste is just a list of 1-5 star ratings.
    import math
    
    # Each list holds one user's ratings for the SAME 5 movies, in order.
    #            [Inception, Titanic, Matrix, Notebook, Interstellar]
    alice    =   [5,         2,       5,      1,        4]
    bob      =   [4,         1,       4,      1,        5]   # very like Alice
    carol    =   [1,         5,       1,      5,        1]   # opposite taste
    
    def cos
    ...

    3Collaborative Filtering — User-User and Item-Item

    Collaborative filtering ignores what an item is and looks only at the pattern of ratings. It comes in two flavours:

    User-user

    Find the people most similar to you, then recommend what they liked that you have not seen. Netflix's original approach. Intuitive, but users come and go and there can be millions of them, so it is hard to scale.

    Item-item

    Find items rated alike and recommend ones similar to what you already enjoyed — "because you watched Inception." Amazon's approach. Items are more stable than users and their similarities can be precomputed, so it scales far better.

    To predict a missing rating, take a similarity-weighted average of your neighbours' ratings: the more similar a neighbour, the more their opinion counts. The worked example fills in the gaps in a small rating matrix and recommends the single best unseen movie for one user.

    Worked Example: User-User Collaborative Filtering

    Find Eve's nearest neighbours, predict her missing ratings, and recommend the top movie

    Try it Yourself »
    Python
    # === User-user collaborative filtering: recommend the top movie for Eve ===
    # Find the users most similar to Eve, then borrow their ratings.
    import math
    
    movies = ["Inception", "Titanic", "Matrix", "Notebook", "Interstellar"]
    
    # 0 means "not rated yet". Eve has only seen two movies.
    ratings = {
        "Alice": [5, 2, 5, 1, 4],
        "Bob":   [4, 1, 4, 1, 5],
        "Carol": [1, 5, 1, 5, 1],
        "Eve":   [5, 0, 4, 0, 0],   # target user — gaps are what we predict
    }
    
    def cosine_similarity(a, b):
        # On
    ...

    4Matrix Factorization — The Intuition

    The user-item rating matrix is huge and mostly empty — millions of users, thousands of items, and almost every cell blank. Comparing shared ratings directly falls apart when two users have barely any items in common. Matrix factorization is the fix that won the Netflix Prize.

    The idea: every user and every item secretly lives in a small space of hidden factors — think invisible dials like "how much sci-fi", "how much romance", "how light vs dark". Factorization learns, for each user, how much they want each dial, and for each item, how much of each dial it has. A predicted rating is just the dot product of a user's dials with an item's dials.

    🧩 In one line:

    Decompose the big rating matrix R into two skinny matrices — one row of factors per user, one row of factors per item — so that multiplying them back together reconstructs the known ratings and fills in the blanks. Those filled-in blanks are your predictions.

    You do not implement the training loop here (it is gradient descent over those factors), but the mental model is the payoff: users and items share one hidden taste space, and a dot product scores any pair. That is the same dot product you already coded for cosine similarity.

    5Evaluating a Recommender — precision@k

    A recommender hands the user a ranked list, and users only ever look at the top of it. So the right question is not "how accurate are all my predictions" but "how many of my top few were any good?"

    precision@k answers exactly that: of the top k items you recommended, what fraction were genuinely relevant to the user? A precision@5 of 0.6 means 3 of your top 5 hit the mark. It is simple, it matches what the user experiences, and it is far more honest than overall rating error.

    The worked example builds an item-item recommendation list ("because you watched Inception…") and then scores that list with precision@k for k = 1, 2, 3.

    Worked Example: Item-Item Similarity + precision@k

    Recommend movies similar to one you watched, then grade the ranked list with precision@k

    Try it Yourself »
    Python
    # === Item-item similarity and precision@k (how good is the list?) ===
    import math
    
    # Columns are users; each row is one MOVIE's ratings across users.
    # Item-item CF asks: "which movies get rated alike?" — Amazon's approach.
    items = {
        "Inception":    [5, 4, 1, 5],   # ratings from [u1, u2, u3, u4]
        "Matrix":       [5, 4, 1, 4],   # almost identical to Inception
        "Interstellar": [4, 5, 1, 5],   # also a sci-fi neighbour
        "Notebook":     [1, 1, 5, 1],   # the odd one out (romance)
    }
    
    
    ...

    ❄️ The Cold-Start Problem

    Collaborative filtering needs history. A brand-new user has rated nothing, and a brand-new item has been rated by nobody — so there is nothing to compare. This is the cold-start problem, and it is the single biggest reason a recommender feels useless on day one.

    Three standard escapes:

    • Fall back to content-based. Item features exist even with zero ratings, so you can still match a new item to a user's taste, and a new user to items.
    • Recommend popular items. Until you know someone, the safest bet is what most people enjoy — then personalise as data arrives.
    • Ask a few onboarding questions. "Pick three movies you love" seeds a profile instantly, which is why so many apps do it on sign-up.

    Now you try. Fill in the blanks marked ___. Cosine similarity is the engine of every example above — finish it and you understand the whole lesson.

    🎯 Your Turn 1: Finish Cosine Similarity

    Complete the dot product and the magnitude so the taste match comes out to 0.974

    Try it Yourself »
    Python
    # 🎯 YOUR TURN — finish the cosine similarity function.
    # Cosine similarity tells you how aligned two taste vectors are (0 to 1).
    import math
    
    you    = [5, 1, 4, 1, 5]   # your star ratings for 5 movies
    friend = [4, 2, 5, 1, 4]   # a friend's ratings for the same 5 movies
    
    def cosine_similarity(a, b):
        dot   = ___    # 👉 sum of a[i]*b[i] for every position i
        mag_a = math.sqrt(sum(x * x for x in a))
        mag_b = ___    # 👉 sqrt of the sum of squares of b, like mag_a
        if mag_a == 0 or m
    ...

    Your turn again. Score each unwatched movie against a taste profile and recommend the single best one.

    🎯 Your Turn 2: Recommend the Top Item

    Loop over the candidates, compute each similarity, and keep the highest-scoring movie

    Try it Yourself »
    Python
    # 🎯 YOUR TURN — recommend the single best unseen item by similarity.
    # Your taste profile is given. Score each unseen movie, then pick the best.
    import math
    
    # Your average taste as a feature vector: [action, romance, sci_fi, drama]
    profile = [0.8, 0.1, 0.9, 0.4]
    
    # Movies you have NOT watched yet, each with the same 4 features.
    candidates = {
        "Blade Runner": [0.7, 0.1, 0.9, 0.7],
        "La La Land":   [0.0, 0.9, 0.0, 0.6],
        "Arrival":      [0.4, 0.1, 0.9, 0.8],
    }
    
    def cosine_similarity(a,
    ...

    6Common Errors (And How to Fix Them)

    These four mistakes trip up almost everyone building their first recommender:

    ❌ Ignoring cold start

    A brand-new user has rated nothing, so collaborative filtering returns an empty or random list and the product feels broken on first use.

    recs = collaborative_filter(new_user)   # ❌ no history → nothing to return

    ✅ Fix: fall back when there is no history.

    if user_has_ratings(new_user):
        recs = collaborative_filter(new_user)
    else:
        recs = content_based(new_user) or most_popular()   # ✅ graceful fallback

    ❌ Popularity bias

    Blockbusters get rated by everyone, so they look "similar" to everyone and end up recommended to everyone. Niche items the user would love never surface.

    ✅ Fix: down-weight popular items (penalise by how often they are rated) and add a diversity term so the list is not just the global top 10.

    ❌ Treating sparse data as real zeros

    A blank cell means "not rated", not "rated zero". Counting missing entries as 0 makes two users who simply have not overlapped look like they disagree.

    sim = cosine(a, b)   # ❌ if a and b are padded with 0s, similarity is wrong

    ✅ Fix: compare only positions both users actually rated.

    pairs = [(a[i], b[i]) for i in range(len(a)) if a[i] > 0 and b[i] > 0]
    # ✅ build the vectors from pairs, then take cosine of those

    ❌ Data leakage in evaluation

    Scoring your recommender on ratings it was trained on (or on items the user already interacted with) makes it look brilliant — then it flops on genuinely unseen items.

    evaluate(model, all_ratings)   # ❌ test items were seen during training

    ✅ Fix: hold out a test set of future or unseen interactions and measure precision@k only on those.

    train, test = split_by_time(ratings)   # ✅ never train on the test rows
    model.fit(train)
    score = precision_at_k(model.recommend(user), test_relevant, k=5)

    📋 Quick Reference

    ConceptWhat it isUse when
    Content-basedMatch item features to past likesNew users, rich item metadata
    User-user CFRecommend what similar users likedMany ratings, manageable user count
    Item-item CFRecommend items rated like your likesMore items than users (e-commerce)
    Cosine similarityAngle between two vectors (0–1)Comparing taste regardless of scale
    Matrix factorizationHidden user/item factors, dot productLarge, sparse rating matrices
    precision@kFraction of top-k that were relevantJudging a ranked recommendation list

    ❓ Frequently Asked Questions

    Q: What is a recommender system?

    A: It is software that predicts which items a user will like and surfaces the best ones, usually as a ranked list. The Netflix home row, Amazon's 'customers also bought', and Spotify's Discover Weekly are all recommender systems. They learn from past behaviour — ratings, clicks, watches — to personalise what each person sees.

    Q: What is the difference between content-based and collaborative filtering?

    A: Content-based filtering recommends items similar to ones you already liked, using the items' own features (genre, tags, text), so it never needs other users. Collaborative filtering ignores item features and instead finds patterns across users: 'people who rated things the way you do also liked X'. Content-based avoids the cold-start problem for new users but tends to recommend more of the same; collaborative filtering discovers surprising picks but struggles when data is sparse.

    Q: What is the difference between user-user and item-item collaborative filtering?

    A: User-user CF finds people whose taste matches yours and recommends what those neighbours liked. Item-item CF instead measures which items are rated alike and recommends items similar to ones you already enjoyed — 'because you watched Inception'. Item-item is usually preferred in production because items are more stable than users and their similarities can be precomputed, which scales better.

    Q: What is cosine similarity and why use it for recommendations?

    A: Cosine similarity measures the angle between two rating vectors: it is the dot product divided by the product of their lengths, giving a number from 0 (unrelated) to 1 (identical direction). It is popular for recommenders because it compares the direction of taste rather than its magnitude, so a generous rater who gives lots of 5s and a stingy rater who gives 3s can still count as similar if they rank things the same way.

    Q: What is matrix factorization in recommender systems?

    A: Matrix factorization decomposes the big, mostly empty user-item rating matrix into two smaller matrices of hidden 'latent factors' — one describing each user, one describing each item. Multiplying a user's factor vector by an item's factor vector predicts the missing rating, which fills in the gaps. It powered the winning Netflix Prize entries and handles sparse data far better than counting shared ratings directly.

    Q: What is the cold-start problem?

    A: Cold start is the difficulty of recommending for a brand-new user or a brand-new item that has no interaction history yet — collaborative filtering has nothing to compare. The usual fixes are to fall back on content-based filtering (which only needs item features), to recommend popular items at first, or to ask the user a few onboarding questions to seed a profile.

    Q: What is precision@k and how do you measure a recommender's quality?

    A: precision@k looks only at the top k items you recommended and asks what fraction of them the user actually found relevant — precision@5 of 0.6 means 3 of your top 5 were good. Because users only ever see the top of the list, ranking metrics like precision@k, recall@k, MAP, and NDCG matter far more than overall rating error (RMSE), which judges predictions the user never sees.

    🎯 Mini-Challenge: A Tiny Content-Based Recommender

    Time to fly with less scaffolding. The starter gives you a taste profile and three unwatched movies plus a comment outline — write the logic yourself. Reuse the cosine similarity you have built twice already.

    Mini-Challenge

    Score every candidate against the profile, sort the list, and print the top recommendation

    Try it Yourself »
    Python
    # 🎯 MINI-CHALLENGE: a tiny content-based recommender
    #
    # You have one user profile and three unwatched movies (4 features each).
    # 1. Write (or reuse) a cosine_similarity(a, b) function.
    # 2. Score every candidate against the profile.
    # 3. Print each movie with its score, sorted from best to worst.
    # 4. Print the single top recommendation.
    #
    # ✅ Expected: "Dune" should win — it is the closest match to a sci-fi taste.
    
    import math
    
    profile = [0.9, 0.0, 1.0, 0.3]   # [action, romance, sci_fi, dra
    ...
    🎉

    Lesson 45 complete — you can now build a recommender from scratch!

    You can compute cosine similarity by hand, run user-user and item-item collaborative filtering, recommend from item features with content-based filtering, explain the intuition behind matrix factorization, and grade a ranked list with precision@k. You also know the four traps: cold start, popularity bias, treating sparse data as zeros, and leakage in evaluation.

    🚀 Up next: Graph Neural Networks — model relationships in social networks and knowledge graphs.

    Sign up for free to track which lessons you've completed and get learning reminders.

    Previous

    Cookie & Privacy Settings

    We use cookies to improve your experience, analyze traffic, and show personalized ads. You can manage your preferences below.

    By clicking "Accept All", you consent to our use of cookies for analytics and personalized advertising. You can customize your preferences or reject non-essential cookies.

    Privacy PolicyTerms of Service