Vecteur clairsemé
Les vecteurs épars représentent des mots ou des phrases à l'aide d'encastrements vectoriels où la plupart des éléments sont nuls, un seul élément non nul indiquant la présence d'un mot spécifique. Les modèles de vecteurs épars, tels que SPLADEv2, sont plus performants que les modèles denses en ce qui concerne la recherche de connaissances hors domaine, la connaissance des mots clés et l'interprétabilité. Ils sont particulièrement utiles dans la recherche d'informations, le traitement du langage naturel et les systèmes de recommandation, où la combinaison de vecteurs épars pour le rappel et d'un grand modèle pour le classement peut améliorer de manière significative les résultats de la recherche.
Dans Milvus, l'utilisation des vecteurs épars suit un processus similaire à celui des vecteurs denses. Elle implique la création d'une collection avec une colonne de vecteurs épars, l'insertion de données, la création d'un index et l'exécution de recherches de similarité et de requêtes scalaires.
Dans ce didacticiel, vous apprendrez à :
- Préparer des encastrements de vecteurs épars ;
- Créer une collection avec un champ de vecteurs épars ;
- Insérer des entités avec des encastrements de vecteurs épars ;
- Indexer la collection et effectuer une recherche ANN sur les vecteurs épars.
Pour voir les vecteurs épars en action, reportez-vous à hello_sparse.py.
Remarques
Actuellement, la prise en charge des vecteurs épars est une fonctionnalité bêta dans la version 2.4.0, et il est prévu de la généraliser dans la version 3.0.0.Préparer les intégrations de vecteurs épars
Pour utiliser des vecteurs épars dans Milvus, préparez des intégrations de vecteurs dans l'un des formats pris en charge :
Sparse Matrices: Utilisez la famille de classes scipy.sparse pour représenter vos intégrations de vecteurs épars. Cette méthode est efficace pour traiter des données à grande échelle et à haute dimension.
Liste de dictionnaires: Représentez chaque encastrement clairsemé sous la forme d'un dictionnaire, structuré comme
{dimension_index: value, ...}
, où chaque paire clé-valeur représente l'indice de dimension et sa valeur correspondante.Exemple :
{2: 0.33, 98: 0.72, ...}
Liste de tables itératives de tuples: Semblable à la liste de dictionnaires, mais en utilisant un itérable de tuples,
[(dimension_index, value)]
, pour spécifier uniquement les dimensions non nulles et leurs valeurs.Exemple :
[(2, 0.33), (98, 0.72), ...]
L'exemple suivant prépare des encastrements épars en générant une matrice éparse aléatoire pour 10 000 entités, chacune ayant 10 000 dimensions et une densité d'éparpillement de 0,005.
# Prepare entities with sparse vector representation
import numpy as np
import random
rng = np.random.default_rng()
num_entities, dim = 10000, 10000
# Generate random sparse rows with an average of 25 non-zero elements per row
entities = [
{
"scalar_field": rng.random(),
# To represent a single sparse vector row, you can use:
# - Any of the scipy.sparse sparse matrices class family with shape[0] == 1
# - Dict[int, float]
# - Iterable[Tuple[int, float]]
"sparse_vector": {
d: rng.random() for d in random.sample(range(dim), random.randint(20, 30))
},
}
for _ in range(num_entities)
]
# print the first entity to check the representation
print(entities[0])
# Output:
# {
# 'scalar_field': 0.520821523849214,
# 'sparse_vector': {
# 5263: 0.2639375518635271,
# 3573: 0.34701499565746674,
# 9637: 0.30856525997853057,
# 4399: 0.19771651149001523,
# 6959: 0.31025067641541815,
# 1729: 0.8265339135915016,
# 1220: 0.15303302147479103,
# 7335: 0.9436728846033107,
# 6167: 0.19929870545596562,
# 5891: 0.8214617920371853,
# 2245: 0.7852255053773395,
# 2886: 0.8787982039149889,
# 8966: 0.9000606703940665,
# 4910: 0.3001170013981104,
# 17: 0.00875671667413136,
# 3279: 0.7003425473001098,
# 2622: 0.7571360018373428,
# 4962: 0.3901879090102064,
# 4698: 0.22589525720196246,
# 3290: 0.5510228492587324,
# 6185: 0.4508413201390492
# }
# }
Remarques
Les dimensions du vecteur doivent être de type Python int
ou numpy.integer
, et les valeurs doivent être de type Python float
ou numpy.floating
.
Pour générer des embeddings, vous pouvez également utiliser le paquetage model
intégré à la bibliothèque PyMilvus, qui offre une gamme de fonctions d'embedding. Pour plus d'informations, reportez-vous à Embeddings.
Création d'une collection avec un champ de vecteurs épars
Pour créer une collection avec un champ de vecteurs épars, définissez le type de données du champ de vecteurs épars sur DataType.SPARSE_FLOAT_VECTOR. Contrairement aux vecteurs denses, il n'est pas nécessaire de spécifier une dimension pour les vecteurs épars.
from pymilvus import MilvusClient, DataType
# Create a MilvusClient instance
client = MilvusClient(uri="http://localhost:19530")
# Create a collection with a sparse vector field
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="scalar_field", datatype=DataType.DOUBLE)
# For sparse vector, no need to specify dimension
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR) # set `datatype` to `SPARSE_FLOAT_VECTOR`
client.create_collection(collection_name="test_sparse_vector", schema=schema)
Pour plus de détails sur les paramètres courants des collections, reportez-vous à la fonction create_collection() .
Insérer des entités avec des encastrements de vecteurs peu denses
Pour insérer des entités avec des encastrements de vecteurs peu denses, il suffit de passer la liste des entités à la méthode insert()
la liste des entités.
# Insert entities
client.insert(collection_name="test_sparse_vector", data=entities)
Indexer la collection
Avant d'effectuer des recherches de similarité, créez un index pour la collection. Pour plus d'informations sur les types d'index et les paramètres, reportez-vous aux méthodes add_index() et create_index().
# Index the collection
# Prepare 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", # the type of index to be created. set to `SPARSE_INVERTED_INDEX` or `SPARSE_WAND`.
metric_type="IP", # the metric type to be used for the index. Currently, only `IP` (Inner Product) is supported.
params={"drop_ratio_build": 0.2}, # the ratio of small vector values to be dropped during indexing.
)
# Create index
client.create_index(collection_name="test_sparse_vector", index_params=index_params)
Pour la construction d'un index sur des vecteurs peu denses, tenez compte de ce qui suit :
index_type
: Le type d'index à construire. Options possibles pour les vecteurs épars :SPARSE_INVERTED_INDEX
: Un index inversé qui associe chaque dimension à ses vecteurs non nuls, ce qui facilite l'accès direct aux données pertinentes lors des recherches. Idéal pour les ensembles de données contenant des données éparses mais à haute dimension.SPARSE_WAND
: Utilise l'algorithme Weak-AND (WAND) pour contourner rapidement les candidats improbables, en concentrant l'évaluation sur ceux qui ont un potentiel de classement plus élevé. Traite les dimensions comme des termes et les vecteurs comme des documents, ce qui accélère les recherches dans les grands ensembles de données éparses.
metric_type
: Seule la métrique de distanceIP
(produit intérieur) est prise en charge pour les vecteurs peu denses.params.drop_ratio_build
: Paramètre d'index utilisé spécifiquement pour les vecteurs peu denses. Il contrôle la proportion de petites valeurs vectorielles qui sont exclues au cours du processus d'indexation. Ce paramètre permet d'affiner le compromis entre efficacité et précision en ignorant les petites valeurs lors de la construction de l'index. Par exemple, sidrop_ratio_build = 0.3
, lors de la construction de l'index, toutes les valeurs de tous les vecteurs épars sont rassemblées et triées. Les 30 % les plus petites de ces valeurs ne sont pas incluses dans l'index, ce qui réduit la charge de travail informatique pendant la recherche.
Pour plus d'informations, voir Index en mémoire.
Effectuer une recherche ANN
Une fois la collection indexée et chargée en mémoire, utilisez la méthode search()
pour extraire les documents pertinents en fonction de la requête.
# Load the collection into memory
client.load_collection(collection_name="test_sparse_vector")
# Perform ANN search on sparse vectors
# for demo purpose we search for the last inserted vector
query_vector = entities[-1]["sparse_vector"]
search_params = {
"metric_type": "IP",
"params": {"drop_ratio_search": 0.2}, # the ratio of small vector values to be dropped during search.
}
search_res = client.search(
collection_name="test_sparse_vector",
data=[query_vector],
limit=3,
output_fields=["pk", "scalar_field"],
search_params=search_params,
)
for hits in search_res:
for hit in hits:
print(f"hit: {hit}")
# Output:
# hit: {'id': '448458373272710786', 'distance': 7.220192909240723, 'entity': {'pk': '448458373272710786', 'scalar_field': 0.46767865218233806}}
# hit: {'id': '448458373272708317', 'distance': 1.2287548780441284, 'entity': {'pk': '448458373272708317', 'scalar_field': 0.7315987515699472}}
# hit: {'id': '448458373272702005', 'distance': 0.9848432540893555, 'entity': {'pk': '448458373272702005', 'scalar_field': 0.9871869181562156}}
Lors de la configuration des paramètres de recherche, tenez compte des points suivants :
params.drop_ratio_search
: Le paramètre de recherche utilisé spécifiquement pour les vecteurs épars. Cette option permet d'affiner le processus de recherche en spécifiant le ratio des plus petites valeurs du vecteur de la requête à ignorer. Elle permet d'équilibrer la précision de la recherche et les performances. Plus la valeur définie pourdrop_ratio_search
est petite, moins ces petites valeurs contribuent au résultat final. En ignorant certaines petites valeurs, les performances de la recherche peuvent être améliorées avec un impact minimal sur la précision.
Effectuer des requêtes scalaires
Outre la recherche ANN, Milvus prend également en charge les requêtes scalaires sur les vecteurs peu denses. Ces requêtes vous permettent d'extraire des documents en fonction d'une valeur scalaire associée au vecteur clairsemé. Pour plus d'informations sur les paramètres, reportez-vous à query().
Filtrer les entités dont le champ scalaire est supérieur à 3 :
# Perform a query by specifying filter expr
filter_query_res = client.query(
collection_name="test_sparse_vector",
filter="scalar_field > 0.999",
)
print(filter_query_res[:2])
# Output:
# [{'pk': '448458373272701862', 'scalar_field': 0.9994093623822689, 'sparse_vector': {173: 0.35266244411468506, 400: 0.49995484948158264, 480: 0.8757831454277039, 661: 0.9931875467300415, 1040: 0.0965644046664238, 1728: 0.7478245496749878, 2365: 0.4351981580257416, 2923: 0.5505295395851135, 3181: 0.7396837472915649, 3848: 0.4428485333919525, 4701: 0.39119353890419006, 5199: 0.790219783782959, 5798: 0.9623121619224548, 6213: 0.453134149312973, 6341: 0.745091438293457, 6775: 0.27766478061676025, 6875: 0.017947908490896225, 8093: 0.11834774166345596, 8617: 0.2289179265499115, 8991: 0.36600416898727417, 9346: 0.5502803921699524}}, {'pk': '448458373272702421', 'scalar_field': 0.9990218525410719, 'sparse_vector': {448: 0.587817907333374, 1866: 0.0994109958410263, 2438: 0.8672442436218262, 2533: 0.8063794374465942, 2595: 0.02122959867119789, 2828: 0.33827054500579834, 2871: 0.1984412521123886, 2938: 0.09674275666475296, 3154: 0.21552987396717072, 3662: 0.5236313343048096, 3711: 0.6463911533355713, 4029: 0.4041993021965027, 7143: 0.7370485663414001, 7589: 0.37588241696357727, 7776: 0.436136394739151, 7962: 0.06377989053726196, 8385: 0.5808192491531372, 8592: 0.8865005970001221, 8648: 0.05727503448724747, 9071: 0.9450633525848389, 9161: 0.146037295460701, 9358: 0.1903032660484314, 9679: 0.3146636486053467, 9974: 0.8561339378356934, 9991: 0.15841573476791382}}]
Filtre les entités par clé primaire :
# primary keys of entities that satisfy the filter
pks = [ret["pk"] for ret in filter_query_res]
# Perform a query by primary key
pk_query_res = client.query(
collection_name="test_sparse_vector", filter=f"pk == '{pks[0]}'"
)
print(pk_query_res)
# Output:
# [{'scalar_field': 0.9994093623822689, 'sparse_vector': {173: 0.35266244411468506, 400: 0.49995484948158264, 480: 0.8757831454277039, 661: 0.9931875467300415, 1040: 0.0965644046664238, 1728: 0.7478245496749878, 2365: 0.4351981580257416, 2923: 0.5505295395851135, 3181: 0.7396837472915649, 3848: 0.4428485333919525, 4701: 0.39119353890419006, 5199: 0.790219783782959, 5798: 0.9623121619224548, 6213: 0.453134149312973, 6341: 0.745091438293457, 6775: 0.27766478061676025, 6875: 0.017947908490896225, 8093: 0.11834774166345596, 8617: 0.2289179265499115, 8991: 0.36600416898727417, 9346: 0.5502803921699524}, 'pk': '448458373272701862'}]
Limites
Lors de l'utilisation de vecteurs épars dans Milvus, il convient de tenir compte des limites suivantes :
Actuellement, seule la métrique de distance IP est prise en charge pour les vecteurs épars.
Pour les champs de vecteur clairsemés, seuls les types d'index SPARSE_INVERTED_INDEX et SPARSE_WAND sont pris en charge.
Actuellement, la recherche par plage, la recherche par groupement et l'itérateur de recherche ne sont pas pris en charge pour les vecteurs peu denses.
FAQ
Quelle est la métrique de distance prise en charge pour les vecteurs épars ?
Les vecteurs épars ne prennent en charge que la métrique de distance du produit intérieur (IP) en raison de la dimensionnalité élevée des vecteurs épars, qui rend la distance L2 et la distance cosinus irréalisables.
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.
Quels types de données sont pris en charge pour les encastrements épars ?
La partie dimension doit être un entier non signé de 32 bits et la partie valeur peut être un nombre flottant non négatif de 32 bits.
La dimension d'un sparse embedding 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 épars et 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.
Quelles sont les conditions requises pour l'insertion ou la recherche d'embeddings peu denses ?
Les intégrations éparses doivent avoir au moins une valeur non nulle et les indices des vecteurs doivent être non négatifs.