链载Ai

标题: 全网首发!小白也能读懂的GraphRAG知识图谱全流程解析,多图预警! [打印本页]

作者: 链载Ai    时间: 昨天 11:18
标题: 全网首发!小白也能读懂的GraphRAG知识图谱全流程解析,多图预警!

在我们上次的交流中,我提及了GraphRAG这个神秘的概念和基础使用。不记得的可以回去阅读:最近爆火的GraphRAG是什么,真的能用于商业应用吗?。我会在接下来过程中用几篇图文丰富的文章,带领你一起彻底解读GraphRAG,并揭示其内部工作流程。

今天,我要和你分享的是如何用GraphRAG从一个普通的txt文件中创建知识图谱,准备好了吗?那就让我们开始吧!

GraphRAG解决了什么问题

当你问:“这个数据集的主题是什么?”这类高级别、概括性的问题时,传统的RAG可能就会束手无策。为什么呢?那是因为这本质上是一个聚焦于查询的总结性任务(Query-Focused Summarization,QFS),而不是一个明确的检索任务。

我知道你现在可能在想,“那我们该如何解决这个问题呢?”好消息是,有人已经找到了解决方案,而且还被详细地描述在论文中:

In contrast with related work that exploits the structured retrieval and traversal affordances of graph indexes (subsection 4.2),we focus on a previously unexplored quality of graphs in this context: their inherent modularity (Newman,2006) and the ability of community detection algorithms to partition graphs into modular communities of closely-related nodes (e.g.,Louvain,Blondel et al.,2008; Leiden,Traag et al.,2019). LLM-generated summaries of these community descriptions provide complete coverage of the underlying graph index and the input documents it represents. Query-focused summarization of an entire corpus is then made possible using a map-reduce approach: first using each community summary to answer the query independently and in parallel,then summarizing all relevant partial answers into a final global answer.

简单来说,就是利用社区检测算法(如Leiden算法)将整个知识图谱划分模块化的社区(包含相关性较高的节点),然后大模型自下而上对社区进行摘要,最终再采取map-reduce方式实现QFS: 每个社区先并行执行Query,最终汇总成全局性的完整答案.

与其他RAG系统类似,GraphRAG整个Pipeline也可划分为索引(Indexing)与查询(Query)两个阶段。索引过程利用LLM提取出节点(如实体)、边(如关系)和协变量(如 claim),然后利用社区检测技术对整个知识图谱进行划分,再利用LLM进一步总结。

鉴于篇幅原因,今天的这篇文章主要聚焦于indexing, 下一篇文章会介绍Query的工作原理,敬请期待!

pipeline

当你运行 "poetry run poe index" 命令时,它会执行 graphrag.index.cli 目录下的 index_cli 入口函数。在 GraphRAG 中,构建知识图谱被视为一个流水线(pipeline)过程,这个流水线包含多个工作流(workflow),例如文本分块、使用LLM来识别实体等。pipeline涵盖的workflow是通过 settings.yml 配置文件进行指定的。index_cli 的主要任务是创建 pipeline_config 对象,并利用 run_pipeline_with_config 函数来运行流水线。所以,我们可以将整个流程概括如下:

整个过程体现了自上而下的编程思想——每个结果依赖于更底层函数的执行,从顶部开始调用,然后逐步深入到底层函数。这样的结构使得整体流程清晰明了,这也是我们平时在项目开发中的编程思路。

workflow

讨论workflow之前,先简单了解下项目使用的另一个框架: DataShaper 是微软开源的一款用于执行工作流处理的库,内置了很多组件(专业名词叫做Verb). 通过定义一个数据处理的工作流,你可以对输入的数据(比如Pandas的DataFrame)定义一系列数据操作的动作(DataShaper中称作Verb)、参数与步骤,执行这个工作流即可完成数据处理过程。在DataShaper中提供了很多开箱即用的Verb,你也可以自定义Verb。多个子工作流也可以组合定义成一个更大的工作流。

当你通过命令行执行完indexing之后,你会看到如下的输出内容:

