返回向量搜索手册

向量数据库简介

Sabrina Aquino

·

2024 年 10 月 9 日

An Introduction to Vector Databases

什么是向量数据库?

vector-database-architecture

我们每天生成的数百万太字节数据中,大多数是非结构化数据。想想你随手拍的食物照片、工作中分享的 PDF 文件,或者你保存下来可能永远不会听的播客。它们都无法整齐地放入行和列中。

非结构化数据缺乏严格的格式或模式,这使得传统数据库难以管理。然而,这种非结构化数据在 AI机器学习现代搜索引擎方面蕴含着巨大的潜力。

一个向量数据库是一个专门设计用于高效处理高维向量数据的系统。它擅长对这些数据进行索引、查询和检索,从而实现传统数据库难以轻松执行的高级分析和相似性搜索。

传统数据库面临的挑战

传统的 OLTPOLAP 数据库几十年来一直是数据存储的支柱。它们非常擅长管理具有明确模式的结构化数据,例如 姓名地址电话号码购买历史

Structure of OLTP and OLAP databases

但是,当数据不容易分类时,例如 PDF 文件中的内容,事情就开始变得复杂了。

你当然可以将 PDF 文件作为原始数据存储,可能还会附带一些元数据。然而,数据库仍然无法理解文档内部的内容、对其进行分类,甚至无法搜索其中包含的信息。

此外,这不仅仅适用于 PDF 文档。想想你每天生成的大量文本、音频和图像数据。如果数据库无法理解这些数据的含义,你如何搜索或找到数据中的关系?

Structure of a Vector Database

向量数据库通过将非结构化数据表示为向量,使你能够理解数据的上下文概念相似性,从而实现基于数据相似性的高级分析和检索。

何时使用向量数据库

不确定应该使用向量数据库还是传统数据库?这张图表或许能帮到你。

特性OLTP 数据库OLAP 数据库向量数据库
数据结构行和列行和列向量
数据类型结构化结构化/部分非结构化非结构化
查询方法基于 SQL(事务查询)基于 SQL(聚合、分析查询)向量搜索(基于相似性)
存储重点基于模式,针对更新优化基于模式,针对读取优化上下文和语义
性能针对高并发事务优化针对复杂分析查询优化针对非结构化数据检索优化
用例库存、订单处理、CRM商业智能、数据仓库相似性搜索、推荐、RAG、异常检测等

什么是向量?

vector-database-vector

当机器需要处理非结构化数据(图像、文本片段或音频文件)时,首先必须将数据转换成可以处理的格式:向量

向量是一种数据的数值表示,可以捕获数据的上下文语义

处理非结构化数据时,传统数据库难以理解其含义。然而,向量可以将数据转换为机器可以处理的内容。例如,从文本生成的向量可以表示词语之间的关系和含义,使得机器能够比较和理解它们的上下文。

向量数据库中定义向量的三个关键要素是:ID维度有效载荷(payload)。这些组成部分协同工作,有效表示系统中的向量。它们共同构成一个,这是向量数据库中存储和检索的核心数据单元。

Representation of a Point in Qdrant

这些部分中的每一个都在向量如何存储、检索和解释中扮演着重要角色。让我们来看看。

1. ID:您的向量的唯一标识符

就像在关系型数据库中一样,向量数据库中的每个向量都有一个唯一的 ID。可以把它想象成您的向量的名牌,一个主键,确保以后可以轻松找到该向量。当向量添加到数据库时,会自动创建 ID。

虽然 ID 本身不参与相似性搜索(相似性搜索是基于向量的数值数据进行操作的),但它对于将向量与其对应的“现实世界”数据(无论是文档、图像还是音频文件)关联起来至关重要。

执行搜索并找到相似向量后,会返回它们的 ID。然后可以使用这些 ID 来获取与结果相关的额外详情或元数据

2. 维度:数据的核心表示

每个向量的核心是一组数字,这些数字共同构成了数据在多维空间中的表示。

从文本到向量:它是如何工作的?

这些数字由嵌入模型(例如深度学习算法)生成,捕获数据中的关键模式或关系。这就是为什么在指代这些模型的输出时,嵌入一词经常与向量互换使用。

