Skip to main content

MMR

1. 什么是 MMR?

最大边际相关性(Maximal Marginal Relevance,MMR) 是一种用于信息检索和文本摘要的算法,由 Carbonell 和 Goldstein 在 1998 年提出。

MMR 的核心目标是:在返回的结果中,既要保证相关性 (结果与查询相关),又要保证多样性 (结果之间不重复、不冗余)。

1.1 为什么需要 MMR?

想象一下,你在搜索引擎中搜索"人工智能最新进展",如果没有 MMR,可能会返回 10 篇都是关于"GPT-4 发布"的相似文章。虽然这些文章都很相关,但信息重复,你无法获得全面的信息。

使用 MMR 后,返回的结果可能是:

  • GPT-4 最新进展
  • 深度学习硬件突破
  • AI 伦理讨论
  • 计算机视觉新应用
  • 医疗 AI 成功案例

这样你就能获得更全面、更多样化的信息。

2. MMR 的核心思想

MMR 算法的核心思想可以用一句话概括:在保证相关性的同时,最大化结果的多样性

2.1 两个关键目标

  1. 相关性(Relevance) :选出的文档应该与查询高度相关
  2. 多样性(Diversity) :选出的文档之间应该尽可能不同,避免重复

2.2 如何平衡?

MMR 通过一个公式来平衡这两个目标:

分数相关性与已选文档的最大相似度MMR分数=λ×相关性−(1−λ)×与已选文档的最大相似度

这个公式的含义是:

  • 相关性部分 (相关性λ×相关性):我们希望文档与查询相关,这部分越大越好
  • 多样性部分 (最大相似度(1−λ)×最大相似度):我们希望文档与已选文档不同,这部分越小越好(所以用减法)

通过调整参数 λ,我们可以控制更偏向相关性还是多样性。

3. 算法公式

MMR 的完整公式如下:

MMR=arg⁡maxDi∈R∖S[λ⋅Sim1(Di,Q)−(1−λ)⋅maxDj∈SSim2(Di,Dj)]

3.1 公式符号说明

  • Di:当前正在评估的候选文档
  • Q:查询(用户输入的问题或关键词)
  • S:已经选择的结果集(已选文档的集合)
  • R:所有候选文档的集合
  • R∖S:候选文档中还未被选中的部分
  • Sim1(Di,Q):文档 Di 与查询 Q 的相关性评分
  • Sim2(Di,Dj):文档 Di 与已选文档 Dj 的相似度
  • maxDj∈SSim2(Di,Dj):文档 Di 与所有已选文档的最大相似度
  • λ:权衡参数,取值范围是 0 到 1

3.2 公式含义

这个公式的意思是:在所有未选中的候选文档中,找到 MMR 分数最高的那个文档。

MMR 分数由两部分组成:

  1. 相关性奖励 :λ×Sim1(Di,Q),文档与查询越相关,这部分越大
  2. 多样性惩罚 :(1−λ)×maxDj∈SSim2(Di,Dj),文档与已选文档越相似,这部分越大,但因为是减法,所以会降低总分

4. 参数 λ 的作用

参数 λ(lambda)是 MMR 算法的关键参数,它控制着相关性和多样性之间的平衡。

4.1 λ 的取值范围

  • λ = 1 :完全偏向相关性

    • 只考虑文档与查询的相关性,不考虑多样性
    • 相当于传统的相关性排序
    • 可能返回很多相似的文档
  • λ = 0 :完全偏向多样性

    • 只考虑文档之间的差异,不考虑与查询的相关性
    • 可能返回与查询不太相关但彼此不同的文档
  • λ = 0.5 :平衡相关性和多样性

    • 相关性和多样性的权重相等
  • λ = 0.7 :略微偏向相关性(常用设置)

    • 更重视相关性,但也会考虑多样性
    • 这是实际应用中最常用的设置

4.2 如何选择 λ?

  • 信息检索 :通常使用 0.5-0.7,保证结果相关且多样
  • 文档摘要 :可以使用 0.3-0.5,更重视多样性
  • 推荐系统 :通常使用 0.6-0.8,保证推荐相关

