🏠 总目录📚 本教程 11 · 重排与多样性
📑 本页目录(点开跳转)

11 · 重排与多样性

20 分钟 | ⭐ 核心


🎯 一句话

精排告诉你「每个物品单独有多好」,但用户看到的是一个列表。 重排解决的是:「这十个放在一起,好不好?」

只看相关性λ = 1.0篮球集锦 #1篮球集锦 #2篮球集锦 #3篮球集锦 #4篮球集锦 #5都对,但看两条就腻了相关性 + 多样性λ = 0.5篮球集锦 #1足球进球集锦篮球集锦 #2健身教程足球战术解析每条都还行,且不重复只看多样性λ = 0.0篮球集锦 #1美食探店股票分析宠物日常装修攻略全不重样,但大半不感兴趣同一批候选,λ 一变,推出来的东西完全不同⭐ 多样性不是「为了好看」—— 它直接影响长期留存只优化单次点击率,模型会收敛到左边那种 —— 短期指标涨,用户很快就走了
同一批候选,λ 一变推出来的东西完全不同。⭐ 多样性不是「为了好看」—— 它直接影响长期留存。只优化单次点击率,模型会收敛到最左边那种:短期指标涨,用户很快就走了

💥 问题:为什么「每个都最好」≠「列表最好」

  精排的输出(按分数排):
    1. 篮球集锦  0.92
    2. 篮球教学  0.91
    3. 篮球比赛  0.90
    4. 篮球新闻  0.89
    5. 篮球鞋测评 0.88
    ...
    10. 篮球训练 0.83

  😐 用户的感受:「怎么全是篮球?我又不是只看篮球」
       → 划走、退出、明天不来了

三个具体的伤害

问题 后果
边际效用递减 第 5 个篮球视频的价值远低于第 1 个
兴趣覆盖不足 用户还有其他兴趣没被满足
无法探索 系统永远学不到用户的新兴趣 → 越推越窄

🔑 核心命题:准确性 vs 多样性的权衡(Accuracy-Diversity Tradeoff) 这是推荐系统里没有标准答案、必须靠 A/B 实验定夺的经典权衡。


🧰 重排要做的事(一张清单)

  精排给了 50 个候选,重排要在 10ms 内做完这些:

  【硬性过滤】必须做,不然出事故
    ☐ 已读/已购去重
    ☐ 黑名单、违规内容过滤
    ☐ 频次控制(同一物品一天最多曝光 3 次)
    ☐ 用户主动屏蔽的作者/话题

  【多样性】
    ☐ 类目打散(不能连续 3 个同类目)
    ☐ 作者打散(同一作者一屏最多 2 条)
    ☐ 内容相似度打散(标题/封面太像的分开)

  【业务规则】
    ☐ 广告插入(第 4、12、20 位)
    ☐ 运营位强插
    ☐ 新内容/新作者流量扶持(生态调控)
    ☐ 时效性内容加权(突发新闻)

  【体验优化】
    ☐ 首位保护(第 1 个必须是高置信度的)
    ☐ 避免开头连续低质

⚙️ 多样性算法一:MMR(最简单,最常用)

Maximal Marginal Relevance —— 边选边看「和已选的重不重」。

$$\text{MMR} = \arg\max_{i \in R \setminus S} \left[ \lambda \cdot \text{rel}(i) - (1-\lambda) \cdot \max_{j \in S} \text{sim}(i, j) \right]$$

💡 人话翻译

每次挑下一个时,算两笔账: 加分 = 它本身有多好(精排分) 扣分 = 它和已经选中的东西有多像 λ 是旋钮:λ=1 完全不管多样性,λ=0 只管多样性。实践中 0.7–0.9 常见。

import numpy as np

def mmr(scores, sim_matrix, k=10, lam=0.8):
    """
    scores:     (N,)   精排分数
    sim_matrix: (N,N)  物品两两相似度
    lam:        0~1,越大越看重相关性
    """
    selected = []
    candidates = list(range(len(scores)))

    for _ in range(k):
        best, best_val = None, -np.inf
        for i in candidates:
            if not selected:
                val = scores[i]
            else:
                redundancy = max(sim_matrix[i][j] for j in selected)
                val = lam * scores[i] - (1 - lam) * redundancy
            if val > best_val:
                best, best_val = i, val
        selected.append(best)
        candidates.remove(best)
    return selected

优点:简单、快、可解释、λ 一个旋钮可调。 缺点:贪心策略,只看「和最像的那个有多像」,不看整体分布。


⚙️ 多样性算法二:DPP(更强,工业界在用)

