在线检索:事实种子与个性化 PageRank
本章讲什么:
retrieve(query)的四步——事实打分、识别记忆过滤、图上 PPR 扩散、排段落,以及"没事实就降级"的兜底。这是 HippoRAG 区别于普通向量 RAG 的核心。入口HippoRAG.retrieve(src/hipporag/HippoRAG.py:418)。
1. 先看主循环
每个问题,retrieve 只做四件事(HippoRAG.py:464-485):
① get_fact_scores 问题向量 · 所有事实向量 → 每条事实一个分
② rerank_facts 取 top-5 候选,交大模型过滤 → top_k_facts
│
len==0 ? ──是──► dense_passage_retrieval 纯向量检索兜底,直接返回
│否
③ graph_search_with_fact_entities 事实→种子权重→跑 PPR
④ _build_retrieval_result 取 PPR 排名前 num_to_retrieve 段
下面逐步拆。
2. 第①步:事实打分
思路
不是拿问题直接和"段落"比(那是普通 DPR),而是先拿问题和每一条三元组事实比相似度。为什么?因为事实是"结构化的关系陈述",和"多跳问题里隐含的关系"更容易对上。
实现
问题用两套指令分别编码(get_query_embeddings, HippoRAG.py:1398):一套 query_to_fact(对事实)、一套 query_to_passage(对段落)。指令文本见 prompts/linking.py:1:
query_to_fact: "Given a question, retrieve relevant triplet facts that matches this question."
query_to_passage: "Given a question, retrieve relevant documents that best answer the question."
打分就是点积 + min-max 归一化(get_fact_scores, HippoRAG.py:1434):
# 示意,非源码(对照 HippoRAG.py:1466-1468)
scores = fact_embeddings @ query_embedding.T # 每条事实一个相似度
scores = min_max_normalize(scores) # 压到 [0,1]
3. 第②步:识别记忆(大模型过滤事实)
要解决的小问题
向量相似 ≠ 真相关。分最高的几条事实里,往往混着"词面像、逻辑上没用"的噪声。HippoRAG 借"海马体识别记忆"的隐喻,再加一道大模型过滤:从候选里挑出真正对回答有用的(rerank_facts, HippoRAG.py:1666)。
怎么做
- 按分取 top
linking_top_k(默认 5)条候选事实(HippoRAG.py:1690-1700)。 - 交给
DSPyFilter(rerank.py:15),它用一段 DSPy 优化过的 few-shot 提示 让大模型输出"过滤后"的事实子集,最多留 4 条。 - 大模型可能改写事实文本,所以用
difflib.get_close_matches模糊匹配回原候选,还原成真实索引(rerank.py:122-125)。
提示长什么样
识别记忆的系 统提示很有意思——它用"高风险决策"话术压模型认真过滤(prompts/filter_default_prompt.py,best_dspy_prompt):
# 系统提示节选(filter_default_prompt.py)
"...select up to 4 relevant facts from the provided candidate list
that have a strong connection to the query... if no facts are relevant,
return an empty list, {\"fact\": []}. You must only use facts from the
candidate list and not generate new facts."
带的 few-shot 例子全是多跳问题(电影导演生卒、地理归属等),教模型"只留能搭起推理链的事实"。
降级:挑不到就退回向量检索
如果过滤后一条事实都不剩(len(top_k_facts)==0),retrieve 直接返回纯向量检索(DPR)结果(HippoRAG.py:472-474)。这是关键的"安全网":对于压根没有多跳结构的简单/长问题,HippoRAG 退化成普通 RAG,不会更差。这正是 README 说的"不牺牲简单任务性能"。
4. 第③步:个性化 PageRank —— 全项目的心脏
直觉
有了几条相关事实,就有了几个"入口实体"。现在要回答:从这些入口出发,图上哪些段落最该被读? 答案不是"直接相连的",而是"顺着关系扩散后,累计到的相关性最高的"。这就是 个性化 PageRank(PPR):普通 PageRank 均匀随机游走,PPR 则让游走总是以一定概率跳回你指定的"种子"节点,于是分数集中在种子附近及其多跳邻域。
三件事:给谁赋权、怎么扩散、读哪段
graph_search_with_fact_entities(HippoRAG.py:1551)干这三件事:
(a) 实体种子权重。 遍历过滤后的每条事实,把它的主语、宾语实体在图上找到,累加"事实分"作权重。巧妙处:权重要除以"该实体出现在多少个块里"(HippoRAG.py:1607-1608)——出现太广的实体(如"美国")被降权,罕见实体被抬高,这是一个 IDF 式的抑制,避免热门实体主导扩散。
(b) 段落权重。 同时跑一遍向量检索(DPR),给每个段落节点一个分,乘以一个小系数 passage_node_weight=0.05 也加进种子(HippoRAG.py:1636-1642)。意思是:图联想为主(权重大),直接向量相似为辅(权重小),两路信号融合。
(c) 跑 PPR。 把实体权重 + 段落权重合成一个"reset 概率"向量,喂给 igraph 的个性化 PageRank(run_ppr, HippoRAG.py:1716):
# 真实调用(HippoRAG.py:1743-1750)
pagerank_scores = self.graph.personalized_pagerank(
vertices=range(len(self.node_name_to_vertex_idx)),
damping=0.5, # 阻尼:0.5 概率继续游走,0.5 跳回种子
directed=False,
weights='weight', # 边权参与游走概率
reset=reset_prob, # 个性化:种子分布
implementation='prpack',
)
扩散完,只取段落节点的分数、降序排列,就是最终该读的段落顺序(HippoRAG.py:1752-1754)。
一图看懂扩散
种子(事实里的实体) PPR 扩散 落到段落
[Leland Stanford]* ──事实边──► [Jane Stanford] ──段落边──► [段落: Jane...]
│* ▲
段落边 │ 分数高
▼ 被扩散点亮
[段落: Stanford 由...](DPR 也给了它一点分)
* = 有种子权重的节点;damping=0.5 让权重从种子向邻域蔓延两三跳
这就是多跳的实现:问题只和第一个实体相关,但 PPR 让相关性顺着"事实边→段落边"走到了第二篇文档。普通向量检索永远做不到这一步。
5. 第④步:出段落 → 问答
_build_retrieval_result(HippoRAG.py:506)按 PPR 排名取前 num_to_retrieve(默认 retrieval_top_k=200)段。
问答阶段 qa(HippoRAG.py:813)只把前 qa_top_k(默认 5)段 拼进提示喂大模型,让它按 Thought: ... Answer: ... 格式作答,再从 Answer: 后面切出预测答案(HippoRAG.py:862)。
6. 进阶:IRCoT 多步检索
单轮 PPR 已能覆盖多数多跳,但更难的题需要"边检索边推理"。retrieve_ircot(HippoRAG.py:514)实现 IRCoT:
检索一轮 → 让大模型基于当前段落想一步(reason_step) → 把"想法"当新 query 再检索
→ 合并两轮段落分数(取 max) → 直到大模型说 "So the answer is:" 或到步数上限
每步的"想一步"由 reason_step(utils/qa_utils.py:31)用数据集专属的 ircot_* 提示模板生成。多轮结果按段落分数取最大值合并(HippoRAG.py:544-547)。默认 max_qa_steps=1,即关闭 IRCoT,走单轮。
7. 本章小结
在线检索的独特价值,全在"事实当种子 + PPR 扩散"这一招:
- 事实打分 + 识别记忆:先在结构化事实层面对上问题,再用大模型去噪,得到高质量种子。
- PPR 扩散:种子权重顺着图边蔓延,把"词面不相似但关系相关"的段落点亮——这是多跳能力的来源。
- 双路融合 + 降级:图信号为主、DPR 为辅;一旦没有可用事实就退回纯 DPR,守住简单任务的下限。
下一章深入种子权重的数学细节、识别记忆的实现取舍、边界,以及横向对比。