FAISS: trouvez rapidement des visages et des clones sur des données de plusieurs millions de dollars





Une fois, à la veille d'une conférence clients organisée chaque année par le groupe DAN, nous avons réfléchi à la possibilité de penser à des choses intéressantes pour que nos partenaires et clients aient des impressions et des souvenirs agréables de l'événement. Nous avons décidé d'analyser une archive de milliers de photos de cette conférence et de plusieurs précédentes (et il y en avait 18 à ce moment-là): une personne nous envoie sa photo, et en quelques secondes nous lui envoyons une sélection de photos avec lui issues de nos archives depuis plusieurs années.



Nous n'avons pas inventé un vélo, nous avons pris la célèbre bibliothèque dlib et avons reçu des plongements (représentations vectorielles) de chaque personne. 



Nous avons ajouté un bot Telegram pour plus de commodité, et tout était super. Du point de vue des algorithmes de reconnaissance faciale, tout fonctionnait à merveille, mais la conférence était terminée et je ne voulais pas me séparer des technologies éprouvées. Nous voulions passer de plusieurs milliers de personnes à des centaines de millions, mais nous n'avions pas de mission commerciale spécifique. Après un certain temps, nos collègues ont eu une tâche qui nécessitait de travailler avec des quantités de données aussi importantes.



La question était d'écrire un système de surveillance de bot intelligent à l'intérieur du réseau Instagram. Ici, notre réflexion a donné lieu à une approche simple et complexe: La



manière simple: considérez tous les comptes qui ont beaucoup plus d'abonnements que d'abonnés, pas d'avatars, le nom complet n'est pas rempli, etc. En conséquence, nous obtenons une foule incompréhensible de comptes à moitié morts.



La manière difficile: puisque les robots modernes sont devenus beaucoup plus intelligents et qu'ils publient maintenant des messages, dorment et même écrivent du contenu, la question se pose: comment les attraper? Tout d'abord, surveillez de près leurs amis, car ce sont souvent des robots, et suivez les photos en double. Deuxièmement, rarement un bot sait comment générer ses propres images (bien que cela soit possible), ce qui signifie que les photos en double de personnes dans différents comptes sur Instagram sont un bon déclencheur pour trouver un réseau de robots.



Et après?



Si un chemin simple est assez prévisible et donne rapidement des résultats, alors un chemin difficile est difficile précisément parce que pour le mettre en œuvre, nous devrons vectoriser et indexer des volumes incroyablement importants de photographies pour des comparaisons ultérieures de similitudes - des millions, voire des milliards. Comment le mettre en pratique? Après tout, des questions techniques se posent:



  1. Vitesse et précision de recherche
  2. Espace disque utilisé par les données
  3. La taille de la RAM utilisée. 


S'il n'y avait que quelques photos, au moins pas plus de dix mille, nous pourrions nous limiter à des solutions simples avec regroupement de vecteurs, mais pour travailler avec d'énormes volumes de vecteurs et trouver les voisins les plus proches d'un certain vecteur, des algorithmes complexes et optimisés sont nécessaires.



Il existe des technologies bien connues et éprouvées, telles que Annoy, FAISS, HNSW. L'algorithme de recherche rapide des voisins HNSW disponible dans les bibliothèques nmslib et hnswlib, montre des résultats de pointe sur le processeur, qui peuvent être vus à partir des mêmes benchmarks. Mais nous le coupons tout de suite, car nous ne sommes pas satisfaits de la quantité de mémoire utilisée lorsque nous travaillons avec de très grandes quantités de données. Nous avons commencé à choisir entre Annoy et FAISS et avons finalement choisi FAISS pour des raisons de commodité, moins d'utilisation de la mémoire, une utilisation potentielle sur le GPU et des critères de performance (vous pouvez voir, par exemple, ici ). D'ailleurs, dans FAISS, l'algorithme HNSW est implémenté en option.



Qu'est-ce que FAISS?



Facebook AI Research Similarity Search - Développé par l'équipe Facebook AI Research pour trouver rapidement les voisins les plus proches et les regrouper dans l'espace vectoriel. La recherche à grande vitesse vous permet de travailler avec de très grandes données - jusqu'à plusieurs milliards de vecteurs.



Le principal avantage de FAISS est ses résultats de pointe sur le GPU, tandis que son implémentation sur le CPU est légèrement inférieure à hnsw (nmslib). Nous voulions pouvoir rechercher à la fois sur le CPU et le GPU. De plus, FAISS est optimisé en termes d'utilisation de la mémoire et de recherche sur de grands lots.



