Vocabulary for this course

The terms the chapters assume, in plain words. You can open this from any chapter.

cold start
The problem of recommending for a new user or a new item that has no interactions yet.
collaborative filtering
Recommending items by finding users with similar taste, or items that similar users liked, without reading item content.
explicit feedback
A signal where the user states a preference, such as a rating or a like, rather than one inferred from behaviour.
implicit feedback
A signal inferred from behaviour, such as a click, watch or purchase, that says something happened but not how much it was liked.
matrix factorisation
A method that gives each user and item a short list of learned numbers, so their match is the sum of products.
nDCG
A ranking score that gives more credit to relevant items near the top, then divides by the score of the best possible order.
popularity baseline
A list ordered by how many interactions each item has, used as the simple result every model must beat.
precision at k
The share of the first k items that are relevant, where k is the number of slots shown.
ranking
The second stage that orders a short list of candidates for one user, usually with a slower model.
recall at k
The share of all relevant items that appear in the first k positions.
relevance
How likely one user is to engage with one item; a ranking needs it per user, not as a global score.
retrieval
The first stage that cuts a large catalogue down to a few hundred candidates cheaply.
slot
One position on the screen that can hold an item; the fixed count of them limits how long a list can be.
two-tower retrieval
A design with one network for users and one for items, so the item side is computed before a request.

Recommender systems from the ground up

Content-based filtering

Content-based filtering turns items into feature vectors and a user into the average of what they liked. It works before anyone else has interacted.

Part 2 of 10

Intermediate15 min readBack to Courses

Items as vectors

Content-based filtering ignores other users. It describes each item by its features: the things you can measure about it. A feature might be a genre, a tag, a TF-IDF score for each word, or an embedding. TF-IDF gives a word more weight when it is rare across the catalogue. An embedding is a list of numbers that puts like items near each other. The system then recommends items that look like the ones a user has liked. The model is a similarity function, and the feature set is the real design choice.

That makes the approach clear. Every result can be traced to a feature the user has shown interest in. That helps with explanations and with debugging. The approach is also brittle. If the features do not describe what people care about, no amount of tuning the similarity will fix it.

The choice of features decides what counts as similar. One-hot tags (a list of zeros with a one for each tag present) make every shared tag count the same. TF-IDF makes a rare tag count more. An embedding folds synonyms together. None of these is more “correct” than another. Each gives a different list of neighbours, and the reader should know which one the product uses.

The profile is an average

A user profile (one vector that stands for a user’s taste) is built from the items they consumed. It is the mean of their vectors, or a weighted mean where recent and high-rated items count more. Scoring is then a dot product or a cosine between the profile and every unseen item. Sort the scores and cut at the slot budget.

Cosine ignores length and compares direction. That is what you want when item vectors have different lengths. A dot product rewards long vectors, so scale to unit length first unless length means something. The choice looks small. It changes the ranking more than most settings you pick before training.

Weighting is where a content model becomes personal. A rating of five can count three times a rating of three. A like from last week can count more than a like from last year. The weights are settings with a product meaning. Choose them from behaviour, not from a grid search on a metric that never sees the page.

TF-IDF and cosine in scikit-learn

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

vectorizer = TfidfVectorizer(stop_words="english")
matrix = vectorizer.fit_transform(descriptions)
profile = matrix[liked_indices].mean(axis=0)
scores = cosine_similarity(profile, matrix).ravel()
print(scores.argsort()[::-1][:10])

A profile from tags

Run the scorer and read the top three. Then weight the second liked item twice and see which recommendation moves to the front.

Output
Click "Run" to execute your code

What it can and cannot do

It can recommend a brand-new item as soon as its features exist. It can also explain a result: you liked something similar. It cannot find a taste the user has never shown. It also tends to overspecialise. The profile keeps pointing at the same corner of the catalogue until every list looks alike.

The fix is not a better similarity. It is variety in the candidate set (the items the system is allowed to rank). It is also deliberate exploration and a profile that fades, so old likes stop dominating. Content-based filtering is a strong cold-start tool: it works when a user or an item is new. It is a weak serendipity tool: it rarely surprises. Knowing which job you are giving it is most of the design.

Content features are also the honest answer where data from other users is thin or sensitive. A new marketplace cannot wait for co-occurrence (users touching the same items). Neither can a regulated catalogue, or a library with one copy of each title. The trade is that the results can only be as good as the descriptions. Someone has to own the metadata.

Normalise item vectors before averaging them into a profile. Otherwise one long, feature-rich item can dominate a user’s taste for months.

Features are the model

Two systems with the same algorithm and different features act like different products. A coarse genre tag says little. A well-written description carries style, era and mood. Text features are cheap and strong. They also inherit every bias and every gap in the copy.

Normalise, deduplicate and version the feature pipeline. When the features change, the results change. Without a version you cannot tell whether the model got better or the catalogue was rewritten underneath it. A feature diff belongs next to the model diff.

Text preprocessing is modelling. Lowercasing, dropping stop words (common words such as “the”), stemming (cutting a word to its root) and splitting on punctuation all change the vector. A pipeline that does them differently at training and at serving time is a skew bug. Keep the preprocessing in one function. Version it with the features, and test it on the same fixture.

Not every word is a signal

Run the scorer, then remove the word music from every description and confirm the ranking barely changes. A term that appears everywhere carries no information.

Output
Click "Run" to execute your code

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What does content-based filtering need that collaborative filtering does not?