Vecteur épars
Les vecteurs épars sont une méthode importante de représentation des données dans la recherche d'informations et le traitement du langage naturel. Alors que les vecteurs denses sont populaires pour leurs excellentes capacités de compréhension sémantique, les vecteurs épars fournissent souvent des résultats plus précis lorsqu'il s'agit d'applications qui requièrent une correspondance précise de mots-clés ou de phrases.
Vue d'ensemble
Un vecteur peu dense est une représentation spéciale de vecteurs à haute dimension dans laquelle la plupart des éléments sont nuls et seules quelques dimensions ont des valeurs non nulles. Cette caractéristique rend les vecteurs épars particulièrement efficaces pour traiter des données à grande échelle, à haute dimension, mais éparses. Les applications les plus courantes sont les suivantes
Analyse de texte : Représentation de documents sous forme de vecteurs de sacs de mots, où chaque dimension correspond à un mot et où seuls les mots apparaissant dans le document ont des valeurs non nulles.
Systèmes de recommandation : Matrices d'interaction utilisateur-élément, où chaque dimension représente l'évaluation d'un utilisateur pour un élément particulier, la plupart des utilisateurs n'interagissant qu'avec quelques éléments.
Traitement d'images : Représentation locale des caractéristiques, se concentrant uniquement sur les points clés de l'image, ce qui donne des vecteurs clairsemés à haute dimension.
Comme le montre le diagramme ci-dessous, les vecteurs denses sont généralement représentés sous forme de tableaux continus où chaque position a une valeur (par exemple, [0.3, 0.8, 0.2, 0.3, 0.1]
). En revanche, les vecteurs peu denses ne stockent que les éléments non nuls et leurs indices, souvent représentés sous la forme de paires clé-valeur (par exemple, [{2: 0.2}, ..., {9997: 0.5}, {9999: 0.7}]
). Cette représentation réduit considérablement l'espace de stockage et augmente l'efficacité des calculs, en particulier lorsqu'il s'agit de données à très haute dimension (par exemple, 10 000 dimensions).
Représentation des vecteurs épars
Les vecteurs épars peuvent être générés à l'aide de diverses méthodes, telles que TF-IDF (Term Frequency-Inverse Document Frequency) et BM25 dans le traitement de texte. En outre, Milvus propose des méthodes pratiques pour faciliter la génération et le traitement des vecteurs épars. Pour plus de détails, voir Embeddings.
Pour les données textuelles, Milvus offre également des fonctionnalités de recherche en texte intégral, ce qui permet d'effectuer des recherches vectorielles directement sur des données textuelles brutes sans utiliser de modèles d'intégration externes pour générer des vecteurs peu denses. Pour plus d'informations, reportez-vous à la section Recherche en texte intégral.
Après la vectorisation, les données peuvent être stockées dans Milvus pour la gestion et la recherche de vecteurs. Le diagramme ci-dessous illustre le processus de base.
Utiliser des vecteurs épars dans Milvus
Outre les vecteurs épars, Milvus prend également en charge les vecteurs denses et les vecteurs binaires. Les vecteurs denses sont idéaux pour capturer des relations sémantiques profondes, tandis que les vecteurs binaires excellent dans des scénarios tels que les comparaisons rapides de similarité et la déduplication de contenu. Pour plus d'informations, voir Vecteur dense et Vecteur binaire.
Utiliser des vecteurs peu denses dans Milvus
Milvus prend en charge la représentation des vecteurs épars dans l'un des formats suivants.
Matrice éparse (à l'aide de la classe
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)
Liste de dictionnaires (formatée comme
{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);
Liste d'itérateurs de n-uplets (formatée comme
[(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)]]
Ajouter un champ de vecteurs
Pour utiliser des vecteurs épars dans Milvus, définissez un champ pour stocker des vecteurs épars lors de la création d'une collection. Ce processus comprend les éléments suivants
Définir
datatype
comme étant le type de données vectorielles éparses pris en charge,SPARSE_FLOAT_VECTOR
.Il n'est pas nécessaire de spécifier la dimension.
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
]
}"
Dans cet exemple, un champ vectoriel nommé sparse_vector
est ajouté pour stocker les vecteurs épars. Le type de données de ce champ est SPARSE_FLOAT_VECTOR
.
Définition des paramètres d'index pour le champ vectoriel
Le processus de création d'un index pour les vecteurs peu denses est similaire à celui des vecteurs denses, mais avec des différences au niveau du type d'index spécifié (index_type
), de la métrique de distance (metric_type
) et des paramètres d'index (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={"drop_ratio_build": 0.2},
)
import io.milvus.v2.common.IndexParam;
import java.util.*;
List<IndexParam> indexes = new ArrayList<>();
Map<String,Object> extraParams = new HashMap<>();
extraParams.put("drop_ratio_build", 0.2);
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: {
drop_ratio_build: 0.2,
},
});
export indexParams='[
{
"fieldName": "sparse_vector",
"metricType": "IP",
"indexName": "sparse_inverted_index",
"indexType": "SPARSE_INVERTED_INDEX",
"params":{"drop_ratio_build": 0.2}
}
]'
Dans l'exemple ci-dessus.
Un index de type
SPARSE_INVERTED_INDEX
est créé pour le vecteur clairsemé. Pour les vecteurs épars, vous pouvez spécifierSPARSE_INVERTED_INDEX
ouSPARSE_WAND
. Pour plus d'informations, reportez-vous à la section Index de vecteurs épars.Pour les vecteurs peu denses,
metric_type
ne prend en charge queIP
(produit intérieur), utilisé pour mesurer la similarité entre deux vecteurs peu denses. Pour plus d'informations sur la similarité, reportez-vous à la section Types de métriques.drop_ratio_build
est un paramètre d'index facultatif spécifique aux vecteurs peu denses. Il contrôle la proportion de petites valeurs vectorielles exclues lors de la construction de l'index. Par exemple, avec{"drop_ratio_build": 0.2}
, les 20 % de valeurs vectorielles les plus petites seront exclues lors de la création de l'index, ce qui réduit l'effort de calcul lors des recherches.
Créer une collection
Une fois que les paramètres des vecteurs épars et de l'index sont terminés, vous pouvez créer une collection contenant des vecteurs épars. L'exemple ci-dessous utilise la méthode create_collection
pour créer une collection nommée my_sparse_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
}"
Insérer des données
Après avoir créé la collection, insérez des données contenant des vecteurs épars.
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"]}}
Effectuer une recherche de similarité
Pour effectuer une recherche de similarité à l'aide de vecteurs épars, préparez le vecteur de requête et les paramètres de recherche.
# 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}]
Dans cet exemple, drop_ratio_search
est un paramètre facultatif spécifique aux vecteurs épars, qui permet d'affiner les petites valeurs du vecteur de requête au cours de la recherche. Par exemple, avec {"drop_ratio_search": 0.2}
, les 20 % de valeurs les plus petites du vecteur de requête seront ignorées lors de la recherche.
Exécutez ensuite la recherche de similarité à l'aide de la méthode 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"}]}
Pour plus d'informations sur les paramètres de recherche de similarité, reportez-vous à la section Recherche ANN de base.
Limites
Lorsque vous utilisez des vecteurs épars dans Milvus, tenez compte des limites suivantes :
Actuellement, seule la métrique de distance IP est prise en charge pour les vecteurs peu denses. La dimensionnalité élevée des vecteurs clairsemés rend les distances L2 et cosinus impraticables.
Seuls les types d'index SPARSE_INVERTED_INDEX et SPARSE_WAND sont pris en charge pour les champs de vecteurs peu denses.
Types de données pris en charge pour les vecteurs peu denses :
- La partie dimension doit être un entier non signé de 32 bits ;
- La partie valeur peut être un nombre à virgule flottante 32 bits non négatif.
Les vecteurs épars doivent répondre aux exigences suivantes en matière d'insertion et de recherche :
- Au moins une valeur du vecteur est non nulle ;
- Les indices du vecteur sont non négatifs.
FAQ
Pouvez-vous expliquer la différence entre SPARSE_INVERTED_INDEX et SPARSE_WAND, et comment choisir entre les deux ?
SPARSE_INVERTED_INDEX est un index inversé traditionnel, tandis que SPARSE_WAND utilise l'algorithme Weak-AND pour réduire le nombre d'évaluations de la distance IP complète pendant la recherche. SPARSE_WAND est généralement plus rapide, mais ses performances peuvent diminuer avec l'augmentation de la densité des vecteurs. Pour choisir entre les deux, effectuez des expériences et des analyses comparatives en fonction de votre jeu de données et de votre cas d'utilisation spécifiques.
Comment dois-je choisir les paramètres drop_ratio_build et drop_ratio_search ?
Le choix des paramètres drop_ratio_build et drop_ratio_search dépend des caractéristiques de vos données et de vos exigences en matière de latence, de débit et de précision de la recherche.
La dimension d'un encastrement clairsemé peut-elle être n'importe quelle valeur discrète dans l'espace uint32 ?
Oui, à une exception près. La dimension d'un encastrement clairsemé peut être n'importe quelle valeur dans l'intervalle
[0, maximum of uint32)
. Cela signifie que vous ne pouvez pas utiliser la valeur maximale de uint32.Les recherches sur les segments croissants sont-elles effectuées à l'aide d'un index ou par force brute ?
Les recherches sur les segments croissants sont effectuées à l'aide d'un index du même type que l'index du segment scellé. Pour les nouveaux segments croissants avant que l'index ne soit construit, une recherche par force brute est utilisée.
Est-il possible d'avoir à la fois des vecteurs denses et peu denses dans une même collection ?
Oui, grâce à la prise en charge de plusieurs types de vecteurs, vous pouvez créer des collections avec des colonnes de vecteurs denses et peu denses et effectuer des recherches hybrides sur ces collections.