在上一篇中,我们介绍了自省式的RAG(Self-RAG)的诞生动机与基本原理,其核心思想可以总结为:

  • 只在必要的时候才执行检索

  • 借助指标与评分算法从多次LLM输出中选择最优答案

  • 通过微调让LLM在输出中带有自省的token标记

本文将基于Self-RAG开源项目中的selfrag_llama2_7b这个微调模型,展示如何基于这个模型来构建完整的Self-RAG应用。内容包括:

  • 模型测试

  • **构建Self-RAG应用
    **

  • 应用的优化思考

  • 模型的微调方法

01

模型测试:selfrag_llama2_7b

我们首先来直接测试与感受带有自省tokens输出能力的selfrag_llama2_7b模型,观察这个模型输出的不同之处。这里使用llama_cpp作为本地LLM推理工具(也可以选择vLLM):

1.首先安装Llama_cpp和huggingface工具:


pip install llama\_cpp\_python  
pip install huggingface-hub


2.下载llama_cpp推理的gguf版本的模型:


huggingface-cli download m4r1/selfrag\_llama2\_7b-GGUF selfrag\_llama2\_7b.q4\_k\_m.gguf --local-dir ./model --local-dir-use-symlinks False


3.运行如下简单的测试代码:


from llama\_cpp import Llama  
\_MODEL\_KWARGS = {"logits\_all": True, "n\_ctx": 2048, "n\_gpu\_layers":200}  
\_GENERATE\_KWARGS = {"temperature": 0.0,"top\_p": 1.0,"max\_tokens": 1024,"logprobs": 1000}  
  
#模型  
llm=Llama(model\_path="./model/selfrag\_llama2\_7b.q4\_k\_m.gguf",\*\*\_MODEL\_KWARGS)  
  
#格式化Prompt,注意必须按照此格式输入问题和关联知识  
def format\_prompt(input, paragraph=None):  
    prompt = "### Instruction:\\n{0}\\n\\n### Response:\\n".format(input)  
    if paragraph is not None:  
       prompt += "\[Retrieval\]<paragraph>{0}</paragraph>".format(paragraph)  
    return prompt  
  
#测试两个问题,一个无需检索,一个需要检索知识  
query\_1 = "写一首歌颂母爱的小诗"  
query\_2 = "能否介绍下字节跳动的AI平台Coze?"  
queries = \[query\_1, query\_2\]  
  
#分别测试,并打印出结果(response); details用来查看更详细的tokens输出细节  
for query in queries:  
    pred = llm(format\_prompt(query),\*\*\_GENERATE\_KWARGS)  
    print("\\nResponse: {0}".format(pred\["choices"\]\[0\]\["text"\]))  
    print(pred\["choices"\]\[0\])


来看第一个问题的输出:

Response: Mother love, so pure and true,A bond that’s stronger than any tie.[No Retrieval]You give your all, 【此处省略】…follow its owners everywhere.[Utility:5]

第一个问题是一个创作问题,并不涉及具体事实,显然无需检索额外知识。因此推理结果中带有**[No Retrieval]**的标记(红色部分)。

再看第二个问题的输出:

Response: Certainly![Retrieval]Coze is a platform.[Utility:5]

第二个问题是一个事实性问题。这里可以看到,在推理过程中,LLM会发现需要额外知识补充,就会输出**[Retrieval]**的标记(红色部分)。

4.针对第二个问题模拟带入检索知识,再次观察模型输出:

这里用paragraph模拟一段检索出来的知识,并在prompt中带入。只需要修改少量的代码即可(其他部分不变):


paragraph="""Coze是字节跳动的大模型应用一站式开发平台。"""  
...  
#注意此处把输入参数paragraph默认成上面的知识  
def format\_prompt(input, paragraph=paragraph):  
...


此时模型对第二个问题的输出如下:

Response: [Relevant]Coze is a platform developed by ByteDance, the parent company of TikTok, for building and deploying large-scale AI models.[Fully supported][Continue to Use Evidence]It provides an all-in-one development platform that includes tools for training, testing, and deploying AI models.[Utility:5]

可以看到,LLM根据带入的知识生成了输出,并且响应中带有自省token标记(红色部分)。比如包括了相关性[Relevant],响应支持度[Fully Supported],以及答案有用性[Utility:5],这些也都是后续需要进一步评分的指标。

02

构建Self-RAG应用

基于上面测试的微调模型(selfrag_llama2_7b)来简单构造一个上层应用,用来实现如下的完整RAG Flow:

