llm-universe-第五节-将LLM 接入 LangChain

发布于 2024-04-26  2 次阅读


1. 验证评估的一般思路

现在,我们已经构建了一个简单的、一般化的大模型应用。回顾整个开发流程,我们可以发现,以调用、发挥大模型为核心的大模型开发相较传统的 AI 开发更注重验证迭代。由于你可以快速构建出基于 LLM 的应用程序,在几分钟内定义一个 Prompt,并在几小时内得到反馈结果,那么停下来收集一千个测试样本就会显得极为繁琐。因为现在,你可以在没有任何训练样本的情况下得到结果。

因此,在使用LLM构建应用程序时,你可能会经历以下流程:首先,你会在一到三个样本的小样本中调整 Prompt ,尝试使其在这些样本上起效。随后,当你对系统进行进一步测试时,可能会遇到一些棘手的例子,这些例子无法通过 Prompt 或者算法解决。这就是使用 LLM 构建应用程序的开发者所面临的挑战。在这种情况下,你可以将这些额外的几个例子添加到你正在测试的集合中,有机地添加其他难以处理的例子。最终,你会将足够多的这些例子添加到你逐步扩大的开发集中,以至于手动运行每一个例子以测试 Prompt 变得有些不便。然后,你开始开发一些用于衡量这些小样本集性能的指标,例如平均准确度。这个过程的有趣之处在于,如果你觉得你的系统已经足够好了,你可以随时停止,不再进行改进。实际上,很多已经部署的应用程序就在第一步或第二步就停下来了,而且它们运行得非常好。

在本章中,我们将逐个介绍大模型应用验证评估的一般方法,并设计本项目验证迭代的过程,从而实现应用功能的优化。但是注意,由于系统评估与优化是一个与业务密切相关的话题,本章我们以理论介绍为主,欢迎读者积极进行自我实践和探索。

我们将首先介绍大模型开发评估的几种方法。对于有简单标准答案的任务来说,评估很容易得到实现;但大模型开发一般是需要实现复杂的生成任务,如何在没有简单答案甚至没有标准答案的情况下实现评估,能够准确地反映应用的效果,我们将简要介绍几种方法。

而随着我们不断寻找到 Bad Case 并做出针对性优化,我们可以将这些 Bad Case 逐步加入到验证集,从而形成一个有一定样例数的验证集。针对这种验证集,一个一个进行评估就是不切实际的了。我们需要一种自动评估方法,实现对该验证集上性能的整体评估。

掌握了一般思路,我们会具体到基于 RAG 范式的大模型应用中来探究如何评估并优化应用性能。由于基于 RAG 范式开发的大模型应用一般包括两个核心部分:检索和生成,所以,我们的评估优化也会分别聚焦到这两个部分,分别以优化系统检索精度和在确定给定材料下的生成质量。

在每一个部分,我们都会首先介绍如何找出 Bad Case 的一些思路提示,以及针对 Bad Case 针对性做出检索优化或 Prompt 优化的一般思路。注意,在这一过程中,你应该时刻谨记我们在之前章节中所讲述的一系列大模型开发原则与技巧,并时刻保证优化后的系统不会在原先表现良好的样例上出现失误。

验证迭代是构建以 LLM 为中心的应用程序所必不能少的重要步骤,通过不断寻找 Bad Case,针对性调整 Prompt 或优化检索性能,来推动应用达到我们目标中的性能与精度。接下来,我们将简要介绍大模型开发评估的几种方法,并概括性介绍从少数 Bad Case 针对性优化到整体自动化评估的一般思路。

2. 大模型评估方法

在具体的大模型应用开发中,我们可以找到 Bad Cases,并不断针对性优化 Prompt 或检索架构来解决 Bad Cases,从而优化系统的表现。我们会将找到的每一个 Bad Case 都加入到我们的验证集中,每一次优化之后,我们会重新对验证集中所有验证案例进行验证,从而保证优化后的 系统不会在原有 Good Case 上失去能力或表现降级。当验证集体量较小时,我们可以采用人工评估的方法,即对验证集中的每一个验证案例,人工评估系统输出的优劣;但是,当验证集随着系统的优化而不断扩张,其体量会不断增大,以至于人工评估的时间和人力成本扩大到我们无法接受的程度。因此,我们需要采用自动评估的方法,自动评估系统对每一个验证案例的输出质量,从而评估系统的整体性能。

我们将首先介绍人工评估的一般思路以供参考,接着深入介绍大模型自动评估的一般方法,并在本系统上进行实际验证,全面评估本系统表现,为系统的进一步优化迭代做准备。同样,在正式开始之前,我们先加载我们的向量数据库与检索链:

