向量数据库与 Pinecone 快速入门一文通
向量数据库介绍
我们先来看一下为什么需要向量数据库。
从互联网开始发展的第一天起,数据的规模就在迅速增长,几乎每一天、每一秒,数据都在增长。这是所有人都知道的事实。
从最早我们的数据库采用关系型数据库,Oracle、PostgreSQL 和 MySQL 大行其道。
但我们很快就发现它们不是很够用,特别是在性能方面。
所以后面出现了以 MongoDB 为代表 NoSQL,还有以 Redis 为代表的缓存数据库,以 InfluxDB 为代表的时序数据库、以 MinIO 为代表的对象存储数据库等等。
它们要么是为了实现海量存储,要么是为了提升性能。总之各自有各自的理由。
本质上说,都是因为复杂数据的增长太快,它们以非结构化的形式出现。这些复杂的结构,通常会以文档、图像、视频或者纯文本的方式出现。传统的结构化数据库只能处理结构化数据,处理复杂数据会很困难,至于查询性能就更难以保证。
怎么样处理这种不规则、多样化的数据结构比较好呢?答案就是机器学习。
机器学习可以把复杂的数据通过一些方式转换为矢量嵌入格式,可以提供更有效的表示形式。
我们可以使用一些开源的机器学习模型,它们有很高的性能,而且还支持微调。比如 www.sbert.net/ 和 github.com/dav/word2ve… 甚至我们还可以自己训练新模型,但通常并不常见,使用业界成熟的模式是一种更好的方式。
对程序员来说,一个向量就是一个数组格式的值,它可能包含很多个元素,像下面这样:
[0.13, -0.21, 0.18, ..., -0.07]
元素的个数就代表向量的维度,比如 300 个元素就是一个 300 纬的向量。
那这个数组有什么用呢?它可以用非常紧凑的方式来表示一个事物的很多特征,以及和另一个事物之间的关系。这样当我们搜索某个事物的时候,就可以通过向量去查找和它相似度高的其他事物。这在自然语言处理中非常常见。
从上面的介绍中我们可以看出,向量数据库最核心的功能是搜索的性能。
向量数据库与AI 的关系
向量数据库其实不是近期出现的,实际上早在 2000 年左右就出现了,只是因为那个时候机器学习相关的技术并没有什么热度,而且效果也都很差,所以向量数据库是非常小众的东西。
但是从 21 年语言大模型(LLM)火了之后,顺带把向量数据库也给带火了。为什么 LLM 可以带动向量数据库呢?因为向量数据库解决了两个比较重要的问题,数据扩展和数据安全。我们接着看。
数据扩展
目前所有 LLM 都有相同的特征,就是需要预先训练,进行训练的数据集具有训练截止日期。在这个日期之后的事情 LLM 一无所知。
也就是说,LLM 的记忆力是有限的,而且无法成长。如果你想让 LLM 知道更多的知识,那么就得训练一个全新的 LLM。但是训练 LLM 又是一件非常非常烧钱的事情。
向量数据库可以为大模型提供实时性的数据,或者是提供 LLM 训练时没有的历史数据。这样就可以解决数据限制上的问题。
另一个,LLM 在使用的过程中,它的上下文记忆力也是有限的。像 GPT 3.5,默认版本只有 4k 的记忆空间,claude 2 则有 100k。超过这个空间的聊天信息就会被遗忘。针对这个问题,向量数据库同样可以通过把当前消息转换为向量进行相关性搜索,从上下文之前的数据中找到相关的信息,插入到当前的消息中,提供上下文。但是实际效果比较一般,甚至相当差。
我们可以把 LLM 简单的想象成是一台电脑。它具有硬盘和内存,都有限制,而且很难提升。向量数据库可以当做一个外接的 U 盘或者内存条,变相的来扩展这台电脑的能力。
数据安全
我们在使用 LLM 的同时,其实也是在向 LLM 背后的运营商发送数据。如果大范围的使用 LLM,难保一些机密数据泄漏。这也是为什么中国全方面抵制 ChatGPT 的一个重要原因。
如果我们使用向量数据库,那么可以私有部署,这样能够间接地减少数据泄露的风险。
多模态搜索
虽然现在向量数据库的主战场仍然是自然语言处理,但其实多模态功能也是向量数据库的强项。
在大模型的发展过程中,逐渐从自然语言处理转向多模态。在未来,向量数据库可以很好地为 LLM 提供跨语言信息检索、图像检索、生物信息检索等能力。
产品
目前向量数据库的市场突然因 AI 的原因被洗牌,等于整个细分行业又开始重新布局。
所以短时间内涌现了大量的向量数据库产品,比较出名的有:Milvus、Pinecone、Weaviate、Qdrant、Chroma、Vespa 和 Vald。
目前有两个趋势。
第一个是趋势是大多数产品都是创业公司在做。有一个例外是 Yahoo,Vespa 和 Vald 都是 Yahoo 开源的。
第二个趋势是开源,大部分的向量数据库都是开源的。不过同样有一个例外,那就是向量数据库云领域的老大 Pinecone。它是向量数据库领域获得单轮投资最多的公司,B 轮融资整整 1 亿美元。同时它也是 LLM 领域最大的公司 OpenAI 推荐的产品。
接下来我们会学习 Pinecone 的使用。
Pinecone 教程
首先注册账号,进入控制台。
目前官方提供了 Python 和 Nodejs 两种语言的库进行 CRUD 操作,Python 是首选语言,Node.js 目前是预览版,API 可能会发生变化。除此之外社区还提供了 Ruby 语言库。
如果使用其他编程语言,还可以选择使用 HTTP 进行操作。
这里使用 Node.js 版本。
1.安装客户端
npm i @pinecone-database/pinecone
2. 获取密钥
在控制台获取。
3. 执行 CRUD
初始化客户端
初始化客户端需要环境和 APIKey 两个参数。
它们都可以在控制台中查看。
import { PineconeClient } from "@pinecone-database/pinecone";
const pinecone = new PineconeClient();
await pinecone.init({
environment: "YOUR_ENVIRONMENT",
apiKey: "YOUR_API_KEY",
});
创建向量索引
name 是索引的名称,dimension 是向量的维度。
await pinecone.createIndex({
createRequest: {
name: "first-index",
dimension: 4
}
})
除了这两个必备的属性外,我们还可以设置一些其他属性。
比如 metric,设置指标。shards,设置分片数量。
检索索引
列出项目中的所有索引。
const indexesList = await pinecone.listIndexes();
这里会输出我们上一步创建的索引。格式是 id 字符串数组。
[ 'first-index' ]
到这里我们的索引就创建好了。
连接索引
使用客户端索引之前,必须连接到索引。
const index = pinecone.Index("first-index")
插入向量
向量的格式是一个数组。
数组的每一项都会包含 id、values 和 metadata 三个部分。
id 是向量的唯一标识,values 是向量的数据,metadata 是元数据,它是可选的。
const upsertRequest = {
vectors: [
{
id: 'vec1',
values: [0.1, 0.2, 0.3, 0.4],
metadata: {
genre: 'drama',
},
},
{
id: 'vec2',
values: [0.2, 0.3, 0.4, 0.5],
metadata: {
genre: 'comedy',
},
},
],
namespace: 'example-namespace',
};
const upsertResponse = await index.upsert({ upsertRequest });
upsertResponse 会返回一个对象,包含一个 upsertedCount 属性,表示插入的向量数量。
获取索引的统计信息
我们可以通过 API 来查询有关索引的统计信息。
const indexStats = await index.describeIndexStats({
describeIndexStatsRequest: {
filter: {}
}
})
返回数据:
{
namespaces: {
'example-namespace': {
vectorCount: 2
}
},
dimension: 4,
indexFullness: 0,
totalVectorCount: 2
}
这里我们可以看到命名空间、向量和维度。
查询索引
现在我们可以查询索引,获取相似向量。
vector 是当前要匹配的向量;topK 是返回相似度最高的 N 个向量;includeValues 是是否包含 values;includeMetadata 是是否包含元数据;filter 可以设置一些条件来过滤掉一些向量。
const queryRequest = {
vector: [0.1, 0.2, 0.3, 0.4],
topK: 10,
includeValues: true,
includeMetadata: true,
filter: {
genre: {
$in: ["comedy", "documentary", "drama"]
},
},
namespace: "example-namespace",
}
const queryResponse = await index.query({ queryRequest })
返回数据:
{
results: [],
matches: [
{
id: 'vec1',
score: 1,
values: [Array],
sparseValues: undefined,
metadata: [Object]
},
{
id: 'vec2',
score: 0.993808031,
values: [Array],
sparseValues: undefined,
metadata: [Object]
}
],
namespace: 'example-namespace'
}
我们可以通过 metches 各项数据中的 score 来查看匹配度。
获取向量
有些时候我们并不想进行查询索引,而是想获取向量。
const vectors = await index.fetch({
ids: ['vec1', 'vec2'],
namespace: 'example-namespace',
});
返回数据:
{
vectors: {
vec1: {
id: 'vec1',
values: [Array],
sparseValues: undefined,
metadata: [Object]
},
vec2: {
id: 'vec2',
values: [Array],
sparseValues: undefined,
metadata: [Object]
}
},
namespace: 'example-namespace'
}
更新向量
如果你需要更新向量,那么可以使用 update 方法。
const updateRequest = {
id: "vec1",
values: [0.1, 0.2, 0.3, 0.4],
setMetadata: { genre: "drama" },
namespace: "example-namespace"
}
const updateResponse = await index.update({ updateRequest });
删除向量
删除向量需要调用 delete1 方法,注意后面有个 1。
传递的参数是一个对象,包含 ids 和 namespace 属性,分别是向量 id 数组和命名空间。
await index.delete1({
ids: ["vec1", "vec2"],
namespace: "example-namespace"
})
删除索引
await pinecone.deleteIndex({
indexName: 'first-index',
});
元数据
描述向量的键值对。
也是一种用来区分数据类型的标签。
比如我们的索引是针对动物的,那么可以对一些向量标记为狗,一些向量标记为猫。
如果是针对语言的,那么可以对一些向量标记为中文,一些向量标记为英语。
元数据是 Pinecone 灵活的表现。
我们可以通过过滤元数据来进行查询索引,这也是元数据对我们来说唯一的作用。
元数据的过滤条件有如下几个操作符:
- $eq:相等
- $ne:不相等
- $gt:大于
- $gte:大于等于
- $lt:小于
- $lte:小于等于
- $in:包含
- $nin:不包含
转载自:https://juejin.cn/post/7264492759985356855