有了前面的模型基础,这个应用实现本身并不复杂。其中相对复杂的部分是**如何对多个增强生成的响应结果进行评分,从而选择“最优解”。**这个评分算法在上一篇中已经介绍过:借助LLM输出中的一个特殊信息 – 每个位置输出的可能token及其概率值。一共有三个指标评分:

  • 知识相关度:检索的知识与输入问题的相关性。

  • 响应支持度:检索的知识对最后输出的支持程度。

  • 答案有用性:最后输出答案对输入问题的有用性。

上篇中已经给出了响应支持度评分的算法,而答案有用性与之类似,参考实现即可,此处不再列出。而知识相关度的计算比较简单:

#相关度计算:[Relevant]输出的概率占比  
def _relevance_score(pred_log_probs: Dict[str, float]) -> float:  
    rel_prob = np.exp(float(pred_log_probs["[Relevant]"]))  
    irel_prob = np.exp(float(pred_log_probs["[Irrelevant]"]))  
    return rel_prob / (rel_prob + irel_prob)

有了这三个评分的基础算法,再设计一个简单的查询引擎,大致结构如下:

【主测试程序】

主程序的逻辑很简单。我们直接构造几个简单的文本作为知识库;然后创建检索器(retriever)与生成器(llm)用来构造查询引擎;最后测试两个输入问题:

import os  
from llama_index.llms.llama_cpp import LlamaCPP  
from llama_index.core import Document, VectorStoreIndex  
from llama_index.core.retrievers import VectorIndexRetriever  
from pathlib import Path  
  
#导入SelfRAGQueryEngine引擎  
from selfrag_queryengine import SelfRAGQueryEngine  
  
#模型参数,注意打开logits_all参数  
_MODEL_KWARGS = {"logits_all": True, "n_ctx": 2048, "n_gpu_layers": -1}  
_GENERATE_KWARGS = {"temperature": 0.0,"top_p": 1.0,"max_tokens": 1000,"logprobs": 32016,}  
  
# 下载的selfrag_llama2_7b模型的保存目录  
download_dir = "../../model"  
  
# 创建测试文档,此处直接用documents构建,方便观察  
documents = [  
    Document(text="Xiaomi 14 is the latest smartphone released by Xiaomi. It adopts a new design concept, the body is lighter and thinner, equipped with the latest processor, and the performance is more powerful."),  
    _"""此处省略了更多的Document对象"""_  
]  
  
# 创建retriever  
index = VectorStoreIndex.from_documents(documents)  
retriever = VectorIndexRetriever(index=index,similarity_top_k=5)  
  
# 创建llm(采用llama_cpp)  
model_path = Path(download_dir) / "selfrag_llama2_7b.q4_k_m.gguf"  
llm = LlamaCPP(model_path=str(model_path), model_kwargs=_MODEL_KWARGS, generate_kwargs=_GENERATE_KWARGS)  
  
# 构造查询引擎,传入llm与retriever  
query_engine = SelfRAGQueryEngine(llm, retriever)  
  
# 查询一:无需检索的创作问题  
response = query_engine.query("write a poem about beautiful sunset")  
  
# 查询二:需要检索的事实性问题  
response = query_engine.query("Tell me some truth about xiaomi 14 phone, especially about its battery and camera?")

【构建查询引擎】

这里代码中的核心组件是SelfRAGQueryEngine,其核心的query接口实现如下:

"""此处省略初始化代码"""``def query(self, query_str: str) -> str:               #调用模型生成               response = self.llm.complete(_format_prompt(query_str))               answer = response.text                  #如果模型反馈需要检索               if "[Retrieval]" in answer:                       #检索多个相关知识                   print_text("需要检索知识,开始检索...\n", color="blue")                   documents = self.retriever.retrieve(query_str)                   print_text(f"共检索到 {len(documents)} 个相关知识\n", color="blue")                      #用检索到的多个知识组装多个Prompt                   paragraphs = [                       _format_prompt(query_str, document.node.text) for document in documents                   ]                                     #重新生成并评估每个结果的评分                   print_text("=====开始:重新生成并评估====\n", color="blue")                   llm_response_per_paragraph,paragraphs_final_score = \                       self._regen_then_eval(paragraphs)                   print_text("===结束:重新生成并评估====\n", color="blue")                      #选择评分最高的答案                   best_paragraph_id = max(                       paragraphs_final_score, key=paragraphs_final_score.get                   )                   answer = llm_response_per_paragraph[best_paragraph_id]                   print_text(f"已选择最佳答案: {answer}\n", color="blue")                  else:                   print_text("无需检索知识,直接输出答案\n",color="green")                  #输出结果,此处需要去除答案中的特殊token               answer = _postprocess_answer(answer)               print_text(f"最终答案: {answer}\n", color="green")               return str(answer)

