用好Python自然语言工具包-- 实例“基于本地知识库的自动问答”

 

首先鸣谢thomas-yanxin

本问中示例来自他在GitHub上的开源项目“基于本地知识库的自动问答”,链接如下:

thomas-yanxin/LangChain-ChatGLM-Webui: 基于LangChain和ChatGLM-6B的针对本地知识库的自动问答 (github.com) 


目录

1. 基础知识:

2. NLTK库的使用

3. 实例代码分析

设备的定义

函数定义:从网络上搜索相关信息

 ​​​​​​​函数定义:加载文件

函数定义:初始化一个向量存储器


1. 基础知识:

NLTK是一个领先的平台,用于构建处理自然语言数据的Python程序。它提供了易于使用的接口,可以访问50多个语料库和词汇资源,如WordNet,以及一套用于分类、分词、词干提取、标注、解析和语义推理的文本处理库、工业级自然语言处理库的封装和活跃的讨论论坛。

什么是tokenization?

Tokenize是指将一段文本分割成单独的词语或符号序列的过程。在自然语言处理中,文本预处理通常包括将原始文本数据转换为可供分析的结构化数据。分词是这个过程中的一个重要步骤,它可以将一段文本分割成有意义的单元,例如单词、标点符号、数字、缩略词等等。

分词技术可以基于不同的规则和算法实现,如空格、标点符号、停用词、正则表达式、最大匹配法等。分词的准确性和效率对于后续的自然语言处理任务(例如词性标注、命名实体识别、文本分类等)的效果有很大影响,因此它是自然语言处理中一个非常重要的步骤。

2. NLTK库的使用

在python中安装NLTK库

pip install NLTK

导入NLTK库

import nltk # 导入NLTK for tokenization

将语料数据导入数据列表中

nltk.data.path.append('./nltk_data')

NLTK是一个用于处理自然语言数据的Python库,其中包含了许多用于自然语言处理的数据集和语料库,这些数据通常存储在默认路径中。然而,有时用户可能需要在其他地方存储这些数据,或者需要加载自己的数据集和语料库,这时就需要将新的路径添加到nltk.data.path路径列表中。

然而,有时用户可能需要在其他地方存储这些数据,或者需要加载自己的数据集和语料库,这时就需要将新的路径添加到nltk.data.path路径列表中。

具体来说,这行代码使用了Python列表的append()方法,将"./nltk_data"添加到nltk.data.path路径列表中的末尾。这样,当使用NLTK库中的函数和方法时,程序将首先搜索默认路径,如果没有找到所需的数据,就会在路径列表中的其他位置继续搜索,直到找到所需的数据为止。这种方式可以方便地扩展NLTK库的数据集和语料库,使其适应用户的特定需求。

字典变量的定义

## 这段代码定义了一个名为 embedding_model_dict 的字典变量,其中包含了5个键值对,每个键值对表示一个预训练的中文词向量模型的名称和对应的地址。
embedding_model_dict = { # 词向量模型
    "ernie-tiny": "nghuyong/ernie-3.0-nano-zh", 
    "ernie-base": "nghuyong/ernie-3.0-base-zh",
    "ernie-medium": "nghuyong/ernie-3.0-medium-zh",
    "ernie-xbase": "nghuyong/ernie-3.0-xbase-zh",
    "text2vec-base": "GanymedeNil/text2vec-base-chinese"
}

## 这段代码定义了一个名为 llm_model_dict 的字典变量,其中包含了5个键值对,每个键值对表示一个预训练的中文语言模型的名称和对应的地址。
llm_model_dict = {
    "ChatGLM-6B": "THUDM/chatglm-6b",
    "ChatGLM-6B-int4": "THUDM/chatglm-6b-int4",
    "ChatGLM-6B-int8": "THUDM/chatglm-6b-int8",
    "ChatGLM-6b-int4-qe": "THUDM/chatglm-6b-int4-qe",
    "ChatGLM-6b-local": "/data/chatglm-6b"
}

这段代码定义了两个字典变量。第一个字典变量 embedding_model_dict 包含了五个键值对,每个键值对表示一个中文词向量模型的名称和对应的地址。具体来说,这些模型是以预训练方式生成的,可以用于将中文文本映射到连续的向量空间中。

