知识图谱是用于表示和查询数据中复杂关系的强大工具。在本文中,我们将介绍如何使用 Neo4j 和 LangChain 从文本数据构建知识图谱。我们还将提供查询示例以从图谱中提取见解。
工具概述
代码演示
让我们使用提供的Python代码,将这个过程分解成清晰的步骤。
步骤1:设置Neo4j连接
首先,初始化与你的Neo4j数据库的连接:
from langchain_community.graphs import Neo4jGraph
url = "bolt://localhost:7687""bolt://localhost:7687"
username = "neo4j"
password = "password"
graph = Neo4jGraph(url=url, username=username, password=password)
确保你的Neo4j实例在本地运行,并用你的实际设置替换凭据。
步骤2:从文本中提取关键词
我们将使用一个简单的函数从文本块中提取关键词:
import re
from collections import Counter
def _extract_keywords(text: str, top_n: int = 5) -> List[str]:
words = re.findall(r"\w+", text.lower())
stop_words = {"the", "a", "an", "and", "or", "but", "in", "on", "at", "to", "for", "of", "with", "by"}
filtered_words = [word for word in words if word not in stop_words and len(word) > 2]
return [word for word, count in Counter(filtered_words).most_common(top_n)]
这个函数对文本进行分词,去除常见的停用词,并返回最频繁的单词。
步骤3:处理PDF数据
使用LangChain的PyPDFLoader,加载PDF文档并将其拆分成易于管理的块:
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_and_process_pdf(pdf_path: str, chunk_size: int = 1000, chunk_overlap: int = 200) -> List[Dict]:
loader = PyPDFLoader(pdf_path)
pages = loader.load()
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, chunk_overlap=chunk_overlap, length_function=len)
splits = text_splitter.split_documents(pages)
processed_chunks = []
for i, chunk in enumerate(splits):
metadata = {
"chunk_id": i,
"source": pdf_path,
"page_number": chunk.metadata.get("page", None),
"total_length": len(chunk.page_content),
"keywords": _extract_keywords(chunk.page_content),
"text_preview": (chunk.page_content[:100] + "..." if len(chunk.page_content) > 100 else chunk.page_content),
}
processed_chunks.append({"text": chunk.page_content, "metadata": metadata})
return processed_chunks
pdf_path = "document.pdf"
chunks = load_and_process_pdf(pdf_path)
这会生成一个块列表,每个块都包含文本和元数据,如关键词、页码和预览。
步骤4:构建图
为了构建知识图,我们为每个文本块和每个关键词创建节点,然后在它们之间建立关系。这个过程首先清除图中任何现有的数据,以确保从一个干净的状态开始。对于每个文本块,都会创建一个Chunk节点,包含元数据,如源文件、页码、文本预览和全文。同时,我们从每个块中提取关键词,并为它们创建Keyword节点(如果它们尚不存在)。
接下来,我们使用HAS_KEYWORD关系建立关系,将每个Chunk节点链接到相关的Keyword节点。这使我们能够追踪块与其相关关键词之间的连接,从而便于后续对图进行查询和分析。
以下是我们的做法:
def create_graph_from_chunks(chunks: List[Dict]):
graph.query("MATCH (n) DETACH DELETE n") # Clear existing data
create_chunk_query = """
MERGE (chunk:Chunk {chunk_id: $chunk_id})
ON CREATE SET
chunk.source = $source,
chunk.page_number = $page_number,
chunk.total_length = $total_length,
chunk.text_preview = $text_preview,
chunk.full_text = $full_text
WITH chunk
UNWIND $keywords AS keyword
MERGE (kw:Keyword {name: keyword})
MERGE (chunk)-[:HAS_KEYWORD]->(kw)
RETURN chunk
"""
for chunk in chunks:
graph.query(
create_chunk_query,
params={
"chunk_id": chunk["metadata"]["chunk_id"],
"source": chunk["metadata"]["source"],
"page_number": chunk["metadata"]["page_number"],
"total_length": chunk["metadata"]["total_length"],
"text_preview": chunk["metadata"]["text_preview"],
"full_text": chunk["text"],
"keywords": chunk["metadata"]["keywords"],
},
)
create_graph_from_chunks(chunks[:200])
这个脚本创建了Chunk和Keyword节点,并用HAS_KEYWORD关系将它们链接起来。
向图中添加更多关系
通过优先级和相关性关系丰富图
为了使图更有意义,我们可以引入像PRECEDES和IS_RELEVANT_TO这样的关系。这些关系有助于理解节点之间的信息流动和上下文连接。
以下是在代码中实现这些关系的方法:
def add_precedency_relationships(graph: Neo4jGraph, chunks: List[Dict], batch_size: int = 50):
query = """
MATCH (c1:Chunk {chunk_id: $chunk_id1}), (c2:Chunk {chunk_id: $chunk_id2})
MERGE (c1)-[:PRECEDES]->(c2)
"""
for i in range(0, len(chunks) - 1, batch_size):
batch_end = min(i + batch_size, len(chunks) - 1)
try:
for j in range(i, batch_end):
graph.query(
query,
params={
"chunk_id1": chunks[j]["metadata"]["chunk_id"],
"chunk_id2": chunks[j + 1]["metadata"]["chunk_id"]
}
)
except ClientError as e:
logging.error(f"Failed to add precedency relationships for batch starting at {i}: {e}")
raise
def add_relevancy_relationships(graph: Neo4jGraph):
try:
# First, ensure we have indexes for better performance
graph.query("CREATE INDEX chunk_id IF NOT EXISTS FOR (c:Chunk) ON (c.chunk_id)")
query = """
MATCH (c1:Chunk)-[:HAS_KEYWORD]->(k:Keyword)<-[:HAS_KEYWORD]-(c2:Chunk)
WHERE c1.chunk_id <> c2.chunk_id
WITH DISTINCT c1, c2
MERGE (c1)-[:IS_RELEVANT_TO]->(c2)
"""
graph.query(query)
except ClientError as e:
logging.error(f"Failed to add relevancy relationships: {e}")
raise
通过添加这些关系,你增强了图的实用性,使其能够以更全面的方式表示数据的结构和意义。
除了将块链接到关键词外,你还可以创建额外的关系来丰富图的结构。例如,你可以:
以下是在代码中实现这样一种关系的方法:
def link_chunks_by_keyword(graph: Neo4jGraph):
try:
# Ensure index exists for better performance
graph.query("CREATE INDEX chunk_id IF NOT EXISTS FOR (c:Chunk) ON (c.chunk_id)")
query = """
MATCH (c1:Chunk)-[:HAS_KEYWORD]->(k:Keyword)<-[:HAS_KEYWORD]-(c2:Chunk)
WHERE c1.chunk_id < c2.chunk_id
WITH DISTINCT c1, c2
MERGE (c1)-[:SHARES_KEYWORD]->(c2)
"""
graph.query(query)
except ClientError as e:
logging.error(f"Failed to link chunks by keyword: {e}")
raise
查询图形
现在图已经构建好了,你可以查询它以获取见解。以下是一些示例:
统计图中的块数量
MATCH (c:Chunk)
RETURN count(c) AS chunk_count
这返回Chunk节点的总数。
检索某个块的关键词
MATCH (c:Chunk)-[:HAS_KEYWORD]->(k:Keyword)
WHERE c.chunk_id = 0
RETURN k.name AS keywords
这获取与特定块相关联的所有关键词。
查找与特定关键词相关的块
MATCH (k:Keyword {name: "learning"})<-[:HAS_KEYWORD]-(c:Chunk)
RETURN c.text_preview, c.page_number
这检索包含关键词“learning”的块。
最常使用的关键词
MATCH (k:Keyword)<-[:HAS_KEYWORD]-()
RETURN k.name, count(*) AS usage_count
ORDER BY usage_count DESC
LIMIT 10
这找出了使用频率最高的前10个关键词。
结论
使用Neo4j和LangChain,从文本数据构建知识图既简单又强大。这个流程可以进一步扩展,集成额外的处理层、可视化工具或更复杂的查询,以满足特定需求。无论你是在分析一本书还是庞大的语料库,这种方法都为你探索数据中的关系提供了可扩展的基础。