从后端开发视角认识向量数据库

以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应用的快速迭代。

参考文章

大模型应用开发:datawhalechina.github.io/llm-univers...

向量数据库:guangzhengli.com/blog/zh/vec...

相关推荐
新知图书28 分钟前
Rust编程与项目实战-模块std::thread(之一)
开发语言·后端·rust
盛夏绽放1 小时前
Node.js 和 Socket.IO 实现实时通信
前端·后端·websocket·node.js
Ares-Wang1 小时前
Asp.net Core Hosted Service(托管服务) Timer (定时任务)
后端·asp.net
我爱学Python!1 小时前
大语言模型与图结构的融合: 推荐系统中的新兴范式
人工智能·语言模型·自然语言处理·langchain·llm·大语言模型·推荐系统
Rverdoser2 小时前
RabbitMQ的基本概念和入门
开发语言·后端·ruby
Tech Synapse3 小时前
Java根据前端返回的字段名进行查询数据的方法
java·开发语言·后端
.生产的驴3 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
微信-since811923 小时前
[ruby on rails] 安装docker
后端·docker·ruby on rails
LZXCyrus4 小时前
【杂记】vLLM如何指定GPU单卡/多卡离线推理
人工智能·经验分享·python·深度学习·语言模型·llm·vllm
代码吐槽菌5 小时前
基于SSM的毕业论文管理系统【附源码】
java·开发语言·数据库·后端·ssm