从这个可以看出GraphRAG的indexing共经历了14个workflow:

  1. create_base_documents
  2. create_final_documents
  3. create_base_text_units
  4. join_text_units_to_entity_ids
  5. join_text_units_to_relationship_ids
  6. create_final_text_units
  7. create_base_extracted_entities
  8. create_summarized_entities
  9. create_base_entity_graph
  10. create_final_entities
  11. create_final_relationships
  12. create_final_nodes
  13. create_final_communities
  14. create_final_community_reports

基本的处理过程如下:首先,它会将输入文本进行拆分,然后提取实体与关系,生成摘要信息,并根据这些信息构建内存中的图(Graph)结构。接下来,它会从这个图中识别出各个社区,为每个社区创建报告,并在图中创建文本块节点和文档节点。

当然,以上只是些核心步骤,但在实际的处理过程中还涉及到许多细节处理,比如生成嵌入(embedding),持久化到存储,以及应用不同的算法策略等等。当然这些并不是这篇文章的重点。如果感兴趣,评论区留言我会单独开别的文章来讲解。

这里可以多说一点,这14个workflow其实又可以进一步细分为四大类:

  1. 关于文档的document_workflows

以cluster = 1为例,通过text_unit_ids,我们能够知道这个社区来源于哪些chunk;通过relationship_ids,我们则可以确定这个社区包含了哪些边。

最后,我们还会对上述数据进行一次处理,主要是生成每个社区的名称:

8. join_text_units_to_entity_ids

join_text_units_to_entity_ids的作用是建立text_unit到entity的映射关系。

首先,GraphRAG会提取出每个实体的"id",以及表示实体来源的字段"text_unit_ids"。接着,我们对"text_unit_ids"进行“打平”操作,也就是将嵌套的数据结构转化为一维的形式。

然后,我们进行聚合操作。具体来说,我们会按照"text_unit_id"对数据进行分类,并把相同类别的实体id聚合成一个数组,命名为"entity_ids"。

这样,在最终的结果中,每一条记录都会包含一个"text_unit_id",以及一个与之关联的"entity_ids"数组:


9. create_final_relationships

create_final_relationships用于创建relationship table, 步骤如下:

首先,我们从create_base_entity_graph中得到的Graph提取出所有的边关系:

接下来,我们会对edge和nodes使用left_join操作,在这个步骤中,我们将新增两列:source_degree和target_degree。这两列分别表示源实体和目标实体的度数,也就是每个实体连接的边的数量。

最后,我们会创建一个新的列"rank"。这个列的值是通过将source_degree和target_degree相加得到的。这样,我们就可以根据rank的值,了解每条边连接的两个实体的总度数:

sourcetargetweightdescriptiontext_unit_idsidhuman_readable_idsource_degreetarget_degreerank
蒙奇·D·路飞草帽一伙1.0蒙奇·D·路飞是草帽一伙的船长和创立者['2808e991f29115cba505836944beb514']392be891f8b649fabdc20e7bf549f6690111930
蒙奇·D·路飞香克斯1.0蒙奇·D·路飞为了实现与香克斯的约定而出海['2808e991f29115cba505836944beb514']0111777c4e9e4260ab2e5ddea7cbcf58111213
蒙奇·D·路飞ONE PIECE1.0蒙奇·D·路飞为了寻找传说中的大秘宝ONE PIECE而扬帆起航['2808e991f29115cba505836944beb514']785f7f32471c439e89601ab81c828d1d211112

10. join_text_units_to_relationship_ids

这个workflow和我们之前讨论过的join_text_units_to_entity_ids非常相似,主要区别在于,现在我们是将text_unit_id映射到它所包含的relationship_id,而不再是entity_ids。

简单来说,我们的目标是理解每个text_unit_id(对应"chunk")都包含了哪些关系(relationship)。为了实现这个目标,我们会创建一种映射关系,把每个text_unit_id连接到它所涉及的所有relationship_id。结果将以类似于字典的形式呈现,其中键是text_unit_id,值是一个列表,包含了所有相关的relationship_id:

11. create_final_community_reports

