←Home KnowML
Decision & Retrieval SystemsChapter 16

Recommenders, Ranking & Search

A billion items, thirty milliseconds, one slot to fill. Every feed you have ever scrolled is the same three-stage funnel solving that, and the interesting part is why it has to be three stages.

24 min read Assumes: embeddings (09), dense retrieval (11)
Start reading
TL;DR

You cannot score a billion items in 30 ms. So production systems narrow first and score later: retrieve a thousand cheap candidates, rank them with an expensive model, then rerank the top few for diversity and business rules.

Retrieval is a two-tower model, because the item tower can be precomputed and the user tower cannot. That single engineering constraint dictates the architecture.

The hardest problem is that your training data was generated by the previous model, so clicks tell you what users saw and only indirectly what they wanted.

01The funnel

Look at the diagram first. Everything else on this page is a detail hanging off one of these three boxes.

One request through the funnel — the numbers are what force the design
CORPUS 10⁹ items RETRIEVAL ~1000 candidates two-tower + ANN ~1 ms · no user-item interaction allowed RANKING ~100 scored heavy cross-features ~20 ms · sees the pair RERANK ~10 diversity, freshness, policy, dedup cost per item ~0 one dot product a full forward pass whole-list logic Each stage is ~10× smaller and ~100× more expensive per item than the one before it.
Read the bottom row. Cost per item rises by orders of magnitude left to right, and the candidate count falls by roughly the same factor — that product is what keeps total latency flat. The funnel is not an optimisation somebody added later. It is the only shape that fits a billion items into 30 milliseconds, and every architectural choice further down this page follows from which box it lives in.

Three stages, each with a different job:

  • Retrieval (also called candidate generation). It reduces a billion items to a thousand, and it must be sublinear in corpus size, so it cannot look at user and item together.
  • Ranking. It scores those thousand precisely, and it can afford rich features because a thousand is a small number.
  • Reranking. It fixes what pointwise scoring cannot see: three near-identical items in the top five, nothing published this week, a policy violation.

The two-stage version of this was set out in the 2016 YouTube paper, and the shape has barely changed since. What changed is what goes inside each box.

02Collaborative filtering: the idea everything else refines

Before any neural network, one observation: you do not need to know what an item is to recommend it.

Collaborative filtering uses only the interaction matrix — who touched what. No genres, no descriptions, no features. Users who agreed in the past will agree again.

Write it as a matrix $R$ with users as rows and items as columns. It is enormously sparse; a typical user has touched a vanishing fraction of the catalogue. Matrix factorization fills the gaps by assuming the matrix is secretly low-rank:

$$R \approx U V^{\top}, \qquad U \in \mathbb{R}^{m \times k},\ V \in \mathbb{R}^{n \times k}$$

Every user and every item becomes a $k$-dimensional vector. A prediction is one dot product.

The idea is easier to believe once you see it work on numbers.

Try it Factorize a tiny ratings matrix and watch the blanks fill in
import numpy as np

# 6 users x 6 items. 0 = never interacted, so the model never sees it.
# Users 0-2 like items 0-2. Users 3-5 like items 3-5. Nobody says so.
R = np.array([[5., 0., 4., 1., 1., 0.],
              [4., 5., 5., 1., 1., 1.],
              [5., 4., 5., 1., 0., 1.],
              [1., 1., 1., 5., 0., 5.],
              [1., 1., 1., 4., 5., 5.],
              [0., 1., 1., 5., 5., 4.]])
mask = R > 0                       # learn from observed entries only

k = 2                              # two latent factors -- the bottleneck
rng = np.random.default_rng(0)
U = rng.normal(0, .1, (6, k))      # user vectors  (6, 2)
V = rng.normal(0, .1, (6, k))      # item vectors  (6, 2)

for _ in range(6000):              # plain SGD, with weight decay
    err = mask * (R - U @ V.T)
    U += .02 * (err @ V - .05 * U)
    V += .02 * (err.T @ U - .05 * V)

P = U @ V.T
print("shapes:", U.shape, "@", V.T.shape, "->", P.shape, "\n")
for (u, i) in [(0, 1), (3, 4), (2, 4), (5, 0)]:
    print(f"user {u}, item {i}: unseen -> predicted {P[u, i]:.1f}")
Look at the four predictions. Every one is a cell the model never saw. It puts user 0 on item 1 at 4.2 and user 3 on item 4 at 5.5, but user 2 on item 4 at 1.0 — it has worked out which group each user belongs to and predicts accordingly. Nothing in the input said there were two groups. That fell out of forcing every user and item through k=2 numbers, which is the entire mechanism.
Why the low rank is doing the work

