大语言模型响应结果的可靠性分析实战

译文
人工智能
本文将对直接提问和检索增强两种方案生成的大语言模型的响应结果生成可信度或可靠性分数展开对比评估。
译者 | 朱先忠

审校 | 重楼

大语言模型LLM的基本原理非常简单:根据训练数据中的统计模式预测单词序列中的下一个单词(或标记)。然而,当这种看似简单的功能可以执行许多令人惊叹的任务(例如文本摘要、创意生成、头脑风暴、代码生成、信息处理和内容创建)时,它就变得异常复杂。话虽如此,LLM没有任何记忆,它们实际上并不“理解”任何东西,除了坚持其基本功能:预测下一个单词。

下一个单词预测的过程是概率性的LLM必须从概率分布中选择每个单词。在此过程中,它们通常会生成虚假、捏造或不一致的内容,以试图产生连贯的响应并用看似合理但不正确的信息填补空白。这种现象称为幻觉Hallucination),这是LLM不可避免的众所周知的特征,需要对其输出进行验证和证实。

检索增强生成RAG方法使LLM与外部知识源协同工作,在一定程度上减少了幻觉,但无法完全消除幻觉。尽管高级RAG可以提供文内引用和URL,但验证这些引用可能非常繁琐且耗时。因此,我们需要一个客观标准来评估LLM响应的可靠性或可信度,无论它是由其自身知识还是外部知识库RAG生成的。

在本文中,我们将讨论如何通过可信语言模型评估LLM输出的可信度,该模型为LLM的输出分配分数。我们将首先讨论如何使用可信语言模型为LLM的答案分配分数并解释可信度。随后,我们将使用LlamaParse和Llamaindex开发一个示例RAG,以评估RAG答案的可信度。

本文的完整代码可在GitHub上的Jupyter笔记本中找到。

为LLM的答案分配可信度分数

为了演示如何为LLM的回复分配可信度分数,我将使用Cleanlab的可信语言模型TLM。此类TLM结合使用不确定性量化和一致性分析来计算LLM响应的可信度分数和解释。

Cleanlab提供免费试用API,可通过在其网站上创建账户获取。我们首先需要安装Cleanlab的Python客户端:

pip install --upgrade cleanlab-studio

Cleanlab支持多种专有模型,例如“gpt-4o”、“gpt-4o-mini”、“o1-preview”、“claude-3-sonnet”、“claude-3.5-sonnet”、“claude-3.5-sonnet-v2”等。以下是TLM为GPT-4o的答案分配可信度分数的方式。可信度分数范围从0到1,其中值越高表示可信度越高。

from cleanlab_studio import Studio
studio = Studio("<CLEANLAB_API_KEY>") # 从上面获取您的API密钥
tlm = studio.TLM(options={"log": ["explanation"], "model": "gpt-4o"}) # GPT, Claude, etc
#设置提示
out = tlm.prompt("How many vowels are there in the word 'Abracadabra'.?")
#TLM响应包含实际输出的“响应”、可信度评分和解释
print(f"Model's response = {out['response']}")
print(f"Trustworthiness score = {out['trustworthiness_score']}")
print(f"Explanation = {out['log']['explanation']}")

上述代码测试了GPT-4o对“‘Abracadabra’这个词中有多少个元音?”这个问题的响应。TLM的输出包含模型的答案(响应)、可信度分数和解释。以下是此代码的输出。

Model's response = The word "Abracadabra" contains 6 vowels. The vowels are: A, a, a, a, a, and a.
Trustworthiness score = 0.6842228802750124
Explanation = This response is untrustworthy due to a lack of consistency in possible responses from the model. Here's one inconsistent alternate response that the model considered (which may not be accurate either):
5.

可以看出,最先进的语言模型对于如此简单的任务会产生幻觉并产生错误的输出。以下是claude-3.5-sonnet-v2对同一问题的回答和可信度分数。

Model's response = Let me count the vowels in 'Abracadabra':
A-b-r-a-c-a-d-a-b-r-a

The vowels are: A, a, a, a, a

There are 5 vowels in the word 'Abracadabra'.
Trustworthiness score = 0.9378276048845285
Explanation = Did not find a reason to doubt trustworthiness.

claude-3.5-sonnet-v2产生了正确的输出。让我们比较一下这两个模型对另一个问题的回答。

python
from cleanlab_studio import Studio
import markdown
from IPython.core.display import display, Markdown

# 使用API密钥初始化Cleanlab Studio
studio = Studio("<CLEANLAB_API_KEY>") #替换为您的实际API密钥

# 要评估的模型列表
models = ["gpt-4o", "claude-3.5-sonnet-v2"]

