AIGC丨如何让 LLMs 的响应更准确?——RAG 在 GPTBots 的实践优化

AIGC丨如何让 LLMs 的响应更准确?——RAG 在 GPTBots 的实践优化
2023年12月14日 14:43 极光开发者

一:简要介绍一下RAG:

1、RAG的一般流程。

什么是RAG(retrievalaugmentedgeneration)系统?我们用一个简单的场景来说明:在GPT中,用户输入问题,例如让GPT总结几百字的中心思想,GPT可以很快做出答复。但是如果用户输入几十万字,GPT就没法处理了。这是现在大语言模型的通病,由于模型设计机制和目前机器的限制,一般的大语言模型只能接受几千-几万个字符的输入。目前虽然有些模型能够通过一些微调方法,例如稀疏注意力,滑动窗口等方法支持20万字级别字符的输入,但是这些方法仍然有一些局限性,例如有些模型输入文本越长,输出效果就越差。而且在现实情况中,用户可能需要在很多个文档中进行查询,这样需要输入的问题长度可能达到几百万几千万字,GPT等LLM(大语言模型)是无力解决这个问题的。

而RAG可以很好地解决这一点,它的方法很简单,如下所示,用户上传文档,我们先将文档解析成文本,然后将文档切成几百字的小切片,然后将切片进行进一步处理(例如将文本转成embedding,我会在后面解释这个概念),存储在向量数据库里。用户问问题时,我们将用户的问题转换成embedding去查询向量数据库,查到和用户问题最相似的几个文本,然后让LLM参考这些文本进行总结。

用更通俗易懂的原理来解释我们为什么要做RAG。假设我们有一个大三学生,他要在没有任何准备的情况下参加一门期末考试。但是我们给他一个宽松的条件,就是把参考资料给他,允许他随时查阅,这些资料就是用户上传的文档,将文档切片相当于把这些资料分成很多的条目,存向量数据库相当于按一定方式把这些条目进行分类,当遇到用户的考试题目时,学生就翻阅这些条目,参考查到的信息给出回复。

2、RAG还是微调?

我们还有另外一种方法让模型学到文档的信息,这种方法叫微调。微调的原理是通过将文本输入模型,让模型内部的参数发生改变来学习到这些文档的词与词之间的联系。相比于RAG,微调更能学习到输入文本的语言风格,但是RAG对知识的提取性比较好。而且这两者并不是互斥的,还是以上面大三学生为例,RAG相当于他不做任何准备参加开卷考试,而微调相当于让他学习一个学期后参加闭卷考试,虽然他经过学习能够理解课程,但是可能会出现有些细节遗忘了而回答不准。如果同时做了RAG和微调,相当于学习了一个学期,有了一定理解后参加开卷考试。

但是微调相比RAG而言,需要花费大量的算力资源和时间进行训练。而RAG的实现的成本较低。RAG的难点在于如何优化。对此,我们公司将RAG流程整合进新推出的产品GPTbots,并对RAG流程做了如下优化:

二:GPTBots如何对RAG流程进行优化:

主要优化点

总体来说,我们做了这些优化,分为两块:

文件解析和文档切片:

1、用一定的规则对表格类文件进行更好的切分。

2、采用更好的embedding模型来进行匹配。

3、将embedding搜索和关键词搜索结合起来。

4、在第3步的基础上,进一步提升关键词匹配的效果。

5、对prompt进行规范化,以取得更好的搜索效果。

1、对表格逐行行切分并分别存储

对于CSV等表格类文件,我们需要不同的处理方法,因为表格类文件每一行的信息都互不相同。我们的处理方法是对数据按行进行切分,用户可以自定义标题是哪几行,我们将标题行和每一行数据存入同一个切片。

举个例子,假如我们有如下表格数据:

我们会一行一行地将它们读成如下切片,分别写入表格,定义第一行为表头。这样用户在问某行某列相关的信息时,我们就能快速定位到对应切片并做出相应回答:

2、更好的embedding匹配

(1)embedding简单解释。

