稀疏向量
稀疏向量是信息检索和自然语言处理中一种重要的数据表示方法。虽然稠密向量因其出色的语义理解能力而广受欢迎,但在涉及需要精确匹配关键词或短语的应用时,稀疏向量往往能提供更精确的结果。
稀疏向量概述
稀疏向量是高维向量的一种特殊表示形式,其中大部分元素为零,只有少数维度具有非零值。这一特性使得稀疏向量在处理大规模、高维但稀疏的数据时特别有效。常见的应用包括
文本分析:将文档表示为词袋向量,其中每个维度对应一个单词,只有在文档中出现的单词才有非零值。
推荐系统:用户-物品交互矩阵,其中每个维度代表用户对特定物品的评分,大多数用户只与少数物品交互。
图像处理:局部特征表示,只关注图像中的关键点,从而产生高维稀疏向量。
如下图所示,密集向量通常表示为连续数组,其中每个位置都有一个值(如[0.3, 0.8, 0.2, 0.3, 0.1]
)。相比之下,稀疏向量只存储非零元素及其索引,通常表示为键值对(如[{2: 0.2}, ..., {9997: 0.5}, {9999: 0.7}]
)。这种表示方法大大减少了存储空间,提高了计算效率,尤其是在处理极高维数据(如 10,000 维)时。
稀疏向量表示法
稀疏向量可以使用多种方法生成,例如文本处理中的TF-IDF(词频-反向文档频率)和BM25。此外,Milvus 还提供了帮助生成和处理稀疏向量的便捷方法。详情请参阅Embeddings。
对于文本数据,Milvus 还提供全文搜索功能,让您可以直接在原始文本数据上执行向量搜索,而无需使用外部嵌入模型来生成稀疏向量。更多信息,请参阅全文搜索。
矢量化后,数据可存储在 Milvus 中,以便进行管理和矢量检索。下图说明了基本流程。
在 Milvus 中使用稀疏向量
在 Milvus 中使用稀疏向量
Milvus 支持以下列任何一种格式表示稀疏向量。
稀疏矩阵(使用
scipy.sparse
类)from scipy.sparse import csr_matrix # Create a sparse matrix row = [0, 0, 1, 2, 2, 2] col = [0, 2, 2, 0, 1, 2] data = [1, 2, 3, 4, 5, 6] sparse_matrix = csr_matrix((data, (row, col)), shape=(3, 3)) # Represent sparse vector using the sparse matrix sparse_vector = sparse_matrix.getrow(0)
字典列表(格式为
{dimension_index: value, ...}
)# Represent sparse vector using a dictionary sparse_vector = [{1: 0.5, 100: 0.3, 500: 0.8, 1024: 0.2, 5000: 0.6}]
SortedMap<Long, Float> sparseVector = new TreeMap<>(); sparseVector.put(1L, 0.5f); sparseVector.put(100L, 0.3f); sparseVector.put(500L, 0.8f); sparseVector.put(1024L, 0.2f); sparseVector.put(5000L, 0.6f);
元组迭代器列表(格式为
[(dimension_index, value)]
)# Represent sparse vector using a list of tuples sparse_vector = [[(1, 0.5), (100, 0.3), (500, 0.8), (1024, 0.2), (5000, 0.6)]]
添加向量场
要在 Milvus 中使用稀疏向量,请在创建 Collections 时定义一个用于存储稀疏向量的字段。这个过程包括
将
datatype
设置为支持的稀疏向量数据类型,即SPARSE_FLOAT_VECTOR
。无需指定维度。
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="http://localhost:19530")
client.drop_collection(collection_name="my_sparse_collection")
schema = client.create_schema(
auto_id=True,
enable_dynamic_fields=True,
)
schema.add_field(field_name="pk", datatype=DataType.VARCHAR, is_primary=True, max_length=100)
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.build());
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.setEnableDynamicField(true);
schema.addField(AddFieldReq.builder()
.fieldName("pk")
.dataType(DataType.VarChar)
.isPrimaryKey(true)
.autoID(true)
.maxLength(100)
.build());
schema.addField(AddFieldReq.builder()
.fieldName("sparse_vector")
.dataType(DataType.SparseFloatVector)
.build());
import { DataType } from "@zilliz/milvus2-sdk-node";
const schema = [
{
name: "metadata",
data_type: DataType.JSON,
},
{
name: "pk",
data_type: DataType.Int64,
is_primary_key: true,
},
{
name: "sparse_vector",
data_type: DataType.SparseFloatVector,
}
];
export primaryField='{
"fieldName": "pk",
"dataType": "VarChar",
"isPrimary": true,
"elementTypeParams": {
"max_length": 100
}
}'
export vectorField='{
"fieldName": "sparse_vector",
"dataType": "SparseFloatVector"
}'
export schema="{
\"autoID\": true,
\"fields\": [
$primaryField,
$vectorField
]
}"
在此示例中,添加了一个名为sparse_vector
的向量字段,用于存储稀疏向量。该字段的数据类型为SPARSE_FLOAT_VECTOR
。
为向量字段设置索引参数
为稀疏向量创建索引的过程与为密集向量创建索引的过程类似,但在指定的索引类型 (index_type
)、距离度量 (metric_type
) 和索引参数 (params
) 上有所不同。
index_params = client.prepare_index_params()
index_params.add_index(
field_name="sparse_vector",
index_name="sparse_inverted_index",
index_type="SPARSE_INVERTED_INDEX",
metric_type="IP",
params={"inverted_index_algo": "DAAT_MAXSCORE"},
)
import io.milvus.v2.common.IndexParam;
import java.util.*;
List<IndexParam> indexes = new ArrayList<>();
Map<String,Object> extraParams = new HashMap<>();
extraParams.put("inverted_index_algo": "DAAT_MAXSCORE");
indexes.add(IndexParam.builder()
.fieldName("sparse_vector")
.indexName("sparse_inverted_index")
.indexType(IndexParam.IndexType.SPARSE_INVERTED_INDEX)
.metricType(IndexParam.MetricType.IP)
.extraParams(extraParams)
.build());
const indexParams = await client.createIndex({
index_name: 'sparse_inverted_index',
field_name: 'sparse_vector',
metric_type: MetricType.IP,
index_type: IndexType.SPARSE_WAND,
params: {
inverted_index_algo: 'DAAT_MAXSCORE',
},
});
export indexParams='[
{
"fieldName": "sparse_vector",
"metricType": "IP",
"indexName": "sparse_inverted_index",
"indexType": "SPARSE_INVERTED_INDEX",
"params":{"inverted_index_algo": "DAAT_MAXSCORE"}
}
]'
在上面的示例中
index_type
:要为稀疏向量场创建的索引类型。有效值:SPARSE_INVERTED_INDEX
:稀疏向量的通用反转索引。SPARSE_WAND
:Milvus v2.5.3 及更早版本支持的专用索引类型。
从 Milvus 2.5.4 起,
SPARSE_WAND
已被弃用。建议在保持兼容性的同时,使用"inverted_index_algo": "DAAT_WAND"
作为等价索引。metric_type
:用于计算稀疏向量之间相似性的度量。有效值:params.inverted_index_algo
:用于建立和查询索引的算法。有效值:"DAAT_MAXSCORE"
(默认):使用 MaxScore 算法进行优化的 Document-at-a-Time (DAAT) 查询处理。MaxScore 通过跳过可能影响最小的术语和文档,为高 k 值或包含大量术语的查询提供更好的性能。为此,它根据最大影响分值将术语划分为基本组和非基本组,并将重点放在对前 k 结果有贡献的术语上。"DAAT_WAND"
:使用 WAND 算法优化 DAAT 查询处理。WAND 算法利用最大影响得分跳过非竞争性文档,从而评估较少的命中文档,但每次命中的开销较高。这使得 WAND 对于 k 值较小的查询或较短的查询更有效,因为在这些情况下跳过更可行。"TAAT_NAIVE"
:基本术语一次查询处理(TAAT)。虽然与DAAT_MAXSCORE
和DAAT_WAND
相比速度较慢,但TAAT_NAIVE
具有独特的优势。DAAT 算法使用的是缓存的最大影响分数,无论全局 Collections 参数(avgdl)如何变化,这些分数都保持静态,而TAAT_NAIVE
不同,它能动态地适应这种变化。
创建 Collections
完成稀疏向量和索引设置后,就可以创建包含稀疏向量的 Collections。下面的示例使用 create_collection
方法创建一个名为my_sparse_collection
的 Collection。
client.create_collection(
collection_name="my_sparse_collection",
schema=schema,
index_params=index_params
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("my_sparse_collection")
.collectionSchema(schema)
.indexParams(indexes)
.build();
client.createCollection(requestCreate);
import { MilvusClient } from "@zilliz/milvus2-sdk-node";
const client = new MilvusClient({
address: 'http://localhost:19530'
});
await client.createCollection({
collection_name: 'my_sparse_collection',
schema: schema,
index_params: indexParams
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_sparse_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
插入数据
创建集合后,插入包含稀疏向量的数据。
sparse_vectors = [
{"sparse_vector": {1: 0.5, 100: 0.3, 500: 0.8}},
{"sparse_vector": {10: 0.1, 200: 0.7, 1000: 0.9}},
]
client.insert(
collection_name="my_sparse_collection",
data=sparse_vectors
)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;
List<JsonObject> rows = new ArrayList<>();
Gson gson = new Gson();
{
JsonObject row = new JsonObject();
SortedMap<Long, Float> sparse = new TreeMap<>();
sparse.put(1L, 0.5f);
sparse.put(100L, 0.3f);
sparse.put(500L, 0.8f);
row.add("sparse_vector", gson.toJsonTree(sparse));
rows.add(row);
}
{
JsonObject row = new JsonObject();
SortedMap<Long, Float> sparse = new TreeMap<>();
sparse.put(10L, 0.1f);
sparse.put(200L, 0.7f);
sparse.put(1000L, 0.9f);
row.add("sparse_vector", gson.toJsonTree(sparse));
rows.add(row);
}
InsertResp insertR = client.insert(InsertReq.builder()
.collectionName("my_sparse_collection")
.data(rows)
.build());
const data = [
{ sparse_vector: { "1": 0.5, "100": 0.3, "500": 0.8 } },
{ sparse_vector: { "10": 0.1, "200": 0.7, "1000": 0.9 } },
];
client.insert({
collection_name: "my_sparse_collection",
data: data,
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"data": [
{"sparse_vector": {"1": 0.5, "100": 0.3, "500": 0.8}},
{"sparse_vector": {"10": 0.1, "200": 0.7, "1000": 0.9}}
],
"collectionName": "my_sparse_collection"
}'
## {"code":0,"cost":0,"data":{"insertCount":2,"insertIds":["453577185629572534","453577185629572535"]}}
执行相似性搜索
要使用稀疏向量执行相似性搜索,请准备好查询向量和搜索参数。
# Prepare search parameters
search_params = {
"params": {"drop_ratio_search": 0.2}, # Additional optional search parameters
}
# Prepare the query vector
query_vector = [{1: 0.2, 50: 0.4, 1000: 0.7}]
在本例中,drop_ratio_search
是专门用于稀疏向量的可选参数,允许在搜索过程中对查询向量中的小值进行微调。例如,使用{"drop_ratio_search": 0.2}
时,查询向量中最小的 20% 值将在搜索过程中被忽略。
然后,使用search
方法执行相似性搜索。
res = client.search(
collection_name="my_sparse_collection",
data=query_vector,
limit=3,
output_fields=["pk"],
search_params=search_params,
)
print(res)
# Output
# data: ["[{'id': '453718927992172266', 'distance': 0.6299999952316284, 'entity': {'pk': '453718927992172266'}}, {'id': '453718927992172265', 'distance': 0.10000000149011612, 'entity': {'pk': '453718927992172265'}}]"]
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.SparseFloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
Map<String,Object> searchParams = new HashMap<>();
searchParams.put("drop_ratio_search", 0.2);
SortedMap<Long, Float> sparse = new TreeMap<>();
sparse.put(10L, 0.1f);
sparse.put(200L, 0.7f);
sparse.put(1000L, 0.9f);
SparseFloatVec queryVector = new SparseFloatVec(sparse);
SearchResp searchR = client.search(SearchReq.builder()
.collectionName("my_sparse_collection")
.data(Collections.singletonList(queryVector))
.annsField("sparse_vector")
.searchParams(searchParams)
.topK(3)
.outputFields(Collections.singletonList("pk"))
.build());
System.out.println(searchR.getSearchResults());
// Output
//
// [[SearchResp.SearchResult(entity={pk=453444327741536759}, score=1.31, id=453444327741536759), SearchResp.SearchResult(entity={pk=453444327741536756}, score=1.31, id=453444327741536756), SearchResp.SearchResult(entity={pk=453444327741536753}, score=1.31, id=453444327741536753)]]
client.search({
collection_name: 'my_sparse_collection',
data: {1: 0.2, 50: 0.4, 1000: 0.7},
limit: 3,
output_fields: ['pk'],
params: {
drop_ratio_search: 0.2
}
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"collectionName": "my_sparse_collection",
"data": [
{"1": 0.2, "50": 0.4, "1000": 0.7}
],
"annsField": "sparse_vector",
"limit": 3,
"searchParams":{
"params":{"drop_ratio_search": 0.2}
},
"outputFields": ["pk"]
}'
## {"code":0,"cost":0,"data":[{"distance":0.63,"id":"453577185629572535","pk":"453577185629572535"},{"distance":0.1,"id":"453577185629572534","pk":"453577185629572534"}]}
有关相似性搜索参数的更多信息,请参阅基本 ANN 搜索。
限制
在 Milvus 中使用稀疏向量时,请考虑以下限制:
目前,稀疏向量仅支持IP和BM25(用于全文搜索)距离度量。稀疏向量的高维度使得 L2 和余弦距离不切实际。
对于稀疏向量场,只支持SPARSE_INVERTED_INDEX和SPARSE_WAND索引类型。
稀疏向量支持的数据类型:
- 维数部分必须是无符号 32 位整数;
- 值部分可以是非负 32 位浮点数。
稀疏向量在插入和搜索时必须满足以下要求:
- 向量中至少有一个值为非零;
- 向量索引为非负。
常见问题
能否解释 SPARSE_INVERTED_INDEX 和 SPARSE_WAND 之间的区别,以及如何在两者之间进行选择?
SPARSE_INVERTED_INDEX是一种传统的倒排索引,而SPARSE_WAND则使用弱-AND算法来减少搜索过程中全 IP 距离评估的次数。SPARSE_WAND通常速度更快,但其性能会随着向量密度的增加而下降。要在它们之间做出选择,请根据您的特定数据集和使用案例进行实验和基准测试。
如何选择 drop_ratio_build 和 drop_ratio_search 参数?
drop_ratio_build和drop_ratio_search的选择取决于数据的特性以及对搜索延迟/吞吐量和准确性的要求。
稀疏嵌入的维度可以是 uint32 空间内的任何离散值吗?
可以,但有一个例外。稀疏嵌入的维度可以是
[0, maximum of uint32)
范围内的任何值。这意味着不能使用 uint32 的最大值。是通过索引还是蛮力对增长的线段进行搜索?
对增长的数据段的搜索是通过与密封数据段索引相同类型的索引进行的。对于索引建立前的新增长区段,则使用蛮力搜索。
是否可以在一个 Collections 中同时包含稀疏向量和密集向量?
可以,通过多向量类型支持,您可以创建既有稀疏向量列又有密集向量列的 Collections,并对它们执行混合搜索。