If $k$ were large enough, $UV^{\top}$ could reproduce $R$ exactly, blanks and all, and you would learn nothing. The bottleneck is the point. Forcing millions of users through 64 numbers means the model must find structure that generalises, because it has nowhere to store the specifics.

This is the bottleneck argument from the autoencoder on page 03: compression forces generalisation.

03Two-tower retrieval, and the constraint that shapes it

The obvious architecture is banned. Understanding why is the single most useful thing on this page.

You want a model that reads a user and an item together and outputs a score. Cross-attention between them, rich interaction features, the works. That model would be far more accurate than a dot product.

You cannot use it for retrieval. At a billion items you would run a billion forward passes per request.

So retrieval accepts a hard constraint: the user and the item must never meet until the final dot product. Two separate encoders, one score:

$$s(u, i) = f_{\theta}(u) \cdot g_{\phi}(i)$$

Two towers, no interaction until the end. That separation is worth a great deal.

Because $g_{\phi}(i)$ does not depend on the user, you can compute every item vector offline, once, and load them into an index. At request time you embed the user, then do approximate nearest neighbour search. The billion never gets touched.

Try it A two-tower model, in-batch negatives, and the shapes at every step
import torch, torch.nn as nn, torch.nn.functional as F
torch.manual_seed(0)

D = 32                                 # embedding width
user_tower = nn.Sequential(
    nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, D))
item_tower = nn.Sequential(
    nn.Linear(48, 128), nn.ReLU(), nn.Linear(128, D))

B = 8
user_feats = torch.randn(B, 64)        # (B, 64)
item_feats = torch.randn(B, 48)        # (B, 48) item i is user i's +ve

u = F.normalize(user_tower(user_feats), dim=-1)   # (B, D)
v = F.normalize(item_tower(item_feats), dim=-1)   # (B, D)

logits = u @ v.T / 0.05                # (B, B) every user vs every item
labels = torch.arange(B)               # the diagonal is the positive
loss = F.cross_entropy(logits, labels)

print("u", tuple(u.shape), "v", tuple(v.shape))
print("logits", tuple(logits.shape), "loss", round(loss.item(), 3))
The trick is logits being (B, B). Each row is one user scored against every item in the batch. The diagonal is the item they clicked; the other B-1 are free negatives you did not have to sample. This is in-batch negative sampling, and it is why batch size matters so much more here than in ordinary supervised training — it is your negative count.
The bias that ships with free negatives

In-batch negatives are drawn from your traffic, so popular items appear as negatives far more often than rare ones. The model learns to push popular items down, which is precisely backwards.

The standard fix is a logQ correction: subtract the log sampling probability of each item from its logit, so frequently-sampled items are not unfairly penalised. It comes from the 2019 Google two-tower paper linked below, and skipping it is a common and quietly expensive mistake.

Where you are

You now have the funnel, the low-rank idea underneath collaborative filtering, and why retrieval must keep the two towers apart. Together these make up the retrieval half of the system.

The rest of this page is the ranking half: what the expensive model does with the thousand candidates, how it is trained, and the several ways the training data lies to you.

04Ranking: where the expensive model earns its keep

A thousand candidates is small enough to score extravagantly, and this stage decides what you see.

Retrieval could not let user and item interact. Ranking can, because it only handles a thousand of them. So the ranker gets everything retrieval was denied: cross features, long user histories, the item's recent click-through rate, the time of day.

The recurring difficulty is that useful signals are usually combinations. "This user likes cooking videos" is weak. "This user likes cooking videos, on a phone, in the evening" is strong. A plain MLP can in principle learn that. In practice, on sparse categorical features, it does so slowly and badly.

Two families of architecture attack this directly:

  • Wide & Deep (Google, 2016) runs two paths side by side. A wide linear model over hand-crafted cross features memorises specific combinations that are known to matter. A deep network generalises to combinations never seen. They are summed and trained jointly.
  • DeepFM (2017) removes the hand-crafting: a factorization machine component learns all pairwise feature interactions automatically, sharing embeddings with a deep component, so no feature engineer is required.
Memorisation and generalisation are both jobs

It is tempting to read the wide path as a legacy component that deep learning should have replaced, but some facts are arbitrary and have to be memorised: this user buys this brand of coffee, for no reason the features explain.