import sys
sys.path.append("../C3 搭建知识库") # 将父目录放入系统路径中

# 使用智谱 Embedding API,注意,需要将上一章实现的封装代码下载到本地
from zhipuai_embedding import ZhipuAIEmbeddings

from langchain.vectorstores.chroma import Chroma
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv, find_dotenv
import os

_ = load_dotenv(find_dotenv())    # read local .env file
zhipuai_api_key = os.environ['ZHIPUAI_API_KEY']
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]

# 定义 Embeddings
embedding = ZhipuAIEmbeddings()

# 向量数据库持久化路径
persist_directory = '../../data_base/vector_db/chroma'

# 加载数据库
vectordb = Chroma(
    persist_directory=persist_directory,  # 允许我们将persist_directory目录保存到磁盘上
    embedding_function=embedding
)

# 使用 OpenAI GPT-3.5 模型
llm = ChatOpenAI(model_name = "gpt-3.5-turbo", temperature = 0)

2.1 人工评估的一般思路

在系统开发的初期,验证集体量较小,最简单、直观的方法即为人工对验证集中的每一个验证案例进行评估。但是,人工评估也有一些基本准则与思路,此处简要介绍供学习者参考。但请注意,系统的评估与业务强相关,设计具体的评估方法与维度需要结合具体业务深入考虑。

准则一 量化评估

为保证很好地比较不同版本的系统性能,量化评估指标是非常必要的。我们应该对每一个验证案例的回答都给出打分,最后计算所有验证案例的平均分得到本版本系统的得分。量化的量纲可以是0~5,也可以是0~100,可以根据个人风格和业务实际情况而定。

量化后的评估指标应当有一定的评估规范,例如在满足条件 A 的情况下可以打分为 y 分,以保证不同评估员之间评估的相对一致。

例如,我们给出两个验证案例:

① 《南瓜书》的作者是谁?

② 应该如何使用南瓜书?

接下来我们分别用版本A prompt(简明扼要) 与版本B prompt(详细具体) 来要求模型做出回答:

from langchain.prompts import PromptTemplate
from langchain.chains import RetrievalQA

template_v1 = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。最多使用三句话。尽量使答案简明扼要。总是在回答的最后说“谢谢你的提问!”。
{context}
问题: {question}
"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template_v1)

qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=vectordb.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

print("问题一:")
question = "南瓜书和西瓜书有什么关系?"
result = qa_chain({"query": question})
print(result["result"])

print("问题二:")
question = "应该如何使用南瓜书?"
result = qa_chain({"query": question})
print(result["result"])

​ 问题一:
​ 南瓜书是以西瓜书为前置知识进行解析和补充的,主要是对西瓜书中比较难理解的公式进行解析和推导细节的补充。南瓜书的最佳使用方法是以西瓜书为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书。谢谢你的提问!
​ 问题二:
​ 应该以西瓜书为主线,遇到推导不出来或看不懂的公式时再查阅南瓜书。不建议初学者深究第1章和第2章的公式,等学得更熟练再回来。如果需要查阅南瓜书中没有的公式或发现错误,可以在GitHub上反馈。谢谢你的提问!

上述是版本A Prompt 的回答,我们再测试版本B:

template_v2 = """使用以下上下文来回答最后的问题。如果你不知道答案,就说你不知道,不要试图编造答
案。你应该使答案尽可能详细具体,但不要偏题。如果答案比较长,请酌情进行分段,以提高答案的阅读体验。
{context}
问题: {question}
有用的回答:"""

QA_CHAIN_PROMPT = PromptTemplate(input_variables=["context","question"],
                                 template=template_v2)

qa_chain = RetrievalQA.from_chain_type(llm,
                                       retriever=vectordb.as_retriever(),
                                       return_source_documents=True,
                                       chain_type_kwargs={"prompt":QA_CHAIN_PROMPT})

print("问题一:")
question = "南瓜书和西瓜书有什么关系?"
result = qa_chain({"query": question})
print(result["result"])

print("问题二:")
question = "应该如何使用南瓜书?"
result = qa_chain({"query": question})
print(result["result"])

