af83

Tire pour les francophones

Tire est une bibliothèque ruby pour effectuer de la recherche full text. Il s'intègre facilement avec Active model et utilise Elastic Search, une application REST basée sur Lucene.

Conçu pour tourner au sein d'une application Rails et d'Active Support, Tire peut aussi être utilisé directement, sans Rails.

Paramétrages

Tire propose un service qui fonctionne bien avec les réglages par défaut, mais qui exprime vraiment son potentiel avec des réglages plus fins.

Analyseurs, la théorie

Un document est découpé en éléments (tokens), qui sont ensuite filtrés. Ces éléments servent ensuite de clef pour désigner le document. On parle d'index inversé.

Le découpage est effectué par un tokenizer, le filtrage par des filters. Un tokenizer assemblé avec des filters est un analyzer.

Elastic Search rassemble des éléments issus des projets Lucene et Solr. Ces éléments ont un long historique, certains sont redondants, d'autres très similaires. De plus, beaucoup de ces outils sont prévus pour l'anglais… Les efforts pour gérer les autres langues sont très variables, et les différents styles de grammaires facilitent plus ou moins la création d'outils spécifiques.

Les filtres classiques ne sont presque pas destructifs, et se contentent de tout mettre en minuscule, d'enlever la ponctuation et même les accents. Mais les dégâts faits par un filtre seront plus ou moins importants selon les langues : enlever les accents en anglais est anecdotique, passable en français et un massacre en vietnamien. De toutes façons, l'indexation est plus une histoire de recette de cuisine mitonnée pour chaque projet qu'une vérité absolue et universelle.

Les "stop words", mots vides, sont des mots trop courants qui, isolés, ne désignent rien de précis - comme les articles, les conjugaisons d'être et avoir, ce genre de mots. R ou go sont deux exemples de mots martyrs de Google, très difficiles à rechercher. Il est possible d'étoffer sa collection de mots vides en piochant dans les mots ayant une trop grande fréquence dans son index.

Le "stemming" ou lemmatisation est un principe plus controversé, qui se base sur un ensemble de règles pour retrouver la racine d'un mot. Il permet de normaliser les singuliers/pluriels, masculin/féminin et autres déclinaisons d'un même mot. Dans certaines langues à déclinaison, ça a une bonne réputation, en français, c'est un peu moins convaincant. Snowball, un lematiseur bien connu s'en tire pas mal en regroupant cheval et chevaux mais sans les confondre avec chevalet et chevalier. Sur des noms propres en revanche, le résultat peut être plus fantaisiste… Et pour des recherches sur des termes précis, comme le titre d'une oeuvre, la lematisation amène des surprises.

De manière générale, en français, il faut se méfier des homonymes : "avions", c'est le verbe avoir conjugué, et donc un mot vide, ou le pluriel d'avion ? Avec les noms de marques et les termes issus de l'anglais, les collisions sont encore plus fréquentes.

Pour gérer des noms propres, il existe des outils qui essayent de convertir les mots en phonèmes. Mais les outils proposés par Elastic Search sont spécifiques à l'anglais et certains algorithmes ont presque un siècle, ce qui les rend peu efficaces en français. Ayons d'ailleurs au passage une pensée pour les administratifs qui appliquaient ces règles à la main pour le recensement…

De façon générale, pour couvrir différents besoins (recherche, tri), il n'est pas rare d'indexer plusieurs fois un même champ avec des règles différentes.

Enfin, quel que soit la solution choisie, pour éviter d'avoir des résultats fantaisistes, il faut utiliser le même analyseur pour indexer et pour rechercher.

Exemples concrets

#encoding: utf-8
require 'rubygems'
require 'tire'

Tire pense que le monde est Rails, deux imports permettent de contourner ça.

require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/object/to_param'

Paramétrages de Tire

tire = Tire::Index.new 'analyzers'
tire.delete
conf = {
  settings: {
    analysis: {
      analyzer: {
        francais: {
          type: 'custom',
          tokenizer: 'standard',
          filter: %w{lowercase stop_francais asciifolding elision}
        },
        francais_boule: {
          type: 'snowball',
          language: 'French'
        }
      },
      filter: {
        stop_francais: {
          type: 'stop',
          stopwords: %w{je tu il nous vous ils le la les un une des a ai et est ayons ça}
        }
      }
    }
  }
}
tire.create conf

Test de différents analyseurs avec du contenu.

%w{ whitespace standard simple french francais francais_boule}.each do |analyzer|
  p analyzer
  text = <<EOF
  Je mange des carottes, des petits pois et des pèches (bien que ça coule sur les doigts).
  J'aime ça. Un chevalier mange du cheval et élève des chevaux.
  Le peintre utilise un chevalet. Je lis des O'Reilly.
EOF
  a = tire.analyze(text, :analyzer => analyzer)
  print "    "
  p a['tokens'].map{ |t| t['token']}
end

Ce qui donne : "whitespace" ["Je", "mange", "des", "carottes,", "des", "petits", "pois", "et", "des", "pèches", "(bien", "que", "ça", "coule", "sur", "les", "doigts).", "J'aime", "ça.", "Un", "chevalier", "mange", "du", "cheval", "et", "élève", "des", "chevaux.", "Le", "peintre", "utilise", "un", "chevalet.", "Je", "lis", "des", "O'Reilly."] "standard" ["je", "mange", "des", "carottes", "des", "petits", "pois", "et", "des", "pèches", "bien", "que", "ça", "coule", "sur", "les", "doigts", "j'aime", "ça", "un", "chevalier", "mange", "du", "cheval", "et", "élève", "des", "chevaux", "le", "peintre", "utilise", "un", "chevalet", "je", "lis", "des", "o'reilly"] "simple" ["je", "mange", "des", "carottes", "des", "petits", "pois", "et", "des", "pèches", "bien", "que", "ça", "coule", "sur", "les", "doigts", "j", "aime", "ça", "un", "chevalier", "mange", "du", "cheval", "et", "élève", "des", "chevaux", "le", "peintre", "utilise", "un", "chevalet", "je", "lis", "des", "o", "reilly"] "french" ["mang", "carott", "petit", "pois", "pech", "bien", "ça", "coul", "doigt", "aim", "ça", "chevali", "mang", "cheval", "élev", "cheval", "peintr", "utilis", "chevalet", "lis", "o'reilly"] "francais" ["mange", "carottes", "petits", "pois", "peches", "bien", "que", "coule", "sur", "doigts", "aime", "chevalier", "mange", "du", "cheval", "eleve", "chevaux", "peintre", "utilise", "chevalet", "lis", "o'reilly"] "francais_boule" ["mang", "carott", "petit", "pois", "pech", "bien", "ça", "coul", "doigt", "j'aim", "ça", "chevali", "mang", "cheval", "élev", "cheval", "peintr", "utilis", "chevalet", "lis", "o'reilly"]

Le filtre whitespace est clairement là à titre pédagogique, il garde la ponctuation. standard donne un résultat brut mais utilisable, simple apporte une meilleur gestion des apostrophes qui brime les irlandais. french est moins généreux en mots vides que francais, snowball raccourcit bien, mais il lui manque un filtre de stopwords (que l'on peut lui ajouter).

Conclusion

Il n'existe pas de configuration ultime pour Tire… Chaque projet est spécifique, il est donc indispensable de tester et d'affiner les analyseurs avec de vraies données.

blog comments powered by Disqus