周末朋友聚餐,你负责从菜单里点菜。你很清楚朋友爱吃辣,于是专挑水煮鱼、毛血旺、麻辣香锅,结果桌上红油一片,朋友吃了几口就喊腻,抱怨“怎么全是辣菜”。推荐系统也有同样的毛病:它只根据你点过的内容推相似内容,你越点,它越推,你的世界越窄。这就是“信息茧房”。 今天要讲的 MMR(最大边缘相关性),像一位懂事的点菜管家:它既知道你爱吃辣,又会在每上一道菜前看一眼桌面——如果这道菜和桌上已有的太像,就往后排。这套策略在“想吃的”和“没重复的”之间寻找平衡,让搜索结果既相关又多样。接下来,我们用“点菜”这条故事线,把 MMR 的目标函数、贪心策略、λ 参数调优和工程落地一次讲透。

岗位关联

掌握 MMR,你就掌握了在相关性与多样性之间做权衡的实用武器;搜索、推荐、内容分发岗位的面试和项目里,它都是高频考点。

目录
  1. 可视化演示
  2. 1. 信息茧房成因:越吃越窄的菜单
  3. 2. 目标函数定义:想吃度与撞菜度的天平
  4. 3. 贪心选择策略:一盘一盘定菜
  5. 4. 参数调优方法:旋转咸淡旋钮
  6. 5. 工程实现路径:从菜单到线上推荐
  7. 6. MMR对比DPP:点菜管家与宴会设计师
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示MMR最大边缘相关性的贪心选择过程、λ参数对相关性与多样性平衡的影响以及与DPP行列式点过程的算法对比,帮助理解推荐系统中多样性建模的经典方法。

本节配有交互动效(进入视口后自动加载,约 3KB)。

'+captions.select+'

1. 信息茧房成因:越吃越窄的菜单

同桌吃饭时,如果每个人都只点自己最爱的菜,结果往往是满桌相似口味。推荐系统也一样:用户点击过某类内容,算法就认为用户只爱这类内容,于是越推越窄。用户再点,再推,形成“点击反馈循环”。久而久之,用户看到的推荐列表越来越同质化,视野不断收窄。这不是用户真的只爱一种口味,而是系统没有主动打破重复。

要把“重复”变成可计算的问题,我们需要一个相似度度量。比如两个视频、两件商品、两篇文章,都可以用向量表示。向量越接近,内容越像。

正是因为“重复”可以被量化,点菜管家才有机会登场:它不追求每道菜都最合口味,而是追求整桌菜既有人爱吃,又不互相撞车。

Sim⁡(di,dj)=di⋅dj∥di∥ ∥dj∥\displaystyle \operatorname{Sim}(d_i,d_j)=\frac{d_i\cdot d_j}{\lVert d_i\rVert\,\lVert d_j\rVert}
Sim 越大,两道菜口味越接近;推荐列表里高 Sim 的结果堆在一起,就是信息茧房的“食材”。
  1. 向量内积分子的 d_i \cdot d_j 计算两个内容向量在同一方向上的重合程度。
    di⋅djd_i \cdot d_j
  2. 模长归一化分母的 \lVert d_i\rVert\,\lVert d_j\rVert 消除向量长度差异,让分数落在可比较的范围内。
    ∥di∥ ∥dj∥\lVert d_i\rVert\,\lVert d_j\rVert
  3. 相似度含义Sim 接近 1 表示两个内容高度相似,接近 0 表示差异很大。
    Sim⁡(di,dj)→1\operatorname{Sim}(d_i,d_j)\rightarrow 1

2. 目标函数定义:想吃度与撞菜度的天平

点菜管家心里有两本账:一本是“想吃度”,衡量候选菜符不符合朋友的口味;另一本是“撞菜度”,衡量候选菜和已经上桌的菜有多像。MMR 的目标,就是把这两本账合并成一个分数,每轮选出综合分最高的那道菜。

形式化地说,D 是候选菜集合,R 是已经上桌的菜集合,q 是用户或查询的“口味画像”。Sim1 是相关度,Sim2 是相似度。MMR 的每一轮选择是这样的:

“边缘相关性”中的“边缘”,指的就是这一道新菜相对于当前桌面带来的新增价值。它和桌上已有的菜越像,边际价值就越低。