​ 问题一:
​ 南瓜书和西瓜书之间的关系是南瓜书是以西瓜书的内容为前置知识进行表述的。南瓜书的目的是对西瓜书中比较难理解的公式进行解析,并补充具体的推导细节,以帮助读者更好地理解和学习机器学习领域的知识。因此,最佳使用方法是以西瓜书为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书。南瓜书的内容主要是为了帮助那些想深究公式推导细节的读者,提供更详细的解释和补充。
​ 问题二:
​ 应该将南瓜书作为西瓜书的补充,主要在遇到自己无法推导或理解的公式时进行查阅。对于初学机器学习的小白来说,建议先简单过一下南瓜书的第1章和第2章的公式,等学得更深入后再回来深究。每个公式的解析和推导都以本科数学基础的视角进行讲解,超纲的数学知识会在附录和参考文献中给出,供感兴趣的同学继续深入学习。如果在南瓜书中找不到想要查阅的公式,或者发现错误,可以在GitHub的Issues中提交反馈,通常会在24小时内得到回复。此外,南瓜书还提供配套视频教程和在线阅读地址,以及最新版PDF获取地址。最后,南瓜书的内容是基于知识共享署名-非商业性使用-相同方式共享4.0国际许可协议进行许可。

可以看到,版本 A 的 prompt 在案例①上有着更好的效果,但版本 B 的 prompt 在案例②上效果更佳。如果我们不量化评估指标,仅使用相对优劣的评估的话,我们无法判断版本 A 与版本 B 哪一个 prompt 更好,从而要找到一个 prompt 在所有案例上表现都更优才能进一步迭代;然而,这很明显是非常困难且不利于我们迭代优化的。

我们可以给每个答案赋予 1~5 的打分。例如,在上述案例中,我们给版本 A 的答案①打分为4,答案②打分为2,给版本 B 的答案①打分为3,答案②打分为5;那么,版本 A 的平均得分为3分,版本 B 的平均得分为4分,则版本 B 优于版本 A。

准则二 多维评估

大模型是典型的生成模型,即其回答为一个由模型生成的语句。一般而言,大模型的回答需要在多个维度上进行评估。例如,本项目的个人知识库问答项目上,用户提问一般是针对个人知识库的内容进行提问,模型的回答需要同时满足充分使用个人知识库内容、答案与问题一致、答案真实有效、回答语句通顺等。一个优秀的问答助手,应当既能够很好地回答用户的问题,保证答案的正确性,又能够体现出充分的智能性。

因此,我们往往需要从多个维度出发,设计每个维度的评估指标,在每个维度上都进行打分,从而综合评估系统性能。同时需要注意的是,多维评估应当和量化评估有效结合,对每一个维度,可以设置相同的量纲也可以设置不同的量纲,应充分结合业务实际。

例如,在本项目中,我们可以设计如下几个维度的评估:

① 知识查找正确性。该维度需要查看系统从向量数据库查找相关知识片段的中间结果,评估系统查找到的知识片段是否能够对问题做出回答。该维度为0-1评估,即打分为0指查找到的知识片段不能做出回答,打分为1指查找到的知识片段可以做出回答。

② 回答一致性。该维度评估系统的回答是否针对用户问题展开,是否有偏题、错误理解题意的情况,该维度量纲同样设计为0~1,0为完全偏题,1为完全切题,中间结果可以任取。

③ 回答幻觉比例。该维度需要综合系统回答与查找到的知识片段,评估系统的回答是否出现幻觉,幻觉比例有多高。该维度同样设计为0~1,0为全部是模型幻觉,1为没有任何幻觉。

④ 回答正确性。该维度评估系统回答是否正确,是否充分解答了用户问题,是系统最核心的评估指标之一。该维度可以在0~1之间任意打分。

上述四个维度都围绕知识、回答的正确性展开,与问题高度相关;接下来几个维度将围绕大模型生成结果的拟人性、语法正确性展开,与问题相关性较小:

⑤ 逻辑性。该维度评估系统回答是否逻辑连贯,是否出现前后冲突、逻辑混乱的情况。该维度为0-1评估。

⑥ 通顺性。该维度评估系统回答是否通顺、合乎语法,可以在0~1之间任意打分。

⑦ 智能性。该维度评估系统回答是否拟人化、智能化,是否能充分让用户混淆人工回答与智能回答。该维度可以在0~1之间任意打分。

例如,我们针对以下回答进行评估:

print("问题:")
question = "应该如何使用南瓜书?"
print(question)
print("模型回答:")
result = qa_chain({"query": question})
print(result["result"])

​ 问题:
​ 应该如何使用南瓜书?
​ 模型回答:
​ 应该将南瓜书作为西瓜书的补充,主要在遇到自己无法推导或理解的公式时进行查阅。对于初学机器学习的小白来说,建议先简单过一下南瓜书的第1章和第2章,等学得更深入后再回来深究。每个公式的解析和推导都以本科数学基础的视角进行讲解,超纲的数学知识会在附录和参考文献中给出,感兴趣的同学可以继续深入学习。如果南瓜书中没有你想要查阅的公式,或者发现有错误,可以在GitHub的Issues中提交反馈,通常会在24小时内得到回复。最终目的是帮助读者更好地理解和应用机器学习知识,成为合格的理工科学生。