在前面,我们知道我们要将每个切片转换成embedding。什么是embedding?不讲复杂的原理,简单来说,在现在的大语言模型中,我们能够将一个字,一句话,或者一个段落都映射成一个固定维度,例如1536维的向量,这个向量就叫做embedding。

embedding有一个重要特性。有类似语义的文本,他们对应的embedding的向量乘积是一样的,举个例子,我们有三句话:

中国的四大发明对人类文明有着深远的影响。”对应的向量为a,

火药把骑士阶层炸得粉碎,指南针打开了世界市场,而印刷术则变成了科学复兴的手段。”对应的向量为b1,

中国男足0:3负于韩国。”对应的向量为b2

a向量实际对应的embedding为[0.0159,0.0077, 0.0269.......] 共计1536维向量

b1向量实际对应的embedding为[-0.0098,-0.0036, 0.0033......] 共计1536维向量

b2向量实际对应的embedding为[-0.0080,-0.0119, 0.0278........] 共计1536维向量

我们把a和b1进行相乘,最终计算出相似度为0.832,a和b2相乘,计算出相似度为0.769。

很明显,虽然a和b2中都有"中国"这个词,但是表达的含义是完全不一样的。而a和b1,虽然这两句话没有一个字是相同的,但是它们的含义相似。在embedding计算中体现了这一点,a*b2计算的相似度0.832是高于a*b1得到的相似度0.769的。所以embedding是一种语义上的匹配。

(2)embedding如何计算相似度。

假如上面的a是用户输入的问题,我们把用户的问题向量a乘以对应文档切片的向量b1,b2,b3,b4,b5……,找出a*b1,a*b2,a*b3,a*b4,a*b5……中的最大值,对应的切片bi就是和用户问题最相近的切片。

一般来说,这种向量匹配有几种计算方法

点乘:a*b

夹角:a*b/(|a|*|b|)

欧几里得距离:||a-b||

(3)一些题外话,文档切片搜索的优化方法。

在现实中,我们的文档切片数量可能有几十万个,如果对于每个用户的问题我们把每个文档都算一遍a*b,是算不过来的。因此我们会有一系列的近似搜索算法。将这些切片建立一些网络关联,在用户问题进来时,我们根据这些关联,只在一小部分切片中进行搜索。这些算法的代表是HNSW和IVF-PQ,但是这些算法涉及的流程比较复杂,篇幅较长,以后如果有机会我出其他文章详细讲述。

(4)优化方法:采用更好的embedding模型进行匹配。

到这里你应该对embedding有一些初步的概念了。对于我们这种用一个问题匹配一堆文档切片的任务,我们叫它长文本匹配任务(MassiveText EmbeddingTask),这种匹配任务的embedding通常是由transformer模型(例如bert)为基础生成的。对专门用于做这种任务的transformer模型,训练一般分为两个步骤,一个步骤是用大量语义不同的文本对训练出一个有基本分辨能力的模型,然后再用一些语义相反的文本对进行微调。这种长文本匹配任务的embedding模型竞争非常激烈,在今年5月份时,业界最好的文本匹配任务还是openai的text-embedding-ada2,但是经过了几个月后,text-embedding-ada2已经掉落到了二十名。截止到发稿时,中文排行榜上最好的模型是阿里的BTE。我们可以采用排行榜靠前的模型来生成embedding。

3、将embedding搜索和关键词搜索结合起来。

(1)embedding搜索的缺点。

但是这种基于embedding的方法是否准确地搜索到信息么?并不一定。举一个例子,假设我们有一篇网易的财报,内容如下:

网易财报如下:《蛋仔派对》Q1热度不减,AIGC赋能游戏提升用户体验:本季度游戏及相关增值服务收入为201 亿元,同比增长7.6%,其中手游营收134.5 亿元,同比增长16.3%;端游收入51.6 亿元,同比下降9.8%。手游《蛋仔派对》本季度下载量登顶国内iOS 免费榜,端游收入收暴雪停服带来的新游戏短缺下降。520发布会开启玩家共创季,未来将上线包括《零号任务》《全明星街球派对》《巅峰极速》《超凡先锋》等游戏。此外,公司积极应用AIGC赋能新游戏,6月将上线实装智能 NPC系统的《逆水寒》手游。公司继续扩张海外市场,年内《哈利波特》将于海外上线,《蛋仔派对》也有出海计划。

