我的Milvus向量数据库实践之路
最近在研究AI应用开发时,我发现向量数据库是一个绕不开的话题。作为神经网络模型的输出格式,向量能够高效地对信息进行编码,在知识库、语义搜索、RAG(检索增强生成)等AI应用中扮演着核心角色。
经过一番调研,我选择了Milvus作为我的向量数据库解决方案。它不仅功能强大,而且有一个轻量级的Milvus Lite版本,非常适合快速上手和原型开发。
环境准备与安装
开始之前,确保你的Python环境是3.8+版本。安装过程非常简单,一条命令就搞定:
$ pip install -U pymilvus
这个包不仅包含了Python客户端库,还内置了Milvus Lite,让我们可以在本地快速搭建向量数据库环境。
创建本地向量数据库
创建一个本地Milvus向量数据库简单得让人惊喜,只需要实例化一个MilvusClient,并指定一个数据文件名:
from pymilvus import MilvusClient
client = MilvusClient("milvus_demo.db")
就这样,我们的向量数据库就创建好了!所有数据都会存储在"milvus_demo.db"这个文件中。
Collections的概念与创建
在Milvus中,Collections是存储向量及其相关元数据的容器,你可以把它理解为传统SQL数据库中的表格。创建Collections时,我们需要定义Schema和索引参数来配置向量的规格,比如维度、索引类型和距离度量方式。
虽然Milvus有很多高级配置选项来优化搜索性能,但对于初学者来说,我们先关注基础功能,使用默认设置就足够了。最基本的,我们只需要设置Collections的名称和向量字段的维度:
if client.has_collection(collection_name="demo_collection"):
client.drop_collection(collection_name="demo_collection")
client.create_collection(
collection_name="demo_collection",
dimension=768, # 我们将使用768维的向量
)
在这个配置中:
- 主键和向量字段使用默认名称(“id"和"vector”)
- 距离度量方式设置为默认的COSINE(余弦相似度)
- 主键字段接受整数,不使用自动递增功能
数据准备:从文本到向量
在这个实践中,我想实现文本的语义搜索功能。这就需要将文本转换为向量,这个过程叫做"嵌入"(Embedding)。
安装模型库
首先安装模型库,它包含了PyTorch等机器学习工具:
$ pip install "pymilvus[model]"
注意:如果你的环境之前没有安装过PyTorch,这个下载过程可能需要一些时间。
生成文本向量
使用Milvus提供的默认嵌入模型来生成向量。数据需要以字典列表的形式准备,每个字典代表一条数据记录(在Milvus中称为"实体"):
from pymilvus import model
# 如果连接 https://huggingface.co/ 失败,可以取消下面两行的注释
# import os
# os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'
# 这会下载一个小型嵌入模型 "paraphrase-albert-small-v2" (~50MB)
embedding_fn = model.DefaultEmbeddingFunction()
# 准备要搜索的文本数据
docs = [
"人工智能作为一门学科成立于1956年。",
"艾伦·图灵是第一个在AI领域进行实质性研究的人。",
"图灵出生于伦敦的梅达维尔,在英格兰南部长大。",
]
vectors = embedding_fn.encode_documents(docs)
# 输出向量有768个维度,与我们刚创建的collection匹配
print("维度:", embedding_fn.dim, vectors[0].shape) # 维度: 768 (768,)
# 每个实体包含id、向量表示、原始文本,以及一个用于演示元数据过滤的主题标签
data = [
{"id": i, "vector": vectors[i], "text": docs[i], "subject": "history"}
for i in range(len(vectors))
]
print("数据包含", len(data), "个实体,每个实体的字段: ", data[0].keys())
print("向量维度:", len(data[0]["vector"]))
输出结果:
维度: 768 (768,)
数据包含 3 个实体,每个实体的字段: dict_keys(['id', 'vector', 'text', 'subject'])
向量维度: 768
数据插入与存储
准备好数据后,我们就可以将其插入到Collections中了:
res = client.insert(collection_name="demo_collection", data=data)
print(res)
输出结果:
{'insert_count': 3, 'ids': [0, 1, 2], 'cost': 0}
这个结果告诉我们成功插入了3条记录,分配的ID分别是0、1、2,操作耗时为0。
语义搜索的魅力
现在到了最激动人心的部分——语义搜索!我们可以通过将搜索查询文本转换为向量,然后在Milvus中进行向量相似性搜索来实现语义搜索。
基础向量搜索
Milvus支持同时处理一个或多个向量搜索请求。让我们试试搜索"Who is Alan Turing?":
query_vectors = embedding_fn.encode_queries(["谁是艾伦·图灵?"])
# 如果你没有嵌入函数,可以使用随机向量来完成演示:
# query_vectors = [ [ random.uniform(-1, 1) for _ in range(768) ] ]
res = client.search(
collection_name="demo_collection", # 目标collection
data=query_vectors, # 查询向量
limit=2, # 返回的实体数量
output_fields=["text", "subject"], # 指定要返回的字段
)
print(res)
输出结果:
data: ["[{'id': 2, 'distance': 0.5859944820404053, 'entity': {'text': '图灵出生于伦敦的梅达维尔,在英格兰南部长大。', 'subject': 'history'}}, {'id': 1, 'distance': 0.5118255615234375, 'entity': {'text': '艾伦·图灵是第一个在AI领域进行实质性研究的人。', 'subject': 'history'}}]"] , extra_info: {'cost': 0}
这个结果很有意思!虽然我们的查询是"谁是艾伦·图灵?",但系统智能地返回了与图灵相关的两条记录,并且按照相似度排序。距离值越小,表示相似度越高。
带元数据过滤的高级搜索
Milvus的一个强大功能是可以在进行向量搜索的同时考虑元数据(在Milvus中称为"标量"字段)。这通过指定过滤表达式来实现。
让我们先添加一些生物学相关的数据:
# 插入另一个主题的更多文档
docs = [
"机器学习已被用于药物设计。",
"AI算法的计算合成预测分子特性。",
"DDR1参与癌症和纤维化。",
]
vectors = embedding_fn.encode_documents(docs)
data = [
{"id": 3 + i, "vector": vectors[i], "text": docs[i], "subject": "biology"}
for i in range(len(vectors))
]
client.insert(collection_name="demo_collection", data=data)
# 这将排除"history"主题中的任何文本,即使它们与查询向量很接近
res = client.search(
collection_name="demo_collection",
data=embedding_fn.encode_queries(["告诉我AI相关的信息"]),
filter="subject == 'biology'",
limit=2,
output_fields=["text", "subject"],
)
print(res)
输出结果:
data: ["[{'id': 4, 'distance': 0.27030569314956665, 'entity': {'text': 'AI算法的计算合成预测分子特性。', 'subject': 'biology'}}, {'id': 3, 'distance': 0.16425910592079163, 'entity': {'text': '机器学习已被用于药物设计。', 'subject': 'biology'}}]"] , extra_info: {'cost': 0}
这个例子展示了元数据过滤的威力:即使历史主题中有与AI相关的内容,但由于我们设置了filter="subject == 'biology'",系统只返回了生物学主题下的AI相关内容。
性能优化建议
默认情况下,标量字段不会建立索引。如果你需要在大型数据集中频繁进行元数据过滤搜索,建议使用固定Schema并开启索引来提高搜索性能。
Schema的深入理解
在Milvus中,Schema定义了数据的结构,包括:
字段(Fields):数据包含哪些属性。在我们的例子中,
id、vector、text和subject都是字段。数据类型(Data Types):每个字段存储什么类型的数据:
id:通常是整数(int64)vector:向量(float_vector)text:字符串(varchar)subject:字符串(varchar)
主键(Primary Key):用于唯一标识每一条记录的字段(通常是
id)。是否是向量字段(Is Vector Field):标记哪个字段是用于向量搜索的。
是否开启索引(Enable Indexing):对于标量字段(非向量字段),你可以选择是否为它们创建索引。创建索引可以显著提高过滤查询(例如
subject == 'biology')的性能,尤其是在数据量很大时。
除了向量搜索,还可以执行其他类型的搜索:
查询
query()是一种操作符,用于检索与某个条件(如过滤表达式或与某些 id 匹配)相匹配的所有实体。
例如,检索标量字段具有特定值的所有实体:
res = client.query(
collection_name="demo_collection",
filter="subject == 'history'",
output_fields=["text", "subject"],
)
通过主键直接检索实体
res = client.query(
collection_name="demo_collection",
ids=[0, 2],
output_fields=["vector", "text", "subject"],
)
删除实体
如果想清除数据,可以删除指定主键的实体,或删除与特定过滤表达式匹配的所有实体。
# Delete entities by primary key
res = client.delete(collection_name="demo_collection", ids=[0, 2])
print(res)
# Delete entities by a filter expression
res = client.delete(
collection_name="demo_collection",
filter="subject == 'biology'",
)
print(res)
[]
[]
加载现有数据
由于 Milvus Lite 的所有数据都存储在本地文件中,因此即使在程序终止后,你也可以通过创建一个带有现有文件的MilvusClient ,将所有数据加载到内存中。例如,这将恢复 “milvus_demo.db “文件中的 Collections,并继续向其中写入数据。
from pymilvus import MilvusClient
client = MilvusClient("milvus_demo.db")
删除 Collections
如果想删除 Collections 中的所有数据,可以通过以下方法删除 Collections
# Drop collection
client.drop_collection(collection_name="demo_collection")
搜索数据模型设计
信息检索系统(又称搜索引擎)是各种人工智能应用(如检索增强生成(RAG)、可视化搜索和产品推荐)的关键。这些系统的核心是精心设计的数据模型,用于组织、索引和检索信息。
Milvus 允许您通过 Collect schema 指定搜索数据模型,组织非结构化数据、它们的密集或稀疏向量表示以及结构化元数据。
数据模型
搜索系统的数据模型设计包括分析业务需求,并将信息抽象为模式表达的数据模型。定义明确的 Schema 对于使数据模型与业务目标保持一致、确保数据一致性和服务质量非常重要。
分析业务需求
要有效满足业务需求,首先要分析用户将执行的查询类型,并确定最合适的搜索方法。
- **用户查询:**确定用户预期执行的查询类型。这有助于确保您的 Schema 支持真实世界的用例并优化搜索性能。这些查询可能包括
- 检索与自然语言查询相匹配的文档
- 查找与参考图片相似或匹配文本描述的图片
- 根据名称、类别或品牌等属性搜索产品
- 根据结构化元数据(如出版日期、标签、评级)过滤项目
- 在混合查询中结合多种标准(例如,在视觉搜索中,同时考虑图像及其说明的语义相似性)
- **搜索方法:**根据用户将执行的查询类型选择适当的搜索技术。不同的方法服务于不同的目的,通常可以结合使用以获得更强大的结果:
- 语义搜索:使用密集向量相似性来查找具有相似含义的项目,非常适合文本或图像等非结构化数据。
- 全文搜索:用关键字匹配补充语义搜索。 全文搜索可利用词法分析,避免将长词分解成零散的标记,在检索过程中抓住特殊术语。
- 元数据过滤:在向量搜索的基础上,应用日期范围、类别或标签等约束条件。
将业务需求转化为搜索数据模型
下一步是将业务需求转化为具体的数据模型,方法是确定信息的核心组件及其搜索方法:
- 定义需要存储的数据,如原始内容(文本、图像、音频)、相关元数据(标题、标签、作者)和上下文属性(时间戳、用户行为等)。
- 为每个元素确定适当的数据类型和格式。例如
- 文本描述 → 字符串
- 图像或文档 Embeddings → 密集或稀疏向量
- 类别、标签或标志 → 字符串、数组和 bool
- 价格或评级等数字属性 → 整数或浮点数
- 结构化信息,如作者详细信息 -> json
明确定义这些元素可确保数据的一致性、搜索结果的准确性以及与下游应用逻辑集成的便捷性。
Schema 设计
在 Milvus 中,数据模型通过 Collections Schema 表达。在 Collections 模式中设计正确的字段是实现有效检索的关键。每个字段都定义了存储在 Collections 中的特定数据类型,并在搜索过程中扮演着不同的角色。在高层次上,Milvus 支持两种主要类型的字段:向量字段和标量字段。
现在,您可以将数据模型映射到字段 Schema 中,包括向量和任何辅助标量字段。确保每个字段都与数据模型中的属性相关联,尤其要注意向量类型(密集型或标量型)及其维度。
向量字段
向量字段存储文本、图像和音频等非结构化数据类型的嵌入。这些嵌入可能是密集型、稀疏型或二进制型,具体取决于数据类型和使用的检索方法。通常,密集向量用于语义搜索,而稀疏向量则更适合全文或词性匹配。当存储和计算资源有限时,二进制向量很有用。一个 Collections 可能包含多个向量场,以实现多模式或混合检索策略。有关该主题的详细指南,请参阅多向量混合检索。
密集型、稀疏型和二进制向量的例子及其使用场景。
1. 密集型向量 (Dense Vectors)
定义: 密集型向量是大多数元素都非零的向量。它们通常由深度学习模型(如神经网络)生成,用于捕捉数据的语义信息。向量中的每个维度都代表了原始数据在某个抽象特征空间中的一个连续值。
例子:
假设我们有一个句子 “The cat sat on the mat.” 经过预训练的语言模型(如 BERT, Word2Vec, Sentence-BERT)编码后,可能会生成一个固定长度的浮点数向量,例如:
[0.123, -0.456, 0.789, 0.010, ..., 0.999] (假设是一个 768 维的向量)
这个向量的每个值都是一个浮点数,且通常大部分值都不是零。
使用场景:
- 语义搜索 (Semantic Search): 这是最常见的应用。当你想根据内容的“含义”而不是精确的关键词来查找信息时,密集向量非常有用。
- 文本: 搜索与查询语义相似的文档,即使它们没有共享相同的关键词。
- 图像: 搜索与查询图像在视觉上相似的图像(例如,用一张猫的图片搜索其他猫的图片)。
- 音频: 搜索与查询音频在声音特征上相似的音频片段。
- 推荐系统: 根据用户过去喜欢的内容的嵌入向量,推荐语义相似的新内容。
- 聚类与分类: 将语义相似的数据点在向量空间中聚集在一起,或进行分类。
- 多模态搜索: 将不同模态(如文本、图像)的数据映射到同一个向量空间,实现跨模态搜索。
2. 稀疏型向量 (Sparse Vectors)
定义: 稀疏型向量是大多数元素都为零的向量。它们通常用于表示离散的特征,例如词袋模型 (Bag-of-Words) 或 TF-IDF 特征。稀疏向量通常以 (索引, 值) 对的形式存储,只存储非零元素,以节省空间。
例子: 仍然是句子 “The cat sat on the mat.”
词袋模型 (Bag-of-Words, BoW): 假设我们的词汇表是
{"the": 0, "cat": 1, "sat": 2, "on": 3, "mat": 4}。 句子 “The cat sat on the mat.” 的 BoW 向量可能是:[2, 1, 1, 1, 1, 0, 0, 0, ...](假设词汇表很大,大部分词都没有出现) 这里,the出现了 2 次,其他词出现 1 次。大部分位置都是 0。TF-IDF (Term Frequency-Inverse Document Frequency): TF-IDF 比 BoW 更复杂,它会给词语赋予权重,表示其在文档中的重要性。 句子 “The cat sat on the mat.” 的 TF-IDF 向量可能看起来像:
[0.1, 0.8, 0.7, 0.9, 0.6, 0.0, 0.0, ...]这里虽然是非零值,但如果词汇表非常大,绝大多数词在特定文档中是不会出现的,所以大部分维度仍然是零。
使用场景:
- 全文搜索 (Full-Text Search): 当你想要基于精确的关键词匹配或词频来搜索文档时,稀疏向量非常有效。
- 倒排索引 (Inverted Index): 稀疏向量可以很好地与倒排索引结合,快速查找包含特定关键词的文档。
- BM25 排名: 传统的搜索引擎排名算法,如 BM25,就是基于稀疏向量(通常是词频)进行计算的。
- 词性匹配 (Lexical Matching): 查找包含特定单词或短语的文档。
- 混合检索 (Hybrid Search): 将稀疏向量与密集向量结合,以同时利用关键词匹配的精确性和语义匹配的召回率。例如,RAG (Retrieval Augmented Generation) 系统中,通常会同时使用稀疏和密集检索来获取更相关的上下文。
3. 二进制向量 (Binary Vectors)
定义: 二进制向量是一种特殊的稀疏向量,其元素只有两个可能的值:0 或 1。它们通常用于表示二元特征(例如,某个属性是否存在)或通过哈希函数将高维数据压缩为二进制码。
例子:
特征存在性: 假设我们有一些商品的特征:
{"防水": 0, "智能": 1, "轻薄": 2, "防震": 3}。 一个商品如果只有 “防水” 和 “轻薄” 特性,它的二进制向量可能是:[1, 0, 1, 0]局部敏感哈希 (Locality Sensitive Hashing, LSH): 通过 LSH 算法,可以将一个高维的浮点向量映射成一个短的二进制向量。例如,一个 768 维的密集向量经过 LSH 转换后,可能变成一个 64 位的二进制向量:
[0, 1, 1, 0, 1, 0, 0, 1, ..., 1]
使用场景:
- 高效相似度搜索: 当存储和计算资源有限时,二进制向量非常有用。它们可以使用位运算(如汉明距离)进行快速的相似度计算。
- 图像检索: 在大规模图像数据集中进行快速近似相似度搜索。
- 指纹识别: 生物识别系统中,指纹特征可以编码为二进制向量进行匹配。
- 特征表示: 表示事物的二元属性。
- 近似近邻搜索 (Approximate Nearest Neighbor, ANN): 作为 ANN 算法的一种预过滤或加速手段,尤其是在内存受限的环境中。
- 数据去重: 快速识别重复或近乎重复的数据项。
总结表格:
| 向量类型 | 特点 | 典型生成方式/表示 | 优点 | 缺点 | 典型应用场景 |
|---|---|---|---|---|---|
| 密集型 | 大多数元素非零,浮点数 | 深度学习模型嵌入 | 捕捉语义信息,高召回率 | 存储和计算成本高 | 语义搜索、推荐系统、聚类、多模态搜索 |
| 稀疏型 | 大多数元素为零,通常为整数或浮点数 | 词袋、TF-IDF | 节省存储,适合精确关键词匹配,计算快 | 难以捕捉语义,召回率可能受限于关键词 | 全文搜索、词性匹配、混合检索 (RAG) |
| 二进制 | 元素只有 0 或 1 | 特征存在、LSH | 极度节省存储,计算速度极快(位运算) | 损失部分信息,精度可能低于密集向量 | 资源受限环境下的快速近似搜索、指纹识别、数据去重 |
Milvus 支持向量数据类型:FLOAT_VECTOR 表示密集向量,SPARSE_FLOAT_VECTOR 表示稀疏向量,BINARY_VECTOR 表示二进制向量
标量字段
标量字段存储原始的结构化值,通常称为元数据,如数字、字符串或日期。这些值可以与向量搜索结果一起返回,对于筛选和排序至关重要。它们允许你根据特定属性缩小搜索结果的范围,比如将文档限制在特定类别或定义的时间范围内。
Milvus 支持标量类型,如BOOL,INT8/16/32/64,FLOAT,DOUBLE,VARCHAR,JSON 和ARRAY ,用于存储和过滤非向量数据。这些类型提高了搜索操作的精度和定制化程度。
在模式设计中利用高级功能
在设计 Schema 时,仅仅使用支持的数据类型将数据映射到字段是不够的。必须全面了解字段之间的关系以及可用的配置策略。在设计阶段牢记关键功能,可确保 Schema 不仅能满足当前的数据处理要求,还具有可扩展性和适应性,以满足未来的需求。通过精心整合这些功能,您可以构建一个强大的数据架构,最大限度地发挥 Milvus 的功能,并支持您更广泛的数据策略和目标。以下是创建 Collections Schema 的主要功能概述:
主键
主键字段是 Schema 的基本组成部分,因为它能唯一标识 Collections 中的每个实体。必须定义主键。它必须是整数或字符串类型的标量字段,并标记为is_primary=True 。可选择为主键启用auto_id ,主键会自动分配整数,随着更多数据被采集到 Collections 中,整数也会随之增长。
有关详细信息,请参阅主字段和自动 ID。
分区
为了加快搜索速度,可以选择打开分区。通过为分区指定一个特定的标量字段,并在搜索过程中根据该字段指定过滤条件,可以有效地将搜索范围限制在相关的分区中。这种方法通过缩小搜索域,大大提高了检索操作的效率。
更多详情,请参阅使用 Partition Key。
分析器
分析器是处理和转换文本数据的重要工具。它的主要功能是将原始文本转换为标记,并对其进行结构化处理,以便编制索引和进行检索。具体做法是对字符串进行标记化处理,去掉停顿词,并将单个词的词干转化为标记。
更多详情,请参阅分析器概述。

