RecSys · textbook
Trainer Widgets Revision About All chapters ← History Exploration →

Part IV · Sequences and the slate · chapter 15 of 19

Re-ranking and diversity

The ranker has produced the scores; all that is left is to take the top \(k\). That could be the end of it — if an item's value did not depend on what stands next to it. But it does, and that is why there is a separate layer that assembles not a list of the best items but the best list. These are different problems, and the second is the harder one.

What to take away
  • The best slate is not the top by score. A less relevant item from another topic gives an output 48% better than a more relevant duplicate.
  • Diversity is bought cheaply, but only at first. The first few percent cost 7.6% of relevance and cover every topic; the further 0.07 of diversity costs another 13 percentage points.
  • Greedy selection is not a hack here. On a submodular function it guarantees no worse than 63.2% of the optimum, while enumerating a slate of 10 from 500 candidates is 2.5 · 1020 options.
  • An offline metric always votes for \(\lambda = 1\). It cannot count the value of dissimilarity, so only an A/B test decides.

1. Why the top by score is not the best output

A ranking model scores items one at a time: \(f(u, i)\) does not know what else will end up in the output. And the user sees the list as a whole, with the items in it interacting.

The arithmetic of an externality

Two items, each with a click probability of 0.20. If they are independent, the probability of at least one click is 0.36. And if they are near-duplicates, the second adds nothing: 0.20.

Now take a third item — from another topic, with a lower probability of 0.12:

SlateProbability of a click
original + duplicate (scores 0.20 and 0.20)0.2000
original + another topic (scores 0.20 and 0.12)0.2960

The numbers are reproduced by the script _tools/rerank_demo.py in this repository.

A less relevant item gives a slate 48% better. A ranker that honestly sorted by score would have put the duplicate there — and would have been formally right about every item taken separately.

Hence the existence of a separate layer: the problem «choose the best \(k\) items» and the problem «assemble the best list of \(k\) items» are different, and the second does not reduce to the first by sorting.

Diversity is not exploration

These two are easy to confuse, and in an interview the confusion shows immediately. The difference is in the goal, not in the mechanism.

  • Exploration: we change the logging policy for the sake of changing the policy — to obtain data about what we have not been showing. That is the story of the feedback loop, and the next chapter is about it.
  • Diversity: we change the output for product reasons — because a list of ten nearly identical products is bad for the user here and now, regardless of what data we will collect.

The mechanism is similar — in both cases we deviate from the greedy top. But exploration is paid for with the future quality of the model, and diversity with the user's present satisfaction. Those are different budgets and different metrics of success.

2. How diversity is measured

Before optimising it you have to be able to measure it. Three levels, and it is useful to keep them apart.

LevelMetricWhat it catches
Inside one outputintra-list diversity — the average dissimilarity of pairs in the slate; the entropy of categories«ten identical products in a row»
Per user over a periodthe share of categories new to them, serendipity«they have been locked into one topic»
Across the whole systemcoverage and Gini over impressions«the catalogue is not turning over, the tail is not being shown»
The entropy of categories — why it beats a simple count

A slate of 10 positions, different arrangements by category:

ArrangementCategoriesEntropyMaximum
one category10.00000.0000
8 + 1 + 130.63901.0986
5 + 3 + 231.02971.0986
4 + 3 + 331.08891.0986
2 in each of five categories51.60941.6094

The numbers are reproduced by the script _tools/rerank_demo.py.

Compare the second and fourth rows: there are three categories in both, and the entropy differs twofold. A simple count of categories would say those slates are equal — while «eight plus one plus one» is an output from a single topic with two random specks in it.

The metric penalises not only a single topic but also a skew. That is exactly what is wanted.

3. Business rules: the most common option

Before talking about MMR and DPP it is worth saying honestly that neither is what usually runs in production. The most widespread and cheapest approach is greedy re-ranking under an explicit rule.

What the rule «no more than two in a row» costs

Candidates with scores and categories: 0.95A, 0.93A, 0.91A, 0.90A, 0.88A, 0.86B, 0.84A, 0.80C, 0.78B, 0.70C. We take six.

without the rule0.95A 0.93A 0.91A 0.90A 0.88A 0.86Bsum 5.432 categories
with the rule0.95A 0.93A 0.86B 0.91A 0.90A 0.80Csum 5.353 categories