例如,为了表示文本数据,嵌入会封装语言的细微之处,例如其维度内的语义和上下文。

Creation of a vector based on a sentence with an embedding model

因此,当比较两个相似的句子时,它们的嵌入将非常相似,因为它们具有相似的语言元素

Comparison of the embeddings of 2 similar sentences

这就是嵌入的妙处。数据的复杂性被提炼成可以在多维空间中进行比较的东西。

3. 有效载荷(Payload):用元数据添加上下文

有时,为了充分理解或优化搜索,您需要的不仅仅是数字。维度捕获了数据的本质,而有效载荷则包含结构化信息的元数据

它可能是文本数据,例如描述、标签、类别,也可能是数值,例如日期或价格。当您想基于未直接编码在向量中的标准来过滤或排序搜索结果时,这些额外信息至关重要。

当您需要应用额外的过滤器排序标准时,这些元数据是无价的。

例如,如果您正在搜索一张狗的照片,向量会帮助数据库找到视觉上相似的图像。但假设您只想看到过去一年内拍摄的图像,或者标记为“度假”的图像。

Filtering Example

有效载荷可以通过忽略与您的查询向量过滤条件不匹配的向量来帮助您缩小结果范围。如果您想全面了解 Qdrant 中的过滤如何工作,请查阅我们的过滤完整指南。

向量数据库的架构

向量数据库由多个不同的实体和关系组成。让我们了解一下这里发生的情况:向量数据库架构图

集合

一个集合本质上是一组逻辑上基于相似性或特定任务分组的向量(或“”)。集合内的每个向量具有相同的维度,并且可以使用单一的度量标准进行比较。除非必要,否则避免创建多个集合;相反,可以考虑使用分片等技术实现跨节点扩展,或使用多租户在同一基础设施中处理不同的用例。

距离度量

这些度量标准定义了如何计算向量之间的相似性。距离度量标准的选择在创建集合时确定,正确的选择取决于您正在处理的数据类型以及向量的创建方式。以下是三种最常见的距离度量标准

  • 欧几里得距离 (Euclidean Distance): 直线路径。就像测量空间中两点之间的物理距离一样。当实际距离(如空间数据)很重要时,选择这种方法。

  • 余弦相似度 (Cosine Similarity): 这种方法关注的是角度,而不是长度。它测量两个向量指向同一方向的程度,因此非常适合用于文本或文档,因为这时您更关心含义而非大小。例如,判断两件事物是相似相反还是不相关

Cosine Similarity Example
  • 点积 (Dot Product): 这种方法衡量两个向量对齐的程度。它在推荐系统中很受欢迎,因为您关心两件事物“一致”的程度。

基于 RAM 和内存映射 (Memmap) 的存储

默认情况下,Qdrant 将向量存储在 RAM 中,对于能够轻松容纳在内存中的数据集,提供极快的访问速度。但是当您的数据集超出 RAM 容量时,Qdrant 提供 Memmap 作为替代方案。

Memmap 允许您将向量存储在磁盘上,如果您有足够的 RAM,仍然可以通过将数据直接映射到内存来高效地访问它们。要启用它,只需在创建集合时设置 "on_disk": true

from qdrant_client import QdrantClient, models

client = QdrantClient(url='http://localhost:6333')

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(
        size=768, distance=models.Distance.COSINE, on_disk=True
    ),
)

对于其他配置,例如 hnsw_config.on_diskmemmap_threshold,请参阅 Qdrant 关于存储的文档。

SDK

Qdrant 提供一系列 SDK。您可以使用最熟悉的编程语言进行编码,无论是 PythonGoRustJavascript/TypescriptC# 还是 Java

向量数据库的核心功能

vector-database-functions

想到传统数据库,操作都很熟悉:您进行创建读取更新删除记录。这些是基本操作。猜猜怎么着?在很多方面,向量数据库的工作方式也类似,但这些操作被转换以适应向量的复杂性。

1. 索引:HNSW 索引和将数据发送到 Qdrant