这个workflow用于生成社区摘要:借助LLM生成每个社区的摘要信息,用来了解数据集的全局主题结构和语义。这也是Microsoft GraphRAG的核心价值所在,也是回答QFS问题的关键。具体步骤如下:

首先借助create_final_nodes的输出,并添加了一个node_details列以存储更多关于节点的信息:

然后对这些nodes使用community_hierarchy来构建社区的层次结构,通过对(community, level) 的分组,将同一组内的节点title聚合成数组:

从上图我们可以看到每个community包含了哪些entity。

接下来,GraphRAG开始分析父子社区的构造情况,如果上一级的社区包含了全部下一级社区的成员,那么它们之间就构成了父子社区的关系,我们发现社区1是个大社区,包含了12、13、14三个子社区,但是它们都属于同一个level:

紧接着GraphRAG会基于三个table: node_df、edge_df、claim_df 做聚合操作,生成每个社区的context_string: 包含社区的所有节点和relationships信息:

为了方便你看到context_string的内容,我摘取了某个社区的context_string内容:

'-----Entities-----\n'
'human_readable_id,title,description,degree\n'
'2,蒙奇·D·路飞,蒙奇·D·路飞是“草帽一伙”的船长,外号“草帽小子”,梦想成为“海贼王”,悬赏金30亿贝里,11\n'
'20,ONE PIECE,,1\n'
'17,五老星,五老星认为蒙奇·D·路飞食用的橡胶果实实际上是人人果实·幻兽种·尼卡形态,1\n'
'10,和之国事件,和之国事件是蒙奇·D·路飞击败原“四皇”之一的“百兽”凯多的事件,1\n'
'19,尼卡,尼卡是五老星认为蒙奇·D·路飞食用的人人果实·幻兽种的形态,1\n'
'9,恶魔果实,恶魔果实是一种神秘的果实,食用后可以获得超人能力,但会失去游泳的能力,1\n'
'11,百兽凯多,百兽凯多是原“四皇”之一,被蒙奇·D·路飞在和之国事件中击败,1\n'
'\n'
'\n'
'-----Relationships-----\n'
'human_readable_id,source,target,description,rank\n'
'0,蒙奇·D·路飞,草帽一伙,蒙奇·D·路飞是草帽一伙的船长和创立者,30\n'
'7,蒙奇·D·路飞,东海,蒙奇·D·路飞的出身地是东海,16\n'
'1,蒙奇·D·路飞,香克斯,蒙奇·D·路飞为了实现与香克斯的约定而出海,13\n'
'6,蒙奇·D·路飞,香波地群岛,蒙奇·D·路飞是“极恶的世代”中登陆香波地群岛的11位超新星之一,13\n'
'9,蒙奇·D·路飞,极恶的世代,蒙奇·D·路飞是“极恶的世代”中登陆香波地群岛的11位超新星之一,13\n'
'2,蒙奇·D·路飞,ONE PIECE,蒙奇·D·路飞为了寻找传说中的大秘宝ONE PIECE而扬帆起航,12\n'
'8,蒙奇·D·路飞,五老星,五老星认为蒙奇·D·路飞食用的橡胶果实实际上是人人果实·幻兽种·尼卡形态,12\n'
'4,蒙奇·D·路飞,和之国事件,蒙奇·D·路飞在和之国事件中击败了百兽凯多,12\n'
'10,蒙奇·D·路飞,尼卡,五老星认为蒙奇·D·路飞食用的橡胶果实实际上是人人果实·幻兽种·尼卡形态,12\n'
'3,蒙奇·D·路飞,恶魔果实,蒙奇·D·路飞因误食恶魔果实而成为了橡皮人,12\n'
'5,蒙奇·D·路飞,百兽凯多,蒙奇·D·路飞在和之国事件中击败了百兽凯多,12\n'

接着LLM会使用community_report.txt中的prompt并把context_string作为输入,对社区按照level进行自下而上的总结,使用的默认prompt中文翻译如下:

你是一个人工智能助手,帮助人类分析员进行一般的信息发现。信息发现是识别和评估与某些实体(例如,组织和个人)相关的相关信息的过程。