The deep path cannot store that without overfitting everything else, while the wide path can. Wide & Deep accepts that a recommender needs both.

Sequence matters, and modelling it changes the answer

A user's history is ordered, and the order carries information. Somebody who watched three episodes of one series in a row wants the fourth. A bag of their all-time favourites does not capture that.

SASRec (2018) applied a causal self-attention stack to the interaction sequence, predicting the next item from the ones before it. This is a language model over items in place of tokens, with the same architecture from page 08, with a different vocabulary.

05Learning to rank: three ways to phrase the loss

You are producing an ordered list, but the obvious loss functions score one item at a time. That mismatch has three standard answers.

ApproachWhat the loss seesOptimisesCost
Pointwiseone itempredicted score vs. labelcheapest, ignores order entirely
Pairwisetwo itemsis the better one ranked higher?matches the task, quadratic pairs
Listwisethe whole lista ranking metric directlyclosest to the goal, hardest to optimise

Pointwise treats ranking as regression or classification: predict a click probability, sort by it. It is simple and it is what most production rankers do, because sorting by a well-calibrated probability is a strong baseline.

Pairwise asks a narrower and better-posed question. Given a clicked item and a skipped one, is the clicked one scored higher? BPR is the canonical version for implicit feedback.

Try it The BPR loss, which is three lines and one good idea
import torch, torch.nn.functional as F

# For one user: an item they clicked, and one they saw but skipped.
s_pos = torch.tensor([2.10, 1.80, 1.50])   # scores for clicked items
s_neg = torch.tensor([1.30, 1.10, 1.90])   # scores for skipped items

# BPR maximises P(positive above negative) = sigmoid(s_pos - s_neg).
margin = s_pos - s_neg
per_pair = -F.logsigmoid(margin)

print("margins:  ", [round(m, 2) for m in margin.tolist()])
print("per-pair: ", [round(x, 3) for x in per_pair.tolist()])
print("total:    ", round(per_pair.mean().item(), 4))
Only the difference appears. The third pair is ranked wrong — the skipped item scores higher — so its margin is negative and its per-pair loss is 0.913 against 0.371 and 0.403 for the two correct pairs. Notice what is absent: any target value. BPR never claims an item deserves a 4.2 — only that it belongs above another one. For implicit feedback, where no ratings exist, that is the only honest thing to ask.

Listwise goes after the metric itself. The trouble is that ranking metrics are step functions of the ordering — swap two items and NDCG jumps discontinuously — so they have no usable gradient. LambdaRank sidesteps this by defining the gradient directly, weighting each pair by how much swapping it would move NDCG.

The metrics, and what each one refuses to tell you

  • Recall@k. Of the items the user wanted, how many made the top $k$? The right metric for the retrieval stage, where you only care about not losing anything.
  • MRR. One over the rank of the first relevant hit. Right when there is one correct answer, such as a navigational search.
  • NDCG@k. Discounts gains logarithmically by position, then normalises by the best possible ordering. The standard ranking metric because it is the one that knows position 1 beats position 10.
All three are offline metrics, and offline metrics disagree with production

Every metric here is computed against logged data, which records what users did with the old ranker's results. A new model that surfaces something better gets no credit, because nobody ever clicked an item the old system never showed.

So serious recommender teams treat offline metrics as a filter and decide with an online A/B test. Page 24 covers the experimentation machinery.

Where you are

The funnel, retrieval, ranking and the loss functions that train them make up the system working as designed.

What remains is the part that makes recommenders hard, and it is not modelling. It is that the data is a recording of your own previous decisions.

06The data lies, and it lies in a specific direction

If you take one thing from this page beyond the funnel, take this. It is the thing that separates people who have run a recommender from people who have read about one.

A click is not a statement that the user wanted an item. It is a statement that the user wanted it out of what they were shown. And what they were shown was chosen by the previous version of your model.

Three distinct problems fall out of that, and they compound:

  • Position bias. Items at the top get clicked because they are at the top. Train naively and you learn that whatever you ranked first is good, which is circular.
  • Exposure bias. An item never shown has no clicks, so it looks bad, so it stays unshown, and the set of items you can learn about shrinks over time.
  • Popularity feedback. Popular items get recommended, which makes them more popular, which makes the model more certain. The catalogue quietly collapses toward a small head.
Why this is not an ordinary distribution shift

Usually distribution shift means the world changed underneath the model. Here the model changed the world. Your system chooses the impressions, the impressions become the training data, and the training data trains the next system.

