RAGAS指标说明

下面介绍五种RAGAs的指标:

Faithfulness(忠诚度)

从召回的上下文(context)中推断出来,这衡量了生成的答案(answer)与召回上下文(context)的事实一致性。它是根据答案(answer)和检索到的上下文(context)计算得出的。答案缩放到 (0,1) 范围。越高越好。
F a i t h f u l n e s = ∣  Number of claims that can be inferred from given context  ∣ ∣  Total number of claims in the generated answer  ∣ Faithfulnes =\frac{\mid \text { Number of claims that can be inferred from given context } \mid}{\mid \text { Total number of claims in the generated answer } \mid} Faithfulnes= Total number of claims in the generated answer  Number of claims that can be inferred from given context 
方法:将生成的答案(answer)分成n块,看这些块是否在上下文(context)里面。
结合源码分析:

p_value = self._create_statements_prompt(row) # 构建prompt,这个prompt的作用时将生成的答案answer分成n块
statements = await self.llm.generate(
            p_value,
            callbacks=callbacks,
        ) # 得到大模型的回答
statements = await _statements_output_parser.aparse( statements.generations[0][0].text, p_value, self.llm, self.max_retries)

if statements is None:
    return np.nan

statements = [item["simpler_statements"] for item in statements.model_dump()]
statements = [item for sublist in statements for item in sublist]

p_value = self._create_nli_prompt(row, statements)  # 构建prompt,这个prompt的作用是判断分成的块是否在上下文context中
nli_result = await self.llm.generate(
            p_value,
            callbacks=callbacks,
            n=self._reproducibility,
        )  # 获得大模型结果
nli_result_text = [
            nli_result.generations[0][i].text for i in range(self._reproducibility)
        ]

Answer Relevance(答案相关性)

侧重于评估生成的答案(answer)与问题(query)的相关性。不完整或包含冗余信息的答案得分较低,得分较高则表示相关性较好。

大型语言模型 (LLM) 从生成的答案中逆向设计问题的“n”个虚拟问题。再计算生成的虚拟问题和原问题之间的余弦相似度
 answer relevancy  = 1 N ∑ i = 1 N cos ⁡ ( E g i , E o )  answer relevancy  = 1 N ∑ i = 1 N E g i ⋅ E o ∥ E g i ∥ ∥ E o ∥ \text { answer relevancy }=\frac{1}{N} \sum_{i=1}^{N} \cos \left(E_{g_{i}}, E_{o}\right) \\ \text { answer relevancy }=\frac{1}{N} \sum_{i=1}^{N} \frac{E_{g_{i}} \cdot E_{o}}{\left\|E_{g_{i}}\right\|\left\|E_{o}\right\|}  answer relevancy =N1i=1Ncos(Egi,Eo) answer relevancy =N1i=1NEgiEoEgiEo
E g i E_{g_{i}} Egi为生成的问题的embedding。

E o E_{o} Eo为原始问题的embedding。

N N N为生成问题的数量,默认为3。
结合源码分析:

prompt_input = ResponseRelevanceInput(response=row["response"]) # 构建prompt,该prompt的作用是从答案中生成虚拟问题。
        responses = []
        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"] # 获取问题query
        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
 
 # 计算相似性
 def calculate_similarity(self, question: str, generated_questions: list[str]):
        assert self.embeddings is not None
        question_vec = np.asarray(self.embeddings.embed_query(question)).reshape(1, -1)  # 计算问题的embedding
        gen_question_vec = np.asarray(
            self.embeddings.embed_documents(generated_questions)
        ).reshape(len(generated_questions), -1)
        norm = np.linalg.norm(gen_question_vec, axis=1) * np.linalg.norm(
            question_vec, axis=1
        ) #计算向量范数
        return (
            np.dot(gen_question_vec, question_vec.T).reshape(
                -1,
            )
            / norm
        ) # 返回余弦相似度

Context Precision(上下文精度)

评估所有在上下文(contexts)中呈现的与真实答案(ground_truths)相关的条目是否排名较高。理想情况下,所有相关文档块(chunks)必须出现在顶层。值范围在 0 到 1 之间,其中分数越高表示精度越高。
 Context Precision@K  = ∑ k = 1 K (  Precision  @ k × v k )  Total number of relevant items in the top  K  results   Precision@  k =  true positives@  k (  true positives@k + false positives@k)  \text { Context Precision@K }=\frac{\sum_{k=1}^{K}\left(\text { Precision } @ \mathrm{k} \times v_{k}\right)}{\text { Total number of relevant items in the top } K \text { results }} \\ \text { Precision@ } \mathrm{k}=\frac{\text { true positives@ } \mathrm{k}}{(\text { true positives@k + false positives@k) }}  Context Precision@K = Total number of relevant items in the top K results k=1K( Precision @k×vk) Precision@ k=( true positives@k + false positives@k)  true positives@ k
源码分析:

源码的整体思路如下:用大模型判断上下文(contexts)与真实答案(ground_truths)是否相关,然后计算指标,如果大模型判断相关,并且该上下文的排名靠前,那么分数较高。

        human_prompts = self._context_precision_prompt(row) # 构建prompt,该prompt用于判断上下文是否和真实答案有关,有关返回“1”,无关返回“0”
        responses = []
        for hp in human_prompts:
            results = await self.llm.generate(
                hp,
                callbacks=callbacks,
                n=self.reproducibility,
            )
            results = [
                await _output_parser.aparse(item.text, hp, self.llm, self.max_retries)
                for item in results.generations[0]
            ]

            responses.append(
                [result.dict() for result in results if result is not None]
            )
  
        answers = []  # 保存大模型得到的结果(0或者1)
        for response in responses:
            agg_answer = ensembler.from_discrete([response], "verdict")
            if agg_answer:
                agg_answer = ContextPrecisionVerification.parse_obj(agg_answer[0])
            answers.append(agg_answer)

        answers = ContextPrecisionVerifications(__root__=answers)
        score = self._calculate_average_precision(answers.__root__) # 计算分数
        return score
        
 def _calculate_average_precision(
        self, verifications: t.List[ContextPrecisionVerification]
    ) -> float:
        score = np.nan
        verdict_list = [1 if ver.verdict else 0 for ver in verifications]
        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))
            ] 
        )  # 表示在前 i 个判决中,有用的判决比例乘以当前判决的值,进而累加到 numerator。
        score = numerator / denominator
        return score
        