Determinantal Point Process(行列式点过程) —— 数学上更优雅。

核心直觉:把每个物品看成一个向量,一组物品的「多样性」= 它们张成的空间体积

   两个向量很像(夹角小)        两个向量不像(接近垂直)
        ↗ ↗                          ↑
       /  /                          │  →
      /__/                           └────
   面积小 → 多样性低               面积大 → 多样性高

数学上,这个「体积」就是核矩阵的行列式。DPP 的目标:

$$\max_{S} \det(L_S), \quad L_{ij} = \underbrace{q_i q_j}_{\text{质量}} \cdot \underbrace{\langle \phi_i, \phi_j\rangle}_{\text{相似度}}$$

💡 人话「既要每个都好(q 大),又要它们互相不像(行列式大)」——两个目标被一个行列式统一了。

实践:用贪心 MAP 近似(Chen et al. 2018 的快速算法),复杂度 O(k²N),能在 10ms 内跑完几百个候选。

📌 快手、YouTube 等都公开发表过 DPP 在重排中的应用。这是目前多样性的工业标准解法之一。


⚙️ 多样性算法三:打散规则(最土,但最有效)

别小看规则。很多公司的重排 80% 是规则。

def scatter(items, key_fn, min_gap=3, max_per_window=2, window=5):
    """
    通用打散:保证同一 key(类目/作者)的物品不要太密集
    这段逻辑的变体在几乎所有推荐系统里都存在
    """
    result, pool = [], list(items)
    while pool:
        placed = False
        for idx, item in enumerate(pool):
            k = key_fn(item)
            # 检查1:最近 min_gap 个里有没有同 key 的
            recent = [key_fn(x) for x in result[-min_gap:]]
            # 检查2:最近 window 个里同 key 的数量
            win = [key_fn(x) for x in result[-window:]]
            if k not in recent and win.count(k) < max_per_window:
                result.append(pool.pop(idx))
                placed = True
                break
        if not placed:                  # 打散不了就按原序放,别死循环
            result.append(pool.pop(0))
    return result

⚠️ 打散的隐藏代价:把高分物品往后挪 = 牺牲了短期 CTR。 必须用 A/B 实验验证长期留存是否上升,否则你只是在无谓地降低指标。


🤖 现代做法:让模型学重排

PRM(Personalized Re-ranking,阿里 2019)

思想:用 Transformer 处理整个候选列表,让每个物品「看到」其他物品。

精排输出的 50 个候选 ↓

Transformer Encoder
→ 学到「上下文效应」 ↓ 重新打分 → 重新排序

它能学到什么规则学不到的: - 「这个物品排在一堆同类里会被淹没,单独出现效果好」 - 「A 和 B 放一起有互补效应,用户更容易连着看」

生成式重排

不是「打分再排序」,而是直接生成一个序列,用一个 Evaluator 评估「整个列表的期望收益」。 代表:Seq2Slate、GRN、PIER


📏 多样性怎么量化

指标 定义 怎么用
Intra-List Diversity (ILD) 列表内两两不相似度的平均 最常用
Coverage 推荐结果覆盖了多少比例的物品库 看长尾是否被挖掘
Gini / Entropy 曝光分布的不平等程度 看是否被爆款垄断
Category Count 一屏内出现几个不同类目 最直观,产品经理能懂 ⭐
Serendipity(惊喜度) 推荐的东西「意外但相关」的程度 难量化,但最贴近用户感受
Novelty(新颖度) 推荐物品的平均流行度(越低越新颖) 反热门偏置
def intra_list_diversity(item_vecs):
    """ILD:列表内两两余弦距离的平均"""
    n = len(item_vecs)
    if n < 2: return 0.0
    v = item_vecs / (np.linalg.norm(item_vecs, axis=1, keepdims=True) + 1e-9)
    sim = v @ v.T
    # 只取上三角(不含对角)
    iu = np.triu_indices(n, k=1)
    return float(1 - sim[iu].mean())

⚖️ 一个必须理解的现实:多样性是「长期指标」

   上线多样性策略后的典型曲线:

   CTR
    ▲
    │ ─────╲
    │       ╲___________________     ← 短期 CTR 下降 1-2%
    └──────────────────────────► 时间

   7日留存
    ▲
    │              ___________
    │ ────────────╱                  ← 长期留存上升
    └──────────────────────────► 时间
        ↑上线

这意味着: - 只看 1 天的 A/B 实验,多样性策略一定是负向的 - 必须做长周期实验(2–4 周),看留存、看使用时长、看用户流失率 - 这也是为什么多样性优化在很多公司推不动——它和短期 KPI 冲突

