Le parcours vers l’optimisation de la recherche d’images à l’échelle du milliard (2/2)
Cet article est la deuxième partie de The Journey to Optimizing Billion-scale Image Search by UPYUN. Si vous avez manqué la première, cliquez ici.
Le système de recherche par image de deuxième génération
Le système de recherche par image de deuxième génération choisit techniquement la solution CNN + Milvus. Le système est basé sur des vecteurs de caractéristiques et offre un meilleur support technique.
Extraction de caractéristiques
Dans le domaine de la vision par ordinateur, l’utilisation de l’intelligence artificielle est devenue la norme. De même, l’extraction de caractéristiques du système de recherche par image de deuxième génération utilise un réseau neuronal convolutif (CNN) comme technologie sous-jacente
Le terme CNN est difficile à comprendre. Ici, nous nous concentrons sur la réponse à deux questions :
- Que peut faire un CNN ?
- Pourquoi puis-je utiliser un CNN pour une recherche d’images ?
Photo par memegenerator.net
Il existe de nombreuses compétitions dans le domaine de l’IA, et la classification d’images est l’une des plus importantes. Le rôle de la classification d’images est de déterminer si le contenu de l’image concerne un chat, un chien, une pomme, une poire ou d’autres types d’objets.
Que peut faire un CNN ? Il peut extraire des caractéristiques et reconnaître des objets. Il extrait des caractéristiques à partir de plusieurs dimensions et mesure à quel point les caractéristiques d’une image sont proches des caractéristiques des chats ou des chiens. Nous pouvons choisir les plus proches comme résultat d’identification, ce qui indique si le contenu d’une image spécifique concerne un chat, un chien ou autre chose.
Quel est le lien entre la fonction d’identification d’objets du CNN et la recherche par image ? Ce que nous voulons n’est pas le résultat final d’identification, mais le vecteur de caractéristiques extrait à partir de plusieurs dimensions. Les vecteurs de caractéristiques de deux images au contenu similaire doivent être proches.
Quel modèle CNN devrais-je utiliser ?
La réponse est VGG16. Pourquoi le choisir ? Premièrement, VGG16 a une bonne capacité de généralisation, c’est-à-dire qu’il est très polyvalent. Deuxièmement, les vecteurs de caractéristiques extraits par VGG16 ont 512 dimensions. S’il y a très peu de dimensions, la précision peut être affectée. S’il y a trop de dimensions, le coût de stockage et de calcul de ces vecteurs de caractéristiques est relativement élevé.
Utiliser un CNN pour extraire les caractéristiques d’images est une solution courante. Nous pouvons utiliser VGG16 comme modèle et Keras + TensorFlow pour l’implémentation technique. Voici l’exemple officiel de Keras :
from keras.applications.vgg16 import VGG16
from keras.preprocessing import image
from keras.applications.vgg16 import preprocess_input
import numpy as np
model = VGG16(weights=’imagenet’, include_top=False)
img_path = ‘elephant.jpg’
img = image.load_img(img_path, target_size=(224, 224))
x = image.img_to_array(img)
x = np.expand_dims(x, axis=0)
x = preprocess_input(x)
features = model.predict(x)
Les caractéristiques extraites ici sont des vecteurs de caractéristiques.
1. Normalisation
Pour faciliter les opérations ultérieures, nous normalisons souvent les caractéristiques :
Ce qui est utilisé par la suite est également le norm_feat normalisé.
2. Description de l’image
L’image est chargée à l’aide de la méthode image.load_img de keras.preprocessing :
from keras.preprocessing import image
img_path = 'elephant.jpg'
img = image.load_img(img_path, target_size=(224, 224))
En fait, il s’agit de la méthode TensorFlow appelée par Keras. Pour plus de détails, consultez la documentation TensorFlow. L’objet image final est en réalité une instance PIL Image (la PIL utilisée par TensorFlow).
3. Conversion en octets
En pratique, le contenu des images est souvent transmis via le réseau. Par conséquent, au lieu de charger les images depuis un chemin, nous préférons convertir directement les données en octets en objets image, c’est-à-dire en PIL Images :
import io
from PIL import Image
# img_bytes: 图片内容 bytes
img = Image.open(io.BytesIO(img_bytes))
img = img.convert('RGB')
img = img.resize((224, 224), Image.NEAREST)
L’img ci-dessus est la même que le résultat obtenu par la méthode image.load_img. Il y a deux choses auxquelles faire attention :
- Vous devez effectuer une conversion RGB.
- Vous devez redimensionner (resize est le deuxième paramètre de la
méthode load_img).
4. Traitement des bordures noires
Les images, telles que les captures d’écran, peuvent parfois comporter un certain nombre de bordures noires. Ces bordures noires n’ont aucune valeur pratique et provoquent beaucoup d’interférences. Pour cette raison, supprimer les bordures noires est également une pratique courante.
Une bordure noire est essentiellement une ligne ou une colonne de pixels où tous les pixels sont (0, 0, 0) (image RGB). Supprimer la bordure noire consiste à trouver ces lignes ou colonnes et à les supprimer. Il s’agit en fait d’une multiplication de matrice 3-D dans NumPy.
Un exemple de suppression des bordures noires horizontales :
# -*- coding: utf-8 -*-
import numpy as np
from keras.preprocessing import image
def RemoveBlackEdge(img):
Args:
img: PIL image instance
Returns:
PIL image instance
"""
width = img.width
img = image.img_to_array(img)
img_without_black = img[~np.all(img == np.zeros((1, width, 3), np.uint8), axis=(1, 2))]
img = image.array_to_img(img_without_black)
return img
C’est à peu près ce dont je voulais parler concernant l’utilisation de CNN pour extraire des caractéristiques d’image et implémenter d’autres traitements d’image. Maintenant, examinons les moteurs de recherche vectorielle.
Moteur de recherche vectorielle
Le problème de l’extraction de vecteurs de caractéristiques à partir d’images a été résolu. Les problèmes restants sont donc :
- Comment stocker les vecteurs de caractéristiques ?
- Comment calculer la similarité des vecteurs de caractéristiques, c’est-à-dire comment rechercher ? Le moteur de recherche vectorielle open source Milvus peut résoudre ces deux problèmes. Jusqu’à présent, il fonctionne bien dans notre environnement de production.
Logo Milvus.
Milvus, le moteur de recherche vectorielle
Extraire des vecteurs de caractéristiques d’une image est loin d’être suffisant. Nous devons également gérer dynamiquement ces vecteurs de caractéristiques (ajout, suppression et mise à jour), calculer la similarité des vecteurs et renvoyer les données vectorielles dans la plage des plus proches voisins. Le moteur de recherche vectorielle open source Milvus effectue ces tâches très efficacement.
Le reste de cet article décrira les pratiques spécifiques et les points à noter.
1. Exigences relatives au CPU
Pour utiliser Milvus, votre CPU doit prendre en charge le jeu d’instructions avx2. Pour les systèmes Linux, utilisez la commande suivante pour vérifier quels jeux d’instructions votre CPU prend en charge :
cat /proc/cpuinfo | grep flags</code?
Vous obtenez alors quelque chose comme :
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm abm cpuid_fault epb invpcid_single pti intel_ppin tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm ida arat pln pts
Ce qui suit flags correspond aux jeux d’instructions pris en charge par votre CPU. Bien sûr, il y en a bien plus que ce dont j’ai besoin. Je veux simplement voir si un jeu d’instructions spécifique, comme avx2, est pris en charge. Il suffit d’ajouter un grep pour le filtrer :
cat /proc/cpuinfo | grep flags | grep avx2
Si aucun résultat n’est renvoyé, cela signifie que ce jeu d’instructions spécifique n’est pas pris en charge. Vous devez alors changer de machine.
2. Planification de la capacité
La planification de la capacité est notre première considération lorsque nous concevons un système. Quelle quantité de données devons-nous stocker ? Quelle quantité de mémoire et d’espace disque les données nécessitent-elles ?
Faisons quelques calculs rapides. Chaque dimension d’un vecteur est float32. Un type float32 occupe 4 Bytes. Un vecteur de 512 dimensions nécessite donc 2 KB de stockage. De la même manière :
- Mille vecteurs de 512 dimensions nécessitent 2 Mo de stockage.
- Un million de vecteurs de 512 dimensions nécessitent 2 Go de stockage.
- 10 millions de vecteurs de 512 dimensions nécessitent 20 Go de stockage.
- 100 millions de vecteurs de 512 dimensions nécessitent 200 Go de stockage.
- Un milliard de vecteurs de 512 dimensions nécessitent 2 To de stockage.
Si nous voulons stocker toutes les données en mémoire, le système a alors besoin d’au moins la capacité mémoire correspondante.
Il est recommandé d’utiliser l’outil officiel de calcul de taille : Milvus sizing tool.
En réalité, notre mémoire peut ne pas être aussi importante. (Cela n’a pas vraiment d’importance si vous n’avez pas assez de mémoire. Milvus vide automatiquement les données sur le disque.) En plus des données vectorielles d’origine, nous devons également prendre en compte le stockage d’autres données telles que les journaux.
3. Configuration du système
Pour plus d’informations sur la configuration du système, consultez la documentation Milvus :
- Configuration du serveur Milvus : https://milvus.io/docs/v0.10.1/milvus_config.md
4. Conception de la base de données
Collection et partition
- Collection est également appelée table.
- Partition désigne les partitions au sein d’une collection.
L’implémentation sous-jacente d’une partition est en réalité la même que celle d’une collection, à ceci près qu’une partition se trouve sous une collection. Mais avec les partitions, l’organisation des données devient plus flexible. Nous pouvons également interroger une partition spécifique dans une collection afin d’obtenir de meilleurs résultats de requête.
Combien de collections et de partitions pouvons-nous avoir ? Les informations de base sur les collections et les partitions se trouvent dans les métadonnées. Milvus utilise soit SQLite (intégration interne à Milvus), soit MySQL (nécessite une connexion externe) pour la gestion interne des métadonnées. Si vous utilisez SQLite par défaut pour gérer les métadonnées, vous subirez une forte perte de performance lorsque le nombre de collections et de partitions est trop élevé. Par conséquent, le nombre total de collections et de partitions ne doit pas dépasser 50 000 (Milvus 0.8.0 limitera ce nombre à 4 096). Si vous devez définir un nombre plus élevé, il est recommandé d’utiliser MySQL via une connexion externe.
La structure de données prise en charge par les collections et les partitions de Milvus est très simple, à savoir ID + vector. En d’autres termes, il n’y a que deux colonnes dans la table : l’ID et les données vectorielles.
Remarque :
- L’ID doit être un entier.
- Nous devons nous assurer que l’ID est unique au sein d’une collection et non au sein d’une partition.
Filtrage conditionnel
Lorsque nous utilisons des bases de données traditionnelles, nous pouvons spécifier des valeurs de champs comme conditions de filtrage. Bien que Milvus ne filtre pas exactement de la même manière, nous pouvons implémenter un filtrage conditionnel simple à l’aide des collections et des partitions. Par exemple, nous avons une grande quantité de données d’images et ces données appartiennent à des utilisateurs spécifiques. Nous pouvons alors diviser les données en partitions par utilisateur. Ainsi, utiliser l’utilisateur comme condition de filtrage revient en fait à spécifier la partition.
Données structurées et mappage vectoriel
Milvus ne prend en charge que la structure de données ID + vecteur. Mais dans les scénarios métier, ce dont nous avons besoin, ce sont des données structurées porteuses de sens métier. En d’autres termes, nous devons trouver des données structurées au moyen de vecteurs. Par conséquent, nous devons maintenir les relations de mappage entre les données structurées et les vecteurs via l’ID.
ID des données structurées <--> table de mappage <--> ID Milvus
Sélection de l’index
Vous pouvez consulter les articles suivants :
- Types d’index : https://www.milvus.io/docs/v0.10.1/index.md
- Comment choisir un index : https://medium.com/@milvusio/how-to-choose-an-index-in-milvus-4f3d15259212
5. Traitement des résultats de recherche
Les résultats de recherche de Milvus sont un ensemble d’ID + distance :
- ID : l’ID dans une collection.
- Distance : une valeur de distance de 0 à 1 indique le niveau de similarité ; plus la valeur est petite, plus les deux vecteurs sont similaires.
Filtrage des données dont l’ID est -1
Lorsque le nombre de collections est trop faible, les résultats de recherche peuvent contenir des données dont l’ID est -1. Nous devons les filtrer nous-mêmes.
Pagination
La recherche de vecteurs est assez différente. Les résultats de la requête sont triés par ordre décroissant de similarité, et les résultats les plus similaires (topK) sont sélectionnés (topK est spécifié par l’utilisateur au moment de la requête).
Milvus ne prend pas en charge la pagination. Nous devons implémenter nous-mêmes la fonction de pagination si nous en avons besoin pour l’activité. Par exemple, si nous avons dix résultats sur chaque page et que nous voulons afficher uniquement la troisième page, nous devons spécifier que topK = 30 et ne renvoyer que les dix derniers résultats.
Seuil de similarité pour l’activité
La distance entre les vecteurs de deux images est comprise entre 0 et 1. Si nous voulons décider si deux images sont similaires dans un scénario métier spécifique, nous devons spécifier un seuil dans cette plage. Les deux images sont similaires si la distance est inférieure au seuil, ou elles sont très différentes l’une de l’autre si la distance est supérieure au seuil. Vous devez ajuster le seuil pour répondre à vos propres besoins métier.
Cet article est rédigé par rifewang, utilisateur de Milvus et ingénieur logiciel chez UPYUN. Si vous aimez cet article, n’hésitez pas à venir dire bonjour @ https://github.com/rifewang.
Continuer à lire

Milvus 2.6.x Now Generally Available on Zilliz Cloud, Making Vector Search Faster, Smarter, and More Cost-Efficient for Production AI
Milvus 2.6.x is now GA on Zilliz Cloud, delivering faster vector search, smarter hybrid queries, and lower costs for production RAG and AI applications.

AI Agents Are Quietly Transforming E-Commerce — Here’s How
Discover how AI agents transform e-commerce with autonomous decision-making, enhanced product discovery, and vector search capabilities for today's retailers.

Selecting the Right ETL Tools for Unstructured Data to Prepare for AI
Learn the right ETL tools for unstructured data to power AI. Explore key challenges, tool comparisons, and integrations with Milvus for vector search.