第二个字典变量 llm_model_dict 包含了五个键值对,每个键值对表示一个预训练的中文语言模型的名称和对应的地址。这些模型可以用于生成中文文本,比如回答问题、生成对话等。其中一些模型的地址指向公共可用的模型,而另一些模型的地址是本地地址,表示这些模型存储在计算机本地的某个路径中。

3. 实例代码分析

我们要分析的就是来自GitHub上的开源项目“基于本地知识库的自动问答”项目中的app.py文件

上边已经分析了NLTK的库的导入、语料数据的导入、字典变量的定义,接下来我们继续研究。

设备的定义

DEVICE = "cuda" if torch.cuda.is_available(
) else "mps" if torch.backends.mps.is_available() else "cpu"

这段代码定义了一个变量 DEVICE,用于指定代码运行所使用的设备。如果当前系统支持CUDA设备(即具有NVIDIA GPU),则将 DEVICE 设置为 "cuda",表示使用GPU加速运算。如果当前系统支持MPS(Multi-Process Service),则将 DEVICE 设置为 "mps",表示使用MPS加速运算。否则,将 DEVICE 设置为 "cpu",表示使用CPU运算。

具体来说,这段代码通过调用 torch.cuda.is_available() 函数检查当前系统是否支持CUDA设备,如果支持则将 DEVICE 设置为 "cuda"。否则,通过调用 torch.backends.mps.is_available() 函数检查当前系统是否支持MPS,如果支持则将 DEVICE 设置为 "mps"。如果系统不支持CUDA或MPS,则将 DEVICE 设置为 "cpu"

这里解释一下torch:

torch是一个开源机器学习框架,基于Python语言,提供了丰富的工具和库,用于构建深度学习模型和进行科学计算。torch框架是由Facebook AI Research团队开发,其主要优势在于它支持张量计算和自动求导,同时提供了高效的GPU加速功能,因此广泛应用于深度学习领域。

torch的核心是张量(tensor)数据结构,它是一种多维数组,类似于numpy中的数组,但可以在GPU上运行加速。torch框架提供了大量的张量操作函数,如加减乘除、矩阵乘法、卷积操作等,同时还支持自动求导和高阶函数(例如梯度下降算法、优化器等)。此外,torch还包括了许多已经预训练好的深度学习模型,以及用于数据处理和可视化的工具。

函数定义:从网络上搜索相关信息

def search_web(query):

    SESSION.proxies = {
        "http": f"socks5h://localhost:7890",
        "https": f"socks5h://localhost:7890"
    }
    results = ddg(query)
    web_content = ''
    if results:
        for result in results:
            web_content += result['body']
    return web_content

这个函数将一个查询作为输入参数。

1)函数的第一行为SESSION变量指定了代理设置,该变量其他代码文件中定义

2)然后,它使用ddg()函数来获取给定查询的搜索结果。

3)代码的下一行初始化了一个名为web_content的空字符串变量。

4)接下来,它检查是否有任何来自上一步骤的结果。

5)如果有任何结果,那么它会遍历这些结果,提取每个结果的“body”部分并将其添加到web_content变量中。

6)最后,该函数返回web_content变量的最终值。

这个函数是一个用于获取指定查询在duckduckgo搜索引擎上的搜索结果,并将结果内容汇总到一个字符串中的函数,同时它使用SESSION变量指定了代理设置。

其中用到的ddg()函数为duckduckgo_search中的函数(路径:\Python39\Lib\site-packages\duckduckgo_search\ddg.py),是指代duckduckgo搜索引擎的搜索函数。

 ​​​​​​​函数定义:加载文件

def load_file(filepath):
    if filepath.lower().endswith(".pdf"):
        loader = UnstructuredFileLoader(filepath)
        textsplitter = ChineseTextSplitter(pdf=True)
        docs = loader.load_and_split(textsplitter)
    else:
        loader = UnstructuredFileLoader(filepath, mode="elements")
        textsplitter = ChineseTextSplitter(pdf=False)
        docs = loader.load_and_split(text_splitter=textsplitter)
    return docs

这段代码定义了一个名为 load_file 的函数,用于加载指定路径下的文本文件,并将其拆分为多个文档(即文档分块)。该函数接受一个参数 filepath,表示指定的文本文件路径。

