如何评估一个RAG系统(RAGas评测框架)-下篇

RAGas是一个用于评测RAG系统的评测框架,它支持与不同大语言模型的集成,并与langchain生态打通,能够很方便的构建评测系统。下面是RAGas的一些链接

官方文档及github对框架的使用介绍的比较详细,本文不会就该方面做更多的赘述。本文尝试深入到框架代码底层,尝试分析相关指标怎样进行的计算。

RAGas QuickStart

  • 安装ragas

    pip install ragas

  • 代码示例

    from datasets import Dataset
    import os
    from ragas import evaluate
    from ragas.metrics import faithfulness, answer_correctness

    os.environ["OPENAI_API_KEY"] = "your-openai-key"

    data_samples = {
    'question': ['When was the first super bowl?', 'Who won the most super bowls?'],
    'answer': ['The first superbowl was held on Jan 15, 1967', 'The most super bowls have been won by The New England Patriots'],
    'contexts' : [['The First AFL--NFL World Championship Game was an American football game played on January 15, 1967, at the Los Angeles Memorial Coliseum in Los Angeles,'],
    ['The Green Bay Packers...Green Bay, Wisconsin.','The Packers compete...Football Conference']],
    'ground_truth': ['The first superbowl was held on January 15, 1967', 'The New England Patriots have won the Super Bowl a record six times']
    }

    dataset = Dataset.from_dict(data_samples)

    score = evaluate(dataset,metrics=[faithfulness,answer_correctness])
    score.to_pandas()

RAGas运行主要依赖于LLM(大语言模型)和embeddings(向量化)的模型,并支持langchain(同时支持llama_index)。这意味着只需要基于langchain框架实现LLM和embeddings相关接口,理论上都可以对接至RAGas框架。

指标实现

在评估RAG系统好坏时,我们通常需要下面四种类型的数据,分别是Question(问题)、Answer(回答)、Context(召回的上下文)、Ground truth(标准答案)(新版本代码中,这些元素的表达可能是user_input、response、retrieved_contexts、reference)。所有的评测指标也是围绕这四个数据元素展开的。

在开始走读代码之前,先就RAGas中的metric类代码继承关系做下简要的说明。所有的指标类都都继承自Metric这个基类,其中必选name、required_cloums两个属性,分别代表指标名和评测该指标所必须的数据列。有四个类继承自Metric,分别是

MetricWithLLM:需要调用LLM进行评测的指标,包含llm属性参数

MetricWithEmbeddings:需要调用Embedding进行评测的指标,包含embeddings成熟性参数

SingleTurnMetric:单轮对话评测指标

MultiTurnMetric:多轮对话评测指标

Faithfulness忠诚度

Faithfulness是用来评判大模型幻觉程度的指标,其核心思想是将回答拆分成一个个陈述,再看该陈述是否能够通过上下文推理得出

class Faithfulness(MetricWithLLM, SingleTurnMetric)

Faithfulness类继承MetricWithLLM, SingleTurnMetric,也就意味着这个指标的评测是依赖于LLM,并且评测对象是单轮对话。同时其_required_columns为"user_input","response","retrieved_contexts",代表评测该指标需要用户输入、大模型回复及召回上下文。其进行指标得分计算的代码逻辑位于_ascore函数中

async def _ascore(self: t.Self, row: t.Dict, callbacks: Callbacks) -> float:
    """
    returns the NLI score for each (q, c, a) pair
    """
    # 判断是否传入llm
    assert self.llm is not None, "LLM is not set"

    # 对回答进行分割,分割后使用大模型生成1~n个陈述语句表达回答中的观点
    statements_simplified = await self._create_statements(row, callbacks)
    if statements_simplified is None:
        return np.nan

    # 将生成的陈述语句,打平到一个数组中
    statements = []
    for component in statements_simplified.sentences:
        statements.extend(component.simpler_statements)
    
    # 利用LLM对每个句子进行判断,判断其是否可以从上下文中推论出来
    verdicts = await self._create_verdicts(row, statements, callbacks)
    # 计算 可推论的句子/句子总数 得到该项指标的分数
    return self._compute_score(verdicts)

