问题背景
常常说要带着问题阅读,当我们阅读文章或者书籍的时候,希望能够从中找到自己的答案,GPT出现后,是否可以通过GPT的问答来直接解决我们的问题,而不需要我们自己从文章或书籍中寻找答案呢?
因为我们希望向GPT询问关于文章的问题,所以需要我们将文章作为Context 一起传给GPT,这样GPT才会有上下文。但一般文章或书籍都是比较长的,比如以下面这篇arxiv.org/pdf/2302.04… 论文为例,全文有几万字,我们把这几万字输入GPT后,会得到1个Token Limit的报错。
Token Limit的原因
这里简述下,为什么GPT随着输入内容的增加,费用也越来越高, 而且会有Token Limit的问题。
GPT是transformer decoder架构, 而transformer架构非常核心的是self-attention机制,由于需要考虑context,所以self-attention 在计算每个输出时都需要考虑所有输入,以下是示意图:
图片来源: speech.ee.ntu.edu.tw/~hylee/ml/m…
可以看到每一层self-attention随着输入内容的增加,计算量将大幅增加,而且transformer中会有很多层self-attention结构。
既然全部输入不是很靠谱,那有没有其他办法呢?
解决方案
既然全部输入不可行,那如果我们可以找到,和问题相关的几段内容,是不是就可以解决了呢?比如:
当我们询问
根据下面的内容回答问题:
1. ...
2. ...
3. ...
4. ...
(1~4是从文章中选取的内容)
Toolformer 做了什么以及如何工作的?
得到了以下不错的答案
那么如何找到问题相关的内容呢?如果阅读者去找,那这个问答系统其实就没有用了,需要让机器帮我们自动找到问题的相关内容。这里用到了word embedding的技术。先简单介绍下word embedding。
word embedding
word embedding是一项让机器能够理解自然语言的技术,它将每个单词转换成向量形式,让计算机能够处理自然语言中的关系和语义。想象一下,如果我们把单词比作彩色积木,而word embedding则是将这些积木按照它们的形状、大小、颜色等特征分门别类地组合起来,让机器能够更好地理解它们。在word embedding的世界里,每个单词都变成了一个向量,向量中的每个维度代表了该单词的一个特征,比如说某一个维度可以表示单词的情感色彩,另一个维度则可以表示单词的运动状态。
当然,在实践中,我们也会遇到一些挑战。比如说,传统的Word embedding方法无法处理多义词和稀有词。这时出现了一些基于预训练模型的word embedding方法,比如BERT、GPT等,它们可以通过大规模的预训练学习来获取单词的上下文信息和语义信息,从而生成高质量的词向量和文本向量。这些词向量和文本向量可以用于下游自然语言处理任务的输入和特征提取,提高任务的准确性和效率。
有了word embedding后,我们可以将问题和文章都转化为向量,然后通过余弦相似度等方式来从文章中寻找相似的内容。以下是流程:
在上面的流程图中,我们将问题和文章转化为向量,但往往对一篇问题进行问答时会问多个问题,此时每次询问的文章是相同的,没必要每次都做向量化,而且文章大多比较长,做向量化也会造成较大的资源浪费和时间消耗。所以我们希望将向量化后的文章存储下来。
接下来我们就简单介绍向量的存储方案,向量数据库。
向量数据库
向量数据库是一种新兴的数据库类型,它的主要特点是能够高效地存储和查询向量数据。
向量数据库的核心是向量的存储方式。在向量数据库中,每个向量都被存储为一行数据,每个维度都是一个列,每个值都是一个单元格。这种存储方式使得向量之间的距离计算变得非常高效。同时,为了能够高效地查询向量数据,向量数据库使用了向量索引。常见的向量索引包括KD-Tree、LSH和HNSW等。这些索引算法能够将向量数据划分成多个子空间,并在每个子空间中建立索引,从而使得向量的查找效率大大提高。
向量数据库的应用场景非常广泛。例如,在图像搜索中,向量数据库可以对每张图片的特征向量建立索引,从而能够快速地找到与某张图片相似的其他图片;在语音识别中,向量数据库可以对每段语音的特征向量建立索引,从而能够快速地找到与某段语音相似的其他语音。此外,在自然语言处理中,向量数据库也可以用来存储词向量或者句向量等数据。
向量数据库主要包含了存储向量和查询相似向量的功能。所以我们的流程变为:
代码实现
最后,我们来看下代码实现,这里我们依旧使用LangChain来做示例,解决这个问题:
import os
os.environ['OPENAI_API_KEY'] = "openai key"
from langchain.indexes.vectorstore import VectorstoreIndexCreator
from langchain.vectorstores import Chroma
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter, TextSplitter
from langchain.document_loaders import PyMuPDFLoader,PDFMinerLoader
from langchain.chains import VectorDBQAWithSourcesChain
from langchain.llms import OpenAIChat
from flask import Flask,request
from flask_cors import CORS
import json
app = Flask(__name__)
CORS(app)
def getPersistPath(name:str):
return "./chroma_store/" + name;
@app.route('/test', methods=["GET"])
def test():
return "read pdf with ai!"
@app.route('/load_pdf', methods=["POST"])
def loadPDF():
data = json.loads(request.data);
name = data['file_url'];
persistPath = getPersistPath(name)
if not os.path.exists(persistPath):
loader = PDFMinerLoader(name)
data = loader.load()
embeddings = OpenAIEmbeddings()
textsplitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
docsearch = Chroma.from_documents(documents=textsplitter.split_documents(data), embedding=embeddings, persist_directory=persistPath)
docsearch.persist()
return json.dumps({'success': "load finish!"})
return json.dumps({'success': "has loaded!"})
@app.route('/read_with_ai', methods=["POST"])
def query():
data = json.loads(request.data);
queryMessage = data['query_message'];
preMessage = data['pre_message'];
name = data['file_url'];
if len(queryMessage):
embeddings = OpenAIEmbeddings()
persistPath = getPersistPath(name)
docsearch = Chroma(embedding_function=embeddings, persist_directory=persistPath)
llm = OpenAIChat()
if len(preMessage):
llm.prefix_messages = preMessage
chain = VectorDBQAWithSourcesChain.from_chain_type(llm, chain_type="stuff", vectorstore=docsearch)
similarityDoc = docsearch.similarity_search(queryMessage, k=5);
chatRes = chain({"input_documents": similarityDoc, "question": queryMessage}, return_only_outputs=True)
answer = chatRes.get('answer')
translationRes = llm.generate(["请将下面语句翻译为中文:" + answer]).generations[0][0].text
res = {'role': 'assistant', 'content': translationRes};
return json.dumps(res);
return "search error"
if __name__ == '__main__':
app.run(port=6666)
代码解释:
/load_pdf接口是用来加载pdf文件的,主要是将pdf文件转化为文本,并将文本进行分割,然后将分割后的文本进行向量化,最后将向量化后的结果进行持久化。/read_with_ai接口是用来和用户交互,主要是将用户的问题进行向量化,然后在pdf文件中进行相似度搜索,找到相似的文本,最后将相似的文本以及问题输入到OpenAI的语言模型中,得到答案并返回给用户。
这里的embedding 方案我们使用openai提供的embedding转换。
我们使用Chroma作为向量数据库,在调用完load_pdf接口后,可以看到已经将pdf向量化并存储在向量数据数据库文件中:
效果展示:
可以看到,通过自动化寻找相似问题作为context,我们已经可以让GPT回答关于目标文章的相关内容。
结尾
AI时代会有很多生活方式被改变,阅读也许就是其中之一,之前我们带着问题在书中寻找答案,现在我们向读过书的AI提问得到答案,未来已来。