首先,函数检查文件路径的后缀名是否为 .pdf,如果是,则使用 UnstructuredFileLoader 对象加载指定的PDF文件,并使用 ChineseTextSplitter 对象将PDF文档分割为多个文档。如果文件路径的后缀名不是 .pdf,则使用 UnstructuredFileLoader 对象以 elements 模式加载文件,并使用 ChineseTextSplitter 对象将文件拆分为多个文档。

最后,函数返回拆分后的文档列表。每个文档都表示一个字符串对象,其中包含原始文本文件中的一部分内容。文档的具体内容取决于拆分器(即 ChineseTextSplitter 对象)的实现方式。

这里解释下为何要进行“文档分块”?

进行文档分块是为了更好地处理大型文本数据。在自然语言处理领域,处理整个文本文件往往不是最佳选择,因为大型文本文件可能包含数千甚至数百万的单词或字符,而处理这样大量的文本数据可能会耗费大量的计算资源,而且对于一些任务(如语言模型训练)可能会导致内存不足或内存泄漏的问题。

为了避免这些问题,可以将大型文本文件分割成多个较小的文本块,即文档分块。每个文档分块通常包含文本文件的一部分内容,例如一段或几段文本。通过将大型文本文件分割成多个文档分块,可以将文本处理任务分解成多个较小的子任务,从而降低整个处理过程的计算复杂度,并且可以更加高效地使用内存资源。

文档分块还有另一个优点,即可以更好地控制训练数据的大小。在一些机器学习任务中,数据的大小往往是一个关键问题,因为过大的训练数据可能会导致过拟合或训练时间过长。通过对文本文件进行分块,可以将训练数据的大小控制在合理的范围内,从而更好地控制训练过程的效率和质量。

函数定义:初始化一个向量存储器

def init_knowledge_vector_store(embedding_model, filepath):
    embeddings = HuggingFaceEmbeddings(
        model_name=embedding_model_dict[embedding_model], )
    embeddings.client = sentence_transformers.SentenceTransformer(
        embeddings.model_name, device=DEVICE)

    docs = load_file(filepath)

    vector_store = FAISS.from_documents(docs, embeddings)
    return vector_store

这段代码定义了一个名为 init_knowledge_vector_store 的函数,用于初始化一个向量存储器,用于存储知识库中文档的向量表示。该函数接受两个参数:embedding_model 表示要使用的预训练词向量模型的名称(如 "ernie-tiny"),filepath 表示知识库文件的路径。

首先,该函数创建一个 HuggingFaceEmbeddings 对象,使用预训练的词向量模型来生成词向量。该对象从 embedding_model_dict 字典中获取 embedding_model 对应的预训练词向量模型地址,并使用它初始化。然后,它创建一个 SentenceTransformer 对象,使用 HuggingFaceEmbeddings 对象生成的词向量模型,并将该对象的 device 属性设置为全局变量 DEVICE,以指定使用CPU或GPU设备进行计算。SentenceTransformer 对象用于将文档转换为向量表示,用于在向量存储器中进行检索。

接下来,函数使用 load_file() 函数加载指定路径下的文本文件,并将其拆分为多个文档。每个文档都表示原始文本文件中的一部分内容,例如一段或几段文本。

最后,函数使用 FAISS 库将文档向量存储在一个向量存储器中,并将该向量存储器作为函数的返回值。 FAISS 是一种高效的向量检索库,它支持基于余弦相似度和欧几里得距离等多种相似度度量方式。 from_documents 方法是 FAISS 库提供的一种函数,它将文档向量存储在一个向量存储器中,以便于进行相似度匹配和向量检索。

函数定义:获取基于知识的答案

