Implémentation de la recherche de similarités dans les images dans Elasticsearch

blog-thumb-website-search.png

Apprenez à implémenter facilement dans Elastic la recherche de similarités dans les images. Commencez par configurer l'environnement de l'application. Ensuite, importez le modèle NLP. Enfin, terminez la génération des plongements pour votre ensemble d'images.

Découvrez la présentation de la recherche de similarités dans les images avec Elastic. >> 

Configuration de votre environnement

La première étape consiste à configurer l'environnement pour votre application. La configuration requise comprend les éléments suivants :

  • Git
  • Python 3.9
  • Docker
  • Des centaines d'images

Il est important d'utiliser des centaines d'images pour s'assurer d'obtenir les meilleurs résultats.

Accédez au dossier de travail et consultez le code du référentiel créé. Ensuite, accédez au dossier du référentiel.

$ git clone https://github.com/radoondas/flask-elastic-image-search.git
$ cd flask-elastic-image-search

Comme vous allez utiliser Python pour exécuter le code, vous devez vérifier que l'ensemble des conditions requises sont respectées et que l'environnement est prêt. Maintenant, créez l'environnement virtuel et installez toutes les dépendances.

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

Modèle de plongement et de cluster Elasticsearch

Connectez-vous à votre compte et lancez un cluster Elasticsearch. Configurez un petit cluster comprenant : 

  • un nœud hot avec 2 Go de mémoire ;
  • un nœud de Machine Learning avec 4 Go de mémoire. (La taille de ce nœud est importante, car le modèle NLP que vous importerez dans Elasticsearch consomme environ 1,5 Go de mémoire.)

Une fois votre déploiement prêt, accédez à Kibana et vérifiez la capacité de vos nœuds de Machine Learning. Un nœud de Machine Learning s'affiche. Aucun modèle n'est chargé pour le moment.

Chargez le modèle de plongement CLIP d'OpenAI en utilisant la bibliothèque Eland. Eland est un client Python Elasticsearch pour l'exploration et l'analyse des données dans Elasticsearch qui peut traiter les textes et les images. Vous utiliserez ce modèle pour générer des plongements à partir de l'entrée textuelle et de la requête en images correspondantes. Pour en savoir plus, consultez la documentation de la bibliothèque Eland.

Pour la prochaine étape, vous aurez besoin du point de terminaison Elasticsearch. Vous pouvez l'obtenir dans la console cloud d'Elasticsearch dans la section consacrée aux informations de déploiement.

À l'aide de l'URL du point de terminaison, exécutez la commande suivante dans le répertoire racine du référentiel. Le client Eland se connectera au cluster Elasticsearch et chargera le modèle dans le nœud de Machine Learning. Référez-vous à l'URL de votre cluster avec le paramètre –url. Pour vous donner une idée, l'exemple ci-dessous se réfère à  image-search.es.europe-west1.gcp.cloud.es.io en tant qu'URL de cluster.

--url https://elastic:<password>@image-search.es.europe-west1.gcp.cloud.es.io:443

Saisissez la commande d'importation d'Eland.

$ eland_import_hub_model --url https://elastic:<password>@<URL>:443 \
  --hub-model-id sentence-transformers/clip-ViT-B-32-multilingual-v1 \
  --task-type text_embedding --ca-certs app/conf/ess-cloud.cer \
  --start

Le résultat obtenu sera similaire à ce qui suit.

2022-12-12 13:40:52,308 INFO : Establishing connection to Elasticsearch
2022-12-12 13:40:52,327 INFO : Connected to cluster named 'image-search-8.6.1' (version: 8.5.3)
2022-12-12 13:40:52,328 INFO : Loading HuggingFace transformer tokenizer and model 'sentence-transformers/clip-ViT-B-32-multilingual-v1'
2022-12-12 13:41:03,032 INFO : Creating model with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'
2022-12-12 13:41:03,050 INFO : Uploading model definition
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:42<00:00,  3.01 parts/s]
2022-12-12 13:41:45,902 INFO : Uploading model vocabulary
2022-12-12 13:41:46,120 INFO : Starting model deployment
2022-12-12 13:41:52,825 INFO : Model successfully imported with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'

Le chargement pourrait prendre quelques minutes, en fonction de votre connexion. Une fois terminé, vérifiez la liste des modèles entraînés sur la page de Kibana dédiée au Machine Learning : Menu -> Analytics (Analyse) -> Machine Learning -> Model management (Gestion des modèles) -> Trained models (Modèles entraînés). Vérifiez que l'état du modèle NLP Clip est bien started (commencé).

Si un message s'affiche à l'écran (par exemple, synchronisation du modèle entraîné et de la tâche de Machine Learning requise), cliquez sur le lien pour synchroniser les modèles.

