LLM代码实现-Qwen(挂载知识库)

为什么要挂载知识库?

LLM 在回答用户的问题时可能会产生幻觉,或者由于训练数据中不包含用户想要的内容而无法回答,通常情况下我们可以选择微调模型或者外挂知识库来缓解这类问题。微调模型的对数据和算力都有一定的要求,而知识库的门槛会更低一些,所以通常情况下会选择外挂知识库高效地来解决这类问题。

挂载知识库其实相当于引入外部知识,为了扩展语言模型以减少歧义,从大型文本数据库中检索相关文档。通常将输入序列分割成块并检索与用户输入的 query 相似的文档,然后将所选文档放在输入文本之前作为前置知识以改进模型的预测。使得模型可以更容易、更准确地访问专业知识。

挂载知识库的流程

文档 -> 文档向量化 -> 文档检索 -> 对话交互

我们可以借助 langchain 来实现这个流程:

1. 文档

我们使用中医药书籍来作为知识库,下载链接(700 本中医药古籍文本),下载完成后运行以下代码以合并数据。

python 复制代码
import os


dir_path = "./TCM-Ancient-Books-master"
for index, filename in enumerate(os.listdir(dir_path)):
    if not filename.endswith(".txt"):
        continue

    file_path = os.path.join(dir_path, filename)
    with open(file_path, "r", encoding='gb18030', errors='ignore') as f:
        text = f.read().replace("\n", "")

    mode = "a" if index else "w"
    with open("./knowledge.txt", mode, encoding="utf-8") as f:
        f.write(text + "\n")

2. 文档向量化

在文档检索的过程中如果利用字符串直接匹配文本相似度效率是很低的,尤其是知识库体量非常大的时候,因此一般会先对文档进行切割和向量化以提升检索的速度。将每个文档转换为数值向量,以便计算文档之间的相似度或进行聚类分析。

python 复制代码
from langchain.document_loaders import UnstructuredFileLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS

def load_knowledge():
    filepath = "./knowledge.txt"
    loader = UnstructuredFileLoader(filepath)
    docs = loader.load()

    # chunk-size是文本最大的字符数。chunk-overlap是前后两个chunk的重叠部分最大字数
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
    docs = text_splitter.split_documents(docs)

    # 这里需要下载一个中文的文本向量化模型
    embeddings = HuggingFaceEmbeddings(model_name="./text2vec-large-chinese",
                                       model_kwargs={'device': 'cuda'})
    
    # 指定向量化文档加载/保存路径
    save_path = "./med_faiss_store.faiss"
    if not os.path.exists(save_path):
        vector_store = FAISS.from_documents(docs, embeddings)
        vector_store.save_local(save_path)

    else:
        vector_store = FAISS.load_local(save_path, embeddings=embeddings)

    return vector_store

3. 文档检索

利用以下代码根据用户的输入按照相关性对向量库中的文本文本进行排序并取出排名靠前的 5 条知识,并将知识库中的知识库和用户的输入拼接在一起作为新的 prompt。

python 复制代码
docs = vector_store.similarity_search(patient_history)     # 计算相似度,并把相似度高的chunk放在前面
knowledge = [doc.page_content for doc in docs[:5]]  # 提取chunk的文本内容
prompt = f"知识库:{knowledge}\n问题如下:\n{patient_input}"

4. 对话交互

最后是把 prompt 送到模型中得到输出,与模型的交互在上一篇文章中有详细的介绍,这里就不赘述了,直接给出完整的代码(运行时要注意模型路径、知识库路径是否正确)。

ini 复制代码
from langchain.document_loaders import UnstructuredFileLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
from langchain.vectorstores import FAISS
from transformers import AutoModelForCausalLM, AutoTokenizer
from transformers.generation import GenerationConfig
import torch
import os


def load_model(model_path):
    tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_path, 
                                                 device_map="auto", 
                                                 trust_remote_code=True
                                                 ).eval()

    return tokenizer, model


def load_knowledge():
    filepath = "./knowledge.txt"
    loader = UnstructuredFileLoader(filepath)
    docs = loader.load()

    # chunk-size是文本最大的字符数。chunk-overlap是前后两个chunk的重叠部分最大字数
    text_splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
    docs = text_splitter.split_documents(docs)

    embeddings = HuggingFaceEmbeddings(model_name="./text2vec-large-chinese",
                                       model_kwargs={'device': 'cuda'}.

    # 指定向量化文档加载/保存路径
    save_path = "./med_faiss_store.faiss"
    if not os.path.exists(save_path):
        vector_store = FAISS.from_documents(docs, embeddings)
        vector_store.save_local(save_path)

    else:
        vector_store = FAISS.load_local(save_path, embeddings=embeddings)

    return vector_store


def clear_screen():
    os.system('clear')
    return [], ""


def chat_qwen(model, tokenizer, SYSTEM_PROMPT, with_knowledge=False):
    vector_store = load_knowledge()
    history, patient_history = clear_screen()

    while True:
        patient_input = input("user:")
        patient_history += patient_input
        if patient_input.lower() == "clc":
            history, patient_history = clear_screen()
            continue

        if with_knowledge:
            docs = vector_store.similarity_search(patient_history)     # 计算相似度,并把相似度高的chunk放在前面
            knowledge = [doc.page_content for doc in docs[:5]]  # 提取chunk的文本内容
            prompt = f"知识库:{knowledge}\n问题如下:\n{inputs}"

        else:
            prompt = inputs

        response, _ = model.chat(tokenizer, prompt, history=history, system=SYSTEM_PROMPT)
        history.append((patient_input, response))    # history 不包括系统提示和知识库信息
        print("assistant:", response, end="\n\n")


if __name__ == '__main__':
    model_path = "/root/autodl-tmp/LLM_MODEL/Qwen-1_8B-Chat"

    SYSTEM_PROMPT = ""

    tokenizer, model = load_model(model_path)
    chat_qwen(model, tokenizer, SYSTEM_PROMPT, with_knowledge=True)
相关推荐
fanxbl957几秒前
深入探索离散 Hopfield 神经网络
人工智能·神经网络
_OLi_5 分钟前
力扣 LeetCode 459. 重复的子字符串(Day4:字符串)
算法·leetcode·职场和发展·kmp
Romanticroom12 分钟前
计算机23级数据结构上机实验(第3-4周)
数据结构·算法
白藏y13 分钟前
数据结构——归并排序
数据结构·算法·排序算法
TaoYuan__13 分钟前
深度学习概览
人工智能·深度学习
云起无垠18 分钟前
第74期 | GPTSecurity周报
人工智能·安全·网络安全
ahadee24 分钟前
蓝桥杯每日真题 - 第12天
c++·vscode·算法·蓝桥杯
好看资源平台24 分钟前
爬虫开发工具与环境搭建——环境配置
爬虫·python
workflower28 分钟前
AI+自动驾驶
人工智能·机器学习·自动驾驶
大G哥33 分钟前
python 数据类型----可变数据类型
linux·服务器·开发语言·前端·python