Télécharger
    Installer
    Présentation
       Architecture
       Serveur
       Applications
       Bases de documents
       Entrepôt
       Multilinguisme
      +Analyseurs <-
       Débuter
    Configuration
    Indexation
    Recherche
    OAI
    Javadoc
    Référence API-XSP
    Migration
    Schemas
    Performances


SDX

Les analyseurs

En informatique documentaire, on est généralement amené à gérer l'indexation et la recherche sur du plein-texte, autrement nommé "texte intégral", "texte libre", "texte rédigé"... Comme il est hors de question d'indexer et donc de proposer des requêtes sur des phrases, des paragraphes voire des textes entiers, il est nécessaire de faire subir un traitement préalable au texte : l'analyse.

Dans SDX, les champs (ou index) qui bénéficient d'une analyse sont définis ainsi dans les fichiers de configuration des applications (application.xconf) :

<sdx:field name="champ" type="word">

Les linguistes bondiront certainement face à l'intitulé word, on ne peut plus flou. On aurait pu utiliser un intitulé moins sémantisé ou sémantisé sur le plan informatique, tokenized par exemple. Peu importe.

SDX ne proposant pour l'instant qu'une indexation Lucene, l'analyse repose entièrement sur les API d'analyse de ce logiciel. Le concept étant suffisament générique, ces API pourraient servir à tout autre moteur d'indexation. C'est pourquoi nous allons le détailler.

Comment se déroule une analyse ?

La classe org.apache.lucene.analysis.Analyzer est naturellement au coeur du processus d'analyse et en particulier son unique méthode, disposant de deux signatures (dont l'une est dépréciée), tokenStream .

Comme son nom l'indique, cette méthode a pour but de fournir un flux de jetons (tokens) pour un champ donné à partir d'un java.io.Reader . Ce Reader permet une très grande abstraction : il peut bien évidemment lire un flux textuel mais aussi tout autre type de flux si bien que l'analyse d'une image ou d'un son serait tout à fait concevable !

La plupart du temps cependant, le Reader que SDX fournira à l'analyseur sera celui fourni par le parseur du document XML généré par la XSLT d'indexation.

En sortie, l'analyseur doit fournir un org.apache.lucene.analysis.TokenStream , c'est à dire une suite de org.apache.lucene.analysis.Token . Cet objet, finalité du processus d'analyse, possède les propriétés suivantes :

  • une représentation textuelle, accessible via la méthode termText() . Quand bien même on analyse une image, l'analyse doit donc, en l'état actuel des choses, renvoyer des Tokens textuels :-)

  • une position de début dans le flux d'origine, accessible via la méthode startOffset() . Il est important de noter que cette information est perdue lorsque le Token devient un Term indexé (v. plus bas).

  • une position de fin dans le flux d'origine, accessible via la méthode endOffset() . Il est important de noter que cette information est perdue lorsque le Token devient un Term indexé (v. plus bas).

  • un incrément par rapport au Token précédemment reçu, accessible via la méthode getPositionIncrement() . Cet incrément est généralement positionné à 1, ce qui signifie que les Tokens se suivent immédiatement les uns à la suite des autres. Ce marqueur de position prend toute son importance dans les requêtes de phrase, y compris les requêtes de proximité.

  • un type, accessible via la méthode type() . Il est important de noter que cette information est perdue lorsque le Token devient un Term indexé (v. plus bas).

La première tâche que devra remplir un Analyzer consistera à découper (tokeniser) le flux reçu par le Reader. A cet effet, on utilisera généralement un descendant de la classe org.apache.lucene.analysis.Tokenizer charger de découper le texte en Tokens, sur les espaces ou les signes de ponctuation par exemple. Il va de soi que le découpage des écritures sans espaces (chinois, égyptien ancien....) relève d'une problématique de découpage complexe.

Une fois ces Tokens bruts obtenus, il sera souhaitable de les transformer et/ou de les supprimer du flux (cas des mots-vides). Cette opération est réalisée grâce à des descendants de la classe org.apache.lucene.analysis.TokenFilter qui recevront un TokenStream en entrée, et qui a leur tour, émettront des Tokens, éventuellement à destination d'autres TokenFilters.

On obtient donc in fine un flux de Tokens qui vont être transformés en objets org.apache.lucene.index.Term stockés dans les index Lucene. Un objet Term comporte 2 propriétés principales :

  • une représentation textuelle, accessible via la méthode text() . Comparer avec Token.termText() .

  • le champ pour lequel on définit le terme, accessible via la méthode field() . Dans SDX, il reprendra naturellement l'attribut name de l'élément <sdx:field/>.