以下是系统查找到的知识片段:

print(result["source_documents"])

​ [Document(page_content='为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;\n• 对于初学机器学习的小白,西瓜书第1 章和第2 章的公式强烈不建议深究,简单过一下即可,等你学得\n有点飘的时候再回来啃都来得及;\n• 每个公式的解析和推导我们都力(zhi) 争(neng) 以本科数学基础的视角进行讲解,所以超纲的数学知识\n我们通常都会以附录和参考文献的形式给出,感兴趣的同学可以继续沿着我们给的资料进行深入学习;\n• 若南瓜书里没有你想要查阅的公式,\n或者你发现南瓜书哪个地方有错误,\n请毫不犹豫地去我们GitHub 的\nIssues(地址:https://github.com/datawhalechina/pumpkin-book/issues)进行反馈,在对应版块\n提交你希望补充的公式编号或者勘误信息,我们通常会在24 小时以内给您回复,超过24 小时未回复的\n话可以微信联系我们(微信号:at-Sm1les)\n;\n配套视频教程:https://www.bilibili.com/video/BV1Mh411e7VU', metadata={'author': '', 'creationDate': "D:20230303170709-00'00'", 'creator': 'LaTeX with hyperref', 'file_path': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'format': 'PDF 1.5', 'keywords': '', 'modDate': '', 'page': 1, 'producer': 'xdvipdfmx (20200315)', 'source': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'subject': '', 'title': '', 'total_pages': 196, 'trapped': ''}), Document(page_content='在线阅读地址:https://datawhalechina.github.io/pumpkin-book(仅供第1 版)\n最新版PDF 获取地址:https://github.com/datawhalechina/pumpkin-book/releases\n编委会\n主编:Sm1les、archwalker、jbb0523\n编委:juxiao、Majingmin、MrBigFan、shanry、Ye980226\n封面设计:构思-Sm1les、创作-林王茂盛\n致谢\n特别感谢awyd234、\nfeijuan、\nGgmatch、\nHeitao5200、\nhuaqing89、\nLongJH、\nLilRachel、\nLeoLRH、\nNono17、\nspareribs、sunchaothu、StevenLzq 在最早期的时候对南瓜书所做的贡献。\n扫描下方二维码,然后回复关键词“南瓜书”\n,即可加入“南瓜书读者交流群”\n版权声明\n本作品采用知识共享署名-非商业性使用-相同方式共享4.0 国际许可协议进行许可。', metadata={'author': '', 'creationDate': "D:20230303170709-00'00'", 'creator': 'LaTeX with hyperref', 'file_path': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'format': 'PDF 1.5', 'keywords': '', 'modDate': '', 'page': 1, 'producer': 'xdvipdfmx (20200315)', 'source': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'subject': '', 'title': '', 'total_pages': 196, 'trapped': ''}), Document(page_content='\x01本\x03:1.9.9\n发布日期:2023.03\n南 ⽠ 书\nPUMPKIN\nB O O K\nDatawhale', metadata={'author': '', 'creationDate': "D:20230303170709-00'00'", 'creator': 'LaTeX with hyperref', 'file_path': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'format': 'PDF 1.5', 'keywords': '', 'modDate': '', 'page': 0, 'producer': 'xdvipdfmx (20200315)', 'source': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'subject': '', 'title': '', 'total_pages': 196, 'trapped': ''}), Document(page_content='前言\n“周志华老师的《机器学习》\n(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读\n者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推\n导细节的读者来说可能“不太友好”\n,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充\n具体的推导细节。\n”\n读到这里,大家可能会疑问为啥前面这段话加了引号,因为这只是我们最初的遐想,后来我们了解到,周\n老师之所以省去这些推导细节的真实原因是,他本尊认为“理工科数学基础扎实点的大二下学生应该对西瓜书\n中的推导细节无困难吧,要点在书里都有了,略去的细节应能脑补或做练习”\n。所以...... 本南瓜书只能算是我\n等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二\n下学生”\n。\n使用说明\n• 南瓜书的所有内容都是以西瓜书的内容为前置知识进行表述的,所以南瓜书的最佳使用方法是以西瓜书\n为主线,遇到自己推导不出来或者看不懂的公式时再来查阅南瓜书;', metadata={'author': '', 'creationDate': "D:20230303170709-00'00'", 'creator': 'LaTeX with hyperref', 'file_path': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'format': 'PDF 1.5', 'keywords': '', 'modDate': '', 'page': 1, 'producer': 'xdvipdfmx (20200315)', 'source': './data_base/knowledge_db/pumkin_book/pumpkin_book.pdf', 'subject': '', 'title': '', 'total_pages': 196, 'trapped': ''})]