对向量进行索引就像在传统数据库中创建条目一样。但对于向量数据库来说,这一步非常重要。向量需要以一种以后容易搜索的方式进行索引。

HNSW (Hierarchical Navigable Small World,分层可导航小世界) 是一种强大的索引算法,大多数向量数据库都依赖它来组织向量,以实现快速高效的搜索。

它构建一个多层图,其中每个向量是一个节点,连接代表相似性。较高层连接广泛相似的向量,而较低层连接密切相关的向量,使得搜索随着深入而逐步细化。

Indexing Data with the HNSW algorithm

执行搜索时,HNSW 从顶层开始,通过在层之间跳转快速缩小搜索范围。随着深入,它只关注相关的向量,并在每一步中细化搜索。

1.1 有效载荷索引

在 Qdrant 中,索引是模块化的。您可以独立配置向量和有效载荷的索引。有效载荷索引负责优化基于元数据的过滤。每个有效载荷索引都是针对特定字段构建的,允许您根据特定条件快速过滤向量。

Searching Data with the HNSW algorithm

您需要为您想要搜索的每个字段构建有效载荷索引。这里的巧妙之处在于它们的结合:HNSW 找到相似的向量,而有效载荷索引确保只有符合您条件的向量被返回。详细了解 Qdrant 的可过滤 HNSW 以及它为何如此构建。

全文搜索与基于向量的搜索结合,为您带来更多灵活性。您可以在同一查询中同时搜索概念相似的文档,同时确保存在特定关键词。

相似性搜索允许您按含义进行搜索。通过这种方式,您可以进行搜索,例如查找能唤起相同情绪的相似歌曲,找到符合您艺术视野的图像,甚至探索文本中的情感模式。

Similar words grouped together

其工作方式是,当用户查询数据库时,该查询也会被转换成一个向量。算法会快速识别图表中可能包含最接近查询向量的向量区域。

Approximate Nearest Neighbors (ANN) Search Graph

然后搜索会逐步向下移动,缩小范围,找到更密切相关和更相关的向量。一旦在最底层识别出最接近的向量,这些点就会转换回实际数据,代表您的得分最高的文档

以下是此过程的高级概述

Vector Database Searching Functionality

3. 更新向量:实时和批量调整

数据不是静态的,向量也不是。保持向量最新对于在搜索中保持相关性至关重要。

向量更新不总是需要立即发生,但当需要时,Qdrant 可以通过简单的 API 调用高效地处理实时修改

client.upsert(
    collection_name='product_collection',
    points=[PointStruct(id=product_id, vector=new_vector, payload=new_payload)]
)

对于大规模更改,例如模型更新后重新索引向量,批量更新允许您在一个操作中更新多个向量,而不会影响搜索性能

batch_of_updates = [
    PointStruct(id=product_id_1, vector=updated_vector_1, payload=new_payload_1),
    PointStruct(id=product_id_2, vector=updated_vector_2, payload=new_payload_2),
    # Add more points...
]

client.upsert(
    collection_name='product_collection',
    points=batch_of_updates
)

4. 删除向量:管理过期和重复数据

高效的向量管理是保持搜索准确和数据库精简的关键。删除代表过期或不相关数据的向量,例如过期产品、旧新闻文章或归档的个人资料,有助于保持性能和相关性。

在 Qdrant 中,删除向量非常简单,只需指定向量 ID 即可

client.delete(
    collection_name='data_collection',
    points_selector=[point_id_1, point_id_2]
)

您可以使用删除功能移除过期数据、清理重复项,并通过在设定时间后自动删除向量来管理向量的生命周期,从而保持数据集的相关性和集中性。

稠密向量 vs 稀疏向量

vector-database-dense-sparse

现在您了解了什么是向量以及它们是如何创建的,让我们进一步了解您可以使用的两种可能的向量类型:稠密向量稀疏向量。两者之间的主要区别在于

1. 稠密向量

稠密向量字面上来说,信息非常“稠密”。向量中的每个元素都对数据的语义含义关系细微之处有所贡献。这句话的稠密向量表示可能看起来像这样

Representation of a Dense Vector

每个数字都有权重。它们共同传达句子的整体含义,并且更适合识别上下文相似的项目,即使词语并不完全匹配。

