Memory向量记忆系统3-数据选取

从SQLite里召回的数据有很多,都要保留吗?可能返回的前 5 条结果可能都在讲同一个话题的不同段落------内容高度重复,信息增益有限。或者有的已经是一年前的信息了。

一、MMR算法

MMR(Maximal Marginal Relevance,最大边际相关性)是用来**平衡 "相关性" 和 "多样性"**的经典算法,最常用于 RAG 检索结果的去重 / 多样化。

解决什么问题?

假设你搜"苹果",召回 10 条结果:

  • 前 5 条全是"苹果手机"(主题重复)
  • 后 5 条是"苹果水果""苹果公司""苹果音乐"等

直接按相似度排序,前几条全是重复主题,用户想看其他角度还得往下翻。

MMR 解决的就是:在保证相关性的前提下,让结果尽量多样化,避免扎堆。

核心公式

MMR=λ⋅Sim(Q,Di)−(1−λ)⋅maxDj∈sSim(Di,Dj)

拆解成三部分:

部分 含义 作用
λ · Sim(Q, Dᵢ) 文档和查询的相似度 保证相关性
(1-λ) · max Sim(Dᵢ, Dⱼ) 文档和已选结果的最大相似度 惩罚重复度
λ 权重参数(0~1) 调平衡的旋钮

大白话翻译:

一个文档的最终得分 = (它跟问题有多相关 × 相关性权重) 减去 (它跟已选结果有多像 × 多样性权重)

关键概念详解

1. Sim(Q, D) ------ 相似度

含义:查询(Query)和文档(Document)的相关程度,值在 0~1 之间,越大越相关。

常见计算方式

方式 原理 适用场景
余弦相似度 两个向量的夹角余弦 embedding 向量、语义匹配(最常用)
Jaccard 相似度 集合交集 ÷ 集合并集 关键词、标签、集合型数据
内积(点积) 向量点积 归一化后等价于余弦

余弦相似度公式(最常用):Sim (Q, D) = (Q・D) / (‖Q‖ × ‖D‖)

向量归一化后(模长=1),直接点积就是余弦相似度。

2. λ(lambda)------ 权重旋钮

含义:控制相关性和多样性的平衡,取值 0~1。

λ 值 效果 适用场景
1.0 完全按相关性排序,不去重 就要最相关的,不管重复
0.7 相关性为主,兼顾多样性 大多数 RAG 场景(常用)
0.5 相关性和多样性各半 想要结果多样化
0.3 多样性为主 更看重覆盖面
0.0 完全追求多样性 只要不一样的,不管相关不相关

经验值 :RAG 场景一般用 0.5 ~ 0.7

完整计算示例

场景

  • 查询 Q:"苹果"
  • 召回 5 个文档:A(手机)、B(手机)、C(水果)、D(公司)、E(手机)
  • λ = 0.5(各占一半)
  • 相似度矩阵:
A B C D E
和查询的相似度 0.95 0.90 0.75 0.70 0.85
和A的相似度 - 0.98 0.30 0.40 0.95
和C的相似度 0.30 0.25 - 0.50 0.20
和D的相似度 0.40 0.35 0.50 - 0.30

第 1 轮:选第一个

所有文档都没选过,直接选最相关的:

  • A(相似度 0.95 最高)
  • 已选集合 S = {A}

第 2 轮:选第二个

计算剩下每个文档的 MMR 分数:

文档 相关性 (λ×Sim) 重复惩罚 ((1-λ)×maxSim) MMR 分数
B 0.5 × 0.90 = 0.45 0.5 × 0.98 = 0.49 0.45 - 0.49 = -0.04
C 0.5 × 0.75 = 0.375 0.5 × 0.30 = 0.15 0.375 - 0.15 = 0.225
D 0.5 × 0.70 = 0.35 0.5 × 0.40 = 0.20 0.35 - 0.20 = 0.15
E 0.5 × 0.85 = 0.425 0.5 × 0.95 = 0.475 0.425 - 0.475 = -0.05

选 C(MMR 最高)

  • 虽然 C 和查询的相似度(0.75)比 B(0.90)低
  • 但它跟已选的 A 差异大,多样性加分后整体排第二
  • 已选集合 S = {A, C}

