微信扫码
添加专属顾问
我要投稿
探索信息检索领域的最新优化技术,掌握MMR算法在提升用户体验中的实战应用。 核心内容: 1. 信息冗余对用户体验的影响分析 2. MMR算法的数学原理与核心机制 3. MMR算法在不同领域的应用实例与代码实现
本文面向 信息检索、推荐系统、自然语言处理领域的工程师以及对 RAG(Retrieval-Augmented Generation) 技术感兴趣的实践者,旨在帮助读者:
? 小提示MMR 不仅是一种算法,更体现了一种在信息排序中平衡多个目标的策略性思维。掌握它有助于构建更智能、用户体验更佳的信息系统。
本次文章配套代码: https://github.com/li-xiu-qi/XiaokeAILabs/blob/main/datas/test_mmr_search/test_mmr_search.py
本次主题:系统性解析最大边际相关性 (MMR) 算法,探讨其在抑制信息冗余、增强结果多样性方面的核心原理、实现细节与应用价值。
? 目录
让我们考虑一个常见的信息检索场景:当用户在搜索引擎中输入查询词,例如“苹果”,期望获得什么样的结果?如果返回的顶部结果清一色指向苹果公司的官方网站,尽管相关性极高,用户可能并不会满意。同样,在推荐系统中,若系统基于用户的某次点击,反复推荐风格、主题极其相似的内容,也会限制用户的发现空间。
这些例子揭示了信息服务中的一个核心挑战:信息冗余。单纯追求相关性的排序策略,虽然能找到匹配查询的结果,但当这些结果彼此间内容重叠度高时,用户从中获取的增量信息(Incremental Information)十分有限。有效的策略不仅应提供相关的结果,还应确保结果具有多样性(Diversity),覆盖用户潜在需求的多个方面,优化信息探索的过程。
为了应对这一挑战,最大边际相关性(Maximal Marginal Relevance, MMR)算法被提出。它提供了一种结构化且有效的方法来同时优化相关性与多样性,构成了现代信息检索与推荐技术的重要组成部分。接下来,我们将深入探讨 MMR 的运作原理。
最大边际相关性 (MMR) 是一种用于对项目集合进行排序或选择的算法,其根本目标是在确保所选项目与用户查询(Query)高度相关的基础上,最大化这些项目之间的内容差异性,从而减少输出结果的冗余度。
我们可以从第一性原理的角度来理解 MMR:在从一个较大的候选池中选择一系列项目(如文档、产品、新闻摘要)构建最终列表时,理想情况下,每一步新加入的项目,不仅要满足与原始查询的相关性要求,还应提供与已选项目集合不同的新视角或信息。
这里的关键词是“边际”(Marginal)。它关注的是向当前已选集合 中添加一个新项目 时所带来的综合价值增量。这个增量是衡量 与查询 的相关性以及 与 中已有项目的差异性的综合体现。
在 MMR 框架内,“边际相关性”具体衡量了一个候选项目 在被考虑加入已选集合 时,所能贡献的“净价值”。这个价值由两个相互作用的部分组成:
MMR 算法的设计思路就是在每一次迭代选择中,精确地挑出那个能最大化这种“边际综合价值”的候选项目。
对 MMR 的需求,根植于信息交互的现实逻辑和用户的信息处理特性:
因此,MMR 不仅是一种排序技术的改进,它更是对“如何设计信息呈现方式以更有效地服务于复杂的用户需求和认知模式”这一问题的解答。它承认并试图解决信息需求的多维性和用户对信息效率的内在偏好。
虽然本文的核心是 MMR,但了解其在更广泛的多样性优化技术谱系中的位置是有益的。平衡相关性与多样性的方法大致可分为几类:
基于重排(Re-ranking)的策略:
直接优化包含多样性的目标函数:
面向意图的多样化(Intent-aware Diversification):
MMR 因其概念直观、实现相对简单、计算开销可控而在工业界获得了广泛应用。它提供了一个清晰、可操作的框架来量化和平衡相关性与多样性这两个核心指标。
MMR 算法通过一个迭代过程来构建最终的结果列表。假设我们有一个初始候选项目集合 (通常是按相关性预排序的),以及一个初始化为空的目标集合 ,用于存储最终被选中的项目。算法重复执行以下步骤,直至 达到预定的大小 :
在每次迭代中,算法从尚未被选入 的候选项目集合 中,选择能够最大化以下 MMR 分数的项目 ,并将其加入 :
选择规则表达为:
这里的各个符号代表:
MMR 算法的执行流程可以概括为以下步骤:
设计思路:我们将实现一个函数 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 == 0or norm_vec2 == 0:
return0.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)。
"""
ifnot item_ids ornot item_embeddings or num_results <= 0:
return []
# 筛选出有效的候选ID(存在于embeddings字典中)
valid_candidate_ids = [id for id in item_ids if id in item_embeddings]
ifnot 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
ifnot 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:\Users\k\Desktop\BaiduSyncdisk\baidu_sync_documents\hf_models\bge-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 选择结果,我们可以清晰观察算法如何在相关性与多样性之间权衡:
需要注意的是:不同的模型需要使用适当调整多样性权重。
MMR 通过 λ 参数灵活调节相关性与多样性的平衡:
MMR 的核心思想——平衡相关性与多样性——使其在众多信息服务场景中具有广泛的应用价值:
? 技术全景图
? 学习汇总
? 动手挑战
♻️ 互动问题
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-10-27
2024-09-04
2024-07-18
2024-05-05
2024-06-20
2024-06-13
2024-07-09
2024-07-09
2024-05-19
2024-07-07
2025-04-16
2025-04-14
2025-04-13
2025-04-11
2025-04-09
2025-04-07
2025-04-05
2025-04-04