2. 稀疏向量

稀疏向量的工作方式不同。它们只关注本质。在大多数稀疏向量中,大量元素为零。当某个特征或标记存在时,它会被标记——否则为零。

在图中,您可以看到句子 “I love Vector Similarity,” 通过分词分解为 “i,” “love,” “vector” 等标记。每个标记从一个大型词汇表中被分配一个唯一的 ID。例如,“i” 变成 193,而 “vector” 变成 15012

How Sparse Vectors are Created

稀疏向量用于精确匹配和基于特定标记的识别。右侧的值,例如 193: 0.049182: 0.12,是每个标记的得分或权重,显示了每个标记在上下文中的相关性或重要性。最终结果是一个稀疏向量

{
   193: 0.04,
   9182: 0.12,
   15012: 0.73,
   6731: 0.69,
   454: 0.21
}

向量空间中的其他所有内容都被假定为零。

稀疏向量非常适合关键词搜索元数据过滤等任务,您需要在这些任务中检查特定标记的存在,而无需捕获完整的含义或上下文。它们适用于数据本身的精确匹配,而不是依赖外部元数据,后者由有效载荷过滤处理。

vector-database-get-started

有时仅有上下文是不够的。有时您还需要精确性。当您需要根据数据的上下文或含义检索结果时,稠密向量非常出色。当您还需要关键词或特定属性匹配时,稀疏向量很有用。

通过混合搜索,您不必二选一,而是可以同时使用它们,从而获得更相关和更过滤的搜索结果。

为了实现这种平衡,Qdrant 使用归一化融合技术来整合多种搜索方法的结果。一种常见的方法是倒数排序融合 (Reciprocal Rank Fusion, RRF),它将不同方法的结果合并,对被两种方法都高度排名的项目给予更高的重要性。这确保了最佳候选者,无论它们是通过稠密向量还是稀疏向量识别的,都能出现在结果的顶部。

Qdrant 通过归一化融合过程结合稠密向量和稀疏向量的结果。

Hybrid Search API - How it works

如何在 Qdrant 中使用混合搜索

Qdrant 通过其 Query API 使实现混合搜索变得容易。以下是如何在您的项目中实现它

Hybrid Query Example

混合查询示例: 假设一位研究人员正在查找有关 NLP 的论文,但该论文的内容必须明确提及“transformers”

search_query = {
    "vector": query_vector,  # Dense vector for semantic search
    "filter": {  # Filtering for specific terms
        "must": [
            {"key": "text", "match": "transformers"}  # Exact keyword match in the paper
        ]
    }
}

在此查询中,稠密向量搜索会找到与 NLP 这一宽泛主题相关的论文,而稀疏向量过滤确保这些论文明确提及“transformers”。

这只是一个简单的例子,您还可以做更多的事情。请参阅我们的完整混合搜索文章指南,了解其背后的工作原理以及构建混合搜索系统的所有可能性。

量化:提速 40 倍的结果

vector-database-architecture

随着您的向量数据集变得越来越大,对其进行搜索的计算需求也随之增加。

量化后的向量更小,更易于比较。使用像二值量化 (Binary Quantization) 这样的方法,您可以看到搜索速度提高高达 40 倍,同时内存使用量减少 32 倍。这些改进在处理大型数据集或需要低延迟结果时可能是决定性的。

它的工作原理是将通常每维使用 4 字节的高维向量转换为二进制表示,每维仅使用 1 比特。大于零的值变为“1”,其余所有值变为“0”。

 Binary Quantization example

量化会降低数据精度,是的,这确实会导致一定的准确性损失。然而,对于二值量化,OpenAI 嵌入仅以 5% 的准确性损失为代价实现了这种性能提升。如果您应用像过采样 (oversampling)重评分 (rescoring) 这样的技术,这种损失可以进一步降低。

然而,二值量化并不是唯一可用的选项。像标量量化 (Scalar Quantization)乘积量化 (Product Quantization) 也是优化向量压缩时流行的替代方案。

