[PYTHON] Une petite introduction de fonction de niche de faiss

Bonjour, c'est le jour 23 du Calendrier de l'Avent ABEJA.

introduction

Connaissez-vous une bibliothèque appelée faiss? La bibliothèque de recherche de quartier de Facebook Resarch est très facile à utiliser et contient de nombreux tutoriels, je l'utilise donc fréquemment pour les affaires et les loisirs. Je vais. Cependant, une bibliothèque aussi pratique a aussi ses faiblesses. Quand j'essaie de faire quelque chose d'un petit créneau, je dis simplement: "Eh bien, expliquez les détails et ayez une idée du code de test (-д ☆)." Screenshot_2019-12-23 facebookresearch faiss.png J'ai appris à l'utiliser en lisant réellement le code de test et parfois en lisant le code C ++ du corps principal, donc je l'écrirai avec un rappel pour moi-même à l'avenir.

Une petite introduction de fonctionnalité de niche

Acquérir des fonctionnalités Faiss

Dans de rares cas, vous souhaitez obtenir les fonctionnalités elles-mêmes, plutôt que de rechercher les fonctionnalités détenues par faiss. Par exemple, avec faiss.IndexFlatL2, le montant de la caractéristique lui-même peut être obtenu à partir de l'attribut xb. Vous pouvez également le convertir en numpy.ndarray en utilisant la fonction faiss.vector_float_to_array.

>>> d = 2
>>> nb = 10
>>> xb = np.random.random((nb, d)).astype(np.float32)
>>>
>>> index = faiss.IndexFlatL2(d)
>>> index.add(xb)
>>>
>>> index.xb
<faiss.swigfaiss.FloatVector; proxy of <Swig Object of type 'std::vector< float > *' at 0x7f7ff26dede0> >
>>> faiss.vector_float_to_array(index.xb)
array([0.70434606, 0.8172881 , 0.27514696, 0.04918063, 0.16584012,
       0.58303493, 0.83627784, 0.7318148 , 0.91633004, 0.16084996,
       0.6760264 , 0.65586466, 0.45432937, 0.35858378, 0.0895841 ,
       0.3424391 , 0.6606455 , 0.7392444 , 0.07704416, 0.13714503],
      dtype=float32)

Vous pouvez vérifier à partir de quel attribut le montant de la fonctionnalité lui-même peut être obtenu à partir de l'en-tête tel que faiss / IndexFlat.h.

Transformation de numpy.ndarray => float * x

