search_ads_recsys / 20 · fairness & diversity lesson 20 / 39

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.

These are not model errors — the loop is working as designed
The root cause traces straight back to 07 · Bias/debiasing: your labels are biased by what the system chose to show. An item only earns a click label if it was exposed; it was exposed because it scored well; it scored well partly because it was exposed in the past. The model learns "popular = good" because its training set is the system's own past output. Accuracy on that biased log is not accuracy on the world.

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.

popular item scores higher more exposure re-rank shows it more clicks collected as labels biased training set "popular = good" re-rank stage the Matthew effect compounds each cycle DIVERSITY / FAIRNESS re-rank: force tail exposure DEBIASING IPS: re-weight the labels

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.

Worked numbers — how a 1-point edge becomes a landslide
Two items start nearly tied. Item A has true CTR 5.0%, item B 4.8% — a real but tiny difference. Watch the loop run (numbers are schematic — exposure each round grows with measured engagement and the gap accelerates as it compounds, so this isn't a single fixed growth rate):
DayA impressionsA clicksB impressionsB clicks
11,000501,00048
21,0405296046
51,3006570034
101,8009025012
Nothing about the items changed — both still convert at ~5% — but by day 10 A gets 7× the exposure of B purely because it started a hair ahead and the loop compounded it. That is the Matthew effect as arithmetic, and it is why "ship if global CTR is flat" misses the damage: aggregate CTR barely moved, but B is being strangled. The two cuts undo exactly this: IPS re-weights B's sparse clicks back up (its few clicks count more because it was under-exposed), and a diversity / exposure floor guarantees B keeps a minimum share so the loop can't run to zero.

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?).

MetricScopeDefinitionReads as
Intra-list similarity (ILS)one listILS = 2 / (|L|(|L|−1)) · Σi≠j sim(i, j) — mean pairwise similarity inside the listlower = more diverse list
Category entropyone listH = −Σc pc log pc over category shares pchigher = flatter mix (aim ~3–5 bits)
Coveragesystem|∪u Lu| / |I| — fraction of the catalog ever recommendedhigher = more of the tail surfaced
Gini coefficientsysteminequality of the exposure distribution across items/creators0 = perfectly even, →1 = winner-take-all
Noveltylist / system∝ Σ 1 / log(popularityi + 1) — inverse popularityhigher = more unfamiliar items
Coverage is gameable — "pseudo-diversity"
You can max coverage by spraying random tail items nobody wants — high diversity, dead feed. The honest target is relevant diversity: vary the list along dimensions the user is plausibly open to, not noise. This is why diversity metrics are guardrails, never the optimization target, and why the verdict is always an online retention test, not an offline coverage number.

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.

MMR λ — the relevance ↔ diversity tradeoff
The candidate pool is deliberately clumped: the top of the raw ranker is mostly one hot category. λ controls how hard MMR fights that clumping. The numbers are toy; the shape of the tradeoff is real.
avg relevance
distinct categories
category entropy
regime
Reading

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.

MMR vs DPP — the engineering tradeoff
MMR: greedy, O(k · n), one intuitive knob, ships in real-time feeds. DPP: mathematically optimal subset diversity, fuses multimodal features in the kernel, achieves lower Gini on the tail — but exact MAP inference is expensive (kernel ops scale ~O(n³); greedy DPP brings it down), the kernel needs careful tuning, and cold-start items have ill-estimated similarities. Common industrial pattern: MMR in pre-rank for speed, DPP in the final re-rank for quality.

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.

SideWhose fairnessThe concern
User sideconsumer 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 sidecreators & 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:

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.

LayerMechanismProCon
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 simplemay 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 objectiveoptimizes the real objective directly; adapts to new contentadds 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 itemseasiest to ship; trivially A/B-testable; model untouchedcan 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.

The three cuts, summarized
IPS / re-weighting debiases the labels so "popular ≠ good" no longer leaks in. Exposure re-weighting / fairness regularizers debias the model's objective. MMR / DPP / quotas debias the final list. They compose: re-weight the data, constrain the model, then re-rank for diversity — and verify the stack end-to-end online.

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.