可以看到,这篇财报中提到了网易公司将在这一年中向海外发布新作品《哈利波特》。假设用户上传了很多游戏公司的财报,然后问问题:"哪一家公司计划发售哈利波特?"我们用点乘的方式计算embedding相似度,结果只有0.766。要注意这是一个相当低的相似度,一般高于0.8的才算比较好的。

我们再给出另外一份毫不相关的文本:

特斯拉将要发售新车型。

这个短文本和"哪一家公司计划发售哈利波特?"的相似度是0.812,虽然这句话提的是特斯拉发售新车型,和哈利波特这个作品毫无关系,但是它的相似度要比正确文档高得多。原因是这个短文本和用户的问题表达的语义都是:“某公司将要发布新产品”。而网易的这段话,虽然包含了“年内《哈利波特》将于海外上线”的正确答案(只拿出这句话和用户问题匹配的相似度是0.862),但是由于整段话的语义主要还是网易的经营情况,正确答案的语义被淹没在整体经营情况里。所以我们如果用单纯的embedding搜索不是这么容易搜到这部分信息。

(2)BM25的原理。

为了解决这个问题,一种可能的方案是关键字搜索。我们观察到,用户的问题里是带有"公司"和”哈利波特“的关键字,如果我们用关键字进行搜索,更容易搜索到结果。

这里我们介绍关键字搜索的应用,这次介绍的是基于统计类的BM25搜索方法。还有更好的搜索优化方案,我将在后面提到。

BM25的原理如下。它分为左边的IDF和右边改进后的TF部分。

假设我们有100篇文档,但是每篇文档都有李白这个关键字,只有一篇有莎士比亚这个关键字。那用户如果问一个李白相关的问题,李白这个关键字就是无效的因为每篇文档都有,但是如果问莎士比亚相关的问题,莎士比亚这个关键字就非常有效因为只有一个文档有。IDF的作用就是把每个文档都会出现的关键词降低权重,只在少数文档出现的关键字的权重提高。让莎士比亚的权重高于李白。

而另外一部分是改进后的TF,TF的概念很容易理解,假如有两篇文档,如果一篇文档中”苏轼“出现的次数是5次,另外一篇文档中”苏轼“只出现1次,那么前者必然比后者更可能包含苏轼相关的内容,TF就是特定关键词出现次数除以该文档所有关键词出现的次数。但是如果一篇文档中"苏轼"出现非常非常多次,那其他文档即使有苏轼,也会输给这篇文档,因此BM25将TF值做了修正,如果"苏轼"出现在一定频率以下,BM25算法退化成TF-IDF算法,如果"苏轼"出现的次数很多,BM25就会修正TF将这个值限制在一定范围内。

在刚才的场景下,由于"公司"和"哈利波特"都在用户问题中,这两个关键字对应的修正后的TF值和IDF值都很高,所以BM25是能够搜索出正确的文档切片的。但是它的缺点是不能理解语义。要将它和embedding搜索结合起来才能发挥更好的效果。但是怎么才能把embedding搜索和关键字搜索结合在一起做混合搜索呢?GBTBots对它的实践如下。

(3)在实践中,如何将BM25和embedding结合起来。

我们还是用a*b的点乘方法,关键点在于,把BM25对应的TF和IDF权重拼接到原有向量的后面:

假设用户的问题对应的embedding是:

a=[a_1,a_2,a_3……a_1535,a_1536]

切片文档对应的embedding是:

b1=[b1_1,b1_2,b1_3……b1_1535,b1_1536]

b2=[b1_1,b1_2,b1_3……b1_1535,b1_1536]

b3 =……