# 目标
在给定属于社区的实体列表及其关系和可选的相关声明的情况下,编写社区的全面报告。报告将用于通知决策者有关社区及其潜在影响的信息。报告内容包括社区关键实体的概述、他们的法律合规性、技术能力、声誉和值得注意的声明。

# 报告结构

报告应包括以下部分:

- 标题:代表其关键实体的社区名称——标题应简短但具体。尽可能在标题中包括具有代表性的命名实体。
- 摘要:对社区整体结构、其实体之间的关系以及与其实体相关的重大信息的执行摘要。
- 影响严重性评分:一个介于0-10之间的浮动评分,表示社区内实体所构成的影响的严重程度。影响是社区的重要性评分。
- 评分解释:用一句话解释影响严重性评分。
- 详细发现:关于社区的5-10个关键见解的列表。每个见解应有一个简短的摘要,后跟根据以下基础规则进行的多段解释性文本。要全面。

返回输出为格式良好的JSON格式的字符串,格式如下:
```json
{
"title": <report_title>,
"summary": <executive_summary>,
"rating": <impact_severity_rating>,
"rating_explanation": <rating_explanation>,
"findings": [
{
"summary":<insight_1_summary>,
"explanation": <insight_1_explanation>
},
{
"summary":<insight_2_summary>,
"explanation": <insight_2_explanation>
}
]
}
```

# 基础规则

支持数据的点应列出其数据引用,如下所示:

“这是一个由多个数据引用支持的示例句子[数据: <dataset name> (记录ID); <dataset name> (记录ID)]。”

在单个引用中不要列出超过5个记录ID。相反,列出最相关的前5个记录ID,并加上“+更多”以表示还有更多。

例如:
“Person X是Company Y的所有者,并且受到许多不当行为指控[数据: 报告 (1), 实体 (5, 7); 关系 (23); 声明 (7, 2, 34, 64, 46, +更多)]。”

其中1, 5, 7, 23, 2, 34, 46和64代表相关数据记录的ID(而不是索引)。

不要包括没有提供支持证据的信息。

# 示例输入
-----------
文本:

实体

id,entity,description
5,VERDANT OASIS PLAZA,绿洲广场是团结游行的地点
6,HARMONY ASSEMBLY,和谐集会是一个在绿洲广场举行游行的组织

关系

id,source,target,description
37,VERDANT OASIS PLAZA,UNITY MARCH,绿洲广场是团结游行的地点
38,VERDANT OASIS PLAZA,HARMONY ASSEMBLY,和谐集会在绿洲广场举行游行
39,VERDANT OASIS PLAZA,UNITY MARCH,团结游行正在绿洲广场进行
40,VERDANT OASIS PLAZA,TRIBUNE SPOTLIGHT,论坛焦点正在报道绿洲广场上的团结游行
41,VERDANT OASIS PLAZA,BAILEY ASADI,Bailey Asadi在绿洲广场上就游行发表演讲
43,HARMONY ASSEMBLY,UNITY MARCH,和谐集会正在组织团结游行

输出:
```json
{
"title": "绿洲广场和团结游行",
"summary": "社区围绕绿洲广场展开,该广场是团结游行的地点。广场与和谐集会、团结游行和论坛焦点都有关系,这些都与游行事件有关。",
"rating": 5.0,
"rating_explanation": "由于团结游行期间可能发生的骚乱或冲突,影响严重性评分为中等。",
"findings": [
{
"summary": "绿洲广场作为中心地点",
"explanation": "绿洲广场是该社区的中心实体,作为团结游行的地点。该广场是所有其他实体的共同联系点,表明其在社区中的重要性。广场与游行的关联可能会导致如公共秩序问题或冲突等问题,具体取决于游行的性质和它引起的反应。[数据: 实体 (5), 关系 (37, 38, 39, 40, 41,+更多)]"
},
{
"summary": "和谐集会在社区中的角色",
"explanation": "和谐集会是社区中的另一个关键实体,他们在绿洲广场组织游行。和谐集会的性质和他们的游行可能是潜在的威胁来源,这取决于他们的目标和引起的反应。和谐集会和广场之间的关系对于理解该社区的动态至关重要。[数据: 实体(6), 关系 (38, 43)]"
},
{
"summary": "团结游行作为重要事件",
"explanation": "团结游行是一个在绿洲广场上发生的重要事件。该事件是社区动态的关键因素,具体取决于游行的性质和它引起的反应,可能是潜在的威胁来源。游行和广场之间的关系对于理解社区的动态至关重要。[数据: 关系 (39)]"
},
{
"summary": "论坛焦点的作用",
"explanation": "论坛焦点正在报道在绿洲广场上举行的团结游行。这表明该事件吸引了媒体的关注,可能会放大其对社区的影响。论坛焦点的作用可能在塑造公众对事件和相关实体的看法方面具有重要意义。[数据: 关系 (40)]"
}
]
}
```

