在信息检索与推荐系统中,如何让搜索结果既精准又丰富,避免千篇一律的重复内容?最大边际相关性(MMR)算法为此提供了一套行之有效的解决方案。本教程将系统解析MMR的原理、实现与应用场景,助你掌握这一优化利器。
前言
让我们设想一个常见的信息检索情境:当用户向搜索引擎输入查询词“苹果”时,期望看到怎样的结果?如果返回的顶部结果全是苹果公司的官网链接,虽然相关性极高,但用户很可能并不满意。同样,在推荐系统中,若系统因用户的一次点击,反复推送风格、主题极为相似的内容,也会限制用户的探索空间。
这些例子揭示了信息服务的核心难题:信息冗余。单纯追求相关性的排序策略,虽能找到匹配查询的结果,但当这些结果彼此内容重叠度高时,用户从中获取的增量信息(Incremental Information)十分有限。一套有效的策略不仅要提供相关的结果,还应确保结果具备多样性(Diversity),覆盖用户潜在需求的多个方面,优化信息探索的体验。
为应对这一挑战,最大边际相关性(Maximal Marginal Relevance, MMR)算法应运而生。它提供了一种结构化的方法来同时优化相关性与多样性,成为现代信息检索与推荐系统的重要组件。接下来,我们将深入剖析MMR的运作原理。
一、什么是最大边际相关性算法?
1.1 定义与核心理念
最大边际相关性 (MMR) 是一种对项目集合进行排序或选择的算法,其核心目标是在确保所选项目与用户查询(Query)高度相关的基础上,尽可能提升项目之间的内容差异性,从而降低输出结果的冗余度。
从第一性原理的角度来理解MMR:在从一个较大的候选池中挑选一系列项目(如文档、产品、新闻摘要)构建最终列表时,理想情况下,每一步新加入的项目既要满足与原始查询的相关性要求,还应提供与已选项目集合不同的新视角或信息。
这里的关键词是“边际”(Marginal)。它衡量的是向当前已选集合中加入一个新项目所能带来的综合价值增量,该增量是查询相关性与已选项目差异性的综合体现。
1.2 “边际”价值的构成
在MMR框架内,“边际相关性”具体衡量一个候选项目在被考虑加入已选集合时所能贡献的“净价值”。这个价值由两个相互作用的部分组成:
- 相关性贡献 (Relevance Contribution):项目与用户查询的匹配程度,通常用一个相关性评分函数量化。
- 多样性贡献 (Diversity Contribution):项目与已选集合中已有项目的不相似程度,通常通过惩罚与集合中最相似项目的相似度来减少冗余。
MMR算法的设计思路就是在每次迭代选择中,精确选出那个能最大化“边际综合价值”的候选项目。
小提示:MMR不仅是一种算法,更是一种在信息排序中平衡多个目标的策略性思维。掌握它有助于构建更智能、用户体验更佳的信息系统。
二、为什么需要最大边际相关性算法?
对MMR的需求根植于信息交互的现实逻辑和用户的信息处理特性:
- 优化认知负荷与信息获取效率:面对海量信息,人类的处理带宽有限。大量重复或高度相似的内容会增加认知负担,降低信息吸收效率。多样化的结果能让用户在有限交互内接触到更宽广、更多维度的信息,尤其在处理探索性或多层面查询(如“气候变化的影响”)时尤为关键,用户可能期望了解环境、经济、社会等不同维度的信息。
- 提升用户满意度与探索体验:过度个性化或单一维度的相关性排序可能导致“信息茧房”(Filter Bubble)效应,限制用户视野。引入多样性不仅能满足用户明确表达的需求,还能带来意外发现(Serendipity),增加探索乐趣,从而提升用户对系统的长期满意度和粘性。
- 支持更优的业务决策:在电子商务推荐或在线广告场景中,展示多样化的商品或广告创意有助于触达用户更广泛的潜在兴趣点,可能促进长尾商品的销售或提高广告活动的整体转化效果,避免流量过度集中于少数头部项目。
- 处理查询意图的不确定性:用户查询往往存在歧义(Ambiguity)。例如搜索“Python”,用户可能指的是编程语言,也可能指蟒蛇。通过MMR引入多样性,可以在结果中适当包含与不同潜在意图相关的代表性内容,从而提高覆盖用户真实意图的概率。
因此,MMR不仅是一种排序技术的改进,更是对“如何设计信息呈现方式以更有效地服务于复杂的用户需求和认知模式”这一问题的回应。它承认并试图解决信息需求的多维性和用户对信息效率的内在偏好。
三、平衡相关性与多样性的相关方法论
虽然本文核心聚焦MMR,但了解其在更广泛的多样性优化技术谱系中的位置是有益的。平衡相关性与多样性的方法大致可分为几类:
基于重排(Re-ranking)的策略:
- MMR是此类方法的经典代表:首先获取一个基于相关性的初始排序列表,然后应用MMR准则进行迭代选择和重排序,将多样性因素整合进来。
- 其他启发式或基于分类的技术:例如,可以先对初始结果进行聚类或主题建模,再从每个簇/主题中选择高质量、有代表性的项目构成最终列表。
直接优化包含多样性的目标函数:
- 一些更复杂的模型,如行列式点过程(Determinantal Point Processes, DPP),试图在模型训练或推断阶段直接优化一个同时包含相关性和多样性度量的全局目标函数。DPP利用行列式的性质来优雅地刻画集合的“容量”或“多样性”,是另一个重要的多样性建模工具。
面向意图的多样化(Intent-aware Diversification):
- 针对具有多种解释的查询,系统首先识别出几个可能的子意图(Subtopics),然后为每个子意图检索相关结果,并根据意图的概率或重要性将这些结果组合起来,确保覆盖主要的查询方面。
MMR因其概念直观、实现相对简单、计算开销可控而在工业界获得了广泛应用。它提供了一个清晰、可操作的框架来量化和平衡相关性与多样性这两个核心指标。
四、MMR算法详解
4.1 核心数学表达
MMR算法通过一个迭代过程来构建最终的结果列表。假设我们有一个初始候选项目集合(通常是按相关性预排序的),以及一个初始化为空的目标集合,用于存储最终被选中的项目。算法重复执行以下步骤,直至达到预定的大小:
在每次迭代中,算法从尚未被选入的候选项目集合中,选择能够最大化以下MMR分数的项目,并将其加入:
选择规则表达为:
MMR = arg max [ λ·Sim1(Di, Q) - (1-λ)·max Sim2(Di, Dj) ]
这里的各个符号代表:
- : 当前正在被评估的候选项目。
- : 用户的查询或上下文信息。
- : 已经选入最终结果列表的项目集合。
- : 剩余的候选项目集合。
- : 相关性评分函数。用于计算候选项目与查询的相似度或匹配度。具体实现可以是余弦相似度、BM25分数或其他任何衡量相关性的指标。值越大表示越相关。
- : 冗余性(或相似性)度量函数。计算候选项目与已选项目之间的相似度。通常采用与相同或兼容的度量方式(例如,都使用向量空间模型的余弦相似度)。值越大,表示与内容越接近,加入可能带来的冗余越高。
- : 这个表达式计算了候选项目与所有已选项目(集合中)的相似度,并取出其中的最大值。这代表了相对于当前已选集合的最大潜在冗余度。
- (Lambda): 平衡参数,取值范围为。它控制着相关性与多样性之间的权衡:
- 当时,公式只考虑项,MMR退化为纯粹的相关性排序。
- 当时,公式变为最小化与已选项目集合的最大相似度(即),极端情况下可能优先选择相关性较低但差异性极大的项目(不过,初始候选集通常已保证了一定的相关性)。
- 当时,算法在相关性得分和多样性惩罚(由最大冗余度体现)之间进行权衡。值越高,结果越偏向相关性;值越低,结果越偏向多样性。表示两者同等重要。
注意:λ参数的取值直接影响最终结果的质量,需要根据业务场景反复调试。
4.2 迭代选择流程
MMR算法的执行流程可以概括为以下步骤:
- 初始化:
- 获取初始候选项目列表。这通常是搜索引擎或推荐系统召回和初步排序的结果,例如Top-K相关文档。
- 初始化一个空的结果列表。
- 设定平衡参数和期望选择的项目总数。
- 选择第一个项目:
- 由于初始时为空,公式中的项通常被视为0或一个非常小的值(不影响比较)。
- 此时,MMR分数主要由决定。
- 因此,选择中相关性最高的项目,将其加入。更新。
- 迭代选择后续项目 (执行次):
- 对于当前候选集中的每一个项目:
- 计算其与查询的相关性得分。
- 计算其与当前已选集合中每个项目的相似度。
- 找到这些相似度中的最大值:。
- 计算的MMR分数:。
- 从中选择具有最高MMR分数的项目。
- 将从移动到。更新。
- 对于当前候选集中的每一个项目:
- 终止:当中的项目数量达到时,或者候选集为空时,算法停止。集合中的项目,按照它们被选入的顺序,构成了最终的、兼顾相关性与多样性的结果列表。
五、MMR算法代码实现 (Python示例)
设计思路:我们将实现一个函数mmr_selection,它接收查询表示、候选项目表示、初始排名、参数和目标数量作为输入。函数内部将模拟MMR的迭代选择过程:首先选出最相关的项目,然后在后续步骤中,为每个剩余候选者计算MMR分数(结合其与查询的相关性及与已选项目的最大相似度),并选择分数最高的项目加入结果集,直至达到数量。
示例代码:我们使用FlagEmbedding库加载bge-large-zh-v1.5模型来获取句子向量,并使用余弦相似度作为和的度量。
import numpy as np
from FlagEmbedding import FlagModel # 导入 FlagModel
def cosine_similarity(vec1, vec2):
"""计算两个 NumPy 向量的余弦相似度"""
vec1 = np.asarray(vec1)
vec2 = np.asarray(vec2)
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
if norm_vec1 == 0 or norm_vec2 == 0:
return 0.0
similarity = dot_product / (norm_vec1 * norm_vec2)
# 根据需要,可以考虑相似度范围,例如映射到 [0, 1]
# return max(0.0, similarity) # 如果需要非负相似度
return similarity # 使用原始余弦相似度 [-1, 1]
def mmr_selection(query_embedding, item_embeddings, item_ids, lambda_param, num_results):
"""
使用 MMR 算法执行项目选择
Args:
query_embedding (np.array): 查询的向量表示。
item_embeddings (dict): 候选项目向量表示的字典 {item_id: np.array}。
item_ids (list): 初始候选项目 ID 列表 (通常是字符串ID)。
lambda_param (float): MMR 的权衡参数 lambda (0 <= lambda <= 1)。
num_results (int): 需要选择的结果数量 N。
Returns:
list: 最终选出的项目 ID 列表 (字符串ID)。
"""
if not item_ids or not item_embeddings or num_results <= 0:
return []
# 筛选出有效的候选ID(存在于embeddings字典中)
valid_candidate_ids = [id for id in item_ids if id in item_embeddings]
if not valid_candidate_ids:
return []
candidate_pool = set(valid_candidate_ids)
selected_item_ids = []
# 预计算所有有效候选项目与查询的相关性 (Sim_1)
candidate_relevance = {
id: cosine_similarity(query_embedding, item_embeddings[id])
for id in valid_candidate_ids
}
# 确保 N 不超过有效候选者数量
num_results = min(num_results, len(valid_candidate_ids))
# 第一步:选择最相关的项目
if valid_candidate_ids:
first_selection_id = max(candidate_relevance, key=candidate_relevance.get)
selected_item_ids.append(first_selection_id)
candidate_pool.remove(first_selection_id)
# 后续迭代选择
while len(selected_item_ids) < num_results and candidate_pool:
mmr_scores = {}
selected_embeddings_list = [item_embeddings[id] for id in selected_item_ids] # 获取已选项目的向量
for candidate_id in candidate_pool:
candidate_emb = item_embeddings[candidate_id]
# Sim_1: 获取预计算的相关性
relevance_score = candidate_relevance.get(candidate_id, -1.0) # 使用预计算的相关性, -1.0作为默认值
# Sim_2: 计算与已选项目的最大相似度
max_similarity_with_selected = -1.0 # 初始化为可能的最低余弦相似度
if selected_item_ids: # 仅当 S 非空时计算
similarities_to_selected = [cosine_similarity(candidate_emb, sel_emb) for sel_emb in selected_embeddings_list]
if similarities_to_selected:
max_similarity_with_selected = max(similarities_to_selected)
# 计算 MMR 分数
# MMR Score = λ * Sim1(Di, Q) - (1 - λ) * max(Sim2(Di, Dj)) for Dj in S
# 注意:如果 Sim1 和 Sim2 可能为负,需要确保公式逻辑正确
mmr_score = lambda_param * relevance_score - (1 - lambda_param) * max_similarity_with_selected
mmr_scores[candidate_id] = mmr_score
if not mmr_scores: # 如果没有更多可计算分数的候选者
break
# 选择当前迭代中 MMR 分数最高的项目
best_next_id = max(mmr_scores, key=mmr_scores.get)
selected_item_ids.append(best_next_id)
candidate_pool.remove(best_next_id) # 从候选池中移除
return selected_item_ids
# --- 使用 FlagEmbedding 获取向量并运行 MMR ---
# 1. 加载模型 (请确保模型路径正确)
model_path = r"C:UserskDesktopBaiduSyncdiskbaidu_sync_documentshf_modelsbge-large-zh-v1.5"
try:
model = FlagModel(model_path, use_fp16=True) # 尝试使用 FP16 加速
print("模型加载成功。")
except Exception as e:
print(f"模型加载失败: {e}")
# 在此可以添加退出或使用备用逻辑
exit() # 或者 return, raise e 等
# 2. 定义查询和候选句子
query_sentence = "大型语言模型有哪些应用?"
candidate_sentences = [
# 与查询直接相关 - 应用类
"大语言模型可用于文本生成,例如写诗歌或代码。", # id=s1
"机器翻译是大语言模型的常见应用场景之一。", # id=s2
"聊天机器人和智能客服常常基于大型语言模型构建。",# id=s3
"大型模型能够进行文本摘要和信息抽取。", # id=s4
# 与查询相关 - 原理/定义类 (与应用类有差异)
"大型语言模型通常指参数量巨大的深度学习模型。", # id=s5
"Transformer架构是现代大语言模型的基础。", # id=s6
"训练大型语言模型需要海量的文本数据和计算资源。",# id=s7
# 不太相关或离题
"今天天气真不错。", # id=s8
"人工智能的研究历史悠久。", # id=s9
]
# 为句子分配 ID
candidate_ids = [f"s{i+1}" for i in range(len(candidate_sentences))]
# 创建ID到句子的映射字典
id_to_sentence = {candidate_ids[i]: candidate_sentences[i] for i in range(len(candidate_sentences))}
# 3. 获取所有句子的嵌入向量
all_sentences = [query_sentence] + candidate_sentences
print("开始计算嵌入向量...")
embeddings = model.encode(all_sentences)
print(f"嵌入向量计算完成,形状: {embeddings.shape}") # 应为 (1 + len(candidate_sentences), 1024)
query_embedding = embeddings[0]
item_embeddings_dict = {candidate_ids[i]: embeddings[i+1] for i in range(len(candidate_sentences))}
# 4. 设定参数并运行 MMR
# 假设初始列表基于某种粗排得到(这里简化为原始顺序)
initial_ranked_ids = candidate_ids
num_select = 5 # 期望选出5个结果
# 场景1: 更注重相关性
lambda_high = 0.7
selected_high_lambda = mmr_selection(query_embedding, item_embeddings_dict, initial_ranked_ids, lambda_high, num_select)
print(f"\n--- MMR 选择结果 (lambda={lambda_high}, N={num_select}) ---")
print("选定句子ID:", selected_high_lambda)
print("选定句子内容:")
for i, item_id in enumerate(selected_high_lambda):
print(f"{i+1}. ID={item_id}: {id_to_sentence[item_id]}")
# 场景2: 更注重多样性
lambda_low = 0.3
selected_low_lambda = mmr_selection(query_embedding, item_embeddings_dict, initial_ranked_ids, lambda_low, num_select)
print(f"\n--- MMR 选择结果 (lambda={lambda_low}, N={num_select}) ---")
print("选定句子ID:", selected_low_lambda)
print("选定句子内容:")
for i, item_id in enumerate(selected_low_lambda):
print(f"{i+1}. ID={item_id}: {id_to_sentence[item_id]}")
代码说明与分析:上述代码首先加载了bge-large-zh-v1.5模型。然后,定义了一个查询句和一组包含不同类别(应用、原理、无关)的候选句子。通过model.encode()计算得到所有句子的嵌入向量。最后,调用mmr_selection函数两次,分别使用高(0.7)和低(0.3)来选择Top 5的句子。
输出:
嵌入向量计算完成,形状: (10, 1024)
--- MMR 选择结果 (lambda=0.7, N=5) ---
选定句子ID: ['s5', 's1', 's2', 's3', 's7']
选定句子内容:
1. ID=s5: 大型语言模型通常指参数量巨大的深度学习模型。
2. ID=s1: 大语言模型可用于文本生成,例如写诗歌或代码。
3. ID=s2: 机器翻译是大语言模型的常见应用场景之一。
4. ID=s3: 聊天机器人和智能客服常常基于大型语言模型构建。
5. ID=s7: 训练大型语言模型需要海量的文本数据和计算资源。
--- MMR 选择结果 (lambda=0.3, N=5) ---
选定句子ID: ['s5', 's8', 's9', 's1', 's6']
选定句子内容:
1. ID=s5: 大型语言模型通常指参数量巨大的深度学习模型。
2. ID=s8: 今天天气真不错。
3. ID=s9: 人工智能的研究历史悠久。
4. ID=s1: 大语言模型可用于文本生成,例如写诗歌或代码。
5. ID=s6: Transformer架构是现代大语言模型的基础。
结果分析
通过对比λ=0.7和λ=0.3的MMR选择结果,我们可以清晰观察算法如何在相关性与多样性之间权衡:
1. 高λ值 (0.7):偏向相关性
- 特点:选出的5个结果全部与查询“大型语言模型有哪些应用?”高度相关。
- 内容分布:包括“大型语言模型可用于文本生成,例如写诗歌或代码”、“机器翻译是大语言模型的常见应用场景之一”、“聊天机器人和智能客服常常基于大型语言模型构建”等应用场景,以及“大型语言模型通常指参数量巨大的深度学习模型”和“训练大型语言模型需要海量的文本数据和计算资源”等原理描述。
- 效果:结果聚焦查询主题,提供实用且互补的信息,适合需要深度相关内容的场景。
2. 低λ值 (0.3):偏向多样性
- 特点:结果既有与查询相关的句子,也有完全无关的内容,但是很明显,无关的内容直接排到第二了,说明这里的参数设置得过于多样化了。
- 内容分布:首选“大型语言模型通常指参数量巨大的深度学习模型”作为最相关项,随后加入“今天天气真不错”和“人工智能的研究历史悠久”等无关句子,最后补选“大型语言模型可用于文本生成,例如写诗歌或代码”和“Transformer架构是现代大语言模型的基础”等相关但主题分散的内容。
- 效果:牺牲部分相关性换取更高多样性,适合探索性场景或查询有歧义时覆盖多方面信息。
3. 共同点与机制
- 首选一致:两种λ值下,首个选择均为“大型语言模型通常指参数量巨大的深度学习模型”,因初始步骤只考虑相关性(Sim_1)。
- 迭代逻辑:后续选择中,λ控制相关性 (λ·Sim1) 与多样性 ((1-λ)·max Sim2) 的权重:
- λ=0.7:相关性权重0.7,多样性0.3,倾向选择与查询紧密相关的句子。
- λ=0.3:多样性权重0.7,相关性0.3,倾向选择与已选内容差异大的句子。
4. 核心优势
MMR通过λ参数灵活调节相关性与多样性的平衡:
p>- 高λ适合d>
- 高λ适合 - λ 0.9 (重度相关性偏重); li> 实际应用中λ应结合业务深度,而
- 精准检索λ=0.8~0.9,多样化推荐λ=0.2~0.4。
六、MMR算法的应用场景
MMR的核心思想——平衡相关性与多样性——使其在众多信息服务场景中具有广泛的应用价值:
- 搜索引擎结果页 (SERP) 优化:
- 当用户查询词具有多重含义或指向多个子主题时(例如,“Jaguar”可能指汽车品牌或动物),MMR可以帮助确保搜索结果不仅相关,而且能覆盖这些不同的方面,提供更全面的信息视图。
- 推荐系统 (Recommender Systems):
- 商品推荐:避免向用户展示一系列极其相似的商品。例如,在推荐服装时,可以利用MMR推荐不同款式、颜色或品牌的衣服,而不是多件同款不同色的T恤。
- 内容(新闻/视频/音乐)推荐:对于同一事件或主题,推送不同视角、来源或风格的内容,避免信息茧房,提升用户发现新内容的体验。
- 自动文本摘要 (Text Summarization):
- 在抽取式摘要任务中,需要从原文中选择若干句子构成摘要。MMR可用于选择那些既能反映原文核心要点(与原文或主题具有高,又彼此间信息重叠较少(句子间较低)的关键句子。
- 计算广告 (Computational Advertising):
- 广告创意轮播与多样化:对于同一广告系列,系统可以使用MMR来选择向特定用户展示的广告创意组合,确保创意之间具有一定的差异性(如不同的视觉元素、文案或促销点),以减少用户疲劳,并探索不同创意的有效性。
- 检索增强生成 (RAG) 中的上下文选择:
- 在RAG架构中,检索模块获取的文档片段(Chunks)若高度相似,将为后续的大语言模型(LLM)提供冗余的上下文信息。应用MMR对检索到的Chunks进行重排,可以选择出既与原始查询相关、又包含互补信息的Chunks集合,从而提升LLM生成答案的质量和全面性。
小提示:在RAG系统中,结合MMR可以显著减少大模型因输入上下文重复导致的“幻觉”风险。
总结与展望
通过本教程,我们全面了解了最大边际相关性(MMR)算法的原理、实现和应用。MMR通过引入多样性维度,有效解决了传统相关性排序中的信息冗余问题,在搜索引擎、推荐系统、文本摘要等众多领域发挥着重要作用。其核心参数λ提供了灵活的控制机制,使得开发者可以根据业务需求在相关性与多样性之间精确平衡。随着信息爆炸时代的持续,MMR这类兼顾多重目标的重排策略将越来越重要。未来,结合深度学习更复杂的多样性度量,MMR有望在更多智能系统中发挥价值。
常见问题
- 问题1: MMR算法致力于解决的核心问题是什么?它是通过何种机制来实现这一目标的?
答案: MMR解决的核心问题是信息冗余,即传统排序只关注相关性导致结果同质化,用户获取增量信息有限。它通过迭代选择,在每一步综合考虑候选项目与查询的相关性(Sim1)和与已选项目的差异性(Sim2),利用参数λ进行加权平衡,选出边际价值最高的项目。 - 问题2: 在MMR的数学表达式中,平衡参数λ扮演了什么角色?当λ的值分别设定为1和0时,算法的行为特征是怎样的?
答案: λ控制相关性与多样性的权重。当λ=1时,MMR退化为纯粹的相关性排序(仅考虑Sim1),结果与初始排序一致;当λ=0时,算法仅考虑最小化与已选项目的相似度(即最大多样性),可能忽略相关性,导致结果中充满与查询无关但彼此差异大的项目。 - 问题3: 请列举一个除搜索引擎结果排序之外的MMR应用实例,并具体说明在该场景下,和可以如何定义?
答案: 例如在新闻推荐系统中,用户查询“科技新闻”。可以定义为新闻与用户兴趣偏好的相关度(如基于历史点击的TF-IDF余弦相似度),可以定义为两篇新闻之间主题的相似度(如使用新闻标题的SBERT嵌入余弦相似度)。MMR将选择既有相关性又覆盖不同科技子话题(如AI、航天、芯片)的新闻条目。
本次文章配套代码: https://github.com/li-xiu-qi/XiaokeAILabs/blob/main/datas/test_mmr_search/test_mmr_search.py
