如何使用Neo4j和LangChain构建知识图谱

2024年12月30日 由 alex 发表 72 0

4


知识图谱是用于表示和查询数据中复杂关系的强大工具。在本文中,我们将介绍如何使用 Neo4j 和 LangChain 从文本数据构建知识图谱。我们还将提供查询示例以从图谱中提取见解。


5


工具概述

  • 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这样的关系。这些关系有助于理解节点之间的信息流动和上下文连接。

  • 优先级(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


通过添加这些关系,你增强了图的实用性,使其能够以更全面的方式表示数据的结构和意义。


除了将块链接到关键词外,你还可以创建额外的关系来丰富图的结构。例如,你可以:

  • 链接共享常见关键词的块:在具有重叠关键词的Chunk节点之间建立SHARES_KEYWORD关系。这有助于识别块之间的主题连接。
  • 对关键词进行分类:通过创建到Category节点的BELONGS_TO关系,将关键词分组为更广泛的类别,如主题、学科或主题。
  • 基于语义相似性关联关键词:使用外部工具或嵌入来计算关键词之间的相似性,并用SIMILAR_TO关系将它们连接起来。


以下是在代码中实现这样一种关系的方法:


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


查询图形


6


现在图已经构建好了,你可以查询它以获取见解。以下是一些示例:


统计图中的块数量


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,从文本数据构建知识图既简单又强大。这个流程可以进一步扩展,集成额外的处理层、可视化工具或更复杂的查询,以满足特定需求。无论你是在分析一本书还是庞大的语料库,这种方法都为你探索数据中的关系提供了可扩展的基础。

文章来源:https://medium.com/@la_boukouffallah/how-to-build-a-knowledge-graph-using-neo4j-and-langchain-d2b13dbaf9b8
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消