当前位置: 代码网 > 科技>电脑产品>内存 > 知识图谱检索增强的GraphRAG(基于Neo4j代码实现)

知识图谱检索增强的GraphRAG(基于Neo4j代码实现)

2024年08月01日 内存 我要评论
对一个查询问题分别进行了测试, 与没有RAG,仅利用LLM生成回复的情况进行对比,在有GraphRAG 的情况下,LLM模型回答的信息量更大、准确会更高。

图检索增强生成(graph rag)正逐渐流行起来,成为传统向量搜索方法的有力补充。这种方法利用图数据库的结构化特性,将数据以节点和关系的形式组织起来,从而增强检索信息的深度和上下文关联性。图在表示和存储多样化且相互关联的信息方面具有天然优势,能够轻松捕捉不同数据类型间的复杂关系和属性。而向量数据库在处理这类结构化信息时则显得力不从心,它们更擅长通过高维向量处理非结构化数据。在 rag 应用中,结合结构化的图数据和非结构化的文本向量搜索,可以让我们同时享受两者的优势,这也是本文将要探讨的内容。

构建知识图谱通常是利用图数据表示的强大功能中最困难的一步。它需要收集和整理数据,这需要对领域知识和图建模有深刻的理解。为了简化这一过程,可以参考已有的项目或者利用llm来创建知识图谱,进而可以把重点放在检索召回,以及llm的生成阶段。下面来进行相关代码的实践。

1 知识图谱构建

为了存储知识图谱数据,首先需要搭建一个 neo4j 实例。最简单的方法是在 neo4j aura 上启动一个免费实例,它提供了 neo4j 数据库的云版本。当然,也可以通过docker本地启动一个,然后将图谱数据导入到neo4j 数据库中。

步骤i:neo4j环境搭建

下面是本地启动docker的运行示例:

docker run -d \
    --restart always \
    --publish=7474:7474 --publish=7687:7687 \
    --env neo4j_auth=neo4j/000000 \
    --volume=/yourdockervolume/neo4j:/data \
    neo4j:5.19.0

步骤ii:图谱数据导入

演示中,我们可以使用伊丽莎白一世的维基百科页面。利用 langchain 加载器从维基百科获取并分割文档,后存入neo4j数据库。为了试验中文上的效果,我们导入这个github上的这个项目(qasystemonmedicalkg)中的医学知识图谱,包含近35000个节点,30万组三元组,大致得到如下结果:

或者利用langchainlangchain 加载器从维基百科获取并分割文档,大致如下面步骤所示:


# 读取维基百科文章  
raw_documents = wikipedialoader(query="elizabeth i").load()  
  
# 定义分块策略  
text_splitter = tokentextsplitter(chunk_size=512, chunk_overlap=24)

documents = text_splitter.split_documents(raw_documents[:3])

llm=chatopenai(temperature=0, model_name="gpt-4-0125-preview")
llm_transformer = llmgraphtransformer(llm=llm)
# 提取图数据
graph_documents = llm_transformer.convert_to_graph_documents(documents)
# 存储到 neo4j  

graph.add_graph_documents(
  graph_documents, 
  baseentitylabel=true, 
  include_source=true
)

2 知识图谱检索

在对知识图谱检索之前,需要对实体和相关属性进行向量嵌入并存储到neo4j数据库中:

  • 实体信息向量嵌入:将实体名称和实体的描述信息拼接后,利用向量表征模型进行向量嵌入(如下述示例代码中的add_embeddings方法所示)。

  • 图谱结构化检索:图谱的结构化检索分为四个步骤:步骤一,从图谱中检索与查询相关的实体;步骤二,从全局索引中检索得到实体的标签;步骤三,根据实体标签在相应的节点中查询邻居节点路径;步骤四,对关系进行筛选,保持多样性(整个检索过程如下述示例代码中的structured_retriever方法所示)。