It is a closed loop with your own model inside it. A better architecture does not break that loop, so the fixes below all concern data collection and leave the model alone.

The practical mitigations are unglamorous and they work:

  • Model position explicitly. Feed the displayed position in as a feature during training, then fix it to a constant at serving time. The model is forced to attribute some of the click to the slot and not only to the item.
  • Inverse propensity weighting. Weight each logged example by one over its probability of being shown, so rarely-shown items count for more.
  • Deliberate exploration. Spend a small slice of traffic on items the model is uncertain about, specifically to generate data you would not otherwise get.

That last one is a genuine cost. You are knowingly showing something worse to some users now, to have a better system later. Section 07 is about how much to spend.

07Cold start and exploration

Collaborative filtering needs interaction history. New users and new items have none, which is a problem every real system faces daily.

Three cases, and only two have good answers:

  • New item. This one is tractable. Use content features — text, images, category — so the item tower can produce a vector on day zero without any interactions.
  • New user. Fall back to popularity and context (locale, device, referrer), then adapt fast. The first session matters disproportionately.
  • New system. There is no data at all, so the system is content-based or rule-based until enough interactions accumulate, which is why the first version of a recommender is usually not a recommender.

Exploration is the systematic version of the same problem. You can exploit what you know, or explore to learn more. Epsilon-greedy shows a random item some fraction of the time. Thompson sampling is better behaved: sample from the posterior over each item's value and show the argmax, so uncertainty drives exploration and not a coin flip. Page 15 covers the bandit machinery properly.

08Making retrieval fast: ANN indexes

The two-tower model gives you a vector. Finding its neighbours among a billion others is a separate engineering problem with its own literature.

Exact nearest-neighbour search is linear in corpus size, which defeats the point. Approximate nearest neighbour search trades a small amount of recall for orders of magnitude of speed.

Two families dominate:

  • Graph-based (HNSW). It builds a navigable small-world graph over the vectors and greedily walks it toward the query, which gives an excellent recall-latency tradeoff at a high memory cost.
  • Partition and quantise (IVF-PQ, as in FAISS). It clusters vectors, searches only the nearest clusters, and compresses each vector into a compact code, which gives a far smaller memory footprint for some accuracy lost to quantisation.
Recall@k of the index is not recall@k of the system

An ANN index is measured against the exact nearest neighbours of the same embedding model. A 98% recall index means you found 98% of what your model would have ranked highest.

It says nothing about whether the model was right. Tuning the index to 99.5% while the embedding model is mediocre is effort in the wrong place, and it is a common way for retrieval work to stall.

09Build this

The feedback loop in section 06 is the one thing here you should not take on trust. You can watch it happen in an afternoon, with no data and no GPU.

Project Simulate the feedback loop until your catalogue collapses ~3 hours · numpy only

Build a synthetic world where you know the true user preferences, then train a recommender on its own logged clicks, repeatedly. You are not measuring accuracy. You are watching how fast the system stops being able to see most of its own catalogue.

  1. Create 1,000 users and 500 items with known latent vectors. True preference is the dot product. This is ground truth that you never show the model.
  2. Round one: show each user 10 random items. Simulate clicks by sampling from the true preference. Log the impressions and the clicks, not just the clicks.
  3. Train matrix factorization on the logged clicks only. Use it to pick each user's next 10 impressions, simulate again, and repeat for 20 rounds.
  4. Each round, record two things: what fraction of the 500 items has been shown to anyone at all, and mean true preference of what was shown.
  5. Now rerun the whole thing with 10% of slots filled randomly, and plot both curves against the pure-exploitation run.
You'll know it worked when catalogue coverage falls off a cliff in the first few rounds and then flattens near the bottom. The model is not broken and its offline accuracy will look fine — it is scoring well on exactly the items it chose to learn about.
What the exploration arm teaches. The 10%-random run will show lower mean preference early on, which looks like a worse system, and higher coverage throughout. Somewhere around round 8 to 12 the curves usually cross, and after that the explored system is better on the metric the greedy one was optimising. That crossover is the entire argument for spending traffic on exploration, and seeing where it falls for your own parameters is more convincing than any assertion here.

10What breaks