# 真实数据

使用以下文本作为答案的依据。不要在答案中编造任何内容。

文本:
{input_text}

报告应包括以下部分:

- 标题:代表其关键实体的社区名称——标题应简短但具体。尽可能在标题中包括具有代表性的命名实体。
- 摘要:对社区整体结构、其实体之间的关系以及与其实体相关的重大信息的执行摘要。
- 影响严重性评分:一个介于0-10之间的浮动评分,表示社区内实体所构成的影响的严重程度。影响是社区的重要性评分。
- 评分解释:用一句话解释影响严重性评分。
- 详细发现:关于社区的5-10个关键见解的列表。每个见解应有一个简短的摘要,后跟根据以下基础规则进行的多段解释性文本。要全面。

返回输出为格式良好的JSON格式的字符串,格式如下:
```json
{
"title": <report_title>,
"summary": <executive_summary>,
"rating": <impact_severity_rating>,
"rating_explanation": <rating_explanation>,
"findings": [
{
"summary":<insight_1_summary>,
"explanation": <insight_1_explanation>
},
{
"summary":<insight_2_summary>,
"explanation": <insight_2_explanation>
}
]
}
```

# 基础规则

支持数据的点应列出其数据引用,如下所示:

“这是一个由多个数据引用支持的示例句子[数据: <dataset name> (记录ID); <dataset name> (记录ID)]。”

在单个引用中不要列出超过5个记录ID。相反,列出最相关的前5个记录ID,并加上“+更多”以表示还有更多。

例如:
“Person X是Company Y的所有者,并且受到许多不当行为指控[数据: 报告 (1), 实体 (5, 7); 关系 (23); 声明 (7, 2, 34, 64, 46, +更多)]。”

其中1, 5, 7, 23, 2, 34, 46和64代表相关数据记录的ID(而不是索引)。

不要包括没有提供支持证据的信息。

输出:

我们看下某个社区的报告内容:

{'findings': [{'explanation': '蒙奇·D·路飞是草帽一伙的船长和创立者,他的梦想是成为海贼王。他的出身地是东海,并且为了实现与香克斯的约定而出海。他还因误食恶魔果实而成为了橡皮人,这使他获得了超人能力但失去了游泳的能力 '
'[Data: Entities (2, 9); Relationships (0, 7, 1, '
'3)].',
'summary': '蒙奇·D·路飞的核心地位'},
{'explanation': '和之国事件是蒙奇·D·路飞击败原“四皇”之一的百兽凯多的事件。这一事件标志着他在海贼世界中的地位进一步提升,并对世界格局产生了深远影响 '
'[Data: Entities (10, 11); Relationships (4, '
'5)].',
'summary': '和之国事件的重要性'},
{'explanation': '五老星认为蒙奇·D·路飞食用的橡胶果实实际上是人人果实·幻兽种·尼卡形态。这一观点揭示了蒙奇·D·路飞的能力可能比之前认为的更为强大和神秘 '
'[Data: Entities (17, 19); Relationships (8, '
'10)].',
'summary': '五老星的观点'},
{'explanation': '恶魔果实是一种神秘的果实,食用后可以获得超人能力,但会失去游泳的能力。蒙奇·D·路飞因误食恶魔果实而成为了橡皮人,这使他在战斗中具有独特的优势 '
'[Data: Entities (9); Relationships (3)].',
'summary': '恶魔果实的影响'},
{'explanation': '草帽一伙是由蒙奇·D·路飞创立的海贼团体,他们在海贼世界中扮演着重要角色。蒙奇·D·路飞作为船长,带领着这支团队在寻找传说中的大秘宝ONE '
'IECE的过程中经历了许多冒险 [Data: Entities (2, 20); '
'Relationships (0, 2)].',
'summary': '草帽一伙的角色'},
{'explanation': '蒙奇·D·路飞是“极恶的世代”中登陆香波地群岛的11位超新星之一。这一身份使他在海贼世界中备受关注,并进一步提升了他的影响力 '
'[Data: Relationships (6, 9)].',
'summary': '极恶的世代'}],
'rating': 8.5,
'rating_explanation': '该社区的影响力很高,因为蒙奇·D·路飞在和之国事件中的胜利对整个世界格局产生了重大影响。',
'summary': '该社区围绕着蒙奇·D·路飞展开,他是草帽一伙的船长,梦想成为海贼王。蒙奇·D·路飞与多个实体有着紧密的联系,包括和之国事件、五老星、百兽凯多等。和之国事件是他击败原“四皇”之一的百兽凯多的重要事件。五老星认为他食用的橡胶果实实际上是人人果实·幻兽种·尼卡形态。',
'title': '蒙奇·D·路飞与和之国事件'}

这份报告包含了社区的总体title、summary和发现等等,这个过程也是最耗费token的。

12. create_final_text_units

这个workflow很简单,就是把对应的chunk和这个chunk有的document_ids, entity_ids, relationship_ids 做关联,成一张表

  1. id: 表示每条记录的唯一标识符。
  2. text: 包含文本内容的列。
  3. n_tokens: 表示文本内容中包含的标记(token)的数量。
  4. document_ids: 包含一个或多个文档标识符的列,表示该记录与哪些文档相关联。
  5. entity_ids: 包含一个或多个实体标识符的列,表示该记录中提到的实体。
  6. relationship_ids: 包含一个或多个关系标识符的列,表示该记录中涉及到的关系。

13. create_base_documents

这个流程也很简单,主要是建立document和text_unit的对应关系表

14. create_final_documents

这个流程完成的工作基本和create_base_documents一致,只是把text_units列名换成了text_unit_ids而已

总结

当GraphRAG完成索引过程后,它默认会将构建知识图谱所需的所有数据持久化。这些数据被存储在输出目录中,并采用Parquet文件格式。Parquet是一种列式压缩存储格式,专为高效的数据存储和分析而设计。你可以将其视为DataFrame的一种持久化方式。

在查询阶段,这些Parquet文件会被加载到内存和向量数据库中。这样做的好处在于,我们可以直接从内存和数据库中检索信息,而无需再次从原始数据源抽取和处理数据。这大大提高了查询的效率和速度。

由于parquet是一种底层文件格式,我们无法用来直观的了解与观察上面构建的知识图谱索引的细节,有什么办法可以做更直观的可视化、分析与检索呢?

由于parquet文件可以很简单的通过pandas库读取成DataFrame表,所以在了解其结构后,就可以通过Cypher语句导入成Neo4j图数据库中的节点与关系。在Github上已经有人完成这样的工作:https://github.com/tomasonjo/blogs/blob/master/msft_graphrag/ms_graphrag_import.ipynb。你如果嫌麻烦,也可以把parquet转成csv格式进行查看,代码也非常简单,不到20行左右,感兴趣的可以评论区留言。下图是抽取的Entity的Neo4j展示:

基于GraphRAG生成的数据导入到Neo4j之后,我们完全可以不再依赖于GraphRAG项目自带的Query功能,可以结合自己的项目需求在自己的Neo4j图数据库上定义自己的RAG应用检索与生成器,从而带来极大的灵活性。






欢迎光临 链载Ai (https://www.lianzai.com/) Powered by Discuz! X3.5