第 3 轮:选第三个

文档 相关性 重复惩罚(和A、C中最大的) MMR 分数
B 0.45 0.5 × max(0.98, 0.25) = 0.49 -0.04
D 0.35 0.5 × max(0.40, 0.50) = 0.25 0.10
E 0.425 0.5 × max(0.95, 0.20) = 0.475 -0.05

选 D

  • 已选集合 S = {A, C, D}

最终结果

MMR 排序:A → C → D → B → E

对比纯相似度排序(A → B → E → C → D):

排序方式 前3名 效果
纯相似度 A(手机) → B(手机) → E(手机) 全是手机,重复度高
MMR A(手机) → C(水果) → D(公司) 三个不同主题,多样性好

代码实现(Python)

ini 复制代码
import numpy as np

def mmr(query_embedding, doc_embeddings, doc_ids, lambda_param=0.5, top_k=5):
    """
    MMR 算法
    
    参数:
        query_embedding: 查询向量 (d,)
        doc_embeddings: 文档向量矩阵 (n, d)
        doc_ids: 文档ID列表
        lambda_param: λ 参数,0~1
        top_k: 返回数量
    
    返回:
        排序后的文档ID列表
    """
    # 归一化,方便算余弦相似度
    query_norm = query_embedding / np.linalg.norm(query_embedding)
    doc_norms = doc_embeddings / np.linalg.norm(doc_embeddings, axis=1, keepdims=True)
    
    # 所有文档和查询的相似度
    query_sims = doc_norms @ query_norm  # shape: (n,)
    
    selected = []
    unselected = list(range(len(doc_ids)))
    
    for _ in range(min(top_k, len(doc_ids))):
        best_mmr = -float('inf')
        best_idx = -1
        
        for idx in unselected:
            # 第一部分:和查询的相关性
            relevance = lambda_param * query_sims[idx]
            
            # 第二部分:和已选文档的最大相似度(惩罚重复)
            if len(selected) == 0:
                diversity_penalty = 0
            else:
                selected_sims = doc_norms[idx] @ doc_norms[selected].T
                diversity_penalty = (1 - lambda_param) * np.max(selected_sims)
            
            # MMR 分数
            mmr_score = relevance - diversity_penalty
            
            if mmr_score > best_mmr:
                best_mmr = mmr_score
                best_idx = idx
        
        selected.append(best_idx)
        unselected.remove(best_idx)
    
    return [doc_ids[i] for i in selected]


# 使用示例
query = np.random.rand(1536).astype(np.float32)
docs = np.random.rand(100, 1536).astype(np.float32)
ids = list(range(100))

result = mmr(query, docs, ids, lambda_param=0.6, top_k=10)
print(result)

应用场景

  1. RAG 检索去重:最常用,避免召回的 chunk 内容重复
  2. 推荐系统:推荐结果多样化,避免全是同类
  3. 搜索结果多样化:搜索引擎的结果多样性
  4. 摘要生成:选信息量大、不重复的句子
  5. 聚类选代表:从每个簇里选最有代表性的

和其他去重方式对比

方法 原理 优点 缺点
MMR 贪心选边际相关性最大的 效果好,可调参数,经典 计算量稍大(两两算相似度)
聚类后选代表 先聚类,每类选一个 多样性强 聚类质量影响大,实现复杂
阈值去重 相似度超阈值就去掉 简单快 阈值不好调,可能丢重要信息
最大最小距离 选最远的点 多样性最强 可能完全不相关

一句话总结

MMR = 既要相关,又要不一样。

每次选文档时,都选"跟查询相关度高、但跟已经选过的差异大"的那个,用 λ 来调两者的权重。RAG 里用它来避免召回的内容全是一个意思,提升答案的丰富度。

二、时间衰减

并非所有记忆都应该拥有相同的权重。昨天的对话比去年的对话更可能与当前任务相关。时间衰减通过指数衰减模型实现"近期优先"。

OpenClaw 的时间衰减用的是指数衰减模型 ,灵感来自心理学的艾宾浩斯遗忘曲线

核心公式

