LLM应用开发-RAG系统评估与优化

bar

前言

Hello,大家好,我是GISer Liu😁,一名热爱AI技术的GIS开发者,在上一篇文章中,我们学习了如何基于LangChain构建RAG应用,并且通过Streamlit将这个RAG应用部署到了阿里云服务器;😀

在本文中作者将通过:

  • LLM应用评估思路

  • 生成内容评估优化

  • 检索内容评估优化

帮助读者学习如何对自己开发的RAG应用进行评估,并通过科学的方法优化结果提高应用性能


一、LLM应用评估策略

1.评估思路

在上一篇文章中,我们基于LangChain构建了一个RAG应用,并使用Streamlit将其部署到阿里云服务器。回顾整个开发流程,我们可以总结出以下几点LLM应用开发特性:

  • 开发流程快捷:以调用API、以LLM为核心的大模型开发流程快捷,更注重于验证和迭代

  • 成本效益高:LLM的强大泛化性使其能够解决许多细节任务,且成本仅为传统应用开发的百分之一

以开发上一篇文章中的RAG应用为例,作者从选择数据、构建向量数据库、选择LLM API、配置Embedding模型,到开发问答检索链,仅用4小时即可完成。并且开发成本不到10元

传统AI应用开发中,我们需要不断收集大量数据来训练和微调模型以优化性能,而LLM应用开发只需通过几个错误案例调整即可获得良好结果无需大量样本迭代。从这里我们可以提取出一点方法论LLM应用开发抓住业务痛点,从错误的案例中分析应用的不足并加以改善,便可快速提升模型性能

llm 应用优化评估
Json内容输出为例,在构建LLM应用程序时,我们会经历以下流程:

少样本调整Prompt:首先,我们会从1-3个小样本中调整我们的Prompt,确保应用能在小样本上成功生成目标结果,例如要求LLM输出Json格式的内容,包括特定字段,如任务名称、任务类型等。初步测试样本后,系统可以在简单任务中正常输出。

Bad Case定向优化:接着,当我们对系统进行进一步测试时,可能会遇到一些棘手的问题,例如要求输出的字段名称,字段格式要求,每个字段有其取值范围;我们针对这些突出问题定向调整、手动解决;

模型性能与开发成本的权衡:最终,当我们积累的问题越来越多时,逐个调整或满足每个问题的要求变得不现实。因此,我们开始选择一些自动化手段来评估模型;以质量换成本,权衡利弊;

这个过程中,我们无需追求完美,而是要综合考虑时间成本和模型性能。得益于LLM的飞速发展,我们的应用通常经过几轮调整,就能达到很好的表现。

!但这仅仅的对简单的,具有标准答案的任务,性能指标在这些任务样本上评估并不明显;在没有简单答案或标准答案的情况下,评价指标才能准确反应模型的性能;

2.评估方法

通过识别和解决“Bad Case”,我们将不断优化应用的性能和精度。具体流程为:

  1. 发现Bad Case并将每个Bad Case加入验证集
  2. 针对性调整对应的Prompt并检索架构。
  3. 每次优化后重新验证整个验证集确保原有Good Case不受影响

对于:

  • 小验证集,采用人工评估
  • 大验证集,则采用自动化评估,以降低成本和时间消耗。

下面我们开始讲解人工评估方法;

环境部署

首先我们加载我们的向量数据库与检索链;继续使用Faiss向量数据库和Mistral模型;代码如下:

# 添加历史对话功能
from __future__ import annotations
import os
from langchain_mistralai.chat_models import ChatMistralAI
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
from langchain_community.vectorstores import FAISS
# 封装Mistral Embedding 
import logging
from typing import Dict, List, Any
# 构建检索问答链
from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA

from langchain.embeddings.base import Embeddings
from langchain.pydantic_v1 import BaseModel, root_validator

logger = logging.getLogger(__name__)

class MistralAIEmbeddings(BaseModel, Embeddings):
    """`MistralAI Embeddings` embedding models."""

    client: Any
    """`mistralai.MistralClient`"""

    @root_validator()
    def validate_environment(cls, values: Dict) -> Dict:
        """
        实例化MistralClient为values["client"]

        Args:
            values (Dict): 包含配置信息的字典,必须包含 client 的字段.

        Returns:
            values (Dict): 包含配置信息的字典。如果环境中有mistralai库,则将返回实例化的MistralClient类;否则将报错 'ModuleNotFoundError: No module named 'mistralai''.
        """
        from mistralai.client import MistralClient
        api_key = os.getenv('MISTRAL_API_KEY')
        if not api_key:
            raise ValueError("MISTRAL_API_KEY is not set in the environment variables.")
        values["client"] = MistralClient(api_key=api_key)
        return values

    def embed_query(self, text: str) -> List[float]:
        """
        生成输入文本的 embedding.

        Args:
            texts (str): 要生成 embedding 的文本.

        Return:
            embeddings (List[float]): 输入文本的 embedding,一个浮点数值列表.
        """
        embeddings = self.client.embeddings(
            model="mistral-embed",
            input=[text]
        )
        return embeddings.data[0].embedding

    def embed_documents(self, texts: List[str]) -> List[List[float]]:
        """
        生成输入文本列表的 embedding.

        Args:
            texts (List[str]): 要生成 embedding 的文本列表.

        Returns:
            List[List[float]]: 输入列表中每个文档的 embedding 列表。每个 embedding 都表示为一个浮点值列表。
        """
        return [self.embed_query(text) for text in texts]

# 实例化Embedding模型
embeddings_model = MistralAIEmbeddings()
# 加载向量数据库
loaded_db = FAISS.load_local("./db/GIS_db", embeddings_model, allow_dangerous_deserialization=True)

# 获取环境变量 MISTRAL_API_KEY
api_key = os.environ['MISTRAL_API_KEY']

# 初始化一个 ChatMistralAI 模型实例,并设置温度为 0
llm = ChatMistralAI(temperature=0,api_key=api_key)


