以ChatGPT为代表的大语言模型应用自问世以来已经火了好几年。在这期间国内外类似产品层出不穷,甚至公司内部团队都开发了好几个AI小助手。刚好最近看了几篇关于大语言模型应用开发的文章,借此了解了一下应用层面的基本知识,也算是接触到了大语言模型领域的一点点边。
写在开头
作为一名纯粹的后端开发,平时的工作内容基本都是面向业务写代码,常常自嘲是CRUD Boy。 在ChatGPT等产品出现后,使用最多的场景就是把它当成搜索引擎,查一些不常用语言的语法和示例代码。
从个人体验来说,此类需求的查询质量比Google要好一些。起码很多示例代码稍作修改都能用,起码不用像某些网站,复制代码还要先登录(说的就是你CSDN)。
选择"向量数据库"作为第一篇大语言模型(后面统称LLM)相关的文章,是因为对于后端开发者来说,数据库平时接触的会多一些,拥有一定的基础,学习和理解起来也会更容易。
LLM的缺陷
在写向量数据库之前,还得从实际使用的角度出发,聊一聊LLM的一些缺陷。
1、文本长度限制
如果大家使用过ChatGPT这类产品,肯定遇到过此类提示"输入内容长度超过限制"。
顾名思义,文本太长了,但提示背后的原因还是值得深究下。尤其是有些时候,明明每次输入的文本就没几个字,怎么就超过限制了呢?
这个问题的源头,当然是LLM在作祟。首先LLM不是以单词个数来确定长度,而是以 token 来计算。不同标记方法(tokenization method),得到的结果也不一样。
举个简单例子,文本"ChatGPT",可能被拆分成"Chat", "GPT" 2个token,换个算法可能就是另一个结果。
另外不同的LLM, token长度限制也不一样。GPT-3.5 限制大约是4096,GPT-4 限制约是32K。不同的文本长度限制,涉及LLM对算力、理解能力的权衡。这属于更深层次的范畴,这里只要知道有这么个逻辑即可。
不过有的时候我们输入的文本明明很短,肉眼估算还远远没达到最大限制,ChatGPT为啥还会提示超过限制呢?
要解释这个问题,就得从LLM应用开发的角度出发。在ChatGPT这类应用中,新建一个聊天窗口,并与之一问一答的过程中。其背后的执行程序,并非是每次将输入的文本,原模原样的发给LLM。而是将上下文进行了组合,再添加最新的输入信息,一同发送给LLM。
这也就解答了上面这个问题,毕竟问答的次数越多,组合在一起的文本(token)就约接近上限。
2、内容更新滞后
训练LLM的数据集,是取自于当时已有的数据(静态数据集),因此无法保证数据集的实时性。ChatGPT刚刚出来的时候,就有提到只能查询21年之前的信息。这就导致了一部分依赖最新数据的业务无法直接使用LLM。
毕竟重新训练一个LLM的时间成本和金钱成本都是很庞大,否则各种LLM也不会只局限在"大厂"之间竞争。
3、效率与准确性
除了上面2点外,LLM也存在产生与客观事实不符的回答(幻觉);以及处理长文本时效率低的情况。这里也不做展开,有兴趣的同学可以自行搜索相关文章。
站在巨人的肩膀
为了解决上面提到的这些问题,业界有两种主流的方式:微调(Finetune) 和 检索增强生成(RAG)。
特征比较 | RAG | 微调 |
---|---|---|
知识更新 | 直接更新检索知识库,无需重新训练。信息更新成本低,适合动态变化的数据。 | 通常需要重新训练来保持知识和数据的更新。更新成本高,适合静态数据。 |
外部知识 | 擅长利用外部资源,特别适合处理文档或其他结构化/非结构化数据库。 | 将外部知识学习到 LLM 内部。 |
数据处理 | 对数据的处理和操作要求极低。 | 依赖于构建高质量的数据集,有限的数据集可能无法显著提高性能。 |
模型定制 | 侧重于信息检索和融合外部知识,但可能无法充分定制模型行为或写作风格。 | 可以根据特定风格或术语调整 LLM 行为、写作风格或特定领域知识。 |
可解释性 | 可以追溯到具体的数据来源,有较好的可解释性和可追踪性。 | 黑盒子,可解释性相对较低。 |
计算资源 | 需要额外的资源来支持检索机制和数据库的维护。 | 依赖高质量的训练数据集和微调目标,对计算资源的要求较高。 |
推理延迟 | 增加了检索步骤的耗时 | 单纯 LLM 生成的耗时 |
降低幻觉 | 通过检索到的真实信息生成回答,降低了产生幻觉的概率。 | 模型学习特定领域的数据有助于减少幻觉,但面对未见过的输入时仍可能出现幻觉。 |
伦理隐私 | 检索和使用外部数据可能引发伦理和隐私方面的问题。 | 训练数据中的敏感信息需要妥善处理,以防泄露。 |
微调可以简单理解成重新训练模型;RAG则被称为LLM的外挂,与微调相比在成本上有着明显的优势。此外RAG技术已经在问答系统、文档检索等领域都取得了成功。
LangChain
LangChain 是一个开发框架,能够方便开发者快速构建RAG应用。类比成java中的springboot可能会好理解一些。
下图蓝框部分就是LangChain的核心链路,同时也是RAG解决LLM缺陷的实现链路。接下来要讲的"向量数据库",就是其中的核心组成部分,在下图中以红框标注。
向量数据库
前面铺垫了这么多,终于来到了重头戏。向量数据库与我们常用的传统数据库,如MySQL、Redis等最大的区别在于它存储数据的方式,以及提供的查询能力。
传统数据库的局限
对于传统数据库,搜索功能都是基于不同的索引方式(B\B+ Tree、倒排索引等)加上精确匹配和排序算法等实现的。本质还是基于文本的精确匹配,这种索引和搜索算法对于关键字的搜索功能非常合适,但对于语义搜索功能就非常弱。
比如搜索"狗",传统数据库里只能查到关键词带有"狗"的相关结果,无法查询到"柯基"、"泰迪"这些字段里不包含有"狗"的数据。这样的查询结果其实是不准确的。
如果要解决这个问题,常见的办法就是引入新字段作为"特征"。下次查询就可以通过"特征"字段的值进行筛选,得到所有"狗"的数据。
生成和挑选特征这个过程,有个专门的领域:特征工程(feature engineering)。特征工程是一个过程,这个过程将数据转换为能更好的表示业务逻辑的特征,从而提高机器学习的性能。
如果将特征提取扩展到非结构化的数据,比如图片、音频、视频。它们包含的特征数量会非常庞大,提取特征的时间、金钱成本也会几何倍的上升。
Vector Embedding
embedding(嵌入)技术,能够以自动化的方式提取数据的特征(包括结构化数据和非结构化数据)。embedding的具体实现依赖不同的算法,将原始数据转换为向量数据。
如上图所示每只狗对应一个二维坐标点,有些狗狗之间的差异很大(最简单的判断方式:点之间的距离),因此能轻易的区分开来,比如金毛和柴犬。
同样有些狗狗之间的距离就很近,说明在[毛长,体型]的维度上,它们很相似。如果要提升区分度,只要添加新的维度,从其它的特征区分,比如鼻子的长短,然后得到一个三维的坐标系和每只狗在三维坐标系中的位置。
理论上只要特征/维度足够多,就能够将所有的狗区分开来,最后就能得到一个高维的坐标系,虽然我们想象不出高维坐标系长什么样,但是在数组中我们只需要一直向数组中追加数字就可以了。
事实上,世间万物都可以用一个多维坐标系来表示,它们都在一个高维的特征空间中对应着一个坐标点。当然这些坐标点的准确性,非常依赖embedding技术。
市面上有很多厂商提供了商用的embedding api,例如OpenAI、文言千帆、讯飞星火等等。甚至同一个厂商还会提供不同版本的api,根据底层模型、算法不同,收费标准也不一样。
相似度搜索(Similarity Search)
借助embedding技术,我们可以将世间万物抽象成一组组向量数据,并存放到向量数据库中。有了数据,下一步就是如何使用了?这就不得不提相似度搜索了。
其实上面已经提到过了,判断2个向量之间的相似度的方式,就是计算2个向量之间的距离。这只是计算相似度方法的其中一种。
用最简单的二维坐标系作为示例;
1、欧几里得距离
欧几里得距离是向量之间的绝对距离,两个向量之间距离越小,就表示越相似。 这种计算方式看似最简单,但计算结果很容易产生偏差。比如当2个维度的单位不同时,结果可能会失效。
2、余弦相似度
余弦相似度是通过计算向量之间的夹角,两个向量的夹角越小,就表示越相似。 这种计算方式对向量长度不敏感,只关注方向的相似性,所以也很容易在一些场景下产生偏差。
3、点积相似度
点积相似度则是计算向量之间的点积,点积越大,就越相似。 点积则兼顾了向量的长度与方向。
这些计算相似度的方法,本身并无法定义有优劣之分,只能说它们有各自适用的领域。毕竟AI应用范围如此广泛,没有最好的,只有最合适的。
过滤(Filter)
实际查询时,肯定不希望每次对全量的数据进行搜索,这样效率就太低了。向量数据库也有这方面的考虑,它一般会维护2个索引:向量索引 、元数据索引。
元数据索引有点类似MySQL上的唯一索引\普通索引。在真正执行时,不论是前置过滤,还是后置过滤,都是借助元数据索引来减少查询范围,达到提升查询效率的目的。
回到RAG
到这里向量数据库的基本知识点写的差不多了,回过头来,还剩下向量数据库在LLM中是如何发挥作用的? 其实LangChain的那张流程图已经回答了这个问题。这里又重新画了一个更加简单的流程图。
在没有引入向量数据库前,和LLM交互的流程如上图红色剪头所示,输入的查询文本会直接结合上下文构造出Prompt(可以简单理解是LLM的request格式)。
而引入了向量数据库后,查询链路就多了蓝框中的这些步骤。输入的查询数据 会经过embedding先处理成向量数据,再请求向量数据库查询得到最匹配的文本(TopN),最终组合最初的问题构成Prompt。
这样做的好处就是,减少了上下文的大小,可以用更准确且精简的文本构造prompt,从而提升LLM回答的质量与效率。 另一方面因为数据源的外置化,可以很方便的更新向量数据库里的数据,实现LLM应用的快速迭代。