Utiliser un dictionnaire de synonymes français dans ElasticSearch

Contexte

Le module Data Exchange (DEX) de la plateforme Blue DME permet notamment aux data scientists de découvrir automatiquement des jeux de données via des algorithmes de machine learning. En parallèle de cette fonctionnalité avancé de recherche de données, Blue DME améliore son moteur de recherche texte intelligent basé sur différents critères autour d’Elastic Search (note qualité, analyse statistiques, données d’usage, etc.). Blue DME cherche continuellement à améliorer la recherche au sein de Data Exchange et a expérimenté récemment la mise en place d’un dictionnaire français de synonyme avec Elastic Search.

Nous proposons ici un retour d’expérience sur l’implémentation d’un dictionnaire français et la raison pour laquelle nous avons décidé de ne pas l’utiliser en production et de nous tourner vers d’autres algorithmes plus adaptés à notre contexte métier.

Problématique

L’une des fonctionnalités majeures du DEX est de pouvoir chercher des jeux de données au sein d’un catalogue de données entier. Dans le cas de la recherche de jeux de données via recherche full-text, nous avons historiquement mis en place une solution simple de recherche utilisant les mots-clés saisis en entrée. La recherche s’effectue sur plusieurs champs de nos documents : titres, descriptions, tags…

Cette recherche ne répondait pas assez à nos besoins pour élargir le champs de la recherche effectuée par nos utilisateurs. En effet, ils souhaitent bien entendu trouver des jeux de données correspondant aux mots clés saisis, mais aussi à des synonymes ou hyperonyme (“hypernym” en anglais) de ces mots-clés. Par exemple, si un utilisateur saisit le mot “docteur”, il s’attend à trouver des résultats en lien avec le mot “médecin”. Or, avec le mode de recherche actuel, ce n’est pas le cas : seuls les jeux de données en lien avec le mot “docteur” font partie des résultats proposés par Elastic Search.

Une solution identifiée était d’intégrer un dictionnaire de synonymes dans ElasticSearch.

Bienvenue à Wordnet

Un dictionnaire de synonymes, intégrable dans ElasticSearch, existe déjà. Wordnet, un projet de l’université de Princeton, est une base de données lexicale en anglais. Pour chaque mot présent dans cette base, Wordnet fournit une courte description et des exemples d’utilisations. Toutes ces informations ne sont pas utiles dans notre contexte, mais Wordnet permet aussi regrouper certains synonymes en “synset” (synonymes cognitifs exprimant un concept distinct et qui sont inter-liés par des relations lexicales et sémantiques).

Wordnet a mis en place un site pour tester sa base de données.

Wordnet est très bien construit et permet, par exemple, de trouver la liste de synonymes suivante pour le mot anglais “doctor” : “doc”, “physician”, “MD”, “Dr.”, “medico”, etc.

Vous pouvez télécharger la base de données Wordnet ici.

Un Wordnet “français”?

La base de données fournie par Wordnet est parfaite pour des projets anglais mais n’est malheureusement pas disponible en français, notre besoin. Lier “doctor” et “physician” n’est donc pas du tout pertinent pour notre produit. Nous devons alors trouver une base de données lexicale française.

Il en existe quelques unes, les plus connues et les plus utilisées étant certainement WoNeF et Wolf. La première semble plus complète mais la dernière peut être testée en ligne.

Si vous jouez un peu avec la base de données, vous verrez que les résultats sont plutôt corrects. Par exemple, “docteur” fournit les résultats associés “médecin” et “toubib”.

Ne crions pas victoire trop tôt et vérifions le format… Aucune des deux base de données lexicales françaises n’est formatée comme Wordnet l’est. Or ElasticSearch n’accepte que 2 formats de dictionnaire de synonymes : Solr et Wordnet (voir le guide ElasticSearch).

Créer un dictionnaire de synonyme Solr depuis WoNeF

Il faut donc que nous convertissions une des bases de données lexicales françaises en un dictionnaire de synonymes compréhensible par ElasticSearch. Nous avons choisi d’utiliser WoNeF et de créer un dictionnaire au format Solr. Pour rappel, le format Solr fournit des résultats du type :

Médecin => docteur,toubib,médecin

Voici un extrait du fichier WoNeF pour les mots en lien avec “médecin” :

