基于知识库的QA
基于知识库的QA,即用户给定相关文档,将文档处理成为知识库,在知识库的基础上进行领域问答。这种技术可以对于用户的领域问题做出更好地领域回答。同时对于模型给出的回答,可以做到回溯这些答案在知识库中的出处。是得模型的回答有理有据。
原理说明
基于知识库的QA分为以下几个步骤:
- 对输入的文档进行处理
- 加载文件:对于不同格式的文件(例如.md、.txt、.pdf、.doc等)的加载。
- 文本分割:该步骤中对于文本的分割大小的设定是个可以考量的问题。个人人为文本分割的适当小,有利于提高答案出处的精确性。
- 构建向量库:为了后续对问题(query)进行相似度匹配.
- 问题向量化(Embedding)
- 向量相似度匹配
- 构造prompt并对LLMs进行问答
代码实现
代码参考LangChain+ChatChat,并在原有代码上改动。
整体代码:
from transformers import AutoModel, AutoTokenizer
from langchain.document_loaders import UnstructuredFileLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.vectorstores import FAISS
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
import torch
EMBEDDING_DEVICE = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("THUDM/chatglm-6b", trust_remote_code=True).half().cuda()
chatglm = model.eval()
# test知识库文本
filepath = "test.txt"
# 加载文件
loader = UnstructuredFileLoader(filepath)
docs = loader.load()
# 文本分割
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=200)
# docs = text_splitter.split_text(docs[0].page_content)
docs = text_splitter.split_documents(docs)
# 构建向量库
embeddings = HuggingFaceEmbeddings(model_name="m3e-base",
model_kwargs={'device':EMBEDDING_DEVICE})
vector_store = FAISS.from_documents(docs, embeddings)
# 根据提问匹配上下文
query = "用户的问题"
docs = vector_store.similarity_search(query)
context = [doc.page_content for doc in docs]
# 构造 Prompt
prompt = f"已知信息:\n{context}\n根据已知信息回答问题:\n{query}"
# llm回答问题
response, history = chatglm.chat(tokenizer, prompt, history=[])
# 输出LLMs的回答
print(response)
加载文件:
# test知识库文本
filepath = "test.txt"
# 加载文件
loader = UnstructuredFileLoader(filepath)
docs = loader.load()
文本分割以及文本向量化:
# 文本分割
text_splitter = CharacterTextSplitter(chunk_size=500, chunk_overlap=200)
docs = text_splitter.split_documents(docs)
# 构建向量库
embeddings = HuggingFaceEmbeddings(model_name="sentence-transformers/all-mpnet-base-v2",
model_kwargs={'device':EMBEDDING_DEVICE})
vector_store = FAISS.from_documents(docs, embeddings)
第2行设置文本分割器,其中设置两个参数chunk_size
和chunk_overlap
。其中chunk_size=500
设置分割大小为500个字符,chunk_overlap=200
即设置文本块之间的最大重叠量,保留一些重叠可以保持文本块之间的连续性。
注意:第3行,在LangChain+ChatChat 项目中写错了,这里做了修改。
embedding库使用的HuggingFaceEmbeddings()
,将该嵌入模型参数下载本地实现。
通过读取源码可以知道获得模型回答的出处这一功能的实现原理:在文本分割那一步中,分割好的文本块以List[Document]
结构存储,Document类
中有两个成员变量如下所示:
class Document(Object):
page_content: str
"""String 类型的文本"""
metadata: dict = Field(default_factory=dict)
"""存放每个文本块(page_content)对应的文档出处
例如
metadata={'source': 'test.txt'}
"""
出处功能实现路径明了:将用户输入向量化,匹配向量库中相似的向量,匹配到的向量都会找到存放在Document类
中的成员变量metadata
,即完成了出处的功能。
提问
# 根据提问匹配上下文
query = "哪些航母参与了“海轨行动”?"
docs = vector_store.similarity_search(query, k=4)
# context存放了前k个相似度最高的文本,类型为List[Document]
context = [doc.page_content for doc in docs]
# 构造 Prompt
prompt = f"已知信息:\n{context}\n根据已知信息回答问题:\n{query}"
# llm回答问题
response, history = chatglm.chat(tokenizer, prompt, history=[])
# 输出LLMs的回答
print(response)
调用LLMs,结合用户输入和搜索到的相似向量进行问答。
similarity_search()
: 相似度搜索
def similarity_search(
self,
query: str,
k: int = 4,
filter: Optional[Dict[str, Any]] = None,
fetch_k: int = 20,
**kwargs: Any,
) -> List[Document]:
"""Return docs most similar to query.
Args:
query: Text to look up documents similar to.
k: 匹配相似的向量,取前k个,默认为4
filter: (Optional[Dict[str, str]]): Filter by metadata. Defaults to None.
fetch_k: (Optional[int]) Number of Documents to fetch before filtering.
Defaults to 20.
Returns:
Document类的List
"""