# 定义提示
prompt_text = "Which one of 9.11 and 9.9 is bigger?"

# 遍历每个模型并进行评估
for model in models:
 tlm = studio.TLM(options={"log": ["explanation"], "model": model})
 out = tlm.prompt(prompt_text)

 md_content = f"""
## 模型: {model}

**响应**: {out['response']}

**可信度评分**: {out['trustworthiness_score']}

**解释**: {out['log']['explanation']}

---
"""
 display(Markdown(md_content))

以下是两个模型的响应:

GPT-4o和Claude-3.5-Sonnet-V2生成的错误输出,以低可信度分数表示GPT-4o和Claude-3.5-Sonnet-V2生成的错误输出,以低可信度分数表示

我们还可以为开源LLM生成可信度分数。让我们来看看最近大肆宣传的开源LLM:DeepSeek-R1。我将使用DeepSeek-R1-Distill-Llama-70B,它基于Meta的Llama-3.3–70B-Instruct模型,并从DeepSeek更大的6710亿参数混合专家MoE模型中提炼而来。知识提炼也称为“知识蒸馏”是一种机器学习技术,旨在将大型预训练模型“教师模型”的学习成果转移到较小的“学生模型”。

import streamlit as st
from langchain_groq.chat_models import ChatGroq
import os
os.environ["GROQ_API_KEY"]=st.secrets["GROQ_API_KEY"]
#初始化Groq Llama即时模型
groq_llm = ChatGroq(model="deepseek-r1-distill-llama-70b", temperature=0.5)
prompt = "Which one of 9.11 and 9.9 is bigger?"
# Get the response from the model
response = groq_llm.invoke(prompt)
#初始化Cleanlab的studio
studio = Studio("226eeab91e944b23bd817a46dbe3c8ae") 
cleanlab_tlm = studio.TLM(optinotallow={"log": ["explanation"]}) #供解释
#得到包含可信度得分和解释的输出
output = cleanlab_tlm.get_trustworthiness_score(prompt, respnotallow=response.content.strip())
md_content = f"""
## 模型: {model}
**Response:** {response.content.strip()}
**Trustworthiness Score:** {output['trustworthiness_score']}
**Explanation:** {output['log']['explanation']}
---
"""
display(Markdown(md_content))

下面是deepseek-r1-distill-llama-70b模型的输出。

deepseek-r1-distill-llama-70b模型的正确输出,具有较高的可信度得分deepseek-r1-distill-llama-70b模型的正确输出,具有较高的可信度得分

开发可信的RAG

我们现在将开发一个RAG来演示如何在RAG中衡量LLM响应的可信度。此RAG将通过从给定的链接中抓取数据、以MarkDown格式解析数据并创建向量存储来开发。

接下来的代码需要安装以下库

pip install llama-parse llama-index-core llama-index-embeddings-huggingface 
llama-index-llms-cleanlab requests beautifulsoup4 pdfkit nest-asyncio

要将HTML渲染为PDF格式,我们还需要从他们的网站安装wkhtmltopdf命令行工具。

将导入以下库:

from llama_parse import LlamaParse
from llama_index.core import VectorStoreIndex
import requests
from bs4 import BeautifulSoup
import pdfkit
from llama_index.readers.docling import DoclingReader
from llama_index.core import Settings
from llama_index.embeddings.huggingface import HuggingFaceEmbedding
from llama_index.core import VectorStoreIndex, SimpleDirectoryReader
from llama_index.llms.cleanlab import CleanlabTLM
from typing import Dict, List, ClassVar
from llama_index.core.instrumentation.events import BaseEvent
from llama_index.core.instrumentation.event_handlers import BaseEventHandler
from llama_index.core.instrumentation import get_dispatcher
from llama_index.core.instrumentation.events.llm import LLMCompletionEndEvent
import nest_asyncio
import os

接下来的步骤将涉及使用Python的BeautifulSoup库从给定的URL抓取数据,使用pdfkit将抓取的数据保存为PDF文件,然后使用LlamaParse(这是一个用LLM构建且专为LLM用例设计的原生AI文档解析平台)将PDF中的数据解析为Markdown文件。

我们将首先配置CleanlabTLM要使用的LLM和嵌入模型(HuggingFace嵌入模型BAAI/bge-small-en-v1.5),该嵌入模型将用于计算抓取数据的嵌入,以创建向量存储。

options = {
 "model": "gpt-4o",
 "max_tokens": 512,
 "log": ["explanation"]
}
llm = CleanlabTLM(api_key="<CLEANLAB_API_KEY>", optinotallow=options) # 从https://cleanlab.ai/获取您的免费API
Settings.llm = llm
Settings.embed_model = HuggingFaceEmbedding(
 model_name="BAAI/bge-small-en-v1.5"
)