La source



FAISS permet de trouver rapidement les k vecteurs les plus proches pour un vecteur x donné. Mais comment fonctionne cette recherche sous le capot?



Indices



Le concept principal de FAISS est l' index et, en fait, il ne s'agit que d'une collection de paramètres et de vecteurs. Les jeux de paramètres sont complètement différents et dépendent des besoins de l'utilisateur. Les vecteurs peuvent rester inchangés ou être réorganisés. Certains indices sont disponibles pour le travail immédiatement après leur avoir ajouté des vecteurs, et certains nécessitent une formation préalable. Les noms de vecteur sont stockés dans l'index: soit dans la numérotation de 0 à n, soit sous la forme d'un nombre qui correspond au type Int64.



Le premier index, et le plus simple que nous avons utilisé lors de la conférence, est Flat . Il ne stocke que tous les vecteurs en lui-même, et la recherche d'un vecteur donné est effectuée par recherche exhaustive, il n'est donc pas nécessaire de l'entraîner (mais plus sur l'entraînement ci-dessous). Avec une petite quantité de données, un index aussi simple peut couvrir entièrement les besoins de la recherche.



Exemple:



import numpy as np
dim = 512  #     512
nb = 10000  #    
nq = 5 #      
np.random.seed(228)
vectors = np.random.random((nb, dim)).astype('float32')
query = np.random.random((nq, dim)).astype('float32')


Créez un index plat et ajoutez des vecteurs sans formation:



import faiss
index = faiss.IndexFlatL2(dim)
print(index.ntotal)  #   
index.add(vectors)
print(index.ntotal)  #    10 000 


Nous trouvons maintenant 7 voisins les plus proches pour les cinq premiers vecteurs à partir de vecteurs:



topn = 7
D, I = index.search(vectors[:5], topn)  #  : Distances, Indices
print(I)
print(D)


Production
[[0 5662 6778 7738 6931 7809 7184]
 [1 5831 8039 2150 5426 4569 6325]
 [2 7348 2476 2048 5091 6322 3617]
 [3  791 3173 6323 8374 7273 5842]
 [4 6236 7548  746 6144 3906 5455]]

[[ 0.  71.53578  72.18823  72.74326  73.2243   73.333244 73.73317 ]
 [ 0.  67.604805 68.494774 68.84221  71.839905 72.084335 72.10817 ]
 [ 0.  66.717865 67.72709  69.63666  70.35903  70.933304 71.03237 ]
 [ 0.  68.26415  68.320595 68.82381  68.86328  69.12087  69.55179 ]
 [ 0.  72.03398  72.32417  73.00308  73.13054  73.76181  73.81281 ]]




On voit que les plus proches voisins avec une distance de 0 sont les vecteurs eux-mêmes, les autres sont rangés en augmentant la distance. Cherchons nos vecteurs à partir de la requête:



D, I = index.search(query, topn) 
print(I)
print(D)


Production
[[2467 2479 7260 6199 8640 2676 1767]
 [2623 8313 1500 7840 5031   52 6455]
 [1756 2405 1251 4136  812 6536  307]
 [3409 2930  539 8354 9573 6901 5692]
 [8032 4271 7761 6305 8929 4137 6480]]

[[73.14189  73.654526 73.89804  74.05615  74.11058  74.13567  74.443436]
 [71.830215 72.33813  72.973885 73.08897  73.27939  73.56996  73.72397 ]
 [67.49588  69.95635  70.88528  71.08078  71.715965 71.76285  72.1091  ]
 [69.11357  69.30089  70.83269  71.05977  71.3577   71.62457  71.72549 ]
 [69.46417  69.66577  70.47629  70.54611  70.57645  70.95326  71.032005]]




Maintenant, les distances dans la première colonne des résultats ne sont pas nulles, car les vecteurs de la requête ne sont pas dans l'index.



L'index peut être enregistré sur le disque puis chargé à partir du disque:



faiss.write_index(index, "flat.index")
index = faiss.read_index("flat.index")


Il semblerait que tout soit élémentaire! Quelques lignes de code - et nous avons déjà une structure de recherche par vecteurs de grande dimension. Mais un tel index avec seulement des dizaines de millions de vecteurs de dimension 512 pèsera environ 20 Go et occupera autant de RAM lorsqu'il est utilisé.



