|
译者 | 朱先忠审校 | 重楼
大语言模型(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 Studiostudio = Studio("") # 从上面获取您的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']}")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
上述代码测试了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.6842228802750124Explanation = 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-aThe vowels are: A, a, a, a, aThere are 5 vowels in the word 'Abracadabra'.Trustworthiness score = 0.9378276048845285Explanation = Did not find a reason to doubt trustworthiness.
claude-3.5-sonnet-v2产生了正确的输出。让我们比较一下这两个模型对另一个问题的回答。
pythonfrom cleanlab_studio import Studioimport markdownfrom IPython.core.display import display, Markdown# 使用API密钥初始化Cleanlab Studiostudio = Studio("") #替换为您的实际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))
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
以下是两个模型的响应:
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 stfrom langchain_groq.chat_models import ChatGroqimport osos.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 modelresponse = groq_llm.invoke(prompt)#初始化Cleanlab的studiostudio = 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))
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
下面是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 LlamaParsefrom llama_index.core import VectorStoreIndeximport requestsfrom bs4 import BeautifulSoupimport pdfkitfrom llama_index.readers.docling import DoclingReaderfrom llama_index.core import Settingsfrom llama_index.embeddings.huggingface import HuggingFaceEmbeddingfrom llama_index.core import VectorStoreIndex, SimpleDirectoryReaderfrom llama_index.llms.cleanlab import CleanlabTLMfrom typing import Dict, List, ClassVarfrom llama_index.core.instrumentation.events import BaseEventfrom llama_index.core.instrumentation.event_handlers import BaseEventHandlerfrom llama_index.core.instrumentation import get_dispatcherfrom llama_index.core.instrumentation.events.llm import LLMCompletionEndEventimport nest_asyncioimport os
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
接下来的步骤将涉及使用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="", optinotallow=options) # 从https://cleanlab.ai/获取您的免费APISettings.llm = llmSettings.embed_model = HuggingFaceEmbedding( model_name="BAAI/bge-small-en-v1.5")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
现在,我们将定义一个自定义事件处理程序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)}")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
接下来,我们将通过从给定的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="https://', 'src="https://').replace('href="https://', '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}")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
在从抓取的数据生成PDF后,我们使用LlamaParse解析这些PDF。我们设置解析指令以提取MarkDown格式的内容,并按页以及文档名称和页码解析文档。这些提取的实体(页面)被称为节点。解析器遍历提取的节点,并通过附加引用标题来更新每个节点的元数据,以便于后续引用。
########################################### 使用LlamaParse解析PDF并注入元数据########################################### 定义解析指令(如果您的解析器支持)parsing_instructions = """提取文档的markdown格式内容。按页将文档拆分为节点(例如)。确保每个节点具有文档名称和页码的元数据。"""# 创建LlamaParse实例parser = LlamaParse( 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}")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
现在,我们创建一个向量存储和一个查询引擎。我们定义一个自定义提示模板来指导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!")
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
现在,让我们测试一些查询及其对应的可信度评分。
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 |
|