template = """使用以下上下文来回答最后的问题。如果你不知道答案或者不确定结果,只需要你不知道,不要试图编造答
案。最多使用三句话。尽量使答案简明扼要。要求使用中文回答。
{context}
问题: {question}
"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template)


# 构建检索问答链
qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=loaded_db.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

# 测试结果
question_1 = "GIS的组成?"
result = qa_chain({"query": question_1})
print(result["result"])

测试一下:
result
没问题!😺

①人工评估方法

适用范围:系统开发初期、验证集体量较小
重点:系统评估与业务强相关,设计具体的评估方法要结合业务深入考虑;

在大模型的应用开发过程中,人工评估是一个不可或缺的环节。其可以帮助我们确保模型的输出质量,尤其是在系统开发的初期,当验证集的体量相对较小的时候

(1)量化评估

量化评估是不同版本应用性能比较的关键对每个验证案例的回答进行打分,我们可以计算所有案例的平均分来评估系统的表现。量化的量纲可以是0-5或0-100,当然这取决于业务需求和个人风格。
这里,作者给出两个验证案例:

  • GIS是什么?
  • GIS的应用场景?
    接下来我们分别用版本A prompt(简明扼要) 与版本B prompt(详细具体) 来要求模型做出回答:
    下面作者的代码:
# 测试结果
question_1 = "GIS是什么"
result1 = qa_chain({"query": question_1})
print(result1["result"])
question_2 = "GIS的应用场景?"
result2 = qa_chain({"query": question_2})
print(result2["result"])

运行结果如下:
量化评估
可以看出其简单粗暴,是提示词工程常见的应用;

(2)多维评估

大模型的回答需要在多个维度上进行回答的结果进行评估,每个维度都应设计相应的评估指标和量纲;下面是评价维度,取值区间默认为[0,1]

  • 知识查找正确性
  • 回答幻觉比例
  • 回答正确性
  • 逻辑性
  • 通顺性
  • 智能性

代码如下:

# 测试结果
print("问题:")
question = "遥感的微波的概念是什么"
print(question)
print("模型回答:")
result = qa_chain({"query": question})
print(result["result"])

经过查询,原始资料为:

微波的的波长比可见光和红外波长,受大气层中云雾的散射影响小,穿透性好,因此能全天候进行遥感。使用微波的遥感被称为微波遥感,它通过接受地面物体发射的微波辐射能量,或接收器本身发出的电磁波束的回波信号,对物体进行探测、识别和分析。由于微波遥感采用主动方式进行,其特点是对云层、地表植被、松散沙层和干燥冰雪具有一定的穿透能力,且不受光照等条线限制。因此微波遥感是一种全天候、全天时的遥感技术。

回答结果为:

问题:
遥感的微波的概念是什么
模型回答:
遥感的微波(Microwave Remote Sensing)指的是使用微波辐射的散射和辐射信息来分析、识别地物或提取专题信息的技术。微波遥感可以使用人工辐射源(如微波雷达)发射微波辐射,然后根据地物散射该辐射返回的反向反射信号来探测和测距。由于微波波长比可见光和红外波长长,因此微波遥感具有较好的穿透能力,可以对云层、地表植被、松散沙层和干燥冰雪等物体进行探测,且不受光照等条件的限制,是一种全天候、全天时的遥感技术。微波遥感中常用的微波辐射源波段为0.8~30cm。

我们可以对其进行打分:

① 知识查找正确性:1分
② 回答一致性:1
③ 回答幻觉比例:1
④ 回答正确性:0.9
⑤ 逻辑性:0.8(微波概念与微波发射混淆)
⑥ 通顺性:0.8(多概念混淆回答)
⑦ 智能性:0.7(技术是严谨的,AI也是,都差不多)

综合上述七个维度,我们可以全面、综合地评估系统在每个案例上的表现,综合考虑所有案例的得分,就可以评估系统在每个维度的表现。

优化空间:

  • 多维评估应与量化评估有效结合,并根据业务实际设置不同的量纲
  • 针对不同维度的不同重要性赋予权值,再计算所有维度的加权平均来代表系统得分。

🤔随着系统逐渐复杂和验证集的不断增大,这种评估的此时会达到成千上万次,人力完全不够胜任,因此我们继续一种自动评估模型的方法😀;

②自动评估方法

自动化评估大语言模型(LLM)的难点主要在于模型生成的答案往往难以判断其准确性

  • 对于客观题,由于有明确的标准答案,评估相对简单
  • 对于主观题,尤其是那些没有固定答案的题目,实现自动化评估则显得尤为困难

然而,在一定程度上牺牲评估的精确性,我们可以将那些复杂且没有标准答案的主观题转换为具有明确答案的问题从而利用简单的自动化评估方法。具体来说,有两种策略可以采用:

  1. 构造客观题:将主观题转化为选择题(如多项选择或单项选择),这样可以通过直接对比模型生成的答案与预设的标准答案来评估其准确性

怎么有股子独热编码的味道? 🤔🤔

  1. 计算标准答案相似度:通过计算模型生成的答案与标准答案之间的相似度,来评估答案的准确性。这种方法虽然不如直接对比答案那么简单直接,但可以在一定程度上量化答案的正确程度。
(1)构造客观题

思路:将主观题转化为多项选择题或单项选择题,以便于通过对比模型生成的答案与标准答案来实现简单评估。

例如我们给出问题:

  • 遥感传感器的性能指标有?

A:空间分辨率 B:时间分辨率 C:视场角FOV D:光谱分辨率;

我们给定的标准答案是ABC,将模型给出的答案与标准答案进行对比即可实现评估打分,根据以上思想,我们可以构造出一个Prompt问题模版,代码如下:

template = """针对以下的问题,进行作答,这是多选题,不要说多余的话,从选择题中选择出你的答案;只需要输出选项即可,无需解释其他内容。只能从ABCD中选择对应的字母
{context}
问题: {question}
"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template)