SignalHealthy bandTrip wire → actionWhy this line
Exposure Gini (creator)0.3 – 0.4> 0.4 → alert, raise fairness weightabove 0.4 the head is swallowing the catalog — Matthew effect winning
7-day content coverage> 60%< 60% → boost tail recall channelbelow 60% of the catalog is going dark within a week
Cold-start penetration> 15% distinct users / 24 hbelow → guaranteed-impression floor for new itemsa new video that can't reach 15% of an audience in a day never escapes the cold
Per-session tail quota≥ 20% long-tail slotshard re-rank constraint, not an alertstructural floor so the loop can't run a session to zero tail
Cross-group CTR ratio0.9 – 1.1outside band → debias / audit featuresone group converting >10% off another signals a quality gap, not taste
DDR (group exposure-difference ratio)≤ 1.5> 1.5 → trigger interventionadvantaged group getting >1.5× the disadvantaged group's mean exposure
Equal-opportunity rate> 30%below → relax quality gate on tailfraction of genuinely-good tail items clearing the CTR bar — survival space
DDR (diversity-decay-ratio)≤ 1.5> 1.5 → re-rank interventionper-user category entropy decaying >1.5× faster than baseline = filter bubble forming
Two things named "DDR" — don't conflate them
The group exposure-difference ratio is a provider-side snapshot (is creator-group A out-exposing group B right now?). The diversity-decay-ratio is a user-side velocity (is this user's feed collapsing toward one category faster than normal?). Both fire at > 1.5, but one protects the supply side and the other the consumer side — they can trip independently, and a healthy system watches both. The payoff for keeping them in band is not just defensive: rebalancing traffic away from an over-concentrated head has been observed to raise overall CTR 5–15%, because the head was already saturated and the marginal impression was wasted on someone who'd seen it.

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:

Worked numbers — computing Lfair
Take λ = 0.5, τ = 0.8. Suppose in the current batch each majority-group item averages 1,000 impressions and each minority-group item averages 600.
ratio = E[exp|min] / E[exp|maj] = 600 / 1000 = 0.60 gap = | ratio − τ | = | 0.60 − 0.80 | = 0.20 L_fair = λ · gap = 0.5 · 0.20 = 0.10
A penalty of 0.10 is added to the loss and its gradient pushes the ranker to lift minority scores. Now watch it shrink as the model responds — minority exposure climbs to 720:
ratio = 720 / 1000 = 0.72 gap = | 0.72 − 0.80 | = 0.08 L_fair = 0.5 · 0.08 = 0.04 ← penalty fell 0.10 → 0.04
And the symmetry check — if a too-aggressive λ over-shoots to 900 impressions for minority items:
ratio = 900 / 1000 = 0.90 (now ABOVE τ = 0.80) gap = | 0.90 − 0.80 | = 0.10 L_fair = 0.5 · 0.10 = 0.05 ← penalty rises again, pulling back down
The penalty bottoms out exactly at ratio = τ = 0.80 (Lfair = 0) and grows in both directions — that two-sided pull is the whole reason for the absolute value. A one-sided max(0, τ − ratio) hinge would let the model freely over-expose the minority, trading one unfairness for another.

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:

δ_t = 0.10 (under target) → w_t = 1/(1+e^(−10·(−0.05))) = 1/(1+e^0.5) ≈ 0.38 δ_t = 0.15 (at target) → w_t = 1/(1+e^0) = 0.50 δ_t = 0.25 (over target) → w_t = 1/(1+e^(−10·0.10)) = 1/(1+e^−1) ≈ 0.73 δ_t = 0.40 (badly skewed) → w_t = 1/(1+e^(−10·0.25)) = 1/(1+e^−2.5) ≈ 0.92

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.

The decoupling prerequisite — why the loss alone isn't enough
A fairness loss assumes the model could be neutral if pushed. But if a sensitive attribute (e.g. content language) is silently reconstructable from other features, the ranker re-learns the bias through the back door no matter how hard Lfair pulls. The fix is the adversarial decoupling from the pre-processing row, applied in training: build the sensitive attribute as an explicit, isolated feature, then attach an adversarial discriminator that tries to predict it from the intermediate representation — and train the encoder to defeat that discriminator. When the discriminator can no longer guess the attribute, the representation is decoupled and Lfair is operating on an honestly neutral backbone. Loss + decoupling are a pair, not alternatives.

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.

┌─────────────────────────────┐ shared experts ───►│ diversity gating branch │ ← extra MMoE gate, fed │ │ (category dist + user-hist │ content + history stats │ │ diversity stats) │ ▼ └──────────────┬──────────────┘ CTR / watch heads ▼ │ Gumbel-Softmax relaxation of top-k │ (differentiable surrogate for coverage) │ │ ▼ ▼ main objective ──╳── gradient block ──► diversity-gate params (the ╳ stops CTR gradients from steering the diversity gate)
  1. 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.
  2. 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.
  3. 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 adversarial sanity test
A coverage constraint that only holds on average is a fake guarantee. Stress it with a synthetic single-category user — a user whose entire history is one category. A soft penalty bends to such a user and serves them an all-one-category feed (locally optimal CTR). A genuinely hard constraint must still inject other categories even here. If the synthetic adversary breaks the floor, the constraint is soft in disguise — ship the fix, not the model.
The hidden cost — a hard floor can force junk
Hard constraints have teeth, and teeth bite. If the catalog is genuinely thin in some category, a coverage floor forces low-quality tail items into the feed to satisfy the count — pseudo-diversity again, now mandated by the loss. The mitigation is a joint quality–diversity filter: the constraint is "cover N categories with items above a quality bar," not "cover N categories." And as with every other dial, the floor should be state-dependent — strict for new users who need exploration, relaxed for an engaged user mid-binge who wants coherence.

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.

Why the offline number lies, and what to actually measure
Offline, more diversity = lower NDCG, full stop — the offline metric only knows about predicting the next click. The payoff (less fatigue → higher retention; healthier creator supply → richer future catalog) is a long-horizon, delayed-feedback effect that only a long-running A/B test on retention, D7 / D30, and creator-side guardrails can detect. A two-week test that watches only session watch-time will reject every good diversity change. Beware Simpson's paradox: aggregate watch-time can rise while a subgroup is harmed — always slice by segment.

Three patterns make the tradeoff manageable rather than a flat tax:

A reference framework

Putting the whole lesson into one design — the shape an interview answer should take:

Interview prompts you should be ready for

  1. "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.)
  2. "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.)
  3. "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.)
  4. "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.)
  5. "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.)
  6. "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.)
  7. "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.)
  8. "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.)
  9. "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.)
Takeaway
Pure accuracy breeds filter bubbles (user-side) and the Matthew effect (item-side), both fed by a loop of biased labels. Diversity (MMR's λ·rel − (1−λ)·sim, DPP's det(LS)) cures the list; fairness (demographic parity, equal opportunity, two-sided marketplace) cures the distribution; IPS / re-weighting cures the labels. The cost is short-term accuracy; the payoff is long-term retention and a healthy marketplace — a tradeoff only a long-horizon A/B test can adjudicate.