您可以在创建新集合时使用 quantization_config 参数设置您选择的量化方法

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(
        size=1536,  
        distance=models.Distance.COSINE
    ),

    # Choose your preferred quantization method
    quantization_config=models.BinaryQuantization(  
        binary=models.BinaryQuantizationConfig(
            always_ram=True,  # Store the quantized vectors in RAM for faster access
        ),
    ),
)

您可以通过在 vectors_config 中设置 on_disk=True 将原始向量存储在磁盘上以节省 RAM 空间,同时将量化后的向量保存在 RAM 中以加快访问速度

我们建议您查阅我们的向量量化指南,以全面了解各种方法以及针对您的特定用例优化性能的技巧。

分布式部署

考虑扩展时,需要考虑的关键因素是容错性负载均衡可用性。单个节点无论多么强大,能力总是有限的。最终,您需要将工作负载分散到多台机器上,以确保系统保持快速和稳定。

分片:跨节点分发数据

在分布式 Qdrant 集群中,数据被分割成更小的单元,称为分片 (shards),这些分片分布在不同的节点上。这有助于平衡负载,并确保查询可以并行处理。

每个集合(一组相关的数据点)可以被分割成不重叠的子集,然后由不同的节点管理。

 Distributed vector database with sharding and Raft consensus

Raft 一致性算法确保所有节点保持同步,并拥有数据的一致视图。每个节点都知道每个分片在哪里,Raft 确保所有节点同步。如果一个节点发生故障,其他节点知道丢失的数据位于何处,并且可以接管。

默认情况下,您的 Qdrant 系统中的分片数量与集群中的节点数量匹配。但如果您需要更多控制权,可以在创建集合时手动选择 shard_number

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
    shard_number=4, # Custom number of shards
)

分片主要有两种类型

  1. 自动分片:点(向量)使用一致性哈希自动分布到分片中。每个分片包含数据的不重叠子集。
  2. 用户定义分片:指定如何分布点,从而对数据组织拥有更多控制权,尤其适用于多租户等用例,其中每个租户(用户、客户端或组织)都有自己的隔离数据。

每个分片被划分为段 (segments)。它们是分片内更小的存储单元,存储一部分向量及其相关的有效载荷(元数据)。执行查询时,它只针对相关的段,并并行处理它们。

Segments act as smaller storage units within a shard

副本:高可用性和数据完整性

您不希望单点故障导致系统崩溃,对吗?副本在不同节点上保留相同数据的多个副本,以确保高可用性

在 Qdrant 中,副本集 (Replica Sets) 管理跨不同节点的分片副本。如果一个副本变得不可用,其他副本会接管并保持系统运行。数据是本地还是远程主要取决于您如何配置集群。

 Replica Set and Replication diagram

发出查询时,如果相关数据存储在本地,则由本地分片处理操作。如果数据位于远程分片上,则通过 gRPC 检索。

您可以使用 replication_factor 控制所需的副本数量。例如,创建一个包含 4 个分片且副本数为 2 的集合,将在集群中产生 8 个物理分片

client.create_collection(
    collection_name="{collection_name}",
    vectors_config=models.VectorParams(size=300, distance=models.Distance.COSINE),
    shard_number=4,
    replication_factor=2, 
)

我们建议同时使用分片和副本,以便您的数据既分散在节点上,又为实现可用性而进行复制。

有关用户定义分片、节点故障恢复一致性保证等功能的更多详细信息,请参阅我们的分布式部署指南。

多租户:多租户架构的数据隔离

vector-database-get-started

分片有效地将数据分布到节点上,而副本则保证了冗余和容错。但是,当您有多个客户端或用户组,并且需要在同一基础设施内保持其数据隔离时,会发生什么呢?

多租户允许您在单个集群内隔离不同租户(用户、客户端或组织)的数据。您无需为 租户 1租户 2 创建单独的集合,而是将它们的数据存储在同一个集合中,但为每个向量标记一个 group_id,以识别它属于哪个租户。

Multitenancy dividing data between 2 tenants

在后端,Qdrant 可以将 租户 1 的数据存储在位于加拿大的分片 1 中(可能出于 GDPR 等合规原因),而将 租户 2 的数据存储在位于德国的分片 2 中。数据将物理隔离,但仍在同一基础设施内。

