menu icon

Expédition vers Synonym Graph dans Elasticsearch

Dans cet article, nous expliquons comment nous sommes passés des anciens filtres de synonymes d'Elasticsearch aux nouveaux filtres de type graphe, les Synonym Graph Token Filter.

Expédition vers Synonym Graph dans Elasticsearch

Pour améliorer notre moteur e-commerce a2 basé sur Elasticsearch, nous avons décidé de remplacer l’ancien filtre de synonymes par le nouveau filtre synonym_graph. Cet article revient sur les problématiques que nous avons rencontrées lors de ce changement.

Pour améliorer un moteur de recherche, une technique classique consiste à utiliser des synonymes.

“Télé” = “Television” = “Ecran”

En général, les synonymes servent à augmenter le recall du moteur, c.a.d. récupérer des documents qui nous auraient échappé.

Depuis ses toutes premières versions, Elasticsearch propose le filtre synonym, mais des problèmes surviennent lorsque l’on veut utiliser des synonymes avec plusieurs termes, et plus particulièrement lorsque le nombre de ces termes n’est pas égal entre les synonymes.

“USA” = “United States” = “United States of America”

En effet, Lucene ne possède pas la structure de données adéquate pour stocker ces cas particuliers et un synonyme avec plus de mots que l’original va “se ranger” avec d’autres termes dans le texte indexé. Le moteur de recherche va alors produire des résultats inattendus et difficiles à analyser.

Pour pallier à ce problème, Elasticsearch (via les nouvelles versions de Lucene) propose désormais un filtre nommé synonym_graph. Pour les raisons de stockage citées ci-dessus, il est impossible d’utiliser ce filtre à l’indexation. Ces filtres sont donc uniquement utilisables à la recherche.

Le moteur e-commerce a2 d’Adelean, comme beaucoup de moteurs basés sur Lucene, utilisait historiquement les synonymes à l’indexation, il fallait donc à la fois changer de type de filtre et passer ce nouveau filtre à la recherche.

Premiers pas et le paramètre auto_generate_synonyms_phrase_query

Une première surprise fut de voir que l’ajout de certains synonymes multi-termes à la recherche entraînait la baisse du nombre de résultats. Etonnant ! Car comme dit précédemment, lorsque l’on ajoute des synonymes, on s’attend plutôt à ramener plus de documents !

Ainsi, pour trois simples documents dans un index standard :

PUT test_synographs/_doc/1
{
  "name": "jus orange"
}
PUT test_synographs/_doc/2
{
  "name": "jus avec orange"
}
PUT test_synographs/_doc/3
{
  "name": "orangeade"
}

Une simple requête :

GET test_synographs/_search
{
  "query": {
    "multi_match": {
      "query": "jus orange",
      "fields": [
        "name"
      ],
      "operator": "and"
    }
  }
}

donne :

"hits" : [
  {
   "_id" : "2",
   "_score" : 0.5753642,
   "_source" : {
     "name" : "jus avec orange"
    }
  },
  {
   "_id" : "1",
   "_score" : 0.5753642,
   "_source" : {
     "name" : "jus orange"
    }
 }
]

Jusqu’ici, le comportement est celui attendu. Pour récupérer le dernier document, ajoutons maintenant le synonyme

“jus orange” = “orangeade”

DELETE test_synographs
PUT test_synographs
{
  "settings": {
    "analysis": {
      "filter": {
        "synonyms_graph": {
          "type": "synonym_graph",
          "synonyms": [
            "jus orange,orangeade"
          ]
        }
      },
      "analyzer": {
        "simple": {
          "filter": [
          ],
          "tokenizer": "standard"
        },
        "with_synonyms": {
          "filter": [
            "synonyms_graph"
          ],
          "tokenizer": "standard"
        }
      }
    }
  },
  "mappings": {
    "_doc": {
      "dynamic": "false",
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "simple",
          "search_analyzer": "with_synonyms"
        }
      }
    }
  }
}

A notre grande surprise, la même requête que précédemment renvoie toujours deux documents ! Et pas les mêmes !

"hits": [
    {
      "_id": "1",
      "_score": 0.5753642,
      "_source": {
        "name": "jus orange"
      }
    },
    {
      "_id": "3",
      "_score": 0.2876821,
      "_source": {
        "name": "orangeade"
      }
    }
  ]

