微信扫码
与创始人交个朋友
我要投稿
之前我们探讨过RAG Flow,其实很难有完美的RAG范式匹配所有需求,毕竟业务需求是复杂的:有的需要绝对的准确性,有的则更看重灵活性;有的用来搜索,有的用来聊天,也有的用来服务其他Agent。后续我们会选择一些典型的RAG研究项目做深入实践,希望对大家在应用时提供更多的参考价值。
Self-RAG,很多朋友或许听过,即自省式RAG。其原始Paper与开源项目很容易看的云里雾里,被一些数学符号与公式吓到,且其中有大量内容是关于算法与模型微调,而应用层面介绍较少。本文将尽量的去繁从简,尽量深入浅出的来说清Self-RAG,相信即使你没有足够的技术背景,也能了解其基本原理。
我们将分成上下两篇细说以下内容:
为什么会有Self-RAG?
Self-RAG的工作原理
Self-RAG的实践 - 模型测试
Self-RAG的实践 - 应用测试
基于LlamaIndex
基于LangChain/LangGraph
01
为什么会有Self-RAG
SPRING HAS ARRIVED
Self-RAG是由来自华盛顿大学、IBM人工智能研究院等机构技术专家提出的一种增强的RAG范式,并在基于此理论的原型项目(开源)及其测试中取得了明显优于其他商业大模型或者传统RAG的测试成绩。那么其设计动机来自哪里呢?
这源自于:尽管RAG给LLM带来了一种借助外部知识的补充,来减少知识密集型任务中的事实性错误的方法。但即使抛开上下文长度与响应时间等技术面的顾虑,其在结果输出上也同时带来了一些负面因素,而这正是Self-RAG试图优化的问题:
过度检索。经典RAG不加区分的对输入问题进行相关知识检索(前K个),可能会反而引入无用甚至偏离的内容,并影响输出结果。
输出一致性问题。无法确保输出与检索知识中的事实保持一致,因为大模型本身不能保证绝对的遵循;更何况知识的相关性也会存疑。
我们用更通俗白话的方式描述这两个问题。如果说RAG就是允许一个优秀学生(LLM)在考试时查阅参考书,那么这两个问题就是:
不管考试的题目如何,都去翻书,这显然不是效率最高的方法。正确的方法应该是熟悉的问题快速回答,不熟悉的才求助。
虽然翻阅了很多参考知识,但是有时大脑并不会严格的遵循它来回答(甚至知识也可能翻错了),最终仍然会解答错误。
当然,在实际构建RAG应用时,我们一般也会通过Workflow的设计以及Prompt的精心调优在一定程度上尝试优化这两个问题。比如:
在检索之前借助LLM来评判是否需要检索
在Prompt指令中要求LLM严格遵循参考知识进行回答
借助LLM来评估答案,并通过多次迭代来提高答案质量
尽管这些方案在很多时候也能工作的不错,但也会带来诸如复杂度、响应性能以及引入更多的不可控等潜在问题。那么Self-RAG又是怎么做的呢?
02
Self-RAG的工作原理
SPRING HAS ARRIVED
Self-RAG的基本工作原理并不复杂。其最大的不同之处在于:Self-RAG通过在模型层面的微调,让大模型本身直接具备了判断按需检索与自我评判的能力,并进而通过与应用层的配合,达到提升生成准确性与质量的问题。
让我们一步步的来认识Self-RAG的不同。
Self-RAG的基本流程
Self-RAG的基本工作流程用这张图简单说明:
1. 检索判断。相对于经典RAG中直接用输入问题检索知识文档,在Self-RAG中首先由模型来决定是需要检索,还是直接输出。
2. 按需检索:
如果无需检索(比如”给我创作一首歌颂母爱的诗歌“),则由模型直接生成。
如果需要检索(比如”介绍我公司最受欢迎的产品“),则让应用执行检索动作,检索出最相关的Top_K知识。
3. 增强生成。使用检索出的K个相关知识与输入问题组装Prompt,一起生成K个输出。(作为对比,在经典RAG中通常是把排序后的K个知识一起组装到Prompt后交给LLM做一次生成输出)。
4. 评判、选择与输出。对上一步中增强生成的K个输出响应进行评估打分,并选择分数最高的一个作为最终结果。
Self-RAG中的四种评判指标
仔细看上面的流程,会发现一共会涉及到两个环节需要借助LLM进行评判:
是否需要知识检索以实现增强?
如何对多个输出的响应结果计算评分?
在这两个环节中Self-RAG共设计了四种类型的评判指标,在原文中用了比较严谨的科学化定义:
这里用简单的方式先来理解这四种评判指标,后续再看如何生成这些指标:
1. Retrieve:是否需要知识检索
表示LLM后续的内容生成是否需要做额外知识检索。取值:
[No Retrieval]:无需检索,LLM直接生成
[Retrieval]:需要检索
[Continue to Use Evidence]:无需检索,使用之前内容
2. IsRel:知识相关性(知识 => 问题)
表示检索出来的知识是否提供了解决问题所需的信息。取值:
[Relevant]:检索出来的知识与需要解决的问题足够相关
[Irrelevant]:检索出来的知识与需要解决的问题无关
3. IsSup:响应支持度(知识 => 响应)
表示生成的响应内容是否得到检索知识的足够支持。取值:
[Fully supported]:输出内容陈述被检索的知识完全支持。
[Partially supported]:输出内容陈述只有部分被检索的知识所支持。
[No support / Contradictory]:输出内容不被检索的知识所支持(即编造)。
一个例子
比如提供的知识中只有“中国的首都是北京”,而输出内容中有“北京是中国的首都,北京最受欢迎的景点是长城。",那么这里的后半部分输出在提供的知识中就没有得到支持。所以属于部分支持即[Partially supported]。
4. IsUse:响应有效性(响应 => 问题)
表示生成的响应内容对于回答/解决输入问题是否有用。取值:
[Utility : x]:按有效的程度x分成1-5分,即最高为[Utility:5]
如何生成评判指标?
那么这四种类型的评判指标如何生成呢?又是在什么时候生成?
一种容易想到的方式是借助LLM与Prompt来判断,比如把输入问题与检索知识交给大模型,要求其判断两者相关性,从而得出IsRel(相关性)指标。这种方式的好处是完全在应用层实现,但缺点是:
过多的LLM交互会带来响应性能下降与tokens成本升高
生成的评判指标只能定性的判断,难以量化
Self-RAG采用了一种不同的方法:通过微调训练LLM,让LLM在推理过程中实现自我反省,直接输出代表这些指标的标记性Tokens,即“自省Tokens”。
比如,LLM在生成的时候,可能会发现需要额外的知识补充,就会输出[Retrieval]并暂停,表示需要知识检索;在获得足够的知识与上下文后,会在输出答案时做自我评估与反省,并插入[Relevant],[Fully supported]等这样的标记性token。我们看两个例子:
1.以下LLM的输出内容中携带了相关性等几个指标
Response:[Relevant] 字节调动的Coze是一个大语言模型的应用开发平台,其提供了一站式开发LLM应用的相关工具、插件与编码环境. [Partially supported] [Utility:5]
2. 以下LLM输出中携带了[Retrieval],表示需要”求助“外部知识
Response: 当然![Retrieval]<paragraph>
通过微调给LLM引入新的Tokens词汇,Self-RAG让LLM自身更加智能并适应后续流程的需要。
当然,这样的模型需要特殊的训练(关于模型的训练我们在后面介绍)。在Self-RAG的开源项目中提供了一个基于llama微调的模型selfrag_llama2_7b。后续我们将借助这个模型来进行Self-RAG的实际应用测试。
如何参考指标来计算评分?
一起来理解这个字段与相关算法。
【logprobs - 对数概率】
LLM的输出其实就是根据提示预测下一个词元(token)并不断循环,直到全部完成的过程。那么它是怎么预测下一个token呢?并非它明确的知道下一个词元(如果是那样,每次输出就是确定的结果),而是经过一系列复杂运算与神经网络处理,最终输出含有多个可能的下一个token及其概率的列表,然后LLM会从其中选择一个来输出。过程类似(简化掉最复杂的推理部分)下图:
LLM最后从多个候选tokens中选择“机器”这个词输出,并将其附加到输入提示,进入下一次生成。而logprobs就是用来保存这里每一步预测时的多个可能的token概率(取对数,所以叫对数概率)。
【评判算法】
对于上面所说的Self-RAG模型中输出的标记性tokens,也一样可以找到对应的概率。比如一次输出中出现了[Fully supported]这个token,那么说明LLM推理的时候计算出了[Fully supported]、[Partially supported]等可能的token输出的概率,但最后选择了[Fully supported]。因此,在评估这次输出的IsSup(响应支持度)的分数时,就可以基于logprobs中这些tokens的概率来计算(上面例子中,显然[Fully supported]这个token的概率越高,说明支持度越高)。
Self-RAG给出了上面三种评判类型(Retrival类型无需量化)的评估算法:
【IsRel】:知识相关度
即用“relevant”token的概率占本类型两种token的概率和的比例。
【IsSup】:响应支持度
即用“fully supported”token的概率占本类型三种类型token概率和的比例,加上“partially supported”token的概率所占比例。但后者要乘以权重0.5。
【IsUse】:响应有效性
(w = {−1, −0.5, 0, 0.5, 1})
即用本类型的5种类型token的概率占总概率的比例乘以对应的权重(分别为从-1到1不等),然后求和。
这里以IsSup为例,参考官方的基准测试代码,可以模拟对应的算法实现:
#响应支持度类型的三种标记token
_IS_SUPPORTED_TOKENS = [
"[Fully supported]",
"[Partially supported]",
"[No support / Contradictory]",
]
#计算响应支持度得分(IsSup)
def calc_is_supported_score(
pred_tokens: List[int], pred_log_probs_dict:
List[Dict[str, float]]
) -> float:
#最终的的得分
is_supported_score = 0
#首先找到输出的标记token所在的位置,找到就可退出,这个类型的标记token
#只有一个
token_appear_id = -1
for tok_idx, token in enumerate(pred_tokens):
if token in _IS_SUPPORTED_TOKENS:
token_appear_id = tok_idx
break
#如果找到了token位置 ,比如为[fully supported]的位置
if token_appear_id > -1:
#在这个位置上查找所有本类型的三种标记token的输出概率
#保存到issup_score_dict这个字典中
issup_score_dict = {}
for token in _IS_SUPPORTED_TOKENS:
prob = pred_log_probs_dict[token_appear_id][token]
issup_score_dict[token] = np.exp(float(prob))
#用上面的计算公式,来计算最终分数
is_supported_score = (
issup_score_dict["[Fully supported]"]
+ 0.5 * issup_score_dict["[Partially supported]"]
) / np.sum(list(issup_score_dict.values()))
return is_supported_score
整个算法还是比较清晰的:在大模型输出中找到需要的标记性token的位置,然后找到此位置的token预测时的对应概率,然后按照公式计算即可。
需要说明两点:
由于logprobs为对数概率,所以在计算时用指数函数exp转化为正常概率
实际使用时,需要参考使用的推理工具(比如是vLLM或者Llama_cpp)文档,找到其输出参数中的pred_tokes与pred_log_probs_dict这两个内容,作为这里算法输入
至此,我们已经把除模型微调之外的Self-RAG基本原理介绍完。下篇中我们将基于selfrag_llama2_7b这个模型来实现真正的Self-RAG应用。
53AI,企业落地应用大模型首选服务商
产品:大模型应用平台+智能体定制开发+落地咨询服务
承诺:先做场景POC验证,看到效果再签署服务协议。零风险落地应用大模型,已交付160+中大型企业
2024-03-30
2024-04-26
2024-05-10
2024-04-12
2024-05-28
2024-04-25
2024-05-14
2024-07-18
2024-04-26
2024-08-13
2024-12-22
2024-12-21
2024-12-21
2024-12-21
2024-12-21
2024-12-20
2024-12-20
2024-12-19