基于embeddings+GPT的Q&A探索
https://liaoxuefeng.com/blogs/all/2023-07-31-qa-using-embeddings/index.html
1、当前存在的问题
我们希望把API文档、帮助文档等“事实回忆”的内容,通过Chat的方式可以快速获得。当前的GPT、一言等存在以下几个问题:
- 【时效性】训练数据是基于某个时间之前的数据,缺少最新的数据,而且每一次增加最新的数据都会带来高昂的训练成本
- 【安全性】无法访问企业内部私密的文档,且这些文档是不能用来Fine-Tunning的
- 【Token限制】每次会话会有Token限制,而文档内容是比较多的,使用Prompt的方式会远超Token限制
事实回忆是指什么?
可以理解为通过Chat的方式做检索。举个例子来说,如果手里有大批的iPipe、iCafe、iCode帮助文档和问答语料,我们希望用户在获取相关功能的内容介绍时、编译失败查看为什么失败时、代码合入冲突如何解决时、查看某个产品有哪些OpenAPI时等等,这些都算是事实回忆。
2、可行方案
2.1、什么是Embeddings
重点了解Embeddings是做什么、怎么用,对其内部原理不做过多分析。
Embedding 是一种以向量的方式表示“数据”的策略,这里的“数据”可以是一个物体、一个词语、一段对话、一部电影等等。无需完全理解其原理,可以简单的记为Embeddings是通过向量的方式来表示真实数据,它可以将简单的数据升维,让数据特征更明显,也可以让复杂的数据降维,让它更容易理解和计算。同时Embeddings还有“相加”、“相减”的能力,这些能力都是通过他的本质数据结构——向量来完成的。
- 升维:升维的目的是将低维数据一些特征放大,便于提取
- 降维:降维的目的让复杂的数据更容易理解和计算,并且减少无效空间的占用_(比如一篇文章1w字,其中1000个字是“的”,那么可以通过降维的手段来把它压缩)
- 相加和相减
- 相似性计算:相似性比较有很多算法,比如比较简单余弦相似度,本质也是在通过向量计算。
2.2、详细方案
之所以用大段文字描述Embeddings,是因为我们需要使用Embeddings方式来压缩大文本数据,同时做检索,从而解决Prompt Token过长的问题。
方案分为准备Embeddings数据、检索Embeddings数据、发起Query获得响应三部分,具体如下:
- 准备Embeddings数据
- 收集: 将帮助文档、API文档、用户问答语料统一收集,或者需要其他知识文档也可以收集起来
- 切片:将文档切片,因为收集的文档普遍较大,不建议将整个文档转换为Embeddings,而是切分成若干段落,这样在后续的检索、组装Prompt也更有效
- 生成:通过调用OpenAI的接口将各个切片生成Embeddings
- 存储:将生成的Embeddings存储,可以使用向量数据库
这一步其实是在构建一个检索库,也可以把Embeddings当成索引,向量数据库想象成ES。
- 检索Embeddings数据
- 用户提问获得Query,调用OpenAI的API获取Query的Embeddings
- 使用这个Embeddings去向量数据库中做相似性计算,获取权重最高的n个切片
- 发起Query获得响应
- 将n个切片和用户原始Query组装成Prompt,注意Prompt不能超出Token限制,如果超出限制要多n个切片进行低权重淘汰
- 发送信息获取响应
一种基于搜索-提问的两步法,使GPT能够使用参考文本库回答问题。
- 搜索:在文本库中搜索相关文本片段;
- 提问:将检索到的文本片段连同消息一并发送至GPT并提问问题。
文本搜索相对于微调的一个缺点是每个模型都受到一次可以读取的最大文本量的限制。
2.3、Embeddings的缺陷
- 多次调用模型API生成Embeddings,会耗费大量的资源(Money)
- 对于切片的粒度要求很高,不能太粗或太细,同时高度依赖于Embeddings的生成质量
3、AutoGPT的方案
最近大火的AutoGPT实现方案和以上内容也有相似之处,其实并不是一个算法、策略的创新,而是一种工程、产品的创新,为了方便理解,做简要分析对比:理论上说,ChatPDF、AgentGPT等各种工具都是通过类似的方式完成。
- 用户输入ai_name(角色)、ai_role(角色描述)、ai_goal(5个目标)
- 让ChatGPT根据输入的角色和目标,分步生成操作序列
- 根据操作序列,调用不同的命令组件(如Shell脚本、网页爬虫、语音播报、Google搜索等)
- 组件调用后的输出存储到缓存或长期记忆中,作为下一个组件的输入
- 直到最后一个组件执行完成,任务结束
- https://github.com/openai/openai-cookbook/blob/d67c4181abe9dfd871d382930bb778b7014edc66/examples/Embedding_long_inputs.ipynb
- https://github.com/openai/openai-cookbook/blob/d67c4181abe9dfd871d382930bb778b7014edc66/examples/Embedding_Wikipedia_articles_for_search.ipynb
- https://github.com/openai/openai-cookbook/blob/main/examples/Question_answering_using_embeddings.ipynb
- https://simonwillison.net/2023/Jan/13/semantic-search-answers/
存在的问题
知识库原始数据向量化的过程中
- 如何选择合适的向量库
选择向量库,主要可以通过以下几个方面考虑:- 向量库本身的可扩展性和可靠性,以及引入向量库怎么保持多个系统之间的一致性
- 是否能在相似度搜索基础上支持 metadata 条件查询
- 是否支持多种类型的用户 query,例如关键词搜索,文档总结,多个文档之间的对比等
- 是否能支持复杂的 index 结构,例如有层级关系的 index
- 如何进行巧妙的切分(chunks)
对于帮助文档存在API文档、用户手册(包含大量图片、视频)、FAQ等类型,不同的文档内容结构是不一样的,因此选择通用的切分方案会将连贯的数据进行切散,如下是一个典型的MD文档,如果复用MD通用拆分方法,直接使用“#”关键词做切割,那么会切割成5部分,这种切法已经把文档一个连贯的描述切散- 方案一:直接使用chunk size作为切割条件
- 完整的描述被切碎
- 用户的问题和切碎后的文本做相似性匹配时召回率非常低
- 方案二:基于MD的标准格式进行切分
- 保证了每个文本块的连续性描述
- 用户的问题往往很短且形式多样,但完整的文本块描述比较长,仍然会导致做相似性匹配时召回率非常低
- 方案三:基于MD的标准格式进行切分,但只向量化标题
- 保证了每个文本块的连续性描述
- 用户的问题往往很短且形式多样,做相似性匹配时只匹配标题,提高召回率
- 方案四:在方案三的基础上,新增关键词提取向量化过程
- 使用LLM针对答案进行关键词提取,约定关键词提取数量,比如最多提取5个
- 提取关键词之后直接使用原始文本存入向量库中
- 用户提问时一方面将Query向量化,另一方面实用LLM对Query进行关键词提取,即使用Query向量化 + Query关键词两个条件去向量库做相似性匹配,从而再次提高召回质量
- 方案一:直接使用chunk size作为切割条件
几篇文档切分论文:
- “A Survey on Text Segmentation Algorithms“ - 这篇论文概述了文本分割算法的不同方法和技术,包括基于规则和统计的方法、深度学习方法等,对于了解该领域的研究现状和发展趋势有帮助。
- “Semantic Text Segmentation using Deep Learning” - 这篇论文提出了一种基于深度学习的语义文本分割方法,使用卷积神经网络(CNN)和长短时记忆网络(LSTM)对文本进行建模,实现了较好的分割效果。
- “Unsupervised Text Segmentation based on Semantic Similarity and Word Embedding” - 这篇论文提出了一种无监督的文本分割方法,利用语义相似度和词嵌入技术对文本进行分割,对于处理无标签的文本数据有一定的借鉴意义。
用户回答过程中
- 如何提高召回的质量
方案三中已经提到了可以通过只对标题向量化提高召回质量,实际上这里做了一个预设,预设用户提的问题都是和标题含义差不多的。但答案里边实际上会有更多的内容可以回答更多的问题,因此,我们还可以基于LLM的能力反向生成一些列问题,并向量化