Dans le projet de la conférence, nous avons utilisé une telle approche de base avec un index plat, tout était super en raison de la quantité relativement faible de données, mais maintenant nous parlons de dizaines et de centaines de millions de vecteurs de haute dimension!



Accélérez les recherches avec les listes inversées





Source



La caractéristique principale et la plus cool de FAISS est l'index de FIV, ouindex de fichier inversé . L'idée des fichiers inversés est laconique et magnifiquement expliquée sur les doigts :



imaginons une armée gigantesque, composée des guerriers les plus hétéroclites, comptant, disons, 1 000 000 de personnes. Il sera impossible de commander toute l'armée à la fois. Comme il est d'usage dans les affaires militaires, nous devons diviser notre armée en sous-unités. Divisons par1000000=1000parties à peu près égales, en choisissant pour le rôle des commandants un représentant de chaque unité. Et nous essaierons d'envoyer des caractères aussi similaires que possible en termes de caractère, d'origine, de données physiques, etc. soldats dans une unité, et nous sélectionnons le commandant afin qu'il représente son unité aussi précisément que possible - il était quelqu'un de «moyen». En conséquence, notre tâche a été réduite de commander un million de soldats à commander 1 000 unités par l'intermédiaire de leurs commandants, et nous avons une excellente idée de la composition de notre armée, car nous savons à quoi ressemblent les commandants.



C'est l'idée derrière l'indice FIV: regroupons un grand ensemble de vecteurs pièce par pièce en utilisant l'algorithme k-means, mettant en correspondance chaque partie un centroïde, est un vecteur qui est le centre choisi pour un cluster donné. La recherche sera effectuée à travers la distance minimale au centre de gravité, et ce n'est qu'alors que nous rechercherons les distances minimales parmi les vecteurs du cluster qui correspond au centre de gravité donné. Prendre k égalnn - le nombre de vecteurs dans l'index, nous obtiendrons une recherche optimale à deux niveaux - premier parmi n centroïde puis parmi nvecteurs dans chaque cluster. La recherche, par rapport à la recherche exhaustive, est accélérée plusieurs fois, ce qui résout l'un de nos problèmes lorsque l'on travaille avec plusieurs millions de vecteurs.





L'espace vectoriel est divisé par la méthode des k-moyennes en k grappes. Chaque cluster se voit attribuer un



code d'exemple de centre de gravité:



dim = 512
k = 1000  #  “”
quantiser = faiss.IndexFlatL2(dim) 
index = faiss.IndexIVFFlat(quantiser, dim, k)
vectors = np.random.random((1000000, dim)).astype('float32')  # 1 000 000 “”


Ou vous pouvez l'écrire beaucoup plus élégamment, en utilisant la chose FAISS pratique pour construire un index:



index = faiss.index_factory(dim, “IVF1000,Flat”)
 :
print(index.is_trained)   # False.
index.train(vectors)  # Train    
 
#  ,      ,      :
print(index.is_trained)  # True
print(index.ntotal)   # 0
index.add(vectors)
print(index.ntotal)   # 1000000


Après avoir examiné ce type d'index après Flat, nous avons résolu l'un de nos problèmes potentiels: la vitesse de recherche, qui devient plusieurs fois plus lente par rapport à une recherche complète.



D, I = index.search(query, topn) 
print(I)
print(D)


Production
[[19898 533106 641838 681301 602835 439794 331951]
 [654803 472683 538572 126357 288292 835974 308846]
 [588393 979151 708282 829598  50812 721369 944102]
 [796762 121483 432837 679921 691038 169755 701540]
 [980500 435793 906182 893115 439104 298988 676091]]

[[69.88127  71.64444  72.4655   72.54283  72.66737  72.71834  72.83057]
 [72.17552  72.28832  72.315926 72.43405  72.53974  72.664055 72.69495]
 [67.262115 69.46998  70.08826  70.41119  70.57278  70.62283  71.42067]
 [71.293045 71.6647   71.686615 71.915405 72.219505 72.28943  72.29849]
 [73.27072  73.96091  74.034706 74.062515 74.24464  74.51218  74.609695]]




Mais il y a un "mais" - la précision de la recherche, ainsi que la vitesse, dépendra du nombre de clusters visités, qui peut être défini à l'aide du paramètre nprobe:



print(index.nprobe)  # 1 –           
index.nprobe = 16  #   -16    top-n  
D, I = index.search(query, topn) 
print(I)
print(D)