当我们进行BM25处理的时候,我们提取的是关键字,而机器能够识别的是数字的值。假设中文单词有100万个词,我们就构造一个字典,把每个关键字都映射成1,2,3,4,5..........100万个index值。我们在原有的1536维之后添加100万维,我们称之为稀疏向量。在文档存向量数据库的时候,我们计算每个稀疏向量的每个关键字对应的修正后的TF值,然后查找它是这100万中对应的那个Index的哪一个,写入向量数据库。这里写很长的0是为了方便理解,实际上我们是不存这些0的,只存对应的index和对应的修正后的TF值。

b1=[b1_1,b1_2,b1_3……b1_1535,b1_15361536,0,0,0……修正后的TF值,……0,0,0,修正后的TF值,……]

b2=[b1_1,b1_2,b1_3……b1_1535,b1_15361536,0,0……修正后的TF值,……0,0,修正后的TF值,……]

b3 =……

在用户查询的时候,我们也提取关键字然后计算对应的IDF值。

a =[a_1,a_2,a_3……a_1535,a_1536,0,0,0……IDF值,……0,0,0,IDF值,……]

搜索的时候,还是a*b1,a*b2,a*b3,a*b4,a*b5,找出其中最大的值。

(4)为什么我们要设计这种向量拼接的方式?

这么设计的巧妙之处在于,

一:在这个向量相乘的过程中,前面的1536维相乘还是老的embedding相似度,后面100万维稀疏向量的相乘,还是修正后的TF值*IDF值=BM25值。这样我们两个值都计算了。

二:我们只把存了修正后的TF值存入向量数据库而没有存IDF值。这样做的原因是,修正后的TF值基本是不变的,由于修正的TF值主要由两个因素计算出来:第一是该文档中该关键字的数量,很明显只要文档不变,这个值就不会变。第二是该文档的字符长度与所有文档字符长度的平均值的比值。由于我们上传文档的切片相对是比较均匀的,所以这个值也不会变。

相对的,IDF是很容易变动的,举个例子,假设原来我们有50篇文档,每篇文档都包含"李白"这个关键词,那李白这个词对应的IDF会很低,但是如果我们再插入50篇文档,那"李白"这个词的IDF会变高,因为有一半文档是没有这个词的。如果把IDF存入数据库,每次用户上传文档我们就需要把所有老文档全部更新一遍,耗费巨大。所以对应的解决方案是,每次用户上传新文档时,我们会用一个文件记录每个关键词在多少个文档中出现过,如果用户上传新的文档,我们只要更新每个关键字在多少个文档中出现的次数即可。

采用BM25+embedding的方案,对于前面的"哪一家公司计划发售哈利波特?"问题,我们提取出关键字"公司","计划","发售","哈利波特",计算BM25,由于前面的文档切片包含“公司”和“哈利波特”,BM25值比较高,所以该文档切片就能被找到了。

4、在第3步的基础上,进一步提升关键词匹配的效果。

上面的BM25+embedding方案仍然存在问题。很多时候,用户问的问题的关键词可能和文章中的关键词并不一致。例如“杜工部”和“杜牧”说的是一个人,如果用户问杜工部相关的问题,用关键词搜索的方法就无法找到杜牧这个词。另一个问题是,这种基于关键字的方法并不能识别出用户问题的意图,只是用统计数值进行粗暴的匹配。

对第一个问题,我们可以做一些近义词的匹配,例如ElasticSearch就提供近义词匹配的功能。在经过近义词匹配后,ElasticSearch还支持用RRF(简单的说,就是embedding做一个排序得分,关键字做一个排序得分,然后将这两种排序得分进行加权得到最终得分)的方法进行排序。

对于第二个问题,有两种解决方法,一种是ElasticSearch提供的ELSER模型,这是一个基于bert蒸馏出的模型,通过学习大量文档得到关键字的权重,相比BM25,这个模型提取出的关键字权重更能反映用户的意图和上下文的关联。第二种方法,我们可以用大语言模型/训练出一个bert模型来提取出文档和用户问题包含的实体,这些实体中包含了用户的真实意图,我们将用户的这些真实意图也存成embedding。在做完原有的topn文档匹配后,我们对这topn个文档基于这些embedding信息进行再次匹配做多路召回,这样可以提升准确率。

对第二个问题的解决方案的一种可能流程如下。