从ascore函数的代码中可以看出,faithfulness指标的计算还是比较简单的。整体逻辑就是将response进行拆解,拆解成一个个小的陈述语句,然后再有大模型判断每个语句是否能够从上下文中进行推断。如果不能进行推断,可能就是回答时由于大模型的幻觉产生的答案。

Answer Relevance回答相关性

回答相关性,是用来评判大模型的回答时与用户提问的相关性如何,其方法是通过回答推理出一系列可能的提问,判断回答是否有解答该提问。并用可能的提问与真实的提问计算余弦相似度,以此来决定其权重大小,最后对数据加和得到得分。

class ResponseRelevancy(MetricWithLLM, MetricWithEmbeddings, SingleTurnMetric)

从指标的继承关系,可以看出,该指标是依赖于LLM和Embedding能力的评测单轮对话的指标。

async def _ascore(self, row: t.Dict, callbacks: Callbacks) -> float:
    assert self.llm is not None, "LLM is not set"

    prompt_input = ResponseRelevanceInput(response=row["response"])
    responses = []
    # 给回答生成'strictness'个可能的问题,并评判答案是否有对问题进行解答
    for _ in range(self.strictness):
        response = await self.question_generation.generate(
            data=prompt_input,
            llm=self.llm,
            callbacks=callbacks,
        )
        responses.append(response)
    
    # 计算得分
    return self._calculate_score(responses, row)

def _calculate_score(
    self, answers: t.Sequence[ResponseRelevanceOutput], row: t.Dict
) -> float:
    question = row["user_input"]
    gen_questions = [answer.question for answer in answers]
    committal = np.any([answer.noncommittal for answer in answers])
    if all(q == "" for q in gen_questions):
        logger.warning(
            "Invalid JSON response. Expected dictionary with key 'question'"
        )
        score = np.nan
    else:
        # 前端都是组装数据,以及判断数据的有效性,重要的是这两行的计算
        # 首先计算生成的问题与用户真实输入的余弦相似度,作为评分权重系数
        cosine_sim = self.calculate_similarity(question, gen_questions)
        # 通过权重系数 * 回答中已解答的数据 作为分数
        score = cosine_sim.mean() * int(not committal)

    return score

ContextPrecision上下文精确度

上下文精度,是用来评判召回的上下文对回答问题的帮助程度大小,如召回无关的文档,将会使该指标评分降低

class LLMContextPrecisionWithReference(MetricWithLLM, SingleTurnMetric)

还是先看类的定义,仔细看过前面文章应该对该指标的依赖和能够评测怎样的数据有大概了解了。接下来我们走读下代码,看看该指标是如何计算的

async def _ascore(
    self: t.Self,
    row: t.Dict,
    callbacks: Callbacks,
) -> float:
    assert self.llm is not None, "LLM is not set"

    # 判别每条context是否有助于生成答案
    responses = []
    for context in row["retrieved_contexts"]:
        verdicts: t.List[Verification] = (
            # 多次调用llm,输出多次判定结果
            await self.context_precision_prompt.generate_multiple(
                data=QAC(
                    question=row["user_input"],
                    context=context,
                    answer=row["reference"],
                ),
                n=self.reproducibility,
                llm=self.llm,
                callbacks=callbacks,
            )
        )

        responses.append([result.model_dump() for result in verdicts])

    answers = []
    for response in responses:
        # 进行一致性投票,以多数服从少数的原则,最终输出一个判定结果
        agg_answer = ensembler.from_discrete([response], "verdict")
        answers.append(Verification(**agg_answer[0]))

    # 分数计算
    score = self._calculate_average_precision(answers)
    return score


# 计算平均精确度
def _calculate_average_precision(self, verdict_list: t.List[int]) -> float:
    score = np.nan

    # 正例的数量
    denominator = sum(verdict_list) + 1e-10
    numerator = sum(
        [
            # 累计精确度
            (sum(verdict_list[: i + 1]) / (i + 1)) * verdict_list[i]
            for i in range(len(verdict_list))
        ]
    )
    score = numerator / denominator
    return score

