LangChain是一個(gè)基于大語(yǔ)言模型(如ChatGPT)用于構(gòu)建端到端語(yǔ)言模型應(yīng)用的 Python 框架。它提供了一套工具、組件和接口,可簡(jiǎn)化創(chuàng)建由大型語(yǔ)言模型 (LLM) 和聊天模型提供支持的應(yīng)用程序的過(guò)程。LangChain 可以輕松管理與語(yǔ)言模型的交互,將多個(gè)組件鏈接在一起,以便在不同的應(yīng)用程序中使用。
今天我們來(lái)學(xué)習(xí)DeepLearning.AI的在線課程:LangChain for LLM Application Development的第五門課:Evaluation(評(píng)估),所謂評(píng)估是指檢驗(yàn)LLM回答的問(wèn)題是否正確的方法,在上一篇博客Q&A over Documents中我們解釋了如何通過(guò)langchain來(lái)實(shí)現(xiàn)對(duì)文檔的問(wèn)答功能,在文檔的問(wèn)答過(guò)程中LLM會(huì)就用戶提出的關(guān)于文檔內(nèi)容的相關(guān)問(wèn)題進(jìn)行回答,那么今天我們需要研究的就是如何來(lái)檢驗(yàn)LLM的回答是否正確?
要評(píng)估LLM回答問(wèn)題的準(zhǔn)確性大致需要下面幾個(gè)步驟:
- 需要?jiǎng)?chuàng)建一組關(guān)于相關(guān)的問(wèn)答測(cè)試集(包含了問(wèn)題和標(biāo)準(zhǔn)答案)
- 讓LLM回答測(cè)試集中的所有問(wèn)題,并收集LLM給出的所有答案
- 將LLM的答案與問(wèn)答測(cè)試集中的標(biāo)準(zhǔn)答案做比對(duì),并給LLM的表現(xiàn)評(píng)分
下面我們就開始來(lái)討論評(píng)估LLM表現(xiàn)吧!
創(chuàng)建基于文檔問(wèn)答的Q/A應(yīng)用
首先我們還是要做一些基礎(chǔ)性工作,比如設(shè)置openai的api key,導(dǎo)入一些langchain的基礎(chǔ)庫(kù):
import pandas as pd
from langchain.chains import RetrievalQA
from langchain.chat_models import ChatOpenAI
from langchain.document_loaders import CSVLoader
from langchain.indexes import VectorstoreIndexCreator
from langchain.vectorstores import DocArrayInMemorySearch
import os
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
接下來(lái)我們需要導(dǎo)入一個(gè)csv文件,該文檔主要包含2列,name和description,其中name表示商品的名稱,description表示該商品的說(shuō)明信息,我們需要對(duì)改文檔的產(chǎn)品信息進(jìn)行問(wèn)答。
df=pd.read_csv("OutdoorClothingCatalog_1000.csv")
df
?下面我們查看一下其中的某個(gè)商品信息:
print(df[:1].name.values[0])
print('------------------------')
print(df[:1].description.values[0])
?下面我們將該商品的信息翻譯成中文,這樣便于大家理解:
?接下來(lái)我們要?jiǎng)?chuàng)建一個(gè)用于回答文檔內(nèi)容的chain:RetrievalQA, 創(chuàng)建RetrievalQA需要包含以下幾個(gè)步驟:
- 創(chuàng)建一個(gè)文檔加載器CSVLoad實(shí)例
- 創(chuàng)建向量數(shù)據(jù)庫(kù)索引index
- 創(chuàng)建llm
- 創(chuàng)建文檔問(wèn)答chain,RetrievalQA
#1.創(chuàng)建文檔加載器
file = 'OutdoorClothingCatalog_1000.csv'
loader = CSVLoader(file_path=file)
data = loader.load()
#2.創(chuàng)建向量數(shù)據(jù)庫(kù)索引
index = VectorstoreIndexCreator(
vectorstore_cls=DocArrayInMemorySearch
).from_loaders([loader])
#3.創(chuàng)建llm
llm= ChatOpenAI(temperature = 0.0)
#4.創(chuàng)建文檔問(wèn)答chain
qa = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=index.vectorstore.as_retriever(),
verbose=True,
chain_type_kwargs = {
"document_separator": "<<<<>>>>>"
}
)
上述代碼的主要功能及作用在LangChain大型語(yǔ)言模型(LLM)應(yīng)用開發(fā)(四):Q&A over Documents這篇博客中都已說(shuō)明,這里不再贅述。
設(shè)置測(cè)試的數(shù)據(jù)
下面我們查看一下經(jīng)過(guò)檔加載器CSVLoad加載后生成的data內(nèi)的信息,這里我們抽取data中的第九和第十條數(shù)據(jù)看看它們的主要內(nèi)容:
手動(dòng)創(chuàng)建測(cè)試集?
需要說(shuō)明的是這里我們的文檔是csv文件,所以我們使用的是文檔加載器是CSVLoader,CSVLoader會(huì)對(duì)csv文件中的每一行數(shù)據(jù)進(jìn)行分割,所以這里看到的data[10],data[11]的內(nèi)容則是csv文件中的第10,第11條數(shù)據(jù)的內(nèi)容。下面我們根據(jù)這兩條數(shù)據(jù)手動(dòng)設(shè)置兩條“問(wèn)答對(duì)”,每一個(gè)“問(wèn)答對(duì)”中包含一個(gè)query,一個(gè)answer:
examples = [
{
"query": "Do the Cozy Comfort Pullover Set\
have side pockets?",
"answer": "Yes"
},
{
"query": "What collection is the Ultra-Lofty \
850 Stretch Down Hooded Jacket from?",
"answer": "The DownTek collection"
}
]
讓LLM生成Q/A測(cè)試用例
在我以前寫的兩篇博客中(使用大型語(yǔ)言模(LLM)構(gòu)建系統(tǒng)(七):評(píng)估1,與 使用大型語(yǔ)言模(LLM)構(gòu)建系統(tǒng)(七):評(píng)估2)我們使用的方法都是通過(guò)手動(dòng)的方法來(lái)構(gòu)建測(cè)試數(shù)據(jù)集,比如說(shuō)我們可以手動(dòng)創(chuàng)建10個(gè)問(wèn)題和10個(gè)答案,然后讓LLM回答這10個(gè)問(wèn)題,再將LLM給出的答案與我們準(zhǔn)備好的答案做比較,最后再給LLM打分。評(píng)估的流程大概就是這樣,但是這里有一個(gè)問(wèn)題,就是我們需要手動(dòng)去創(chuàng)建所有的問(wèn)題集和答案集,那會(huì)是一個(gè)非常耗費(fèi)人力和時(shí)間的成本。那有沒有一種可以自動(dòng)創(chuàng)建大量問(wèn)題集和答案集的方法呢?那當(dāng)然是有的,今天我們就來(lái)介紹Langchain提供的方法:QAGenerateChain,我們可以通過(guò)QAGenerateChain來(lái)為我們的文檔自動(dòng)創(chuàng)建問(wèn)答集:
from langchain.evaluation.qa import QAGenerateChain
example_gen_chain = QAGenerateChain.from_llm(ChatOpenAI())
new_examples = example_gen_chain.apply([{"doc": t} for t in data[:5]])
print(new_examples)
?這里我們對(duì)上述代碼做個(gè)簡(jiǎn)單說(shuō)明,我們創(chuàng)建了一個(gè)QAGenerateChain,然后我們應(yīng)用了QAGenerateChain的apply方法對(duì)data中的前5條數(shù)據(jù)創(chuàng)建了5個(gè)“問(wèn)答對(duì)”,由于創(chuàng)建問(wèn)答集是由LLM來(lái)自動(dòng)完成的,因此會(huì)涉及到token成本的問(wèn)題,所以我們這里出于演示的目的,只對(duì)data中的前5條數(shù)據(jù)創(chuàng)建問(wèn)答集。
那QAGenerateChain是如何自動(dòng)創(chuàng)建問(wèn)題集的,一個(gè)簡(jiǎn)單的apply方法似乎隱藏了很多的細(xì)節(jié),如果你對(duì)這個(gè)隱藏的細(xì)節(jié)感興趣,那我們可以嘗試用debug的方式來(lái)打開這個(gè)潘多拉魔盒:
import langchain
#打開debug
langchain.debug = True
new_examples = example_gen_chain.apply([{"doc": t} for t in data[:5]])
#關(guān)閉debug
langchain.debug = False
?從上面展現(xiàn)的細(xì)節(jié)中我們可以看到,原來(lái)在QAGenerateChain中有一個(gè)內(nèi)置的prompt,在這個(gè)內(nèi)置的prompt的前綴信息中,以"Human"的角色要求LLM對(duì)給與它的文檔產(chǎn)生一個(gè)question和answer。這個(gè)prompt的前綴信息大概就長(zhǎng)這個(gè)樣子:
?QAGenerateChain會(huì)在data中的每一條數(shù)據(jù)中都運(yùn)用這個(gè)prompt模板,因此data中的每一條數(shù)據(jù)都會(huì)產(chǎn)生一條“問(wèn)答對(duì)”。有了問(wèn)答集以后,我們還需要對(duì)問(wèn)答集進(jìn)行解析,從中過(guò)濾出真正有用的信息,不過(guò)我們首先需要?jiǎng)?chuàng)建一個(gè)解析函數(shù)parse_strings:
def parse_strings(strings_list):
parsed_list = []
for s in strings_list:
s = s.replace('\n\n','\n')
split_s = s.split('\n')
# Ensure there are 2 parts in the split string
if len(split_s) != 2:
continue
question_part, answer_part = split_s
# Ensure each part has the correct prefix
if not question_part.startswith('QUESTION: ') or not answer_part.startswith('ANSWER: '):
continue
# Remove the prefixes and strip leading/trailing whitespace
question = question_part.replace('QUESTION: ', '').strip()
answer = answer_part.replace('ANSWER: ', '').strip()
parsed_list.append({"query": question, "answer": answer})
return parsed_list
#對(duì)問(wèn)答集進(jìn)行解析
new_examples = parse_strings([t['text'] for t in new_examples])
print(new_examples)
?這里經(jīng)過(guò)解析以后我們的new_examples 中只包含了5個(gè)query和5個(gè)answer,沒有其他多余的信息,這正是我們想要的測(cè)試集。
組合測(cè)試集
還記得我們前面手動(dòng)創(chuàng)建的兩個(gè)問(wèn)答集嗎?現(xiàn)在我們需要將之前手動(dòng)創(chuàng)建的問(wèn)答集合并到QAGenerateChain創(chuàng)建的問(wèn)答集中,這樣在答集中既有手動(dòng)創(chuàng)建的例子又有l(wèi)lm自動(dòng)創(chuàng)建的例子,這會(huì)使我們的測(cè)試集更加完善:
examples += new_examples
examples
?這里我們看到examples 的前兩條數(shù)據(jù)就是我們先前手動(dòng)創(chuàng)建的,接下來(lái)我們就需要讓之前創(chuàng)建的文檔問(wèn)答chain來(lái)回答這個(gè)測(cè)試集里的問(wèn)題,來(lái)看看LLM是怎么回答的吧:
qa.run(examples[0]["query"])
?
?這里我們看到qa回答了第0個(gè)問(wèn)題:“Yes, the Cozy Comfort Pullover Set does have side pockets.” ,這里的第0個(gè)問(wèn)題就是先前我們手動(dòng)創(chuàng)建的第一個(gè)問(wèn)題,并且我們手動(dòng)創(chuàng)建的answer是 :"Yes", 這里我們發(fā)現(xiàn)問(wèn)答chain qa回答的也是“Yes”,只是它比我們的答案還多了一段說(shuō)明:“the Cozy Comfort Pullover Set does have side pockets.”。
你想知道問(wèn)答chain qa是怎么找到問(wèn)題的答案的嗎?魔鬼往往隱藏在細(xì)節(jié)中,下面讓我們打開debug,看看問(wèn)答chain qa是如何找到問(wèn)題的答案!
langchain.debug = True
qa.run(examples[0]["query"])
langchain.debug = False
?這里我們稍微對(duì)問(wèn)答chain qa尋找答案的過(guò)程進(jìn)行一些說(shuō)明,首先qa拿到問(wèn)題,然后根據(jù)問(wèn)題去向量數(shù)據(jù)庫(kù)中搜索和問(wèn)題相關(guān)的產(chǎn)品信息(會(huì)在全部產(chǎn)品中搜索),由于向量數(shù)據(jù)庫(kù)中可能會(huì)存在多條產(chǎn)品信息和問(wèn)題相關(guān),因此這里會(huì)用“<<<<>>>>>”來(lái)分隔搜索到的多個(gè)產(chǎn)品信息,這里所謂的搜索是指向量間的相似度計(jì)算和比較,首先將問(wèn)題轉(zhuǎn)換成向量,再計(jì)算問(wèn)題向量和數(shù)據(jù)庫(kù)中每個(gè)向量的相似度,獲取相似度最高的n條向量,然后再將這些相似的向量再轉(zhuǎn)換成對(duì)應(yīng)的文本即可。當(dāng)這些步驟完成以后我們就看到了上述的結(jié)果,其中羅列了question和content,question是我們提出的問(wèn)題,而“content”則是搜索到的多個(gè)相關(guān)產(chǎn)品信息,它們被用“<<<<>>>>>”分隔。這里需要加入一個(gè)我的個(gè)人判斷:在搜索相關(guān)文檔的時(shí)候應(yīng)該是沒有l(wèi)lm參與的,因此不會(huì)產(chǎn)生token成本的問(wèn)題。在有了問(wèn)題和相關(guān)產(chǎn)品信息后,接下來(lái)就需要LLM登場(chǎng)了,這里就會(huì)有一個(gè)prompt,在這個(gè)prompt中有一個(gè)System前綴信息,它告訴llm需要做什么,緊接著前綴信息的是多個(gè)產(chǎn)品信息,它們被用<<<<>>>>>進(jìn)行分隔,最后是我們的問(wèn)題,這里用Human來(lái)標(biāo)識(shí)我們的問(wèn)題。
?
?下面是輸出部分,LLM會(huì)根據(jù)給它的prompt輸出一個(gè)內(nèi)容較多的json格式的結(jié)果,其中包含了問(wèn)題的答案:
?
?最后經(jīng)過(guò)過(guò)濾,得到了最終的答案:
前面我們讓問(wèn)答chain qa回答了測(cè)試集中的一個(gè)問(wèn)題,下面我們要做的是讓qa來(lái)回答測(cè)試集中的所有問(wèn)題:
predictions = qa.apply(examples)
??
?基于LLM的自我評(píng)估
讓我們來(lái)理一下思路,首先我們讓LLM自動(dòng)創(chuàng)建了問(wèn)答測(cè)試集,接著我們又讓LLM回答了測(cè)試集中所有的問(wèn)題并得到了所有問(wèn)題的回復(fù)信息。接下來(lái)我們要做的就是將這些問(wèn)題的回復(fù)信息與測(cè)試集里的答案進(jìn)行比對(duì),更其妙的是這個(gè)比對(duì)過(guò)程也將是由LLM自己來(lái)完成,也就是說(shuō)我們的LLM既當(dāng)球員,又當(dāng)裁判,最后再由“裁判”給出比對(duì)的結(jié)果,不過(guò)我需要指出的這里既當(dāng)球員,又當(dāng)裁判的LLM并非是同一個(gè)chain構(gòu)成的,它們來(lái)自于不同的chain,也就是說(shuō)這些chain的職能是不同的:
from langchain.evaluation.qa import QAEvalChain
#創(chuàng)建LLM
llm = ChatOpenAI(temperature=0)
#創(chuàng)建評(píng)估chain
eval_chain = QAEvalChain.from_llm(llm)
#生成評(píng)估結(jié)果
graded_outputs = eval_chain.evaluate(examples, predictions)
#統(tǒng)計(jì)評(píng)估結(jié)果
for i, eg in enumerate(examples):
print(f"Example {i}:")
print("Question: " + predictions[i]['query'])
print("Real Answer: " + predictions[i]['answer'])
print("Predicted Answer: " + predictions[i]['result'])
print("Predicted Grade: " + graded_outputs[i]['text'])
print()
?從上面的返回結(jié)果中我們看到,每一個(gè)問(wèn)題中都包含了Question,Real Answer,Predicted Anser和Predicted Grade 四組內(nèi)容,其中Real Answer是有先前的QAGenerateChain創(chuàng)建的問(wèn)答測(cè)試集中的答案,而Predicted Answer則是由我們的問(wèn)答chain qa回答的問(wèn)題,最后的Predicted Grade則是由上面代碼中的QAEvalChain回答的。
總結(jié)
今天我們學(xué)習(xí)了如何利用Langchain來(lái)評(píng)估LLM的表現(xiàn),和以前評(píng)估openai模型的方法不同的是,這里我們使用的是全自動(dòng)方式,即全自動(dòng)方式生成測(cè)試集,然后全自動(dòng)的給出問(wèn)題的預(yù)測(cè)結(jié)果,最好全自動(dòng)的評(píng)估預(yù)測(cè)結(jié)果的準(zhǔn)確性,通過(guò)這種全自動(dòng)的方式解放了我們的雙手,使我們不需要因?yàn)闆]有測(cè)試數(shù)據(jù)集而苦惱,大大提高了生產(chǎn)率。
參考資料
QA Generation | ????? Langchain文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-557833.html
Question Answering | ????? Langchain文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-557833.html
到了這里,關(guān)于LangChain大型語(yǔ)言模型(LLM)應(yīng)用開發(fā)(五):評(píng)估的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!