MMR⁡(D,R,q)=arg⁡max⁡di∈D∖R[λ Sim⁡1(di,q)−(1−λ)max⁡dj∈RSim⁡2(di,dj)]\displaystyle \operatorname{MMR}(D,R,q)=\mathop{\arg\max}_{d_i\in D\setminus R}\left[\lambda\, \operatorname{Sim}_1(d_i,q)-(1-\lambda)\max_{d_j\in R}\operatorname{Sim}_2(d_i,d_j)\right]
第一项奖励“想吃”,第二项惩罚“和桌上已有的菜太像”,λ 是两者之间的旋钮。
  1. 候选范围d_i\in D\setminus R 表示只考虑还没上桌的菜。
    D∖RD\setminus R
  2. 相关项\lambda\, \operatorname{Sim}_1(d_i,q) 是候选菜对口味偏好的满足程度。
    λ Sim⁡1(di,q)\lambda\, \operatorname{Sim}_1(d_i,q)
  3. 冗余惩罚(1-\lambda)\max_{d_j\in R}\operatorname{Sim}_2(d_i,d_j) 找出候选菜与已上桌菜最像的那道,像得越多,惩罚越重。
    max⁡dj∈RSim⁡2(di,dj)\max_{d_j\in R}\operatorname{Sim}_2(d_i,d_j)
  4. 选择规则\mathop{\arg\max} 表示把边际得分最高的菜端上桌。
    arg⁡max⁡\mathop{\arg\max}

3. 贪心选择策略:一盘一盘定菜

点菜管家不会一上来就想明白整桌 20 道菜的最优排列,它只做一件事:当前桌上有哪些菜,下一道选谁能让边际价值最大。选完就上桌,不再回头。这就是贪心策略。

形式化地,第 t 轮从尚未选择的候选集中选出一道菜:

贪心的好处是快:朴素实现下,跑完 k 轮的时间复杂度大约为 O(k²n),候选集合几百条时完全可接受。但它只是近似最优,不是全局最优解。

dt∗=arg⁡max⁡d∈D∖Rt−1[λ rel⁡(d)−(1−λ)max⁡r∈Rt−1sim⁡(d,r)],Rt=Rt−1∪{dt∗}\displaystyle d_t^*=\mathop{\arg\max}_{d\in D\setminus R_{t-1}}\left[\lambda\, \operatorname{rel}(d)-(1-\lambda)\max_{r\in R_{t-1}}\operatorname{sim}(d,r)\right],\qquad R_t=R_{t-1}\cup\{d_t^*\}
每一轮都保证“当前桌上”的边际得分最大,但不保证整桌菜全局最优。
  1. 上一轮桌面R_{t-1} 是已经选好的菜集合,当前候选要拿它和这桌菜比较。
    Rt−1R_{t-1}
  2. 本轮最佳菜d_t^*=\mathop{\arg\max} 表示本轮选边际得分最大的候选。
    dt∗=arg⁡max⁡d_t^*=\mathop{\arg\max}
  3. 更新桌面R_t=R_{t-1}\cup\{d_t^*\} 把选中的菜加入已选集合,下一轮从剩余候选里继续选。
    Rt=Rt−1∪{dt∗}R_t=R_{t-1}\cup\{d_t^*\}

4. 参数调优方法:旋转咸淡旋钮

同一道菜,有人觉得咸,有人觉得淡。λ 就是点菜管家的咸淡旋钮:λ 越接近 1,越重视口味相关;λ 越接近 0,越重视桌面多样。默认从 0.5 开始,不代表 0.5 永远最好。

实际调参时,我们会准备一组候选值,比如 0.1、0.3、0.5、0.7、0.9,对每个 λ 跑一遍离线评估,再用线上 A/B 验证。评估指标通常分为相关性和多样性两类:

调参不是一次性的。搜索结果页可能 λ 要更高,因为用户有明确目标;信息流推荐 λ 可以低一点,因为用户愿意探索。就像做菜一样,最终以“上桌后的反馈”为准。

λ∗=arg⁡max⁡λ∈Λ(α⋅Rel⁡(λ)+β⋅Div⁡(λ))\displaystyle \lambda^*=\mathop{\arg\max}_{\lambda\in\Lambda}\left(\alpha\cdot \operatorname{Rel}(\lambda)+\beta\cdot \operatorname{Div}(\lambda)\right)
Rel 是相关性指标,Div 是多样性指标,α 和 β 代表业务对不同目标的偏好。
  1. 候选 λ 集合\lambda\in\Lambda 表示我们只在预先准备的几个旋钮档位里找。
    Λ={0.1,0.3,0.5,0.7,0.9}\Lambda=\left\{0.1,0.3,0.5,0.7,0.9\right\}
  2. 业务加权\alpha\cdot \operatorname{Rel}(\lambda)+\beta\cdot \operatorname{Div}(\lambda) 把两个指标加权成一个总分。
    α⋅Rel⁡(λ)+β⋅Div⁡(λ)\alpha\cdot \operatorname{Rel}(\lambda)+\beta\cdot \operatorname{Div}(\lambda)
  3. 选择最优\mathop{\arg\max} 找到让总分最高的 λ。
    λ∗=arg⁡max⁡λ∈Λ\lambda^*=\mathop{\arg\max}_{\lambda\in\Lambda}