我们做出相应评估:

① 知识查找正确性——1

② 回答一致性——0.8(解答了问题,但是类似于“反馈”的话题偏题了)

③ 回答幻觉比例——1

④ 回答正确性——0.8(理由同上)

⑤ 逻辑性——0.7(后续内容与前面逻辑连贯性不强)

⑥ 通顺性——0.6(最后总结啰嗦且无效)

⑦ 智能性——0.5(具有 AI 回答的显著风格)

综合上述七个维度,我们可以全面、综合地评估系统在每个案例上的表现,综合考虑所有案例的得分,就可以评估系统在每个维度的表现。如果将所有维度量纲统一,那么我们还可以计算所有维度的平均得分来评估系统的得分。我们也可以针对不同维度的不同重要性赋予权值,再计算所有维度的加权平均来代表系统得分。

但是,我们可以看到,越全面、具体的评估,其评估难度、评估成本就越大。以上述七维评估为例,对系统每一个版本的每一个案例,我们都需要进行七次评估。如果我们有两个版本的系统,验证集中有10个验证案例,那么我们每一次评估就需要 $ 10 \times 2 \times 7 = 140$ 次;但当我们的系统不断改进迭代,验证集会迅速扩大,一般来说,一个成熟的系统验证集应该至少在几百的体量,迭代改进版本至少有数十个,那么我们评估的总次数会达到上万次,带来的人力成本与时间成本就很高了。因此,我们需要一种自动评估模型回答的方法。

3.2 简单自动评估

大模型评估之所以复杂,一个重要原因在于生成模型的答案很难判别,即客观题评估判别很简单,主观题评估判别则很困难。尤其是对于一些没有标准答案的问题,实现自动评估就显得难度尤大。但是,在牺牲一定评估准确性的情况下,我们可以将复杂的没有标准答案的主观题进行转化,从而变成有标准答案的问题,进而通过简单的自动评估来实现。此处介绍两种方法:构造客观题与计算标准答案相似度。

方法一 构造客观题

主观题的评估是非常困难的,但是客观题可以直接对比系统答案与标准答案是否一致,从而实现简单评估。我们可以将部分主观题构造为多项或单项选择的客观题,进而实现简单评估。例如,对于问题:

​ 【问答题】南瓜书的作者是谁?

我们可以将该主观题构造为如下客观题:

​ 【多项选择题】南瓜书的作者是谁? A 周志明 B 谢文睿 C 秦州 D 贾彬彬

要求模型回答该客观题,我们给定标准答案为 BCD,将模型给出答案与标准答案对比即可实现评估打分。根据以上思想,我们可以构造出一个 Prompt 问题模板:

prompt_template = '''
请你做如下选择题:
题目:南瓜书的作者是谁?
选项:A 周志明 B 谢文睿 C 秦州 D 贾彬彬
你可以参考的知识片段:
~~~
{}
~~~
请仅返回选择的选项
如果你无法做出选择,请返回空
'''

当然,由于大模型的不稳定性,即使我们要求其只给出选择选项,系统可能也会返回一大堆文字,其中详细解释了为什么选择如下选项。因此,我们需要将选项从模型回答中抽取出来。同时,我们需要设计一个打分策略。一般情况下,我们可以使用多选题的一般打分策略:全选1分,漏选0.5分,错选不选不得分:

def multi_select_score_v1(true_answer : str, generate_answer : str) -> float:
    # true_anser : 正确答案,str 类型,例如 'BCD'
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(true_answer)
    '''为便于计算,我们假设每道题都只有 A B C D 四个选项'''
    # 先找出错误答案集合
    false_answers = [item for item in ['A', 'B', 'C', 'D'] if item not in true_answers]
    # 如果生成答案出现了错误答案
    for one_answer in false_answers:
        if one_answer in generate_answer:
            return 0
    # 再判断是否全选了正确答案
    if_correct = 0
    for one_answer in true_answers:
        if one_answer in generate_answer:
            if_correct += 1
            continue
    if if_correct == 0:
        # 不选
        return 0
    elif if_correct == len(true_answers):
        # 全选
        return 1
    else:
        # 漏选
        return 0.5

基于上述打分函数,我们可以测试四个回答:

① B C

② 除了 A 周志华之外,其他都是南瓜书的作者

③ 应该选择 B C D

④ 我不知道

answer1 = 'B C'
answer2 = '西瓜书的作者是 A 周志华'
answer3 = '应该选择 B C D'
answer4 = '我不知道'
true_answer = 'BCD'
print("答案一得分:", multi_select_score_v1(true_answer, answer1))
print("答案二得分:", multi_select_score_v1(true_answer, answer2))
print("答案三得分:", multi_select_score_v1(true_answer, answer3))
print("答案四得分:", multi_select_score_v1(true_answer, answer4))