5. 算法步骤

MMR 算法采用贪心策略 ,逐步选择文档。下面是详细的步骤:

5.1 算法流程

输入:
- R:候选文档集合
- Q:查询
- k:需要选择的文档数量
- λ:权衡参数

输出:
- S:选择的结果集

步骤:
1. 初始化 S = ∅(空集合)
2. 计算所有候选文档与查询的相关性分数
3. 选择第一个文档:
- 选择与查询最相关的文档
- 将其加入 S,并从 R 中移除
4. 重复以下步骤,直到选择了 k 个文档:
a. 对 R 中每个未选文档 D_i:
计算 MMR 分数:
分数 = λ × Sim1(D_i, Q) - (1-λ) × max(Sim2(D_i, D_j)),其中 D_j ∈ S
b. 选择 MMR 分数最高的文档
c. 将该文档加入 S,并从 R 中移除
5. 返回 S

5.2 为什么先选最相关的?

第一个文档直接选择最相关的,因为此时还没有已选文档,无法计算多样性。从第二个文档开始,才需要考虑与已选文档的差异。

5.3 代码实现

# 导入NumPy库
import numpy as np

# 定义余弦相似度计算函数
def cosine_similarity(from_vec, to_vecs):
"""
计算一个向量与多个向量的余弦相似度

参数:
from_vec: 单个向量(1D或2D数组或列表)
to_vecs: 多个向量(2D或3D数组,每一行为一个向量)

返回:
一维数组,包含单个向量与每个多个向量的相似度

示例:
sims = cosine_similarity(from_vec, to_vecs)
"""
# 将from_vec转换为NumPy数组并强制类型为float
from_vec = np.array(from_vec, dtype=float)
# 将to_vecs转换为NumPy数组并强制类型为float
to_vecs = np.array(to_vecs, dtype=float)

# 计算from_vec的范数(模长)
norm1 = np.linalg.norm(from_vec)
# 如果from_vec是零向量,直接返回全0相似度数组
if norm1 == 0:
return np.zeros(len(to_vecs))

# 初始化用于存储相似度的列表
similarities = []
# 遍历每一个待比较的向量to_vec
for to_vec in to_vecs:
# 计算from_vec和to_vec的点积
dot_product = np.sum(from_vec * to_vec)
# 计算to_vec的模长(范数)
norm_vec = np.linalg.norm(to_vec)
# 如果to_vec为零向量,相似度设为0
if norm_vec == 0:
similarities.append(0.0)
else:
# 计算余弦相似度
similarity = dot_product / (norm1 * norm_vec)
similarities.append(similarity)

# 将列表转换为NumPy数组后返回
return np.array(similarities)

# 封装MMR算法为函数
def mmr_select(query_vector, doc_vectors, k=3, lambda_mult=0.5):
"""
使用最大边际相关性(MMR)算法选择文档

参数:
query_vector: 查询向量(1D数组)
doc_vectors: 文档向量集合(2D数组,每行一个文档向量)
k: 要选择的文档数量(默认3)
lambda_mult: λ参数,平衡相关性与多样性(默认0.5)
- λ=1: 只看相关性
- λ=0: 只看多样性
- λ=0.5: 平衡相关性和多样性

返回:
selected: 选中的文档索引列表(从0开始)

示例:
selected = mmr_select(query_vector, doc_vectors, k=3, lambda_mult=0.5)
"""
# 计算所有文档与查询向量的余弦相似度
query_similarities = cosine_similarity(query_vector, doc_vectors)

# 打印所有文档与查询向量的余弦相似度分数
print("文档与查询的相关性分数:")
for i, sim in enumerate(query_similarities):
# 依次输出文档编号(从1开始)及其相似度分数
print(f"文档{i+1}: {sim:.4f}")

# 找到与查询最相关的文档的下标,作为初始已选文档
selected = [int(np.argmax(query_similarities))]

