最近,围绕着利用 LLM(Language Model)和知识图谱(KG,Knowledge Graphs)构建RAG(Retrieval Augmented Generation)流程引起了很多关注。
在本文中,让我们通过利用 LlamaIndex 和 NebulaGraph 为费城费城人队(Philadelphia Phillies)构建一个RAG流程,深入探讨知识图谱。
文章目录
- 技术交流
- 用通俗易懂的方式讲解系列
- 应用案例
- 知识图谱(KG)
- 主要组成部分
- 三元组
- NebulaGraph
- 安装
- 详细实现
- 第一步:安装和配置
- 第二步:连接到NebulaGraph并设置新空间
- 第三步:加载数据并创建KG索引
- 第四步:运行查询来探索 NebulaGraph
- 第五步:探索七种查询方法
- 查询方法 1:基于知识图谱向量的实体检索
- 查询方法 2:基于关键词的知识图谱实体检索
- 查询方法 3:混合实体检索
- 方法 4:原始向量索引检索
- 查询方法 5:自定义组合查询引擎(知识图谱检索器和向量索引检索器的组合)
- 查询方法 6:KnowledgeGraphQueryEngine
- 查询方法 7:KnowledgeGraphRAGRetriever
- 总结
技术交流
技术要学会分享、交流,不建议闭门造车。一个人走的很快、一堆人可以走的更远。
建立了大模型技术交流群, 大模型学习资料、数据代码、技术交流提升, 均可加知识星球交流群获取,群友已超过2000人,添加时切记的备注方式为:来源+兴趣方向,方便找到志同道合的朋友。
方式①、微信搜索公众号:机器学习社区,后台回复:技术交流
方式②、添加微信号:mlc2060,备注:技术交流
用通俗易懂的方式讲解系列
- 用通俗易懂的方式讲解:不用再找了,这是大模型最全的面试题库
- 用通俗易懂的方式讲解:这是我见过的最适合大模型小白的 PyTorch 中文课程
- 用通俗易懂的方式讲解:一文讲透最热的大模型开发框架 LangChain
- 用通俗易懂的方式讲解:基于 LangChain + ChatGLM搭建知识本地库
- 用通俗易懂的方式讲解:基于大模型的知识问答系统全面总结
- 用通俗易懂的方式讲解:ChatGLM3 基础模型多轮对话微调)
- 用通俗易懂的方式讲解:最火的大模型训练框架 DeepSpeed 详解来了
- 用通俗易懂的方式讲解:这应该是最全的大模型训练与微调关键技术梳理
- 用通俗易懂的方式讲解:Stable Diffusion 微调及推理优化实践指南
- 用通俗易懂的方式讲解:大模型训练过程概述
- 用通俗易懂的方式讲解:专补大模型短板的RAG
- 用通俗易懂的方式讲解:大模型LLM Agent在 Text2SQL 应用上的实践
- 用通俗易懂的方式讲解:大模型 LLM RAG在 Text2SQL 上的应用实践
- 用通俗易懂的方式讲解:大模型微调方法总结
- 用通俗易懂的方式讲解:涨知识了,这篇大模型 LangChain 框架与使用示例太棒了
- 用通俗易懂的方式讲解:掌握大模型这些优化技术,优雅地进行大模型的训练和推理!
应用案例
我们将使用知识图谱,特别是开源的NebulaGraph,来查询位于费城的大联盟棒球队费城人队的信息。我整个家庭都是费城人队的铁杆球迷!
架构图如下所示:
对于那些已经熟悉知识图谱和NebulaGraph的人,可以跳过到“详细实现”部分。对于那些对NebulaGraph新手的人,请继续阅读。
知识图谱(KG)
知识图谱是一个使用图结构化的数据模型或拓扑来整合数据的知识库。它是表示关于现实世界实体及其相互关系的信息的一种方式。知识图谱通常用于驱动搜索引擎、推荐系统、社交网络等。
主要组成部分
知识图谱通常由两个主要组成部分组成:
顶点/节点:表示知识领域中的实体或对象。每个节点对应一个唯一的实体,并通过唯一标识符进行标识。例如,在一个关于费城人队的知识图谱中,节点可以具有“费城人队”和“大联盟棒球”等值。
边:表示两个节点之间的关系。例如,一个边“参与比赛”可能连接“费城人队”的节点和“大联盟棒球”的节点。
三元组
三元组是图中的基本数据单元。它由三个部分组成:
- 主体:三元组所涉及的节点。
- 客体:关系指向的节点。
- 谓词:主体和客体之间的关系。
在下面的三元组示例中,“费城人队”是主体,“参与比赛”是谓词,“大联盟棒球”是客体。
(费城人队)- [参与比赛]->(大联盟棒球)
知识图谱数据库可以通过存储三元组来高效存储和查询复杂的图数据。
Cypher
Cypher是一种由知识图谱支持的声明性图查询语言。通过Cypher,我们告诉知识图谱我们想要什么,但不告诉它如何做到。这使得Cypher查询更易读和易于维护。Cypher易于学习、使用,并且足够表达复杂的图查询。
下面是一个简单Cypher查询的例子:
%%ngql
MATCH (p:`entity`)-[e:relationship]->(m:`entity`)
WHERE p.`entity`.`name` == 'Philadelphia Phillies'
RETURN p, e, m;
此查询将匹配与费城人队相关的所有实体。
NebulaGraph
NebulaGraph 是市场上最好的知识图谱数据库之一。它是开源的、分布式的,并且能够处理具有亿万边和顶点的大规模图,延迟在毫秒级别。大型公司广泛将其用于各种应用,包括社交媒体、推荐系统、欺诈检测等。
安装
对于我们的费城人队RAG流程,我们需要在本地安装NebulaGraph。通过Docker Desktop是安装NebulaGraph的最快方式之一。详细的说明可以在NebulaGraph的文档中找到。
对于那些对NebulaGraph新手的人,请查阅NebulaGraph的详细文档以了解更多信息。
详细实现
第一步:安装和配置
除了LlamaIndex,我们还需要安装一些库:
- ipython-ngql:一个Python包,增强了您从Jupyter笔记本或iPython连接到NebulaGraph的能力。
- nebula3-python:用于连接和管理NebulaGraph数据库的Python客户端。
- pyvis:一个使您能够使用最少的Python代码快速生成可视化网络图的库。
- networkx:用于研究图和网络的Python库。
- youtube_transcript_api:一个Python API,让您可以获取YouTube视频的剧本/字幕。
%pip install llama_index==0.8.33 ipython-ngql nebula3-python pyvis networkx youtube_transcript_api
让我们还设置我们的OpenAI API密钥并配置我们应用程序的日志记录:
import os
import logging
import sys
os.environ["OPENAI_API_KEY"] = "sk-####################"
logging.basicConfig(stream=sys.stdout, level=logging.INFO)
第二步:连接到NebulaGraph并设置新空间
假设您已经在本地安装了NebulaGraph;现在我们可以从您的Jupyter笔记本连接到它
按照下面的代码片段进行操作:
- 连接到您的本地NebulaGraph(默认密码为nebula)。
- 创建一个名为phillies_rag的新空间。
- 在新空间中创建标签、边和标签索引。
os.environ["GRAPHD_HOST"] = "127.0.0.1"
os.environ["NEBULA_USER"] = "root"
os.environ["NEBULA_PASSWORD"] = "nebula"
os.environ["NEBULA_ADDRESS"] = "127.0.0.1:9669"
%reload_ext ngql
connection_string = f"--address {os.environ['GRAPHD_HOST']} --port 9669 --user root --password {os.environ['NEBULA_PASSWORD']}"
%ngql {connection_string}
%ngql CREATE SPACE IF NOT EXISTS phillies_rag(vid_type=FIXED_STRING(256), partition_num=1, replica_factor=1);
%%ngql
USE phillies_rag;
CREATE TAG IF NOT EXISTS entity(name string);
CREATE EDGE IF NOT EXISTS relationship(relationship string);
%ngql CREATE TAG INDEX IF NOT EXISTS entity_index ON entity(name(256));
创建了 NebulaGraph 中的新空间后,让我们构建我们的 NebulaGraphStore。请参阅下面的代码片段:
from llama_index.storage.storage_context import StorageContext
from llama_index.graph_stores import NebulaGraphStore
space_name = "phillies_rag"
edge_types, rel_prop_names = ["relationship"], ["relationship"]
tags = ["entity"]
graph_store = NebulaGraphStore(
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
)
storage_context = StorageContext.from_defaults(graph_store=graph_store)
第三步:加载数据并创建KG索引
是时候加载我们的数据了。我们的源数据来自费城人队的维基百科页面以及一个关于Trea Turner在2023年8月收到起立鼓掌的YouTube视频。
为了节省时间和成本,我们首先检查我们的本地storage_context以加载KG索引。如果存在索引,则加载索引。如果不存在(例如第一次访问应用程序时),我们需要加载两个源文档,然后从中构建KG索引,并将文档存储、索引存储和向量存储持久化到项目根目录下的本地storage_graph目录中。
from llama_index import (
LLMPredictor,
ServiceContext,
KnowledgeGraphIndex,
)
from llama_index.graph_stores import SimpleGraphStore
from llama_index import download_loader
from llama_index.llms import OpenAI
# 定义LLM
llm = OpenAI(temperature=0.1, model="gpt-3.5-turbo")
service_context = ServiceContext.from_defaults(llm=llm, chunk_size=512)
from llama_index import load_index_from_storage
from llama_hub.youtube_transcript import YoutubeTranscriptReader
try:
storage_context = StorageContext.from_defaults(persist_dir='./storage_graph', graph_store=graph_store)
kg_index = load_index_from_storage(
storage_context=storage_context,
service_context=service_context,
max_triplets_per_chunk=15,
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
verbose=True,
)
index_loaded = True
except:
index_loaded = False
if not index_loaded:
WikipediaReader = download_loader("WikipediaReader")
loader = WikipediaReader()
wiki_documents = loader.load_data(pages=['Philadelphia Phillies'], auto_suggest=False)
print(f'Loaded {len(wiki_documents)} documents')
youtube_loader = YoutubeTranscriptReader()
youtube_documents = youtube_loader.load_data(ytlinks=['https://www.youtube.com/watch?v=k-HTQ8T7oVw'])
print(f'Loaded {len(youtube_documents)} YouTube documents')
kg_index = KnowledgeGraphIndex.from_documents(
documents=wiki_documents + youtube_documents,
storage_context=storage_context,
max_triplets_per_chunk=15,
service_context=service_context,
space_name=space_name,
edge_types=edge_types,
rel_prop_names=rel_prop_names,
tags=tags,
include_embeddings=True,
)
kg_index.storage_context.persist(persist_dir='./storage_graph')
在KG索引构建中需要指出的几点:
-
max_triplets_per_chunk:在一个块中提取的三元组的最大数量。让我们给它15,这样就可以覆盖大多数内容,如果不是全部的话。
-
include_embeddings:指定在创建知识图谱索引时是否应包含数据的嵌入。嵌入是一种将文本数据表示为向量的方式,它们捕捉了数据的语义含义。它们通常用于使模型能够理解不同文本片段之间的语义相似性。当include_embeddings=True时,KnowledgeGraphIndex将在索引中包含这些嵌入。当您希望在知识图谱上执行语义搜索时,这可能会很有用,因为嵌入可以用来查找与查询在语义上相似的节点和边。
第四步:运行查询来探索 NebulaGraph
现在让我们运行一个简单的查询。
查询一些关于费城人队(Philadelphia Phillies)的事实。
query_engine = kg_index.as_query_engine()
response = query_engine.query("Tell me about some of the facts of Philadelphia Phillies.")
display(Markdown(f"{response}"))
我们得到了以下响应,这是费城人队维基百科页面的一个很好的摘要!
通过Cypher查询,我们得到以下结果:
%%ngql
MATCH (p:`entity`)-[e:relationship]->(m:`entity`)
WHERE p.`entity`.`name` == 'Philadelphia Phillies'
RETURN p, e, m;
该查询将匹配与费城人队相关的所有实体。查询结果将是与费城人队相关的所有实体的列表,以及它们与费城人队的关系,当然还包括费城人队实体本身。让我们在我们的Jupyter笔记本中执行这个Cypher查询。
接下来,运行ng_draw命令,这是ipython-ngql包中的一个命令,它可以将NebulaGraph查询结果渲染成一个单个的HTML文件;我们得到了以下图形。以“Philadelphia Phillies”节点为中心,它展开了其他九个节点,每个节点代表上面Cypher查询结果中的一行项目。将每个节点连接到中心节点的是边,表示两个节点之间的关系。
第五步:探索七种查询方法
基于我们的知识图谱索引,让我们使用不同的方法查询我们的知识图谱,并观察它们的结果。
查询方法 1:基于知识图谱向量的实体检索
query_engine = kg_index.as_query_engine()
这种方法使用向量相似性查找知识图谱实体,提取相关的文本块,并可选择探索关系。这是LlamaIndex的知识图谱查询引擎的默认方式,它基于索引构建。它非常简单、开箱即用,不需要额外的参数。
查询方法 2:基于关键词的知识图谱实体检索
kg_keyword_query_engine = kg_index.as_query_engine(
include_text=False,
retriever_mode="keyword",
response_mode="tree_summarize",
)
这个查询引擎使用查询中的关键词来检索相关的知识图谱实体,提取相关的文本块,并可选择探索关系以获取更多上下文。它通过指定参数retriever_mode="keyword"来配置使用基于关键词的检索器。
include_text=False:查询引擎仅使用原始三元组进行查询;不会在响应中包含来自相应节点的文本。
response_mode=“tree_summarize”:响应将是知识图谱树结构的摘要。树将以递归方式构建,查询位于根节点,最相关的答案位于叶节点。tree_summarize响应模式对于摘要任务非常有用,比如提供一个主题的高级概述或回答需要全面响应的问题。它还可以生成更复杂的响应,比如提供为什么某件事情是真实的列表,或者解释涉及的步骤。
查询方法 3:混合实体检索
kg_hybrid_query_engine = kg_index.as_query_engine(
include_text=True,
response_mode="tree_summarize",
embedding_mode="hybrid",
similarity_top_k=3,
explore_global_knowledge=True,
)
通过指定embedding_mode=“hybrid”,这个查询引擎配置为使用混合方法——基于向量的实体检索和基于关键词的实体检索来检索知识图谱中的信息,带有去重。混合实体检索使用关键词来查找相关的三元组。然后,它还使用基于向量的实体检索来找到基于语义相似性的相似三元组。因此,实质上,混合模式结合了关键词搜索和语义搜索,利用了两种方法的优势,以提高搜索结果的准确性和相关性。
include_text=True:查询引擎将在响应中使用来自相应节点的文本。
similarity_top_k=3:它将基于嵌入检索前三个最相似的结果。根据您的用例,可以随意调整此值。
explore_global_knowledge=True:指定查询引擎在检索信息时是否考虑知识图谱的全局上下文。当explore_global_knowledge=True时,查询引擎不会限制其搜索到局部上下文(即节点的直接邻居),而是会考虑知识图谱的更广泛的全局上下文。当您希望检索与查询不直接相关但在知识图谱的更大上下文中相关的信息时,这可能会很有用。
关键词实体检索和混合实体检索之间的主要区别在于我们从知识图谱中检索信息的方法:关键词实体检索使用基于关键词的方法,而混合实体检索使用混合方法,结合了嵌入和关键词。
方法 4:原始向量索引检索
这种方法根本不涉及知识图谱。它基于向量索引。让我们首先从文档构建向量索引,然后从向量索引构建向量查询引擎。
vector_index = VectorStoreIndex.from_documents(wiki_documents + youtube_documents)
vector_query_engine = vector_index.as_query_engine()
查询方法 5:自定义组合查询引擎(知识图谱检索器和向量索引检索器的组合)
LlamaIndex已经创建了一个CustomRetriever。您可以在下面的代码中看到它的样子。它执行了知识图谱搜索和向量搜索。默认模式OR保证了两者搜索的联合,导致搜索结果包含了最佳的两个世界,并进行了去重:
- 来自知识图谱搜索的微妙细节(KGTableRetriever)。
- 来自向量索引搜索的语义相似度搜索细节(VectorIndexRetriever)。
from llama_index import QueryBundle
from llama_index.schema import NodeWithScore
from llama_index.retrievers import BaseRetriever, VectorIndexRetriever, KGTableRetriever
from typing import List
class CustomRetriever(BaseRetriever):
def __init__(
self,
vector_retriever: VectorIndexRetriever,
kg_retriever: KGTableRetriever,
mode: str = "OR",
) -> None:
"""Init params."""
self._vector_retriever = vector_retriever
self._kg_retriever = kg_retriever
if mode not in ("AND", "OR"):
raise ValueError("Invalid mode.")
self._mode = mode
def _retrieve(self, query_bundle: QueryBundle) -> List[NodeWithScore]:
"""Retrieve nodes given query."""
vector_nodes = self._vector_retriever.retrieve(query_bundle)
kg_nodes = self._kg_retriever.retrieve(query_bundle)
vector_ids = {n.node.node_id for n in vector_nodes}
kg_ids = {n.node.node_id for n in kg_nodes}
combined_dict = {n.node.node_id: n for n in vector_nodes}
combined_dict.update({n.node.node_id: n for n in kg_nodes})
if self._mode == "AND":
retrieve_ids = vector_ids.intersection(kg_ids)
else:
retrieve_ids = vector_ids.union(kg_ids)
retrieve_nodes = [combined_dict[rid] for rid in retrieve_ids]
return retrieve_nodes
from llama_index import get_response_synthesizer
from llama_index.query_engine import RetrieverQueryEngine
from llama_index.retrievers import VectorIndexRetriever, KGTableRetriever
# create custom retriever
vector_retriever = VectorIndexRetriever(index=vector_index)
kg_retriever = KGTableRetriever(
index=kg_index, retriever_mode="keyword", include_text=False
)
custom_retriever = CustomRetriever(vector_retriever, kg_retriever)
# create response synthesizer
response_synthesizer = get_response_synthesizer(
service_context=service_context,
response_mode="tree_summarize",
)
custom_query_engine = RetrieverQueryEngine(
retriever=custom_retriever,
response_synthesizer=response_synthesizer,
)
查询方法 6:KnowledgeGraphQueryEngine
到目前为止,我们已经探索了使用索引构建的不同查询引擎。现在,让我们看看另一个由LlamaIndex精心制作的知识图谱查询引擎——KnowledgeGraphQueryEngine。请参阅下面的代码片段:
query_engine = KnowledgeGraphQueryEngine(
storage_context=storage_context,
service_context=service_context,
llm=llm,
verbose=True,
)
KnowledgeGraphQueryEngine是一个查询引擎,允许我们使用自然语言查询知识图谱。它使用LLM生成Cypher查询,然后在知识图谱上执行这些查询。这使得可以在不学习Cypher或任何其他查询语言的情况下查询知识图谱。
KnowledgeGraphQueryEngine接受storage_context、service_context和llm,并构建了一个具有NebulaGraphStore作为storage_context.graph_store的知识图谱查询引擎。
查询方法 7:KnowledgeGraphRAGRetriever
KnowledgeGraphRAGRetriever是LlamaIndex中的RetrieverQueryEngine,它在知识图谱上执行Graph RAG查询。它以问题或任务作为输入,并执行以下步骤:
- 使用关键词提取或嵌入搜索相关实体在知识图谱中。
- 从知识图谱中获取这些实体的子图,默认深度为2。
- 基于子图构建上下文。
- 然后,下游任务(例如LLM)可以使用上下文生成响应。请参阅下面的代码片段来构建一个KnowledgeGraphRAGRetriever:
graph_rag_retriever = KnowledgeGraphRAGRetriever(
storage_context=storage_context,
service_context=service_context,
llm=llm,
verbose=True,
)
kg_rag_query_engine = RetrieverQueryEngine.from_args(
graph_rag_retriever, service_context=service_context
)
现在我们对所有七种查询方法有了很好的了解。
总结
在本文中,我们探讨了知识图谱,特别是 NebulaGraph。我们使用 LlamaIndex、NebulaGraph 和 GPT-3.5为费城费城队建立了一个 RAG 流水线。
我们探索了七种查询引擎,研究了它们的内部工作原理。我们比较了每种查询引擎的优缺点,并更好地了解了每种查询引擎设计的用例。希望您会觉得本文有帮助。