class graphrag(object):
    def __init__(self):
        """any embedding function implementing `langchain.embeddings.base.embeddings` interface."""
        self._database = 'neo4j'
        self.label = 'med'
        self._driver = neo4j.graphdatabase.driver(
            uri=os.environ["neo4j_uri"],
            auth=(os.environ["neo4j_username"],
                  os.environ["neo4j_password"]))
        self.embeddings_zh = huggingfaceembeddings(model_name=os.environ["embedding_model"])
        self.vectstore = neo4jvector(embedding=self.embeddings_zh,
                                     username=os.environ["neo4j_username"],
                                     password=os.environ["neo4j_password"],
                                     url=os.environ["neo4j_uri"],
                                     node_label=self.label,
                                     index_name="vector"
                                     )


    def query(self, query: str, params: dict = {}) -> list[dict[str, any]]:
        """query neo4j database."""
        from neo4j.exceptions import cyphersyntaxerror

        with self._driver.session(database=self._database) as session:
            try:
                data = session.run(query, params)
                return [r.data() for r in data]
            except cyphersyntaxerror as e:
                raise valueerror(f"generated cypher statement is not valid\n{e}")

    def add_embeddings(self):
        """add embeddings to neo4j database."""
        # 查询图中所有节点,并且根据节点的描述和名字生成embedding,添加到该节点上
        query = """match (n) where not (n:{}) return id(n) as id, labels(n) as labels, n""".format(self.label)
        print("qurey node...")
        data = self.query(query)
        ids, texts, embeddings, metas = [], [], [], []
        for row in tqdm(data,desc="parsing node"):
            ids.append(row['id'])
            text = row['n'].get('name','') + row['n'].get('desc','')
            texts.append(text)
            metas.append({"label": row['labels'], "context": text})
        self.embeddings_zh.multi_process = false

        print("node embeddings...")
        embeddings = self.embeddings_zh.embed_documents(texts)
        print("adding node embeddings...")
        ids_ret = self.vectstore.add_embeddings(
            ids=ids,
            texts=texts,
            embeddings=embeddings,
            metadatas=metas
        )
        return ids_ret

    # fulltext index query
    def structured_retriever(self, query, limit=3, simlarity=0.9) -> str:
        """
        collects the neighborhood of entities mentioned in the question
        """
        # step1 从图谱中检索与查询相关的实体。
        docs_with_score = self.vectstore.similarity_search_with_score(query, k=topk)
        entities = [item[0].page_content for item in data if item[1] > simlarity] # score
        self.vectstore.query(
            "create fulltext index entity if not exists for (e:med) on each [e.context]")
        result = ""
        for entity in entities:
            qry = entity
            # step2 从全局索引中查出entity label,
            query1 =  f"""call db.index.fulltext.querynodes('entity', '{qry}') yield node, score 
                            return node.label as label,node.context as context, node.id as id, score limit {limit}"""
            data1 = self.vectstore.query(query1)
            # step3 根据label在相应的节点中查询邻居节点路径
            for item in data1:
                node_type = item['label']
                node_type = item['label'] if type(node_type) == str else node_type[0]
                node_id = item['id']
                query2 = f"""match (node:{node_type})-[r]-(neighbor) where id(node) = {node_id} return type(r) as rel, node.name+' - '+type(r)+' - '+neighbor.name as output limit 50"""
                data2 = self.vectstore.query(query2)
                # step4 为了保持多样性,对关系进行筛选
                rel_dict = defaultdict(list)
                if len(data2) > 3*limit:
                    for item1 in data2:
                        rel_dict[item1['rel']].append(item1['output'])
                if rel_dict:
                    rel_dict = {k:random.sample(v, 3) if len(v)>3 else v for k,v in rel_dict.items()}
                    result += "\n".join(['\n'.join(el) for el in rel_dict.values()]) +'\n'
                else:
                    result += "\n".join([el['output'] for el in data2]) +'\n'
        return result

3 结合llm生成

最后利用大语言模型(llm)根据从知识图谱中检索出来的结构化信息,生成最终的回复。下面的代码中我们以通义千问开源的大语言模型为例:

步骤i:加载llm模型


from langchain import huggingfacepipeline
from transformers import pipeline, autotokenizer, automodelforcausallm

def custom_model(model_name, branch_name=none, cache_dir=none, temperature=0, top_p=1, max_new_tokens=512, stream=false):
    tokenizer = autotokenizer.from_pretrained(model_name,
                                             revision=branch_name,
                                             cache_dir=cache_dir)
    model = automodelforcausallm.from_pretrained(model_name,
                                        device_map='auto',
                                        torch_dtype=torch.float16,
                                        revision=branch_name,
                                        cache_dir=cache_dir
                                        )
    pipe = pipeline("text-generation",
                    model = model,
                    tokenizer = tokenizer,
                    torch_dtype = torch.bfloat16,
                    device_map = 'auto',
                    max_new_tokens = max_new_tokens,
                    do_sample = true
                    )
    llm = huggingfacepipeline(pipeline = pipe,
                              model_kwargs = {"temperature":temperature, "top_p":top_p,
                                              "tokenizer":tokenizer, "model":model})
    return llm
    
tongyi_model = "qwen1.5-7b-chat"
llm_model = custom_model(model_name=tongyi_model)
tokenizer = llm_model.model_kwargs['tokenizer']
model = llm_model.model_kwargs['model']

步骤ii:输入检索数据生成回复



final_data = self.get_retrieval_data(query)
prompt = ("请结合以下信息,简洁和专业的来回答用户的问题,若信息与问题关联紧密,请尽量参考已知信息。\n"
          "已知相关信息:\n{context} 请回答以下问题:{question}".format(context=final_data, question=query))

messages = [
    {"role": "system", "content": "你是**开发的智能助手。"},
    {"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=false, add_generation_prompt=true)
model_inputs = tokenizer([text], return_tensors="pt").to(self.device)

generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512)
generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=true)[0]
print(response)

4 结语

对一个查询问题分别进行了测试, 与没有rag,仅利用llm生成回复的情况进行对比,在有graphrag 的情况下,llm模型回答的信息量更大、准确会更高。

如何学习ai大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的ai知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的ai大模型资料包括ai大模型入门学习思维导图、精品ai大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 ai 学习资料已经上传csdn,朋友们如果需要可以微信扫描下方csdn官方认证二维码免费领取【保证100%免费

一、全套agi大模型学习路线

ai大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套ai大模型报告合集

这套包含640份报告的合集,涵盖了ai大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对ai大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、ai大模型经典pdf籍

随着人工智能技术的飞速发展,ai大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如gpt-3、bert、xlnet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些pdf籍就是非常不错的学习资源。

img

四、ai大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com