检索增强生成 (RAG) 将 LLM 自然语言生成功能与信息检索功能相结合,从而实现更具情境感知、更准确、更相关和更细微的响应。通过将检索功能纳入生成过程,RAG 系统可保持较高的相关性和事实准确性,使其成为知识管理、客户支持和研究等应用中不可或缺的一部分,因为这些应用中精确且情境适当的信息至关重要。
传统的检索增强生成 (RAG) 技术虽然在某些情况下有效,但通常难以捕捉现实世界数据中存在的复杂关系和上下文细微差别。另一方面,知识图谱提供了信息的结构化表示,从而实现了更高效的检索和推理。然而,有效地将知识图谱与 LLM 集成以提高 RAG 性能仍然是一项具有挑战性的任务。
在 RAG 系统中使用知识图谱越来越多地被认可为可以改善数据组织和检索精度。基于图谱的方法现在通常与使用大型语言模型 (LLM) 相关联,以从文本语料库中提取和构建复杂关系,这是微软今年早些时候首次推出的 [1]。然而,使用 LLM 构建和维护准确的知识图谱仍然需要大量资源,并且面临数据稀疏、重复和需要持续更新等挑战。为了解决这些问题,人们倾向于使用模块化和分层的图结构,这样可以有效地管理大型数据集。社区检测和摘要等技术正在被用于提高基于图的 RAG 系统的可扩展性和效率。
在本文中,我将重点介绍我称之为固定实体架构 (FEA) 的新方法。微软的 GraphRAG 和 FEA 这两种方法都解决了知识图谱构建和利用中的常见问题,例如可扩展性、复杂性和精度。这些方法代表了将知识图谱与 RAG 集成、利用 LLM 和高效图结构技术的最新进展。它们是两种截然不同的方法,取决于给定的用例和给定的数据。这两种方法适用于不同类型的数据和查询,使其广泛应用于各个行业和研究领域。
本文重点
本文介绍了一种构建知识图谱的新方法,该方法可以作为许多用例中 RAG 应用程序更有效的知识库。与传统的基于 LLM 的图构建方法不同,FEA 方法旨在解决现有技术的以下局限性:
- 在图形创建步骤中过度使用 LLM
- 避免实体重复并消除实体解析的需要
- 图稀疏性
本文结构如下:
· GraphRAG 和固定实体架构分析
· GraphRAG——现有微软方法概述
· 固定实体架构:介绍和比较概述
· 本体论鱼骨
· 增加知识
· 检索过程
我将使用爱因斯坦的一句名言的简单示例来说明这些概念,展示如何构建知识库并执行高级检索。这种方法旨在为您的 RAG 系统开辟新视野,并展示进一步探索和开发的巨大潜力。
GraphRAG 和固定实体架构分析
GraphRAG - 现有 Microsoft 方法概述
2024 年 4 月,微软发表了第一篇关于 GraphRAG 的论文,介绍了一种有趣的方法,即使用大型语言模型 (LLM) 从文本语料库中提取实体和关系,并基于这些实体构建知识图谱。他们将实体聚合到社区中,然后将其变成其内容的摘要。实际的检索增强生成 (RAG) 是在这些摘要上执行的,展示了一种具有巨大信息检索潜力的方法。
与任何方法一样,GraphRAG 也有其优点和缺点。下表 1 从我的角度重点介绍了 Microsoft GraphRAG 方法的优点和挑战。值得注意的是,在某些用例中,尤其是处理大量文本数据的用例中,提前了解实体之间的关系可能并不总是适用。
就我而言,我当时正在使用已知或至少部分已知的本体进行概念验证 (POC),并且迫切需要实施 RAG。任务是在非常非结构化的数据上构建一个高效的知识库。
我最初尝试使用 Microsoft 的方法来构建数据图表,花费了大量时间编写和优化查询以从文本块中提取实体和关系。在创建了我的第一个甚至第二个 LLM 生成的图形数据库变体后,我遇到了一个重大问题。信息检索不适合 GenAI 驱动的应用程序。数据库中充斥着重复项,实体解析不够准确,导致过程耗时且成本高昂。总之,对于我的特定用例来说,它太昂贵、太混乱、太复杂、太难以控制。
我意识到,对于我正在研究的明确定义的领域,我需要一种不同的方法来实现知识图谱上的 RAG — 这种方法要快速、基本自动化,并且不严重依赖昂贵的 LLM 调用。此外,我希望它非常可控且灵活。LLM 创建的大量经常重复的实体让我想到,我希望这些实体是固定的、数量较少的,并且能够高精度地了解它们的连接方式。
本文介绍了使用固定实体架构在图形上构建 RAG 的 FEA 方法。知识图谱是使用 Neo4J 创建的。
固定实体架构
固定实体架构——新方法介绍
本文提出的固定实体架构基于预定义的实体和关系,它们构成了用例域的本体“鱼骨图”。确定要将哪些内容纳入此结构通常是一个深奥的哲学问题,需要广泛的领域知识才能开发出强大的固定实体架构。或者,您可以考虑需要知识库做什么,并确定关键或模板文档作为本体鱼骨图的基础。
与微软的方法不同,固定实体架构不依赖大型语言模型 (LLM) 来构建图。相反,它利用特定于用例的专有领域知识,并结合简单的数学技术。这种方法提供了一种非常有效的方法来解决与基于 LLM 的方法相关的许多缺点。
比较概述
表 1 提供了 Microsoft 的 GraphRAG 和固定实体架构两种方法的比较概述。
表 1.两种方法的比较概述。
总之,固定实体架构非常适合需要高精度和控制的明确定义的狭窄领域。它提供了较低的复杂性、较低的计算成本,并最大限度地减少了对 LLM 的依赖。然而,它缺乏灵活性,在大型数据集中的可扩展性较差,并且需要先验领域知识。
Microsoft 的 GraphRAG擅长处理大型、多样化的数据集和复杂的查询,提供跨各个领域的可扩展性和适应性。它支持本地和全局查询,但复杂性更高、资源成本更高,并且对 LLM 的依赖性更强。当优先考虑简单性、低维护性或固定实体时,它不太合适。
这里的建议是根据数据集性质、控制需求和可用资源进行选择。结合两种方法的元素可以优化性能。
本体论鱼骨
什么是本体论?您是否考虑过自己微观世界的本体论?每天在工作中,您执行任务时并没有意识到您的大脑如何识别这些“事物”以及如何在它们之间建立联系。尝试这个练习:尝试为您每天做或使用的“事物”创建一个元模型,然后绘制出它们之间的联系。虽然这听起来很有挑战性,但我有个好消息——这完全有可能,因为您知道自己在做什么!
本体是帮助我们理解周围世界的强大工具。“鱼骨”比喻鼓励我们深入研究定义我们概念领域的基本构建块和连接。创建本体需要仔细考虑要包含的内容、元素之间的关联以及最重要的内容。鱼骨结构可帮助我们识别核心元素,同时承认将我们的想法变为现实的错综复杂的连接网络。
现在,假设您在一个狭窄的领域内工作,并且清楚地了解构成您工作的实体的“鱼骨”。您如何轻松地获取所有这些信息,将它们连接起来,并为任何 RAG 应用程序构建知识库?请继续阅读 - 我对这些问题有答案。
创建基本的“鱼骨”结构:爱因斯坦示例
在许多组织中,主题专家可以轻松识别明确定义领域内的关键实体和关系。这种基础知识对于构建有效的知识图谱至关重要。通过利用这种专业知识,可以在几天内建立关键实体及其关系的基本“鱼骨图”,为进一步丰富详细信息奠定坚实的基础。
创建鱼骨图时,请记住尽可能为实体添加描述。让我们考虑一个著名的例子来说明知识图谱是如何构建的:“阿尔伯特·爱因斯坦创立了相对论,彻底改变了理论物理学和天文学。”这个著名的句子展示了如何提取实体和关系。下面的图 1 显示了根据这句话构建的图。
图 1. 句子“阿尔伯特·爱因斯坦创立了相对论,彻底改变了理论物理学和天文学”的图形表示——固定实体架构图的“鱼骨图”本体。
Cypher 是一种声明性查询语言,用于与图形数据库(尤其是 Neo4j)交互。它允许用户以直观的类似 SQL 的语法表达复杂的图形模式和关系。Cypher 旨在方便用户阅读,并提供查询、更新和管理图形数据的强大功能。它支持模式匹配、过滤和节点与关系的操作,非常适合涉及社交网络、推荐引擎和其他基于图形的数据模型的任务。有关更多详细信息,您可以访问 Neo4j Cypher 官方文档 [2]。
这里,我假设读者熟悉嵌入、余弦相似度、点积和向量索引等术语。如果不熟悉,请参阅其他文献,例如 [3–5]。
在我们的爱因斯坦示例中,我们有四个实体(阿尔伯特·爱因斯坦、相对论、理论物理学和天文学)和三个边,它们有两种类型(已开发和已革命)。我们可以为每个实体添加简短的描述,例如:“阿尔伯特·爱因斯坦是 20 世纪伟大的物理学家”等。将其翻译成 Cypher 代码,您的图表创建将如下所示:
CREATE (a:Entity {label: 'Person' , name: 'Albert Einstein', embeddings: $person_emb}),
(b:Entity {label:'Theory' ,name: 'Theory of relativity', embeddings: $theory_emb}),
(c:Entity {label:'Field', name: 'Theoretical physics', embeddings: $field1_emb }),
(d:Entity {label:'Field', name: 'Astronomy', embeddings: $field2_emb })
// Create edges
CREATE (a)-[:DEVELOPED]->(b),
(b)-[:REVOLUTIONIZED]->(c),
(b)-[:REVOLUTIONIZED]->(d)
请注意,我创建的节点只有一个关联标签,名为“Entity”,但我为每个节点添加了不同的标签作为属性。这样做是为了在以后的搜索中保持清晰度。所有节点(内部称为实体)形成固定的鱼骨结构,用于后续操作。我稍后会添加不同标签的节点,但它们将发挥不同的作用——所以请继续阅读!😉
采用这种方法的另一个原因是我还没有找到同时为两个或多个节点标签构建统一索引的方法(如果您知道一种方法,我很乐意稍后在评论部分听到您的想法)。
这里的参数embeddings
仅包含“标签:名称”值,但在实践中,我建议包括实体的详细描述并将其添加到嵌入向量中。
添加知识
现在,我们有了 GraphDB 的“鱼骨图”,我们将在此基础上构建知识库,以便在 RAG 应用程序中进行检索(图 2)。要创建这个知识库,我们需要文档。对于阿尔伯特·爱因斯坦,我使用了一篇维基百科文章 [6]。我使用维基百科 API 复制了文本,并将其拆分为 59 个块,每个块的大小为 2000。请注意,在此演示中,我没有优化块大小;选择的大小是任意的。
图 2. Neo4J 中的固定实体鱼骨图表示
将文档添加到图表中
让我们按照以下方式将文档块添加到图表中:
prev_node_id = None # Initialize prev_node_id before the loop
for i, chunk in enumerate(chunks):
# Create the chunk node
query = f'''
CREATE (d:Document {{
chunkID: "{f"chunk_{i}"}",
url: "{url}",
docID: "{document_name}",
full_text: '{escaped_chunk}',
embeddings: {embeddings.embed_documents(chunk).tolist()}}}
)
RETURN ID(d)
'''
run_query(driver, query)
chunk_node_id = result[0]['ID(d)']
# If this is not the first chunk, create a NEXT relationship to the previous chunk
if prev_node_id is not None:
query = f'''
MATCH (c1:Document), (c2:Document)
WHERE ID(c1) = {prev_node_id} AND ID(c2) = {chunk_node_id}
CREATE (c1)-[:NEXT]->(c2)
CREATE (c2)-[:PREV]->(c1)
'''
run_query(driver, query)
prev_node_id = chunk_node_id
此代码导致块被添加为标记为“文档”的节点(见图 3)。
图 3. 阿尔伯特·爱因斯坦维基百科文章的前 25 个部分。
将文档块相互连接起来,在以后的检索中具有显著的优势。您可以使用 Cypher 查询定义,如果找到类似的块,您还可以检索前一个和下一个块。就这么简单!
将文档与固定实体连接起来
接下来,让我们将文本块连接到我们的固定实体“鱼骨”。请注意,这种方法不使用任何昂贵的 LLM 技术,并避免在知识库中创建重复项。此外,它只需几毫秒即可执行。我们使用一个称为点积的简单数学公式,它提供每个块和我们的固定实体架构之间的余弦相似度值。以下 Cypher 代码将实现此目的:
MATCH (e:Entity), (d:Document)
WHERE e.embeddings IS NOT NULL AND d.embeddings IS NOT NULL AND size(e.embeddings) = size(d.embeddings)
WITH e, d,
reduce(numerator = 0.0, i in range(0, size(e.embeddings)-1) | numerator + toFloat(e.embeddings[i])*toFloat(d.embeddings[i])) as dotProduct,
sqrt(reduce(eSum = 0.0, i in range(0, size(e.embeddings)-1) | eSum + toFloat(e.embeddings[i])^2)) as eNorm,
sqrt(reduce(dSum = 0.0, i in range(0, size(d.embeddings)-1) | dSum + toFloat(d.embeddings[i])^2)) as dNorm
WITH e, d, dotProduct / (eNorm * dNorm) as cosineSimilarity
WHERE cosineSimilarity > 0.8
MERGE (e)-[r:RELATES_TO]->(d)
ON CREATE SET r.cosineSimilarity = cosineSimilarity
RETURN ID(e), e.name, ID(d), d.full_text, r.cosineSimilarity
ORDER BY cosineSimilarity DESC
在此示例中,我们指示数据库获取所有实体节点,并通过计算它们的向量之间的角度将它们与每个文本块进行比较。如果余弦相似度超过给定阈值(在本例中为 0.8),数据库将RELATES_TO
在实体节点和文档之间创建一条边。由于块未优化,并且固定实体缺乏详细描述,因此匹配度可能不是很高。此示例旨在说明该技术。
表 2. 将文档节点附加到固定实体的上一个查询的结果。
图 3. 根据余弦相似性属性自连接的文档节点。
图 4 显示了此查询的结果。部分块自动链接到余弦相似度高于定义阈值 0.8 的固定实体。您可以根据需要调整此阈值。此方法允许在检索过程中进行进一步过滤,使您能够仅选择最匹配的块。
不仅“阿尔伯特·爱因斯坦”这个实体与相关文本块相关联,而且“相对论”也是如此。随着更多文档的添加,“鱼骨”实体将越来越多地充当元素之间的连接器。这是使用图表的优势之一——几乎所有事物都是相互关联的。不存在需要聚类的稀疏性问题。您可以进一步探索这一点,但这将是另一个话题。现在,让我们继续添加更多文档。
请参阅表 3,了解维基百科理论物理学文章中的文本如何与各种固定实体连接的示例。
表 3. 维基百科理论物理学文章中匹配实体到添加文档的返回结果。
图 5 显示了将文档 [6–9] 附加到爱因斯坦句子的固定实体的结果。这些块无缝链接到相关实体。
这种附加文档的技术用途广泛,可应用于各种类型的文档。通过矢量化数据,您可以轻松集成和连接所有信息。
为混合搜索构建不同类型的索引
Neo4j 的优点在于它能够在图表上创建各种类型的索引,甚至更好的是,您只需使用单个 Cypher 查询即可执行混合搜索。让我们为检索准备索引。
创建向量索引
首先,我们创建向量索引:
queries = [
"DROP INDEX test_index_document IF EXISTS;",
"DROP INDEX test_index_entity IF EXISTS;"
]
for query in queries:
run_query(driver, query)
# create vector index on document embeddings
query = '''CREATE VECTOR INDEX test_index_document
IF NOT EXISTS
FOR (d:Document)
ON (d.embeddings)
OPTIONS {indexConfig: {
`vector.dimensions`: 768,
`vector.similarity_function`: 'cosine'
}}
'''
run_query(driver, query)
# create vector index on entity embeddings
query = '''CREATE VECTOR INDEX test_index_entity IF NOT EXISTS
FOR (n:Entity)
ON (n.embeddings)
OPTIONS {indexConfig: {
`vector.dimensions`: 768,
`vector.similarity_function`: 'cosine'
}}
'''
run_query(driver, query)
在节点上创建向量索引后,如果您有边的描述,也可以在边上创建向量索引。由于我目前没有关于关系的任何文本,因此我将跳过此步骤。
创建文本索引
接下来我将创建一个文本索引,它也将作为标准关键字索引添加到搜索中。
queries = [
"DROP INDEX text_index_entity IF EXISTS;",
"DROP INDEX text_index_document IF EXISTS;",
]
for query in queries:
run_query(driver, query)
# create full-text index on Entity description
query = '''
CREATE FULLTEXT INDEX text_index_entity FOR (n:Entity) ON EACH [n.name]
'''
run_query(driver, query)
# create full-text index on Document full_text
query = '''
CREATE FULLTEXT INDEX text_index_document FOR (d:Document) ON EACH [d.full_text]
'''
run_query(driver, query)
检索
现在我们的知识库已准备好进行检索并构建我们的 RAG 应用程序,我们可以开始查询图形数据库。
让我们从一个简单的查询开始。我们将要求数据库找到给定查询的最佳匹配答案。例如,我们将使用查询:“阿尔伯特·爱因斯坦的研究领域”
首先,我将使用基于实体的向量索引。查询涉及将原始用户查询嵌入向量索引和/或将其用作基于关键字的索引的文本。我将从一个简单的查询(查询 1)开始:
CALL db.index.vector.queryNodes('test_index_entity', 10, $user_query)
YIELD node AS vectorNode, score as vectorScore
WITH vectorNode, vectorScore
ORDER BY vectorScore DESC
RETURN vectorNode.name AS label, vectorScore AS score
这里,我仅查询固定实体,具体就是使用在其上建立的向量索引。结果如下所示(表4)。
由于我们只有四个实体,因此结果在某种程度上是可以预测的。仅提取实体(尤其是在此阶段没有描述)对 RAG 应用程序的价值有限。为了增强我们的结果,我们需要提取与问题相关的相关文档。让我们更进一步,确定找到的实体最相关的文档(查询 2):
CALL db.index.vector.queryNodes('test_index_entity', 10, $user_query)
YIELD node AS vectorNode, score as vectorScore
WITH vectorNode, vectorScore
MATCH (vectorNode)-[r]->(d:Document)
WITH vectorNode.name AS label, vectorScore as score, d.docID as closest_document_name,
d.full_text as closest_document_text, r.cosineSimilarity as similarity
ORDER BY similarity DESC
RETURN label, closest_document_name, closest_document_text, similarity
LIMIT 10
如表 5 所示,结果与初始查询略有不同。这里首先返回的是“天文学”。然而,与预期的“相对论”不同,接下来出现的是“阿尔伯特·爱因斯坦”。使用 GraphRAG 和这种方法的优势在于它的灵活性:您可以调整搜索以更好地满足您的应用程序需求。让我们优化查询,将“天文学”和“相对论”优先列为该问题的最佳结果。
为了进一步增强结果,我们可以实施跨编码器重新排序步骤。虽然我通常对描述进行重新排序,但在这种情况下,由于缺乏描述,我们将把它应用于标签。为此,我将使用“sentence_transformers”库中的“cross-encoder/ms-marco-MiniLM-L-6-v2”模型。表 6 显示了查询 2 的重新排序结果。
表 6.使用查询 2 检索的信息(包括重新排序)。
如您所见,重新排名与查询相结合并未产生预期的结果。为了改善结果,我们可以结合全文或关键字搜索(查询 3):
CALL db.index.vector.queryNodes('test_index_entity', 10, $my_query_emb_list)
YIELD node AS vectorNode, score as vectorScore
WITH vectorNode, vectorScore
MATCH (vectorNode)-[r]->(d:Document)
WITH vectorNode.name AS label, vectorScore as score, d.docID as closest_document_name,
d.full_text as closest_document_text, r.cosineSimilarity as similarity
ORDER BY similarity DESC
RETURN label, closest_document_name, closest_document_text, similarity
LIMIT 10
UNION
CALL db.index.fulltext.queryNodes('text_index_entity', $my_query)
YIELD node AS textNode, score as textScore
WITH textNode, textScore
MATCH (textNode)-[r]->(d:Document)
WITH textNode.name AS label, textScore as score, d.docID as closest_document_name,
d.full_text as closest_document_text, r.cosineSimilarity as similarity
ORDER BY similarity DESC
RETURN label, closest_document_name, closest_document_text, similarity
LIMIT 10
该查询的结果如表7所示。
表 7.使用查询 3 检索到的信息,包括混合向量关键字搜索。
之前的尝试(包括重新排名)并未产生预期的结果。现在,我将展示 GraphDB 中检索的真正威力。为了避免 APOC 库的问题和 Python 工具的兼容性问题,我专注于一种清晰、透明的方法。我将使用纯数学方法来说明 GraphDB 搜索功能的有效性。下一个查询展示了我在工作中所说的“智能搜索”(查询 4)。
CALL db.index.vector.queryNodes('test_index_entity', 10, $user_query_emb)
YIELD node AS vectorNode, score as vectorScore
WITH vectorNode, vectorScore
MATCH (vectorNode)-[r]->(d:Document)
WITH DISTINCT vectorNode as e, d, r, r.cosineSimilarity as cosineSimilarity
ORDER BY cosineSimilarity DESC
WITH ID(e) as id, e.label as title,
cosineSimilarity,
e.description as description,
head(collect(d.docID)) as document_id,
head(collect(d.chunkID)) as chunkID,
head(collect(d.full_text)) as document_text,
reduce(mDot = 0.0, i IN range(0, size($user_query_emb) - 1) | mDot + $user_query_emb[i] * e.embeddings[i]) /
(sqrt(reduce(mSq = 0.0, x IN $user_query_emb | mSq + x^2)) * sqrt(reduce(eSq = 0.0, y IN e.embeddings | eSq + y^2))) AS entity_similarity,
reduce(mDot = 0.0, i IN range(0, size($user_query_emb) - 1) | mDot + $user_query_emb[i] * d.embeddings[i]) /
(sqrt(reduce(mSq = 0.0, x IN $user_query_emb | mSq + x^2)) * sqrt(reduce(dSq = 0.0, y IN d.embeddings | dSq + y^2))) AS document_similarity
WITH id, title, description, document_id, document_text, entity_similarity, document_similarity, chunkID,
(entity_similarity + document_similarity) / 2 AS similarity, cosineSimilarity
WHERE similarity > 0.8
RETURN id, title, document_id, document_text, similarity, chunkID
ORDER BY cosineSimilarity DESC, similarity DESC
对于此查询,我首先使用向量索引提取固定实体,然后识别最佳匹配的文档。接下来,我计算提取的实体与用户查询之间的点积,以及找到的文档与用户查询之间的点积。然后,我将这些结果组合起来,并按 0.8 阈值进行过滤。这种方法与重新排名相结合,产生了以下结果(表 8):
表 8.使用查询 4 检索的信息,包括重新排序。
通过这个实验,我旨在展示 GraphDB 中检索的强大功能。通过完全控制固定实体和添加文档的过程,并灵活地构建几乎任何检索 Cypher 查询,潜力是巨大的。根据您的特定用例定制检索过程的自由几乎是无限的。在这个实验中,我们使用了有限的固定实体架构,缺乏元素之间的广泛描述和关系。但是,通过整合更多信息和连接,可以取得显著的成果。
概括
本文探讨了如何使用 GraphDB 构建和查询知识库,重点介绍固定实体架构及其在检索增强生成 (RAG) 系统中的应用。固定实体架构依靠预定义的实体和关系来构建知识图谱,具有复杂度低、精度高、计算成本低等优点。但是,它可能面临灵活性和可扩展性的限制。
本文演示了使用“鱼骨”本体构建知识图谱并将文档集成到图中的过程。通过利用向量索引和 Cypher 查询,文档基于余弦相似度与实体相连。此方法增强了检索能力,但可能需要优化才能获得更好的结果。
执行一系列查询来评估基于图的检索方法的有效性。初始查询使用向量索引和关键字搜索来查找匹配的实体和文档。尽管存在一些挑战,例如重新排名结果不理想,但本文展示了 GraphDB 实现强大、灵活检索的潜力。最终的智能搜索查询结合了向量相似度和点积计算,说明了该方法在优化搜索结果方面的有效性。
未来方向和创新
- 改进机会:增强基于 LLM 的提取方法并扩展预定义的本体可以进一步完善知识图谱的功能。
- 新兴趋势:知识图谱开发的新技术和进步可能会增强 RAG 系统,为复杂的检索任务提供更强大的解决方案。
总体而言,本文强调了 GraphDB 灵活搜索功能的优势以及优化实体连接和文档集成以实现高质量检索结果的重要性。提取技术和新兴技术的未来发展有望进一步提高 RAG 应用程序中的知识图谱功能。