# 不断循环直到已选择k个文档
while len(selected) < k:
# 存储每个未选文档的MMR分数(二元组)
mmr_scores = []
# 遍历所有文档编号
for i in range(len(doc_vectors)):
# 如果当前文档未被选择
if i not in selected:
# 获取当前文档与查询的相关性分数
relevance = query_similarities[i]
# 获取当前已选文档的向量集合
selected_vecs = doc_vectors[selected]
# 计算当前文档与所有已选文档的余弦相似度
sims = cosine_similarity(doc_vectors[i], selected_vecs)
# 取对已选集中最大相似度(最“不多样性的”)
max_sim = np.max(sims)
# 按MMR公式计算分数
mmr_score = lambda_mult * relevance - (1 - lambda_mult) * max_sim
# 存储该文档的编号和分数
mmr_scores.append((i, mmr_score))

# 如果没有可选文档则跳出循环
if not mmr_scores:
break

# 选取MMR分数最高的文档编号
best_idx, best_score = max(mmr_scores, key=lambda x: x[1])
# 将选中的文档添加到已选列表
selected.append(best_idx)

# 返回最终已选文档的索引
return selected

# 使用示例区域
# 设置λ参数,权衡相关性与多样性
lambda_mult = 1
# 指定需要选择的文档数量k
k = 3
# 构造查询向量(比如“人工智能”主题)
query_vector = np.array([4, 2])

# 构造待选文档的向量列表
doc_vectors = np.array([
[9, 2],
[2, 9],
[7, 8],
[1, 3],
[6, 1]
])
# 调用MMR算法选择文档
selected = mmr_select(query_vector, doc_vectors, k=k, lambda_mult=lambda_mult)

# 打印最终选中的所有文档的编号(转换为从1计算的序号)
print(f"\nλ={lambda_mult:.1f}: 最终选择文档 {[int(s)+1 for s in selected]}")

5.4 贪心策略的优缺点

优点

  • 简单高效,容易实现
  • 计算速度快

缺点

  • 可能不是全局最优解
  • 早期选择会影响后续选择

6. 实际应用示例

6.1 搜索引擎结果多样化

场景 :用户搜索"Python 教程"

不使用 MMR :可能返回 10 个都是"Python 基础教程"的相似结果

使用 MMR :返回多样化的结果

  • Python 基础语法教程
  • Python 数据分析教程
  • Python Web 开发教程
  • Python 机器学习教程
  • Python 爬虫教程

6.2 推荐系统

场景 :用户喜欢看科幻电影

不使用 MMR :可能推荐 10 部都是"太空科幻"的相似电影

使用 MMR :推荐多样化的科幻电影

  • 太空科幻(星际穿越)
  • 时间旅行(回到未来)
  • 人工智能(机械姬)
  • 反乌托邦(银翼杀手)
  • 外星人(E.T.)

6.3 文档摘要

场景 :为一篇长文章生成摘要

不使用 MMR :可能选择的句子都来自同一段落,信息重复

使用 MMR :选择覆盖文章不同方面的句子,信息更全面

7. 优缺点分析

7.1 优点

  1. 简单有效 :算法逻辑清晰,容易理解和实现
  2. 可调节 :通过 λ 参数可以灵活控制相关性和多样性的平衡
  3. 通用性强 :适用于多种场景(检索、推荐、摘要等)
  4. 效果明显 :能够有效减少结果冗余,提高信息覆盖度

7.2 缺点

  1. 贪心策略 :采用贪心算法,可能不是全局最优解
  2. 计算复杂度 :时间复杂度为 O(kn²),当文档数量 n 很大时,计算较慢
  3. 需要调参 :λ 参数需要根据具体任务调整,没有统一的最优值
  4. 依赖相似度计算 :效果很大程度上依赖于相似度计算的准确性

7.3 适用场景

适合使用 MMR 的场景

  • 需要返回多样化结果的检索系统
  • 推荐系统(避免推荐相似商品)
  • 文档摘要(选择覆盖不同方面的句子)
  • 结果数量较少(k < 20)的场景

不适合使用 MMR 的场景

  • 只需要相关性排序,不需要多样性
  • 文档数量非常大(n > 10000),计算成本高
  • 对实时性要求极高的场景