# 构建检索问答链
qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=loaded_db.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

# 测试结果
print("问题:")
question = "遥感传感器的性能指标有?A:空间分辨率 B:时间分辨率 C:视场角FOV D:光谱分辨率;"
print(question)
print("模型回答:")
result = qa_chain({"query": question})
print(result["result"])

输出结果如下:
遥感传感器性能指标

这种思路类似于政治类考试的主观题,通过列举出客观的选项,来回答这种主观问题;

当然,鉴于大模型的不稳定性、即使我们要求其给出选择项,其也可能会返回一大堆文字,(解释选择原因或无用信息),因此我们需要构建一种打分方法:我们需要从文字中提取出选择答案;这里我们设计打分策略为:全选为1分,漏选为0.5分,错选不得分;代码如下:

def multi_select_score_v1(true_answer : str, generate_answer : str) -> float:
    # true_anser : 正确答案,str 类型,例如 'BCD'
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(true_answer)
    '''为便于计算,我们假设每道题都只有 A B C D 四个选项'''
    # 先找出错误答案集合
    false_answers = [item for item in ['A', 'B', 'C', 'D'] if item not in true_answers]
    # 如果生成答案出现了错误答案
    for one_answer in false_answers:
        if one_answer in generate_answer:
            return 0
    # 再判断是否全选了正确答案
    if_correct = 0
    for one_answer in true_answers:
        if one_answer in generate_answer:
            if_correct += 1
            continue
    if if_correct == 0:
        # 不选
        return 0
    elif if_correct == len(true_answers):
        # 全选
        return 1
    else:
        # 漏选
        return 0.5


# 测试结果
print("问题:")
question = "遥感传感器的性能指标有?A:空间分辨率 B:时间分辨率 C:视场角FOV D:光谱分辨率;"
print(question)
print("模型回答:")
result = qa_chain({"query": question})
print(result["result"])

score = multi_select_score_v1(true_answer='ABCD', generate_answer=result["result"])
print("模型得分:", score)

运行结果为:
model-score

但是可以看到,我们要求模型在不能回答的情况下不做选择,错选和不选均为0分,这样其实鼓励了模型的幻觉回答,因此我们可以根据情况调整打分策略,让错选扣一分:除此之外,这里作者认为不够完善,我们可以在prompt中通过格式化输出解析来精准提取回答文本,辅助提高解析精度;修改后我们再次测试:

template = """针对以下的问题,进行作答,不要说多余的话,
{context}
问题: {question}

-----------
从选择题中选择出你的答案;只能从ABCD中选择对应的字母;
要求输出的多个选项答案全部包裹在一个```answer```中,例如
```answer
ABC
```,要记住,这是多选题,不要当做单选题
"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template)


# 构建检索问答链
qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=loaded_db.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})



def multi_select_score_v1(true_answer : str, generate_answer : str) -> float:
    # true_anser : 正确答案,str 类型,例如 'BCD'
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(true_answer)
    '''为便于计算,我们假设每道题都只有 A B C D 四个选项'''
    # 先找出错误答案集合
    false_answers = [item for item in ['A', 'B', 'C', 'D'] if item not in true_answers]
    # 如果生成答案出现了错误答案
    for one_answer in false_answers:
        if one_answer in generate_answer:
            return 0
    # 再判断是否全选了正确答案
    if_correct = 0
    for one_answer in true_answers:
        if one_answer in generate_answer:
            if_correct += 1
            continue
    if if_correct == 0:
        # 不选
        return 0
    elif if_correct == len(true_answers):
        # 全选
        return 1
    elif if_correct >= len(true_answers):
        # 多选
        return 1
    else:
        # 漏选
        return 0.5


# 正则提取任务
def parse_answer(content): # 从模型生成中字符串匹配提取生成的代码
    pattern = r'```answer(.*?)```'  # 使用非贪婪匹配
    match = re.search(pattern, content, re.DOTALL)
    answer= match.group(1) if match else content
    return answer

# 测试结果
print("问题:")
question = "遥感传感器的性能指标有?A:空间分辨率 B:时间分辨率 C:视场角FOV D:光谱分辨率;"
print(question)
print("模型回答:")
result = qa_chain({"query": question})
print(result["result"])
answer = parse_answer(result["result"])
print("模型答案:",answer)
score = multi_select_score_v1(true_answer='ABCD', generate_answer=answer)
print("模型得分:", score)

auto_answer
可以看到,这样我们就实现了快速、自动又有区分度的自动评估。在这样的方法下,我们只需对每一个验证案例进行客观题构造,之后每一次验证、迭代都可以完全自动化进行,从而实现了高效的验证。

(2) 计算答案相似度

学习之前文章中的RAG原理的读者应该对向量相似度有一定的了解,我们一般可以针对每个问题构建对应的标准答案,无论主客观题目;我们将模型的回答结果与标准答案都进行向量化,然后相似度计算,相似度越高,我们则认为正确答案程度越高;计算相似度的方法有很多,这里我们使用BLEU来进行相似度计算:

BLEU(Bilingual Evaluation Understudy)🏆是一种用于评估机器翻译质量的指标,它通过比较机器翻译的文本与一组参考翻译(通常是人工翻译)的相似度来计算得分。BLEU的计算原理基于以下几个关键步骤:

  1. N-gram 匹配
  • BLEU的核心思想是计算机器翻译文本中的n-gram(连续的n个词)与参考翻译中的n-gram的匹配程度
  • 通常,BLEU会计算1-gram(单个词)、2-gram(两个词)、3-gram4-gram的匹配情况。
  1. 精度(Precision)计算
  • 对于每个n-gram,计算其在机器翻译文本中出现的次数与在参考翻译 中出现的次数的比值
  • 如果机器翻译中的某个n-gram在参考翻译中出现的次数更多,则只计
    算参考翻译中出现的次数。
  1. 惩罚因子(Brevity Penalty)
  • 为了防止过短的机器翻译获得过高的分数,BLEU引入了一个惩罚因子
  • 如果机器翻译的长度小于参考翻译的长度,则根据长度差异计算一个惩罚因子,使得过短的翻译得分降低。
  1. 综合得分
  • 各个n-gram的精度得分进行加权平均,通常权重是几何平均
  • 最后,乘以惩罚因子得到最终的BLEU得分。

BLEU得分的计算公式可以表示为:
BLEU = BP ⋅ exp ⁡ ( ∑ n = 1 N w n log ⁡ p n ) \text{BLEU} = \text{BP} \cdot \exp\left(\sum_{n=1}^{N} w_n \log p_n\right) BLEU=BPexp(n=1Nwnlogpn)
其中:

  • BP \text{BP} BP 是惩罚因子。
  • p n p_n pn 是n-gram的精度。
  • w n w_n wn是n-gram的权重,通常取几何平均。

BLEU的优点是计算简单且与人工评估有较好的相关性,但它也有一些局限性,比如对语法错误和语义差异不够敏感。尽管如此,BLEU仍然是机器翻译领域广泛使用的评估指标之一。😀

我们可以使用NLTK库中的bleu打分函数进行计算:

from nltk.translate.bleu_score import sentence_bleu
import jieba

def bleu_score(true_answer : str, generate_answer : str) -> float:
    # true_anser : 标准答案,str 类型
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(jieba.cut(true_answer))
    # print(true_answers)
    generate_answers = list(jieba.cut(generate_answer))
    # print(generate_answers)
    bleu_score = sentence_bleu(true_answers, generate_answers)
    return bleu_score
true_answer = '微波的的波长比可见光和红外波长,受大气层中云雾的散射影响小,穿透性好,因此能全天候进行遥感。使用微波的遥感被称为微波遥感,它通过接受地面物体发射的微波辐射能量,或接收器本身发出的电磁波束的回波信号,对物体进行探测、识别和分析。由于微波遥感采用主动方式进行,其特点是对云层、地表植被、松散沙层和干燥冰雪具有一定的穿透能力,且不受光照等条线限制。因此微波遥感是一种全天候、全天时的遥感技术。'

print("答案一:")
answer1 = '遥感的微波(Microwave Remote Sensing)指的是使用微波辐射的散射和辐射信息来分析、识别地物或提取专题信息的技术。微波遥感可以使用人工辐射源(如微波雷达)发射微波辐射,然后根据地物散射该辐射返回的反向反射信号来探测和测距。由于微波波长比可见光和红外波长长,因此微波遥感具有较好的穿透能力,可以对云层、地表植被、松散沙层和干燥冰雪等物体进行探测,且不受光照等条件的限制,是一种全天候、全天时的遥感技术。微波遥感中常用的微波辐射源波段为0.8~30cm。'
print(answer1)
score = bleu_score(true_answer, answer1)
print("得分:", score)
print("答案二:")
answer2 = '红外线根据其性质可分为近红外、中红外、远红外和超远红外。近红外主要源于太阳辐射,中红外主要源于太阳辐射及地物热辐射,而远红外和超远红外主要源于地物热辐射。红外波段较宽,在此波段地物间不同的反射特性和发射特性均可较好地表现出来,因此该波段在遥感成像中有着重要作用。在整个红外波段中进行的遥感统称红外遥感,按其内部波段的详细划分可分为近红外遥感和热红外遥感。中红外、远红外和超远红外三者是产生热感的主要原因,因此又称为热红外。热红外遥感最大的特点就是具有昼夜工作的能力。由于摄影胶片感光范围的限制,除了近红外可用于摄影成像外,其他波段不能用于摄影成像,而整个红外波段都可用于扫描成像。'
print(answer2)
score = bleu_score(true_answer, answer2)
print("得分:", score)

运行代码测试一下:
bleu

可以看到,答案与标准答案一致性越高,则打分评估就越高,通过这种方法,我们同样只需要对一个验证集中的每一个问题构造一个标准答案,之后就可以实现自动、高效的评估;

这种方法的缺点:

  • 需要人工构建标准答案,某些垂直领域而言,构建标准答案相对比较空难;
  • 通过相似度判断,如果生成回答与标准答案高度相似,但关键位置恰恰相反则会导致答案啊完全错误,但是依旧有很高的BLEU打分;
  • 通过计算与标准答案一致性灵活性很差,如果模型生成了比标准答案更好的回答,但评估得分反而会降低;
  • 无法评估回答的智能性、流畅性。如果回答是各个标准答案中的关键词拼接出来的,我们认为这样的回答是不可用无法理解的,但 bleu 得分会较高。

利用Embedding向量相似度来打分怎么样?或者使用LLM为LLM打分?🤔🤔🤔

3. 使用大模型进行评估

使用人工评估成本高,效率低;
使用简单自动化评估效率高,但质量低;
那什么样的评估方法可以兼并两者的优点呢?
LLM的发展为我们提供了一种新的方法——使用大模型来评估,我们可以通过提示词工程来让LLM充当一个老师的身份,对我们的LLM应用进行评估;由于其强大的泛化性,其既可以满足多维评估和量化评估,又因为其作为API的特性,我们可以24小时调用它,因此大模型评估可以看做是人工评估的最佳替代;

例如,我们可以构造如下的 Prompt Engineering,让大模型进行打分:

prompt = '''
你是一个模型回答评估员。
接下来,我将给你一个问题、对应的知识片段以及模型根据知识片段对问题的回答。
请你依次评估以下维度模型回答的表现,分别给出打分:

① 知识查找正确性。评估系统给定的知识片段是否能够对问题做出回答。如果知识片段不能做出回答,打分为0;如果知识片段可以做出回答,打分为1。

② 回答一致性。评估系统的回答是否针对用户问题展开,是否有偏题、错误理解题意的情况,打分分值在0~1之间,0为完全偏题,1为完全切题。

③ 回答幻觉比例。该维度需要综合系统回答与查找到的知识片段,评估系统的回答是否出现幻觉,打分分值在0~1之间,0为全部是模型幻觉,1为没有任何幻觉。

④ 回答正确性。该维度评估系统回答是否正确,是否充分解答了用户问题,打分分值在0~1之间,0为完全不正确,1为完全正确。

⑤ 逻辑性。该维度评估系统回答是否逻辑连贯,是否出现前后冲突、逻辑混乱的情况。打分分值在0~1之间,0为逻辑完全混乱,1为完全没有逻辑问题。

⑥ 通顺性。该维度评估系统回答是否通顺、合乎语法。打分分值在0~1之间,0为语句完全不通顺,1为语句完全通顺没有任何语法问题。

⑦ 智能性。该维度评估系统回答是否拟人化、智能化,是否能充分让用户混淆人工回答与智能回答。打分分值在0~1之间,0为非常明显的模型回答,1为与人工回答高度一致。

你应该是比较严苛的评估员,很少给出满分的高评估。
用户问题:
~~~
{}
~~~
待评估的回答:
~~~
{}
~~~
给定的知识片段:
~~~
{}
~~~
你应该返回给我一个可直接解析的 Python 字典,字典的键是如上维度,值是每一个维度对应的评估打分。
不要输出任何其他内容。
'''

运行代码进行测试:

可以看到通过LLM评估的结果又快有准;

大模型评估的问题:

  • 评估大模型基座模型要优于被评估基座模型
  • 回答结果不能太长或者评价维度太多

解决方案:

  1. 拆分评估维度。如果评估维度太多,模型可能会出现错误格式导致返回无法解析,可以考虑将待评估的多个维度拆分,每个维度调用一次大模型进行评估,最后得到统一结果;
  2. 合并评估维度。如果评估维度太细,模型可能无法正确理解以至于评估不正确,可以考虑将待评估的多个维度合并,例如,将逻辑性、通顺性、智能性合并为智能性等;
    3… 提供详细的评估规范。如果没有评估规范,模型很难给出理想的评估结果。可以考虑给出详细、具体的评估规范,从而提升模型的评估能力;
  3. 提供少量示例。模型可能难以理解评估规范,此时可以给出少量评估的示例,供模型参考以实现正确评估。

4.混合评估

就像是决策树与随机森林的关系,在业务开发中,我们要具体问题具体分析,评估方法没有最好的,只有最合适的,因此我们要针对不同的问题进行针对分析;我们更推荐多种评估方法混合评估,对评估问题进行分类;不同的类别使用不同的评估方式,以个人知识库为例:

  • 客观题:具有标准答案的题目,可以构造客观题自动评估和答案相似性进行加权评估;
  • 主观题:构造客观题自动评估+大模型分维度综合评估
  • 回答结果是否智能:因为模型回答的智能性与问题弱相关,通过对评价结果中少许抽样,人工分析Prompt和模型来评估;
  • 知识查找正确性:知识查找正确性指对于特定问题,从知识库检索到的知识片段是否正确、是否足够回答问题。知识查找正确性推荐使用大模型进行评估,即要求模型判别给定的知识片段是否足够回答问题。同时,该维度评估结果结合主观正确性可以计算幻觉情况,即如果主观回答正确但知识查找不正确,则说明产生了模型幻觉。

二、生成内容评估优化

在前面的章节中,我们讲到了如何评估一个基于 RAG 框架的大模型应用的整体性能。通过针对性构造验证集,可以采用多种方法从多个维度对系统性能进行评估。但是,评估的目的是为了更好地优化应用效果,要优化应用性能,我们需要结合评估结果,对评估出的 Bad Case 进行拆分,并分别对每一部分做出评估和优化。

AG 全称为检索增强生成,因此,其有两个核心部分:检索部分和生成部分。检索部分的核心功能是保证系统根据用户 query 能够查找到对应的答案片段,而生成部分的核心功能即是保证系统在获得了正确的答案片段之后,可以充分发挥大模型能力生成一个满足用户要求的正确回答。

优化一个大模型应用,我们往往需要从这两部分同时入手,分别评估检索部分和优化部分的性能,找出 Bad Case 并针对性进行性能的优化。而具体到生成部分,在已限定使用的大模型基座的情况下,我们往往会通过优化 Prompt Engineering 来优化生成的回答。在本章中,我们将首先结合我们刚刚搭建出的大模型应用实例——个人知识库助手,向大家讲解如何评估分析生成部分性能,针对性找出 Bad Case,并通过优化 Prompt Engineering 的方式来优化生成部分

###1.初始化检索链

2. 少样本提升回答质量

寻找 Bad Case 的思路有很多,最直观也最简单的就是评估直观回答的质量,结合原有资料内容,判断在什么方面有所不足。例如,上述的测试我们可以构造成一个 Bad Case:

我们再针对性地修改 Prompt 模板,加入要求其回答具体,并去掉“谢谢你的提问”的部分:

可以看到,改进后的 v2 版本能够给出更具体、详细的回答,解决了之前的问题。但是我们可以进一步思考,要求模型给出具体、详细的回答,是否会导致针对一些有要点的回答没有重点、模糊不清?我们测试以下问题:

可以看到,针对我们关于 LLM 课程的提问,模型回答确实详细具体,也充分参考了课程内容,但回答使用首先、其次等词开头,同时将整体答案分成了4段,导致答案不是特别重点清晰,不容易阅读。因此,我们构造以下 Bad Case:

针对该 Bad Case,我们可以改进 Prompt,要求其对有几点的答案进行分点标号,让答案清晰具体:

升回答质量的方法还有很多,核心是围绕具体业务展开思考,找出初始回答中不足以让人满意的点,并针对性进行提升改进,此处不再赘述。

3. 标明知识来源,提升可信度

原始提问:

们可以要求模型在生成回答时注明知识来源,这样可以避免模型杜撰并不存在于给定资料的知识,同时,也可以提高我们对模型生成答案的可信度,代码如下:


运行结果如下:

但是,附上原文来源往往会导致上下文的增加以及回复速度的降低,我们需要根据业务场景酌情考虑是否要求附上原文。

4.构造思维链

大模型往往可以很好地理解并执行指令,但模型本身还存在一些能力的限制,例如大模型的幻觉、无法理解较为复杂的指令、无法执行复杂步骤等。我们可以通过构造思维链,将 Prompt 构造成一系列步骤来尽量减少其能力限制,例如,我们可以构造一个两步的思维链,要求模型在第二步做出反思,以尽可能消除大模型的幻觉问题。
我们首先有这样一个 Bad Case:

问题:我们应该如何去构造一个 LLM 项目
初始回答:略
存在不足:事实上,知识库中中关于如何构造LLM项目的内容是使用 LLM API 去搭建一个应用,模型的回答看似有道理,实则是大模型的幻觉,将部分相关的文本拼接得到,存在问题
question = "我们应该如何去构造一个LLM项目"
result = qa_chain({"query": question})
print(result["result"])Copy to clipboardErrorCopied

对此,我们可以优化 Prompt,将之前的 Prompt 变成两个步骤,要求模型在第二个步骤中做出反思:

template_v4 = """
请你依次执行以下步骤:
① 使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答案。
你应该使答案尽可能详细具体,但不要偏题。如果答案比较长,请酌情进行分段,以提高答案的阅读体验。
如果答案有几点,你应该分点标号回答,让答案清晰具体。
上下文:
{context}
问题: 
{question}
有用的回答:
② 基于提供的上下文,反思回答中有没有不正确或不是基于上下文得到的内容,如果有,回答你不知道
确保你执行了每一个步骤,不要跳过任意一个步骤。
"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template_v4)
qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=vectordb.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