​ 答案一得分: 0.5
​ 答案二得分: 0
​ 答案三得分: 1
​ 答案四得分: 0

但是我们可以看到,我们要求模型在不能回答的情况下不做选择,而不是随便选。但是在我们的打分策略中,错选和不选均为0分,这样其实鼓励了模型的幻觉回答,因此我们可以根据情况调整打分策略,让错选扣一分:

def multi_select_score_v2(true_answer : str, generate_answer : str) -> float:
    # true_anser : 正确答案,str 类型,例如 'BCD'
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(true_answer)
    '''为便于计算,我们假设每道题都只有 A B C D 四个选项'''
    # 先找出错误答案集合
    false_answers = [item for item in ['A', 'B', 'C', 'D'] if item not in true_answers]
    # 如果生成答案出现了错误答案
    for one_answer in false_answers:
        if one_answer in generate_answer:
            return -1
    # 再判断是否全选了正确答案
    if_correct = 0
    for one_answer in true_answers:
        if one_answer in generate_answer:
            if_correct += 1
            continue
    if if_correct == 0:
        # 不选
        return 0
    elif if_correct == len(true_answers):
        # 全选
        return 1
    else:
        # 漏选
        return 0.5

如上,我们使用第二版本的打分函数再次对四个答案打分:

answer1 = 'B C'
answer2 = '西瓜书的作者是 A 周志华'
answer3 = '应该选择 B C D'
answer4 = '我不知道'
true_answer = 'BCD'
print("答案一得分:", multi_select_score_v2(true_answer, answer1))
print("答案二得分:", multi_select_score_v2(true_answer, answer2))
print("答案三得分:", multi_select_score_v2(true_answer, answer3))
print("答案四得分:", multi_select_score_v2(true_answer, answer4))

​ 答案一得分: 0.5
​ 答案二得分: -1
​ 答案三得分: 1
​ 答案四得分: 0

可以看到,这样我们就实现了快速、自动又有区分度的自动评估。在这样的方法下,我们只需对每一个验证案例进行构造,之后每一次验证、迭代都可以完全自动化进行,从而实现了高效的验证。

但是,不是所有的案例都可以构造为客观题,针对一些不能构造为客观题或构造为客观题会导致题目难度骤降的情况,我们需要用到第二种方法:计算答案相似度。

方法二:计算答案相似度

生成问题的答案评估在 NLP 中实则也不是一个新问题了,不管是机器翻译、自动文摘等任务,其实都需要评估生成答案的质量。NLP 一般对生成问题采用人工构造标准答案并计算回答与标准答案相似度的方法来实现自动评估。

例如,对问题:

​ 南瓜书的目标是什么?

我们可以首先人工构造一个标准回答:

​ 周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充具体的推导细节。

接着对模型回答计算其与该标准回答的相似程度,越相似则我们认为答案正确程度越高。

计算相似度的方法有很多,我们一般可以使用 BLEU 来计算相似度,其原理详见:知乎|BLEU详解,对于不想深究算法原理的同学,可以简单理解为主题相似度。

我们可以调用 nltk 库中的 bleu 打分函数来计算:

from nltk.translate.bleu_score import sentence_bleu
import jieba

def bleu_score(true_answer : str, generate_answer : str) -> float:
    # true_anser : 标准答案,str 类型
    # generate_answer : 模型生成答案,str 类型
    true_answers = list(jieba.cut(true_answer))
    # print(true_answers)
    generate_answers = list(jieba.cut(generate_answer))
    # print(generate_answers)
    bleu_score = sentence_bleu(true_answers, generate_answers)
    return bleu_score

测试一下:

true_answer = '周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充具体的推导细节。'

print("答案一:")
answer1 = '周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充具体的推导细节。'
print(answer1)
score = bleu_score(true_answer, answer1)
print("得分:", score)
print("答案二:")
answer2 = '本南瓜书只能算是我等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二下学生”'
print(answer2)
score = bleu_score(true_answer, answer2)
print("得分:", score)

​ 答案一:
​ 周志华老师的《机器学习》(西瓜书)是机器学习领域的经典入门教材之一,周老师为了使尽可能多的读者通过西瓜书对机器学习有所了解, 所以在书中对部分公式的推导细节没有详述,但是这对那些想深究公式推导细节的读者来说可能“不太友好”,本书旨在对西瓜书里比较难理解的公式加以解析,以及对部分公式补充具体的推导细节。
​ 得分: 1.2705543769116016e-231
​ 答案二:
​ 本南瓜书只能算是我等数学渣渣在自学的时候记下来的笔记,希望能够帮助大家都成为一名合格的“理工科数学基础扎实点的大二下学生”
​ 得分: 1.1935398790363042e-231

