导航菜单

  • 1.vector
  • 2.milvus
  • 3.pymilvus
  • 4.rag
  • 5.rag_measure
  • 7.search
  • ragflow
  • heapq
  • HNSW
  • cosine_similarity
  • math
  • typing
  • etcd
  • minio
  • collections
  • jieba
  • random
  • beautifulsoup4
  • chromadb
  • sentence_transformers
  • numpy
  • lxml
  • openpyxl
  • PyMuPDF
  • python-docx
  • requests
  • python-pptx
  • text_splitter
  • all-MiniLM-L6-v2
  • openai
  • llm
  • BPETokenizer
  • Flask
  • RAGAS
  • BagofWords
  • langchain
  • Pydantic
  • abc
  • faiss
  • MMR
  • scikit-learn
  • 1. 什么是 MMR?
    • 1.1 为什么需要 MMR?
  • 2. MMR 的核心思想
    • 2.1 两个关键目标
    • 2.2 如何平衡?
  • 3. 算法公式
    • 3.1 公式符号说明
    • 3.2 公式含义
  • 4. 参数 λ 的作用
    • 4.1 λ 的取值范围
    • 4.2 如何选择 λ?
  • 5. 算法步骤
    • 5.1 算法流程
    • 5.2 为什么先选最相关的?
    • 5.3 代码实现
    • 5.4 贪心策略的优缺点
  • 6. 实际应用示例
    • 6.1 搜索引擎结果多样化
    • 6.2 推荐系统
    • 6.3 文档摘要
  • 7. 优缺点分析
    • 7.1 优点
    • 7.2 缺点
    • 7.3 适用场景

1. 什么是 MMR? #

  • 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 通过一个公式来平衡这两个目标:

$$ \text{MMR分数} = \lambda \times \text{相关性} - (1 - \lambda) \times \text{与已选文档的最大相似度} $$

这个公式的含义是:

  • 相关性部分($\lambda \times \text{相关性}$):我们希望文档与查询相关,这部分越大越好
  • 多样性部分($(1 - \lambda) \times \text{最大相似度}$):我们希望文档与已选文档不同,这部分越小越好(所以用减法)

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

3. 算法公式 #

MMR 的完整公式如下:

$$ \text{MMR} = \arg\max_{D_i \in R \setminus S} \left[ \lambda \cdot \text{Sim}_1(D_i, Q) - (1 - \lambda) \cdot \max_{D_j \in S} \text{Sim}_2(D_i, D_j) \right] $$

3.1 公式符号说明 #

  • $D_i$:当前正在评估的候选文档
  • $Q$:查询(用户输入的问题或关键词)
  • $S$:已经选择的结果集(已选文档的集合)
  • $R$:所有候选文档的集合
  • $R \setminus S$:候选文档中还未被选中的部分
  • $\text{Sim}_1(D_i, Q)$:文档 $D_i$ 与查询 $Q$ 的相关性评分
  • $\text{Sim}_2(D_i, D_j)$:文档 $D_i$ 与已选文档 $D_j$ 的相似度
  • $\max_{D_j \in S} \text{Sim}_2(D_i, D_j)$:文档 $D_i$ 与所有已选文档的最大相似度
  • $\lambda$:权衡参数,取值范围是 0 到 1

3.2 公式含义 #

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

MMR 分数由两部分组成:

  1. 相关性奖励:$\lambda \times \text{Sim}_1(D_i, Q)$,文档与查询越相关,这部分越大
  2. 多样性惩罚:$(1 - \lambda) \times \max_{D_j \in S} \text{Sim}_2(D_i, D_j)$,文档与已选文档越相似,这部分越大,但因为是减法,所以会降低总分

4. 参数 λ 的作用 #

参数 $\lambda$(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 代码实现 #

  • MMR执行
# 导入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),计算成本高
  • 对实时性要求极高的场景

访问验证

请输入访问令牌

Token不正确,请重新输入