question = "我们应该如何去构造一个LLM项目"
result = qa_chain({"query": question})
print(result["result"])

可以看出,要求模型做出自我反思之后,模型修复了自己的幻觉,给出了正确的答案。我们还可以通过构造思维链完成更多功能,此处就不再赘述了,欢迎读者尝试。

5. 增加一个输出解析

这一步也就是我们之前说的输出解析器:通过限定格式和每个输出的类型,使其结果更加专业化;代码如下:


通过上述讲解的思路,结合实际业务情况,我们可以不断发现 Bad Case 并针对性优化 Prompt,从而提升生成部分的性能。但是,上述优化的前提是检索部分能够检索到正确的答案片段,也就是检索的准确率和召回率尽可能高。那么,如何能够评估并优化检索部分的性能呢?下一章我们会深入探讨这个问题。


三、检索内容评估优化

我们讲解了如何针对生成部分评估优化 Prompt Engineering,来提高大模型的生成质量。但生成的前提是检索,只有当我们应用的检索部分能够根据用户 query 检索到正确的答案文档时,大模型的生成结果才可能是正确的。因此,检索部分的检索精确率和召回率其实更大程度影响了应用的整体性能。但是,检索部分的优化是一个更工程也更深入的命题,我们往往需要使用到很多高级的、源于搜索的进阶技巧并探索更多实用工具,甚至手写一些工具来进行优化。因此,在本章中,我们仅大致讨论检索部分评估与优化的思路,