[faiss / c_api / pour utiliser des fonctions d'interface swig non implémentées dans faiss / python / faiss.py Si vous regardez IndexFlat_c.h](https://github.com/facebookresearch/faiss/blob/master/c_api/IndexFlat_c.h), vous pouvez rencontrer float * x. Si vous utilisez faiss.swig_ptr pour numpy.ndarray, vous pouvez le convertir en un pointeur de swig, vous pouvez donc utiliser une fonction légèrement de niche.

>>> x = np.random.random(10).astype(np.float32)
>>> faiss.swig_ptr(x)
<Swig Object of type 'faiss::IndexReplicasTemplate< faiss::Index >::component_t *' at 0x7fe8a57c3b10>

Gérer le sous-espace

Jusqu'à présent, de nombreuses fonctions de base ont été introduites, mais à partir de maintenant, elles seront un peu plus avancées. A propos de la gestion du sous-espace dans faiss. Le premier est le calcul de la distance dans le sous-espace. En utilisant compute_distance_subset, cela peut être réalisé comme suit.

>>> def compute_distance_subset(index, xq, subset):
...     n, _ = xq.shape
...     _, k = subset.shape
...     distances = np.empty((n, k), dtype=np.float32)
...     index.compute_distance_subset(
...             n, faiss.swig_ptr(xq),
...             k, faiss.swig_ptr(distances),
...             faiss.swig_ptr(subset)
...     )
...     return distances
... 
>>>
>>> d = 2
>>> nb = 10000
>>> nq = 10
>>> k = 5
>>> xb = np.random.random((nb, d)).astype(np.float32)
>>> xq = np.random.random((nq, d)).astype(np.float32)
>>> subset = np.random.choice(range(nb), (nq, k))
>>> 
>>> index = faiss.IndexFlatL2(d)
>>> index.add(xb)
>>> 
>>> compute_distance_subset(index, xq, subset)
array([[0.04731181, 0.1585833 , 0.4276843 , 0.02083743, 0.14153683],
       [0.55289364, 0.19499591, 0.24127454, 0.16293366, 0.02044217],
       [0.61750704, 0.48981428, 0.51042193, 0.12334089, 0.55514073],
       [0.5959296 , 0.6604827 , 0.20945217, 0.10136123, 0.01619768],
       [0.13882631, 0.16818088, 0.01572821, 0.17454663, 0.03992677],
       [0.46265444, 0.70609426, 0.49902472, 0.730565  , 0.09248901],
       [1.1638596 , 1.1041545 , 0.73789394, 0.60920525, 0.21328084],
       [0.02405633, 0.00557276, 0.6880306 , 0.821055  , 0.0421453 ],
       [0.08726364, 0.33441633, 0.15067156, 0.28792596, 0.30785137],
       [0.04219329, 0.747885  , 0.01912764, 0.19305223, 0.51132184]],
      dtype=float32)

Il est à noter que compute_distance_subset ne calcule que la distance du sous-ensemble, et ne calcule pas le voisinage de K.

Ensuite, comment créer le sous-espace lui-même. Ceci peut être réalisé en utilisant copy_subset_to. Si vous regardez Fractionner et fusionner les index, vous pouvez le comprendre à peu près, donc je n'écrirai que les notes. Le premier point est décrit dans le document, mais il ne peut être utilisé qu'avec ʻIndex IVF`. Le deuxième point est qu'il y a des difficultés à créer un sous-espace. Comme vous pouvez le voir dans le Code source, seules les copies continues et périodiques sont prises en charge.

faiss/IndexIVF.h


/** copy a subset of the entries index to the other index
 *
 * if subset_type == 0: copies ids in [a1, a2)
 * if subset_type == 1: copies ids if id % a1 == a2
 * if subset_type == 2: copies inverted lists such that a1
 *                      elements are left before and a2 elements are after
 */
virtual void copy_subset_to (IndexIVF & other, int subset_type,
                             idx_t a1, idx_t a2) const;

Utilisez votre propre système d'identité

Par défaut, des ID consécutifs sont attribués, mais vous pouvez également utiliser votre propre système d'identification en utilisant ʻIndexIDMap`. Si vous regardez Faiss ID mapping, vous pouvez le voir, mais l'ID unique est le suivant. Vous pouvez utiliser le système.

>>> d = 2
>>> nb = 10000
>>> nq = 10
>>> k = 2
>>> xb = np.random.random((nb, d)).astype(np.float32)
>>> xq = np.random.random((nq, d)).astype(np.float32)
>>> ids = np.arange(nb) * 10
>>>
>>> index = faiss.IndexFlatL2(d)
>>> custom_index = faiss.IndexIDMap(index)
>>> custom_index.add_with_ids(xb, ids)
>>> custom_index.search(xq, k)
(array([[1.97887421e-05, 2.86698341e-05],
       [1.38282776e-05, 6.81877136e-05],
       [4.52995300e-06, 1.12056732e-05],
       [8.55922699e-05, 9.26256180e-05],
       [1.41859055e-05, 1.38044357e-04],
       [2.40206718e-05, 4.58657742e-05],
       [6.55651093e-06, 3.46302986e-05],
       [5.24520874e-06, 1.35898590e-05],
       [2.90870667e-05, 3.90410423e-05],
       [2.38418579e-07, 2.86102295e-05]], dtype=float32), array([[38210, 66060],
       [51500, 97890],
       [17100, 97780],
       [42300, 51430],
       [ 3790, 63660],
       [19050, 26470],
       [22070, 45900],
       [39140,  4190],
       [10040,  7850],
       [14390, 48690]]))

Il y a une mise en garde ici. Puisque faiss.IndexIDMap ne mappe que le système d'index et d'ID d'origine, si vous utilisez une ancienne version de faiss, vous obtiendrez une erreur de segmentation dans les situations suivantes où l'index peut être GC. La dernière version de faiss (1.5.3) semble résoudre ce problème.

>>> index = faiss.IndexIDMap(faiss.IndexFlatL2(d))
>>> index.add_with_ids(xb, ids)
Segmentation fault (core dumped)

Soit dit en passant, il n'est pas très utile, mais vous pouvez également rechercher l'index d'origine.

>>> index.search(xq, k)
(array([[1.97887421e-05, 2.86698341e-05],
       [1.38282776e-05, 6.81877136e-05],
       [4.52995300e-06, 1.12056732e-05],
       [8.55922699e-05, 9.26256180e-05],
       [1.41859055e-05, 1.38044357e-04],
       [2.40206718e-05, 4.58657742e-05],
       [6.55651093e-06, 3.46302986e-05],
       [5.24520874e-06, 1.35898590e-05],
       [2.90870667e-05, 3.90410423e-05],
       [2.38418579e-07, 2.86102295e-05]], dtype=float32), array([[3821, 6606],
       [5150, 9789],
       [1710, 9778],
       [4230, 5143],
       [ 379, 6366],
       [1905, 2647],
       [2207, 4590],
       [3914,  419],
       [1004,  785],
       [1439, 4869]]))

Résumé

J'ai introduit une petite fonction de niche de faiss, qui est une bibliothèque de recherche de quartier de Facebook Resarch, avec un rappel pour moi-même. Il semble que ce sera un article de niche à mi-chemin et qui le lira ... mais j'espère pouvoir jouer le rôle d'une seule personne. Je continuerai à l'ajouter si j'essaye une petite fonction de niche de faiss.

Recommended Posts

Une petite introduction de fonction de niche de faiss
Un petit examen minutieux de Pandas 1.0 et Dask
Introduction d'une nouvelle bibliothèque d'extraction de fonctionnalités vocales Surfboard
Introduction de PyGMT
Introduction de Python
Ajouter une liste de fonctions de bibliothèque numpy petit à petit --a
[PyTorch] Un peu de compréhension de CrossEntropyLoss avec des formules mathématiques
[python] [Gracenote Web API] Une petite personnalisation de pygn
Introduction de trac (Windows + trac 1.0.10)
Introduction de ferenOS 1 (installation)
Introduction du wrapper Virtualenv
Ajouter une liste de fonctions de bibliothèque numpy petit à petit --c
[Introduction à AWS] Mémorandum de création d'un serveur Web sur AWS
J'ai essayé un peu le comportement de la fonction zip