The loss is 1.47% of the sum of scores.

The numbers are reproduced by the script _tools/rerank_demo.py.

And here is why it works out so cheaply — a thought worth saying out loud in an interview. The scores of neighbouring candidates by rank are almost identical. Swapping one item for another a few positions down, we lose hundredths and gain a structural change in the output.

The flip side: if the scores diverge sharply, the same swap becomes expensive. The cost of a rule is not a constant of the product but a property of the distribution of scores, and it has to be measured rather than assumed.

The upsides of the approach: simple, predictable, easy to explain to product and easy to roll back. There is one downside, but a substantial one — a rule knows nothing about substantive similarity. Two products from different categories can be practically identical, and the rule will be formally satisfied.

4. MMR

Greedy selection with a penalty for similarity

We build the slate greedily. At step \(k\) we choose the item:

$$ i_k = \arg\max_{i \notin S}\Bigl[\lambda \cdot \mathrm{rel}(u,i) - (1-\lambda)\cdot \max_{j \in S}\mathrm{sim}(i,j)\Bigr] $$

where \(S\) is the set of elements already chosen, similarity is usually computed over a content embedding, and \(\lambda\) is a hyperparameter. It reads literally: «take the most relevant thing among what is not too similar to what has already been taken».

Note the \(\max_{j \in S}\): the penalty runs over the nearest item already chosen, not over the average. That matters — one duplicate in the slate poisons a candidate entirely, even if it resembles nothing else there.

The trade-off curve

18 items in three topical clusters, a slate of five. The relevance is concentrated in one cluster — as it tends to be, since similar items get similar scores.

\(\lambda\)Clusters coveredDiversitySum of relevanceLoss
1.010.31734.37450.0%
0.910.31734.37450.0%
0.820.61874.22803.3%
0.730.81594.04257.6%
0.630.81594.04257.6%
0.330.86913.695015.5%
0.030.88743.464220.8%

The numbers are reproduced by the script _tools/rerank_demo.py; the same script independently repeats the widget's computation.

This table has to be read bottom-up and top-down at the same time.

  • At \(\lambda = 1\) this is the ordinary top, and the whole output has slid into one cluster — 1 of 3 covered. The reason is exactly the one described above: similar items get similar scores, so a top by score is topically homogeneous by construction.
  • The first few percent of diversity are almost free. Moving to \(\lambda = 0.7\) covers all three clusters and raises diversity from 0.32 to 0.82 — for 7.6% of relevance.
  • After that the price rises sharply. From \(\lambda = 0.7\) to \(\lambda = 0\) diversity adds only 0.07 while the loss grows from 7.6% to 20.8%.

The shape of the curve is typical and is itself the practical advice: work at the knee, not at the ends.

Offline always votes for \(\lambda = 1\)

Note the first row of the table: at \(\lambda = 1\) the sum of relevance is maximal by definition. And NDCG will be maximal, and Recall@k, and any other offline ranking metric.

Offline metrics cannot count the value of dissimilarity. They evaluate an output as a set of independent items — the very assumption because of which this layer was needed in the first place.

The practical consequence is hard: \(\lambda\) cannot be chosen offline. Only an A/B test, and only by product metrics — return rate, session length, the share of users interacting with more than one topic.

What to look for here
  1. Set \(\lambda = 1\) and look at the picture: the selected points huddle in one cluster.
  2. Lower \(\lambda\) and watch two numbers at once — diversity and the sum of relevance. Find the knee.
  3. Switch to DPP mode and compare: there is no manual \(\lambda\) there, the trade-off is built into the construction.

What to say in an interview: «MMR is greedy selection with a penalty for similarity to what is already chosen. Offline it always looks like a loss, because offline metrics cannot count the value of dissimilarity; only an A/B test decides».

5. DPP: diversity through volume

A determinant instead of two terms

We define a distribution over subsets in which the probability is proportional to the determinant of the corresponding submatrix of a similarity matrix, and sample from it:

$$ P(S) \;\propto\; \det\bigl(L_S\bigr), \qquad L = \operatorname{diag}(q)\, S\, \operatorname{diag}(q) $$

Here \(q\) are the relevances and \(S\) is the similarity matrix. The intuition is geometric and worth remembering: the determinant of a Gram matrix equals the squared volume of the parallelepiped spanned by the vectors.