5. 工程实现路径:从菜单到线上推荐

线上系统通常不会让 MMR 处理百万级候选,它更适合放在“重排层”:先用召回和精排筛出最相关的几百条候选,再用 MMR 对这些候选做多样性重排。这就像点菜管家只从 50 道备选菜里挑 10 道,而不是翻遍整个菜市场。

为了控制延迟,相似度矩阵可以离线预计算并缓存;线上只需要做查表和贪心选择。候选越多,计算量增长越快,所以常见做法是“头部截断”——只对精排 Top-N 做 MMR。

冷启动场景也很适合 MMR:新用户没有历史行为,系统先用热门内容做候选,再用类目、标签、内容向量计算相似度,保证第一屏什么类型都有,给用户更多探索机会。

Cost⁡(n,k)=O(k2n),n=∣D∣, k=∣R∣\displaystyle \operatorname{Cost}(n,k)=O(k^2 n),\quad n=|D|,\ k=|R|
n 是候选数,k 是最终列表长度;把 n 从百万降到几百,是工程上最有效的一刀。
  1. 候选规模n=|D| 表示进入 MMR 的候选数量,通常控制在几百。
    n=∣D∣n=|D|
  2. 目标规模k=|R| 表示最终要输出的结果数量,比如 10 条或 20 条。
    k=∣R∣k=|R|
  3. 复杂度含义O(k^2 n) 表示轮数越多、候选越多,耗时按二次关系增长。
    O(k2n)O(k^2 n)

6. MMR对比DPP:点菜管家与宴会设计师

MMR 像一位手脚麻利的点菜管家,一道一道地加菜,每次只看局部。另一种思路叫 DPP(行列式点过程),它更像宴会设计师:不只看下一道菜,而是直接评价“整桌菜摆在一起好不好”。

DPP 用一个核矩阵描述候选集合之间的关系,矩阵元素由质量分和相似度共同构成。对某个候选子集 Y,它的“好菜程度”用行列式来衡量:

DPP 能更好地建模全局多样性,但求解更复杂。实际中,如果候选少、多样性强需求高,可以选 DPP;如果线上延迟敏感、希望快速落地,MMR 通常更实用。

Lij=qiqjSij,P(Y)∝det⁡(LY)\displaystyle L_{ij}=q_i q_j S_{ij},\qquad P(Y)\propto \det(L_Y)
L_Y 是子集 Y 对应的核矩阵子块;两盘菜越像,行列式越小,组合得分越低。
  1. 核矩阵元素L_{ij}=q_i q_j S_{ij} 由两个物品各自的质量分和相似度相乘得到。
    Lij=qiqjSijL_{ij}=q_i q_j S_{ij}
  2. 子矩阵L_Y 是只保留 Y 中元素的行和列形成的子矩阵。
    LYL_Y
  3. 行列式\det(L_Y) 衡量这个集合的“体积”,越相似的行越会压缩体积,导致得分下降。
    det⁡(LY)\det(L_Y)

AI 应用场景

搜索结果重排:同义结果打散

阶段:召回与精排之后的“重排层”

解决的问题:传统搜索只按相关度排序,搜“华为”时前十条可能全是手机资讯,品牌旗舰店、售后、耳机、鸿蒙系统等内容被淹没。MMR 在精排分数之上减去与已选结果的最大相似度,让同义内容分散,提高信息覆盖率。

具体案例:电商搜索“连衣裙”,精排前十个结果可能款式高度相似。MMR 用“风格、价格、面料”等维度构造相似度,重排后前十条覆盖法式、通勤、碎花、纯色等不同方向,同时保持基本相关。

信息流推荐:别让信息茧房越收越紧

阶段:个性化推荐的后处理与多样性重排

解决的问题:推荐系统偏向点击高、与用户历史高度相似的内容,用户点得越多,推荐越窄。MMR 在 CTR 预估分数上加入类目、话题、作者的多样性惩罚,让喜欢科技的用户也能看到生活、财经等泛内容。

具体案例:短视频 App 识别出用户有“宠物”强兴趣,但仍会在每 10 条推荐中插入 1~2 条泛知识内容。MMR 从候选里选择与已展示视频最不重复、同时保留一定相关度的视频,观察次留与人均时长。