【实现_regen_then_eval】

这里有个重要方法是**_regen_then_eval**,用来对多个输入Prompt做生成与评分,最后返回响应及评分(评分算法参考上文说明):

def _regen_then_eval(self, paragraphs: List[str]) ->Tuple[Dict[int,str],Dict[int,float]]:  
            """  
            运行评判模块,对给定的段落进行生成,并评分。  
            参数:  
            paragraphs (List[str]): 包含要评分的段落的列表。  
            返回:  
            Tuple[Dict[int,str],Dict[int,float]]: 包含生成的结果和评分。  
            """  
            paragraphs_final_score = {}  
            llm_response_text = {}  
  
            for p_idx, paragraph in enumerate(paragraphs):  
                #循环生成多个响应  
                response = self.llm.complete(paragraph)  
                pred = response.raw  
                llm_response_text[p_idx] = response.text  
  
                #从raw信息中取得tokens概率相关信息  
                #top_logprobs保存了每个位置上输出每个token的概率  
                logprobs = pred["choices"][0]["logprobs"]  
                pred_log_probs = logprobs["top_logprobs"]  
  
                # 计算isRel分数,相关度为第一个token,直接传入0  
                isRel_score = _relevance_score(pred_log_probs[0])  
  
                # 计算isSup分数  
                isSup_score = _supported_score(logprobs["tokens"], pred_log_probs)  
  
                # 计算isUse分数  
                isUse_score = _useful_score(logprobs["tokens"], pred_log_probs)  
  
                #最终得分  
                paragraphs_final_score[p_idx] = (  
                    isRel_score + isSup_score + 0.5 * isUse_score  
                )  
  
                print_text(  
                    f"输入: {paragraph}\n响应: {llm_response_text[p_idx]}\n评分: {paragraphs_final_score[p_idx]}\n",  
                    color="blue",  
                 )  
                print_text(  
                    f"已完成 {p_idx + 1}/{len(paragraphs)} 段落\n\n", color="blue"  
                )  
  
            return llm_response_text, paragraphs_final_score

【测试结果】

这样就构建了一个简单的Self-RAG范式的应用,直接运行就可以看到上面两个测试问题的处理过程及区别:

问题一:

由于是个创作型的问题,因此llm认为无需检索,所以直接输出答案。

问题二:

这是个需要基于事实回答的问题,因此LLM认为需要检索。在检索知识后,应用通过循环重新生成并进行评分(此处只展示了第一个)。比如这里第一个知识段落生成的评分为1.767657…。在所有响应都被重新评估后,最终有个答案“脱颖而出”(即评分最高的答案):

不错,应用测试的输出符合对Self-RAG的预期。

03

应用的优化思考

Self-RAG借助在模型层次的微调使得LLM自身具备了自我判断按需检索与自我反省的能力,在很大程度上减少了应用层面的复杂性,且不会降低模型自身的能力。当然,我们完全可以结合RAG其他范式在不同环节的优化方法,来让Self-RAG工作的更加完美。

在上面的原型应用中,有一个比较明显的优化策略来自于这里的Self-RAG的多次生成是基于知识检索出的top_K文档逐个生成,但是在实际测试中我们发现有几个问题:

  • 由于检索出的文档已经过了语义相似度排序,所以生成的结果评分很多时候会与文档排序一致,这就丧失了评估的意义

  • 由于实际应用中知识结构的复杂性,很多时候需要把多个知识一次输入LLM做生成,以给予LLM更完整的“参考”

  • 没有采用并行的生成方式

因此,如果想充分利用Self-RAG的自我评估能力,可以能够根据实际需要优化这里检索(retriever)策略。比如做多次知识检索,并针对多次检索的结果分别做生成与评估;而不是针对一次检索的多个文档做生成评估,这样既可以给予LLM更多的上下文知识,也能利用Self-RAG的自省机制在多次生成中获取质量最高的输出。实现多次检索的策略可以更加灵活,比如:

  • 借助查询Rewrite后再次检索知识文档,可使用不同的Rewrite算法

  • 使用不同的检索算法(比如关键词检索与语义检索)获得不同的知识文档

  • 检索后使用不同的Rerank算法做知识文档的重排后进行多次生成