similar items: the vectors are nearly collinear area ≈ 0 det(L_S) → 0, the subset is almost never sampled different items: the vectors are nearly orthogonal a large area det(L_S) is large — such a slate is likely
The lengths of the vectors set the relevance, the angles the dissimilarity. The volume accounts for both at once.
How the volume reacts to angle and to length

For two items of unit length \(\det = 1 - \cos^2\theta = \sin^2\theta\):

Angle15°30°60°90°
\(\det\)0.00000.06700.25000.75001.0000

Now let us fix the angle at 60° and move the relevance of the first item:

\(q_1\)0.51.02.0
\(\det\)0.18750.75003.0000

The numbers are reproduced by the script _tools/rerank_demo.py.

Collinear vectors give \(\det = 0\) — such a subset is not sampled at all, not «more rarely» but never. And the determinant grows as the square of relevance and as the square of the sine of the angle.

Hence the main advantage of DPP over MMR: one construction accounts for both relevance (the lengths) and diversity (the angles), with no manual \(\lambda\) balancing two terms of different natures.

An honest disclaimer

Building such a distribution explicitly is very hard. Algorithms for approximate sampling in reasonable time exist, but in real production DPP is rarely used.

The reason is engineering: even approximate algorithms usually require the similarity matrix to be computed explicitly and decomposed. For hundreds of candidates per request and a budget of a few milliseconds that is expensive — while MMR requires exactly \(k \cdot |C|\) similarity computations and always fits.

DPP is worth knowing because people ask about it and because the geometric intuition is useful in itself. Putting it into production — probably not.

6. Why greedy selection is a legitimate algorithm

MMR, business rankers and almost everything else in this layer are greedy. A fair question arises: is that not a botch? The answer is no, and it has a proof.

Submodularity and the guarantee

The «usefulness of a slate» function is usually monotone (adding an item does not hurt) and submodular: every next item adds no more than it would have added had it been taken earlier. This is a formalisation of saturation — the second product of the same category is less useful than the first.

$$ f(S \cup \{i\}) - f(S) \;\ge\; f(T \cup \{i\}) - f(T) \quad \text{for } S \subseteq T $$

For such functions there is a classical result: the greedy algorithm gives no worse than \(1 - 1/e \approx 0.6321\) of the optimum.

Why that is not a consolation prize

The alternative to greediness is enumerating subsets:

a slate of 5 from 18 (as in the widget)8 568 optionsenumerable
a slate of 10 from 500 (as in production)2.46e+20 optionsimpossible

The numbers are reproduced by the script _tools/rerank_demo.py.

At the real sizes of the problem, greediness is the only option that has any proven guarantee at all. That is not a compromise on quality, it is the one point on the map where quality is bounded from below by anything.

7. An autoregressive slate

There is also a fundamentally different route that removes the problem at once. If ranking is solved in the sequential formulation — predicting the next positive item — then the slate can be built autoregressively: every next item is chosen given those already chosen.

Why this is elegant and why it is not free

Elegant: the model learns for itself that after a pair of trainers it should not show four more pairs of trainers — because in the training data trainers are usually not followed by the same thing. Diversity comes out as a side effect of the right formulation rather than as a separate layer with a manual \(\lambda\). This is the same technique as generative retrieval: replace an external constraint with the structure of the problem.

Not free: it needs \(k\) sequential passes instead of one batch (position \(k\) cannot be computed without having chosen \(k-1\)), and that is exactly what the budget cannot bear. Plus the model inherits its diversity from the logs — and the logs were collected by a previous policy that was not distinguished by diversity.

Interview questions

Why is a separate re-ranking layer needed if the ranker has already sorted everything?

Because the ranker scores items one at a time while the user sees the list as a whole, with the items interacting. Two items with a click probability of 0.20 give 0.36 if they are independent and only 0.20 if they are duplicates. And the original plus a less relevant item from another topic (0.12) gives 0.296 — a slate 48% better.

So «choose the best k items» and «assemble the best list of k items» are different problems, and the second does not reduce to the first by sorting.

How does diversity differ from exploration?

By the goal, not the mechanism. In exploration we deviate from the greedy top in order to obtain data about what we have not been showing — we pay with present quality for the future quality of the model. In working with diversity we deviate for product reasons: a list of ten nearly identical products is bad here and now, regardless of what data we will collect.