新用户冷启动:没有历史也避开千篇一律

阶段:冷启动阶段的候选多样性控制

解决的问题:新用户没有行为日志,如果直接上热门榜,前几屏可能全是同质爆款。MMR 用物品内容相似度对冷启动候选重排,让第一屏类目足够宽,便于探索用户真正兴趣。

具体案例:音乐 App 新用户注册后,用“语种、曲风、年代”构造相似度,从热门曲库中选 20 首,保证流行、民谣、说唱、古典都有代表,而不是前十首全是同一歌手的伤感情歌。

Python 演示

python36 行
# -*- coding: utf-8 -*- def mmr_select(relevance, similarity, lam=0.5, k=3):    selected = []    candidates = list(range(len(relevance)))    while len(selected) < k and candidates:        best_score = float("-inf")        best_idx = None        for i in candidates:            redundancy = 0.0            for j in selected:                if similarity[i][j] > redundancy:                    redundancy = similarity[i][j]            score = lam * relevance[i] - (1 - lam) * redundancy            if score > best_score:                best_score = score                best_idx = i        selected.append(best_idx)        candidates.remove(best_idx)    return selected if __name__ == "__main__":    dishes = ["红烧肉", "糖醋里脊", "清炒时蔬", "剁椒鱼头", "麻婆豆腐", "蒜蓉西兰花"]    relevance = [0.9, 0.8, 0.7, 0.6, 0.5, 0.4]    similarity = [        [0.0, 0.8, 0.3, 0.9, 0.4, 0.2],        [0.8, 0.0, 0.6, 0.7, 0.5, 0.3],        [0.3, 0.6, 0.0, 0.2, 0.8, 0.7],        [0.9, 0.7, 0.2, 0.0, 0.3, 0.1],        [0.4, 0.5, 0.8, 0.3, 0.0, 0.9],        [0.2, 0.3, 0.7, 0.1, 0.9, 0.0],    ]    for lam in [0.1, 0.5, 0.9]:        order = mmr_select(relevance, similarity, lam, 3)        names = [dishes[i] for i in order]        print("lambda=%.1f -> %s" % (lam, "、".join(names)))
目的:用纯 Python 实现 MMR 贪心选择,在不同 λ 下观察“想吃度”和“多样性”如何影响最终上桌的菜。
  1. def mmr_select(relevance, similarity, lam=0.5, k=3):定义点菜管家接口:输入相关度、相似度矩阵、λ 和菜数量。
  2. for j in selected:遍历已选中的菜,找当前候选菜与它们的最大相似度。
  3. if similarity[i][j] > redundancy:用打擂台方式更新“撞菜度”。
  4. score = lam * relevance[i] - (1 - lam) * redundancyMMR 核心打分公式。
  5. selected.append(best_idx)把本轮得分最高的菜端上桌。
  6. candidates.remove(best_idx)从候选菜单里划掉这道菜,避免重复上桌。
  7. for lam in [0.1, 0.5, 0.9]:对比三个 λ 档位,直观感受参数变化。

常见误区

误区我把 λ 设为 0.5,相关性和多样性就刚好平衡,模型不用再调了。

正解0.5 只是中性起点,不是普适最优。不同业务中,用户对重复的容忍度完全不同:搜索精确意图时 λ 要高,信息流探索场景 λ 可以低。必须用离线指标和线上实验调,不能拍脑袋。

为何常见:公式里两项权重看起来对称,很多人误以为对半就是“平衡”。

误区MMR 中的相似度就是文本余弦相似度,其他信息用不上。

正解实际工程里,相似度可以由类目、标签、embedding、协同过滤、甚至用户行为共现共同构成。视频推荐会用封面、导演、演员相似;冷启动时用物品内容属性。相似度定义直接决定“多样性”的语义。

为何常见:很多教程用文本检索举例,容易让人以为 MMR 只服务于文本场景。

误区我训练好 CTR 模型后,用 MMR 再排一遍,就不需要 Learning to Rank 了。

正解MMR 是重排层,不是主排序模型。它拿精排模型给出的相关度分数作为输入,再叠加多样性惩罚。如果候选本身相关度很差,MMR 也只能在差列表里挑差异。它不能学习特征,也不能替代精排。

为何常见:因为 MMR 改变了最终顺序,看起来像在学习排序,实际只是后处理。

误区每一轮选边际得分最大的菜,最终整桌菜一定是最优组合。