1. 评估检索效果

在这里插入图片描述
针对用户输入的一个 query,系统会将其转化为向量并在向量数据库中匹配最相关的文本段,然后根据我们的设定选择 3~5 个文本段落和用户的 query 一起交给大模型,再由大模型根据检索到的文本段落回答用户 query 中提出的问题。在这一整个系统中,我们将向量数据库检索相关文本段落的部分称为检索部分,将大模型根据检索到的文本段落进行答案生成的部分称为生成部分。
因此,检索部分的核心功能是找到存在于知识库中、能够正确回答用户 query 中的提问的文本段落。因此,我们可以定义一个最直观的准确率在评估检索效果:对于 N 个给定 query,我们保证每一个 query 对应的正确答案都存在于知识库中。假设对于每一个 query,系统找到了 K 个文本片段,如果正确答案在 K 个文本片段之一,那么我们认为检索成功;如果正确答案不在 K 个文本片段之一,我们任务检索失败。那么,系统的检索准确率可以被简单地计算为:
𝑎𝑐𝑐𝑢𝑟𝑎𝑐𝑦=𝑀𝑁accuracy=NM
其中,M 是成功检索的 query 数。
通过上述准确率,我们可以衡量系统的检索能力,对于系统能成功检索到的 query,我们才能进一步优化 Prompt 来提高系统性能。对于系统检索失败的 query,我们就必须改进检索系统来优化检索效果。但是注意,当我们在计算如上定义的准确率时,一定要保证我们的每一个验证 query 的正确答案都确实存在于知识库中;如果正确答案本就不存在,那我们应该将 Bad Case 归因到知识库构建部分,说明知识库构建的广度和处理精度还有待提升。
当然,这只是最简单的一种评估方式,事实上,这种评估方式存在很多不足。例如:
● 有的 query 可能需要联合多个知识片段才能做出回答,对于这种 query,我们如何评估?
● 检索到的知识片段彼此之间的顺序其实会对大模型的生成带来影响,我们是否应该将检索片段的排序纳入考虑?
● 除去检索到正确的知识片段之外,我们的系统还应尽量避免检索到错误的、误导性知识片段,否则大模型的生成结果很可能被错误片段误导。我们是否应当将检索到的错误片段纳入指标计算?
上述问题都不存在标准答案,需要针对项目实际针对的业务、评估的成本来综合考虑。
除去通过上述方法来评估检索效果外,我们还可以将检索部分建模为一个经典的搜索任务。让我们来看看经典的搜索场景。搜索场景的任务是,针对用户给定的检索 query,从给定范围的内容(一般是网页)中找到相关的内容并进行排序,尽量使排序靠前的内容能够满足用户需求。
其实我们的检索部分的任务和搜索场景非常类似,同样是针对用户 query,只不过我们相对更强调召回而非排序,以及我们检索的内容不是网页而是知识片段。因此,我们可以类似地将我们的检索任务建模为一个搜索任务,那么,我们就可以引入搜索算法中经典的评估思路(如准确率、召回率等)和优化思路(例如构建索引、重排等)来更充分地评估优化我们的检索效果。这部分就不再赘述,欢迎有兴趣的读者进行深入研究和分享。
二、优化检索的思路
上文陈述来评估检索效果的几种一般思路,当我们对系统的检索效果做出合理评估,找到对应的 Bad Case 之后,我们就可以将 Bad Case 拆解到多个维度来针对性优化检索部分。注意,虽然在上文评估部分,我们强调了评估检索效果的验证 query 一定要保证其正确答案存在于知识库之中,但是在此处,我们默认知识库构建也作为检索部分的一部分,因此,我们也需要在这一部分解决由于知识库构建有误带来的 Bad Case。在此,我们分享一些常见的 Bad Case 归因和可行的优化思路。

  1. 知识片段被割裂导致答案丢失
    该问题一般表现为,对于一个用户 query,我们可以确定其问题一定是存在于知识库之中的,但是我们发现检索到的知识片段将正确答案分割开了,导致不能形成一个完整、合理的答案。该种问题在需要较长回答的 query 上较为常见。
    该类问题的一般优化思路是,优化文本切割方式。我们在《C3 搭建知识库》中使用到的是最原始的分割方式,即根据特定字符和 chunk 大小进行分割,但该类分割方式往往不能照顾到文本语义,容易造成同一主题的强相关上下文被切分到两个 chunk 总。对于一些格式统一、组织清晰的知识文档,我们可以针对性构建更合适的分割规则;对于格式混乱、无法形成统一的分割规则的文档,我们可以考虑纳入一定的人力进行分割。我们也可以考虑训练一个专用于文本分割的模型,来实现根据语义和主题的 chunk 切分。
  2. query 提问需要长上下文概括回答
    该问题也是存在于知识库构建的一个问题。即部分 query 提出的问题需要检索部分跨越很长的上下文来做出概括性回答,也就是需要跨越多个 chunk 来综合回答问题。但是由于模型上下文限制,我们往往很难给出足够的 chunk 数。
    该类问题的一般优化思路是,优化知识库构建方式。针对可能需要此类回答的文档,我们可以增加一个步骤,通过使用 LLM 来对长文档进行概括总结,或者预设提问让 LLM 做出回答,从而将此类问题的可能答案预先填入知识库作为单独的 chunk,来一定程度解决该问题。
  3. 关键词误导
    该问题一般表现为,对于一个用户 query,系统检索到的知识片段有很多与 query 强相关的关键词,但知识片段本身并非针对 query 做出的回答。这种情况一般源于 query 中有多个关键词,其中次要关键词的匹配效果影响了主要关键词。
    该类问题的一般优化思路是,对用户 query 进行改写,这也是目前很多大模型应用的常用思路。即对于用户输入 query,我们首先通过 LLM 来将用户 query 改写成一种合理的形式,去除次要关键词以及可能出现的错字、漏字的影响。具体改写成什么形式根据具体业务而定,可以要求 LLM 对 query 进行提炼形成 Json 对象,也可以要求 LLM 对 query 进行扩写等。
  4. 匹配关系不合理
    该问题是较为常见的,即匹配到的强相关文本段并没有包含答案文本。该问题的核心问题在于,我们使用的向量模型和我们一开始的假设不符。在讲解 RAG 的框架时,我们有提到,RAG 起效果是有一个核心假设的,即我们假设我们匹配到的强相关文本段就是问题对应的答案文本段。但是很多向量模型其实构建的是“配对”的语义相似度而非“因果”的语义相似度,例如对于 query-“今天天气怎么样”,会认为“我想知道今天天气”的相关性比“天气不错”更高。
    该类问题的一般优化思路是,优化向量模型或是构建倒排索引。我们可以选择效果更好的向量模型,或是收集部分数据,在自己的业务上微调一个更符合自己业务的向量模型。我们也可以考虑构建倒排索引,即针对知识库的每一个知识片段,构建一个能够表征该片段内容但和 query 的相对相关性更准确的索引,在检索时匹配索引和 query 的相关性而不是全文,从而提高匹配关系的准确性。
    优化检索部分的思路还有很多,事实上,检索部分的优化往往是 RAG 应用开发的核心工程部分。限于篇幅原因,此处就不再赘述更多的技巧及方

