RAG评价框架RAGAs指标解释
值范围在 0 到 1 之间,其中分数越高表示精度越高。它是根据ground truth和检索到的Context计算出来的,取值范围在 0 到 1 之间,值越高表示性能越好。**来计算,范围为(0~1),得分越高越说明生成的答案与真实的答案更接近,也就意味着准确性越高。)是否相关,然后计算指标,如果大模型判断相关,并且该上下文的排名靠前,那么分数较高。如果列表为[1,1,0,0]那么最终的分数就是1
文章目录
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=1∑Ncos(Egi,Eo) answer relevancy =N1i=1∑N∥Egi∥∥Eo∥Egi⋅Eo
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的时,分数为1i = 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
中但不存在于生成的答案中的事实或陈述。
- TP 真正例:事实或陈述同时存在于
- 生成的答案(
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
运行报错
使用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]
更多推荐
所有评论(0)