我们需要执行以下步骤:
概念通常是句子中的名词,它们可以与其他概念建立关系。
这些概念将成为知识图谱中的节点。
此外,为了避免重复,我们会将概念转换为小写,并使用词干提取(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"。
概念的邻居是与其存在关系的其他概念。我们可以使用依存句法分析(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" 建立关系。
我们可以使用图数据库来存储概念及其关系。
在 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属性,用于跟踪该关系在知识图谱更新过程中被提及的次数。
概念的上下文是指它在句子中的使用情况。
因此,对于每个概念,我们首先从知识图谱中获取其上下文信息:
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,因为它的上下文被更新了。
最终的知识图谱如下所示:
假设我们要回答以下问题:
谁想去巴黎旅行?
我们可以利用知识图谱来生成答案,具体步骤如下:
使用与更新知识图谱时相同的 NLP 处理流程,我们确定问题中唯一的概念是 "pari"。
我们在知识图谱中查找与 "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"
}我们根据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如下:
我们首先获取问题中涉及的“核心概念”(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接下来,我们将构建的上下文作为提示词(prompt)传递给 LLM,并生成答案。
最终的提示词如下:
使用以下陈述(statements)来回答问题(question)。这些陈述按照时间顺序排列,每句陈述在其时间步内均为真实:
statements:
.Brandonlovescoffee
.Brandonlovescoffee.BrandonwantstotraveltoParis
.BrandonwantstotraveltoParis
question:
谁想去巴黎旅行?
Answer:由于提示词已经包含了足够的信息,GPT-3.5-Turbo 能够正确生成答案:"Brandon"。
论文作者设计了一项实验,以评估 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 记忆机制的一项重要进展,未来的研究仍有很多改进和优化的空间。
| 欢迎光临 链载Ai (https://www.lianzai.com/) | Powered by Discuz! X3.5 |