def get_knowledge_based_answer(query,
                               large_language_model,
                               vector_store,
                               VECTOR_SEARCH_TOP_K,
                               web_content,
                               history_len,
                               temperature,
                               top_p,
                               chat_history=[]):
    if web_content:
        prompt_template = f"""基于以下已知信息,简洁和专业的来回答用户的问题。
                            如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息",不允许在答案中添加编造成分,答案请使用中文。
                            已知网络检索内容:{web_content}""" + """
                            已知内容:
                            {context}
                            问题:
                            {question}"""
    else:
        prompt_template = """基于以下已知信息,请简洁并专业地回答用户的问题。
            如果无法从中得到答案,请说 "根据已知信息无法回答该问题" 或 "没有提供足够的相关信息"。不允许在答案中添加编造成分。另外,答案请使用中文。

            已知内容:
            {context}

            问题:
            {question}"""
    prompt = PromptTemplate(template=prompt_template,
                            input_variables=["context", "question"])
    chatLLM = ChatGLM()
    chatLLM.load_model(model_name_or_path=llm_model_dict[large_language_model])
    chatLLM.history = chat_history[-history_len:] if history_len > 0 else []

    chatLLM.temperature = temperature
    chatLLM.top_p = top_p

    knowledge_chain = RetrievalQA.from_llm(
        llm=chatLLM,
        retriever=vector_store.as_retriever(
            search_kwargs={"k": VECTOR_SEARCH_TOP_K}),
        prompt=prompt)
    knowledge_chain.combine_documents_chain.document_prompt = PromptTemplate(
        input_variables=["page_content"], template="{page_content}")

    knowledge_chain.return_source_documents = True

    result = knowledge_chain({"query": query})
    return result

这段代码定义了一个名为 get_knowledge_based_answer 的函数,用于基于预定义的知识库,回答用户提出的问题。该函数接受多个参数,包括:

  • query:表示用户提出的问题;
  • large_language_model:表示要使用的大型语言模型的名称;
  • vector_store:表示存储文档向量的向量存储器;
  • VECTOR_SEARCH_TOP_K:表示要返回的文档数量;
  • web_content:表示从网络检索中获得的已知信息;
  • history_len:表示要考虑的历史对话轮数;
  • temperature:表示用于控制生成文本多样性的温度参数;
  • top_p:表示用于控制生成文本长度的 top-p 参数;
  • chat_history:表示当前对话的历史记录。

该函数首先根据 web_content 参数确定不同的提示文本模板,并将其传递给 PromptTemplate 对象,用于生成提示文本。然后,该函数使用 ChatGLM 类创建一个大型语言模型对象,并使用 load_model 方法从指定的模型名称或路径中加载模型。接下来,函数设置该模型的 temperaturetop_p 参数。

然后,函数使用 RetrievalQA.from_llm 方法创建一个 RetrievalQA 对象,该对象将大型语言模型和向量存储器作为检索器,并将提示文本作为提示。该对象使用检索器在向量存储器中查找与查询最相似的文档,并使用大型语言模型生成答案。查询结果包含了最有可能的答案,以及相应的文档信息和相似度分数。

最后,函数返回 result 变量,其中包含与查询最相关的答案、相关文档信息和相似度分数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/14685.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第四章——数学知识2

欧拉函数 欧拉函数表示的是1-n中与n互质数的个数。 如1-6中:1,5都和6互质,因此互质数为2 欧拉函数分解质因数后表示为: 互质数个数可表示为 int main() {int n;cin >> n;while(n--){int a;cin >> a;//分解质因数int r…

Spring Bean的作用域及生命周期

目录 前言: Bean的作用域(Scope) 单例模式 原型模式(多例作用域) 请求作用域(request) 会话作用域 全局作用域 网络长连接 Spring执行流程 Bean的生命周期 测试 小结: 前…

面试redis之两大金刚,你懂吗

前言 Redis持久化,一个老掉牙的问题,但是面试官就是喜欢问。这也是我们学Redis必会的一个知识点。Redis作为内存数据库,它工作时,数据都保存在内存里,这也是它为什么很快的一个原因。但存到内存里肯定是有丢数据的风险…

易基因:ChIP-seq等揭示热休克转录因子A1b调控植物高温胁迫响应的分子机制|应激反应

在拟南芥中,热休克转录因子A1b(HEAT SHOCK TRANSCRIPTION FACTORA1b,HSFA1b)通过影响种子产量来调控对环境胁迫的抗性。HSFA1b是生殖适应性的决定性因素,这种调控机制怎么形成的呢? 2018年,英国…

【微电网_储能】基于启发式状态机策略和线性程序策略优化方法的微电网中的储能研究【给定系统约束和定价的情况下】(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

前端项目代码规范

一、变量与函数的命名(变量名和函数名是最好的注释) 通常情况下函数小陀峰、类名大陀峰、变量短横线/小陀峰、const全大写单词要表达出正确的语义,如:array类型或其它集合类型用英语复数格式、其它类型不要用复数格式区分函数为功…

【服务器】Linux搭建我的世界服务器 + 公网远程联机教程

Yan-英杰的主页 悟已往之不谏 知来者之可追 C程序员,2024届电子信息研究生 目录 前言 1. 安装JAVA 2. MCSManager安装 3.局域网访问MCSM 4.创建我的世界服务器 5.局域网联机测试 6.安装cpolar内网穿透 7. 配置公网访问地址 8.远程联机测试 9. 配置固定…

C++ 线程

linux使用线程 在linux使用线程可能出现,在编译时不会报错,但执行出错的问题。 undefined reference to pthread_create这是由于ubuntu平台下调用pthread_create()函数,pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 …

MII、 RMII、 GMII、 RGMII 接口介绍

1、RGMII 接口概要 以太网的通信离不开物理层 PHY 芯片的支持,以太网 MAC 和 PHY 之间有一个接口,常用的接口有MII、 RMII、 GMII、 RGMII 等。 MII(Medium Independent Interface, 媒体独立接口): MII 支持…

RabbitMQ之介绍以及安装

1.1 MQ的相关概念 1.1.1 什么是MQ ​ MQ,从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ…

移远通信笔试题

限时60分钟 1.下列关于栈叙述正确的是 A A) 栈顶元素最先能被删除 B)栈顶元素最后才能被删除 C)栈底元素永远不能被删除 D)以上三种都不对 在栈中,最后被压入的元素总是在栈顶上方,而栈顶元素总是最先被弹出的元…