其实上面的核心代码就是

numerator = sum(
            [
                (sum(verdict_list[: i + 1]) / (i + 1)) * verdict_list[i]
                for i in range(len(verdict_list))
            ] 
        )

这段代码会给1在列表前面的情况高的分数。

举个例子:

verdict_list = [1, 0, 1, 1] # 判断的列表
sum(verdict_list) # 计算1的数量,这里为3
i = 0: (1 / 1) * 1 = 1.0 # i=0的时,分数为1

i = 1: (1 / 2) * 0 = 0.0 # i=1的时,分数为0

i = 2: (2 / 3) * 1 ≈ 0.6667 # i=2的时,分数为0.66

i = 3: (3 / 4) * 1 = 0.75 # i=3的时,分数为0.66

numerator ≈ 1.0 + 0.0 + 0.6667 + 0.75 = 2.4167
最终分数:score ≈ 2.4167 / 3.0000000001 ≈ 0.8056

这个函数希望有用的文本在前列。因为在计算分子的过程中,当前判决的值与之前判决的比例相结合,意味着如果有用的上下文(即判决为1)出现在前面,它会对最终的平均精确度产生更积极的影响。

如果列表为[1,1,0,0]那么最终的分数就是1,当0在全部1的后面时,最终的分数都为1,这也是我们希望看到的情况。

Context recall(上下文召回率)

衡量上下文(contexts)与真实答案(ground_truths)的一致程度。它是根据ground truth和检索到的Context计算出来的,取值范围在 0 到 1 之间,值越高表示性能越好。

真实答案(ground_truths)中的每个句子是否在上下文(contexts)中。

c o n t e x t     r e c a l l = ∣  GT claims that can be attributed to context  ∣ ∣  Number of claims in GT  ∣ context \ \ \ recall =\frac{\mid \text { GT claims that can be attributed to context } \mid}{\mid \text { Number of claims in GT } \mid} context   recall= Number of claims in GT  GT claims that can be attributed to context 

源码分析:

源码的整体思路如下:用大模型判断上下文(contexts)是否与真实答案(ground_truths)一直,如果大模型判断一致就返回1,不一致返回0。

    async def _ascore(self, row: t.Dict, callbacks: Callbacks) -> float:
        assert self.llm is not None, "set LLM before use"
        p_value = self._create_context_recall_prompt(row) # 构建prompt
        results = await self.llm.generate( 
            p_value,
            callbacks=callbacks,
            n=self.reproducibility,
        )# 大模型返回结果
        results = [results.generations[0][i].text for i in range(self.reproducibility)]

        answers = [ await _output_parser.aparse(text, p_value, self.llm, self.max_retries) for text in results]
        return self._compute_score(answers)

    def _compute_score(self, response: t.Any) -> float: # 计算分数
        response = [1 if item.attributed else 0 for item in response.root]
        denom = len(response) # 总数量
        numerator = sum(response) # 1的数量,也就是大模型判断相关的数量
        score = numerator / denom if denom > 0 else np.nan
        if np.isnan(score):
            logger.warning("The LLM did not return a valid classification.")
        return score

Answer Correctness(答案正确性)

根据 ground_truths 和 **answer**来计算,范围为(0~1),得分越高越说明生成的答案与真实的答案更接近,也就意味着准确性越高。

答案正确性包含两个方面:

  • 生成的答案(answer)与 ground_truths 之间事实的相似性
    • TP 真正例:事实或陈述同时存在于 ground_truths 和生成的答案中。
    • FP 假正例:存在于生成答案中但不存在于 ground_truths 中的事实或陈述。
    • FN 假负例:存在于 ground_truths 中但不存在于生成的答案中的事实或陈述。
  • 生成的答案(answer)与 ground_truths 之间语义的相似性

参考

RAGAS|RAG系统的综合评估框架(含precision详解) - 知乎 (zhihu.com)

https://docs.ragas.io/en/stable/concepts/index.html

https://mp.weixin.qq.com/s/YeeZRRXtWNRZ550adVx-AQ

https://techdiylife.github.io/blog/blog.html?category1=c02&blogid=0053

高级RAG(四):RAGAs评估-CSDN博客

运行报错

使用RAGAs的context_recall时出现bug:AttributeError: ‘ContextRecallClassificationAnswers’ object has no attribute ‘**root**’

出现位置,RAGAs源码:ragas/src/ragas/metrics/_context_recall.py

    def _compute_score(self, response: t.Any) -> float:
     **   response = [1 if item.attributed else 0 for item in response.__root__]**
        denom = len(response)
        numerator = sum(response)
        score = numerator / denom if denom > 0 else np.nan

response为ContextRecallClassificationAnswers类,用于存储多个分类答案对象

class ContextRecallClassificationAnswers(RootModel):
    **root: t.List[ContextRecallClassificationAnswer]**

    def dicts(self) -> t.List[t.Dict]:
        return self.model_dump()

可以看到只有root,并没有__root__

因此修改代码即可:

 response = [1 if item.attributed else 0 for item in response.__root__]
 改为
 response = [1 if item.attributed else 0 for item in response.root]
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