<?xml version="1.0" encoding="UTF-8"?>
<SYNSET>
  <ILR type="hypernym">eng-30-10305802-n</ILR>
  <ILR type="holo_member">eng-30-13837840-n</ILR>
  <ILR type="eng_derivative">eng-30-00080304-v</ILR>
  <ILR type="eng_derivative">eng-30-02893338-a</ILR>
  <ID>eng-30-10020890-n</ID>
  <SYNONYM>
     <LITERAL lnote="wonef-monosemous(1);wonef-multiplesource(0)">médecin</LITERAL>
     <LITERAL lnote="wonef-levenshtein(0)">docteur</LITERAL>
     <LITERAL lnote="wonef-monosemous(1);wonef-multiplesource(0)">toubib</LITERAL>
  </SYNONYM>
  <DEF>personne qui a un diplôme de docteur en médecine/ DEF(en): medical doctor</DEF>
  <USAGE>I felt so bad I went to see my doctor</USAGE>
  <BCS>1</BCS>
  <POS>n</POS>
</SYNSET>

 

Nous avons écrit un script Python pour parser ce fichier et créer un dictionnaire de synonyme au format Solr. (Page Gist : https://gist.github.com/litil/a0f248dbe8334f1f4874986868fc28f4)

# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import re
import unicodedata

# This method adds all given synonyms into the correct dictionary entry.
def extendDictEntry(dict, key, xmlSynonyms):
   for child in xmlSynonyms:
       childText = child.text.encode('utf-8')

       if (childText not in dict[key]):
           dict[key].extend([childText])
   return dict

# This method buils the synonyms dictionary from the WoNeF file.
def buildSynonymDictionary():
    tree = ET.parse('wonef-fscore-0.1.xml')

    root = tree.getroot()
    dict = {}

    # fill synonyms dictionary
    for synset in root:
        for child in synset:
            if child.tag == "SYNONYM":
                for literal in child:
                    currLiteralText = literal.text.encode('utf-8')

                    if currLiteralText in dict:
                        # add all SYNONYM tags text into the correct entry of the map
                        extendDictEntry(dict, currLiteralText, child)
                    else:
                        # create a new entry in the map
                        dict[currLiteralText] = [currLiteralText]
                        extendDictEntry(dict, currLiteralText, child)

    return dict

def removeAccents(str):
    return ''.join(c for c in unicodedata.normalize('NFD', str.decode('utf-8'))
                  if unicodedata.category(c) != 'Mn').encode('utf-8')

# This method writes the synonym file in the Solr format
def writeSolrSynonymFile():
    dict = buildSynonymDictionary()
    file = open("solr_synonym.txt","w")
    file.write("# Solr Synonmys File \n\n")

    for key in dict:
        try:
            file.write(
                removeAccents(key) +
                " => " +
                removeAccents(", ".join(dict[key])) +
                "\n")
        except UnicodeEncodeError:
            print("UnicodeEncodeError: " + key + " - " + ", ".join(dict[key]))

    file.close()


writeSolrSynonymFile()

Voilà un extrait du fichier obtenu :

docteur => docteur, médecin, toubib

en  chêne => en  chêne, en bois de chêne, de bois de chêne, de chêne, en chêne, de  chêne

bout de vergue => bout de vergue

graille => graille, tortore, bouffe, snacks, boustifaille, becquetance, casse-croûte, frichti, cuistance, soupe

adjectivalement => adjectivalement, adjectivement

maladie de  peau => maladie de  peau, maladie de la peau

fait divers => fait divers, info, information

Intégrer un dictionnaire de synonymes à ElasticSearch

Le plus dur est fait ! Maintenant, il ne vous reste plus qu’à suivre ces étapes :

  1. Créer un dossier “analysis” dans le dossier “config” d’ElasticSearch
  2. Copier le fichier créé précédemment dans ElasticSearch (config/analysis/)
  3. Démarrer ElasticSearch

Ensuite, nous allons créer un nouvel index ElasticSearch :

  • Un “synonym filter”
  • Un “synonym analyzer”, utilisant le filtre créé, lors de l’indexation des documents
  • Un mapping simple, avec un champ utilisant l’analyzer synonyme

Voici la requête à envoyer pour créer cet index:

PUT

http://localhost:9200/dummy/
{
 "settings" : {
"index" : {
    "analysis" : {
        "analyzer" : {
            "synonym" : {
                "tokenizer" : "whitespace",
                "filter" : ["synonym"]
            }
        },
        "filter" : {
            "synonym" : {
                "type": "synonym",
                    "format": "wordnet",
                    "synonyms_path": "analysis/wn_s.pl"
            }
        }
    }
}
 },
 "mappings" : {
  "_default_": {
       "properties" : {
           "name" : {
               "type" : "string",
               "analyzer" : "synonym"
           }
       }
    }
 }
}

Prochaine et dernière étape avant de tester : créer des documents. Nous allons créer des documents très simples pour valider que notre dictionnaire de synonymes fonctionne. Voici les requêtes à envoyer :

POST

http://localhost:9200/dummy/project/1
{
"name" : "toubib"
}

http://localhost:9200/dummy/project/2

{
"name" : "medecin"
}

http://localhost:9200/dummy/project/2

{
"name" : "fleur"
}

 

A présent, testons! Sans dictionnaire de synonyme, une recherche sur “toubib” n’aurait ramené qu’un seul document. Maintenant, nous devrions avoir deux résultats:

 

GET

http://localhost:9200/dummy/_search?pretty=true

{
        "query" : {
    "match": {
         "name": {
             "query": "toubib"
         }
    }
        }
}

Résultat :

{
      "took": 2,
      "timed_out": false,
      "_shards":
      {
          "total": 1,
          "successful": 1,
          "failed": 0
      },
      "hits":
      {
          "total": 2,
          "max_score": 2.3731742,
          "hits":
          [
              {
                  "_index": "dummy",
                  "_type": "dummy",
                  "_id": "1",
                  "_score": 2.3731742,
                  "_source":
                  {
                      "name": "toubib"
                  }
              },
              {
                  "_index": "dummy",
                  "_type": "dummy",
                  "_id": "2",
                  "_score": 0.028331274,
                  "_source":
                  {
                      "name": "medecin"
                  }
              }
          ]
      }
   }

 Hourah!

Utilisation dans un contexte métier adapté

Nous l’avons vu avec l’exemple précédent, ElasticSearch retourne bien des documents contenant des synonymes du mot recherché. Mais la vraie question est : est-ce que les résultats retournés par notre nouvelle recherche sont plus pertinents ou non dans le contexte métier d’utilisation?

Malheureusement, dans notre cas, la réponse est non. Le dictionnaire de synonyme à partir de WoNeF est trop complet et apporte donc du bruit dans les résultats de notre recherche.

Par exemple, un des synonymes proposés de “voiture” est “caisse”. C’est en effet correct, puisqu’en argot, une “caisse” signifie une “voiture”. Or si un utilisateur saisit “voiture” dans le champ de recherche, un des documents retournés a pour titre “Caisse d’Allocations …”. Ce problème en particulier pourrait être solutionné (en nettoyant le dictionnaire, en ajoutant des filtres dans ElasticSearch …), mais il existe une multitude de problèmes qui sont soulevés. Dans un contexte où l’on souhaiterait interpréter des commentaires sur des réseaux sociaux par exemple, ce dictionnaire pourrait être très pertinent.

L’utilisation d’un dictionnaire de synonymes contenant un vocabulaire trop large ne nous convient pas et nous ne voulons pas passer du temps à le nettoyer manuellement. Nous avons besoin d’un algorithme permettant d’apprendre automatiquement le vocabulaire utilisé dans nos données ainsi que les relations entre les mots. De tels algorithmes existent, par exemple doc2vec (dérivé de word2vec). Nous nous sommes tournés vers ce type d’algorithmes pour analyser une suite de mots et de créer un ou plusieurs labels en fonction du contenu. Ceci va nous permettre d’améliorer la pertinence de notre recherche, sans apporter autant de bruit qu’un dictionnaire de synonyme général. Nous ferons prochainement un retour d’expérience sur cet algorithme.

 

Article réalisé par Guillaume Lambert.

Remerciements :

Nous tenons à remercier Quentin Pradet, qui travaille sur le projet WoNeF, pour ses conseils quant à l’utilisation de la base de données lexicale “WoNeF »

Quelques liens utiles pour en savoir plus :

Pour plus d’informations sur les solutions de Blue DME, n’hésitez pas à nous contacter ici : http://www.bluedme.com/contact/