功能
Milvus 允许你定义内置函数作为 Schema 的一部分,以自动推导出某些字段。例如,您可以添加内置 BM25 函数,从VARCHAR 字段生成稀疏向量,以支持全文搜索。这些函数派生字段可简化预处理,并确保 Collections 保持自足和查询就绪。
更多详情,请参阅全文检索。
真实世界示例
在本节中,我们将概述上图所示多媒体文档搜索应用程序的 Schema 设计和代码示例。该 Schema 设计用于管理包含文章的数据集,数据映射到以下字段:
| 字段 | 数据源 | 搜索方法使用 | 主键 | 分区键 | 分析器 | 函数输入/输出 |
|---|---|---|---|---|---|---|
article_id (INT64) | 启用后自动生成auto_id | 使用 “获取 “进行查询 | Y | N | N | N |
标题 (VARCHAR) | 文章标题 | 文本匹配 | N | N | Y | N |
时间戳 (INT32) | 发布日期 | 按分区密钥过滤 | N | Y | N | N |
文本 (VARCHAR) | 文章原始文本 | 多向量混合搜索 | N | N | Y | 输入 |
文本密集向量 (FLOAT_VECTOR) | 由文本 Embeddings 模型生成的密集向量 | 基本向量搜索 | N | N | N | N |
文本稀疏向量 (SPARSE_FLOAT_VECTOR) | 由内置 BM25 函数自动生成的稀疏向量 | 全文搜索 | N | N | N | 输出 |
有关Schema 的更多信息以及添加各类字段的详细指导,请参阅Schema Explained。
初始化模式
首先,我们需要创建一个空模式。这一步为定义数据模型建立了基础结构。
from pymilvus import MilvusClient
schema = MilvusClient.create_schema()
添加字段
创建 Schema 后,下一步就是指定构成数据的字段。每个字段都与各自的数据类型和属性相关联。
from pymilvus import DataType
schema.add_field(field_name="article_id", datatype=DataType.INT64, is_primary=True, auto_id=True, description="article id")
schema.add_field(field_name="title", datatype=DataType.VARCHAR, enable_analyzer=True, enable_match=True, max_length=200, description="article title")
schema.add_field(field_name="timestamp", datatype=DataType.INT32, description="publish date")
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=2000, enable_analyzer=True, description="article text content")
schema.add_field(field_name="text_dense_vector", datatype=DataType.FLOAT_VECTOR, dim=768, description="text dense vector")
schema.add_field(field_name="text_sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR, description="text sparse vector")
在本例中,为字段指定了以下属性:
- 主键:
article_id用作主键,可自动为输入实体分配主键。 - Partition Key:
timestamp被指定为分区键,允许通过分区进行过滤。这可能是 - 文本分析器:文本分析器应用于 2 个字符串字段
title和text,分别支持文本匹配和全文搜索。
添加功能
为增强数据查询功能,可在 Schema 中加入函数。例如,可以创建一个函数来处理与特定字段相关的数据。
from pymilvus import Function, FunctionType
bm25_function = Function(
name="text_bm25",
input_field_names=["text"],
output_field_names=["text_sparse_vector"],
function_type=FunctionType.BM25,
)
schema.add_function(bm25_function)
本例在 Schema 中添加了一个内置 BM25 函数,利用text 字段作为输入,并将生成的稀疏向量存储在text_sparse_vector 字段中。