Recommender failures are rarely a bad model. They are a metric that stopped meaning what you thought.

  • Offline metrics improve, the A/B test does not. The single most common outcome. Your offline set was generated by the old ranker, so it rewards agreeing with it.
  • The filter bubble closes. Coverage drops quietly while engagement looks stable, until the catalogue is effectively a few hundred items.
  • Popularity masquerades as personalisation. A model that just predicts global popularity can score respectably. Always compare against that baseline explicitly — it is embarrassing how often it wins.
  • Training and serving features drift apart. The ranker was trained on a feature computed one way in the warehouse and served one computed another way. Page 24 calls this training-serving skew; it is endemic here because there are so many features.
  • In-batch negatives punished popular items. No logQ correction, so the model systematically suppresses exactly the items most people want.
  • The index degraded. Recall@k of the ANN index drifts as the corpus grows and nobody re-tunes it. Measured end to end, this looks like the model getting worse even though the model has not changed.
  • Delayed feedback. A click arrives in seconds, a purchase or a cancellation in days. Train on the fast signal alone and you optimise for clickbait.

11Where you meet this in the wild

Same funnel, very different weightings.

A video feed

Enormous corpus, implicit feedback only, strong sequence signal. Watch time is the target, which immediately raises the question of whether watch time is the thing you want to maximise.

Two-tower retrieval, sequential ranker, heavy diversity reranking.

E-commerce search

The query is an explicit statement of intent, which is a luxury a feed does not have. Relevance and revenue pull in different directions, and resolving that tension is a product decision as much as a modelling one.

Hybrid lexical plus semantic retrieval, then a learned ranker over both.

Music streaming

Repeat consumption is normal and desirable, which breaks the usual assumption that a consumed item should be suppressed. Sessions have a shape: a workout playlist is not a sleep playlist.

Session-based models, strong context features.

Job or dating matching

The market is two-sided, so a recommendation is only good if both parties want it, and one side has finite capacity — a job can be filled, a person can only reply so often.

Reciprocal objectives plus congestion control.

Not a standard one-sided ranker.

12Interview questions

BeginnerWhy do recommender systems use a multi-stage funnel instead of one model?

Because cost per item and number of items cannot both be large. Scoring a billion items with a rich model inside a 30-millisecond budget is impossible, so the system narrows first with something cheap and scores later with something expensive. Retrieval reduces roughly a billion candidates to a thousand using a single dot product per item against a precomputed index. Ranking then scores that thousand with a full model that can afford cross features and long histories. A final reranking stage fixes list-level properties such as diversity and freshness that pointwise scoring cannot see. Each stage is about an order of magnitude smaller and two orders more expensive per item, which is what keeps total latency roughly flat.

BeginnerWhat is collaborative filtering, and what does matrix factorization add?

Collaborative filtering recommends using only the interaction matrix of who engaged with what, with no content features at all, on the assumption that users who agreed before will agree again. Matrix factorization implements that by assuming the sparse user-item matrix is approximately low-rank, factorizing it into a user matrix and an item matrix of some small dimension k, so a prediction becomes a dot product between a user vector and an item vector. The low rank is the essential part: forcing millions of users through a few dozen dimensions means the model cannot memorise individual entries and must find structure that generalises, which is what lets it predict the blanks.

IntermediateWhy must a retrieval model keep user and item towers separate?

So that item vectors can be computed offline. If the architecture lets user and item features interact before the final score, every item's representation depends on the user, and you would have to run the model once per item per request — a billion forward passes. Restricting interaction to a single dot product at the end means the item tower can be evaluated once per item, ahead of time, and stored in an approximate nearest neighbour index. At request time you embed only the user and query the index, which is sublinear in corpus size. The accuracy cost is real, since no cross features are possible, and that is precisely what the ranking stage exists to recover on the surviving thousand candidates.

IntermediateWhat are in-batch negatives, and what bias do they introduce?

In a batch of B user-item positive pairs, you score every user against every item in the batch, giving a B×B matrix whose diagonal holds the true positives and whose off-diagonal entries serve as negatives. It is efficient because the negatives are already encoded, and it makes batch size effectively the negative count. The bias is that negatives are sampled from your traffic distribution, so popular items appear as negatives far more often than rare ones and the model learns to suppress them — exactly backwards. The standard correction is logQ: subtract the log sampling probability of each item from its logit so frequently sampled items are not penalised for being frequent.

IntermediateCompare pointwise, pairwise and listwise learning to rank.