Nous avons certes récupéré le document “orangeade” mais perdu le document n°2 “jus avec orange” en cours de route.

En fouillant la documentation Elasticsearch, nous trouvons la raison. Une requête multi-match possède une option auto_generate_synonyms_phrase_query à true par défaut. Ce qui veut dire qu’une phrase query est automatiquement ajoutée à notre requête initiale pour les synonymes définis sur le champ requêté. Un choix étonnant de la part d’Elasticsearch !

Il suffit de modifier légèrement notre requête pour enfin obtenir les trois documents :

GET test_synographs/_search
{
  "query": {
    "multi_match": {
      "query": "galettes de chaise",
      "fields": [
        "name"
      ],
      "operator": "and",
      "auto_generate_synonyms_phrase_query": "false"
    }
  }
}

Cohabitation avec les autres filtres

Si l’on veut utiliser le Synonym Graph Token avec d’autres filtres dans notre chaîne d’analyse, il est conseillé de le mettre en dernier dans la liste. De cette manière les filtres précédents seront également appliqués aux synonymes.

Par exemple, si vous utilisez la lemmatisation pour que tomates devienne tomate, mettre le filtre de synonymes en dernier vous permettra d’écrire ce genre de règles :

“fruit => orange, pomme, poire”

au lieu de

“fruit, fruits => orange, oranges, pomme, pommes, poire, poires”

C’est également valable pour les minuscules, supprimer les accents, etc..

C’est particulièrement utile dans notre moteur e-commerce, car la liste des synonymes est éditée par des équipes métiers qui ne connaissent pas le fonctionnement technique d’Elasticsearch. De plus, mettre toutes les formes d’un mot (avec et sans majuscule, avec et sans accent, au pluriel, au singulier) serait un travail de titan extrêmement rébarbatif.

En revanche, nous rencontrons rapidement un problème avec les stopwords: Elasticsearch ne nous laisse pas générer un tel index :

PUT test_synographs
{
  "settings": {
    "analysis": {
      "filter": {
        "stopword_filter": {
          "type": "stop",
          "ignore_case": true,
          "stopwords": [
            "un",
            "une"
          ]
        },
        "synonyms_graph": {
          "type": "synonym_graph",
          "synonyms": [
            "jus orange,une orangeade"
          ]
        }
      },
      "analyzer": {
        "simple": {
          "filter": [],
          "tokenizer": "standard"
        },
        "with_synonyms": {
          "filter": [
            "stopword_filter",
            "synonyms_graph"
          ],
          "tokenizer": "standard"
        }
      }
    }
  },
  "mappings": {
    "_doc": {
      "dynamic": "false",
      "properties": {
        "name": {
          "type": "text",
          "analyzer": "simple",
          "search_analyzer": "with_synonyms"
        }
      }
    }
  }
}

En effet, un message d’erreur apparaît dès qu’un synonyme contient un des stopwords de la liste. Le bug n’a toujours pas trouvé de solution aujourd’hui (avril 2021). La solution consistant à inverser l’ordre ne génère pas d’erreurs, mais les synonymes multi termes se remettent à générer des résultats incohérents.

Pour ce problème, nous avons donc été contraints de passer à la moulinette cette liste de synonymes pour y supprimer les stopwords.

Crossfield et Search analyzers

A ce moment-là, notre moteur de recherche commence à fonctionner relativement correctement. Seul souci, certains documents censés remonter, sont toujours absents.

Notre moteur a2 utilise des requêtes dites crossfield : si je cherche “jus orange", le moteur peut renvoyer un document possédant “jus” dans un champ et “orange” dans l’autre. Or, crossfield ne semble pas fonctionner correctement. Il ne s’agit pas, cette fois-ci, d’un bug d’Elasticsearch. En effet, crossfield s’applique uniquement entre des champs possédant le même search analyzer. Et nous avons défini des listes de synonymes différentes selon les champs. Ce qui veut dire des search analyzers différents !

Gardons l’exemple de notre document avec “jus” dans un champ et “orange” dans l’autre. Si j’ai une liste de synonymes pour le champ contenant “jus", et une autre pour le champ contenant “orange", ma requête multi-match crossfield avec “jus orange” ne sera pas capable de retrouver ce document !

