ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding-right: 0.2em;padding-left: 0.2em;color: rgb(255, 255, 255);background: rgb(1, 155, 252);">引言
ingFang SC";letter-spacing: 0.1em;color: rgb(63, 63, 63);">在为大语言模型(LLM)提供长期记忆的过程中,主流方法通常涉及检索增强生成(RAG)方案,其中向量数据库充当长期记忆的存储机制。这引发了一个问题:我们能否在没有向量数据库的情况下实现相同的效果?
ingFang SC";letter-spacing: 0.1em;color: rgb(63, 63, 63);">让我们来看看 RecallM: 一种具有时间理解能力的可适应记忆机制(https://arxiv.org/abs/2307.02738),该论文由 Brandon Kynoch、Hugo Latapie 和 Dwane van der Sluis 共同提出。论文提出了一种利用自动构建的知识图谱作为 LLM 长期记忆核心的方法。
ingFang SC";letter-spacing: 0.1em;color: rgb(63, 63, 63);">本文将深入探讨 RecallM 的工作原理,重点介绍其知识图谱的更新方式及推理机制,并通过一系列示例进行说明。
ingFang SC";letter-spacing: 0.1em;color: rgb(63, 63, 63);">我们将首先探讨知识图谱的更新机制,并通过两个具体示例来阐明该过程。接下来,我们将研究 RecallM 的推理机制,并通过示例展示它如何利用知识图谱生成回答。此外,我们还会讨论时间推理的示例,展示 RecallM 在理解和应用时间相关知识方面的能力。最后,我们将分析该方法的局限性,以全面评估其能力及未来改进方向。 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;display: table;padding-right: 0.2em;padding-left: 0.2em;color: rgb(255, 255, 255);background: rgb(1, 155, 252);">知识图谱更新 ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;padding-left: 8px;color: rgb(63, 63, 63);">第一轮:添加新事实
ingFang SC";letter-spacing: 0.1em;color: rgb(63, 63, 63);">假设我们想将以下句子添加到知识图谱中: ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 14px;padding: 1em;border-radius: 6px;color: rgba(0, 0, 0, 0.5);background: rgb(247, 247, 247);">
ingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;font-size: 1em;letter-spacing: 0.1em;color: rgb(63, 63, 63);">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 deffetch_neigbouring_concepts(concepts,distance=1): concepts=sorted(concepts) foriinrange(len(concepts)): concepts[i].related_concepts=[] forjinrange(-distance,distance+1,1):#如果当前概念的距离小于参数distance if0<=i+j<len(concepts):#确保索引在范围内 ifj==0: continue ifconcepts[i].name<concepts[i+j].name:#确保在Neo4J图数据库中仅创建一个连接 concepts[i].related_concepts.append(concepts[i+j])执行此步骤后,"brandon" 与 "coffe" 建立了关系,而 "coffe" 没有与 "brandon" 建立关系。
步骤 3:创建概念节点及其关系 我们可以使用图数据库来存储概念及其关系。
在 Neo4J 中,最终的知识图谱如下所示:
红色节点表示概念,它们通过“RELATED”(白色箭头)关系相连。
蓝色节点是“全局时间步”(global timestep),在时间推理中起着重要作用。其值为 1,表示这是第一次更新知识图谱。
以下是创建该图的 Cypher 查询:
MERGE(c00:Concept{name:'brandon'}) MERGE(c01:Concept{name:'coffe'}) WITHc00,c01 MERGE(c00)-[rc00c01:RELATED]->(c01) WITHc00,c01,rc00c01,COALESCE(rc00c01.strength,0)+1ASrc00c01ic SETc00.t_index=1,c01.t_index=1 SETrc00c01.strength=rc00c01ic SETrc00c01.t_index=1需要注意两点:
1. 节点和关系都有一个t_index属性,用于跟踪它们上次更新的时间步。
2. “RELATED” 关系有一个strength属性,用于跟踪该关系在知识图谱更新过程中被提及的次数。
步骤 4:添加概念的上下文 概念的上下文是指它在句子中的使用情况。
因此,对于每个概念,我们首先从知识图谱中获取其上下文信息:
MATCH(n:Concept) WHEREn.nameIN['brandon','coffe'] RETURNn.name,n.context,n.revision_count由于这是我们首次添加这些概念,查询结果将为空。
接下来,我们将当前上下文(即句子 "Brandon loves coffee")添加到概念节点,并更新知识图谱:
MATCH(n:Concept) WHEREn.nameIN['brandon','coffe'] WITHn, CASEn.name WHEN'brandon'THEN'.Brandonlovescoffee' WHEN'coffe'THEN'.Brandonlovescoffee' ELSEn.context ENDASnewContext, CASEn.name WHEN'brandon'THEN0 WHEN'coffe'THEN0 ELSE0 ENDASrevisionCount SETn.context=newContext SETn.revision_count=revisionCount请注意,"brandon" 和 "coffe" 具有相同的上下文,因为它们出现在同一句话中,而我们目前仅处理这一句话。
最终的知识图谱如下所示:
第二轮:扩展事实 现在,我们要将以下句子添加到知识图谱中:
Brandon 想去巴黎旅行
该句中的概念是 "Brandon" 和 "Paris"。
经过词干化和小写转换后,我们得到 "brandon" 和 "pari"。
将这些概念添加到知识图谱后,我们得到如下结构:
请注意:
• 全局时间步(global timestep)已更新为 2。
• 新概念 "pari" 已添加到知识图谱中。
• "brandon" 和 "pari" 通过 "RELATED" 关系相连。
• "brandon" 的t_index更新为 2,因为它在时间步 2 进行了更新。
以下是用于更新知识图谱的 Cypher 查询:
MERGE(c00:Concept{name:'brandon'}) MERGE(c01:Concept{name:'pari'}) WITHc00,c01 MERGE(c00)-[rc00c01:RELATED]->(c01) WITHc00,c01,rc00c01,COALESCE(rc00c01.strength,0)+1ASrc00c01ic SETc00.t_index=2,c01.t_index=2 SETrc00c01.strength=rc00c01ic SETrc00c01.t_index=2更新概念的上下文 首先,我们从知识图谱中获取概念的上下文信息:
{ "brandon":{ "context":".Brandonlovescoffee", "revision_count":0 }, "pari":{ "context":null, "revision_count":null } }可以看到,"brandon" 已经有了上下文,而 "pari" 还没有。
然后,我们将新句子追加到已有的上下文中,并更新知识图谱:
MATCH(n:Concept) WHEREn.nameIN['brandon','pari'] WITHn, CASEn.name WHEN'brandon'THEN'.Brandonlovescoffee.BrandonwantstotraveltoParis' WHEN'pari'THEN'.BrandonwantstotraveltoParis' ELSEn.context ENDASnewContext, CASEn.name WHEN'brandon'THEN1 WHEN'pari'THEN0 ELSE0 ENDASrevisionCount SETn.context=newContext SETn.revision_count=revisionCount请注意,"brandon" 的上下文现在包含了之前的句子和新句子。此外,它的revision_count也更新为 1,因为它的上下文被更新了。
最终的知识图谱如下所示:
推理机制 假设我们要回答以下问题:
谁想去巴黎旅行?
我们可以利用知识图谱来生成答案,具体步骤如下:
步骤 1:识别概念 使用与更新知识图谱时相同的 NLP 处理流程,我们确定问题中唯一的概念是 "pari"。
步骤 2:查找相关概念 我们在知识图谱中查找与 "pari" 相关的概念:
MATCH(startNode:Concept{name:'pari'}) CALLapoc.path.spanningTree(startNode,{relationshipFilter:"",minLevel:0,maxLevel:2})YIELDpath WITHpath,nodes(path)aspathNodes,startNode.t_indexascurrent_t UNWINDrange(0,size(pathNodes)-1)ASindex WITHpath,pathNodes[index]asnode,current_t ORDERBYnode.t_indexDESC WHEREnode.t_index<=current_tANDnode.t_index>=current_t-15 WITHDISTINCTnodeLIMIT800 MATCH()-[relation]->() RETURNnode,relation该查询会找到从 "pari" 出发、深度最多为 2 的所有路径,并筛选出时间步范围在current_t - 15到current_t之间的概念。
查询结果如下:
{ "pari":".BrandonwantstotraveltoParis", "brandon":".Brandonlovescoffee.BrandonwantstotraveltoParis", "coffe":".Brandonlovescoffee" }步骤 3:对相关概念进行排序 我们根据t_index(时间步)和概念之间的关系强度对相关概念进行排序。
以下代码片段用于计算排序分值sort_val:
graph_concepts=graph_concept_nodes.values() forconceptingraph_concepts: concept.sort_val=0 forrelationinconcept.related_concepts: concept.sort_val+=(relation.t_index*3)+relation.strength#3是一个超参数,可调整 graph_concepts=sorted(graph_concepts,key=lambdac:c.sort_val,reverse=True)#按降序排列排序后,各个概念的sort_val如下:
步骤 4:构建上下文 我们首先获取问题中涉及的“核心概念”(essential concepts),即 "pari"。
其上下文为:
.BrandonwantstotraveltoParis 然后,我们按照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: .Brandonlovescoffee .Brandonlovescoffee.BrandonwantstotraveltoParis .BrandonwantstotraveltoParis question: 谁想去巴黎旅行? Answer:由于提示词已经包含了足够的信息,GPT-3.5-Turbo 能够正确生成答案:"Brandon"。
时间推理(Temporal Reasoning) 论文作者设计了一项实验,以评估 RecallM 在时间理解和记忆更新方面的能力。
他们构建了一个包含按时间顺序排列的陈述的数据集,每个新陈述都会更新之前的事实。然后,他们在不同时间步对系统进行提问,以测试其时间推理能力。
实验包括两类问题:
1.标准时间推理问题 :测试系统对时间概念的理解,以及它是否能正确更新记忆。
2.长距离时间推理问题 :要求系统回忆数百个时间步之前的信息,并进行推理。例如,在 25 轮更新后,系统需要回忆 1500 多个更新之前的知识。
以下是论文提供的一些实验结果,展示了 RecallM 在时间推理方面的有效性,并与向量数据库进行了对比:
局限性 该方法的主要局限性在于知识图谱的构建,特别是缺乏指代消解(coreference resolution) 。
例如,假设我们向系统输入以下文本:
Brandon 喜欢咖啡。他想去巴黎旅行。他喜欢猫。
理想情况下,知识图谱应如下所示:
但实际上,我们得到的是:
尽管如此,像以下问题:
仍然能够得到正确答案,因为生成的上下文仍然包含 "Brandon loves coffee",这有助于 LLM 生成正确的回答。
但在某些情况下,可能会出现错误。
一种可能的解决方案是将文本转换为“命题” ,然后将这些命题存入知识图谱,而不是直接存储原始文本。这一想法在论文 Dense X Retrieval: What Retrieval Granularity Should We Use?(https://arxiv.org/abs/2312.06648) 中有所探讨。
结论 总的来说,RecallM 提出了一种仅使用图数据库就能为 LLM 提供长期记忆的方法。
该方法在知识更新和时间推理方面表现出色,但在构建精确知识图谱方面仍然存在挑战。
尽管如此,RecallM 代表了 AI 记忆机制的一项重要进展,未来的研究仍有很多改进和优化的空间。