Production
[[ 28707 811973  12310 391153 574413  19898 552495]
 [540075 339549 884060 117178 878374 605968 201291]
 [588393 235712 123724 104489 277182 656948 662450]
 [983754 604268  54894 625338 199198  70698  73403]
 [862753 523459 766586 379550 324411 654206 871241]]

[[67.365585 67.38003  68.17187  68.4904   68.63618  69.88127  70.3822]
 [65.63759  67.67015  68.18429  68.45782  68.68973  68.82755  69.05]
 [67.262115 68.735535 68.83473  68.88733  68.95465  69.11365  69.33717]
 [67.32007  68.544685 68.60204  68.60275  68.68633  68.933334 69.17106]
 [70.573326 70.730286 70.78615  70.85502  71.467674 71.59512  71.909836]]




Comme vous pouvez le voir, après avoir augmenté nprobe, nous avons des résultats complètement différents, le sommet des distances les plus courtes en D est devenu meilleur.



Vous pouvez prendre nprobe égal au nombre de centroïdes dans l'index, alors cela équivaudra à une recherche par force brute, la précision sera la plus élevée, mais la vitesse de recherche diminuera sensiblement.



Recherche sur le disque - Listes inversées sur disque



Très bien, nous avons résolu le premier problème, maintenant nous obtenons une vitesse de recherche acceptable sur des dizaines de millions de vecteurs! Mais tout cela est inutile tant que notre énorme index ne rentre pas dans la RAM.



Spécifiquement pour notre tâche, le principal avantage de FAISS est la possibilité de stocker des listes inversées de l'index IVF sur le disque, en ne chargeant que les métadonnées dans la RAM.



Comment créer un tel index: nous formons indexIVF avec les paramètres nécessaires sur la quantité maximale possible de données qui tiennent en mémoire, puis ajoutons des vecteurs (ceux qui ont été entraînés et pas seulement) à l'index entraîné par parties et écrivons l'index pour chacune des parties sur le disque.



index = faiss.index_factory(512, “,IVF65536, Flat”, faiss.METRIC_L2)


La formation sur l'indice GPU est effectuée de cette manière:



res = faiss.StandardGpuResources()
index_ivf = faiss.extract_index_ivf(index)
index_flat = faiss.IndexFlatL2(512)
clustering_index = faiss.index_cpu_to_gpu(res, 0, index_flat)  #  0 –  GPU
index_ivf.clustering_index = clustering_index


faiss.index_cpu_to_gpu (res, 0, index_flat) peut être remplacé par faiss.index_cpu_to_all_gpus (index_flat) pour utiliser tous les GPU ensemble.



Il est hautement souhaitable que l'échantillon d'apprentissage soit aussi représentatif que possible et ait une distribution uniforme, c'est pourquoi nous pré-composons l'ensemble de données d'apprentissage à partir du nombre requis de vecteurs, en les sélectionnant au hasard dans l'ensemble de données.



train_vectors = ...  #     
index.train(train_vectors)

#    ,   :
faiss.write_index(index, "trained_block.index") 

#       
#      :
for bno in range(first_block, last_block+ 1):
    block_vectors = vectors_parts[bno]
    block_vectors_ids = vectors_parts_ids[bno]  # id ,  
    index = faiss.read_index("trained_block.index")
    index.add_with_ids(block_vectors, block_vectors_ids)
    faiss.write_index(index, "block_{}.index".format(bno))


Après cela, nous unissons toutes les listes inversées. Ceci est possible, puisque chacun des blocs est essentiellement le même index entraîné, juste avec différents vecteurs à l'intérieur.



ivfs = []
for bno in range(first_block, last_block+ 1):
    index = faiss.read_index("block_{}.index".format(bno), faiss.IO_FLAG_MMAP)
    ivfs.append(index.invlists)
    #  index   inv_lists 
    #        :
    index.own_invlists = False

#   :
index = faiss.read_index("trained_block.index")

#   invlists
#  invlists       merged_index.ivfdata
invlists = faiss.OnDiskInvertedLists(index.nlist, index.code_size, "merged_index.ivfdata")
ivf_vector = faiss.InvertedListsPtrVector() 

for ivf in ivfs: 
    ivf_vector.push_back(ivf)

ntotal = invlists.merge_from(ivf_vector.data(), ivf_vector.size())
index.ntotal = ntotal  #     
index.replace_invlists(invlists)  
faiss.write_index(index, data_path + "populated.index")  #    


