RAG进阶:混合稠密检索和知识图谱来提升精度
HuixiangDou 这个项目,定位是群聊场景下的 LLM 知识助手。群聊里人多口杂,机器人显然不能每条消息都回复,它的设计哲学就三条:

- ——直接拒答
无关内容不吭声
- ——启动检索
明确该答的,直接回复
- ——确保可靠
不能违反核心价值观
在上篇文章里,我们用真实的群聊数据做了一轮测试,对比了多种方法和参数调优,最终把拒答的 F1 score 干到了 75.88。
今天这篇,要聊的是如何把知识图谱和稠密检索混着用,让 F1 进一步攀升到 77.57。
先把目前测试过的所有方法做个对比:
方法 | F1 score | 备注 |
| BCE+KG混合(本文) | 77.57 | KG 权重约 20% |
| BCE | 75.88 | 需配合特定 splitter |
| BGE | 72.23 | 使用 bge-large-zh-v1.5 |
| BGE-M3 | 70.62 | 测试数据 token 不足 8192,无法评估能力 |
| M3 稠密+稀疏混合 | 63.85 | 使用 milvus hybrid_search 测试,WeightedRanker 中稀疏占比越大效果越差 |
本文方法的本质,其实就是在稠密检索的过程中,给那些高频词汇加个权重。这么做有几个好处:
- 。核心实现只有几百行代码,而且完美兼容旧版本。具体的 Pull Request 可以参考 这里。
简单
- 。反复测试下来,只要参数设置合理,精度提升是稳定的。
可靠
- 。不需要依赖多轮 LLM 调用就能提升精度,本文只做 2 轮 LLM 的 NER 来提取知识库里的实体词。
成本可控
1. 术语介绍
考虑到读者的背景可能不太一样,先把文里涉及的关键词捋一遍:
- :一种结构化的知识库,用图的形式把实体、属性、关系及类型都组织起来。
知识图谱(Knowledge Graph)
- :从自然语言里提取有意义的实体,比如人名、昵称、时间等等。
命名实体识别(Named Entity Recognition)
- :属于非结构化方法。先靠模型把文本、图像、语音的特征提取出来,再算算特征之间的距离来找匹配目标。人脸识别里用的就是这招。
稠密检索(Dense Retrieval)
- :一个 Python 写的开源图论和复杂网络分析库,能让你方便地创建和操作各种图结构。
networkx
- :成熟的图形数据库管理系统,很适合用节点和边来保存知识图谱。
neo4j
- :开源向量数据库,专门设计来存储、搜索和分析海量向量数据。
milvus
2. 方案阐述
RAG 为什么需要 KG?或者更直接点,KG 能给 HuixiangDou 带来什么?
理想状态下,KG 应该能带来三样东西:
- 。稠密检索的那个高维空间,说实话,调试起来基本靠猜。
提升系统的可解释性
- 。比如在杂交水稻这个领域,无论你用稠密还是稀疏方法,都很难清晰地表达“野败”和“南优2”之间的亲本关系。
保证术语间的层级关系
- 。KG 的加入,不能明显干扰现有的服务流程和精度表现。
无侵入性
本文用的 KG,是以属性为中心来连接 chunk 的。
举个具体的例子,拿 MMDeploy 和 MMPose 这两个项目的 README 来说,它们的交集会落在 “mmpose” 和 “ncnn” 这类术语上。如果一个名词(比如 “ncnn”)能关联到大量文档,那说明它要么很重要,要么很常见。本文的核心假设就是:这类高频词汇,在 RAG 中理应获得更大的权重。
2.1 建立知识库
这里用了 qwen1.5-110B 来做 NER。为了控制成本,调的是 silicon cloud 的 API。知识库方面,用的还是 OpenMMLab 那 9 个算法库。
建库过程大概需要 14M token,单并发跑下来要 12 个小时以上,费用差不多 50 元。
python3 -m huixiangdou.service.kg --build
建库成功后,workdir/kg 目录下会出现 jsonl 格式的节点和关系文件。
此时可以试试检索效果,比如问一下怎么安装 MMPose:
python3 -m huixiangdou.service.kg --query 如何安装mmpose?
考虑到 API 欠费、网络断开这些不可控因素,代码里会记录已完成的部分,支持断点续建。
2.2 可视化
在 HuixiangDou 里,知识图谱的存储用的是 jsonl,图计算则靠 networkx。为了白嫖 neo4j 的可视化能力,我们也支持把 jsonl 转到 neo4j 里。
python3 -m huixiangdou.service.kg --dump-neo4j --neo4j-uri ${URI} --neo4j-user ${USER} --neo4j-passwd ${PWD}
# 30 万节点和关系数据,远程通信预计耗时 4 小时
下面是部分节点可视化的例子,看起来就像蒲公英一样:
- 红色是属性节点
- 蓝色是 chunk
- 橙色是文档
- 灰色是图片
2.3 直接检索测试
检索的过程和建库类似,先用 LLM 提取实体词,然后去匹配候选文档。
关于 score 的计算,我们事先统计了所有命中个数的分布。多数问题关联不了 100 个文档。考虑到后续还要缩放分值,就简单做了个拍脑袋的处理:
score = min(100, count(docs)) / 100
这里的阈值也是候选文档个数。举个例子,如果对某条用户输入,检索到 5 个以上的候选文档,就判定为 True,机器人会继续处理这句话,不拒绝。
测试结果很直观:随着阈值提高,知识图谱的检索结果会越来越保守,很多正类样本会被错误地归到负类里去。
2.4 混合检索测试
不过话说回来,
保守也是一种可靠
这种保守的特质,很适合用来计算正值 [0, +1],然后叠加到稠密检索的结果上,让原来的分布方差变得更大。
本文用的混合检索,说白了就是“考试加分”。具体公式如下:
final_score = dense_score + 0.2 * kg_score
这样一来,在实现层面就相当于变相改了阈值,而不用动稠密检索的代码。具体流程是:
- 先算 kg_score
- 重置 query 的阈值:throttle = throttle_in_config - 0.2 * kg_score
- 继续走原有的稠密检索流程
这样一来,知识图谱就变成了一个可开关的选项,和旧版本的特征库完美兼容!
3. 总结
总的来说,这套基于知识图谱和稠密检索的混合方案,本质就是在稠密检索过程中给高频词汇加权,最终带来了不到 2 个点的精度提升。
当然,现阶段实现得还有点糙,只支持 markdown 和纯文本格式。速度方面也没做任何优化,KG-LLM 的完整能力还没发挥出来。
接下来会继续完善代码,并在更多领域里跑一轮测试。
-
- 关于宇宙的好的网名有哪些
- 角色扮演 | 1
- 网名