现在,我们将定义一个自定义事件处理程序GetTrustworthinessScore,它继承自一个基础事件处理程序类。该处理程序在LLM(大语言模型)完成时被触发,并从响应元数据中提取可信度评分。我们创建了一个辅助函数display_response用于显示LLM的响应及其可信度评分。

# 可信度评分事件处理程序
class GetTrustworthinessScore(BaseEventHandler):
 events: ClassVar[List[BaseEvent]] = []
 trustworthiness_score: float = 0.0
 @classmethod
 def class_name(cls) -> str:
 return "GetTrustworthinessScore"
 def handle(self, event: BaseEvent) -> Dict:
 if isinstance(event, LLMCompletionEndEvent):
 self.trustworthiness_score = event.response.additional_kwargs.get("trustworthiness_score", 0.0)
 self.events.append(event)
 return {}

# 显示LLM响应的辅助函数
def display_response(response):
 response_str = response.response
 trustworthiness_score = event_handler.trustworthiness_score
 print(f"Response: {response_str}")
 print(f"Trustworthiness score: {round(trustworthiness_score, 2)}")

接下来,我们将通过从给定的URL抓取数据来生成PDF。为了演示目的,我们仅从这篇关于大语言模型的维基百科文章(遵循Creative Commons Attribution-ShareAlike 4.0许可)抓取数据。

注意:建议读者始终仔细检查即将抓取的内容和数据的状态,并确保他们被允许这样做。

下面的代码片段通过发出HTTP请求并使用Python的BeautifulSoup库解析HTML内容来从给定的URL抓取数据。HTML内容通过将协议相对URL转换为绝对URL进行清理。随后,抓取的内容使用pdfkit转换为PDF文件。

##########################################
# 从多个URL生成PDF
##########################################
# 配置wkhtmltopdf路径
wkhtml_path = r'C:\Program Files\wkhtmltopdf\bin\wkhtmltopdf.exe'
config = pdfkit.configuration(wkhtmltopdf=wkhtml_path)
# 定义URL和分配文档名称
urls = {
 "LLMs": "https://en.wikipedia.org/wiki/Large_language_model"
}
# 保存PDF的目录
pdf_directory = "PDFs"
os.makedirs(pdf_directory, exist_ok=True)
pdf_paths = {}
for doc_name, url in urls.items():
 try:
 print(f"Processing {doc_name} from {url} ...")
 response = requests.get(url)
 soup = BeautifulSoup(response.text, "html.parser")
 main_content = soup.find("div", {"id": "mw-content-text"})
 if main_content is None:
 raise ValueError("Main content not found")
 # 将协议相对URL替换为绝对URL
 html_string = str(main_content).replace('src="//', 'src="https://').replace('href="//', 'href="https://')
 pdf_file_path = os.path.join(pdf_directory, f"{doc_name}.pdf")
 pdfkit.from_string(
 html_string,
 pdf_file_path,
 optinotallow={'encoding': 'UTF-8', 'quiet': ''},
 cnotallow=config
 )
 pdf_paths[doc_name] = pdf_file_path
 print(f"Saved PDF for {doc_name} at {pdf_file_path}")
 except Exception as e:
 print(f"Error processing {doc_name}: {e}")

在从抓取的数据生成PDF后,我们使用LlamaParse解析这些PDF。我们设置解析指令以提取MarkDown格式的内容,并按页以及文档名称和页码解析文档。这些提取的实体(页面)被称为节点。解析器遍历提取的节点,并通过附加引用标题来更新每个节点的元数据,以便于后续引用。

##########################################
# 使用LlamaParse解析PDF并注入元数据
##########################################

# 定义解析指令(如果您的解析器支持)
parsing_instructions = """提取文档的markdown格式内容。
按页将文档拆分为节点(例如)。
确保每个节点具有文档名称和页码的元数据。"""

