微信扫码
添加专属顾问
我要投稿
探索知识图谱生成的新突破,如何将大型语言模型与图谱技术结合以提升特定领域的应用性能。 核心内容: 1. 大型语言模型在特定领域应用的局限性 2. 知识图谱与检索增强生成技术的结合应用 3. LlamaIndex与Phi-3在知识图谱生成中的实验与优化
大型语言模型(LLM)越来越多地应用于医疗诊断、IT 告警分流和分类、网络运维和产品支持等领域。然而,这些通用预训练模型在这些特定领域中的表现往往不尽如人意,并且经常出现幻觉问题。检索增强生成(Retrieval-Augmented Generation, RAG)是一种流行的有效方法,可以将 LLM 应用到特定领域。但是,这种方法无法理解或考虑在块检索期间嵌入在向量化文档中的知识。
相反,知识图谱是一种知识库,它使用图结构数据模型来表示数据。根据维基百科的定义,知识图谱存储实体(如对象、事件、情况或概念)之间相互关联的描述,同时编码这些实体背后的语义或关系。随着 LLM 生成能力的不断提高,流行的 AI 框架库 LlamaIndex 利用这一能力自动从非结构化文档中生成此类知识图谱,并提供对这些图谱进行高效查询的支持。
在本文中,我们将探讨如何使用 PropertyGraphIndex
模块(该模块最近取代了已弃用的 KnowledgeGraphIndex
模块,并基本保持了原有的使用方式)生成知识图谱以进行问答(QA)。然后,我们将定制此受知识图谱启发的 QA 系统,以获得最佳的块路径和生成的节点详细程度,并使用五种不同的本地 LLM 进行实验,以获得最佳性能。考虑到资源受限设备的应用,我们用于比较实验的 LLM 将包括 Mistral 7B、Llama-3 8B、Phi-3 3.8B、TinyLlama 1.1B 和 Gemma-2 9B 的量化版本。
1.0 技术栈和环境设置
1.1 PropertyGraphIndex
:工作原理
2.0 QA 系统实现
3.0 定制和测试
3.1 参数 max_paths_per_chunk
的影响
3.2 参数 include_text
的影响
3.3 LLM 的影响
4.0 总结
本实验在配备 8GB 内存的 MacBook Air M1 上进行,运行 MacOS Ventura。使用的 Python 版本为 3.11.5。
首先,让我们创建一个虚拟环境来管理这个项目。要创建和激活环境,请运行以下命令:
python3.11 -m venv kg_qa
source kg_qa/bin/activate
LlamaIndex
是一个用于构建 LLM 应用程序的编排框架。与 LangChain 类似,LlamaIndex 提供了用于数据摄取、索引和查询的工具,使其成为生成式 AI 的综合解决方案。该库正在经历许多变化,以跟上该领域正在发生的巨大变化。LlamaIndex 具有 PropertyGraphIndex
模块,该模块从已弃用的 KnowledgeGraphIndex
演变而来,它借助 LLM 处理来自非结构化文本的自动知识图谱构建,并促进基于实体的查询。在之前的一篇文章中,我们发现,采用来自知识图谱和嵌入向量空间的向量相似性搜索的复合上下文,有助于极大地提高 LLM 在特定领域的生成能力:
知识图谱在 RAG Powered By Cassandra 中的影响:https://ai.gopubby.com/impact-of-a-knowledge-graph-in-rag-powered-by-cassandra-5f7442b4b355
本文的重点是寻找改进知识图谱本身的机会,无论是在生成期间还是在查询期间。
为了为我们的实验设置本地 LLM,我们将使用具有 Metal 支持的多功能 llama-cpp-python
库,该库将用于加载 LLM 的量化版本。使用量化版本有助于减少我们机器的计算资源需求。以下显示了用于安装所需库的 pip install
命令:
pip install llama-index llama-index-readers-file llama-index-embeddings-huggingface
CMAKE_ARGS="-DLLAMA_METAL=on" FORCE_CMAKE=1 pip install --upgrade --force-reinstall llama-cpp-python --no-cache-dir
PropertyGraphIndex
:工作原理使用 PropertyGraphIndex
生成的图是一个带标签的节点集合,这些节点具有属性(即元数据),并通过关系链接在一起,从而形成结构化路径 \[1]。PropertyGraphIndex
围绕以下内容提供关键编排:
• 构建图
• 查询图
LlamaIndex
中的知识图谱构建通过对每个文档块使用一个或多个 kg\_extractors 执行提取,并将实体和关系作为元数据附加到每个节点来工作。此处可以链接任意数量的提取器,并且它们将独立应用。默认情况下,采用每个块最大路径数为 10 的 SimpleLLMPathExtractor
和 ImplicitPathExtractor
。前者使用 LLM 和预设提示词提取简短语句,并解析格式为 (实体 1, 关系, 实体 2) 的单跳路径,也称为三元组。它使用以下默认提示模板:https://github.com/run-llama/llama_index/blob/4fa2d2bfd684baaf0916105850a89f7437330fb4/llama-index-core/llama_index/core/prompts/default_prompts.py#L331 "",可以使用类参数 extract_prompt
进行自定义:
DEFAULT_KG_TRIPLET_EXTRACT_TMPL = (
"Some text is provided below. Given the text, extract up to "
"{max_knowledge_triplets} "
"knowledge triplets in the form of (subject, predicate, object). Avoid stopwords.\n"
"---------------------\n"
"Example:"
"Text: Alice is Bob's mother."
"Triplets:\n(Alice, is mother of, Bob)\n"
"Text: Philz is a coffee shop founded in Berkeley in 1982.\n"
"Triplets:\n"
"(Philz, is, coffee shop)\n"
"(Philz, founded in, Berkeley)\n"
"(Philz, founded in, 1982)\n"
"---------------------\n"
"Text: {text}\n"
"Triplets:\n"
)
同样,可以通过多种方式查询知识图谱以检索节点和路径。此外,LlamaIndex 允许我们一次组合多种节点检索方法。默认情况下,使用 LLMSynonymRetriever
和 VectorContextRetriever
(如果启用了嵌入)。LLMSynonymRetriever
采用查询,并借助 LLM 生成关键字和同义词,并检索节点以及连接到这些节点的路径。它使用以下默认提示模板:https://github.com/run-llama/llama_index/blob/4fa2d2bfd684baaf0916105850a89f7437330fb4/llama-index-core/llama_index/core/indices/property_graph/sub_retrievers/llm_synonym.py#L19,可以使用类参数 synonym_prompt
进行自定义:
DEFAULT_SYNONYM_EXPAND_TEMPLATE = (
"Given some initial query, generate synonyms or related keywords up to {max_keywords} in total, "
"considering possible cases of capitalization, pluralization, common expressions, etc.\n"
"Provide all synonyms/keywords separated by '^' symbols: 'keyword1^keyword2^...'\n"
"Note, result should be in one-line, separated by '^' symbols."
"----\n"
"QUERY: {query_str}\n"
"----\n"
"KEYWORDS: "
)
VectorContextRetriever
基于其向量相似性检索节点,并提取与这些节点相关的路径。
了解了知识图谱构建的原理后,接下来我们将展示如何利用这些技术实现一个简单的问答系统。
让我们开发一个系统,该系统可以从本地目录读取 pdf 文档,生成知识图谱并查询所选问题。首先,导入所有必需的模块。
from llama_index.core import (
PropertyGraphIndex,
SimpleDirectoryReader,
Document,
StorageContext,
load_index_from_storage,
Settings,
)
from llama_index.llms.llama_cpp import LlamaCPP
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core.graph_stores import SimpleGraphStore
from llama_index.core.indices.property_graph import SimpleLLMPathExtractor
LLM 是知识图谱生成和查询的核心,接下来我们将实例化一个 LlamaCPP
对象,以从本地目录 ./models/
加载所需的语言模型。我们还将实例化选定的嵌入模型。为了允许 LlamaIndex 模块全局使用此 LLM,请将类 Settings
属性 llm
分配给此 LLM 实例和嵌入模型。否则,LlamaIndex 将默认尝试访问 OpenAI。以下代码摘录总结了这些步骤:
llm = LlamaCPP(
model_path='./models/mistral-7b-instruct-v0.3.Q2_K.gguf',
#model_path='./models/Llama-3-Instruct-8B-SPPO-Iter3-Q2_K.gguf',
#model_path='./models/Phi-3-mini-4k-instruct-q4.gguf',
#model_path='./models/tinyllama-1.1b-chat-v1.0.Q5_K_M.gguf',
#model_path='./models/gemma-2-9b-it-Q2_K.gguf',
temperature=0.1,
max_new_tokens=256,
context_window=2048,
# kwargs to pass to __init__()
model_kwargs={"n_gpu_layers": 1},
verbose=False
)
embed_model = HuggingFaceEmbedding()
Settings.llm = llm
Settings.embed_model = embed_model
Settings.chunk_size = 512
为了加载 pdf 文档,我们将使用 SimpleDirectoryReader
从目录 ./pdf
读取,并创建一个 SimpleGraphStore
的 StorageContext
实例。现在,真正的乐趣来了。LlamaIndex 使从非结构化文档生成知识图谱变得非常容易。我们只需使用加载的文档和存储上下文调用 PropertyGraphIndex.from_documents
调用,如下所示:
documents = SimpleDirectoryReader("./pdf/").load_data()
graph_store = SimpleGraphStore()
gstorage_context = StorageContext.from_defaults(graph_store=graph_store)
kg_index = PropertyGraphIndex.from_documents(
documents,
storage_context=gstorage_context,
max_triplets_per_chunk=10,
include_embeddings=True,
)
如果您加载了大型文档或许多文档,则图生成非常消耗计算资源,并且可能需要很长时间。如果您反复重新运行代码,则有一种更好的处理方法。在尝试生成图之前,让我们尝试从我们期望的目录 ./storage
加载索引文件。否则,继续执行生成步骤,并使用 gstorage_context.persist()
坚持生成后的索引(默认情况下写入目录 ./storage
),如下所示:
try:
# check if index on disk
gstorage_context = StorageContext.from_defaults(persist_dir='./storage')
kg_index = load_index_from_storage(storage_context = gstorage_context)
except FileNotFoundError:
graph_store = SimpleGraphStore()
gstorage_context = StorageContext.from_defaults(graph_store=graph_store)
documents = SimpleDirectoryReader("./pdf/").load_data()
kg_index = PropertyGraphIndex.from_documents(
documents,
storage_context=gstorage_context,
max_triplets_per_chunk=10,
include_embeddings=True,
)
# save to disk
gstorage_context.persist()
在此阶段,我们已准备好用于查询的图。与生成类似,LlamaIndex 使此查询步骤感觉像小菜一碟。从图索引创建一个查询引擎,其中涉及一个关键参数 include_text
。如果将其设置为 False
,它将返回匹配的节点作为原始三元组。查询引擎现在已准备好进行查询!
kg_keyword_query_engine = kg_index.as_query_engine(
# setting to false uses the raw triplets instead of adding the text from the corresponding nodes
include_text=True,
similarity_top_k=2,
)
query = "When should simplified routing be used on SteelHeads?"
response = kg_keyword_query_engine.query(query)
print(f'Query: {query}\nResponse: {response.response}')
代码和环境的进一步描述可在以下 GitHub 存储库中找到:
GitHub - drskennedy/qa-kgraph:使用 LlamaIndex 知识图谱查询私人文档以及…
如上所述,我们仅需几行代码即可实现完整功能。接下来,我们将探讨如何通过调整参数进一步优化响应生成。
在上一节中,我们看到只需几行代码,LlamaIndex 就可以轻松自然地从文档生成知识图谱并查询它们。根据您域的需求,有足够的定制空间。为了说明这种定制的影响,我们将首先使用一篇关于 SteelHead(一种与网络应用程序加速相关的设备)的 2 页知识库文章来代表我们的特定领域。
max_paths_per_chunk
的影响接下来,我们将探讨如何自定义知识图谱的生成过程。其中一个关键点是设置每个块的最大路径数,这可以通过 SimpleLLMPathExtractor
来实现。默认情况下,此提取器使用参数 max_paths_per_chunk
设置为 10。通过使用参数 kg_extractors
,我们可以提名我们选择的提取器列表,作为调用 PropertyGraphIndex.from_documents
的一部分。这是带有此自定义的代码摘录:
kg_extractor = SimpleLLMPathExtractor(
llm=llm,
max_paths_per_chunk=5,
num_workers=4,
)
# kg generation
kg_index = PropertyGraphIndex.from_documents(
documents,
storage_context=gstorage_context,
kg_extractors=[kg_extractor],
max_triplets_per_chunk=10,
include_embeddings=True,
)
现在让我们通过将其值从 5、7 和 10 更改来探索 max_paths_per_chunk
的影响。我们将使用以下查询进行此测试:
何时应在 SteelHeads 上使用简化的路由?
对于每个选定的最大路径值,将显示 LLM 响应以及返回以用作 LLM 上下文的第一个源节点。后一个输出是通过以下行获得的:
for node in response.source_nodes:
print(node)
最大路径数为 5 的响应
响应:当流量被重定向回 SteelHead 并且用户不想为每个 IP 地址添加静态路由时,应在 SteelHeads 上使用简化的路由。此方法从它接收的每个数据包中收集 IP 到下一跳 MAC 地址的映射,以用于寻址其自己的流量,从而覆盖所有其他内容。如果下一跳出现故障,它将在它接收到的来自/到新路由器的下一个数据包上按每个 IP 地址学习新的下一跳。
Node ID: dbbed016-e13d-4022-8a3f-cd2bf63bd951
Text: Here are some facts extracted from the provided text:
Simplified routing -> Uses -> Per ip address basis Simplified routing
-> Is -> Gathering ip to next hop mac address mapping Categories:Best
Practices, Principal Article, Networking, SteelHead
(Appliance)Solution Number:S14848Last Modified:2021-10-15Simplified
Routing (Best Practices)IssueWith...
Score: 1.000
最大路径数为 7 的响应
响应:当流量被重定向回 SteelHead 并且用户当前必须将一个路由器定义为默认网关,并将静态路由定义为其他路由器,以使流量不会被重定向回 SteelHead 时,应使用简化的路由。当使用第 2 层 WAN 或虚拟路由器时,也可以使用它,并且发送到/通过 Steelhead 的数据包可以返回到同一路由器。
Node ID: e6703787-bc9e-421d-867b-5cb1c6c85768
Text: Here are some facts extracted from the provided text: Steelhead
-> Uses -> Simplified routing Simplified routing -> Overrides ->
Static routes or gateway Simplified routing -> Uses -> Ip to next hop
mac address mapping Simplified routing -> Is -> Gathering ip to next
hop mac address mapping Simplified routing -> Overrides -> Acls on the
default...
Score: 1.000
最大路径数为 10 的响应
响应:当 SteelHead 设备安装在与客户端或服务器不同的子网中时,应使用简化的路由。在这种情况下,用户必须将一个路由器定义为默认网关,并将静态路由定义为其他路由器,以使流量不会被重定向回 SteelHead。为了避免强制用户添加这些静态路由,SteelHead 可以在发送相同目标地址的数据包时,重用接收到的 IP 地址对应的 MAC 地址。简化的路由从它接收的每个数据包中收集 IP 到下一跳 MAC 地址的映射,以用于寻址其自己的流量。
Node ID: 837a1ae7-5d95-48fa-b632-bd14b57140c3
Text: Here are some facts extracted from the provided text: Layer-2
wans -> Used by -> Simplified routing Simplified routing -> Uses -> Ip
to next hop mac address mapping Simplified routing -> Learns -> New
next hop on the next packet Simplified routing -> Overrides -> Static
routes or gateway Simplified routing -> Is -> Gathering ip to next hop
mac ...
从以上三个响应中可以看出,最大路径数为 10 时给出的响应最为准确。前两个响应错误地解释了简化路由技术的“如何”方面。因此,对于进一步的测试,我们将继续使用 10 作为每个块的最大路径数。
include_text
的影响在本节中,我们将测试查询引擎中的布尔参数 include_text
对响应生成的影响。具体来说,本次测试使用的查询语句如下:“如果您通过 SteelHead 具有非对称设置,它是否对简化的路由功能有任何影响?”该参数决定了返回源节点的内容,进而影响 LLM 的响应生成。
include_text
为 True 的响应
响应:是的,如果您通过 SteelHead 具有非对称设置,它可能会导致简化路由功能的不良结果。这是因为简化路由 (SR) 使用基于每个 IP 地址的方式来收集 IP 到下一跳 MAC 地址的映射。如果 SR 没有特定 IP 的映射,它将使用静态路由或网关。但是,在非对称路由网络中,SR 可能会导致不良结果,因为即使到达服务器的路由发生变化,客户端 SteelHead 中的代理表仍然具有该服务器的条目,从而导致缓存旧 MAC 地址。这可能会导致流量被发送到缓存的(旧)MAC 地址,而不是新的 MAC 地址,除非启用了源收集并且有来自该服务器的传入流量。
Node ID: beeb4f83-219a-4d93-8466-66e8cd375927
Text: Here are some facts extracted from the provided text: Steelhead
-> Installed in -> Subnet different than clients or servers Steelhead
-> Overrides -> Everything else Steelhead -> Learns -> New route for
10.0.0.1 Steelhead -> Overrides -> Static routes or gateway Steelhead
-> Uses -> Static routes or gateway Steelhead -> Learns -> New next
hop o...
Score: 1.000
include_text
为 False 的响应
响应:是的,在非对称设置中,SteelHead 仍将使用其简化路由功能来收集 IP 到下一跳 MAC 地址的映射。但是,由于返回流量可能采用与出站流量不同的路径,因此 SteelHead 可能没有学习到返回流量的正确下一跳,从而可能导致通信出现问题。
Node ID: c3964314-8e7b-46c0-a499-a70244222553
Text: Simplified routing -> Is -> Gathering ip to next hop mac address
mapping
Score: 1.000
Node ID: c15bd206-1024-4856-b31d-6fca769e4efb
Text: Steelhead -> Overrides -> Everything else
Score: 1.000
Node ID: d7a5129f-172e-4414-97cf-fe4c68ddde0a
Text: Steelhead -> Learns -> New route for 10.0.0.1
Score: 1.000
Node ID: bf61c62d-797a-4161-bdb2-68b469c783a7
Text: Simplified routing -> Uses -> Per ip address basis
Score: 1.000
当布尔标志为 False
时,得到了更准确的响应,这有些出乎意料。使用此设置,仅返回原始三元组,如上面的输出所示。相比之下,当标志为 True
时,返回的输出更详细,包括来自我们文档的文本。因此,我们将使用 False
继续进行后续实验。
为了测试不同模型的表现,我们利用 LlamaCPP
加载多种 GGUF 格式的本地 LLM。这些模型包括 Mistral 7B、Llama-3 8B 等,我们将分析它们对知识图谱生成和查询的影响。为了适应特定模型,我们只需将 LlamaCPP
的 model_path
更改为正确的目录路径。通过此单一更改,我们将对每个 LLM 执行以下问题:
何时应在 SteelHeads 上使用简化的路由?
在什么情况下不应在 SteelHeads 上启用简化的路由?
如果您通过 SteelHead 具有非对称设置,它是否对简化的路由功能有任何影响?
表 1 捕获了图生成时间、模型对每个查询的响应以及它们的生成时间(以秒为单位)(一些响应被截断以减少混乱)。绿色文本显示了给出最佳答案的模型。没有明显的赢家,但 Mistral 7B、Llama-3 8B 和 Phi-3 各自设法为我们的问题生成了一个最佳响应。在这三者中,Mistral 的响应生成速度最快。
为了探索这些模型在较长文档中的表现如何,我们将使用一个 10 页的 pdf 文档。对于此比较,由于 TinyLlama 和 Gemma2 模型即使在较小的文档中也表现欠佳,因此将被排除。表 2 捕获了 Mistral 7B、Llama-3 和 Phi-3 在此较大文档上的性能。Phi-3 的知识图谱生成速度最快。在响应准确性方面,Phi-3 是赢家,它为问题 Q2 和 Q3 返回了两个准确的响应。
让我们可视化使用 Mistral 和 Phi-3 生成的知识图谱,以帮助我们了解为什么它们能够生成更好的响应。图 1 描绘了借助 Mistral 7B 生成的图。实体和关系的选择大多是好的。为了解决围绕连接池的查询 Q1,此图顶部的子树具有解决它所需的节点。这为 Mistral 提供了最佳上下文,以生成此问题的最准确响应。
图 2 表示借助 Phi-3 模型生成的知识图谱。对于其连接池实体,它与 WAN 可见性模式 的概念有关系,根据文档,这并不完全准确,但在 Phi-3 的查询响应中有所体现。相反,它专门为 SteelHead 模型选择的概念生成了一个图,该图正确地捕获了文档中记录的所有因素,并将相关实体与关系 based-on 相关联。这在图 3 中单独显示,它帮助 Phi-3 为 Q3 生成了最全面的响应。
根据这些结果,很明显,本地 LLM 的选择在识别 PropertyGraphIndex
形成知识图谱所需的有意义的实体和关系方面起着关键作用。对于资源受限的机器,即使在 2 位或 4 位量化时,Phi-3 和 Mistral 7B 模型似乎也表现出色。
检索增强生成 (RAG) 是一种流行的有效方法,可以将 LLM 应用于特定领域。但是,它会受到幻觉的影响。知识图谱的使用是处理这种困境的一种尝试。LlamaIndex 利用 LLM 不断提高的能力来协助从非结构化文档生成知识图谱,并提供对这些图谱进行高效查询的支持。
在本文中,我们为此目的使用了 LlamaIndex 模块 PropertyGraphIndex
。通过不到 20 行代码,我们拥有了一个受知识图谱启发的有效问答系统,可以查询我们的本地文档。我们测试了最大路径数、源节点详细程度以及 LLM 本身的选择对响应生成性能的影响。借助 LlamaCPP
,我们可以轻松地跨 5 种不同的 LLM 进行测试,并发现 Phi-3 和 Mistral 7B 在促进 PropertyGraphIndex
为我们的特定领域生成具有有意义的实体和关系的知识图谱方面表现最佳。
53AI,企业落地大模型首选服务商
产品:场景落地咨询+大模型应用平台+行业解决方案
承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2025-03-05
低成本+高性能+超灵活!Deepseek 671B+Milvus重新定义知识库搭建
2025-03-04
一键发布知识图谱:Obsidian 与 Quartz 的高效协作
2025-03-04
构建智能知识库 - 知识获取:Obsidian Web Clipper 的 AI 自动化流程
2025-03-02
大模型时代的知识工程:企业级智能知识库构建与增强指南
2025-03-02
从 0 到 3000 节点: 我用 DeepSeek + NebulaGraph 构建农业知识图谱
2025-02-26
将知识图谱与大模型 (LLM) 协同化:实现语义增强智能的途径
2025-02-23
DeepSeek+dify知识库,查询数据库的两种方式(api+直连)
2025-02-22
深挖DeepSeek对数据资产管理的影响和应用!
2025-01-02
2024-07-17
2025-01-03
2024-08-13
2024-07-11
2024-06-24
2024-08-27
2024-07-13
2024-06-10
2024-07-12
2025-02-13
2025-01-14
2025-01-10
2025-01-06
2025-01-02
2024-12-16
2024-12-10
2024-12-04