A première vue, il semblerait que le passage d'un Token à un Term fasse perdre beaucoup d'information : la position de début dans le flux d'entrée, la position de fin dans le flux d'entrée, le type de Token, ainsi que la position relative par rapport au Token précédent disparaissent.

C'est vrai pour les trois premières propriétés, qui n'ont pour but que de récupérer les information fournies par le flux d'entrée et/ou les différents processus de filtrage des Tokens, mais c'est faux pour la dernière : la méthode termPositions() de la classe org.apache.lucene.index.IndexReader est capable de retrouver la position d'un terme et donc de permettre les requêtes de proximité. En effet, pour des raisons de performances, l'architecture de Lucene a préféré mutualiser la position des termes du niveau des index ainsi que l'indique la documentation de la méthode :

Returns an enumeration of all the documents which contain term. For each document, in addition to the document number and frequency of the term in that document, a list of all of the ordinal positions of the term in the document is available. Thus, this method implements the mapping:

Term => <docNum, freq, <pos1, pos2, ... posfreq-1> >*

Pour un terme donné, l'index stocke donc un numéro d'ordre de document, le nombre d'occurences du terme dans le document, ainsi que les différentes positions du terme dans ce document.

On peut donc tout à fait légitimement comparer un analyseur d'indexation à un Pipeline Cocoon, le Tokenizer jouant le rôle du générateur, les TokenFilters jouant les rôle des transformateurs et la classe org.apache.lucene.index.IndexWriter jouant le rôle de sérialiseur. Un analyseur de requête, serait, lui, sérialisé vers un objet org.apache.lucene.search.Query composé d'une ou plusieurs requêtes "atomiques" issues du package org.apache.lucene.search

L'analyseur français

La documentation sur la multilinguisme explique clairement la façon de définir un analyseur. Nous allons décrire la façon dont opère l'analyseur français fourni par défaut dans SDX, fr.gouv.culture.sdx.search.lucene.analysis.Analyzer_fr

Exmaminons sa methode tokenStream :

public final TokenStream tokenStream(String fieldName, Reader reader) {

	// The token stream that will be returned.
	TokenStream result;

	// Builds the chain...
	result = new StandardTokenizer(reader);

	FrenchStandardFilter fsf = new FrenchStandardFilter();
	fsf.enableLogging(logger);
	fsf.setUp(result);

	result = fsf;

	result = new LowerCaseFilter(result);
	if (!keepAccents) {
		ISOLatin1AccentFilter ilf = new ISOLatin1AccentFilter();
		ilf.enableLogging(logger);
		ilf.setUp(result);
		result = ilf;
	}

	if (stopTable != null) result = new StopFilter(result, stopTable);

	// And returns the end of the chain
	return result;
}

La tokenization est assurée par la classe org.apache.lucene.analysis.standard.StandardTokenizer qui renvoie des Tokens typés suivant les constantes définies dans org.apache.lucene.analysis.standard.StandardTokenizerConstants . Tout caractère ne participant pas à la définition d'un Token d'un type reconnu est considéré comme un non-Token remplissant fondamentalement la fonction de séparateur de Tokens.

Les Tokens sont ensuite filtrés par fr.gouv.culture.sdx.search.lucene.analysis.filter.FrenchStandardFilter qui supprime les apostrophes et normalise les acronymes en ôtant les points.

Le filtre suivant, fr.gouv.culture.sdx.search.lucene.analysis.filter.ISOLatin1AccentFilter , est appelé en fonction de la configuration de l'analyseur. Il a pour rôle d'appauvrir les caractères diacritiques, notamment les caractères accentués, du jeu de caractères ISO-8859-1.

Le filtre suivant, org.apache.lucene.analysis.LowerCaseFilter remplace les majuscules par des minuscules.

Le dernier filtre, org.apache.lucene.analysis.StopFilter filtre les Tokens qui apparaissent dans une liste de mots vides ; cette liste peut éventuellement être mentionnée dans le fichier de configuration de l'analyseur.

Un cas particulier d'analyseur : l'analyseur arabe

L'arabe, comme l'hébreu et certaines autre langues sémitiques anciennes (égyptien, araméen, ougaritique...), n'exprime pas le besoin de noter les voyelles brèves. Quand bien même celles-ci ont une importance fondamentale quand à la catégorisation d'un mot (mode, voie, nombre, sens...), l'écriture attend du lecteur un processus de déduction à partir du contexte, censé lever toute ambiguité entre mots dotés d'un vocalisme différent mais disposant à l'écrit du même squelette consonnantique.



Auteur : Pierrick Brihaye - 2003-11-22