La solution a été de rassembler tous les synonymes et de tous les appliquer sur tous les champs. Une petite perte fonctionnelle, mais moins dommageable que la perte de nos documents.

Notre moteur fonctionne désormais correctement pour tout type de synonymes !

Des requêtes plus complexes

Il faut quand même rajouter une dernière petite remarque sur les performances : si mettre les synonymes à l’indexation peut entraîner une augmentation de la taille des index, les mettre à la recherche peut générer des requêtes plus complexes. C’est particulièrement vrai pour les recherches avec plusieurs termes si ces derniers ont chacun un très grand nombre de synonymes définis.

Il nous est ainsi arrivé de dépasser le nombre maximal de requêtes booléennes Lucene sous-jacentes (dont le maximum par défaut est 1024), nous obligeant à repérer ces requêtes complexes (très rares heureusement) et à les simplifier dynamiquement.

Nous n’avons pas noté de ralentissement sensible pour la très grande majorité des requêtes, en passant aux synonymes à la recherche.

Nous espérons que cet article vous permettra de passer plus facilement au Synonym Graph Token Filter et de résoudre un problème classique sur tous les moteurs de recherche basés sur Lucene : les synonymes multi-termes. Si toutefois vous avez besoin d’assistance n’hésitez pas à nous contacter.

Assurer la scalabilité d’un moteur de recherche pour des milliers de magasins en ligne – retour sur la conférence ElasticON

10/03/2023

Retour sur la présentation Assurer la scalabilité d’un moteur de recherche pour des milliers de magasins en ligne par Roudy Khoury et Aline Paponaud à ElasticON 2023

Lire l'article

Question answering, une approche plus humaine à nos recherches sur all.site.

19/01/2023

Tout sur les Question-Answering et comment l'implémenter en utilisant flask et elasticsearch.

Lire l'article

Retour d’Expérience - Fine-tuning d’un modèle VOSK

05/01/2022

all.site est un moteur de recherche collaboratif. Il fonctionne comme Bing ou Google mais il a l’avantage de pouvoir aller plus loin en indexant par exemple les contenus média et en organisant les données de systèmes comme Slack, Confluence ou l’ensemble des informations présentes dans l’intranet d’une entreprise.

Lire l'article

Retour d’Expérience - Indexation des transcriptions de fichiers média

17/12/2021

all.site est un moteur de recherche collaboratif. Il fonctionne comme Bing ou Google mais il a l’avantage de pouvoir aller plus loin en indexant par exemple les contenus média et en organisant les données de systèmes comme Slack, Confluence ou l’ensemble des informations présentes dans l’intranet d’une entreprise.

Lire l'article

La revue de presse du 25 Novembre 2021

25/11/2021

Bientôt le weekend, bientôt l'hiver, alors une petite revue de presse pour occuper vos longues soirées...

Lire l'article

Nouveau meetup Search & Data - E-Commerce Search et Open Source

28/10/2021

La cinquième édition du meetup Search and Data est dédiée au search e-commerce et à l'open source. Un bel agenda pour cette édition de rentrée et de reprise.

Lire l'article

Quand les requêtes sont très verbeuses

22/02/2021

Dans cet article, nous présentons une méthode simple pour réécrire les requêtes utilisateurs afin qu'un moteur de recherche basé sur des mots clés puisse mieux les comprendre. Cette méthode est très utile dans le contexte d'une recherche vocale ou une conversation avec un chatbot, contexte dans lequel les requêtes utilisateur sont généralement plus verbeuses.

Lire l'article

Enrichir les données et réécrire les requêtes avec le percolator Elasticsearch

26/04/2019

Cet article est une transcription de notre intervention cette semaine à Haystack - une conférence sur l'amélioration de la pertinence des moteurs de recherche. Nous avons montré une méthode permettant d'enrichir et de réécrire les requêtes des utilisateurs en utilisant Wikidata et le percolator Elasticsearch.

Lire l'article

A2 le moteur qui sublime Elasticsearch

13/06/2018

Elasticsearch est une technologie ouverte qui permet aux intégrateurs de construire des solutions toujours plus innovantes et puissantes.

Lire l'article