AI知识库

53AI知识库

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


大模型记忆可以不使用向量数据库实现吗?图数据库是一个候选
发布日期:2024-04-08 12:11:18 浏览次数: 1968


给大型语言模型(LLMs)赋予长期记忆的常见方式,往往依赖于检索增强生成(RAG)技术,并使用向量数据库作为长期记忆的存储工具。那么,如果不采用向量数据库,我们能否实现相同效果呢?本文介绍一篇由 Brandon Kynoch、Hugo Latapie 和 Dwane van der Sluis 发表的论文《RecallM: An Adaptable Memory Mechanism with Temporal Understanding for Large Language Models 》【1】,它提出使用自动构建的知识图谱作为 LLM 长期记忆的支持。
本文将深入探讨 RecallM 的机制,重点介绍它如何更新知识图谱和执行推理,并通过一系列示例加以说明。首先,将探讨知识图谱更新的工作原理,并通过两个具体示例来阐明这一过程。随后,将通过另一个示例来研究 RecallM 的推理机制,展示它是如何利用知识图谱生成响应的。讨论还将涉及时间推理的示例,展示 RecallM 在理解和应用基于时间的知识方面的能力。最后,讨论这种方法的局限性,对其能力和进一步发展的领域提供一个平衡的视角。

知识图谱更新

第一阶段:新增一个事实(Fact)

比如我们想把下面这个句子加入到知识图谱中:

Brandon loves coffee

这是我们需要执行的步骤:

步骤 1:确定概念

概念基本上是句子中的一个名词,它可以与另一个概念发生关系。概念是知识图谱中的节点。此外,为了避免重复,会将概念小写并以其词根形式作为词干。所有这些都可以通过 Stanza 等 NLP 软件包来完成。下面是使用 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:寻找每个概念的相关概念

一个概念的邻域是与之有关系的其他概念。我们可以使用依赖关系解析器来查找概念之间的关系。不过,为了简单起见,我们可以直接使用句子中概念之间的 "distance"来确定它们之间的关系。"distance "只是概念在从句子中提取的概念列表中的位置。因此,"brandon "位于位置 0,"coffe "位于位置 0,"coffe "位于位置 1。这个代码片段将有助于澄清 "distance "的概念:
# distance is set to 1 by defaultdef fetch_neigbouring_concepts(concepts, distance):    concepts = sorted(concepts)    for i in range(len(concepts)):        concepts[i].related_concepts = []        for j in range(-distance, distance + 1, 1): # If distance from current concept is less than parameter distance            if i + j >= 0 and i + j < len(concepts): # If index is in bounds
if j == 0: continue
if concepts[i].name < concepts[i+j].name: # Ensure that we only create one connection between nodes in Neo4J graph concepts[i].related_concepts.append(concepts[i+j])
这一步的成果是,“brandon”和“coffe”之间建立了关系,但反过来“coffe”和“brandon”之间并没有建立关系。

步骤 3:构建概念节点和它们的联系

可以采用图形数据库来储存这些概念及其之间的联系。完成这一步之后,知识图谱在 Neo4J 数据库中的样子大概会是这样:

图1:t=1时的知识图

红色的节点代表概念。它们通过一种称为“相关”(白色箭头)的关系相连接。蓝色的节点代表“全局时点”,在稍后的时间推理过程中会扮演一个重要的角色。它的值为 1,表示我们正首次建立并更新知识图谱。下面是用于构建图谱的 Cypher 查询代码:

MERGE (c00:Concept {name: 'brandon'})MERGE (c01:Concept {name: 'coffe'})WITH c00, c01MERGE (c00)-[rc00c01:RELATED]->(c01)WITH c00, c01, rc00c01, COALESCE(rc00c01.strength, 0) + 1 AS rc00c01icSET c00.t_index = 1, c01.t_index = 1SET rc00c01.strength = rc00c01icSET rc00c01.t_index = 1
Cypher 查询中有两个注意点:
1. 节点和关系都具有一个属性,名为 `t_index`(追踪最后更新时间)
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 revisionCountSET n.context = newContextSET n.revision_count = revisionCount
注意,“Brandon” 和 “Coffee” 的上下文是相同的。这是由于它们出现在同一个句子中,并且到目前为止我们仅处理了这唯一的一句。在完成这一步骤后,我们的知识图谱将展现如下面的样子:

图2:t=1时以及添加概念上下文后的知识图

第二阶段:用另一事实来扩展现有事实

设想我们想要把以下这句话添加进知识图谱:

Brandon wants to travel to Paris

可以看出,这句话中的关键概念是“Brandon”和“Paris”。经过词干提取和转换为小写后,我们得到“brandon”和“pari”

图3:t=2时以及添加“brandon”和“pari”概念后的知识图

注意到全局时点已经更新至2,并且新的概念“pari”已经加入到了知识图谱。"brandon" 与 "pari" 之间建立了“RELATED”(相关)关系。由于在第2个时间步进行了更新,“brandon”的 t_index 也更新为了2。

下面是一个用于将概念及其关联关系添加到知识图谱中的 Cypher 查询:
MERGE (c00:Concept {name: 'brandon'})MERGE (c01:Concept {name: 'pari'})WITH c00, c01MERGE (c00)-[rc00c01:RELATED]->(c01)WITH c00, c01, rc00c01, COALESCE(rc00c01.strength, 0) + 1 AS rc00c01icSET c00.t_index = 2, c01.t_index = 2SET rc00c01.strength = rc00c01icSET 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 revisionCountSET n.context = newContextSET n.revision_count = revisionCount

