自去年年底OpenAI发布ChatGPT以来,大型语言模型在人工智能领域掀起了一股热潮。随后,各家公司纷纷推出自己的大型语言模型,如百度的文心一言、讯飞的星火大模型等。在这个过程中,文本转图片和文本转视频等相关领域也备受关注。然而,很显然,这只是一时的潮流。作者对这些领域进行了调研,根据调研结果来看,这两个领域距离通用的技术能力还有很大距离,目前仅处于试水阶段。
但是chatGPT的文本理解能力确实是一个里程碑式的事件,在文本理解上,chatGPT已经可以完全媲美人类甚至超过人类。在这个过程中,业界开始逐渐探讨具体商业使用场景,并在业务上进行落地实现。
今天就给大家带来langchain+chatGPT的能力,构建一个自有知识库的问答机器人,也就是人们所说的智能客服。
LangChain是一个基于大语言模型(LLM)开发应用程序的框架。他是介于开发者和LLM之间的一个工具,将大模型的一些能力进行封装,方便开发者进行使用。
Langchain的官网文档地址:Introduction | ️ Langchain
Langchain的中文社区地址:LangChain 中文入门教程 - General - LangChain中文社区
One-Hot 编码:
在机器学习领域,对于一些离散特征(比如性别:男,女)需要进行编码才能进行处理,常用的编码手段是One-Hot 。One-Hot 编码是一种常用的分类数据编码方式,用于将离散的数据转换为稀疏向量。在 One-Hot 编码中,每个特征值对应一个唯一的索引位置,其中该特征值的值为1,其他位置为0。例如,对于一个有三个类别的特征,使用 One-Hot 编码后,类别1将表示为[1, 0, 0],类别2将表示为[0, 1, 0],类别3将表示为[0, 0, 1]。这种编码方式保留了类别之间的相对距离,但也带来了高维度的稀疏向量表示。
embedding编码:
Embedding 编码是一种将离散特征映射到连续向量空间的编码方式。它通过将每个离散特征值映射到一个低维度的实数向量(嵌入向量),使得特征之间的关系能够以连续的方式表示。这种编码方式可以通过训练来学习嵌入向量,也可以使用预训练的嵌入向量模型。嵌入向量具有一定的语义含义,因此可以捕捉到特征之间的相关性和语义信息。相比于 One-Hot 编码,Embedding 编码可以更好地处理高维度数据,并且可以有效地表示稀疏特征。
通俗的理解,embedding编码方式保留了不同特征值之间的相关性和语义信息,可以通过计算embedding向量之间的余弦相似度来获取两个特征值的相似性,这种编码方式在信息检索、文本分类、问题问答等领域有广泛应用。
自有知识库的问答机器人的实现原理就是基于embedding文本嵌入式向量来实现的。具体的实现逻辑是:
1、将自有知识库(一个文本文件)进行切割,然后对切割之后的每一个文本进行Embedding向量化,形成一个文本向量化数据库。
2、当你提问时,将你的问题也进行Embedding向量化,然后从文本向量化数据库中检索和你的问题相似度最高的文本。
3、将这些检索出来的和问题相似度很高的文本,统一发给chatGPT,让他在这些文本范围内,重新组织语言进行回答。
1、需要去注册一个openAI的账户,然后获取api-key。 如何注册,可以从网上搜索,作者后续会把自己注册的过程写出来(但是是需要fanqiang花钱滴!)
2、在本地安装openai和langchain的库
pip install openai
pip install langchain
3、执行代码:
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.vectorstores import Chroma
from langchain.chains import RetrievalQAWithSourcesChain
from langchain import OpenAI
from langchain.document_loaders import PyPDFLoader
import os
#填写你的openai_api_key
os.environ['OPENAI_API_KEY'] = "xxxxxx"
#加载你的自有知识库
#TextLoader加载纯文本文件; PyPDFLoader可以加载pdf文件
loader = TextLoader('./jiling', encoding="utf-8")
# loader = PyPDFLoader('./jiling.pdf')
documents = loader.load()
#按照逗号对文本进行切割
text_splitter = CharacterTextSplitter(chunk_size=50, chunk_overlap=5, separators = ',')
texts = text_splitter.split_documents(documents)
#打印切割之后的文本进行查看。
for data in texts:
print(data)
#使用OpenAI的Embedding模型进行文本向量化
embeddings = OpenAIEmbeddings()
#将文本向量化之后,存储到向量数据库Chroma中,下次使用直接读取向量数据库,减少对openai的调用。
docSearch = Chroma.from_documents(texts, embeddings, metadatas=[{"source": str(i)} for i in range(len(texts))],persist_directory="D:/vector_store", overrides=True)
docSearch.persist()
#从向量数据库直接读取向量化文本
# docSearch = Chroma(persist_directory="D:/vector_store", embedding_function=embeddings)
#还记得上面的第三步吗?获取到相似文本之后,还需要调用chatGPT进行文本的整合和回答。
#所以这里还需要OpenAI的chatGPT模型
llm = OpenAI(temperature=0)
#构建检索方式
retriever = docSearch.as_retriever()
retriever.search_kwargs['distance_metric'] = 'cos' #相似性计算使用余弦相似性
retriever.search_kwargs['fetch_k'] = 100
retriever.search_kwargs['maximal_marginal_relevance'] = True
retriever.search_kwargs['k'] = 10 #检索最相似的前10条数据
#通过langchain构建一个问答链
chain = RetrievalQAWithSourcesChain.from_chain_type(llm, chain_type="map_reduce", retriever=retriever, return_source_documents=True)
#问题集合
questions = [
'你知道facebook吗?,根据已知信息回答,不要进行其他信息的扩展。',
'你知道腾讯公司吗?,腾讯公司的总部在哪儿?是谁创建的?主要做什么业务?根据已知信息回答,不要进行其他信息的扩展。',
'疯吉是做什么的?根据已知信息回答,不要进行其他信息的扩展。',
'你说错了,疯吉不是做鼠标的,极领是做鼠标的。'
]
#执行问答
for question in questions:
result = chain({"question": question}, return_only_outputs=True)
print(result)
chat_history.append((question, result['answer']))
print(f">>Question: {question}")
print(f">>>>Answer: {result['answer']} \n")
4、执行结果:
>>Question: 你知道facebook吗?,根据已知信息用中文回答,不要进行其他信息的扩展。
>>>>Answer: 是的,我知道Facebook。
>>Question: 你知道腾讯公司吗?,腾讯公司的总部在哪儿?是谁创建的?主要做什么业务?根据已知信息用中文回答,不要进行其他信息的扩展。
>>>>Answer: 是马化腾、张志东、许晨晔、陈一丹等人于1998年11月创立的腾讯公司,总部位于中国深圳,主要从事互联网服务业务。
>>Question: 疯吉是做什么的?根据已知信息用中文回答,不要进行其他信息的扩展。
>>>>Answer: 无法回答,不了解疯吉的信息。
>>Question: 你说错了,疯吉不是做鼠标的,极领是做鼠标的。
>>>>Answer: 疯吉不是做鼠标的,极领是做鼠标的。
说明:
从执行结果上来看,效果并不是太好,最大的一个问题是:
对于自有知识库中没有的内容,openai自己进行了知识的扩展,比如:facebook和腾讯的相关内容。
5、那么如何解决上述openai知识扩展的问题呢?
在这里只是给出一个建议进行参考,具体的更好的优化方案还不太清楚。
使用docs = docSearch.similarity_search_with_score('知道腾讯公司吗?,腾讯公司的总部在哪儿?是谁创建的?主要做什么业务?根据已知信息用中文回答,不要进行其他信息的扩展', k = 10)代码,来获取和问题最相似的文档及其分数,当分数达到一定阈值的情况下,才去做回答,否则回答不知道。
docs = docSearch.similarity_search_with_score('知道腾讯公司吗?,腾讯公司的总部在哪儿?是谁创建的?主要做什么业务?根据已知信息用中文回答,不要进行其他信息的扩展', k = 10)
for doc in docs:
print(doc)
打印的结果如下:
(Document(page_content=',决定创立一家公司', metadata={'source': './jiling'}), 0.3955434262752533)
(Document(page_content='结论:\n极领是一家专注于鼠标制造的公司', metadata={'source': './jiling'}), 0.42965108156204224)
(Document(page_content='极领是一家专注于鼠标制造的公司', metadata={'source': './jiling'}), 0.4341241121292114)
(Document(page_content='。李明是一位对电脑科技充满热情的工程师', metadata={'source': './jiling'}), 0.43468624353408813)
(Document(page_content='\n技术支持: 用户可以通过官方网站或客户服务热线获取产品的技术支持和使用指南', metadata={'source': './jiling'}), 0.43557584285736084)
(Document(page_content='\n用户反馈: 极领欢迎用户提供产品使用过程中的反馈和建议', metadata={'source': './jiling'}), 0.4556066691875458)
(Document(page_content='创建人: 李明\n创建时间: 2005年', metadata={'source': './jiling'}), 0.4594237804412842)
(Document(page_content='产品名称: 极领\n创建人: 李明', metadata={'source': './jiling'}), 0.4658869206905365)
(Document(page_content=',极领确保用户能够方便地购买和使用产品', metadata={'source': './jiling'}), 0.46902239322662354)
(Document(page_content='创建时间: 2005年\n历史背景:', metadata={'source': './jiling'}), 0.47098231315612793)
说明:后面的分数代表了文本和问题的相似度,分数越小,代表相似度越大。我们可以规定一个阈值,当分数小于阈值的时候,代表用文本进行回答是有效的,否则不进行回答。但是这也会带来另外一个问题,比如问“这篇文章主要讲了些什么”这种比较笼统的问题的时候,召回的相关性也会比较低,就有点无解。
这篇文章只是一个自有知识库问答的演示示例,尚未达到商业应用的水平,可以看作是一次探索的过程。作者认为,随着 ChatGPT 的迭代和越来越多的人开始尝试和探索大语言模型(LLM)进行应用开发,未来几年可能会形成一个以大语言模型为基础的全新生态系统。在这个生态系统中,各种商业应用将运行在大语言模型之上,就像 Android 和 iOS 生态系统一样,走进每个消费者的手中。