文章参考

  • OpenAI官方文档
  • DeepSeek官方文档
  • Mistral官方文档
  • ChatGLM官方文档

项目地址

  • Github地址
  • 拓展阅读
  • 专栏文章

thank_watch

如果觉得我的文章对您有帮助,三连+关注便是对我创作的最大鼓励!或者一个star🌟也可以😂.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/754827.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

expandtabs()方法——tab符号转为空格

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 expandtabs()方法把字符串中的tab(\t)符号转为空格,tab(\t)符号默认的空格数是…

软考初级网络管理员__操作系统单选题

1.在Windows资源管理器中,假设已经选定文件,以下关于“复制”操作的叙述中,正确的有()。 按住Ctr键,拖至不同驱动器的图标上 按住AIt键,拖至不同驱动器的图标上 直接拖至不同驱动器的图标上 按住Shift键&#xff0…

普元EOS学习笔记-EOS8.3精简版安装

前言 普元EOS是优秀的高低开结合的企业应用软件开发框架。 普元:这是普元公司的名字,普元信息,上市公司哦,这里就不过多安利了。 EOS:普元公司的企业应用开发系统,开发语言是基于Java,技术框…

LLDP 基本原理

LLDP 简介 定义 LLDP(Link Layer Discovery Protocol,链路层发现协议)是 IEEE 802.1ab 中定义的第二层发现(Layer 2 Discovery)协议。 LLDP 提供了一种标准的链路层发现方式,可以将本端设备的主要能力、…