可以看到,答案与标准答案一致性越高,则评估打分就越高。通过此种方法,我们同样只需对验证集中每一个问题构造一个标准答案,之后就可以实现自动、高效的评估。

但是,该种方法同样存在几个问题:① 需要人工构造标准答案。对于一些垂直领域而言,构造标准答案可能是一件困难的事情;② 通过相似度来评估,可能存在问题。例如,如果生成回答与标准答案高度一致但在核心的几个地方恰恰相反导致答案完全错误,bleu 得分仍然会很高;③ 通过计算与标准答案一致性灵活性很差,如果模型生成了比标准答案更好的回答,但评估得分反而会降低;④ 无法评估回答的智能性、流畅性。如果回答是各个标准答案中的关键词拼接出来的,我们认为这样的回答是不可用无法理解的,但 bleu 得分会较高。

因此,针对业务情况,有时我们还需要一些不需要构造标准答案的、进阶的评估方法。

2.3 使用大模型进行评估

使用人工评估准确度高、全面性强,但人力成本与时间成本高;使用自动评估成本低、评估速度快,但存在准确性不足、评估不够全面的问题。那么,我们是否有一种方法综合两者的优点,实现快速、全面的生成问题评估呢?

以 GPT-4 为代表的大模型为我们提供了一种新的方法:使用大模型进行评估。我们可以通过构造 Prompt Engineering 让大模型充当一个评估者的角色,从而替代人工评估的评估员;同时大模型可以给出类似于人工评估的结果,因此可以采取人工评估中的多维度量化评估的方式,实现快速全面的评估。

例如,我们可以构造如下的 Prompt Engineering,让大模型进行打分:

prompt = '''
你是一个模型回答评估员。
接下来,我将给你一个问题、对应的知识片段以及模型根据知识片段对问题的回答。
请你依次评估以下维度模型回答的表现,分别给出打分:

① 知识查找正确性。评估系统给定的知识片段是否能够对问题做出回答。如果知识片段不能做出回答,打分为0;如果知识片段可以做出回答,打分为1。

② 回答一致性。评估系统的回答是否针对用户问题展开,是否有偏题、错误理解题意的情况,打分分值在0~1之间,0为完全偏题,1为完全切题。

③ 回答幻觉比例。该维度需要综合系统回答与查找到的知识片段,评估系统的回答是否出现幻觉,打分分值在0~1之间,0为全部是模型幻觉,1为没有任何幻觉。

④ 回答正确性。该维度评估系统回答是否正确,是否充分解答了用户问题,打分分值在0~1之间,0为完全不正确,1为完全正确。

⑤ 逻辑性。该维度评估系统回答是否逻辑连贯,是否出现前后冲突、逻辑混乱的情况。打分分值在0~1之间,0为逻辑完全混乱,1为完全没有逻辑问题。

⑥ 通顺性。该维度评估系统回答是否通顺、合乎语法。打分分值在0~1之间,0为语句完全不通顺,1为语句完全通顺没有任何语法问题。

⑦ 智能性。该维度评估系统回答是否拟人化、智能化,是否能充分让用户混淆人工回答与智能回答。打分分值在0~1之间,0为非常明显的模型回答,1为与人工回答高度一致。

你应该是比较严苛的评估员,很少给出满分的高评估。
用户问题:
~~~
{}
~~~
待评估的回答:
~~~
{}
~~~
给定的知识片段:
~~~
{}
~~~
你应该返回给我一个可直接解析的 Python 字典,字典的键是如上维度,值是每一个维度对应的评估打分。
不要输出任何其他内容。
'''

我们可以实际测试一下其效果:

# 使用第二章讲过的 OpenAI 原生接口

from openai import OpenAI

client = OpenAI(
    # This is the default and can be omitted
    api_key=os.environ.get("OPENAI_API_KEY"),
)

def gen_gpt_messages(prompt):
    '''
    构造 GPT 模型请求参数 messages

    请求参数:
        prompt: 对应的用户提示词
    '''
    messages = [{"role": "user", "content": prompt}]
    return messages

def get_completion(prompt, model="gpt-3.5-turbo", temperature = 0):
    '''
    获取 GPT 模型调用结果

    请求参数:
        prompt: 对应的提示词
        model: 调用的模型,默认为 gpt-3.5-turbo,也可以按需选择 gpt-4 等其他模型
        temperature: 模型输出的温度系数,控制输出的随机程度,取值范围是 0~2。温度系数越低,输出内容越一致。
    '''
    response = client.chat.completions.create(
        model=model,
        messages=gen_gpt_messages(prompt),
        temperature=temperature,
    )
    if len(response.choices) > 0:
        return response.choices[0].message.content
    return "generate answer error"

