在本地跑一個大語言模型(2) - 給模型提供外部知識庫

有何m不可發表於2024-04-02

轉載自 https://babyno.top/posts/2024/03/run-a-large-language-model-locally-2/

上一篇文章裡,我們展示瞭如何透過Ollama這款工具,在本地執行大型語言模型。本篇文章將著重介紹下如何讓模型從外部知識庫中檢索定製資料,來提升大型語言模型的準確性,讓它看起來更“智慧”。

本篇文章將涉及到LangChainRAG兩個概念,在本文中不做詳細解釋。

準備模型

訪問Ollama的模型頁面,搜尋qwen,我們這次將使用對中文語義瞭解的更好的“通義千問”模型進行實驗。

執行模型

1
ollama run qwen:7b

第一輪測試

編寫程式碼如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from langchain_community.chat_models import ChatOllama
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate


model_local = ChatOllama(model="qwen:7b")
template = "{topic}"
prompt = ChatPromptTemplate.from_template(template)
chain = model_local | StrOutputParser()
print(chain.invoke("身長七尺,細眼長髯的是誰?"))

模型返回的答案:

這句話描述的是中國古代文學作品《三國演義》中的角色劉備。劉備被描繪為一位身高七尺(約1.78米),眼睛細小但有神,長著長鬚的蜀漢開國皇帝。

可以看到,我問了模型一個問題:“身長七尺,細眼長髯的是誰?“這是一個開放型的問題,沒有指定上下文,答案並不確定。模型給到的答案是“劉備”,作為中國人訓練出來的模型,四大名著應該是沒有少看的。因此憑藉問題的描述,模型能聯想到三國裡的人物,並不讓人感覺意外。但答案還不對。

引入RAG

檢索增強生成(Retrieval Augmented Generation),簡稱 RAG。RAG的工作方式是在共享的語義空間中,從外部知識庫中檢索事實,將這些事實用作決策過程的一部分,以此來提升大型語言模型的準確性。因此第二輪測試我們將讓模型在回答問題之前,閱讀一篇事先準備好的《三國演義》章節,讓其在這篇章節裡尋找我們需要的答案。

RAG前的工作流程如下:向模型提問->模型從已訓練資料中查詢資料->組織語言->生成答案。

RAG後的工作流程如下:讀取文件->分詞->嵌入->將嵌入資料存入向量資料庫->向模型提問->模型從向量資料庫中查詢資料->組織語言->生成答案。

嵌入

在人工智慧中,嵌入(Embedding)是將資料向量化的一個過程,可以理解為將人類語言轉換為大語言模型所需要的計算機語言的一個過程。在我們第二輪測試開始前,首先下載一個嵌入模型:nomic-embed-text 。它可以使我們的Ollama具備將文件向量化的能力。

1
ollama run nomic-embed-text

使用LangChain

接下來需要一個Document loaders文件

1
2
3
4
from langchain_community.document_loaders import TextLoader  
  
loader = TextLoader("./index.md")  
loader.load()

接下來需要一個分詞器Text Splitter文件

1
2
3
4
5
6
from langchain_text_splitters import CharacterTextSplitter

text_splitter = CharacterTextSplitter.from_tiktoken_encoder(
    chunk_size=100, chunk_overlap=0
)
texts = text_splitter.split_text(state_of_the_union)

接下來需要一個向量資料庫來儲存使用nomic-embed-text模型項量化的資料。既然是測試,我們就使用記憶體型的DocArray InMemorySearch文件

1
2
embeddings = OllamaEmbeddings(model='nomic-embed-text')
vectorstore = DocArrayInMemorySearch.from_documents(doc_splits, embeddings)

第二輪測試

首先下載測試文件,我們將會把此文件作為外部資料庫供模型檢索。注意該文件中提到的:

忽見一彪軍馬,盡打紅旗,當頭來到,截住去路。為首閃出一將,身長七尺,細眼長髯,官拜騎都尉,沛國譙郡人也,姓曹,名操,字孟德。

編寫程式碼如下:

 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
from langchain_community.document_loaders import TextLoader
from langchain_community import embeddings
from langchain_community.chat_models import ChatOllama
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import DocArrayInMemorySearch
from langchain_community.embeddings import OllamaEmbeddings

model_local = ChatOllama(model="qwen:7b")

# 1. 讀取檔案並分詞
documents = TextLoader("../../data/三國演義.txt").load()
text_splitter = CharacterTextSplitter.from_tiktoken_encoder(chunk_size=7500, chunk_overlap=100)
doc_splits = text_splitter.split_documents(documents)

# 2. 嵌入並儲存
embeddings = OllamaEmbeddings(model='nomic-embed-text')
vectorstore = DocArrayInMemorySearch.from_documents(doc_splits, embeddings)
retriever = vectorstore.as_retriever()

# 3. 向模型提問
template = """Answer the question based only on the following context:
{context}
Question: {question}
"""
prompt = ChatPromptTemplate.from_template(template)
chain = (
    {"context": retriever, "question": RunnablePassthrough()}
    | prompt
    | model_local
    | StrOutputParser()
)
print(chain.invoke("身長七尺,細眼長髯的是誰?"))

模型返回的答案:

身長七尺,細眼長髯的人物是曹操,字孟德,沛國譙郡人。在《三國演義》中,他是主要人物之一。

可見,使用RAG後,模型給到了正確答案。

總結

本篇文章我們使用LangChainRAG對大語言模型進行了一些微調,使之生成答案前可以在我們給到的文件內進行檢索,以生成更準確的答案。

RAG是檢索增強生成(Retrieval Augmented Generation),主要目的是讓使用者可以給模型制定一些額外的資料。這一點非常有用,我們可以給模型提供各種各樣的知識庫,讓模型扮演各種各樣的角色。

LangChain是開發大語言模型應用的一個框架,內建了很多有用的方法,比如:文字讀取、分詞、嵌入等。利用它內建的這些功能,我們可以輕鬆構建出一個RAG的應用。

這次的文章就到這裡了,下回我們將繼續介紹更多本地LLM的實用場景。

相關文章