作者 | 崔皓
審校 | 重樓
摘要
在自然語言處理領(lǐng)域,為了讓模型能夠處理特定領(lǐng)域的問題,需要進(jìn)行Fine-tuning,即在基礎(chǔ)模型上訓(xùn)練模型以理解和回答特定領(lǐng)域的問題。在這個(gè)過程中,Embedding起到了關(guān)鍵作用,它將離散型的符號(hào)轉(zhuǎn)換為連續(xù)型的數(shù)值向量,幫助模型理解文本信息。詞嵌入是一種常用的Embedding方法,通過將每個(gè)單詞轉(zhuǎn)換為多維向量來捕獲其語義信息。本文通過LangChain,ChromaDB以及OpenAI實(shí)現(xiàn)Fine-tuning的過程,通過更新Embedding層來讓模型更好地理解特定領(lǐng)域的詞匯。
開篇
在自然語言處理領(lǐng)域,最常見的用例之一是與文檔相關(guān)的問題回答。雖然這方面ChatGPT已經(jīng)做的足夠好了,但它也只能作為一個(gè)通才,如果需要了解更多專業(yè)領(lǐng)域的內(nèi)容還需要進(jìn)一步學(xué)習(xí)。 你可以想象一下將你所在領(lǐng)域的文檔,包括pdf、txt 或者數(shù)據(jù)庫(kù)中的信息教給模型, 讓模型也具備回答相關(guān)領(lǐng)域問題的能力。此時(shí)的模型就好像一個(gè)行業(yè)專家可以回答行業(yè)內(nèi)的各種問題, 當(dāng)然你需要喂給它大量的數(shù)據(jù)才能讓它飽讀詩書。
Fine-tuning
假設(shè)你正在使用一個(gè)預(yù)訓(xùn)練的語言模型來建立一個(gè)電影推薦系統(tǒng)。這個(gè)語言模型已經(jīng)在大量的文本數(shù)據(jù)上進(jìn)行了訓(xùn)練,因此它已經(jīng)學(xué)會(huì)了理解和生成人類語言。但是,此時(shí)該模型并不知道和電影相關(guān)的事情,如果你希望這個(gè)模型能夠理解和回答有關(guān)電影的特定問題,例如“這部電影的評(píng)分是多少?”或“這部電影的主角是誰?”。
為了讓模型能夠處理這些特定的問題,你需要對(duì)模型進(jìn)行Fine-tuning。具體來說,就是需要收集一些電影相關(guān)的問題和答案,然后使用這些數(shù)據(jù)來訓(xùn)練模型。在訓(xùn)練過程中,模型的參數(shù)(或者說“權(quán)重”)將會(huì)被稍微調(diào)整,以使模型更好地理解和回答這些電影相關(guān)的問題。這就是Fine-tuning的過程。
需要注意的是,F(xiàn)ine-tuning通常比從零開始訓(xùn)練模型需要更少的數(shù)據(jù)和計(jì)算資源,因?yàn)轭A(yù)訓(xùn)練的模型已經(jīng)學(xué)會(huì)了許多基礎(chǔ)的語言知識(shí)。我們所做的Fine-tuning,只是在基礎(chǔ)知識(shí)添加相關(guān)電影的知識(shí)從而幫助模型完成處理電影問答的工作。
Embedding
有了上面的思路,我們知道如果讓一個(gè)通才變成我們需要的專才就需要對(duì)其進(jìn)行專業(yè)知識(shí)的教學(xué),這個(gè)就是Fine-tunning 要做的事情。 它在基礎(chǔ)的模型上面進(jìn)行微調(diào),告訴它更多的專業(yè)知識(shí)。這些專業(yè)的知識(shí)是以文本的形式存在,并保存到已經(jīng)生成的模型庫(kù)中。
以電影專業(yè)為例,我們會(huì)將大量的電影相關(guān)的信息轉(zhuǎn)換成文本,然后將其保存到數(shù)據(jù)模型庫(kù)中。這也就意味著需要將文本的內(nèi)容拆成一個(gè)個(gè)的單詞并對(duì)其進(jìn)行保存。
這里我們就需要用到"Embedding", 它是將離散型的符號(hào)(比如單詞)轉(zhuǎn)換為連續(xù)型的數(shù)值向量的過程。在我們的電影推薦系統(tǒng)例子中,Embedding可以幫助模型理解電影名稱、演員名字、電影類型等文本信息。
例如,當(dāng)我們談?wù)撾娪盎蜓輪T的名稱時(shí),我們通常會(huì)使用詞嵌入(word embeddings)。這種嵌入可以把每一個(gè)單詞轉(zhuǎn)換為一個(gè)多維的向量,這個(gè)向量能捕獲該詞的語義信息。詞嵌入的一個(gè)重要特性是,語義相似的詞會(huì)被映射到向量空間中相近的位置。如圖1所示,“king,” “queen,” “man,” 和 “woman.”根據(jù)經(jīng)驗(yàn)直觀地理解這些詞之間的關(guān)系。例如,man在概念上比queen更接近king。所以我們將這些詞轉(zhuǎn)化為笛卡爾空間上的數(shù)據(jù),以直觀的方式標(biāo)注詞在空間中的關(guān)系。
但是這還不夠,我們還需要進(jìn)一步衡量詞與詞之間的關(guān)系。從詞義上man和king會(huì)更加接近一些,對(duì)woman和queen也是這樣。于是,我們將坐標(biāo)軸中的點(diǎn)變成向量,也就是有長(zhǎng)度和方向的量。圖2 中,Man 和King 分別用黃色和藍(lán)色的向量線表示,它們形成的夾角就表示了它們之間的關(guān)系,這個(gè)角越小關(guān)系就更緊密。對(duì)于Woman 和Queen 來說也是如此。 因此,我們可以通過詞嵌入之后形成的向量夾角來測(cè)量詞與詞之間的關(guān)系。
說明:
在詞嵌入中,向量的"大小"通常指的是向量的長(zhǎng)度,這是由向量的所有元素(或坐標(biāo))的平方和的平方根計(jì)算出來的。這是一個(gè)數(shù)學(xué)概念,與向量在幾何空間中的實(shí)際長(zhǎng)度相對(duì)應(yīng)。
然而,這個(gè)"大小"或"長(zhǎng)度"在詞嵌入中通常沒有明確的語義含義。也就是說,一個(gè)詞的嵌入向量的長(zhǎng)度并不能告訴我們關(guān)于這個(gè)詞的具體信息。例如,一個(gè)詞的嵌入向量的長(zhǎng)度并不能告訴我們這個(gè)詞的重要性、頻率、情感等。
在創(chuàng)建詞嵌入時(shí),我們通常不會(huì)直接定義向量的大小。相反,向量的大小是由嵌入模型(如Word2Vec或GloVe)在學(xué)習(xí)過程中自動(dòng)確定的。這個(gè)過程通常是基于大量的文本數(shù)據(jù),并考慮到詞語在文本中的上下文信息。
在某些情況下,我們可能會(huì)對(duì)詞嵌入向量進(jìn)行歸一化,使得每個(gè)向量的長(zhǎng)度都為1。這樣做的目的通常是為了消除向量長(zhǎng)度的影響,使得我們可以更純粹地比較向量之間的角度,從而衡量詞語之間的語義相似性。
圖2
實(shí)際上在將專業(yè)知識(shí)不斷更新到模型庫(kù)的過程就是Fine-tuning,在更新過程中需要將詞保存到模型的操作就是Embedding。此時(shí),模型的Embedding層會(huì)因?yàn)镕ine-tuning 而被更新。例如,如果預(yù)訓(xùn)練的模型是在通用的文本數(shù)據(jù)上訓(xùn)練的,那么它可能并不完全理解電影相關(guān)的一些專有名詞或者俚語。在Fine-tuning過程中,我們可以通過更新Embedding層來讓模型更好地理解這些電影相關(guān)的詞匯。
需要注意的是,雖然我們有時(shí)候會(huì)在Fine-tuning過程中更新Embedding層,但不是必須的。如果預(yù)訓(xùn)練的模型已經(jīng)有很好的詞嵌入,并且新任務(wù)的數(shù)據(jù)不夠多,我們可能會(huì)選擇凍結(jié)(即不更新)Embedding層,只更新模型的其他部分,以防止模型在小型數(shù)據(jù)集上過擬合。
假設(shè)你正在使用一個(gè)預(yù)訓(xùn)練的模型來識(shí)別各種影片。你的預(yù)訓(xùn)練模型可能是在數(shù)百萬條影片信息上訓(xùn)練而來的,由于訓(xùn)練的數(shù)據(jù)足夠大,模型已經(jīng)識(shí)別各種影片。然而,你想要使用這個(gè)模型來識(shí)別特定種類的影片,比如說文藝片和紀(jì)錄片。
此時(shí),你需要對(duì)模型進(jìn)行Fine-tuning,但是,你手上的文藝片和記錄片的訓(xùn)練集只有幾百條,這比預(yù)訓(xùn)練模型的幾百萬相差很大, Embedding 的效果就不會(huì)太好了。
此時(shí),需要"凍結(jié)"模型的Embedding層。這個(gè)層已經(jīng)在預(yù)訓(xùn)練過程中學(xué)會(huì)了如何從眾多影片信息中提取有用的電影特征。如果執(zhí)意進(jìn)行Fine-tuning,并Embedding你手上的 幾百條信息,模型可能會(huì)過度適應(yīng)小型數(shù)據(jù)集,導(dǎo)致其在未見過的數(shù)據(jù)上表現(xiàn)不佳。這就是我們所說的過擬合。
如何進(jìn)行我們的Fine-tuning 和Embedding
有了上面的概念,我們需要確定創(chuàng)建自己專業(yè)模型的思路。首先,需要有一個(gè)預(yù)處理的模型,就是一個(gè)已經(jīng)被別人訓(xùn)練好的LLM(大語言模型),例如OpenAI的GPT-3等。有了這個(gè)LLM之后,把我們的專業(yè)知識(shí)(文本)Embedding 到其中形成新的模型就齊活了。
為了達(dá)到上面的目的,我們使用了LangChain作為管理和創(chuàng)建基于LLMs的應(yīng)用程序的工具。LangChain是一個(gè)軟件開發(fā)框架,旨在簡(jiǎn)化使用大型語言模型(LLMs)創(chuàng)建應(yīng)用程序的過程。作為一個(gè)語言模型集成框架,LangChain的使用案例大致與一般的語言模型重合,包括文檔分析和摘要,聊天機(jī)器人和代碼分析。
說明:
LangChain于2022年10月作為一個(gè)開源項(xiàng)目由Harrison Chase在機(jī)器學(xué)習(xí)初創(chuàng)公司Robust Intelligence工作時(shí)發(fā)起。該項(xiàng)目迅速獲得了人氣,GitHub上有數(shù)百名貢獻(xiàn)者進(jìn)行了改進(jìn),Twitter上有熱門討論,項(xiàng)目的Discord服務(wù)器活動(dòng)熱烈,有許多YouTube教程,以及在舊金山和倫敦的見面會(huì)。這個(gè)新的初創(chuàng)公司在宣布從Benchmark獲得1000萬美元的種子投資一周后,就從風(fēng)投公司Sequoia Capital籌集了超過2000萬美元的資金,公司估值至少為2億美元。
有了處理LLM的工具,那么再找個(gè)LLM 讓我們?cè)谏厦?Fine-tuning 就好了。 我們選擇了Chroma,它是一個(gè)開源的嵌入式數(shù)據(jù)庫(kù)。如圖3所示,Chroma通過使知識(shí)、事實(shí)和技能可以輕松地為大型語言模型(LLMs)插入信息,從而簡(jiǎn)化了LLM應(yīng)用的構(gòu)建。它可以存儲(chǔ)嵌入和元數(shù)據(jù),嵌入文檔和查詢,搜索嵌入式。我們會(huì)使用ChromaDB作為向量庫(kù),用來保存Embedding 的信息。
圖3
當(dāng)然還需要OpenAI 提供的預(yù)處理模型,將文本轉(zhuǎn)化為機(jī)器可以理解的向量形式,方便Embedding。
這個(gè)編程環(huán)境我使用了CoLab,Google Colab(全名為Google Colaboratory)是一個(gè)由Google提供的免費(fèi)云端Jupyter Notebook環(huán)境。用戶可以在其中編寫和執(zhí)行Python代碼,無需進(jìn)行任何設(shè)置,僅需要一個(gè)Google帳戶即可使用。Google Colab被廣泛用于數(shù)據(jù)分析、機(jī)器學(xué)習(xí)、深度學(xué)習(xí)等領(lǐng)域。它還提供了免費(fèi)的計(jì)算資源:包括CPU,GPU,甚至TPU(Tensor Processing Units)。這樣就省去了我安裝Python 的煩惱,打開網(wǎng)頁就可以直接使用。
開始編碼
首先,需要安裝一些庫(kù)。需要Langchain和OpenAI來實(shí)例化和管理LLMs。
每個(gè)命令的含義如下:
pip install langchain
pip install openai
pip install chromadb
pip install tiktoken
上面的代碼主要是安裝各種工具:
- pip install langchain:安裝Langchain庫(kù)。
- pip install openai:安裝OpenAI庫(kù)。OpenAI庫(kù)提供了一個(gè)Python接口,用于訪問OpenAI的各種API,包括用于生成文本的GPT-3等模型的API。
- pip install chromadb:安裝ChromaDB庫(kù)。ChromaDB是一個(gè)開源的嵌入數(shù)據(jù)庫(kù),它提供了存儲(chǔ)和搜索嵌入向量的功能。
- pip install tiktoken:安裝TikToken庫(kù)。TikToken是OpenAI開發(fā)的一個(gè)庫(kù),它能夠用于分析如何計(jì)算一個(gè)給定文本的token數(shù)量。這里的token是用來記錄Embedding中字、詞或者句子的個(gè)數(shù)。
接著,需要一個(gè)文本文件,也就是我們需要教模型學(xué)習(xí)的內(nèi)容。這里可以通過網(wǎng)絡(luò)獲取,為了方便,我就手動(dòng)寫了幾個(gè)字符串,用作測(cè)試。
import requests
#text_url = '【輸入你的文本的網(wǎng)絡(luò)地址】'
#response = requests.get(text_url)
#data = response.text
data="Bob likes blue. Bob is from China."
接著,我們導(dǎo)入需要的類。
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.embeddings.cohere import CohereEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores.elastic_vector_search import ElasticVectorSearch
from langchain.vectorstores import Chroma
導(dǎo)入了`OpenAIEmbeddings`,用于獲取OpenAI大型語言模型生成的詞向量(或者句向量)。
導(dǎo)入了`CohereEmbeddings`,用于獲取Cohere大型語言模型生成的詞向量(或者句向量)。Cohere是一個(gè)提供預(yù)訓(xùn)練語言模型服務(wù)的公司。
導(dǎo)入了`CharacterTextSplitter`,用于將文本按照字符進(jìn)行切割。
導(dǎo)入了`ElasticVectorSearch`,用于在Elasticsearch中進(jìn)行向量搜索。
導(dǎo)入了`Chroma`,用于操作ChromaDB。
總的來說,是導(dǎo)入一些處理文本、獲取和存儲(chǔ)詞向量、以及進(jìn)行向量搜索的工具。
接著將導(dǎo)入的文本進(jìn)行處理,主要是將我們輸入的文本轉(zhuǎn)換成向量,并且保存到ChromaDB的向量庫(kù)中。我將代碼的含義通過注釋的方式展示如下:
import openai
#將你的OpenAI API的密鑰存儲(chǔ)在變量myApiKey中
myApiKey = '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'
#創(chuàng)建了一個(gè)CharacterTextSplitter的實(shí)例,這是一個(gè)用于將文本分割成較小部分的工具
text_splitter = CharacterTextSplitter()
#使用text_splitter將輸入的文本data分割成較小的部分,并將這些部分存儲(chǔ)在變量texts中。
texts = text_splitter.split_text(data)
print(texts)
#創(chuàng)建了一個(gè)OpenAIEmbeddings的實(shí)例,用于獲取OpenAI大型語言模型生成的詞向量(或者句向量)。
embeddings = OpenAIEmbeddings(openai_api_key=myApiKey)
persist_directory = 'db'
#使用ChromaDB創(chuàng)建了一個(gè)文本的詞向量數(shù)據(jù)庫(kù)。它將texts中的文本部分轉(zhuǎn)換為詞向量,然后將這些詞向量和相應(yīng)的元數(shù)據(jù)存儲(chǔ)在指定的持久化目錄中。
docsearch = Chroma.from_texts(
texts,
embeddings,
persist_directory=persist_directory,
metadatas=[{"source":f"{i}-pl"} for i in range(len(texts))]
)
既然上面的代碼將我們的文本Embedding到項(xiàng)目庫(kù)中了,那么當(dāng)我們提問的時(shí)候就可以從這個(gè)庫(kù)中讀取相關(guān)的信息。下面的代碼使用LangChain庫(kù)構(gòu)建一個(gè)檢索型問答(Retrieval-based Question Answering)系統(tǒng),然后使用這個(gè)系統(tǒng)來回答一個(gè)特定的問題。
#從LangChain庫(kù)中導(dǎo)入了RetrievalQAWithSourcesChain類,這是一個(gè)用于構(gòu)建檢索型問答系統(tǒng)的類。
from langchain.chains.qa_with_sources.retrieval import RetrievalQAWithSourcesChain
#創(chuàng)建了一個(gè)RetrievalQAWithSourcesChain的實(shí)例,即一個(gè)檢索型問答系統(tǒng)。該系統(tǒng)使用OpenAI的大型語言模型(設(shè)置了溫度參數(shù)為0)進(jìn)行問答,使用Retriever進(jìn)行信息檢索,并且設(shè)置了返回來源文檔的選項(xiàng)。
chain = RetrievalQAWithSourcesChain.from_chain_type(
llm = OpenAI(openai_api_key=myApiKey,temperature=0),
chain_type="stuff",
retriever = retriever,
return_source_documents = True
)
#一個(gè)函數(shù),用于處理問答系統(tǒng)返回的結(jié)果。該函數(shù)會(huì)打印出答案以及來源文檔。
def process_result(result):
print(result['answer'])
print("\n\n Sources: ", result['sources'])
print(result['sources'])
#提出問題
question = '鮑勃喜歡什么顏色'
#使用定義的問答系統(tǒng)來回答問題,并將結(jié)果存儲(chǔ)在result變量中
result = chain({"question":question})
#調(diào)用定義的函數(shù)來處理并打印問答結(jié)果。
process_result(result)
執(zhí)行上面的代碼得到如圖4 結(jié)果??梢钥吹轿覀冚斎氲奈谋尽癇ob likes blue color”被作為答案回應(yīng)了我們的提問。
整體代碼清單
pip install langchain
pip install openai
pip install chromadb
pip install tiktoken
import requests
#text_url = '【輸入你的文本的網(wǎng)絡(luò)地址】'
#response = requests.get(text_url)
#data = response.text
data="Bob likes blue. Bob is from China."
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.embeddings.cohere import CohereEmbeddings
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores.elastic_vector_search import ElasticVectorSearch
from langchain.vectorstores import Chroma
import openai
#將你的OpenAI API的密鑰存儲(chǔ)在變量myApiKey中
myApiKey = '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'
#創(chuàng)建了一個(gè)CharacterTextSplitter的實(shí)例,這是一個(gè)用于將文本分割成較小部分的工具
text_splitter = CharacterTextSplitter()
#使用text_splitter將輸入的文本data分割成較小的部分,并將這些部分存儲(chǔ)在變量texts中。
texts = text_splitter.split_text(data)
print(texts)
#創(chuàng)建了一個(gè)OpenAIEmbeddings的實(shí)例,用于獲取OpenAI大型語言模型生成的詞向量(或者句向量)。
embeddings = OpenAIEmbeddings(openai_api_key=myApiKey)
persist_directory = 'db'
#使用ChromaDB創(chuàng)建了一個(gè)文本的詞向量數(shù)據(jù)庫(kù)。它將texts中的文本部分轉(zhuǎn)換為詞向量,然后將這些詞向量和相應(yīng)的元數(shù)據(jù)存儲(chǔ)在指定的持久化目錄中。
docsearch = Chroma.from_texts(
texts,
embeddings,
persist_directory=persist_directory,
metadatas=[{"source":f"{i}-pl"} for i in range(len(texts))]
)
#從LangChain庫(kù)中導(dǎo)入了RetrievalQAWithSourcesChain類,這是一個(gè)用于構(gòu)建檢索型問答系統(tǒng)的類。
from langchain.chains.qa_with_sources.retrieval import RetrievalQAWithSourcesChain
#創(chuàng)建了一個(gè)RetrievalQAWithSourcesChain的實(shí)例,即一個(gè)檢索型問答系統(tǒng)。該系統(tǒng)使用OpenAI的大型語言模型(設(shè)置了溫度參數(shù)為0)進(jìn)行問答,使用Retriever進(jìn)行信息檢索,并且設(shè)置了返回來源文檔的選項(xiàng)。
chain = RetrievalQAWithSourcesChain.from_chain_type(
llm = OpenAI(openai_api_key=myApiKey,temperature=0),
chain_type="stuff",
retriever = retriever,
return_source_documents = True
)
#一個(gè)函數(shù),用于處理問答系統(tǒng)返回的結(jié)果。該函數(shù)會(huì)打印出答案以及來源文檔。
def process_result(result):
print(result['answer'])
print("\n\n Sources: ", result['sources'])
print(result['sources'])
#提出問題
question = '鮑勃喜歡什么顏色'
#使用定義的問答系統(tǒng)來回答問題,并將結(jié)果存儲(chǔ)在Result變量中
result = chain({"question":question})
#調(diào)用定義的函數(shù)來處理并打印問答結(jié)果。
process_result(result)
最后的思考
在自然語言處理領(lǐng)域,為了讓模型能夠處理特定領(lǐng)域的問題,需要進(jìn)行Fine-tuning,并利用Embedding方法將文本信息轉(zhuǎn)換為數(shù)值向量。這樣的過程使得模型能夠具備特定領(lǐng)域的專業(yè)知識(shí),從而能夠回答相關(guān)問題。詞嵌入是一種常用的Embedding方法,通過將單詞轉(zhuǎn)換為多維向量來捕獲其語義信息。在Fine-tuning過程中,我們可以更新Embedding層來增強(qiáng)模型對(duì)特定領(lǐng)域詞匯的理解能力。整個(gè)過程需要借助工具和庫(kù)來實(shí)現(xiàn),如LangChain和ChromaDB。通過這樣的流程,我們可以建立一個(gè)專業(yè)領(lǐng)域的問答系統(tǒng),提供準(zhǔn)確的答案和相關(guān)的來源文檔。
作者介紹
崔皓,51CTO社區(qū)編輯,資深架構(gòu)師,擁有18年的軟件開發(fā)和架構(gòu)經(jīng)驗(yàn),10年分布式架構(gòu)經(jīng)驗(yàn)。