【51单片机入门】数码管原理

文章目录 前言共阴极与共阳极数码管多个数码管显示原理 总结 前言 在我们的日常生活中,数码管被广泛应用于各种电子设备中,如电子表、计时器、电子钟等。数码管的主要功能是显示数字和一些特殊字符。在这篇文章中,我们将探讨数码管的工作原理…

【详细】CNN中的卷积计算是什么-实例讲解

本文来自《老饼讲解-BP神经网络》https://www.bbbdata.com/ 目录 一、 CNN的基础卷积计算1.1.一个例子了解CNN的卷积计算是什么1.2.卷积层的生物意义 二、卷积的拓展:多输入通道与多输出通道2.1.多输入通道卷积2.2.多输出通道卷积 三、卷积的实现3.1.pytorch实现卷积…

助农扶贫网站

摘要:随着信息科技的快速发展和互联网的普及,信息技术在助力农业发展、促进农村振兴以及扶贫工作中发挥着越来越重要的作用。本文基于Spring Boot框架和Vue.js前端开发技术,设计完成了一个助农扶贫电商网站。网站提供便捷的农产品信息发布、农…

【验收支撑】项目验收计划书(Word原件)

软件验收相关的文档通常包括以下,这些文档在软件项目的不同阶段和验收过程中起着关键作用: 1、概要设计说明书: 描述了软件系统的整体架构、主要模块及其相互关系。 2、详细设计说明书: 提供了软件系统中各个模块的详细设计信息&a…

确保家电安全无忧:可燃气体报警器检验的重要性与必要性

随着家电行业的快速发展,安全问题已成为消费者关注的焦点。 可燃气体报警器作为一种重要的安全装置,在保障家庭安全中扮演着至关重要的角色。它能够实时监测室内可燃气体浓度,一旦超过预设的安全阈值,便会发出警报,提…

【Linux】服务器被work32病毒入侵CPU占用99%

文章目录 一、问题发现二、问题解决2.1 清楚病毒2.2 开启防火墙2.3 修改SSH端口2.4 仅使用凭据登录(可选) 一、问题发现 我的一台海外服务器,一直只运行一项服务(你懂的),但是前不久我发现CPU占用99%。没在…

simulink开发stm32,使用中断模块,无法产生中断,其中包括使用timer模块,以及ADC都无法产生中断,需要注意的地方

1,其中包括使用timer模块,以及ADC都无法产生中断,需要注意的地方 原来是需要在配置文件里开启一下timer的中断,其他模块自动加载ioc就可以了,这个timer需要注意力,需要自己勾选一下 如下图: 看…

【嵌入式操作系统(Linux篇)】实验期末复习(1)

以下是关于嵌入式操作系统(Linux篇)的实验汇总,大概率都是会考的 特别是shell程序和文件IO的操作 嵌入式操作系统实验小结—涉及期末大题 (一)Linux操作系统的使用实验 1、认识Linux操作系统的目录结构 请进入自己…

一个开源的、独立的、可自托管的评论系统,专为现代Web平台设计

大家好,今天给大家分享的是一个开源的、独立的、可自托管的评论系统,专为现代Web平台设计。 Remark42是一个自托管的、轻量级的、简单的(但功能强大的)评论引擎,它不会监视用户。它可以嵌入到博客、文章或任何其他读者…

如何知道docker谁占用的显卡的显存?

文章目录 python环境安装nvidia-htop查看pid加一个追踪总结一下【找到容器创建时间】使用说明示例 再总结一下【用PID找到容器创建时间,从而找到谁创建的】使用说明示例 python环境安装nvidia-htop nvidia-htop是一个看详细的工具。 pip3 install nvidia-htop查看…

Qt:4.信号和槽

目录 1.信号源、信号和槽: 2.Qt类的继承关系: 3.自定义槽函数: 4.第一种信号和槽的连接的方法: 5.第二种信号和槽的连接的方法: 6.自定义信号: 7.发射信号: 8.信号和槽的传参:…

第7章_低成本 Modbus 传感器的实现

文章目录 第7章 低成本 Modbus 传感器的实现7.1 硬件资源介绍与接线7.2 开发环境搭建7.3 创建与体验第 1 个工程7.3.1 创建工程7.3.2 配置调试器7.3.3 配置 GPIO 操作 LED 7.4 UART 编程7.4.1 使用 STM32CubeMX 进行配置1.UART12.配置 RS485方向引脚 7.4.2 封装 UART7.4.3 上机…

已解决javax.security.auth.login.LoginException:登录失败的正确解决方法,亲测有效!!!

已解决javax.security.auth.login.LoginException:登录失败的正确解决方法,亲测有效!!! 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 1. 检查用户名和密码 用户名和密码验证 2. 验证配置文件 …

前端面试题(基础篇十二)

一、link标签定义、与import的区别 link 标签定义文档与外部资源的关系。 link 元素是空元素,它仅包含属性。 此元素只能存在于 head 部分,不过它可出现任意数。 link 标签中的 rel 属性定义了当前文档与被链接文档之间的关系。常见的 stylesheet 指的是…

ServletConfig与ServletContext详解

文章目录 概要web.xmlServletConfig介绍ServletConfig实例ServletConfig细节ServletContext介绍ServletContext实例ServletContext细节ServletContext获得服务访问次数&#xff08;可拓展&#xff09;总结 概要 web.xml <?xml version"1.0" encoding"UTF-…

使用uniapp编写微信小程序

使用uniapp编写微信小程序 文章目录 使用uniapp编写微信小程序前言一、项目搭建1.1 创建项目方式1.1.1 HBuilderX工具创建1.1.2 命令行下载1.1.3 直接Gitee下载 1.2 项目文件解构1.2.1 安装依赖1.2.2 项目启动1.2.3 文件结构释义 1.2 引入uni-ui介绍 二、拓展2.1 uni-app使用uc…