question = "应该如何使用南瓜书?"
result = qa_chain({"query": question})
answer = result["result"]
knowledge = result["source_documents"]

response = get_completion(prompt.format(question, answer, knowledge))
response

​ '{\n "知识查找正确性": 1,\n "回答一致性": 0.9,\n "回答幻觉比例": 0.9,\n "回答正确性": 0.9,\n "逻辑性": 0.9,\n "通顺性": 0.9,\n "智能性": 0.8\n}'

但是注意,使用大模型进行评估仍然存在问题:

① 我们的目标是迭代改进 Prompt 以提升大模型表现,因此我们所选用的评估大模型需要有优于我们所使用的大模型基座的性能,例如,目前性能最强大的大模型仍然是 GPT-4,推荐使用 GPT-4 来进行评估,效果最好。

② 大模型具有强大的能力,但同样存在能力的边界。如果问题与回答太复杂、知识片段太长或是要求评估维度太多,即使是 GPT-4 也会出现错误评估、错误格式、无法理解指令等情况,针对这些情况,我们建议考虑如下方案来提升大模型表现:

  1. 改进 Prompt Engineering。以类似于系统本身 Prompt Engineering 改进的方式,迭代优化评估 Prompt Engineering,尤其是注意是否遵守了 Prompt Engineering 的基本准则、核心建议等;

  2. 拆分评估维度。如果评估维度太多,模型可能会出现错误格式导致返回无法解析,可以考虑将待评估的多个维度拆分,每个维度调用一次大模型进行评估,最后得到统一结果;

  3. 合并评估维度。如果评估维度太细,模型可能无法正确理解以至于评估不正确,可以考虑将待评估的多个维度合并,例如,将逻辑性、通顺性、智能性合并为智能性等;

  4. 提供详细的评估规范。如果没有评估规范,模型很难给出理想的评估结果。可以考虑给出详细、具体的评估规范,从而提升模型的评估能力;

  5. 提供少量示例。模型可能难以理解评估规范,此时可以给出少量评估的示例,供模型参考以实现正确评估。

2.4 混合评估

事实上,上述评估方法都不是孤立、对立的,相较于独立地使用某一种评估方法,我们更推荐将多种评估方法混合起来,对于每一种维度选取其适合的评估方法,兼顾评估的全面、准确和高效。

例如,针对本项目个人知识库助手,我们可以设计以下混合评估方法:

  1. 客观正确性。客观正确性指对于一些有固定正确答案的问题,模型可以给出正确的回答。我们可以选取部分案例,使用构造客观题的方式来进行模型评估,评估其客观正确性。

  2. 主观正确性。主观正确性指对于没有固定正确答案的主观问题,模型可以给出正确的、全面的回答。我们可以选取部分案例,使用大模型评估的方式来评估模型回答是否正确。

  3. 智能性。智能性指模型的回答是否足够拟人化。由于智能性与问题本身弱相关,与模型、Prompt 强相关,且模型判断智能性能力较弱,我们可以少量抽样进行人工评估其智能性。

  4. 知识查找正确性。知识查找正确性指对于特定问题,从知识库检索到的知识片段是否正确、是否足够回答问题。知识查找正确性推荐使用大模型进行评估,即要求模型判别给定的知识片段是否足够回答问题。同时,该维度评估结果结合主观正确性可以计算幻觉情况,即如果主观回答正确但知识查找不正确,则说明产生了模型幻觉。

使用上述评估方法,基于已得到的验证集示例,可以对项目做出合理评估。限于时间与人力,此处就不具体展示了。

是一名喜欢每天折腾的咸鱼! 也是一名半退役的算竞摸鱼选手,参与过icpc,天梯赛,蓝桥等比赛.Datawhale 成员及优秀队长 --------------------------------------------------- 认证类: 华为 Harmony OS应用开发者高级认证, NISP 一级认证, H3C NE-RS网络工程师认证 --------------------------------------------------- 荣获奖项荣誉: 第十八届“挑战杯”全国大学生课外学术科技作品竞赛 “揭榜挂帅”专项赛-全国特等奖、 “美亚杯”第八届中国电子取证大赛 三等奖、 中国高校计算机大赛-团体程序天梯赛 省高校一等奖、 “蓝桥杯”省一等奖、 H3C新华三杯 省三等奖、 中国移动“梧桐杯”大数据创新大赛 省三等奖、 百度 飞桨领航团 金牌团长
最后更新于 2024-04-26