HippoRAG:让 AI 像人类一样 记忆 和 回忆
HippoRAG:让 AI 像人类一样"记忆"和"回忆"
先问个问题
假设你有一个包含 1000 篇 Wikipedia 文章的知识库,我问你:
"Thomas Sudhof 和 Karl Deisseroth 有什么共同点?"
你会怎么找?
传统 RAG 的做法:
问题 → 向量检索 → 找到最相似的 5 篇文章 → 答案问题:如果答案散落在多篇文章里,或者需要推理连接,传统方法就失效了。
HippoRAG 的做法:
问题 → 找到关键实体 → 在知识图谱上"游走" → 连接多篇文章 → 答案这更像人类的思考过程:先提取关键人名,再回忆他们各自的信息,最后找出联系。
核心想法:模仿人类记忆
HippoRAG 的假设是:人类记忆不是一张大表格,而是一张网。
你听到 "George Rankin"
↓
大脑激活相关节点: politician, government, elected
↓
沿着记忆网络扩散
↓
想起更多相关信息HippoRAG 用知识图谱 + 向量检索模拟这个过程。
Part 1:把文档"存"进大脑 (Index)
一个例子
假设你有一段话:
"Erik Hort's birthplace is Montebello. Montebello is a part of Rockland County."HippoRAG 会把它拆解成:
1. 拆成 chunk(语义完整的段落)
2. 提取三元组(谁什么关系谁)
- ("Erik Hort", "birthplace", "Montebello")
- ("Montebello", "part of", "Rockland County")
3. 构建图谱
Erik Hort ──birthplace──→ Montebello ──part of──→ Rockland County
4. 给每个节点打上向量标签(方便后续查找)完整流程
原始文档
│
├─→ Chunk 1: "Erik Hort's birthplace..."
│ │
│ ├─→ 提取三元组 → ("Erik Hort", "birthplace", "Montebello")
│ │
│ └─→ 存入 Chunk Embedding Store (向量)
│
├─→ Chunk 2: "Montebello is a part of..."
│ │
│ ├─→ 提取三元组 → ("Montebello", "part of", "Rockland County")
│ │
│ └─→ 存入 Chunk Embedding Store (向量)
│
└─→ 构建知识图谱
│
├─→ 实体节点: Erik Hort, Montebello, Rockland County
├─→ 文档节点: Chunk 1, Chunk 2
├─→ 事实边: birthplace, part of
└─→ 同义边: politician ↔ political figure (通过向量相似度发现)为什么需要同义边?
因为同一个概念可能有不同说法:
用户问: "What political figures..."
知识图谱里有: "politician"
没有同义边 → 找不到
有同义边 → political figure → politician → 找到了!Index 步骤总结
Part 2:从大脑中"回忆" (Retrieve)
一个完整的检索过程
假设问题是:
"What county is Erik Hort's birthplace in?"
Step 1: 问题向量化
"What county is Erik Hort's birthplace in?"
↓
用 instruction 引导编码
"Given a question, retrieve relevant triplet facts..."
↓
得到一个向量,专门用来匹配三元组为什么需要 instruction?
同一个问题,不同用途需要不同向量:
# 用于匹配事实
query_to_fact_vector = encode(query, "retrieve relevant triplet facts...")
# 用于匹配文档
query_to_passage_vector = encode(query, "retrieve relevant documents...")
# 两个向量方向不同,分别优化用于匹配不同目标Step 2: 向量检索找到候选事实
问题向量 · 所有事实向量 = 相似度分数
("Erik Hort", "birthplace", "Montebello") → 0.87 ✓
("Montebello", "part of", "Rockland County") → 0.65 ✓
("Cinderella", "attended", "ball") → 0.12 ✗
("George Rankin", "is a", "politician") → 0.21 ✗初筛可能返回 50-100 个候选事实。
Step 3: LLM 精炼 (Recognition Memory)
向量检索可能有噪声,让 LLM 再筛一遍:
输入:
问题: "What county is Erik Hort's birthplace in?"
候选事实: [初筛的 50 个三元组]
输出:
精选事实: [最相关的 5 个三元组]得到:
("Erik Hort", "birthplace", "Montebello")
("Montebello", "part of", "Rockland County")这一步叫 Recognition Memory(识别记忆),模拟人类从多个记忆片段中识别出最相关的。
Step 4: 在图上"游走" (PPR)
这是 HippoRAG 的魔法时刻 —— Personalized PageRank 算法结合静态图结构和动态查询相关性,给所有节点打分。
PPR 的核心:两种权重的协作
┌─────────────────────────────────────────────────────────────────┐
│ 静态图 + 动态查询 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 静态图 (Index 阶段构建,可复用): │
│ ┌────────────────────────────────────────┐ │
│ │ 节点 + 边,每条边有固定权重 │ │
│ │ │ │
│ │ Erik ──w=1.0──→ Montebello ──w=1.0──→ Rockland │
│ │ │ │ │ │
│ │ w=1.0 w=1.0 w=1.0 │
│ │ ↓ ↓ ↓ │
│ │ Chunk1 Chunk2 Chunk3 │
│ └────────────────────────────────────────┘ │
│ │
│ 动态查询 (Retrieve 阶段): │
│ ┌────────────────────────────────────────┐ │
│ │ 查询与每条 fact 边的相关度: │ │
│ │ (Erik, birthplace, Montebello) → 0.87 │ │
│ │ (Montebello, part_of, Rockland) → 0.65│ │
│ │ │ │
│ │ 这些相关度 → 节点初始权重 │ │
│ └────────────────────────────────────────┘ │
│ │
│ PPR 结合两者遍历图: │
│ 初始权重 + 边权重 → 传播 → 所有节点分数 │
│ │
└─────────────────────────────────────────────────────────────────┘完整流程
1. 准备静态图结构
Index 阶段已构建好的知识图谱:
fact edge (w=1.0) fact edge (w=1.0)
Erik ─────────────────→ Montebello ─────────────→ Rockland
│ │ │
passage edge passage edge passage edge
(w=1.0) (w=1.0) (w=1.0)
│ │ │
↓ ↓ ↓
Chunk1 Chunk2 Chunk3
边权重含义:
- fact edge: 三元组出现的次数 (语义关系的强度)
- passage edge: 固定 1.0 (包含关系)2. 计算查询相关度
查询: "Erik Hort 的出生地在哪个县?"
向量化 → 匹配 fact_embedding_store:
Fact 1: ("Erik Hort", "birthplace", "Montebello")
向量相似度 = 0.87
→ 这是查询与这条 fact 边的相关度
Fact 2: ("Montebello", "part of", "Rockland County")
向量相似度 = 0.65
→ 这是查询与这条 fact 边的相关度
注意: 这些分数表示"这条边与查询有多相关"3. 转换为节点初始权重
将 fact 边的相关度转换为节点的初始权重:
Fact 1 相关度 0.87:
→ Erik Hort 权重 += 0.87
→ Montebello 权重 += 0.87
Fact 2 相关度 0.65:
→ Montebello 权重 += 0.65
→ Rockland County 权重 += 0.65
初始权重向量 (reset):
Erik Hort: 0.87
Montebello: 1.52 (0.87 + 0.65)
Rockland County: 0.65
George Rankin: 0.00 (无关)
图上状态:
Erik (0.87) ───────→ Montebello (1.52) ───────→ Rockland (0.65)
│ │ │
↓ ↓ ↓
Chunk1 (0) Chunk2 (0) Chunk3 (0)4. PPR 迭代传播
迭代公式:
p_{t+1} = (1 - α) × reset + α × p_t × M
其中:
p_t = 当前所有节点的权重
reset = 初始权重 (固定不变,来自 fact 相关度)
α = damping 系数 (0.5,表示 50% 传播,50% 重置)
M = 转移矩阵 (包含边权重)
关键理解:
- reset: 表示"查询从哪里开始" (动态,每次查询不同)
- M: 表示"节点间的连接强度" (静态,图结构)
- PPR: 结合两者,让权重沿图边流动迭代过程:
初始 (t=0): 从相关实体开始
Erik: 0.87, Monte: 1.52, Rockland: 0.65
Chunks: 都是 0
迭代 1: 权重沿边传播
Erik (0.87) ──w=1.0──→ Montebello: 新增 0.87 × 1.0 = 0.87
Monte (1.52) ──w=1.0──→ Erik: 新增 1.52 × 1.0 = 1.52
Monte (1.52) ──w=1.0──→ Rockland: 新增 1.52 × 1.0 = 1.52
各实体 ──w=1.0──→ 对应 Chunk: passage edge 传播
应用 damping 和 reset:
新权重 = 0.5 × reset + 0.5 × 传播后权重
Erik: 0.5×0.87 + 0.5×传播权重 ≈ 1.20
Monte: 0.5×1.52 + 0.5×传播权重 ≈ 2.10
Rockland: 0.5×0.65 + 0.5×传播权重 ≈ 1.09
Chunk1: 0 + 0.5×Erik的传播 ≈ 0.6
Chunk2: 0 + 0.5×Montebello的传播 ≈ 1.0
迭代 2-10: 继续传播,权重重新分配...
收敛 (t=n):
Erik: 1.5
Monte: 2.3
Rockland: 1.8
Chunk1: 2.1 (包含 Erik)
Chunk2: 2.5 (包含 Montebello,最高分!)
Chunk3: 1.3 (包含 Rockland)关键概念澄清
Q1: Fact 相似度是边权重吗?
不是!它们是两个不同概念:
边权重 (静态):
- 在 Index 阶段确定
- 表示"节点间连接的强度"
- 由三元组出现次数决定
- 存储在图中,每次查询复用
Fact 相似度 (动态):
- 在 Retrieve 阶段计算
- 表示"这条边与查询的相关度"
- 由向量相似度决定
- 转换为节点初始权重
为什么分开?
- 边权重固定 → 图可以复用
- Fact 相似度动态 → 每次查询不同Q2: 为什么不直接用 Chunk 包含的实体分数累加?
直接累加: Chunk 分数 = Σ(包含实体分数)
→ 假设实体相互独立
→ 丢失实体间的关系
PPR 传播: Chunk 分数 = 图传播后的分数
→ 利用图结构
→ 实体相互影响
→ 支持多跳推理
示例:
查询: "Thomas 和 Karl 的共同点?"
直接累加:
Chunk A (包含 Thomas) ≠ Chunk B (包含 Karl)
→ 找不到关系 ✗
PPR:
Thomas → Stanford ← Karl
→ 通过共同连接找到相关 Chunk ✓Q3: PPR 如何做多跳推理?
传统检索 (向量匹配):
Query → 向量相似度 → 直接匹配的文档
→ 找不到两跳之外的信息
PPR (图传播):
Query → fact 相关度 → 初始权重
→ 沿图边传播
→ Erik → Montebello → Rockland
→ Rockland 获得权重 (两跳!)
→ 包含 Rockland 的 Chunk 获得分数
→ 找到了两跳之外的文档 ✓
本质: 权重可以沿着图边"流"到多跳之外的节点Q4: 为什么会收敛?
1. Damping: 每次迭代 50% 权重重置
→ 不会无限累积
2. 有限图: 节点数固定
→ 权重分配空间有限
3. 数学保证: PPR = 计算矩阵特征向量
→ 必然收敛
实际停止:
- 变化 < 0.000001
- 或达到最大迭代 (100-500次)最终输出
文档节点排序:
Chunk 2 "Montebello is part of Rockland County" → 2.5
Chunk 1 "Erik Hort's birthplace is Montebello" → 2.1
Chunk 3 "Rockland County is in New York" → 1.3
返回 top-k 文档给 LLM 生成最终答案Step 5: 返回排序后的文档
1. "Montebello is a part of Rockland County" (score: 0.92)
2. "Erik Hort's birthplace is Montebello" (score: 0.88)
3. "Rockland County is in New York" (score: 0.45)
4. "George Rankin is a politician" (score: 0.12)完整召回链路
问题
│
↓ 向量化 (query_to_fact)
问题向量
│
↓ 匹配 fact_embedding_store
候选事实 (50-100个)
│
↓ LLM 重排序
关键事实 (5-10个)
│
↓ 提取实体
实体节点 (带权重)
│
↓ 图上 PPR 传播
文档排序
│
↓
最终答案争议:这么复杂值得吗?
支持方
| 优势 | 说明 |
|---|---|
| 多跳推理 | Erik Hort → Montebello → Rockland County(两跳) |
| 语义连接 | 通过同义边连接不同表述 (politician ↔ political figure) |
| 结构化知识 | 图比纯向量更能表达关系 |
| 测试表现 | 在 MuSiQue、HotpotQA 等多跳问答基准上领先 |
反对方
| 质疑 | 说明 |
|---|---|
| 复杂度高 | 6-7 个步骤,每个都可能出错 |
| 依赖 LLM | OpenIE 和重排序都要调用 LLM,慢且贵 |
| 收益有限 | 简单问答和普通 RAG 差距不大 |
| 过度设计 | 很多场景下 DPR 就够用 |
我的看法
HippoRAG 的价值取决于场景:
简单问答: "George Rankin 的职业是什么?"
→ 普通 RAG 够用,快且便宜
多跳推理: "Erik Hort 出生地属于哪个县?"
→ HippoRAG 有优势,能跨文档推理
实时系统: 需要 <100ms 响应
→ HippoRAG 太复杂,可能不适合
离线分析: 可以容忍几秒延迟
→ HippoRAG 可以考虑兜底机制
HippoRAG 作者也意识到复杂性的问题,代码中有兜底:
# 如果 LLM 重排序后没有相关事实,退回到简单 DPR
if len(top_k_facts) == 0:
sorted_doc_ids = self.dense_passage_retrieval(query)这说明:复杂的图检索是锦上添花,不是必需品。
本质:还是表格匹配?
本质上就是维护了一个表格,通过几个字段进行映射搜索匹配
HippoRAG 本质上就是:
chunk_table
┌─────────────┬──────────┬────────────────┐
│ chunk_hash │ chunk │ chunk_embedding │
├─────────────┼──────────┼────────────────┤
│ hash_001 │ "Erik..."│ [0.1, 0.2...] │
└─────────────┴──────────┴────────────────┘
entity_table
┌─────────────┬─────────┬──────────────────┐
│ entity_hash │ entity │ entity_embedding │
├─────────────┼─────────┼──────────────────┤
│ hash_e001 │ "Erik.."│ [0.3, 0.1...] │
└─────────────┴─────────┴──────────────────┘
fact_table
┌─────────────┬───────────────────┬────────────────┐
│ fact_hash │ fact (三元组) │ fact_embedding │
├─────────────┼───────────────────┼────────────────┤
│ hash_f001 │ (Erik, birthplace │ [0.5, 0.8...] │
│ │ , Montebello) │ │
└─────────────┴───────────────────┴────────────────┘
graph
┌─────────┬─────────┬────────┐
│ node_1 │ node_2 │ weight │
├─────────┼─────────┼────────┤
│ Erik │Montebello│ 1.0 │
└─────────┴─────────┴────────┘但关键在于:
- 图结构 让这些表之间可以"游走"
- PPR 算法 让相关性可以传播
- 同义边 让相似实体可以互通
所以不只是简单的表连接,而是带权重的图推理。
总结:你应该用 HippoRAG 吗?
适合用的场景
- ✓ 需要多跳推理的问答系统
- ✓ 知识密集型领域(医疗、法律、科研)
- ✓ 对准确度要求高于延迟
- ✓ 有预算调用 LLM
- ✓ 问题类型复杂(需要连接多个信息点)
不适合用的场景
- ✗ 简单的文档问答
- ✗ 实时响应要求高
- ✗ 预算有限
- ✗ 问题类型单一
- ✗ 数据规模小
建议
- 先试简单方案: 从普通 RAG/DPR 开始
- 评估瓶颈: 如果发现多跳推理是瓶颈,再考虑 HippoRAG
- A/B 测试: 在你的数据上对比 HippoRAG 和基线方法
- 成本分析: 计算 LLM 调用成本是否值得提升的准确度
写在最后
HippoRAG 代表了一个方向:让 AI 更像人类思考。它把向量检索的"直觉"和知识图谱的"推理"结合起来,虽然复杂,但确实在多跳问答上展示了潜力。
至于是否值得在你的项目中使用?取决于你的问题有多复杂,以及你愿意为这多出来的准确性付出多少代价。
但无论如何,HippoRAG 提供了一个很好的思路:不要只盯着向量相似度,有时候结构和关系更重要。
评论