更多的优化方法与策略还需要在实际应用中不断发现与完善。

04

模型的微调

上面我们依赖于selfrag_llama2_7b这个微调模型展开了一系列的测试,最后简单说说如何训练这样的模型。

【训练目标】

让LLM能够生成带有自省tokens(比如[Irrelevant]或者[Relevant])的文本。

【训练方法】

为了达到上述目标,Self-RAG需要训练两个模型,一个是评估模型,一个是**生成模型,**这两个模型都需要用自省tokens来扩展词汇表并进行训练。参考Self-RAG的方案介绍,整个过程分为两个阶段:

  1. 评估模型训练

评估模型的训练是为了能够根据指令与输入,直接生成不同类型的自省tokens(比如[Irrelevant]或者[Relevant])。因此需要大量的输入文本并标记好对应的自省token,这很显然无法用人工来完成,因此Self-RAG借助了GPT4来批量生成训练数据。所以这个阶段的步骤是:

  • 训练数据创建:使用 GPT4来帮助生成训练数据。

  • 评估模型训练:使用生成的训练数据来训练评估模型。

2. 生成模型训练

在评估模型训练完成后,此时会借助检索与训练好的评估模型,来对大量的输入-输出文本对插入自省token,形成“增强后”的输出文本,进而作为生成模型的训练数据。注意看下图中的两个例子,右边的例子中借助评估模型的判断,发现需要检索,因此插入了[Retrieve],[Relavant]等标记token,据此形成了一条新的训练数据。

训练数据准备完成后,最后再用来对模型进行训练,让模型不仅能够预测下一个内容的token,还能预测自省的token标记。

具体的训练数据生成代码、微调脚本等可以参考Github上Self-RAG项目。

可能大家都想学习AI大模型技术,也想通过这项技能真正达到升职加薪,就业或是副业的目的,但是不知道该如何开始学习,因为网上的资料太多太杂乱了,如果不能系统的学习就相当于是白学。为了让大家少走弯路,少碰壁,这里我直接把全套AI技术和大模型入门资料、操作变现玩法都打包整理好,希望能够真正帮助到大家。

👉AI大模型学习路线汇总👈
大模型学习路线图,整体分为7个大的阶段:(全套教程文末领取哈)
在这里插入图片描述

第一阶段: 从大模型系统设计入手,讲解大模型的主要方法;

第二阶段: 在通过大模型提示词工程从Prompts角度入手更好发挥模型的作用;

第三阶段: 大模型平台应用开发借助阿里云PAI平台构建电商领域虚拟试衣系统;

第四阶段: 大模型知识库应用开发以LangChain框架为例,构建物流行业咨询智能问答系统;

第五阶段: 大模型微调开发借助以大健康、新零售、新媒体领域构建适合当前领域大模型;

第六阶段: 以SD多模态大模型为主,搭建了文生图小程序案例;

第七阶段: 以大模型平台应用与开发为主,通过星火大模型,文心大模型等成熟大模型构建大模型行业应用。

👉大模型实战案例👈
光学理论是没用的,要学会跟着一起做,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
在这里插入图片描述

👉大模型视频和PDF合集👈
观看零基础学习书籍和视频,看书籍和视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
在这里插入图片描述

在这里插入图片描述

👉学会后的收获:👈
• 基于大模型全栈工程实现(前端、后端、产品经理、设计、数据分析等),通过这门课可获得不同能力;

• 能够利用大模型解决相关实际项目需求: 大数据时代,越来越多的企业和机构需要处理海量数据,利用大模型技术可以更好地处理这些数据,提高数据分析和决策的准确性。因此,掌握大模型应用开发技能,可以让程序员更好地应对实际项目需求;

• 基于大模型和企业数据AI应用开发,实现大模型理论、掌握GPU算力、硬件、LangChain开发框架和项目实战技能, 学会Fine-tuning垂直训练大模型(数据准备、数据蒸馏、大模型部署)一站式掌握;

• 能够完成时下热门大模型垂直领域模型训练能力,提高程序员的编码能力: 大模型应用开发需要掌握机器学习算法、深度学习框架等技术,这些技术的掌握可以提高程序员的编码能力和分析能力,让程序员更加熟练地编写高质量的代码。

👉获取方式:
😝有需要的小伙伴,可以保存图片到wx扫描二v码免费领取【保证100%免费】🆓

在这里插入图片描述

Logo

更多推荐