likes
comments
collection
share

向量数据库与 Pinecone 快速入门一文通

作者站长头像
站长
· 阅读数 2

向量数据库介绍

我们先来看一下为什么需要向量数据库。

从互联网开始发展的第一天起,数据的规模就在迅速增长,几乎每一天、每一秒,数据都在增长。这是所有人都知道的事实。

从最早我们的数据库采用关系型数据库,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 教程

首先注册账号,进入控制台。

www.pinecone.io/

目前官方提供了 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:不包含