🔑 给未来的你的忠告:如果你要推多样性优化,先和产品/老板对齐实验周期和成功指标,否则第 3 天就会被叫停。


🌐 更大的问题:信息茧房

多样性不只是「体验优化」,它涉及产品伦理。

现象 机制
Filter Bubble(过滤气泡) 系统只推你喜欢的 → 你的信息世界越来越窄
Echo Chamber(回音室) 只看到同类观点 → 观点极化
反馈循环放大 推荐 → 点击 → 训练 → 更极端的推荐 → ……

工业界的部分对策: - 强制探索配额(每 N 个位置留 1 个探索位) - 兴趣多样性约束(一屏必须覆盖 ≥3 个类目) - 长期价值建模(用强化学习优化累计收益而非单次点击) - 对争议性/极端内容的排序降权

但这是个未解决的问题。 作为从业者,知道这个代价存在,比假装它不存在重要。


🔗 这一章连到哪里

去哪为什么
上线之后 15CTR 只是代理指标:多样性短期负、长期正,正是代理指标与真实目标的帕累托前沿问题
上线之后 122–4 周长周期实验怎么做、新奇效应怎么排除,否则多样性策略第 3 天就被叫停
强化学习 07强制探索配额、信息茧房的反馈循环,本质就是只「利用」不「探索」的后果
博弈论 09「帕累托」这个词的定义处。⚠️ 本章正文其实一次都没用过这个词,但你整章在做的事就是它 —— 「准确性 vs 多样性」那条取舍曲线就是一条二维帕累托前沿(λ 一变推出来的东西完全不同,短期 CTR 降 1–2%、长期留存上升)。它到底怎么定义、为什么不能把两个目标的分数加起来比大小(答案:效用是序数的,两把尺子不通用),在那一章第五节。⚠️ 顺带纠正一个常见误解:帕累托最优完全不管公平——「准确性拉满、多样性归零」也是帕累托最优的

✅ 检查点

  1. 为什么「每个物品都最好」不等于「列表最好」?
  2. MMR 的 λ 调小会发生什么?
  3. DPP 用什么数学量表示「多样性」?
  4. 多样性策略上线后,短期 CTR 通常会怎样?该怎么评估它?
  5. 列举 3 个多样性的量化指标。
  6. 什么是信息茧房?推荐系统的哪个机制在放大它?
👀 答案 1. 边际效用递减(第5个同类物品价值远低于第1个)+ 兴趣覆盖不足 + 无法探索新兴趣。 2. λ 小 = 更看重多样性、更不看重精排分 → 推荐更杂,短期 CTR 下降。 3. 核矩阵的行列式 —— 几何上等于物品向量张成空间的体积,向量越不相似体积越大。 4. 短期 CTR 通常下降 1-2%,长期留存/时长上升。必须做 2-4 周的长周期实验,看留存类指标。 5. ILD(列表内多样性)、Coverage(物品库覆盖率)、Gini/Entropy(曝光分布不平等度)、Category Count、Novelty。 6. 只推你喜欢的导致信息世界收窄。放大机制是反馈循环:推荐→点击→用这些点击训练→推得更窄。

🛑 可以停在这里

走神救援

重排解决「列表整体好不好」。要做:硬过滤(去重/频控/黑名单) + 多样性(MMR用λ调, DPP用行列式=向量体积, 打散规则) + 业务规则(广告/扶持)。现代做法 PRM 用 Transformer 建模整个列表。多样性会短期降 CTR、长期升留存,必须做 2-4 周长周期实验。信息茧房是未解决的伦理问题。MMR 和 DPP 的分界值得记住:MMR 是贪心,每一步在「与 query 的相关性」和「与已选集合的最大相似度」之间用 λ 权衡,十几行就能写完;DPP 用行列式 = 向量张成的体积来度量整个集合的多样性,把「两两比较」升级成「集合级别」,效果更好但也更贵。⚠️ 最土的打散规则(同作者、同类目至少间隔 N 位)在工业界反而用得最多——可解释、可调、出事能立刻关掉,别看不起它。⭐ 量化多样性别只盯 ILD:Coverage 看长尾有没有被挖出来,Gini / Entropy 看曝光是不是被爆款垄断,Serendipity 最贴近用户感受却最难量化,而产品经理真正听得懂的是「一屏出现了几个类目」。⚠️ 推多样性最现实的一条忠告:先和产品、老板对齐实验周期和成功指标,因为短期 CTR 必跌,指标没谈好就撑不到第二周。

下一节 👉 12-评估与AB实验.md

打卡记录保存在你的浏览器里,首页能看到总进度