正解贪心只保证当前步骤最优,不保证全局最优。MMR 对应的组合优化问题是 NP 难的;贪心是高效近似。DPP 之类方法通过行列式建模全局多样性,但计算更重。

为何常见:每一步看起来都很合理,缺少全局反例的直觉。

面试题

Q1. 在 MMR 公式中,λ 控制的是什么?

单选题 · easy

  • A.相关性和多样性的权重← 正确答案
  • B.候选集合的样本数量
  • C.用户点击概率阈值
  • D.物品向量空间维度

答案:A。λ 是 MMR 目标函数里相关项和多样性惩罚项之间的权重。λ 越大越重视相关,越小越重视多样;它不控制候选数量、点击阈值或向量维度。

Q2. MMR 的第二项为什么对已选集取“最大相似度”?

单选题 · easy

  • A.找出与候选最撞菜的一道← 正确答案
  • B.找出与候选最相关的一道
  • C.找出候选里相似度最小的
  • D.找出已选集合的平均值

答案:A。MMR 要惩罚“和桌上已有的哪道菜最像”的重复感,所以先找到相似度最大的那个已选项,再用它代表冗余程度。平均值会稀释“最撞菜”的风险。

Q3. λ=0.8 时,MMR 会更偏向下列哪种结果?

单选题 · mid

  • A.相关性更高的结果← 正确答案
  • B.多样性更高的结果
  • C.点击率更高的结果
  • D.随机性更高的结果

答案:A。λ=0.8 时,相关项权重为 0.8,而多样性惩罚权重只有 0.2,所以整体更偏向相关。点击率是排序模型的输出,不是 MMR 的权重语义。

Q4. MMR 每轮选择的依据是什么?

单选题 · mid

  • A.当前边际得分最高← 正确答案
  • B.全桌组合得分最高
  • C.单独相关度最高
  • D.单独差异度最高

答案:A。MMR 是贪心算法,只选“当前桌面状态下”边际得分最高的候选。它不是全局组合最优,也不是只按相关度或只按差异度排序。

Q5. MMR 最适合放在推荐系统的哪个流程?

单选题 · mid

  • A.召回后的重排阶段← 正确答案
  • B.日志采集与清洗
  • C.用户画像存储
  • D.特征离线训练

答案:A。MMR 需要精排给出的相关度分数和候选物品,适合在召回和精排之后做重排。日志、画像和特征训练都不是它的直接场景。

Q6. 朴素实现 k 轮 MMR 的复杂度约为多少?

单选题 · mid

  • A.O(k×n),约线性
  • B.O(k²×n),约二次← 正确答案
  • C.O(n²),只随候选增长
  • D.O(k×n²),轮数线性

答案:B。每一轮要为每个候选遍历已选集合,第 t 轮约 n×t 次比较,k 轮累计约 n×k²。因此朴素实现的复杂度是 O(k²n)。

Q7. DPP 选择子集时,打分函数通常由什么表示?

单选题 · extreme

  • A.子集对应行列式的值← 正确答案
  • B.子集元素数量总和
  • C.子集元素最大相关度
  • D.子集元素采样顺序

答案:A。DPP 用核矩阵的行列式衡量子集的多样性和质量。相似的物品会让矩阵行向量靠近,行列式变小,组合得分降低。DPP 由此建模全局多样性。

Q8. 下列哪些做法能降低 MMR 线上开销?

多选题 · mid

  • A.预计算物品相似度← 正确答案
  • B.只重排头部候选← 正确答案
  • C.对全量候选穷举
  • D.使用近似最近邻← 正确答案

答案:ABD。预计算相似度可以避免线上重复计算;只重排头部候选能把 n 从百万降到几百;近似最近邻可以加快相似物品检索。对全量候选穷举反而会增加开销。

Q9. 哪些属于信息茧房的典型成因?

多选题 · easy

  • A.推荐列表单一同质← 正确答案
  • B.点击反馈放大偏好← 正确答案
  • C.用户视野不断收窄← 正确答案
  • D.服务器资源不足

答案:ABC。信息茧房来自推荐列表同质化、点击反馈循环、用户视野收窄三者的互相强化。服务器资源不足是工程问题,不是信息茧房的成因。

Q10. 调优 MMR 的 λ 时,可以观察哪些信号?

多选题 · hard

  • A.离线相关性指标← 正确答案
  • B.用户平均停留时长← 正确答案
  • C.推荐结果覆盖率← 正确答案
  • D.用户设备型号

答案:ABC。离线相关性衡量相关不塌方,覆盖率衡量多样性提升,平均停留时长反映线上真实体验。设备型号与 λ 调优没有直接关系。