The mechanism is similar, but the budgets and the metrics of success are different.

How do you measure diversity?

At three levels. Inside the output — intra-list diversity (the average dissimilarity of pairs) and the entropy of categories. Per user over a period — the share of categories new to them, serendipity. Across the system — coverage and Gini over impressions.

Entropy is more convenient than a simple count of categories because it also penalises a skew: the slates «8 + 1 + 1» and «4 + 3 + 3» both contain three categories, but the entropies are 0.639 against 1.089 — nearly double. And «8 + 1 + 1» is an output from one topic with two random specks.

What is MMR?

Greedy selection with a penalty for similarity to what is already chosen: at every step we take \(\arg\max [\lambda\,\mathrm{rel}(u,i) - (1-\lambda)\max_{j\in S}\mathrm{sim}(i,j)]\). The penalty runs over the nearest item already chosen rather than the average: one duplicate in the slate poisons a candidate entirely.

The shape of the trade-off curve is typical: the first few percent of diversity are almost free (all three clusters are covered for 7.6% of relevance, and diversity grows from 0.32 to 0.82), after which the price rises sharply — the remaining 0.07 of diversity costs another 13 percentage points. Work at the knee.

How do you choose λ?

Only by A/B. Offline metrics always vote for λ = 1: at that value the sum of relevance is maximal by definition, and so are NDCG and Recall@k. They evaluate an output as a set of independent items — the very assumption because of which the re-ranking layer was needed at all.

What has to be measured is product metrics: return rate, session length, the share of users interacting with more than one topic.

What is a DPP and why does a determinant mean diversity?

We define a distribution over subsets with \(P(S) \propto \det(L_S)\), where \(L = \mathrm{diag}(q)\,S\,\mathrm{diag}(q)\), and sample from it. The determinant of a Gram matrix is the squared volume of the parallelepiped on those vectors.

Collinear vectors (nearly identical items) give a volume of zero, and such a subset is never sampled at all. Orthogonal ones give the maximum. For two unit vectors det = sin²θ: 0 at 0°, 0.75 at 60°, 1 at 90°. And the lengths are responsible for relevance — the determinant grows as its square.

The advantage over MMR: one construction accounts for both relevance and diversity, with no manual λ balancing two terms of different natures. But it is rarely used in production — even approximate algorithms require the similarity matrix to be computed explicitly and decomposed, which does not fit the budget.

Why is a greedy algorithm acceptable here?

Because the usefulness of a slate is usually monotone and submodular: every next item adds no more than it would have added earlier — a formalisation of saturation. For such functions the greedy algorithm gives no worse than 1 − 1/e ≈ 0.632 of the optimum.

And that is not a consolation prize: enumerating a slate of 10 from 500 candidates is 2.5·10²⁰ options. At the real sizes of the problem, greediness is the only option that has any lower bound at all.

Can you do without a separate diversity layer?

Yes, if the slate is built autoregressively: solving the problem as predicting the next item given those already chosen. Then the model learns for itself that after a pair of trainers it should not show four more — diversity comes out as a side effect of the formulation, with no manual λ.

The price: k sequential passes instead of one batch, because position k cannot be computed without choosing the previous ones — and that is exactly what the ranking budget cannot bear. Plus the model inherits its diversity from logs collected by a previous policy.

One-screen cheat sheet

Why the layer

The score is per item, the output is shared. A duplicate: 0.20 against 0.296 for a less relevant item from another topic.

Do not confuse

Exploration is for data. Diversity is for the user now. Different budgets.

Metrics

ILD and entropy in the slate, novelty per user, coverage and Gini across the system.

Rules

The most common option. «No more than two in a row» costs 1.47% — neighbouring scores are nearly equal.

MMR

\(\lambda\,\mathrm{rel} - (1-\lambda)\max_j \mathrm{sim}\). The penalty by the nearest. The knee of the curve is 7.6%.

λ only online

Offline is always for \(\lambda=1\): the metrics cannot count the value of dissimilarity.

DPP

\(\det\) = a squared volume: lengths give relevance, angles diversity. Rarely in production.

Greediness

Submodularity gives \(1-1/e = 0.632\). Enumerating 10 from 500 is 2.5e20 options.

Primary sources