Fairness & diversity
Every lesson before this one optimized one number: predicted engagement, ranked well. This lesson is about what that single-minded optimizer does to the system around it — and the re-rank-stage corrections that fix it.
The pathologies of pure accuracy
Suppose your ranker is perfect at predicting the next click. Two failure modes still emerge — structural, not bugs.
Filter bubbles / echo chambers. A user who watched three cooking videos gets ranked toward cooking, clicks more cooking, gets ranked harder toward cooking. The system narrows the user. Short-term this looks great — high CTR, high relevance — but it produces aesthetic fatigue, kills discovery of latent interests, and caps long-term retention. The user's world collapses to the eigenvector of their own past behavior.
Popularity bias & the Matthew effect. Popular items have more interactions → better-estimated, higher predicted scores → more exposure → more interactions. "To those who have, more will be given." A small head captures a disproportionate share of impressions; the long tail and new creators starve. This is the supply-side mirror of the filter bubble — and it is a self-reinforcing feedback loop, not a one-shot error.
The feedback loop, drawn
The flywheel — "clicks become tomorrow's labels" — is also the engine of bias. Here is the loop that manufactures popularity bias, and the two places we cut into it. This entire lesson lives at the re-rank stage of 01 · The funnel.
Two cuts, two layers. Debiasing attacks the bottom edge — it corrects the labels before they teach the model. Diversity and fairness re-ranking attack the top edge — they override the score at serving time to inject tail exposure. The rest of the lesson is these two cuts in detail.
| Day | A impressions | A clicks | B impressions | B clicks |
|---|---|---|---|---|
| 1 | 1,000 | 50 | 1,000 | 48 |
| 2 | 1,040 | 52 | 960 | 46 |
| 5 | 1,300 | 65 | 700 | 34 |
| 10 | 1,800 | 90 | 250 | 12 |
Measuring diversity — the metrics you must know
You cannot trade off what you cannot measure. Borrowing from 08 · Evaluation, diversity metrics live at two scopes: within one list (did this user's feed look varied?) and across the whole system (did the catalog get explored?).
| Metric | Scope | Definition | Reads as |
|---|---|---|---|
| Intra-list similarity (ILS) | one list | ILS = 2 / (|L|(|L|−1)) · Σi≠j sim(i, j) — mean pairwise similarity inside the list | lower = more diverse list |
| Category entropy | one list | H = −Σc pc log pc over category shares pc | higher = flatter mix (aim ~3–5 bits) |
| Coverage | system | |∪u Lu| / |I| — fraction of the catalog ever recommended | higher = more of the tail surfaced |
| Gini coefficient | system | inequality of the exposure distribution across items/creators | 0 = perfectly even, →1 = winner-take-all |
| Novelty | list / system | ∝ Σ 1 / log(popularityi + 1) — inverse popularity | higher = more unfamiliar items |
MMR — diversity as a greedy re-rank
The classic re-rank-stage cure. Maximal Marginal Relevance builds the output list greedily: at each step pick the candidate that is both relevant and dissimilar to what you've already chosen. With S = items chosen so far:
next = arg maxd ∉ S [ λ · rel(q, d) − (1−λ) · maxd′ ∈ S sim(d, d′) ]
The first term is the ranker's relevance score (the output of 04 · Ranking models). The second penalizes a candidate for resembling anything already in the list. λ is the single dial: λ = 1 is pure accuracy (reproduce the ranker), λ = 0 is pure diversity (ignore relevance). It is O(k · n) greedy — cheap enough for online serving, which is why it dominates real-time scenarios like an infinite-scroll feed.
Turn the dial and watch the list change. A toy candidate pool — each item has a relevance score and a category; MMR re-orders the top picks and the two KPIs move in opposite directions.
Interactive · the MMR λ dial
λ near 1: the list is the raw ranker output (categories clump at the top). Drag λ down: MMR demotes items whose category already appears, spreading the feed — at the cost of average relevance. There is no free lunch; watch both numbers move.
DPP — diversity with a determinant
MMR is a heuristic — greedy, pairwise, no global guarantee. Determinantal Point Processes are the principled version. Build a kernel matrix L where the diagonal Lii encodes item quality (relevance) and the off-diagonal Lij encodes item similarity. The probability of selecting a subset S is proportional to a determinant:
P(S) ∝ det(LS)
The geometry is the whole idea: det(LS) is the squared volume of the parallelepiped spanned by the items' feature vectors. Two near-identical items span almost no volume → near-zero probability; a spread-out set spans large volume → high probability. So maximizing the determinant jointly maximizes quality (long vectors) and diversity (orthogonal vectors) in one objective — no separate λ term, the kernel carries both.
Fairness — the two-sided marketplace
Diversity is about one user's list. Fairness is about the distribution across groups, and a recommender has two sides to be fair to.
| Side | Whose fairness | The concern |
|---|---|---|
| User side | consumer groups (gender, region, age, new vs returning) | do different user groups get comparably good, comparably diverse recommendations? Are new / low-activity users systematically served worse? |
| Item / provider side | creators & content (head vs long tail, big vs small creators, minority-language content) | is exposure allocated fairly, or does the Matthew effect concentrate all traffic on a head and drive small creators to leave? |
This is fundamentally a marketplace health problem, not a courtesy. Provider unfairness starves the supply side: if small and new creators never get exposure, they stop producing, the catalog homogenizes, and the consumer experience degrades a quarter later. Fairness on the supply side is an investment in tomorrow's inventory — and it interlocks directly with 15 · Cold start, where a brand-new item has no interaction history and will never escape the cold unless the system deliberately grants it impressions.
Group-fairness notions
Borrowed from the ML-fairness literature, two are load-bearing:
- Demographic parity — each group's share of exposure matches its share of the population / catalog. "Items from group A get exposure in proportion to how many group-A items exist." Simple, but blind to quality.
- Equal opportunity — among genuinely good items (those that would satisfy a user), the chance of getting exposed is equal across groups. Conditions on quality, so it doesn't force junk onto users to hit a quota.
Plus individual fairness ("similar users get similar recommendations") and counterfactual fairness ("the recommendation wouldn't change if we flipped a sensitive attribute, holding everything else fixed"). You measure violations with exposure Gini, the group exposure-ratio (e.g. minority / majority ≈ τ), and cross-group AUC / CTR gaps.
Debiasing levers across the stack
Fairness and diversity can be enforced at any of three layers — the standard pre / in / post taxonomy. Each maps to a stage of 11 · System design and carries a different cost.
| Layer | Mechanism | Pro | Con |
|---|---|---|---|
| Pre-processing (data) | re-sample / re-weight training data; adversarial debiasing (a discriminator can't predict the sensitive attribute from the embedding) | fixes bias at the root; model stays simple | may discard useful signal; costly to rebuild when the catalog churns hourly |
| In-processing (model) | fairness regularizer / exposure-variance penalty in the loss; constrained optimization; multi-task heads (MMoE / PLE) with a fairness objective | optimizes the real objective directly; adapts to new content | adds model complexity & training cost; can slow inference |
| Post-processing (re-rank) | MMR / DPP diversity; exposure quotas ("≥20% tail per session"); a guaranteed-impression floor for cold items | easiest to ship; trivially A/B-testable; model untouched | can wreck the ranker's ordering; over-compensation drops watch time |
Inverse-propensity weighting — debiasing the labels
The most important tool, because it attacks the bottom edge of the feedback loop directly. The problem: a click is only observed if the item was exposed, and exposure is not random — popular items are over-exposed. So a naïve average over clicks over-counts items the system already favored. IPS corrects this by weighting each observed event by the inverse of its exposure propensity pi (the probability the system would have shown it):
naïve: Σshown i clicki → IPS: Σshown i clicki / pi
An item shown 1% of the time (p = 0.01) gets weight 100; an over-exposed head item shown 50% of the time gets weight 2. This re-inflates the under-exposed tail so the training signal reflects what users would do if every item had a fair shot, not what they did under a biased exposure policy. The catch: small pi gives huge, high-variance weights — so IPS is usually clipped, or upgraded to the lower-variance doubly-robust estimator (a model prediction plus an IPS-corrected residual), which stays unbiased even if either the propensity or the model is wrong. This is the same machinery behind debiased offline replay in 07 · Bias/debiasing.
Operating thresholds — the numbers that page someone
The metrics above (08 · Evaluation owns how to compute them; 34 · Metrics the definitions) are inert until you attach a trip wire to each. A guardrail without a number can't fire an alert and can't gate a release. The values below are the defensible defaults a short-video platform actually runs; the engineering point is not the exact constant but that each one exists, is monitored continuously, and triggers a specific action when crossed.
| Signal | Healthy band | Trip wire → action | Why this line |
|---|---|---|---|
| Exposure Gini (creator) | 0.3 – 0.4 | > 0.4 → alert, raise fairness weight | above 0.4 the head is swallowing the catalog — Matthew effect winning |
| 7-day content coverage | > 60% | < 60% → boost tail recall channel | below 60% of the catalog is going dark within a week |
| Cold-start penetration | > 15% distinct users / 24 h | below → guaranteed-impression floor for new items | a new video that can't reach 15% of an audience in a day never escapes the cold |
| Per-session tail quota | ≥ 20% long-tail slots | hard re-rank constraint, not an alert | structural floor so the loop can't run a session to zero tail |
| Cross-group CTR ratio | 0.9 – 1.1 | outside band → debias / audit features | one group converting >10% off another signals a quality gap, not taste |
| DDR (group exposure-difference ratio) | ≤ 1.5 | > 1.5 → trigger intervention | advantaged group getting >1.5× the disadvantaged group's mean exposure |
| Equal-opportunity rate | > 30% | below → relax quality gate on tail | fraction of genuinely-good tail items clearing the CTR bar — survival space |
| DDR (diversity-decay-ratio) | ≤ 1.5 | > 1.5 → re-rank intervention | per-user category entropy decaying >1.5× faster than baseline = filter bubble forming |
The in-processing fairness loss, with numbers
The in-processing row above said "fairness regularizer" abstractly. Here is the concrete form — a penalty that pulls the minority-to-majority exposure ratio toward a target τ and adds it straight to the training loss:
Lfair = λ · | E[exposure | minority] / E[exposure | majority] − τ |
The total objective becomes L = LCTR + Lfair. Read the parts as engineering knobs, each with a failure mode if you get it wrong:
- τ — the target ratio, typically τ ≈ 0.8. Not 1.0: forcing exactly equal exposure ignores that the groups may genuinely differ in catalog size and quality; 0.8 says "the minority should get at least 80% of the majority's per-item exposure" — a floor, not a quota that drowns users in junk.
- λ — how hard the penalty pulls. λ too small and the term is decorative (CTR gradient dominates, nothing changes); too large and the model sacrifices ranking quality to chase the ratio, tanking watch-time. You tune λ on the Pareto frontier, same as the re-rank dials.
- The |·| makes it symmetric — over-exposing the minority (ratio > τ) is penalized too, so the model doesn't overcorrect into reverse unfairness.
From static penalty to dynamic weight — reacting to live exposure bias
A fixed λ is blunt: it applies the same pressure whether the system is already fair or wildly skewed. The next refinement makes the weight react to the live exposure bias δt — the measured gap between a group's current exposure share and its target — through a sigmoid gate:
wt = 1 / (1 + e−k(δt − δtarget))
When live bias δt sits below the tolerance δtarget, wt shrinks toward its floor and the fairness term mostly steps aside — little needless drag on CTR (it only truly reaches 0 in the limit of bias far below tolerance). As bias grows past tolerance, wt → 1 and the penalty switches on hard. The slope k sets how abruptly. Concretely, with k = 10 and δtarget = 0.15:
So the fairness contribution becomes wt · Lfair — self-tightening when the platform drifts unfair, self-loosening when it recovers. This pairs with a regret / compensation mechanism: content that was historically under-exposed accrues a regret balance, and the system pays it back with a temporary score boost until the deficit clears. Static Lfair only equalizes the current rate; the regret term repays the accumulated debt a creator suffered while the model was still learning — without it, an item starved for its first week never recovers the audience it should have built.
Diversity as a hard constraint inside the ranker
Everything so far treats diversity and fairness as soft — a penalty you can pay down with enough CTR. Sometimes the business wants a hard guarantee: "every feed must clear a coverage floor, full stop." Putting that inside a multi-task model (12 · MMoE / PLE) instead of bolting it on at re-rank is hard for one reason: top-k selection is non-differentiable. Coverage is computed over the discrete set of items the model picks, and you can't backprop through an arg-top-k. Three moves make it trainable.
- Gumbel-Softmax surrogate. Replace the hard top-k pick with a differentiable relaxation — Gumbel-Softmax samples a temperature-controlled soft selection that sharpens toward a discrete k-hot subset as temperature drops (top-k is the k-subset generalization of the one-hot single-pick case). Coverage computed over these soft selections has a real gradient, so "raise coverage" becomes a trainable signal instead of a wall.
- Gradient blocking. Add a dedicated diversity gating branch (its input: category distribution + the user's historical-interaction diversity stats), and block the main objective's gradients from flowing into that branch's parameters. Why: watch-time and coverage pull in opposite directions, so if the CTR loss could edit the diversity gate it would quietly sabotage it. Blocking lets the diversity branch optimize its own target unmolested while still sharing the lower feature layers.
- Lagrangian violation check. "Hard constraint" must be verified, not hoped for. Offline, treat the coverage floor as a Lagrangian constraint and measure the violation rate on a validation set — the fraction of feeds that miss the floor. If it exceeds a threshold, the constraint isn't really binding and the model retrains with a heavier multiplier. Online, run the new gate in shadow mode, logging the long-tail ratio over a sliding window before it ever touches live traffic.
The accuracy–diversity tradeoff
Here is the uncomfortable truth the λ slider made concrete: pushing diversity or fairness almost always lowers short-term accuracy metrics — CTR and per-user watch time dip, because you are deliberately not always showing the single most-clickable item. The naïve read is "diversity hurts the business." The correct read is that you are trading a short-term metric for a long-term one.
Three patterns make the tradeoff manageable rather than a flat tax:
- Pareto framing. Don't pick "accuracy vs diversity" — search the Pareto frontier (sweep λ, the exposure quota, the fairness weight) and pick a point where you give up, say, <3% watch-time for a 15% Gini reduction. Frontier search turns a fight into a negotiated tradeoff with a defensible number.
- State-dependent dials. Diversity demand isn't constant. New users need high exploration (you don't know them yet); engaged users mid-binge want coherence. Make λ a function of user state — high for newcomers, relaxed when a session is going well. This is exactly the explore/exploit framing of 16 · Reinforcement learning.
- Composite objectives. Replace raw watch-time with a metric that already prices in variety (e.g. diversity-weighted watch-time, or watch-time conditioned on a healthy category entropy), so the optimizer stops treating diversity as a pure enemy.
A reference framework
Putting the whole lesson into one design — the shape an interview answer should take:
- Retrieval — multi-channel recall with at least one channel dedicated to diversity / tail (cluster sampling, content-tag recall), and a reserved exploration slice for cold items.
- Ranking — a multi-task model (MMoE / PLE) trained on IPS-debiased labels, optionally with a fairness / exposure-variance regularizer and adversarial removal of sensitive attributes.
- Re-rank — MMR or DPP for list diversity, plus hard exposure floors for tail creators and a per-session diversity quota.
- Weighting — combine objectives as score = α · CTR + β · diversity + γ · fairness; tune α / β / γ on the offline Pareto frontier, then adjust online (Thompson sampling / bandit) under the constraint "protect core watch-time first, then satisfy diversity / fairness floors."
- Verification — long-horizon A/B with dual guardrails: core metric (watch-time, retention) and ecosystem metric (Gini, tail penetration, creator retention), sliced by segment to dodge Simpson's paradox.
Interview prompts you should be ready for
- "Your ranker is perfect at CTR — why isn't that enough?" (Two structural pathologies even with a perfect predictor: filter bubbles narrow the user; the Matthew effect concentrates exposure on a popular head. Both are fed by a feedback loop of biased labels, not by model error.)
- "Write the MMR objective and explain λ." (next = arg max λ·rel(q,d) − (1−λ)·max sim(d, d′ in S). λ=1 reproduces the ranker; λ=0 is pure diversity. Greedy, O(k·n), ships in real-time feeds.)
- "MMR vs DPP — when do you reach for each?" (MMR: cheap greedy heuristic, one knob, pre-rank or latency-bound feeds. DPP: P(S) ∝ det(L_S), the determinant = squared volume jointly encodes quality + diversity, lower tail Gini, but ~O(n³) and kernel tuning. Often MMR for speed, DPP in final re-rank.)
- "How does IPS debias clicks, and what breaks it?" (Weight each observed event by 1/p_i, the inverse exposure propensity, to re-inflate the under-exposed tail. Breaks on tiny p_i → huge high-variance weights; fix with clipping or the doubly-robust estimator, unbiased if either propensity or model is right.)
- "What's the difference between demographic parity and equal opportunity in exposure fairness?" (Parity matches each group's exposure share to its catalog share — blind to quality, can force junk. Equal opportunity equalizes exposure only among genuinely good items — conditions on quality. Add individual / counterfactual fairness for completeness.)
- "Diversity drops NDCG offline. How do you justify shipping it?" (Offline metrics only know the next click; the payoff is long-horizon retention + creator supply. Run a long A/B on D7/D30 and ecosystem guardrails (Gini, tail penetration), slice by segment for Simpson's paradox, and frame it as a Pareto point: <3% watch-time for 15% Gini.)
- "Where in the stack do you enforce fairness — data, model, or re-rank?" (Pre: re-sample / adversarial debias — root fix but discards signal and churns. In: fairness regularizer / exposure-variance penalty — optimizes the real objective, adds cost. Post: MMR / DPP / quotas — easiest and A/B-testable but can wreck ordering. Production usually composes all three.)
- "Write the in-training fairness loss and walk through the numbers." (L_fair = λ·|E[exp|min]/E[exp|maj] − τ|, added to L_CTR; τ≈0.8 is a floor not 1.0, the |·| is two-sided so over-correction is also penalized. Worked: λ=0.5, ratio 600/1000=0.6, gap 0.2 → L_fair=0.10; as minority exposure rises to 720 the penalty falls to 0.04; it bottoms at ratio=τ. Upgrade to a dynamic weight w_t=σ(k(δ_t−δ_target)) that sleeps when fair and a regret term that repays accumulated exposure debt. And it needs adversarial decoupling underneath, or the model re-learns the bias from proxy features.)
- "How do you make diversity a hard constraint inside an MMoE, given top-k isn't differentiable?" (Add a diversity gating branch; relax top-k with Gumbel-Softmax so coverage has a gradient; gradient-block the main CTR objective from editing the diversity gate so it can't sabotage it; verify with an offline Lagrangian violation-rate check and online shadow-mode long-tail monitoring; stress with a synthetic single-category user — a soft penalty caves, a true hard floor still injects other categories. Pair with a quality bar so the floor doesn't mandate junk.)