理解龙格库塔法基本C程序

先学习龙格-库塔法; 龙格-库塔,Runge-Kutta,该方法用于数值求解微分方程; 其中包括著名的欧拉法; 经典四阶法 该方法主要是在已知方程导数和初值信息,利用计算机仿真时应用,省去求解微分方…

人工智能之配置环境教程一:安装VsCode和Anaconda

人工智能之配置环境教程一:安装VsCode和Anaconda 作者介绍一. 安装VScode编辑器二. 安装Anaconda 作者介绍 孟莉苹,女,西安工程大学电子信息学院,2021级硕士研究生,张宏伟人工智能课题组。 研究方向&#…

shell脚本----条件判断语句

文章目录 一、条件测试1.1 文件测试和整数测试文件测试整数值比较 1.2字符串测试和逻辑测试字符串测试:逻辑测试 二、if语句三、case语句 一、条件测试 1.1 文件测试和整数测试 文件测试 test命令 测试表达是是否成立,若成立则返回0,否则返…

元宇宙的应用领域

应用领域一:游戏 1.游戏是最先成长起来的元宇宙场景。虚拟社交身份、开放性、经济系统、沉浸感、世界可持续性是元宇宙游戏需关注的五大特征。 2.元宇宙游戏依然是游戏,现阶段参与元宇宙游戏的主要是游戏爱好者。新的概念依旧需要好的游戏产品支撑。团…

malloc的一些知识

这是一个叫malloc的家伙,一直勤勤恳恳帮你为所欲为的玩转系统内存。可是长路漫漫,唯malloc作伴,我却不懂它。走近malloc,多了解一下总没错。 可能对我们来讲,malloc就是void* malloc (size_t len),调用就是…

4月24日作业

作业1 #include <iostream> using namespace std; template <typename T> class Node { private: T* p; //指针指向栈的首地址 int maxsize; //栈最大容量 int top-1; //栈顶 public: Node(){} //无参构造 Node(int max):maxsize(max)//有参构造 填最大容…

JavaScript 知识总结下篇(更新版)

91.实现一个 promise 参考链接&#xff1a;实现一个完美符合Promise/A规范的Promise Issue #4 forthealllight/blog GitHub function myPromise(constructor) {let self this;self.status "pending" // 定义状态改变前的初始状态self.value undefined;// 定义状…

基于html+css的图片展示32

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

STM32WB55_NUCLEO开发(11)----发送数据到手机

概述 本篇文章将详细介绍如何在上节配置的基础上&#xff0c;实现通过点击STM32WB开发板上的按键发送数据到手机上。 硬件准备 首先需要准备一个开发板&#xff0c;这里我准备的是NUCLEO-WB55RG 的开发板&#xff1a; 蓝牙配置 选择“mySVC”选项卡。添加第二个特征&…