Création des plongements pour les images

Après avoir configuré le cluster Elasticsearch et importé le modèle de plongement, vous devez vectoriser vos données au format image et créer des plongements pour chaque image de votre ensemble de données.

Pour créer des plongements pour les images, utilisez un simple script Python. Ce dernier est disponible sur cette page : create-image-embeddings.py. Le script parcourt le répertoire de vos images et génère les plongements correspondants. Il crée le document avec le nom et le chemin relatif, puis l'enregistre dans un index Elasticsearch intitulé my-image-embeddings en utilisant le mapping fourni. 

Enregistrez toutes vos images (photos) dans le dossier intituléapp/static/images. Utilisez une structure de répertoire composée de sous-dossiers afin de classer les images. Une fois que toutes les images sont prêtes, exécutez le script avec quelques paramètres.

Il est essentiel de disposer d'au moins quelques centaines d'images pour obtenir des résultats raisonnables. Un nombre insuffisant d'images ne permet pas d'obtenir les résultats attendus, car l'espace dans lequel vous allez mener vos recherches est très petit et les distances vers les vecteurs de recherche seront très similaires.

Dans le dossier image_embeddings, exécutez le script et utilisez vos valeurs pour les variables.

$ cd image_embeddings
$ python3 create-image-embeddings.py \
  --es_host='https://image-search.es.europe-west1.gcp.cloud.es.io:443' \
  --es_user='elastic' --es_password=<password> \
  --ca_certs='../app/conf/ess-cloud.cer'

En fonction du nombre d'images, de leur taille, de votre processeur et de votre connexion Internet, cette tâche durera un certain temps. Avant de tenter de traiter l'ensemble de données complet, faites un essai avec un petit nombre d'images.
Une fois le script terminé, vous pouvez vérifier si l'index my-image-embeddings existe et comprend les documents correspondants à l'aide des outils de développement de Kibana.

GET _cat/indices/my-image-embeddings?v

health status index               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   my-image-embeddings vfA3wOheT1C79R-PceDyXg   1   1       1222            0     24.4mb         12.2mb

En étudiant les documents, vous remarquerez des objets JSON très similaires (voir l'exemple fourni). Dans le dossier images, vous verrez le nom de l'image, son identifiant et le chemin relatif. Cet emplacement est ensuite utilisé dans l'application front-end afin d'afficher correctement l'image au moment de la recherche.
La partie la plus importante du document JSON est image_embedding qui comprend le vecteur dense produit par le modèle CLIP. Ce vecteur est utilisé lorsque l'application cherche une image spécifique ou similaire.

{
   "_index": "my-image-embeddings",
   "_id": "_g9ACIUBMEjlQge4tztV",
   "_score": 6.703597,
   "_source": {
     "image_id": "IMG_4032",
     "image_name": "IMG_4032.jpeg",
     "image_embedding": [
       -0.3415695130825043,
       0.1906963288784027,
       .....
       -0.10289803147315979,
       -0.15871885418891907
       ],
     "relative_path": "phone/IMG_4032.jpeg"
   }
}

Utilisation de l'application Flask pour rechercher des images

À présent que votre environnement est entièrement configuré, vous pouvez passer à l'étape suivante, à savoir rechercher réellement des images à l'aide du langage naturel et trouver des images similaires, grâce à l'application Flask que nous fournissons en tant que démonstration de faisabilité. L'application web est dotée d'une interface utilisateur simple qui facilite la recherche d'image. Vous pouvez accéder au prototype de l'application Flask dans ce référentiel GitHub. 

L'application en arrière-plan exécute deux tâches. Après avoir saisi la chaîne de recherche dans la zone dédiée, le texte sera vectorisé à l'aide du point de terminaison _infer de Machine Learning. Ensuite, la requête comprenant votre vecteur dense est exécutée dans l'index my-image-embeddings à l'aide des vecteurs.

Ces deux requêtes sont illustrées dans l'exemple ci-dessous. Le premier appel d'API utilise le point de terminaison _infer. Le résultat obtenu est un vecteur dense.

POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "endless route to the top"}
    ]
}

Pour la seconde tâche, à savoir la requête de recherche, nous utiliserons le vecteur dense et trierons les images selon leur notation.

GET my-image-embeddings/_search
{
  "knn": {
    "field": "image_embedding",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
    -0.19898493587970734,
    0.1074572503566742,
    -0.05087625980377197,
    ...
    0.08200495690107346,
    -0.07852292060852051
  ]
  },
  "fields": [
    "image_id", "image_name", "relative_path"

  ],
  "_source": false
}