Pointwise treats each item independently, predicting a score or click probability and sorting by it. It is simple, calibrated and ignores the fact that ranking is about relative order, yet it remains a strong production baseline. Pairwise looks at two items at a time and asks only whether the better one is scored higher, which matches the real task better and suits implicit feedback where absolute relevance labels do not exist; BPR is the canonical example. Listwise optimises a ranking metric over the whole list, which is closest to the goal but hardest to train, because metrics like NDCG are step functions of the ordering with no usable gradient. LambdaRank works around this by defining the gradient directly, weighting each pair by the NDCG change that swapping it would cause.

DeepYour offline NDCG improves by 8% but the A/B test is flat. What is going on?

The most likely explanation is that offline evaluation rewards agreement with the system that produced the logs. Your evaluation set records impressions chosen by the current ranker, so a new model can only be credited for items that were already shown; anything genuinely better that the old system never surfaced has no click to be right about. Related causes are position bias, where the model learns that whatever was ranked first gets clicked and so learns the old ranker's ordering, and a train-serve feature mismatch that only manifests online. Diagnose by checking whether the gain persists on a small randomly-ranked exploration slice, which is unbiased by construction, by inspecting how much of the improvement is concentrated in top positions, and by verifying feature parity between training and serving. The general rule is that offline metrics filter candidates for an online test rather than substituting for one.

DeepExplain the feedback loop in a deployed recommender and how you would mitigate it.

The model chooses impressions, impressions generate clicks, clicks become training data, and that data trains the next model, so the system is learning from a world it created. Three effects compound: position bias, where clicks reflect placement rather than preference; exposure bias, where unshown items accumulate no evidence and therefore stay unshown; and popularity amplification, where recommending popular items makes them more popular and shrinks effective catalogue coverage. This is not ordinary distribution shift, because the model caused it, so no architectural change escapes it. Mitigations act on data collection rather than the model: include displayed position as a training feature and hold it constant at serving so the model attributes some click probability to the slot, weight logged examples by inverse propensity so rarely-shown items count for more, and reserve a slice of traffic for deliberate exploration that produces unbiased data. Exploration has a genuine short-term cost, which is the point — you are paying now for data you cannot otherwise obtain.

DeepHow would you handle cold start for a brand-new item in a two-tower system?

Make the item tower depend on content rather than on an ID embedding. If the tower consumes text, images, category and other metadata, it can produce a usable vector the moment the item is created, with no interactions at all, and that vector can be indexed immediately. A pure ID embedding cannot: it is randomly initialised and stays random until interactions arrive, which they will not, because an unindexed item is never shown. In practice you blend the two, learning an ID embedding that gradually takes over as evidence accumulates while the content vector carries the early period. Alongside that, reserve some exploration traffic specifically for new items so they accumulate evidence at all, and expect that without it the system has no mechanism to ever discover them.

13Go deeper

📄
Paper
Deep Neural Networks for YouTube Recommendations
Covington, Adams & Sargin, RecSys 2016 — where the candidate-generation / ranking split was laid out. The origin of the funnel on this page.
📄
Paper
Sampling-Bias-Corrected Neural Modeling for Large Corpus Item Recommendations
Yi et al., RecSys 2019 — the two-tower retrieval model and the logQ correction for in-batch negatives.
📄
Paper
BPR: Bayesian Personalized Ranking from Implicit Feedback
Rendle et al. — the pairwise loss in section 05, and the standard way to train on clicks rather than ratings — arXiv:1205.2618
📄
Paper
Wide & Deep Learning for Recommender Systems
Cheng et al., 2016 — memorisation and generalisation as two paths trained jointly — arXiv:1606.07792
📄
Paper
DeepFM: A Factorization-Machine based Neural Network for CTR Prediction
Guo et al., 2017 — learns pairwise feature interactions without hand-crafted crosses — arXiv:1703.04247
📄
Paper
Self-Attentive Sequential Recommendation (SASRec)
Kang & McAuley, 2018 — a causal transformer over the interaction sequence. Page 08's architecture, item vocabulary — arXiv:1808.09781
📄
Paper
Efficient and robust approximate nearest neighbor search using HNSW graphs
Malkov & Yashunin — the graph index behind most vector databases — arXiv:1603.09320
📄
Paper
Billion-scale similarity search with GPUs (FAISS)
Johnson, Douze & Jégou — IVF-PQ and the memory-accuracy tradeoff in section 08 — arXiv:1702.08734
🔧
Tool
FAISS
The library to build the index with. Start here before reaching for a hosted vector database.

Other techniques for this problem

A scoped slice of the full Technique Map — every technique this page covers, grouped by what it solves.

My Notes — 16 Recommenders, Ranking & Search

Free notes

Highlights on this page