在当今数字化时代,人工智能AI正迅速改变着我们的生活和工作方式。从智能助手到自动驾驶汽车,AI正在成为各行各业的创新引擎。然而,这种AI的崛起也带来了一个关键的挑战:如何有效地处理和分析越来越丰富和复杂的数据。在这个背景下,向量数据库技术应运而生,为AI提供了强大的加速引擎。
AI时代的数据挑战:随着AI应用范围的扩大,大量的数据涌入各个行业。图像、文本、音频等多种数据形式都成为了AI的输入。这些数据的特点是多模态、高维、复杂和关联性强。传统的关系型数据库虽然在一些场景中仍然有用,但在处理这种多模态、高维数据时显得力不从心。因此,需要一种更适合AI应用需求的数据库技术,这就是向量数据库。
AI的加速引擎:向量数据库是一种专门为存储和检索向量数据而设计的数据库。它的核心思想是将数据映射到向量空间中,从而使得数据的相似性计算、聚类、分类和检索变得更加高效和精确。
向量数据库是专门用来存储和查询向量的数据库,其存储的向量来自于对文本、语音、图像、视频等的向量化。同传统数据库相比,向量数据库不仅能够完成基本的CRUD(添加、读取查询、更新、删除)等操作,还能够对向量数据进行更快速的相似性搜索
向量数据库拓展了大模型的边界,这种拓张包含两个方面,时间边界和空间边界:
时间边界的扩展:向量数据库能够使得大模型LLM拥有“长期记忆”。
目前的大模型(无论是NLP领域的GPT系列还是CV领域的ResNET系列)都是预先训练Pretrain的大模型,有着非常明晰的训练截止日Cut-off Date,这导致这些模型对于训练截止日之后发生的事情一无所知。而随着向量数据库的引入,其内部存储的最新的信息向量能够极大地拓展大模型的应用边界,向量数据库可以使得大模型保持准实时性,提高大模型的适用性,并使得大模型能够动态调整。因此向量数据库使得大模型具有了长期记忆。
假设一个预训练的新闻摘要模型在2021年底完成了训练,到了2023年,许多新闻事件和趋势已经发生了变化。为了使大模型能够处理这些新信息,可以使用向量数据库来存储和查询2023年的新闻文章向量。
在推荐系统中,预训练的大模型可能无法识别新用户和新产品的特征,通过向量数据库,可以实时更新用户和产品的特征向量,从而使大模型能够根据最新的信息为用户提供更精准的推荐。
此外,向量数据库还可以支持实时监测和分析。例如,在金融领域,预训练的股票预测模型可能无法获取训练截止日期之后的股票价格信息。通过将最新的股票价格向量存储在向量数据库中,大模型可以实时分析和预测未来股票价格走势。还有就是在客服领域,向量数据库将使得大模型可以追溯到对话的开始。
空间边界的扩展:向量数据库能够协助解决目前企业界最担忧的大模型泄露隐私的问题。
用户给出的Prompt可能会包含一些敏感信息。根据媒体报道,员工A用ChatGPT帮自己查一段代码的bug,而这段源代码与半导体设备测量数据有关;员工B想用ChatGPT帮自己优化一段代码,就直接将与产量和良品率记录设备相关的一段代码输入了其中。
这些行为直接导致了三星关键数据的泄露,而ChatGPT本身其实也出现过隐私泄露事件,使得有一小部分的对话历史/支付数据会被其他用户查看,这些数据都极为敏感,而通过本地部署,向量数据库能够在很大程度上解决这个问题。
向量数据库本地部署后可以存储企业有关的大量隐私数据,在本地部署或者专有云部署大模型后,通过特别的Agent大模型可以在有保护的情况下访问向量数据库的隐私数据,进而可以在不向外网暴露公司的隐私的情况下,使得公司的业务得到大模型的助力。
向量数据库自带多模态功能,这意味着它能够通过机器学习方法处理和理解来自不同源的多种模态信息,如文本、图像、音频和视频等,数据向量化过程使得这些不同模态数据的内部隐藏信息得以暴露,进而为多模态应用提供支持。
一个典型的应用场景是多语言搜索,向量数据库支持跨语言的信息检索,用户可以使用英语、法语、中文等多种语言搜索图书库,而无需事先对书名进行多语言翻译处理。这得益于向量表示能够捕捉到语义相似性,使得来自不同语言的查询和内容能够相互匹配。
Milvus 是一款云原生向量数据库,它具备高可用、高性能、易拓展的特点,用于海量向量数据的实时召回。
Milvus官网地址:Milvus
Milvus 基于FAISS、Annoy、HNSW 等向量搜索库构建,核心是解决稠密向量相似度检索的问题。在向量检索库的基础上,Milvus 支持数据分区分片、数据持久化、增量数据摄取、标量向量混合查询、time travel 等功能,同时大幅优化了向量检索的性能,可满足任何向量检索场景的应用需求。通常,建议用户使用 Kubernetes 部署 Milvus,以获得最佳可用性和弹性。
Milvus 采用共享存储架构,存储计算完全分离,计算节点支持横向扩展。从架构上来看,Milvus 遵循数据流和控制流分离,整体分为了四个层次,分别为接入层(access layer)、协调服务(coordinator service)、执行节点(worker node)和存储层(storage)。各个层次相互独立,独立扩展和容灾。
Milvus 向量数据库能够帮助用户轻松应对海量非结构化数据(图片/视频/语音/文本)检索。单节点 Milvus 可以在秒内完成十亿级的向量搜索,分布式架构亦能满足用户的水平扩展需求。
milvus特点总结如下:
非结构化数据:非结构化数据指的是数据结构不规则,没有统一的预定义数据模型,不方便用数据库二维逻辑表来表现的数据。非结构化数据包括图片、视频、音频、自然语言等,占所有数据总量的 80%。非结构化数据的处理可以通过各种人工智能(AI)或机器学习(ML)模型转化为向量数据后进行处理。
特征向量:向量又称为embedding vector,是指由 embedding 技术从离散变量(如图片、视频、音频、自然语言等各种非结构化数据)转变而来的连续向量。在数学表示上,向量是一个由浮点数或者二值型数据组成的 n 维数组。
通过现代的向量转化技术,比如各种人工智能(AI)或者机器学习(ML)模型,可以将非结构化数据抽象为 n 维特征向量空间的向量。这样就可以采用最近邻算法(ANN)计算非结构化数据之间的相似度。
向量相似度检索:相似度检索是指将目标对象与数据库中数据进行比对,并召回最相似的结果。同理,向量相似度检索返回的是最相似的向量数据。近似最近邻搜索(ANN)算法能够计算向量之间的距离,从而提升向量相似度检索的速度。如果两条向量十分相似,这就意味着他们所代表的源数据也十分相似。
Collection-集合:包含一组entity,可以等价于关系型数据库系统(RDBMS)中的表。
Entity-实体:包含一组 field。field 与实际对象相对应。field 可以是代表对象属性的结构化数据,也可以是代表对象特征的向量。primary key 是用于指代一个 entity 的唯一值。注意:你可以自定义primary key,否则 Milvus 将会自动生成primary key。目前Milvus 不支持primary key去重,因此有可能在一个collection内出现primary key相同的entity。
Field-字段:Entity 的组成部分。Field可以是结构化数据,例如数字和字符串,也可以是向量。注意:Milvus2.0现已支持标量字段过滤。并且,Milvus 2.0在一个集合中只支持一个主键字段。
Milvus与关系型数据库的对应关系如下:
Partition-分区:分区是集合(Collection)的一个分区。Milvus 支持将收集数据划分为物理存储上的多个部分。这个过程称为分区,每个分区可以包含多个段。
Segment-段:Milvus 在数据插入时,通过合并数据自动创建的数据文件。一个collection可以包含多个segment。一个segment可以包含多个entity。在搜索中,Milvus会搜索每个segment,并返回合并后的结果。
Sharding-分片:Shard是指将数据写入操作分散到不同节点上,使 Milvus 能充分利用集群的并行计算能力进行写入。默认情况下,单个Collection包含 2 个分片(Shard)。目前 Milvus 采用基于主键哈希的分片方式,未来将支持随机分片、自定义分片等更加灵活的分片方式。注意: 分区的意义在于通过划定分区减少数据读取,而分片的意义在于多台机器上并行写入操作。
索引:索引基于原始数据构建,可以提高对 collection 数据搜索的速度。Milvus 支持多种索引类型。为提高查询性能,你可以为每个向量字段指定一种索引类型。目前,一个向量字段仅支持一种索引类型。切换索引类型时,Milvus 自动删除之前的索引。相似性搜索引擎的工作原理是将输入的对象与数据库中的对象进行比较,找出与输入最相似的对象。索引是有效组织数据的过程,极大地加速了对大型数据集的查询,在相似性搜索的实现中起着重要作用。对一个大规模向量数据集创建索引后,查询可以被路由到最有可能包含与输入查询相似的向量的集群或数据子集。在实践中,这意味着要牺牲一定程度的准确性来加快对真正的大规模向量数据集的查询。
PChannel:PChannel 表示物理通道。每个 PChannel 对应一个日志存储主题。默认情况下,将分配一组 256 个 PChannels 来存储记录 Milvus 集群启动时数据插入、删除和更新的日志。
VChannel:VChannel 表示逻辑通道(虚拟通道)。每个集合将分配一组 VChannels,用于记录数据的插入、删除和更新。VChannels 在逻辑上是分开的,但在物理上共享资源。
Binlog:binlog 是一个二进制日志,或者是一个更小的段单位,记录和处理 Milvus 向量数据库中数据的更新和更改。 一个段的数据保存在多个二进制日志中。 Milvus 中的 binlog 分为三种:InsertBinlog、DeleteBinlog 和 DDLBinlog。
日志代理(Log broker):日志代理是一个支持回放的发布订阅系统。它负责流数据持久化、可靠异步查询的执行、事件通知和查询结果的返回。当工作节点从系统崩溃中恢复时,它还确保增量数据的完整性。
日志订阅者:日志订阅方通过订阅日志序列来更新本地数据,并以只读副本的形式提供服务。
日志序列(Log sequence):日志序列记录了在 Milvus 中更改集合状态的所有操作。
正则化:正则化是指转换嵌入(向量)以使其范数等于1的过程。 如果使用内积 (IP) 来计算embeddings相似度,则必须对所有embeddings进行正则化。 正则化后,内积等于余弦相似度。
Milvus文档地址:Milvus doc
整个系统分为四个层次:
各个层次相互独立,独立扩展和容灾。
接入层由一组无状态 proxy 组成,是整个系统的门面,对外提供用户连接的 endpoint。接入层负责验证客户端请求并减少返回结果。
协调服务是系统的大脑,负责向执行节点分配任务。它承担的任务包括集群拓扑节点管理、负载均衡、时间戳生成、数据声明和数据管理等。
协调服务共有四种角色:
执行节点是系统的四肢,负责完成协调服务下发的指令和 proxy 发起的数据操作语言(DML)命令。由于采取了存储计算分离,执行节点是无状态的,可以配合 Kubernetes 快速实现扩缩容和故障恢复。
执行节点分为三种角色:
存储服务是系统的骨骼,负责 Milvus 数据的持久化,分为元数据存储(meta store)、消息存储(log broker)和对象存储(object storage)三个部分。
元数据存储:负责存储元信息的快照,比如:集合 schema 信息、节点状态信息、消息消费的 checkpoint 等。元信息存储需要极高的可用性、强一致和事务支持,因此,etcd 是这个场景下的不二选择。除此之外,etcd 还承担了服务注册和健康检查的职责。
对象存储:负责存储日志的快照文件、标量/向量索引文件以及查询的中间处理结果。Milvus 采用 MinIO 作为对象存储,另外也支持部署于 AWS S3 和Azure Blob 这两大最广泛使用的低成本存储。但是,由于对象存储访问延迟较高,且需要按照查询计费,因此 Milvus 未来计划支持基于内存或 SSD 的缓存池,通过冷热分离的方式提升性能以降低成本。
消息存储:消息存储是一套支持回放的发布订阅系统,用于持久化流式写入的数据,以及可靠的异步执行查询、事件通知和结果返回。执行节点宕机恢复时,通过回放消息存储保证增量数据的完整性。
目前,分布式版Milvus依赖 Pulsar 作为消息存储,单机版Milvus依赖 RocksDB 作为消息存储。消息存储也可以替换为 Kafka、Pravega 等流式存储。
整个 Milvus 围绕日志为核心来设计,遵循日志即数据的准则,因此在 2.0 版本中没有维护物理上的表,而是通过日志持久化和日志快照来保证数据的可靠性。
日志系统作为系统的主干,承担了数据持久化和解耦的作用。通过日志的发布订阅机制,Milvus 将系统的读、写组件解耦。一个极致简化的模型如上图所示,整个系统主要由两个角色构成,分别是消息存储(log broker)(负责维护”日志序列“)与“日志订阅者”。其中的“日志序列”记录了所有改变库表状态的操作,“日志订阅者”通过订阅日志序列更新本地数据,以只读副本的方式提供服务。 发布订阅机制还为系统在变更数据捕获(CDC)和全面的分布式部署方面的可扩展性提供了空间。
Milvus 支持两种部署模式,单机模式(standalone)和分布式模式(cluster)。两种模式具备完全相同的能力,用户可以根据数据规模、访问量等因素选择适合自己的模式。Standalone 模式部署的 Milvus 暂时不支持在线升级为 cluster 模式。
单机版 Milvus包括三个组件:
分布式版 Milvus 由八个微服务组件和三个第三方依赖组成,每个微服务组件可使用 Kubernetes 独立部署。
微服务组件
第三方依赖
使用Milvus向量数据库,可以快速搭建符合自己场景需求的向量相似度检索系统。Milvus 的使用场景如下所示:
wget https://github.com/milvus-io/milvus/releases/download/v2.2.13/milvus-standalone-docker-compose.yml -O docker-compose.yml
sudo docker-compose up -d
sudo docker-compose ps
通过命令查看显示信息如下:
Name Command State Ports
--------------------------------------------------------------------------------------------------------------------
milvus-etcd etcd -advertise-client-url ... Up 2379/tcp, 2380/tcp
milvus-minio /usr/bin/docker-entrypoint ... Up (healthy) 9000/tcp
milvus-standalone /tini -- milvus run standalone Up 0.0.0.0:19530->19530/tcp, 0.0.0.0:9091->9091/tcp
验证连接:
docker port milvus-standalone 19530/tcp
停止Milvus
sudo docker-compose down
停止后删除数据
sudo rm -rf volumes
Attu地址:Attu
Milvus与Attu对应关系:
Milvus Version | Recommended Attu Image Version |
---|---|
v2.0.x | v2.0.5 |
v2.1.x | v2.1.5 |
v2.2.x | v2.2.6 |
执行命令:
docker run -p 8000:3000 -e MILVUS_URL={your machine IP}:19530 zilliz/attu:v2.2.6
启动docker后,在浏览器中访问“http://{your machine IP}:8000”,点击“Connect”进入Attu服务。连接方式用户名和密码。
安装pymilvus
pip install pymilvus==2.2.15
from pymilvus import connections, db
conn = connections.connect(host="192.168.1.156", port=19530)
database = db.create_database("sample_db")
切换和显示db
db.using_database("sample_db")
db.list_database()
from pymilvus import CollectionSchema, FieldSchema, DataType
from pymilvus import Collection, db, connections
conn = connections.connect(host="192.168.1.156", port=19530)
db.using_database("sample_db")
m_id = FieldSchema(name="m_id", dtype=DataType.INT64, is_primary=True,)
embeding = FieldSchema(name="embeding", dtype=DataType.FLOAT_VECTOR, dim=768,)
count = FieldSchema(name="count", dtype=DataType.INT64,)
desc = FieldSchema(name="desc", dtype=DataType.VARCHAR, max_length=256,)
schema = CollectionSchema(
fields=[m_id, embeding, desc, count],
description="Test embeding search",
enable_dynamic_field=True
)
collection_name = "word_vector"
collection = Collection(name=collection_name, schema=schema, using='default', shards_num=2)
通过Attu查看创建结果:
from pymilvus import Collection, utility, connections, db
conn = connections.connect(host="192.168.1.156", port=19530)
db.using_database("sample_db")
index_params = {
"metric_type": "IP",
"index_type": "IVF_FLAT",
"params": {"nlist": 1024}
}
collection = Collection("word_vector")
collection.create_index(
field_name="embeding",
index_params=index_params
)
utility.index_building_progress("word_vector")
通过Attu查看结果:
索引方式:
from pymilvus import Collection, db, connections
import numpy as np
conn = connections.connect(host="192.168.1.156", port=19530)
db.using_database("sample_db")
coll_name = 'word_vector'
mids, embedings, counts, descs = [], [], [], []
data_num = 100
for idx in range(0, data_num):
mids.append(idx)
embedings.append(np.random.normal(0, 0.1, 768).tolist())
descs.append(f'random num {idx}')
counts.append(idx)
collection = Collection(coll_name)
mr = collection.insert([mids, embedings, descs, counts])
print(mr)
运行结果:
(insert count: 100, delete count: 0, upsert count: 0, timestamp: 443639998144839682, success count: 100, err count: 0)
通过Attu查看:
from pymilvus import Collection, db, connections
import numpy as np
conn = connections.connect(host="192.168.1.156", port=19530)
db.using_database("sample_db")
coll_name = 'word_vector'
search_params = {
"metric_type": 'IP',
"offset": 0,
"ignore_growing": False,
"params": {"nprobe": 16}
}
collection = Collection(coll_name)
collection.load()
results = collection.search(
data=[np.random.normal(0, 0.1, 768).tolist()],
anns_field="embeding",
param=search_params,
limit=16,
expr=None,
# output_fields=['m_id', 'embeding', 'desc', 'count'],
output_fields=['m_id', 'desc', 'count'],
consistency_level="Strong"
)
collection.release()
print(results[0].ids)
print(results[0].distances)
hit = results[0][0]
print(hit.entity.get('desc'))
print(results)
运行结果如下:
[0, 93, 77, 61, 64, 79, 22, 43, 25, 35, 83, 49, 51, 84, 75, 36]
[0.7047597169876099, 0.5948767066001892, 0.54373699426651, 0.5294350981712341, 0.5216281414031982, 0.5035749673843384, 0.41662347316741943, 0.4026581346988678, 0.40143388509750366, 0.3841533362865448, 0.371593713760376, 0.35352253913879395, 0.3377170264720917, 0.33591681718826294, 0.32786160707473755, 0.3214406967163086]
random num 0
['["id: 0, distance: 0.7047597169876099, entity: {\'m_id\': 0, \'desc\': \'random num 0\', \'count\': 0}", "id: 93, distance: 0.5948767066001892, entity: {\'m_id\': 93, \'desc\': \'random num 93\', \'count\': 93}", "id: 77, distance: 0.54373699426651, entity: {\'m_id\': 77, \'desc\': \'random num 77\', \'count\': 77}", "id: 61, distance: 0.5294350981712341, entity: {\'m_id\': 61, \'desc\': \'random num 61\', \'count\': 61}", "id: 64, distance: 0.5216281414031982, entity: {\'m_id\': 64, \'desc\': \'random num 64\', \'count\': 64}", "id: 79, distance: 0.5035749673843384, entity: {\'m_id\': 79, \'desc\': \'random num 79\', \'count\': 79}", "id: 22, distance: 0.41662347316741943, entity: {\'m_id\': 22, \'desc\': \'random num 22\', \'count\': 22}", "id: 43, distance: 0.4026581346988678, entity: {\'m_id\': 43, \'desc\': \'random num 43\', \'count\': 43}", "id: 25, distance: 0.40143388509750366, entity: {\'m_id\': 25, \'desc\': \'random num 25\', \'count\': 25}", "id: 35, distance: 0.3841533362865448, entity: {\'m_id\': 35, \'desc\': \'random num 35\', \'count\': 35}"]']
from pymilvus import Collection, db, connections
conn = connections.connect(host="192.168.1.156", port=19530)
db.using_database("sample_db")
coll_name = 'word_vector'
collection = Collection(coll_name)
ids = [str(idx) for idx in range(10)]
temp_str = ', '.join(ids)
query_expr = f'm_id in [{temp_str}]'
result = collection.delete(query_expr)
print(result)
运行结果显示:
(insert count: 0, delete count: 10, upsert count: 0, timestamp: 443640854673883146, success count: 0, err count: 0)
为了检索提供性能,Milvus 中引入 bitset,当调用删除数据时,Milvus对数据进行软删除。软删除的向量仍然存在于数据库中,但在向量相似性搜索或查询期间不会被计算。位集中的每个位对应于一个索引向量。如果一个向量在位集中被标记为 1
,则意味着该向量被软删除,并且在向量搜索期间不会涉及该向量。
向量数据库技术是为AI而生的创新,它充分发挥了向量表示的优势,为多模态、高维、复杂数据的存储、检索和分析提供了高效解决方案。随着AI应用的不断发展,向量数据库将成为推动AI加速创新的重要工具之一,为各行各业带来更高效、智能的解决方案。无疑,在这个AI驱动的时代,向量数据库必将继续发挥其重要作用。