其中:

  • λ(lambda) = ln(2) / 半衰期天数
  • 半衰期 = 经过这么多天后,分数变成原来的一半(默认 30 天)

具体例子(默认半衰期 30 天)

时间 衰减系数 权重
今天 1.0 💯 100%
7 天前 ~0.84 🟢 84%
30 天前 0.5 🟡 50%(半衰期)
60 天前 0.25 🟠 25%
90 天前 0.125 🔴 12.5%(只剩 1/8)

规律:每过 30 天,权重减半。

代码实现(TypeScript)

typescript 复制代码
function temporalDecay(params: {
  ageInDays: number;      // 距离今天多少天
  halfLifeDays: number;   // 半衰期天数,默认30
}): number {
  const lambda = Math.LN2 / params.halfLifeDays;  // λ = ln2 / 半衰期
  return Math.exp(-lambda * params.ageInDays);    // e^(-λ × 天数)
}

// 使用
const score = 0.9;                    // 原始相似度得分
const days = 30;                      // 30天前的内容
const decay = temporalDecay({ ageInDays: days, halfLifeDays: 30 });
const finalScore = score * decay;     // 0.9 × 0.5 = 0.45

为什么用指数衰减?

  1. 符合遗忘规律:人的记忆就是指数式遗忘的,刚发生的事记得清楚,越久忘得越多
  2. 平滑过渡:不是突然降到 0,而是慢慢衰减,更自然
  3. 只有一个参数:调半衰期就行,简单直观
  4. 数学性质好:半衰期翻倍,衰减速度减半,容易理解和调参

🔧 怎么调参?

半衰期 效果 适用场景
7 天 衰减很快,非常看重时效性 新闻、热点、实时数据
30 天 适中,默认值 一般对话记忆、日常笔记
90 天 衰减慢,比较看重历史 知识库、长期记忆
365 天 几乎不衰减 常青知识、文档

OpenClaw 还有一些特殊机制

1. 常青文件不衰减

MEMORY.md 这种重要的总结文件,不参与时间衰减,永远是满分权重。

2. 访问次数抵抗衰减

  • 如果你经常引用某个知识点(accessCount 高),它会抵抗衰减
  • 即使几周没提到,也不会很快变"冷"
  • 防止重要但不常提的知识被遗忘

3. 重新加热(Reheat)

  • 一个很久没访问的"冷"知识,一旦被搜到/被引用
  • 就会重新加热,下次排名又变高
  • 类似"复习了一下,记忆又清晰了"

一句话总结

OpenClaw 的时间衰减 = 指数衰减 + 半衰期 30 天 + 常青文件不衰减 + 高频访问抵抗衰减。

本质就是模拟人的遗忘曲线:刚发生的记得牢,越久越模糊,但重要的东西忘得慢,复习一下又想起来了。

总结

openclaw的召回流程如下:

技术真的发展到一定程度了,很多时候基建都有了,我们就是组装。但里面有很多很多细节,都属于工程范畴,能够把产品推得更前面、更强一些。

相关推荐
程序员麻辣烫1 小时前
Memory向量记忆系统2-SQLite
后端·aigc
阳光是sunny1 小时前
LangGraph高级教程:Multi Schema多状态管理详解
前端·人工智能·后端
神奇霸王龙2 小时前
Gemini CLI 中转站配置使用教程
人工智能·ai·ai作画·aigc·ai编程·gemini·goolge
阳光是sunny2 小时前
LangGraph实战教程:状态管理与`graph.invoke`入参深度解析
前端·人工智能·后端
神奇小汤圆2 小时前
深入解析 Flink Kafka Connector:原理、配置与最佳实践
前端·后端
神奇小汤圆3 小时前
在 Nacos 点了下线,为什么流量还是打到了停机的机器上?
后端
KaneLogger3 小时前
花了2天写了个全平台的技能管理工具
aigc·agent·ai编程
阳光是sunny3 小时前
LangGraph实战教程:一文搞懂图的状态(State)管理
前端·人工智能·后端
atbigapp.com3 小时前
一次踩坑如何变成团队记忆:智忆 Capture → Review → Recall 实战
aigc·ai编程