AI知识库

53AI知识库

学习大模型的前沿技术与行业应用场景


Neo4j+RecallM:打造无需向量数据库的知识图谱

发布日期:2025-02-13 08:44:47 浏览次数: 2074 来源:活水智能
推荐语

探索未来知识图谱技术的新突破,无需向量数据库实现大语言模型的长期记忆。

核心内容:
1. RecallM:一种新型的适应记忆机制
2. 知识图谱构建与更新过程解析
3. 时间推理能力与局限性分析

杨芳贤
53A创始人/腾讯云(TVP)最具价值专家

引言

在为大语言模型(LLM)提供长期记忆的过程中,主流方法通常涉及检索增强生成(RAG)方案,其中向量数据库充当长期记忆的存储机制。这引发了一个问题:我们能否在没有向量数据库的情况下实现相同的效果?

让我们来看看 RecallM: 一种具有时间理解能力的可适应记忆机制(https://arxiv.org/abs/2307.02738),该论文由 Brandon Kynoch、Hugo Latapie 和 Dwane van der Sluis 共同提出。论文提出了一种利用自动构建的知识图谱作为 LLM 长期记忆核心的方法。

本文将深入探讨 RecallM 的工作原理,重点介绍其知识图谱的更新方式及推理机制,并通过一系列示例进行说明。

我们将首先探讨知识图谱的更新机制,并通过两个具体示例来阐明该过程。接下来,我们将研究 RecallM 的推理机制,并通过示例展示它如何利用知识图谱生成回答。此外,我们还会讨论时间推理的示例,展示 RecallM 在理解和应用时间相关知识方面的能力。最后,我们将分析该方法的局限性,以全面评估其能力及未来改进方向。

知识图谱更新

第一轮:添加新事实

假设我们想将以下句子添加到知识图谱中:

Brandon 喜欢咖啡

我们需要执行以下步骤:

步骤 1:识别概念

概念通常是句子中的名词,它们可以与其他概念建立关系。

这些概念将成为知识图谱中的节点。

此外,为了避免重复,我们会将概念转换为小写,并使用词干提取(stemming)技术将其归一化。

所有这些操作可以使用 NLP 工具包 Stanza 来完成。

以下是 Stanza 对句子 "Brandon loves coffee" 进行标注的结果:

[
  [
    {
      "id": 1,
      "text": "Brandon",
      "lemma": "Brandon",
      "upos": "PROPN",
      "xpos": "NNP",
      "feats": "Number=Sing",
      "head": 2,
      "deprel": "nsubj",
      "start_char": 0,
      "end_char": 7,
      "ner": "S-PERSON",
      "multi_ner": [
        "S-PERSON"
      ]
    },
    {
      "id": 2,
      "text": "loves",
      "lemma": "love",
      "upos": "VERB",
      "xpos": "VBZ",
      "feats": "Mood=Ind|Number=Sing|Person=3|Tense=Pres|VerbForm=Fin",
      "head": 0,
      "deprel": "root",
      "start_char": 8,
      "end_char": 13,
      "ner": "O",
      "multi_ner": [
        "O"
      ]
    },
    {
      "id": 3,
      "text": "coffee",
      "lemma": "coffee",
      "upos": "NOUN",
      "xpos": "NN",
      "feats": "Number=Sing",
      "head": 2,
      "deprel": "obj",
      "start_char": 14,
      "end_char": 20,
      "ner": "O",
      "multi_ner": [
        "O"
      ],
      "misc": "SpaceAfter=No"
    }
  ]
]

在这个例子中,名词是 "Brandon" 和 "coffee"。

使用 Porter 词干提取器进行词干化,并转换为小写后,我们得到 "brandon" 和 "coffe"。

步骤 2:查找每个概念的邻居

概念的邻居是与其存在关系的其他概念。我们可以使用依存句法分析(dependency parser)来确定概念之间的关系。然而,为了简化,我们可以仅根据概念在句子中的“距离”来确定它们的关系。

“距离”指的是概念在句子中出现的位置。因此,"brandon" 位于索引 0,而 "coffe" 位于索引 1。

以下代码片段可以帮助理解“距离”的概念:

# 默认距离设为 1
def fetch_neigbouring_concepts(concepts, distance=1):
    concepts = sorted(concepts)
    for i in range(len(concepts)):
        concepts[i].related_concepts = []
        for j in range(-distance, distance + 11):  # 如果当前概念的距离小于参数 distance
            if 0 <= i + j < len(concepts):  # 确保索引在范围内
                
                if j == 0:
                    continue

                if concepts[i].name < concepts[i + j].name:  # 确保在 Neo4J 图数据库中仅创建一个连接
                    concepts[i].related_concepts.append(concepts[i + j])

执行此步骤后,"brandon" 与 "coffe" 建立了关系,而 "coffe" 没有与 "brandon" 建立关系。

步骤 3:创建概念节点及其关系

我们可以使用图数据库来存储概念及其关系。

在 Neo4J 中,最终的知识图谱如下所示:

图 1:t=1 时的知识图谱

红色节点表示概念,它们通过“RELATED”(白色箭头)关系相连。

蓝色节点是“全局时间步”(global timestep),在时间推理中起着重要作用。其值为 1,表示这是第一次更新知识图谱。

以下是创建该图的 Cypher 查询:

MERGE (c00:Concept {name: 'brandon'})
MERGE (c01:Concept {name: 'coffe'})
WITH c00, c01
MERGE (c00)-[rc00c01:RELATED]->(c01)
WITH c00, c01, rc00c01, COALESCE(rc00c01.strength, 0) + 1 AS rc00c01ic
SET c00.t_index = 1, c01.t_index = 1
SET rc00c01.strength = rc00c01ic
SET rc00c01.t_index = 1

需要注意两点:

  1. 1. 节点和关系都有一个 t_index 属性,用于跟踪它们上次更新的时间步。

  2. 2. “RELATED” 关系有一个 strength 属性,用于跟踪该关系在知识图谱更新过程中被提及的次数。

步骤 4:添加概念的上下文

概念的上下文是指它在句子中的使用情况。

因此,对于每个概念,我们首先从知识图谱中获取其上下文信息:

MATCH (n:Concept)
WHERE n.name IN ['brandon', 'coffe']
RETURN n.name, n.context, n.revision_count

由于这是我们首次添加这些概念,查询结果将为空。

接下来,我们将当前上下文(即句子 "Brandon loves coffee")添加到概念节点,并更新知识图谱:

MATCH (n:Concept)
WHERE n.name IN ['brandon', 'coffe']
WITH n,
        CASE n.name
                WHEN 'brandon' THEN '. Brandon loves coffee'
                WHEN 'coffe' THEN '. Brandon loves coffee'
                ELSE n.context
        END AS newContext,
        CASE n.name
                WHEN 'brandon' THEN 0
                WHEN 'coffe' THEN 0
                ELSE 0
        END AS revisionCount
SET n.context = newContext
SET n.revision_count = revisionCount

请注意,"brandon" 和 "coffe" 具有相同的上下文,因为它们出现在同一句话中,而我们目前仅处理这一句话。

最终的知识图谱如下所示:

图 2:t=1 时的知识图谱(添加概念上下文后)

第二轮:扩展事实

现在,我们要将以下句子添加到知识图谱中:

Brandon 想去巴黎旅行

该句中的概念是 "Brandon" 和 "Paris"。

经过词干化和小写转换后,我们得到 "brandon" 和 "pari"。

将这些概念添加到知识图谱后,我们得到如下结构:

图 3:t=2 时的知识图谱(添加

请注意:

  • • 全局时间步(global timestep)已更新为 2。

  • • 新概念 "pari" 已添加到知识图谱中。

  • • "brandon" 和 "pari" 通过 "RELATED" 关系相连。

  • • "brandon" 的 t_index 更新为 2,因为它在时间步 2 进行了更新。

以下是用于更新知识图谱的 Cypher 查询:

MERGE (c00:Concept {name: 'brandon'})
MERGE (c01:Concept {name: 'pari'})
WITH c00, c01
MERGE (c00)-[rc00c01:RELATED]->(c01)
WITH c00, c01, rc00c01, COALESCE(rc00c01.strength, 0) + 1 AS rc00c01ic
SET c00.t_index = 2, c01.t_index = 2
SET rc00c01.strength = rc00c01ic
SET rc00c01.t_index = 2

更新概念的上下文

首先,我们从知识图谱中获取概念的上下文信息:

{
    "brandon": {
        "context": ". Brandon loves coffee",
        "revision_count": 0
    },
    "pari": {
        "context": null,
        "revision_count": null
    }
}

可以看到,"brandon" 已经有了上下文,而 "pari" 还没有。

然后,我们将新句子追加到已有的上下文中,并更新知识图谱:

MATCH (n:Concept)
WHERE n.name IN ['brandon', 'pari']
WITH n,
        CASE n.name
                WHEN 'brandon' THEN '. Brandon loves coffee. Brandon wants to travel to Paris'
                WHEN 'pari' THEN '. Brandon wants to travel to Paris'
                ELSE n.context
        END AS newContext,
        CASE n.name
                WHEN 'brandon' THEN 1
                WHEN 'pari' THEN 0
                ELSE 0
        END AS revisionCount
SET n.context = newContext
SET n.revision_count = revisionCount

请注意,"brandon" 的上下文现在包含了之前的句子和新句子。此外,它的 revision_count 也更新为 1,因为它的上下文被更新了。

最终的知识图谱如下所示:

图 4:t=2 时的知识图谱(添加概念上下文后)

推理机制

假设我们要回答以下问题:

谁想去巴黎旅行?

我们可以利用知识图谱来生成答案,具体步骤如下:

步骤 1:识别概念

使用与更新知识图谱时相同的 NLP 处理流程,我们确定问题中唯一的概念是 "pari"。

步骤 2:查找相关概念

我们在知识图谱中查找与 "pari" 相关的概念:

MATCH (startNode:Concept{name: 'pari'})
CALL apoc.path.spanningTree(startNode, {relationshipFilter: "", minLevel: 0, maxLevel: 2}) YIELD path
WITH path, nodes(path) as pathNodes, startNode.t_index as current_t
UNWIND range(0, size(pathNodes)-1) AS index
WITH path, pathNodes[index] as node, current_t
ORDER BY node.t_index DESC
WHERE node.t_index <= current_t AND node.t_index >= current_t - 15
WITH DISTINCT node LIMIT 800
MATCH ()-[relation]->()
RETURN node, relation

该查询会找到从 "pari" 出发、深度最多为 2 的所有路径,并筛选出时间步范围在 current_t - 15 到 current_t 之间的概念。

查询结果如下:

{
    "pari": ". Brandon wants to travel to Paris",
    "brandon": ". Brandon loves coffee. Brandon wants to travel to Paris",
    "coffe": ". Brandon loves coffee"
}

步骤 3:对相关概念进行排序

我们根据 t_index(时间步)和概念之间的关系强度对相关概念进行排序。

以下代码片段用于计算排序分值 sort_val

graph_concepts = graph_concept_nodes.values()
for concept in graph_concepts:
    concept.sort_val = 0
    for relation in concept.related_concepts:
        concept.sort_val += (relation.t_index * 3) + relation.strength  # 3 是一个超参数,可调整
graph_concepts = sorted(graph_concepts, key=lambda c: c.sort_val, reverse=True)  # 按降序排列

排序后,各个概念的 sort_val 如下:

步骤 4:构建上下文

我们首先获取问题中涉及的“核心概念”(essential concepts),即 "pari"。

其上下文为:

. Brandon wants to travel to Paris

然后,我们按照 sort_val 的降序依次添加相关概念的上下文:

. Brandon loves coffee  # 相关概念:coffe

. Brandon loves coffee. Brandon wants to travel to Paris  # 相关概念:brandon

. Brandon wants to travel to Paris  # 核心概念:pari

步骤 5:生成回答

接下来,我们将构建的上下文作为提示词(prompt)传递给 LLM,并生成答案。

最终的提示词如下:

使用以下陈述(statements)来回答问题(question)。这些陈述按照时间顺序排列,每句陈述在其时间步内均为真实:  

statements:
. Brandon loves coffee  

. Brandon loves coffee. Brandon wants to travel to Paris  

. Brandon wants to travel to Paris  

question:
谁想去巴黎旅行?  

Answer:

由于提示词已经包含了足够的信息,GPT-3.5-Turbo 能够正确生成答案:"Brandon"。


时间推理(Temporal Reasoning)

论文作者设计了一项实验,以评估 RecallM 在时间理解和记忆更新方面的能力。

他们构建了一个包含按时间顺序排列的陈述的数据集,每个新陈述都会更新之前的事实。然后,他们在不同时间步对系统进行提问,以测试其时间推理能力。

实验包括两类问题:

  1. 1. 标准时间推理问题:测试系统对时间概念的理解,以及它是否能正确更新记忆。

  2. 2. 长距离时间推理问题:要求系统回忆数百个时间步之前的信息,并进行推理。例如,在 25 轮更新后,系统需要回忆 1500 多个更新之前的知识。

以下是论文提供的一些实验结果,展示了 RecallM 在时间推理方面的有效性,并与向量数据库进行了对比:

图 5:RecallM 与向量数据库在时间推理任务上的对比

局限性

该方法的主要局限性在于知识图谱的构建,特别是缺乏指代消解(coreference resolution)

例如,假设我们向系统输入以下文本:

Brandon 喜欢咖啡。他想去巴黎旅行。他喜欢猫。

理想情况下,知识图谱应如下所示:

图 6:理想的知识图谱

但实际上,我们得到的是:

图 7:实际生成的知识图谱

尽管如此,像以下问题:

  • • 谁想去巴黎旅行?

  • • 谁喜欢猫?

  • • 谁喜欢咖啡?

仍然能够得到正确答案,因为生成的上下文仍然包含 "Brandon loves coffee",这有助于 LLM 生成正确的回答。

但在某些情况下,可能会出现错误。

一种可能的解决方案是将文本转换为“命题”,然后将这些命题存入知识图谱,而不是直接存储原始文本。这一想法在论文 Dense X Retrieval: What Retrieval Granularity Should We Use?(https://arxiv.org/abs/2312.06648) 中有所探讨。


结论

总的来说,RecallM 提出了一种仅使用图数据库就能为 LLM 提供长期记忆的方法。

该方法在知识更新和时间推理方面表现出色,但在构建精确知识图谱方面仍然存在挑战。

尽管如此,RecallM 代表了 AI 记忆机制的一项重要进展,未来的研究仍有很多改进和优化的空间。

 

53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费场景POC验证,效果验证后签署服务协议。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询