要实现这一点,您需要在 upsert 操作期间为每个向量标记一个租户特定的 group_id

client.upsert(
    collection_name="tenant_data",
    points=[models.PointStruct(
        id=2, 
        payload={"group_id": "tenant_1"}, 
        vector=[0.1, 0.9, 0.1]
    )],
    shard_key_selector="canada"
)

每个租户的数据保持隔离,同时仍然受益于共享基础设施。这优化了数据隐私、符合本地法规和可扩展性,而无需为每个租户创建过多的集合或维护单独的集群。

如果您想了解更多关于在 Qdrant 中使用多租户设置的信息,您可以查阅我们的多租户和自定义分片专题指南。

数据安全和访问控制

向量数据库中一个常见的安全风险是存在嵌入反演攻击的可能性,攻击者可以从嵌入中重建原始数据。在将向量数据库投入生产之前,您可以使用多层保护措施来确保您的实例安全,这非常重要。

对于更简单的用例,为了快速实现安全,您可以使用 API 密钥认证。要启用它,请在配置或环境变量中设置 API 密钥。

service:
  api_key: your_secret_api_key_here
  enable_tls: true  # Make sure to enable TLS to protect the API key from being exposed

设置完成后,请记住在您的所有请求中包含 API 密钥

from qdrant_client import QdrantClient

client = QdrantClient(
    url="https://localhost:6333",
    api_key="your_secret_api_key_here"
)

在更高级的设置中,Qdrant 使用 JWT (JSON Web Tokens) 来实施基于角色的访问控制 (Role-Based Access Control, RBAC)

RBAC 定义角色并分配权限,而 JWT 安全地将这些角色编码到令牌中。每个请求都会根据用户的 JWT 进行验证,确保他们只能根据分配的权限访问或修改数据。

您可以通过 Qdrant Web UI 轻松设置访问令牌并安全访问敏感数据:

Qdrant Web UI for generating a new access token.

默认情况下,Qdrant 实例是不安全的,因此在投入生产之前配置安全措施非常重要。要了解有关如何为您的 Qdrant 实例配置安全性以及其他高级选项的更多信息,请查阅Qdrant 官方安全文档。

是时候进行实验了

正如我们在本文中看到的,向量数据库绝不仅仅是我们传统意义上的数据库。它开启了一个充满可能性的世界,从高级相似性搜索到允许结合上下文和精确性进行内容检索的混合搜索。

但学习的最佳方式莫过于实践。尝试构建一个语义搜索引擎,或者从零开始实验部署一个混合搜索服务。您会发现利用向量的方式无穷无尽。

用例工作原理示例
相似性搜索使用向量距离查找相似数据点查找相似产品图像、基于主题检索文档、发现相关话题
异常检测基于向量空间中的偏差识别异常值检测银行中的异常用户行为、发现不规则模式
推荐系统使用向量嵌入学习和建模用户偏好个性化电影或音乐推荐、电商产品建议
RAG(检索增强生成)将向量搜索与大型语言模型 (LLM) 结合,以获得具有上下文相关性的答案客户支持、自动生成文档摘要、研究报告
多模态搜索在一次查询中搜索不同类型的数据,如文本、图像和音频。搜索包含描述和图像的产品,基于音频或文本检索图像
语音和音频识别使用向量表示识别和检索音频内容语音转文本、语音控制智能设备、识别和分类声音
知识图谱增强使用向量将非结构化数据链接到知识图谱中的概念将研究论文链接到相关研究,将客户评论连接到产品特性,按创新趋势组织专利

您还可以观看我们的视频教程,并开始使用 Qdrant 从示例数据集中生成语义搜索结果和推荐。

呼!希望您觉得这里的一些概念有用。如果您有任何问题,请随时在我们的Discord 社区中提出,我们的团队将非常乐意帮助您!

记住,不要在向量空间中迷失!🚀

此页面有用吗?

感谢您的反馈!🙏

很抱歉听到这个。😔 您可以在 GitHub 上编辑此页面,或者创建一个 GitHub issue。