本次我们拆解下代码中调用大模型的prompt,看看调用大模型都做了些什么。首先是context_precision_prompt如下。其要求大模型根据给出的问题、答案以及上下文来判断,这条context是否对于输出答案有帮助,如果有帮助则输出1,否则输出0

Given question, answer and context verify if the context was useful in arriving at the given answer. Give verdict as "1" if useful and "0" if not with json output.

同时在上述计算得分的代码中,使用平均精确度计算最后的得分,而不是使用 有帮助的context/context总数的方法。是因为,使用平均精确度还会考虑context在返回文本中的位置,如果有助于推论的context越靠前,其在最终得分的权重会越高。

AnswerSimilarity回答&答案相似度

该指标是用来评判基于RAG系统的回答与标准答案之间的相似程度。该指标的计算比较简单,分别取回答和答案,对其进行向量化和归一化处理后,计算其余弦相似度。如果有设置检测阈值的话,分数将输出0、1的二值数据

class SemanticSimilarity(MetricWithLLM, MetricWithEmbeddings, SingleTurnMetric)

async def _ascore(self: t.Self, row: t.Dict, callbacks: Callbacks) -> float:
    assert self.embeddings is not None, "embeddings must be set"

    ground_truth = t.cast(str, row["reference"])
    answer = t.cast(str, row["response"])

    if self.is_cross_encoder and isinstance(self.embeddings, HuggingfaceEmbeddings):
        raise NotImplementedError(
            "async score [ascore()] not implemented for HuggingFace embeddings"
        )
    else:
        embedding_1 = np.array(await self.embeddings.embed_text(ground_truth))
        embedding_2 = np.array(await self.embeddings.embed_text(answer))
        # Normalization factors of the above embeddings
        norms_1 = np.linalg.norm(embedding_1, keepdims=True)
        norms_2 = np.linalg.norm(embedding_2, keepdims=True)
        embedding_1_normalized = embedding_1 / norms_1
        embedding_2_normalized = embedding_2 / norms_2
        similarity = embedding_1_normalized @ embedding_2_normalized.T
        score = similarity.flatten()

    assert isinstance(score, np.ndarray), "Expects ndarray"
    if self.threshold:
        score = score >= self.threshold

    return score.tolist()[0]

写在后面

本文介绍了4种最常用的用来评判RAG系统好坏的指标,可以看出在RGAas框架下的指标评测代码非常清晰。同时在最新版本的RAGas代码中还包含其他多种指标类型,包含评判有害性的指标、评测多轮对话的指标、评测Agent能力的指标以及评测SQL生成能力的指标

同时RAGas支持langchain生态和llamaindex的接入,可以非常容易的应用到生产系统中

相关推荐
井底哇哇12 分钟前
ChatGPT是强人工智能吗?
人工智能·chatgpt
Coovally AI模型快速验证16 分钟前
MMYOLO:打破单一模式限制,多模态目标检测的革命性突破!
人工智能·算法·yolo·目标检测·机器学习·计算机视觉·目标跟踪
AI浩41 分钟前
【面试总结】FFN(前馈神经网络)在Transformer模型中先升维再降维的原因
人工智能·深度学习·计算机视觉·transformer
可为测控1 小时前
图像处理基础(4):高斯滤波器详解
人工智能·算法·计算机视觉
一水鉴天1 小时前
为AI聊天工具添加一个知识系统 之63 详细设计 之4:AI操作系统 之2 智能合约
开发语言·人工智能·python
倔强的石头1062 小时前
解锁辅助驾驶新境界:基于昇腾 AI 异构计算架构 CANN 的应用探秘
人工智能·架构
佛州小李哥2 小时前
Agent群舞,在亚马逊云科技搭建数字营销多代理(Multi-Agent)(下篇)
人工智能·科技·ai·语言模型·云计算·aws·亚马逊云科技
说私域3 小时前
社群裂变+2+1链动新纪元:S2B2C小程序如何重塑企业客户管理版图?
大数据·人工智能·小程序·开源
程序猿阿伟3 小时前
《探秘鸿蒙Next:如何保障AI模型轻量化后多设备协同功能一致》
人工智能·华为·harmonyos
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter