Content-Based Recommendation
Recommend from what items and users are, not from who touched what. It works on day one for a new item and is limited to what the attributes can express.
The problem, the obvious approach, and why it breaks
Every lesson starts where the work starts: someone has a problem, and the first model that comes to mind looks fine offline.
When should a recommender use item and user attributes instead of interactions, and what does an attribute-based model structurally miss?
A news product publishes hundreds of articles a day and each one is stale within hours. The editor says: "by the time an article has enough clicks to be recommended, it is old news. We need to recommend it the minute it is published."
Represent each article by its text embedding, represent each user by the mean embedding of what they read, recommend the articles closest to the user vector. No interactions needed, works instantly for a new article.
The user vector is the mean of their history, so the recommendations are more of the same: a reader who read three articles on one topic sees only that topic. The system cannot surface something the attributes do not say the user likes — the discovery that interaction-based models get for free from other users.
- The user vector is the mean of their history, so the recommendations are more of the same: a reader who read three articles on one topic sees only that topic. The system cannot surface something the attributes do not say the user likes — the discovery that interaction-based models get for free from other users.
- The attributes express what the model can see, not what makes an article good. Two articles on the same event with the same entities have nearly identical embeddings; one is excellent and one is thin, and the model cannot tell.
- Offline evaluation against past reads looks fine because past reads are, tautologically, close to the mean of past reads. Online, session length falls because the feed is monotonous.
- A feature the attributes do express — recency — dominates when it is added naively: everything published in the last hour outranks everything else, whatever the user's interests.
What is being predicted, and from what data
This domain leads with these two. A target nobody defined precisely is a label nobody can trust, and a dataset nobody can describe is a model nobody can debug.
- For a user and a freshly published item, a score for whether the user will read it — where "read" is defined as a dwell time above a threshold, not a click (Target Definition).
- The label is available only for articles the user was shown, and for a news product most articles are never shown to most users.
- Item attributes: text, section, author, entities mentioned, publication time. A text embedding from a pretrained encoder is the richest attribute and is available the moment the article exists (Foundation Models).
- User attributes: a profile built from the articles the user read before — the aggregate of their attributes — plus declared interests from onboarding.
- Interactions exist but are useless for a fresh article by construction: an article's interaction history starts at zero and the recommendation must be made before it grows.
How it actually works
Precisely enough to predict its behaviour — not a framework API.
- A content-based model scores a (user, item) pair from their attributes. In its simplest form the score is a similarity between an item vector and a user vector built from the items the user engaged with; in a richer form a model takes both attribute sets and predicts engagement, learning which attribute combinations matter.
- Because the item side depends only on the item's attributes, the score exists the moment the attributes do. That is why it handles new items and why it cannot learn anything the attributes do not encode (Raw Features vs Learned Representations).
- Collaborative signals enter only if they are added as attributes — "users similar to you read this" is an interaction-derived feature — and at that point the model has become a hybrid.
Score from attributes, and the score exists on day one
The simplest content model is a similarity: an item vector from the article's text, a user vector from the articles the user read, a cosine between them (Cosine Similarity). A richer model takes both attribute sets and learns which combinations predict a read, but the structural property is the same — the item side needs nothing but the item.
That property is the whole reason to use the approach. A news article, a job posting, a new product listing all need a recommendation before any user has touched them, and an interaction-based model has nothing to say (Collaborative Filtering).
1def content_score(user_profile, item, now, half_life_hours=6.0):2 # user_profile: several recent-interest vectors, not one mean3 sim = max(cosine(v, item.embedding) for v in user_profile.interest_vectors)4 age_h = (now - item.published_at).total_seconds() / 36005 recency = 0.5 ** (age_h / half_life_hours)6 return sim * recency7 8def rank(user_profile, items, now, k=10, diversity=0.3):9 chosen = []10 while len(chosen) < k and items:11 def adjusted(it):12 base = content_score(user_profile, it, now)13 # penalise similarity to what is already in the list, or the14 # feed converges on one topic15 redundancy = max((cosine(it.embedding, c.embedding) for c in chosen), default=0.0)16 return base - diversity * redundancy17 best = max(items, key=adjusted)18 chosen.append(best); items.remove(best)19 return chosenThe diversity term is not decoration. Without it the top-k is the k nearest articles to the user's dominant interest, and the offline similarity metric will report that as a success while session length falls.
The evaluation that flatters similarity
A held-out set of past reads, scored by how close the recommended articles are to what the user read, rewards exactly the behaviour that makes the feed worse. The model that recommends near-duplicates of the history wins offline and loses users.
The gap closes only online, and only with a metric that a monotonous feed lowers. Click-through does not qualify — users click on what they are interested in, and the monotonous feed is full of it.
Held-out reads were ranked higher by the new content model than by the recency baseline, on a time-based split with new articles only.
Click-through matched the offline expectation; median session length fell and seven-day return rate fell with it.
- 1The feed converged on each user's dominant topic; users read one or two articles and had nothing else to do.
- 2The offline metric rewarded similarity to history, which is the same thing the model optimised, so it could not detect the monotony.
- 3The recency baseline was accidentally diverse — whatever was newest, across all sections — and the diversity was what kept sessions long.
What the attributes must keep meaning
The model is a function of attributes, so its assumptions are about the attribute pipeline: the encoder, the field definitions, the people who fill in the fields. Any of these can change without a single interaction changing, and the model will not notice.
An encoder upgrade is the sharpest case. Every item vector moves at once, the user profiles built from old vectors are in a different space, and similarity between old profiles and new items is meaningless until the profiles are rebuilt.
Item attributes are produced by the same pipeline and encoder version the user profiles were built from, and are not being gamed by whoever creates the items.
holds when The encoder version is pinned and travels with the model; profiles are rebuilt whenever it changes; attribute fields are validated and their distributions monitored.
breaks when An encoder is upgraded in place; a new section taxonomy is introduced; item creators learn that stuffing entities or mislabelling sections raises exposure.
respond Re-embed the catalogue and rebuild profiles under the new encoder before serving from it; validate creator-supplied fields at ingestion rather than trusting them at ranking time.
Which signal should carry the recommendation for this product?
when Items are short-lived or numerous relative to interactions, and rich attributes exist — news, jobs, listings.
cost No discovery beyond what the attributes express; a monotony problem that must be engineered against; an encoder pipeline to operate.
when Items persist, the catalogue is stable, and interaction history is dense — film, music, established retail.
cost Nothing to say about a new item; popularity bias to correct; an exposure-logging requirement from day one.
when Both signals exist and the team can log exposure and manage two feature pipelines.
cost Two pipelines, an exposure bias imported through the interaction features, and a system whose failures are harder to attribute.
How to build it
Most important first.
- Choose attributes that carry the decision: a pretrained text embedding for the article body, plus structured fields the embedding misses (section, recency, length). Validate that the embedding separates articles users treat differently, not merely articles about different topics.
- Build the user representation as more than a mean: keep several recent-interest vectors, or a small learned model over the user's history, so the profile can hold more than one topic.
- Add diversity to the ranking explicitly — a penalty for similarity to what is already in the list — because the score alone will produce a monotonous feed.
- Hybridise as interactions arrive: let a collaborative signal take weight once an item has enough interactions, and let the content score carry it until then (Cold Start).
- Treat recency as a feature with its own decay, not as a filter, and test the decay against session length rather than click-through.
What to measure
Which number actually maps to the decision — and which numbers look relevant and are not.
- Session length and return rate on an A/B test — the numbers that a monotonous feed lowers and click-through does not show.
- Diversity of the served list per user (mean pairwise distance in the attribute space) as a guardrail metric alongside the engagement metric.
- Do not measure similarity between recommended items and the user's history and call it quality. High similarity is the model working as designed and is exactly the failure.
What must stay true after deployment
The field this whole domain exists for. A model is a set of assumptions with weights attached; these are the ones a monitor or a test should be checking.
- The attributes still describe what makes an item engaging for a user — the encoder's notion of similarity still matches the product's notion of relevance.
- The user profile reflects current interests; the history it was built from is recent enough and weighted so that a change of interest shows within days.
- Item attributes are produced consistently: the same pipeline, the same encoder version, the same field definitions as at training time (Preprocessing Lives in the Artifact).
- Offline: hold out by time and evaluate on articles published after the split, so the evaluation is about new items; report diversity of the top list next to the engagement metric.
- Online: A/B test on session length and return rate, with a diversity guardrail; watch for a rising click-through with a falling session length.
- Over time: distribution of served-list diversity and of attribute completeness per week; a sudden change in the embedding space after an encoder upgrade should show as a step in both.
What can go wrong
- The encoder is upgraded and every item vector moves; the user profiles built from old vectors are now in a different space and the similarities are nonsense until profiles are rebuilt (Embedding Drift).
- A declared interest from onboarding is stale for years and the profile keeps a topic the user stopped caring about after a week.
- The diversity penalty is tuned on click-through, which prefers less diversity, so it is tuned to nearly zero.
- The attributes are populated by the people who create the items, and they learn what the model rewards: sections are mislabelled, entity lists are stuffed.
- Works on day one for a new item and never discovers a taste the attributes do not express — the trade is exactly the inverse of collaborative filtering.
- A good attribute pipeline is real infrastructure — an encoder to run, a version to pin, a re-embedding job when it changes — that an interaction-only model does not need.
- Diversity and recency terms are hand-tuned knobs that the offline metric will always want to turn the wrong way.
- "We have embeddings, so we have semantic recommendations." The embedding expresses topic similarity. Whether topic similarity is what makes a user read is an empirical question that the feed's session length answers, often negatively.
- "Content-based has no cold-start problem." It has no *item* cold-start problem. A new user with no history and no declared interests has an empty profile, and the model recommends the centroid.
- "Just add interactions as a feature and get the best of both." That is a hybrid, and it inherits the exposure bias of the interaction feature; the content model was immune only while it ignored the log.
Where this applies
ML advice is stated as universal far more often than it is. These labels say what each claim is specific to — and where CONTESTED appears, the note gives the strongest form of the opposing view.
- DOMAIN-SPECIFICContent-based dominates where items are short-lived or numerous relative to interactions — news, job listings, classified ads — and loses to collaborative signals where items persist and the catalogue is stable enough for interactions to accumulate, such as film or music.
- DATA-SPECIFICThe approach is only as good as the attributes: with a pretrained text or image encoder the attribute space is rich; with a handful of hand-entered categories the model can express little and a popularity baseline may match it.
- CONTESTEDA serious position holds that a pure content model is the wrong starting point and a hybrid should be built from the first version, since every real system needs both signals eventually and the hybrid's exposure bias can be managed with a randomised slice. The argument for starting pure is that the content model is an honest baseline whose failures are legible, and that adding interaction features before exposure logging exists imports the loop without the means to measure it.
Where the depth lives
This domain teaches the model and hands the rest off by name.