4.1 检索器目标:召回最相关、最能回答问题的知识片段
在检索增强生成(RAG)系统中,检索器的核心目标是高效且准确地从庞大的知识库中召回与用户查询最相关的知识片段。这些片段应当能够直接回答用户的问题,或者为后续的生成步骤提供必要的上下文信息。检索器的性能直接影响着整个 RAG 系统的效果,一个优秀的检索器能够显著提升生成答案的准确性、相关性和信息丰富度。
4.2 向量检索策略:
向量检索是一种基于语义相似度的检索方法,它将文本数据(例如文档、段落)和用户查询都编码成低维向量空间中的向量,然后通过计算向量之间的距离(例如余弦相似度)来找到语义上最相关的文本片段。
4.2.1 稠密检索(Dense Retrieval):基于 Embedding 的语义搜索实现
- 原理: 稠密检索的核心在于使用深度学习模型(例如 Transformer 模型,如 Sentence-BERT、BERT、或 OpenAI Embeddings)将文本编码成稠密的向量表示(embeddings)。这些 embeddings 旨在捕捉文本的语义信息,使得在向量空间中,语义上相似的文本片段的向量距离更近。
- 实现: 实现稠密检索通常包括以下步骤:
- 构建索引: 预先将知识库中的所有文本数据通过 Embedding 模型编码成向量,并构建高效的向量索引(例如使用 FAISS、Annoy、Pinecone、Milvus、Chroma 等库)。选择合适的索引结构(如 Flat、IVF、HNSW)需要在速度、准确性和内存消耗之间进行权衡。
- 查询编码: 当接收到用户查询时,使用相同的 Embedding 模型将查询编码成向量。
- 相似度搜索: 在向量索引中搜索与查询向量最相似的 Top-K 个文本向量。现代向量数据库通常采用近似最近邻搜索(ANN)算法来加速搜索过程。
- 优点: 能够捕捉文本的深层语义信息,即使查询和文档在字面上不匹配,也能找到语义相关的结果。对于处理同义词、释义和理解上下文意图非常有效。
- 缺点: 对于精确匹配和处理稀疏词汇可能不如稀疏检索。性能高度依赖于所使用的 Embedding 模型的质量。
4.2.2 稀疏检索(Sparse Retrieval):BM25/TF-IDF 原理与应用
- 原理: 稀疏检索方法(如 BM25 和 TF-IDF)基于词频统计,将文本表示成高维的稀疏向量。向量的每个维度对应一个词语,而维度上的值表示该词语在文本中的重要性。
- TF-IDF (Term Frequency-Inverse Document Frequency): TF 表示词语在文档中出现的频率,IDF 表示词语在整个文档集合中的稀有程度。TF-IDF 越高,表示词语对该文档越重要。
- BM25 (Best Matching 25): 是一种在 TF-IDF 基础上改进的排序函数,考虑了文档长度等因素,通常在信息检索领域表现更好。BM25 及其变体仍然是许多实际应用中的强大基线模型。
- 实现: 实现稀疏检索通常包括以下步骤:
- 构建倒排索引: 对知识库中的文本数据进行分词,并构建倒排索引,记录每个词语出现在哪些文档中以及出现的频率。
- 查询处理: 对用户查询进行分词。
- 计算相关性得分: 使用 BM25 或 TF-IDF 等算法计算查询中的词语与文档之间的相关性得分。
- 排序: 根据相关性得分对文档进行排序,返回得分最高的 Top-K 个文档。
- 优点: 对于精确匹配和处理包含特定关键词的查询非常有效,计算成本相对较低。在处理长尾查询和文档集合中词语分布不均匀的情况下表现良好。
- 缺点: 无法很好地捕捉文本的语义信息,对于同义词或语义相关的查询效果可能不佳。对词序和上下文的理解能力有限。
4.2.3 混合检索(Hybrid Search):结合两者的优势与实现
- 原理: 混合检索通过结合稠密检索和稀疏检索的优势,通常能够获得更好的检索效果。稠密检索擅长语义匹配,而稀疏检索擅长关键词匹配。研究表明,混合检索往往能够显著提高检索系统的整体性能。
- 实现: 实现混合检索的常见方法包括:
- 并行检索: 同时使用稠密检索和稀疏检索方法独立地检索结果。
- 结果融合: 将两种检索方法返回的结果进行融合。融合的方法可以包括:
- 加权平均: 为两种检索方法的结果分配不同的权重,然后根据加权后的得分进行排序。权重的分配可以基于经验、交叉验证或更复杂的学习方法。
- 交叉排序(Cross-ranking): 将两种方法返回的结果进行交叉排序,例如,按照某种规则交替选择结果。
- 使用一种方法的结果来过滤或增强另一种方法的结果。例如,可以使用稀疏检索的结果作为候选集,然后使用稠密检索进行更精确的排序。
- 多阶段检索: 可以设计多阶段的检索流程,例如,先使用一种方法进行粗略召回,再使用另一种方法进行精细筛选。
- 优点: 能够兼顾语义相关性和关键词匹配,通常在各种类型的查询上都能取得较好的性能,鲁棒性更强。
- 缺点: 相较于单一检索方法,实现和调优可能更复杂。
4.3 图谱检索核心:Graph RAG 详解
图谱检索,也称为基于知识图谱的检索,利用知识图谱中实体和关系之间的结构化信息来检索相关的上下文。与向量检索侧重于文本的语义相似性不同,图谱检索侧重于实体之间的关联和路径,能够进行更深层次的推理和关系探索。
4.3.1 原理:利用图结构关系进行上下文推理和检索
知识图谱由节点(代表实体)和边(代表实体之间的关系)组成。图谱检索的核心在于利用这些结构化的关系来找到与用户查询相关的实体和它们之间的连接。这种方法能够进行更深层次的推理和关系探索,从而召回更精确和更具上下文的知识片段,尤其适用于需要理解实体间复杂关系的查询。
4.3.2 实现方法 1:文本到实体链接 + 图遍历/路径查找
- 实体链接的准确性与歧义消解: 首先需要将用户查询中的关键词链接到知识图谱中对应的实体。这是一个关键步骤,实体链接的准确性直接影响后续的检索效果。实体链接面临的挑战包括同名实体歧义、指代消解、以及处理图谱中不存在的实体等。常用的实体链接工具包括 spaCy、Stanford CoreNLP、DBpedia Spotlight 等。
- 图遍历策略:广度优先搜索(BFS)、深度优先搜索(DFS)等: 一旦确定了查询相关的起始实体,就可以在知识图谱上进行遍历,查找与之相关的其他实体和关系。常用的遍历策略包括:
- 广度优先搜索(BFS): 从起始实体开始,逐层探索其邻居节点,直到达到指定的深度或找到目标实体。适用于查找直接关联或较短路径的关系。
- 深度优先搜索(DFS): 从起始实体开始,沿着一条路径尽可能深地探索,直到达到指定的深度或找到目标实体,然后回溯到上一个节点继续探索其他路径。
- 其他策略: 还包括基于路径长度、节点重要性(如 PageRank)或特定关系类型的遍历策略。
- 返回相关实体的邻居节点和关系: 最简单的图遍历方法是找到与查询实体直接相连的邻居节点和连接它们的边,这些邻居和关系可以作为 RAG 系统的上下文信息。对于更复杂的查询,可能需要返回包含多个实体和关系的子图。
- 示例: 假设用户查询是“与《盗梦空间》的导演合作过的演员有哪些?”,首先将“盗梦空间”链接到知识图谱中的电影实体,“导演”链接到关系,“演员”链接到角色。然后,通过图遍历,可以先找到《盗梦空间》的导演(例如克里斯托弗·诺兰),再找到与该导演合作过的演员。这可能涉及到多跳查询,例如
MATCH (m:Movie {title: "盗梦空间"})<-[:DIRECTED]-(d:Person)-[:ACTED_IN]->(a:Movie) WHERE a <> m RETURN DISTINCT a.title
(Cypher 示例,实际查询会更复杂,可能需要考虑关系的类型和方向)。
4.3.3 实现方法 2:LLM 生成结构化查询(Cypher/SPARQL)直接检索精确事实或子图
- 提示工程在生成准确结构化查询中的作用与挑战: 利用大型语言模型(LLM)的强大文本理解和生成能力,可以将自然语言的用户查询转化为知识图谱的查询语言(如 Neo4j 使用的 Cypher,或者 RDF 图谱使用的 SPARQL)。提示工程在指导 LLM 生成符合语法和语义的正确查询方面至关重要。挑战在于如何设计有效的提示,处理复杂的查询意图,并确保生成的查询能够准确地表达用户的需求,避免生成错误的或无法执行的查询。可以采用 few-shot learning 或 instruction tuning 等技术来提高 LLM 生成结构化查询的能力。
- 不同场景下的查询设计示例(知识问答、推荐等):
- 知识问答: 例如,对于查询“谁创立了苹果公司?”,LLM 可以生成 Cypher 查询
MATCH (p:Person)-[:FOUNDED]->(c:Company {name: "苹果公司"}) RETURN p.name
。这个查询匹配了名为“苹果公司”的公司实体,然后查找了通过FOUNDED
关系指向该公司的“人”实体,并返回这个人的姓名。 - 推荐: 例如,对于查询“喜欢科幻电影的用户还可能喜欢什么类型的书籍?”,LLM 可以生成 SPARQL 查询,首先找到喜欢科幻电影的用户,然后找到他们喜欢的书籍类型。这可能涉及到多个
SELECT
和WHERE
子句来表达复杂的条件和关系。
- 知识问答: 例如,对于查询“谁创立了苹果公司?”,LLM 可以生成 Cypher 查询
- 直接检索精确事实或子图: 通过生成精确的结构化查询,可以直接从知识图谱中检索到用户需要的特定事实或相关的子图结构,作为 RAG 的上下文。这种方法可以实现高度精确的检索,尤其适用于需要特定关系或属性的查询。
- 示例: 假设用户查询是“2020年发布了哪些由日本动画制作的科幻电影?”,LLM 可能需要生成如下的 Cypher 查询(简化示例):
这个查询首先匹配了 2020 年发布的科幻电影,然后进一步匹配了由日本动画工作室制作的这些电影,并最终返回它们的标题。更复杂的场景可能需要 LLM 理解用户意图并进行多轮交互或更精细的提示工程,例如使用 few-shot prompting 提供一些示例查询及其对应的结构化查询,以提高生成准确性。MATCH (m:Movie) WHERE m.releaseYear = 2020 AND m.genre = "科幻" MATCH (m)<-[:PRODUCED_BY]-(studio:Studio) WHERE studio.country = "日本" AND studio.type = "动画" RETURN m.title
4.3.4 实现方法 3:图 Embedding 与图上的相似性搜索(Node Embeddings, Subgraph Embeddings)
- 常见的图嵌入算法:Node2Vec、GraphSAGE 等及其原理: 图嵌入技术旨在将图结构中的节点和边映射到低维向量空间中,使得在向量空间中,结构上或语义上相似的节点或子图的向量距离更近。常见的算法包括:
- Node2Vec: 通过在图上进行随机游走,然后使用 Word2Vec 等词嵌入技术学习节点的向量表示。能够捕捉节点在图中的局部邻居结构。
- GraphSAGE: 是一种归纳式学习框架,可以为未见过的节点生成嵌入,它通过聚合节点的邻居信息来学习嵌入。适用于动态图和大规模图。
- 其他算法: 还包括 GCN (Graph Convolutional Networks)、GAT (Graph Attention Networks) 等。
- 相似度度量方法:例如余弦相似度: 在获得图嵌入后,可以使用向量相似度度量方法(如余弦相似度、点积等)来衡量节点或子图之间的相似性。选择合适的相似度度量方法取决于具体的应用场景和嵌入算法。
- 基于节点嵌入和子图嵌入的相似性搜索: 可以将用户查询(通常也编码成向量)与知识图谱中节点或子图的嵌入进行比较,找到最相似的节点或子图作为 RAG 的上下文。对于查询的嵌入,可以基于查询中实体的嵌入进行聚合或使用专门的查询嵌入模型。
- 实际应用案例:
- 节点嵌入: 在生物医药领域,假设用户查询提到了一个拼写略有偏差的罕见疾病名称。实体链接可能无法直接找到精确匹配的实体。此时,可以利用生物医药知识图谱(如 DrugBank 或 UMLS)中疾病节点的嵌入向量,计算与查询文本的嵌入向量的相似度,找到语义上最相关的疾病实体,从而作为后续检索的起点,例如找到该疾病相关的治疗方法或基因。
- 子图嵌入: 在金融领域,假设用户查询描述了一种复杂的洗钱模式,涉及到多个账户和不同类型的交易。可以将这种模式编码成子图嵌入,然后在金融交易知识图谱中搜索相似的子图模式,从而识别潜在的可疑活动。这种方法可以捕捉到实体之间的结构化关系,而不仅仅是单个实体的相似性。
4.3.5 Graph RAG 的流程设计与挑战
- 典型流程: 用户查询 -> 实体链接 -> 图查询生成/图遍历/图嵌入相似性搜索 -> 获取相关子图/节点/嵌入 -> 结合 LLM 生成答案。
- 挑战:
- 知识图谱的质量问题: 不完整、错误或过时的知识图谱会严重影响检索效果。可能的解决方案包括:自动化知识图谱补全技术(例如,基于图嵌入的链接预测)、建立数据验证和质量控制流程、以及允许用户反馈和修正知识。同时,知识图谱的构建和维护成本也较高。
- 有效地将图结构信息融入 LLM 的输入: 直接将复杂的子图输入 LLM 可能会超出其处理能力。可以考虑将子图转化为自然语言描述(例如,通过遍历子图生成文本),或者使用专门为图数据设计的神经网络结构(例如图神经网络 GNNs)来处理图信息,并将其与文本信息融合。研究人员也在探索如何设计更有效的提示和输入格式,以便 LLM 能够更好地理解和利用图结构信息。
- 处理复杂或多跳查询的效率: 对于大型知识图谱和复杂的多跳查询,图遍历的效率可能会成为瓶颈。可以采用图索引技术(例如,建立基于属性或关系的索引)、查询优化策略(例如,限制遍历深度、使用启发式搜索)来提高查询效率。对于 LLM 生成结构化查询的方法,查询的复杂性也可能影响生成和执行的效率。
- 知识图谱的 Schema 设计: 一个清晰、一致且能充分表达领域知识的 Schema 是至关重要的。Schema 的设计通常需要领域专家参与,并且可能需要根据实际应用进行迭代和调整。Schema 的灵活性和可扩展性也是需要考虑的因素。此外,如何确保图谱中的概念和连接与用户的查询意图相关,避免引入无关信息,也是一个重要的挑战 (DataStax Blog)。
- Graph RAG 的部署复杂性: 相较于传统的 RAG,Graph RAG 可能需要引入新的、专门的软件工具和基础设施,增加了部署的复杂性 (DataStax Blog)。
- 图谱连接与向量搜索的互补性: 如何确保图谱连接能够真正补充而不是替代向量搜索的效果,避免出现冗余或不一致的检索结果,需要仔细设计和测试 (DataStax Blog)。
4.4 对比与融合:向量 RAG vs. Graph RAG
4.4.1 优劣势分析:
- 向量 RAG:
- 优势: 擅长处理隐含的语义关系,对于开放域和长文本的检索效果较好,构建和维护成本相对较低,易于扩展到大规模数据集。
- 劣势: 难以处理需要精确事实和复杂关系推理的查询,可解释性较差,对知识的结构性理解不足。
- Graph RAG:
- 优势: 擅长处理显式的关系和结构化信息,能够进行精确的事实检索和多步推理,可解释性强,能够提供更结构化的上下文。
- 劣势: 构建和维护成本较高,对于处理隐含语义和泛化到图谱中不存在的关系可能较弱,对知识图谱的质量依赖性强。
- 具体案例分析: 可以举例说明在不同类型的任务中,两种方法的表现差异。例如,对于“介绍一下最新的iPhone的功能和用户评价”,向量 RAG 可能更擅长;而对于“找到与A疾病相关的基因,并且这些基因参与了B通路,同时受到C药物的影响”,Graph RAG 可能更有效。
4.4.2 适用场景:何时优先选择 Graph RAG?
- 需要进行复杂关系推理的场景(例如,供应链分析、药物相互作用、金融风险评估)。
- 需要精确的事实性答案的场景,尤其当事实以结构化形式存储在知识图谱中时。
- 知识结构相对稳定且明确的领域(例如,科学、医学、金融)。
- 需要答案具有较高可解释性和可追溯性的场景。
4.4.3 融合策略:如何结合向量检索和图谱检索的结果?
研究表明,结合向量检索和图谱检索的混合方法通常能够取得最佳的检索效果 (Hybrid RAG)。
- 示例:先向量召回,再用图谱丰富上下文: 可以先使用向量检索召回相关的文档或段落,然后从这些文本中提取实体,再利用这些实体在知识图谱中进行查询,获取更结构化的信息来增强原始的上下文,提供更全面的信息给生成模型。
- 并行检索,然后对结果进行加权或排序: 同时进行向量检索和图谱检索,然后根据某种策略(例如,基于置信度、相关性得分、或学习到的权重)对两者的结果进行加权或排序,最终选择 Top-K 个结果作为 RAG 的上下文。
- 使用向量检索结果指导图谱检索(实体提取用于图查询): 可以先使用向量检索找到与查询相关的文本,然后利用实体识别技术从这些文本中提取关键实体,再使用这些实体作为图谱检索的起点,构建更精确的图查询。
- 使用图谱检索结果增强向量检索(利用关系信息改进向量表示): 可以利用知识图谱中的关系信息来改进文本的向量表示,例如,通过图神经网络学习到的节点嵌入可以作为文档或段落嵌入的补充信息。
4.5 检索效果增强策略:
4.5.1 查询变换:
-
HyDE (Hypothetical Document Embedding): 这种方法首先使用 LLM 基于用户查询生成一个假设性的相关文档,然后将这个假设文档编码成向量,作为检索的查询向量,通常能够比直接使用原始查询的向量获得更好的检索效果,尤其是在处理语义模糊的查询时。同样,这个思路也可以应用于图检索,例如,生成假设的子图结构或查询语句。
-
Multi-Query: 对于一个用户查询,可以生成多个不同的、但语义上相关的查询(例如,通过释义、添加上下文等),然后使用这些查询分别进行检索,并将所有检索结果合并起来。这种方法可以提高检索的覆盖率和多样性,捕获用户查询的不同方面,同样适用于驱动图检索,例如,生成关于同一个主题的不同侧重点的图查询。研究还提出了 Step-back Prompting 和 Sub-query Decomposition 等更复杂的查询变换技术 (Nir Diamant GitHub repository)。
4.5.2 重排序(Re-ranking):
-
Cross-Encoder: 是一种更精细的文本对文本的编码器,它直接对查询和检索到的文档进行联合编码,能够更准确地评估它们之间的相关性,通常用于对初步检索结果进行重排序。Cross-Encoder 模型通常比用于初始检索的 Embedding 模型具有更强的语义理解能力。
-
LLM Re-ranker: 可以使用 LLM 对初步检索到的结果进行打分和排序。LLM 能够理解更复杂的语义关系和上下文信息,从而实现更智能的重排序,并可以考虑多个文档之间的相互关系。NVIDIA Developer Blog 讨论了使用 LLM 进行重排序以提高 RAG 管道性能的方法。重排序是提高 RAG 管道性能的关键步骤,可以确保最终提供给生成模型的上下文是最高质量的。
4.6 实操:实现基础的向量检索、混合检索,并演示简单的 Graph RAG 流程
-
实现基础的向量检索:
import faiss import numpy as np # 假设我们有一些文档的嵌入向量,存储在名为 embeddings 的 NumPy 数组中, # 形状为 (文档数量, 嵌入维度),这里假设嵌入维度是 128。 embeddings = np.random.rand(100, 128).astype('float32') # 使用 FAISS 库创建一个简单的基于 L2 距离的索引。 index = faiss.IndexFlatL2(128) # 将文档的嵌入向量添加到索引中。 index.add(embeddings) # 假设我们有一个查询向量,存储在名为 query_vector 的 NumPy 数组中, # 形状为 (1, 嵌入维度)。 query_vector = np.random.rand(1, 128).astype('float32') k = 5 # 我们希望搜索最相似的 5 个文档。 # 在索引中搜索与查询向量最相似的 k 个向量,返回它们的距离和索引。 distances, indices = index.search(query_vector, k) print("最相似的文档索引:", indices) print("对应的距离:", distances)
这段代码演示了如何使用 FAISS 库创建一个简单的 L2 距离索引,并对一组随机生成的文档嵌入向量进行搜索。实际应用中,您需要使用真实的文档和预训练的 Embedding 模型来生成向量。
-
实现混合检索:
import numpy as np # 假设我们已经有了向量检索和稀疏检索的结果 # vector_results 是一个包含 (文档索引, 相似度得分) 元组的列表 vector_results = [(1, 0.9), (5, 0.85), (10, 0.8), (15, 0.75), (20, 0.7)] # sparse_results 是一个包含 (文档索引, 相关性得分) 元组的列表 sparse_results = [(1, 1.2), (8, 1.1), (15, 1.0), (25, 0.95), (5, 0.9)] # 将结果存储在字典中,以便按文档索引聚合得分 combined_results = {} # 处理向量检索结果 for index, score in vector_results: combined_results[index] = combined_results.get(index, 0) + score # 处理稀疏检索结果 for index, score in sparse_results: combined_results[index] = combined_results.get(index, 0) + score # 将字典转换为 (文档索引, 总得分) 的列表 final_results = list(combined_results.items()) # 按照总得分降序排序 final_results.sort(key=lambda item: item[1], reverse=True) print("混合检索结果 (文档索引, 总得分):", final_results) # 在实际应用中,您可能需要对不同检索器的得分进行归一化和加权 # 例如,可以使用不同的权重来调整向量检索和稀疏检索的重要性 weighted_combined_results = {} vector_weight = 0.6 sparse_weight = 0.4 for index, score in vector_results: weighted_combined_results[index] = weighted_combined_results.get(index, 0) + score * vector_weight for index, score in sparse_results: weighted_combined_results[index] = weighted_combined_results.get(index, 0) + score * sparse_weight weighted_final_results = list(weighted_combined_results.items()) weighted_final_results.sort(key=lambda item: item[1], reverse=True) print("加权混合检索结果 (文档索引, 加权总得分):", weighted_final_results)
-
演示简单的 Graph RAG 流程:
- 实体链接:
- 假设用户提出的问题是:“告诉我关于苹果公司(Apple Inc.)的信息。”
- 检索器的第一步通常是将用户查询中的关键词(例如“苹果公司”)链接到知识图谱中对应的实体节点。这可能涉及到使用实体识别和消歧技术,以确保将“苹果公司”正确地链接到代表这家科技公司的唯一实体,而不是其他可能也包含“苹果”这个词的实体。
- 链接成功后,知识图谱就能够理解用户查询的目标是知识库中关于“苹果公司”的特定信息。
- 基于邻居查找的图遍历:
这段代码展示了如何使用 Neo4j 的 Python 驱动连接到 Neo4j 图数据库,并定义一个函数来查找指定名称实体的直接邻居节点及其关系类型。您需要确保您的 Neo4j 数据库中已经包含相关数据。from neo4j import GraphDatabase # 替换为您的 Neo4j 连接信息,例如 Bolt 协议的 URI。 uri = "bolt://localhost:7687" username = "neo4j" password = "your_password" # 创建一个 Neo4j 驱动实例,用于连接数据库。 driver = GraphDatabase.driver(uri, auth=(username, password)) # 定义一个函数,用于在事务中查找给定实体名称的邻居节点和关系。 def find_neighbors(tx, entity_name): query = f""" MATCH (n {{name: '{entity_name}'}})-[r]-(m) RETURN n.name, type(r), m.name """ # 执行 Cypher 查询。 result = tx.run(query) # 遍历查询结果,并打印实体名称、关系类型和邻居实体名称。 for record in result: print(f"({record['n.name']}) -[{record['type(r)']}]-> ({record['m.name']})") # 使用驱动创建一个会话,并在会话中执行只读事务来查找“苹果公司”的邻居。 with driver.session() as session: session.execute_read(find_neighbors, "苹果公司") # 关闭驱动,释放资源。 driver.close()
- 实体链接:
内容同步在gzh:智语Bot