Pour exécuter l'application Flask, accédez au dossier racine du référentiel et configurez le fichier .env. Les valeurs contenues dans le fichier de configuration sont utilisées pour se connecter au cluster Elasticsearch. Vous devez insérer les valeurs pour les variables suivantes. Il s'agit des mêmes valeurs utilisées lors de la génération des plongements pour les images.

  • ES_HOST='URL:PORT'
  • ES_USER='elastic'
  • ES_PWD='password'

Ensuite, exécutez l'application Flask dans le dossier principal et attendez qu'elle se lance.

# In the main directory 
$ flask run --port=5001

Si l'application se lance, vous verrez s'afficher une sortie similaire à l'exemple ci-dessous indiquant à la fin l'URL que vous devez consulter pour accéder à l'application.

flask run --port=5001
 * Serving Flask app 'flask-elastic-image-search.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit

Félicitations ! Votre application devrait maintenant s'exécuter et être accessible à l'adresse http://127.0.0.1:5001 que vous pouvez ouvrir dans votre navigateur Internet.

Accédez à l'onglet de recherche d'image et saisissez le texte décrivant le mieux votre image. Essayez d'utiliser un texte descriptif ou dépourvu de mot-clé.

Dans l'exemple ci-dessous, le texte saisi est "endless route to the top" (route infinie jusqu'en haut). Les résultats indiqués proviennent de notre ensemble de données. Si un utilisateur aime une image en particulier dans les résultats obtenus, il suffit de cliquer sur le bouton qui apparaît à côté d'elle pour afficher des images similaires. Les utilisateurs peuvent répéter l'opération à l'infini et concevoir leur propre chemin dans l'ensemble de données au format image.

La recherche fonctionne aussi en chargeant une image tout simplement. L'application convertira l'image en vecteur et recherchera une image similaire dans l'ensemble de données. Pour ce faire, accédez au troisième onglet intitulé Similar Image (Image similaire), chargez une image à partir du disque et cliquez sur le bouton Search (Rechercher).

Comme le modèle NLP (sentence-transformers/clip-ViT-B-32-multilingual-v1) que nous utilisons dans Elasticsearch est multilingue et prend en charge l'inférence dans de nombreuses langues, essayez de chercher des images dans votre langue. Puis, vérifiez les résultats en utilisant également du texte en anglais.

Il est important de remarquer que les modèles utilisés sont génériques et relativement précis. Cependant, les résultats que vous obtiendrez varieront en fonction du cas d'utilisation ou d'autres facteurs. Si une précision supérieure est requise, vous devrez adapter un modèle générique ou développer le vôtre. Le modèle CLIP sert simplement de point de départ.

Récapitulatif du code

Le code complet est disponible dans le référentiel GitHub. Vous pouvez inspecter le code dans routes.py, qui implémente la principale logique de l'application. Outre la définition évidente du routage, vous devriez vous concentrer sur les méthodes définissant les points de terminaison _infer et _search (infer_trained_model et knn_search_images). Le code qui génère les plongements pour les images se trouve dans le fichier create-image-embeddings.py.

Résumé

À présent que l'application Flask est configurée, vous pouvez facilement mener des recherches dans votre propre ensemble d'images. Elastic fournit une intégration native de la recherche vectorielle dans sa plateforme, ce qui évite la communication avec les processus externes. Vous bénéficiez de la flexibilité requise pour développer et utiliser les modèles de plongements personnalisés que vous avez peut-être conçus à l'aide de PyTorch.

La recherche sémantique d'image fournit les avantages suivants par rapport aux autres approches traditionnelles de la recherche d'image :

  • Précision accrue. La similarité vectorielle capture le contexte et les associations sans se reposer sur les métadescriptions textuelles des images.
  • Expérience utilisateur renforcée. Décrivez ce que vous recherchez ou fournissez un échantillon d'image au lieu de deviner les mots-clés susceptibles d'être pertinents.
  • Catégorisation des bases de données d'images. Ne vous inquiétez pas du catalogage de vos images. La recherche de similarités trouve dans une pile d'images celles qui sont pertinentes sans devoir les organiser.

Si votre cas d'utilisation se repose davantage sur des données textuelles, vous pouvez en apprendre davantage sur l'implémentation de la recherche sémantique et l'application du traitement du langage naturel au texte en lisant nos articles précédents. Pour les données textuelles, une association des similarités vectorielles aux scores traditionnels par mots-clés permet de bénéficier des avantages des deux techniques.
Vous sentez-vous capable de vous lancer ? Inscrivez-vous à un atelier pratique dédié à la recherche vectorielle sur notre hub d'événements virtuels et échangez avec notre communauté sur notre forum de discussion en ligne.