# 创建LlamaParse实例
parser = LlamaParse(
 api_key="<LLAMACLOUD_API_KEY>", # 替换为您的实际密钥
 parsing_instructinotallow=parsing_instructions,
 result_type="markdown",
 premium_mode=True,
 max_timeout=600
)
# 保存合并的Markdown文件的目录(每个PDF一个)
output_md_dir = os.path.join(pdf_directory, "markdown_docs")
os.makedirs(output_md_dir, exist_ok=True)
# 列表,用于保存所有更新后的节点以供索引
all_nodes = []
for doc_name, pdf_path in pdf_paths.items():
 try:
 print(f"Parsing PDF for {doc_name} from {pdf_path} ...")
 nodes = parser.load_data(pdf_path) # 返回节点列表
 updated_nodes = []
 # 处理每个节点:更新元数据并在文本中注入引用标题。
 for i, node in enumerate(nodes, start=1):
 # 复制现有元数据(如果有),并添加我们自己的键。
 new_metadata = dict(node.metadata) if node.metadata else {}
 new_metadata["document_name"] = doc_name
 if "page_number" not in new_metadata:
 new_metadata["page_number"] = str(i)
 # 构建引用标题。
 citation_header = f"[{new_metadata['document_name']}, page {new_metadata['page_number']}]\n\n"
 # 在节点的文本前添加引用标题。
 updated_text = citation_header + node.text
 new_node = node.__class__(text=updated_text, metadata=new_metadata)
 updated_nodes.append(new_node)
 # 使用更新后的节点文本为文档保存一个合并的Markdown文件。
 combined_texts = [node.text for node in updated_nodes]
 combined_md = "\n\n---\n\n".join(combined_texts)
 md_filename = f"{doc_name}.md"
 md_filepath = os.path.join(output_md_dir, md_filename)
 with open(md_filepath, "w", encoding="utf-8") as f:
 f.write(combined_md)
 print(f"Saved combined markdown for {doc_name} to {md_filepath}")
 # 将更新后的节点添加到全局列表以供索引。
 all_nodes.extend(updated_nodes)
 print(f"Parsed {len(updated_nodes)} nodes from {doc_name}.")
 except Exception as e:
 print(f"Error parsing {doc_name}: {e}")

现在,我们创建一个向量存储和一个查询引擎。我们定义一个自定义提示模板来指导LLM在回答问题时的行为。最后,我们创建一个查询引擎,使用创建的索引来回答问题。对于每个查询,我们根据节点与查询的语义相似性从向量存储中检索前3个节点。LLM使用这些检索到的节点来生成最终答案。

##########################################

# 创建索引和查询引擎

##########################################

# 从所有节点创建索引。

index = VectorStoreIndex.from_documents(documents=all_nodes)

# 定义一个自定义提示模板,强制包含引用。

prompt_template = """
你是一个具有主题专业知识的AI助手。
仅使用提供的上下文回答问题。
在必要时,以格式良好的Markdown格式回答,包含项目符号和章节。
如果提供的上下文不支持答案,请回复“我不知道。”
上下文:
{context_str}
问题:
{query_str}
答案:
"""
# 使用自定义提示创建查询引擎。
query_engine = index.as_query_engine(similarity_top_k=3, llm=llm, prompt_template=prompt_template)
print("Combined index and query engine created successfully!")

现在,让我们测试一些查询及其对应的可信度评分。

query = "When is mixture of experts approach used?"
response = query_engine.query(query)
display_response(response)

回答“何时使用专家混合方法?”的问题(图片来自作者本人)回答“何时使用专家混合方法?”的问题(图片来自作者本人)

query = "How do you compare Deepseek model with OpenAI's models?"
response = query_engine.query(query)
display_response(response)

回答“How do you compare the Deepseek model with OpenAI’s models?您如何将Deepseek模型与OpenAI的模型进行比较?”的问题(作者提供的图片)

总之,为LLM的响应分配可信度分数(无论是通过直接推理还是RAG生成)有助于定义AI输出的可靠性并在需要时优先考虑人工验证。这对于关键领域尤其重要,因为错误或不可靠的响应可能会造成严重后果。

译者介绍

朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。

原文标题:How to Measure the Reliability of a Large Language Model’s Response,作者:Umair Ali Khan

责任编辑:华轩 来源: 51CTO
相关推荐

2012-08-01 14:00:55

JMPMinitab可靠性分析

2014-10-31 15:49:56

JMP

2010-12-28 19:50:21

可靠性产品可靠性

2010-12-28 20:14:53

2019-08-30 12:10:05

磁盘数据可靠性RAID

2010-12-28 19:55:20

软件架构可靠性

2020-12-06 14:51:23

物联网可靠性IOT

2010-12-28 20:04:10

网络的可靠性网络解决方案可靠性

2011-05-25 19:31:07

Stratus信息化

2010-12-28 20:16:24

2024-10-05 11:30:00

模型训练

2013-11-04 17:05:37

银行容错

2024-02-28 10:26:04

物联网数据存储

2018-09-27 14:13:27

云服务可靠故障

2021-02-02 11:01:31

RocketMQ消息分布式

2023-11-17 09:00:00

Kafka开发

2011-05-04 19:17:36

VPLSVPN技术

2009-04-08 10:23:00

软交换网络可靠

2023-06-01 14:25:17

数据中心服务器

2018-05-07 10:20:38

Kafka存储机制
点赞
收藏

51CTO技术栈公众号