Bottom line: maintenant notre index est populated.index et fichiers merged_blocks.ivfdata .



Dans populated.index enregistré le chemin complet d'origine vers le fichier avec les listes inversées, donc si le chemin du fichier ivfdata pour une raison quelconque, le changement de lecture de l'index devra utiliser l'indicateur faiss.IO_FLAG_ONDISK_SAME_DIR , qui vous permet de rechercher le fichier ivfdata dans le même répertoire que le populated.index:



index = faiss.read_index('populated.index', faiss.IO_FLAG_ONDISK_SAME_DIR)


L' exemple de démonstration de Github du projet FAISS a été pris comme base .



Un mini-guide pour choisir un index peut être trouvé dans le FAISS Wiki . Par exemple, nous avons pu intégrer un ensemble de données d'apprentissage de 12 millions de vecteurs dans la RAM, nous avons donc choisi l'indice IVFFlat sur 262144 centroïdes pour ensuite passer à des centaines de millions. Le guide propose également d'utiliser l'index IVF262144_HNSW32, dans lequel le vecteur appartenant au cluster est déterminé par l'algorithme HNSW avec 32 voisins les plus proches (en d'autres termes, le quantificateur IndexHNSWFlat est utilisé), mais, comme il nous a semblé lors de tests ultérieurs, la recherche d'un tel indice est moins précise. En outre, il convient de garder à l'esprit qu'un tel quantificateur élimine la possibilité d'utilisation sur le GPU.



Divulgacher:
on disk inverted lists FAISS . RAM , , , RAM . FAISS wiki Github , .





Utilisation du disque considérablement réduite grâce à la quantification du produit



Grâce à la méthode de recherche sur disque, il était possible de supprimer la charge de la RAM, mais l'index avec un million de vecteurs occupait encore environ 2 Go d'espace disque, et nous discutons de la possibilité de travailler avec des milliards de vecteurs, ce qui nécessiterait plus de deux To! Bien sûr, le volume n'est pas si important si vous définissez un objectif et allouez de l'espace disque supplémentaire, mais cela nous dérange un peu.



Et ici, le codage vectoriel vient à la rescousse, à savoir la quantification scalaire (SQ) et la quantification de produit (PQ)... SQ est le codage de chaque composante vectorielle avec n bits (généralement 8, 6 ou 4 bits). Nous considérerons l'option PQ, car l'idée d'encoder un composant float32 avec huit bits semble trop déprimante en termes de perte de précision. Bien que dans certains cas, la compression de SQfp16 en float16 soit presque sans perte de précision.



L'essence de la quantification du produit est la suivante: les vecteurs de dimension 512 sont divisés en n parties, dont chacune est regroupée en 256 clusters possibles (1 octet), c'est-à-dire nous représentons un vecteur avec n octets, où n est généralement au plus 64 dans une implémentation FAISS. Mais une telle quantification n'est pas appliquée aux vecteurs de l'ensemble de données lui-même, mais aux différences de ces vecteurs et des centroïdes correspondants obtenus lors de la génération des listes inversées! Il s'avère que les listes inversées seront des ensembles de distances codées entre les vecteurs et leurs centroïdes.



index = faiss.index_factory(dim, "IVF262144,PQ64", faiss.METRIC_L2)


Il s'avère que maintenant nous n'avons pas besoin de stocker tous les vecteurs - il suffit d'allouer n octets par vecteur et 2048 octets par vecteur centroïde. Dans notre cas, nous avons prisn=64, c'est à dire 51264=8- la longueur d'un sous-vecteur, qui est définie dans l'un des 256 groupes.







Lors de la recherche par le vecteur x, les centroïdes les plus proches seront déterminés d'abord avec le quantificateur Flat habituel, puis x est également divisé en sous-vecteurs, chacun étant codé par le numéro de l'un des 256 centroïdes correspondants. Et la distance au vecteur est définie comme la somme de 64 distances entre les sous-vecteurs.



Quel est le résultat?



Nous avons arrêté nos expériences sur l'index "IVF262144, PQ64", car il satisfaisait pleinement tous nos besoins en termes de vitesse de recherche et de précision, et nous avons également assuré une utilisation raisonnable de l'espace disque lors de la mise à l'échelle de l'index. Plus précisément, pour le moment, avec 315 millions de vecteurs, l'index occupe 22 Go d'espace disque et environ 3 Go de RAM en cours d'utilisation.