注意,“brandon”获得了一个新的上下文环境,这个环境不但包含了之前的上下文还包括了新的句子。因为其上下文进行了更新,它的修订计数(revision_count)也更新为1了。在这一步骤完成后,知识图谱将呈现如下的样子:

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


推理机制

假设现在我们需要对这样一个问题给出回答:Who wants to travel to Paris?以下是我们利用知识图谱来生成回答的方法:

步骤 1:确认概念

利用此前用于更新知识图谱中的句子所用的相同的NLP pipeline,确定这个问题中只含有一个概念:“pari”。

步骤 2:寻找相关概念

利用知识图谱来寻找和“pari”有关的概念:

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

上面的查询从“pari”概念节点开始查找深度高达2级的所有路径,根据其t_index筛选结果节点,以忽略相对于“pari“早于15个时间点的概念,获取前800个唯一节点,并返回图中的这些节点和关系。获得以下概念节点:

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

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

根据每个相关概念的t_index以及它们与自己的相关概念之间关系的强度对它们进行排序。以下是执行此操作的代码片段:

 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     # TODO: 3 is a hyperparameter, move this to header    graph_concepts = sorted(graph_concepts, key=lambda c: c.sort_val)    graph_concepts.reverse()    # We want them in descending order, so that highest temporal relations are first

在代码片段中,“score”被称为sort_val。sort_val越高,概念在时间上的相关性就越大。以下是发现与“pari”相关的每个概念的sort_val:


步骤 4:构建上下文

从问题中存在的概念的上下文开始称之为“基本概念”。在这种情况下,它只是“pari”。上下文只是一句“Brandon wants to travel to Paris”。然后,按照sort_val递减的顺序,为上一步中找到的概念的上下文做准备。这提供了传递给LLM的上下文:

. Brandon loves coffee # from related concept: coffee
. Brandon loves coffee. Brandon wants to travel to Paris # from related concept: brandon
. Brandon wants to travel to Paris # from essential concepts

步骤5:生成响应

剩下的就是用我们在前一步中构建的上下文提示LLM,并让它生成响应。提示如下所示:

Using the following statements when necessary, answer the question that follows. Each sentence in the following statements is true when read in chronological order: 
statements:. Brandon loves coffee
. Brandon loves coffee. Brandon wants to travel to Paris
. Brandon wants to travel to Paris
question:Who wants to travel to Paris?
Answer:

有了这样一个全面而深思熟虑的提示,LLM在这种情况下使用gpt-3.5-turbo,能够生成正确的响应:“Brandon”。

时间推理能力(Temporal Reasoning

为了评估 RecallM 的时间理解和记忆更新能力,研究人员设计了一个简易的实验。他们构建了一个数据集,包含了一系列按时间顺序排列的陈述,每个后续的陈述都反映了更新后的真实情况覆盖了之前的陈述。系统会定期接受提问,目的是考察其对于时间概念的理解,这些概念包括事件发生的先后和先前陈述中的知识。实验中包括了两类问题:传统的时间问题和长范围的时间问题。前者是为了测试系统对时间概念的理解、更新信念和记忆的能力;而后者则需要系统回忆数百条之前的陈述得到的信息。系统为了在25轮重复提问后正确回答长距离的时间问题,必须能够回想并推理出1500次以上之前更新的知识。这篇论文还分享了一些例子,展示了 RecallM 架构在维持和利用时间知识方面的有效性:


图5:时间推理中的RecallM与Vector DB

局限

这个方法的一个主要局限在于构建知识图谱时遇到的问题。最关键的是,它没有实现指代消解(coreference resolution)功能。这意味着,如果向系统输入如下文本:
Brandon loves coffee. He wants to travel to Paris. He likes cats.
那么我们期待知识图谱能够反映出下面这样的信息:
图6:预期知识图谱

然而,事实上的图关系:

图7:实际知识图谱

然而,即使是提出如下问题:

  • Who wants to travel to Paris?

  • Who likes cats?

  • Who loves coffee?

仍然会得到正确的答案,因为得到的上下文中仍然有 "Brandon loves coffee",这有助于 LLM 生成正确的答案。但不确定在某些边缘情况下是否不会出现这种情况。解决这一问题的方法是将文本段落转化为 "命题",正如 Chen 等人在论文《Dense X Retrieval: What Retrieval Granularity Should We Use?》中讨论的那样,将文本段落翻译成 "命题",然后将这些命题输入知识图谱,而不是原始文本段落。
结论

RecallM 提出了一种方法,仅利用图数据库就为大语言模型(LLM)集成了长期记忆功能。它在更新储存的知识和理解时间关系方面显示了其有效性,但如何创建精准的知识图谱等挑战仍旧存在。尽管如此,这是 AI 系统的一项重要进展,持续的研究为其完善和改善提供了机遇。

原文:https://pub.towardsai.net/how-to-do-rag-without-vector-databases-45fd4f6ced06

【1】https://arxiv.org/abs/2307.02738


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

产品:大模型应用平台+智能体定制开发+落地咨询服务

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

联系我们

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

微信扫码

与创始人交个朋友

回到顶部

 
扫码咨询