5、对prompt进行规范化,以取得更好的搜索效果。

我们还有没有其他方法来优化搜索效果呢?有的,另外一个方法是prompt改写。

我们用用户的问题和文档之间embedding的相似度进行计算。这样做有一个潜在的问题,因为embedding相似度计算的是两个文本之间的相关性,在训练这些embedding时,大部分文档都是陈述句。而用户问问题时大部分用的是询问句,和embedding训练的方法是不一样的,这样会造成embedding相似度的降低。

而另外一种方法,是将用户的问题进行prompt改写,转换成一个陈述句,经过测试,改写后的陈述句embedding的匹配率要更高。

假如我们有以下的文档:

李白是唐代著名的诗人,他的朋友圈十分广泛,以下是一些与他结交的朋友:

1.高适:是唐朝著名的边塞诗人,与李白在公元744年相遇并结伴游历山水,两人一同寻访夷门、梁园,后直达宋州。安史之乱后,李白被流放,高适拒绝营救,从此两人再无联系。

2.孟浩然:公元725年,李白与孟浩然相遇并结为好友,分别时李白写下了千古流传的《黄鹤楼送孟浩然之广陵》。

3.贺知章:李白进入仕途的引路人就是贺知章。公元742年,李白在京城受到贺知章的赞赏,二人共同饮酒、写诗,成为好友。

4.刘长卿:“五言长城”,公元760年,刘长卿被贬,李白也在奔赴夜郎的途中,在馀干刘长卿与李白相遇,分别时刘长卿送给李白一首《将赴南巴至馀干别李十二》。

5.王昌龄:二者都是“七言绝句”领域的佼佼者,公元739年在湖南岳阳相遇,王昌龄写下了《巴陵送李十二》,后来,王昌龄被贬,李白写下了《闻王昌龄左迁龙标遥有此寄》一诗。

除了以上五位好友外,李白还与其他诗人、官员、道士等许多人有来往。他以文会友,结识了许多志同道合的朋友,为他的诗歌创作提供了不少灵感和素材。

用户可以这样问问题:"谁是李白的朋友",然后我们改写成,"李白的朋友是谁。"前者和上面文本的embedding相似度为0.873,后者和上面文本的embedding相似度为0.882。我们可以看到,经过改写后,文本和相关内容的相似度提升了。因此我们是可以用prompt改写的方法来提升匹配的相似度的。

还有一个担忧,在进行了这种改写后,其他的不相关文档对应的相似度会不会也跟着提升,导致这种改写prompt的方法失去效力?并不会,假设我们给出不相关文档:

李诗歌唱雄伟壮丽的自然,善于描写和歌咏山河,气势豪迈而奔放,不屑于细微的雕琢与对偶的安排,而用大刀阔斧、变幻莫测的手法与线条,涂写心目中的印象和感情,创造艺术的鲜明形象,雄放无比的风格。

李白擅用乐府民歌的语言,很少雕饰,自然率真。乐府精神和民歌语言的运用,达到了极其成熟和解放的阶段。

对应的,我们的问题"谁是李白的朋友"与这篇文档的相关度是0.783,而"李白的朋友是谁"的相似度是0.780,并未提升反而还下降了。所以改写后的内容并不会提升不相干文档的相似度。

以上就是对RAG的基本介绍和GPTBots对它做的一些优化,大语言模型在经过几年的技术积累后,在今年厚积薄发,相关技术也在不断迭代升级。本文只是抛砖引玉,许多方面的优化(例如训练语言模型来做段落间的语义分割,将搜索到的切片排序的损失函数加入模型中做训练)笔者并未提及。希望读者在掌握了本文这些基本的RAG技巧后做更多的尝试和探索。

极光基于RAG原理搭建了LLMOps平台GPTBots。通过「稠密向量+稀疏向量」的组合检索方式,能够更加灵活及高效地完成知识召回工作。除了RAG,GPTBots还支持多LLM接入、可视化FLOW编排、自定义插件及多人协同等功能,能帮助开发者有效地提高AI应用开发效率。欢迎访问「gptbots.ai」进行注册,免费试用体验。

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部