Un autre détail intéressant que nous n'avons pas mentionné précédemment est la métrique utilisée par l'indice. Par défaut, les distances entre deux vecteurs quelconques sont calculées dans la métrique euclidienne L2, ou dans un langage plus compréhensible, les distances sont calculées comme la racine carrée de la somme des carrés des différences de coordonnées. Mais vous pouvez spécifier une autre métrique, en particulier, nous avons testé la métrique METRIC_INNER_PRODUCT, ou métrique des distances de cosinus entre les vecteurs. Il est cosinus car le cosinus de l'angle entre deux vecteurs dans le système de coordonnées euclidien est exprimé comme le rapport du produit scalaire (en termes de coordonnées) des vecteurs au produit de leurs longueurs, et si tous les vecteurs de notre espace ont une longueur 1, alors le cosinus de l'angle sera exactement égal au produit en coordonnées. Dans ce cas, plus les vecteurs sont proches dans l'espace, plus leur produit scalaire sera proche de l'un.



La métrique L2 a une transition mathématique directe vers la métrique du produit scalaire. Cependant, lors de la comparaison expérimentale des deux métriques, l'impression était que la métrique des produits scalaires nous aide à analyser les coefficients de similitude des images de manière plus efficace. De plus, les plongements de nos photos ont été obtenus en utilisantInsightFace , qui implémente l'architecture ArcFace à l' aide de distances cosinus. Il existe également d'autres métriques dans les index FAISS que vous pouvez lire ici .



quelques mots sur le GPU
FAISS GPU , , , . GPU L2. 



, , PQ GPU 56- , float32 float16, .



FAISS GPU , CPU , , GPU . , :



faiss.omp_set_num_threads(N)




Conclusion et exemples curieux



Donc, revenons là où tout a commencé. Et cela a commencé, rappelons-le, avec la motivation de résoudre le problème de la recherche de bots sur le réseau Instagram, et plus précisément - de rechercher des messages en double avec des personnes ou des avatars dans certains groupes d'utilisateurs. Au cours du processus d'écriture du matériel, il est devenu clair qu'une description détaillée de notre méthodologie de recherche de robots nécessiterait un article séparé, que nous discuterons dans les publications suivantes, mais pour l'instant, nous nous limiterons à des exemples de nos expériences avec FAISS.



Vous pouvez vectoriser des images ou des visages de différentes manières, nous avons choisi la technologie InsightFace (vectoriser des images et en extraire des caractéristiques à n dimensions est une longue histoire à part). Au cours des expériences avec l'infrastructure que nous avons obtenue, des propriétés plutôt intéressantes et amusantes ont été découvertes.



Par exemple, après avoir obtenu la permission de collègues et de connaissances, nous avons téléchargé leurs visages dans la recherche et avons rapidement trouvé des photos dans lesquelles ils sont présents:





notre collègue a pris une photo d'un visiteur du Comic-Con, étant à l'arrière-plan dans une foule. Source





Pique-nique dans un grand groupe d'amis, photo du compte d'un ami. Source





Je viens de passer. Un photographe inconnu a capturé les gars pour son profil thématique. Ils ne savaient pas où était passée leur photo et après 5 ans, ils ont complètement oublié comment ils avaient été photographiés. Source





Dans ce cas, le photographe est inconnu et photographié en secret!

A immédiatement rappelé la fille suspecte avec SLR, assise en ce moment devant :) Source




Ainsi, par de simples actions, FAISS vous permet d'assembler sur votre genou un analogue du célèbre FindFace.



Autre particularité intéressante: dans l'indice FAISS, plus les faces se ressemblent, plus les vecteurs correspondants dans l'espace sont proches les uns des autres. J'ai décidé de regarder de plus près les résultats de recherche légèrement moins précis sur mon visage et j'ai trouvé des clones terriblement similaires à moi :)





Certains des clones de l'auteur.

Sources photographiques: 1 , 23 De




manière générale, FAISS ouvre un immense champ pour la mise en œuvre de toute idée créative. Par exemple, en utilisant le même principe de proximité vectorielle de faces similaires, on pourrait construire des chemins d'une personne à une autre. Ou, en dernier recours, faites de FAISS une usine pour la production de tels mèmes:





Source



Merci pour votre attention et nous espérons que ce matériel sera utile aux lecteurs de Habr!



L'article a été écrit avec le soutien de mes collègues Artyom Korolev (Korolevart), Timur Kadyrov et Arina Reshetnikova.



R&D Réseau Dentsu Aegis Russie.



All Articles