UIMA FR
Communauté francophone autour d'UIMA
  • Accueil
  • Top 10
  • Statistiques
  • Inscription
  • Archives
  • Contact

Informations

BilboPlanet - An Open Source RSS feed aggregator written in PHP.
 

Abonnement

  • feed  Fil de tous les articles
  • feed  Fil des articles populaires

Membres

  • feed Fabien POULARD
  • feed Jérôme Rocheteau
  • feed Laurent Audibert
  • feed Matthieu Vernier
  • feed Nicolas Hernandez
  • feed Nicolas Hernandez

Participer

  • meta  Ajouter votre blog
  • meta  Administration

 

Filtrer les articles :     Articles du jour   -   Articles de la semaine   -   Articles du mois   -   Tous les articles

Accès rapide aux derniers articles de la page

  • 09/01/2012 : Premiers pas avec UIMA AS
  • 05/01/2012 : Définition d'un descripteur primitif d'Analysis Engine
  • 05/01/2012 : Implémenter le code métier d'un composant Analysis Engine
  • 05/01/2012 : Définir les types de données (type system) à manipuler au sein d'un composant UIMA
  • 05/01/2012 : Installation des plugins UIMA-AS sous Eclipse Indigo
  • 04/01/2012 : Développer son premier composant UIMA : l'Analysis Engine (AE)
  • 11/12/2011 : Mise en correspondance de vues via un Aggregate pour interconnecter des composants UIMA (view et sof...
  • 08/06/2011 : UIMA sur HADOOP
  • 05/06/2011 : Copier une annotation d'une vue à une autre dans UIMA
  • 25/05/2011 : Construire des modélisations du French Treebank pour le UIMA HMM Tagger
Page suivante »
 

Premiers pas avec UIMA AS

0 vote
Par Fabien POULARD le 09/01/2012 à 10:00 Voir l'article

UIMA offre un cadre de développement structurant pour la mise au point de chaînes de traitement de l'information non structurée. S'il permet simplement de déployer des chaînes complexes et tirer parti de la puissance de calcul des processeurs multicœurs, l'ordonnanceur -- le CPM -- a plusieurs limites :

  • Il n'est pas aisé de déployer une chaîne sur plusieurs machines ;
  • L'affectation de ressources se fait au niveau de la chaîne (CPE) et non au niveau des composants alors que le coût d'exécution de ces derniers est fortement variable ;
  • Les chaînes ne peuvent pas directement traiter un flux continu de données.

Le développement d'UIMA AS cherche à répondre à ces limitations.

Principe général

UIMA AS est développé en remplacement du Collection Processing Manager (CPM) dans le but d'offrir de meilleures capacités de flexibilités et montée en charge. UIMA AS ne remplace nullement UIMA, et les composants développés pour UIMA peuvent aussi bien fonctionner dans une chaîne UIMA AS. AS profite du système d'Aggregate pour organiser les composants en chaînes de traitement, tirant parti au passage des capacités des flow controllers.

L'approche CPM (UIMA classique)

Lorsqu'une chaîne de traitement UIMA est déployée par le CPM, la configuration suivante se met en place :

  • le CPM instancie autant de processing pipelines (PP, combinaison séquentielle de composants) que nécessaire, chaque PP contenant une seule et unique instance de chaque composant ;
  • le Collection Reader (CR) est instancié dans un seul thread (pas de parallélisation possible) ;
  • les CAS produits par le CR sont stockés dans une queue tampon avant d'être distribués vers les différents PP qui ne peuvent qu'en traiter un à la fois : un CAS en sortie d'un composant est directement passé en entrée du composant suivant.

Ce fonctionnement est décrit par le schéma suivant tiré de la documentation d'Apache UIMA si ce n'est que les cas consumers n'existent plus en tant qu'entités particulières mais sont des composants comme les autres (et donc instanciés dans les PP) :

cpe-detail.png

L'approche UIMA AS

Les chaînes dans UIMA AS sont déployées sous forme de services auxquels peuvent se connecter des clients. Les clients envoient des requêtes au service puis attendent le résultat du traitement. L'interface entre les clients et les services se fait au travers d'un système de messagerie asynchrone :

  • les requêtes des clients sont stockées dans une queue tampon en entrée du service similaire à la queue du PP pour le CPM ;
  • les résultats des traitements sont renvoyées dans une queue tampon propre à chaque client.

Ce fonctionnement est décrit par le schéma ci-dessous tiré de la documentation d'Apache UIMA AS. Il permet d'instancier une seule fois une chaîne de traitement et de lui soumettre un flux continu de données à traiter en provenance potentiellement de plusieurs sources.

uima-as-arch.png

Le système de messagerie asynchrone prend en charge la distribution des requêtes entre différentes instances potentielles du service permettant ainsi une montée en charge à chaud. Il suffit de déployer une nouvelle instance du service et lui indiquer de se connecter à la queue tampon d'entrée pour augmenter les capacités de traitement. Le système de messagerie utilisé par défaut, ActiveMQ, ayant la capacité de communiquer au travers du réseau, ledit service peut tout à fait être déployé sur une machine distante.

Une autre force de UIMA AS est que ce système de queue tampons et de routage des CAS au travers d'un système de messagerie asynchrone peut être mis en place au sein même du service entre les différents composants. Pour cette première prise en main évitons tout de même de compliquer les choses...

Mise en place de l'environnement

La mise en place d'une chaîne de traitement UIMA AS comparable à un CPE nécessite trois éléments : le service UIMA AS en charge du traitement, le client qui soumet les données et récupère les résultats du traitement et le système de messagerie coordonne la communication entre ces deux premiers.

La mise en place d'un service UIMA AS nécessite, outre le développement des composants, un descripteur de déploiement (deployment descriptor). Afin de simplifier son écriture dans la prochaine section de ce billet, il est préférable d'installer les plugins Eclipse dédiés.

Un certain nombre de dépendances sont nécessaires pour la compilation et l'exécution d'un service et d'un client UIMA AS, a minima :

  • les bibliothèques UIMA AS ; par simplicité les bibliothèques classiques UIMA (hors addons) sont également packagées dans l'archive UIMA AS. La suite du billet repose sur la version 2.3.1.
  • un certain nombre de bibliothèques Spring :
    • spring-aop
    • spring-asm
    • spring-beans
    • spring-context
    • spring-context-support
    • spring-core
    • spring-expression
    • spring-jms
    • spring-tx
    • spring-web
    • spring-webmvc

Finalement, il est nécessaire d'installer un système de messagerie de type JMS. Le plus simple est d'utiliser celui proposé par défaut : Apache ActiveMQ. Une version est distribuée dans l'archive UIMA AS. Toutefois, si vous êtes sous un environnement Linux, il sera plus aisé et pérenne du paquetage de votre distribution.

Ainsi pour Debian Wheezy, installez le paquet :

sudo apt-get install activemq

puis activez l'instance par défaut :

cd /etc/activemq/instances-enabled/
sudo ln -s ../instances-available/main/

et lancez enfin le système :

sudo /etc/init.d/activemq start

Création et déploiement d'un service

Descripteur de déploiement

Le déploiement d'un service UIMA AS nécessite deux descripteurs :

  • Un descripteur de composant qui définit l'aggregate qui sera déployé ;
  • Un descripteur de déploiement qui décrit comment l'aggregate sera déployé.

Pour l'exercice nous déploierons un composant de prétraitement : découpage en phrases, en mots et étiquetage des rôles grammaticaux. Nous utiliserons pour cela les composants addons distribués avec UIMA : le WhitespaceTokenizer et le HMMTagger. L'écriture de l'aggregate ne pose pas de difficulté particulière, il s'agit d'un composant UIMA classique sans aucune spécificité que nous nommerons POSTagger.xml.

Le descripteur de déploiement est une nouveauté de UIMA AS. Il est disponible dans l'assistant de création d'un nouveau fichier sous la catégorie UIMA aux côtés des autres descripteurs, si tant est que vous avez correctement installé les plugins UIMA AS. Nous le nommerons POSTagger-Service.xml.

Création d'un nouveau descripteur de déploiement UIMA AS

Le descripteur de déploiement se compose de deux onglets : un onglet de configuration générale (Overview) et un onglet de configuration du déploiement (Deployment Configurations). Il faut préciser dans l'onglet de configuration générale le nom de la queue de messagerie sur laquelle le service sera joignable (Name for input queue) et le nom de l'aggregate à déployer comme service (Top analysis engine descriptor). Ces deux champs sont mis en valeur dans la capture d'écran ci-dessous.

Complétion de la configuration générale du déploiement

Par défaut il n'est pas nécessaire d'intervenir sur la configuration du déploiement. Le service sera alors déployé tel qu'il l'aurait été sous forme d'un CPE, c-à-d sans contrôle particulier sur les composants qui composent l'aggregate. Pour l'exercice, nous allons le déployé comme un aggregate UIMA AS, c-à-d que chaque composant de l'aggregate est lui-même déployé comme un service connecté aux autres composants par le système de messageries et de queues.

Ce déploiement nécessite simplement de cocher la case Run as AS aggregate. Une fois coché, les différents composants qui constituent l'aggregate sont accessibles pour une configuration propre.

Configuration du déploiement comme un AS Aggregate

Pour simplifier nous considérerons qu'ils sont tous déployés en local et dans la même JVM. Nous allons simplement demander de déployer trois instances du HMMTagger étant donné que c'est le composant le plus coûteux. Il suffit pour ce faire de sélectionner le composant HMMTagger dans la liste puis de positionner le paramètre Number of replicated instances sur 3.

Configuration du nombre d'instances déployées pour chaque service

Écriture du client asynchrone

Une fois le descripteur de déploiement écrit, il ne reste plus qu'à écrire le code du client asynchrone qui va s'y connecter. La documentation d'UIMA AS décrit en détail un cas d'utilisation duquel ce billet s'inspire.

Tout d'abord, toujours dans le cadre de l'exercice, nous faisons le choix que le service soit déployé par l'application elle-même :

public static final String DD2SPRINGXSLTFILEPATH = 
	"/LIBS-APPS/UIMA/apache-uima-as-2.3.1/bin/dd2spring.xsl";
public static final String SAXONCLASSPATH = 
	"file:/LIBS-APPS/UIMA/apache-uima-as-2.3.1/saxon/saxon8.jar";
public static final String DEPLOYMENTDESCRIPTOR =
	"desc/POSTagger-Service.xml";
...
// Create Asynchronous Client API
BaseUIMAAsynchronousEngine_impl uimaAsEngine = 
		new BaseUIMAAsynchronousEngine_impl();
 
///////////////////////////////////////////////////// SERVICE DEPLOYING
System.out.println("Deploying service");
// Create a Map to hold required parameters
Map<String, Object> servCtxt = new HashMap<String, Object>();
servCtxt.put(UimaAsynchronousEngine.DD2SpringXsltFilePath, 
		DD2SPRINGXSLTFILEPATH);
servCtxt.put(UimaAsynchronousEngine.SaxonClasspath,
		SAXONCLASSPATH);
String serviceId = uimaAsEngine.deploy(DEPLOYMENTDESCRIPTOR, servCtxt);
System.out.println("\\t...Service deployed !");

Une fois le service déployé, nous allons configurer un client asynchrone pour s'y connecter et soumettre des requêtes. Il est important ici que :

  • le paramètre UimaAsynchronousEngine.ServerUri corresponde à l'adresse du gestionnaire de messagerie (broker) spécifié auparavant dans le descripteur de déploiement (nous avons laissé la valeur par défaut ${defaultBrokerURL}, nous verrons après comment la spécifier à l'exécution) ;
  • le paramètre UimaAsynchronousEngine.Endpoint doit correspondre au nom de la queue sur laquelle le service est joignable.
///////////////////////////////////////////////////// SERVICE EXECUTION
System.out.println("Preparing for execution");
// Create map to pass server URI and Endpoint parameters
Map<String, Object> appCtxt = new HashMap<String, Object>();
appCtxt.put(UimaAsynchronousEngine.ServerUri, "tcp://localhost:61616");
appCtxt.put(UimaAsynchronousEngine.Endpoint, "POSTaggerQueue");
appCtxt.put(UimaAsynchronousEngine.CasPoolSize, 2);
// Initialize
uimaAsEngine.initialize(appCtxt);
uimaAsEngine.addStatusCallbackListener(new MyStatusCallbackListener());
// Get an empty CAS from the Cas pool
CAS cas = uimaAsEngine.getCAS();
System.out.println("\\t...CAS retrieved");
// Initialize it with input data
cas.setDocumentText("Un exemple de texte...");
cas.setDocumentLanguage("fr");
// Send CAS to service for processing
uimaAsEngine.sendCAS(cas);
System.out.println("\\t...CAS sent");

Il est intéressant d'observer le fonctionnement du client :

  1. L'initialisation consiste, outre la configuration des différents paramètres précités, à définir l'instance StatusCallbackListener qui sera chargée de consommer les résultats du traitement par le service (toute l'asynchronicité du client réside dans ce fonctionnement) ;
  2. L'envoi d'une demande de traitement passe d'abord par la demande d'un CAS disponible par la méthode getCAS(). Une fois le CAS obtenu, il est initialisé comme le ferait un Collection Reader, puis soumis au système par la méthode sendCAS().

Il ne reste alors plus qu'à implémenter le StatusCallbackListener et plus particulièrement la méthode entityProcessComplete appelée lorsque le traitement d'un CAS est terminé :

static class MyStatusCallbackListener extends UimaAsBaseCallbackListener {
 
	@Override
	public void entityProcessComplete(CAS aCas, EntityProcessStatus aStatus) {
		System.out.println("Entity process complete.");
		// Handle errors
		if ( (aStatus != null) && aStatus.isException() ) {
			List<Exception> exceptions = aStatus.getExceptions();
			for(Exception e: exceptions) {
				e.printStackTrace();
			}
			return;
		}
		// Process CAS
		try {
			System.out.println("Concepts identified:");
			JCas jcas = aCas.getJCas();
			FSIterator<Annotation> it = 
					jcas.getAnnotationIndex(T_Token.type).iterator();
			while( it.hasNext() ) {
				T_Token token = (T_Token) it.next();
				System.out.println("\\t+ POS:" + token.getPos());
			}
		} catch (CASException e) {
			System.out.println("Problem getting a JCas !");
		}
	}
 
}

Exécution

Une fois tout ce travail réalisé, il ne reste plus qu'à exécuter le tout. Il est nécessaire d'ajouter toutes les dépendances nécessaires à l'exécution soit toutes les bibliothèques UIMA et UIMA AS ainsi que les nombreuses dépendances Spring.

Il est également nécessaire de spécifier, par le biais d'une variable de JVM l'adresse du gestionnaire de messagerie que nous avons positionné jusqu'alors à ${defaultBrokerURL}. Pour l'exercice, nous utilisons un broker ActiveMQ local. Le paramètre de la JVM est donc le suivant :

-DdefaultBrokerURL=tcp://localhost:61616

À partir d'ici le service devrait se déployer et le client asynchrone s'y connecter sans accroc. Si vous rencontrez des erreurs du type Connection refused c'est très probablement que votre système de messagerie ne tourne pas ou bien que les noms de queues précisés dans le descripteur et pour le client sont différents.

Retour au sommaire
 

Définition d'un descripteur primitif d'Analysis Engine

0 vote
Par Nicolas Hernandez le 05/01/2012 à 18:18 Voir l'article

Ce post est en lien avec le post Développer son premier composant UIMA : l'Analysis Engine (AE) 

La dernière étape consister à créer un descripteur indiquant au framework UIMA comment utiliser le composant. En particulier il définit la classe métier de l'Annotator, le Type System manipulé, les Types qui sont utilisés comme input et ceux qui seront des output. C'est aussi ici que sont déclarés les paramètres et les ressources partagées utilisés si il y a lieu.

La création du descripteur du composant est facilitée à travers Eclipse et les plugins installés. Dans le répertoire desc/opinionRecognizer du projet (ou seulement desc si vous n'avez pas packagé votre projet) :
  • Créer le fichier descripteur du composant, opinionRecognizerAE.xml en cliquant dessus avec le bouton droit et New - Other - UIMA - Analysis Engine Descriptor File.
  • Sur la première page (onglet Overview accessible au bas du cadre) spécifier le nom de la classe qui implémente votre code métier (i.e. opinionRecognizer.OpinionRecognizerAE).
  • Sous l'onglet Type System, ajouter (Add) par nom (by name) le type system défini pour votre composant (i.e. desc/opinionRecognizer/opinionRecognizerTS.xml).
  • Enfin sous l'onglet Capabilities, spécifiez les types des annotations qui doivent apparaître en sortie (i.e. SentenceAnnotation, TokenAnnotation et Opinion).
Si en cliquant sur ce fichier pour l'ouvrir, vous n'accédez qu'à son contenu XML, alors demandez d'ouvrir avec le Component Descriptor Editor en cliquant sur le fichier avec le bouton droit.

Retour au sommaire
 

Implémenter le code métier d'un composant Analysis Engine

0 vote
Par Nicolas Hernandez le 05/01/2012 à 18:16 Voir l'article

Ce post est en lien avec le post Développer son premier composant UIMA : l'Analysis Engine (AE) 

La seconde étape correspond au développement du code métier du composant.

Le code métier d'un AE est à minima constitué d'une classe dite Annotator qui étend la classe JCasAnnotator_ImplBase du framework UIMA. En particulier, on trouve le code métier dans une méthode surchargée appelée process qui est automatiquement appelée à l'exécution pour chaque CAS traitée. La méthode process a accès au CAS et via l'API d'UIMA elle peut manipuler l'artifact ou les (index d') annotations qui ont été précédemment ajoutées ou bien en ajouter de nouvelles.

La classe JCasAnnotator_ImplBase est une implémentation par défaut de la classe AnalysisComponent. Cette implémentation par défaut implémente toutes les méthodes exceptée la méthode process. En général on travaille directement à partir de celle-ci quitte à surcharger les méthodes déjà implémentées. 
La classe AnalysisComponent a plusieurs méthodes dont les plus importantes sont initialize, process et collectionProcessComplete. La méthode initialize est appelée une fois par le framework UIMA à la création de la première instance de la classe Annotator ; elle sert par exemple à récupérer la valeur de paramètres ou à charger des ressources qui seront partagées par les différentes instances de la classe. La méthode process est donc appélée une fois par item traité. La méthode collectionProcessComplete est appelée quand l'entière collection a été traitée et sert à produire des résultats relatifs à toute la collection.

Dans cet exercice, vous n'aurez besoin que d'implémenter la méthode process.

Création de la classe Annotator
  1. Dans le répertoire src, créer le package opinionRecognizer.
  2. Au sein du package, créer la classe OpinionRecognizerAE. Par convention les classes Annotator se termineront par le suffixe AE.
  3. Faire étendre la classe de JCasAnnotator_ImplBase
  4. et surcharger la méthode process.
Cela doit donner quelque chose comme :
package opinionRecognizer;
import
org.apache.uima.analysis_component.JCasAnnotator_ImplBase;
import org.apache.uima.jcas.JCas;
import opinionRecognizer.types.*;
public class OpinionRecognizerAE extends JCasAnnotator_ImplBase{
    public void process(JCas aJCas)  {
        // Faire quelque chose
    }
}
Création du code métier au sein de la méthode process

La méthode process reçoit en argument une instance de JCas laquelle constitue le document analysé ainsi que toutes les annotations qui y ont été associées lors d'éventuelles précédentes analyses. Le JCas fournit une approche JNI (Java Native Interface) pour la manipulation des objets CAS et de leur propriétés (i.e.~avec des new, des getter/setter, ... Le framework UIMA se charge de passer le CAS d'un composant à un autre.

Pour cet exercice, l'analyseur devra ajouter une nouvelle annotation Opinion délimitée aux offsets (begin et end) de toutes les phrases SentenceAnnotation contenant des mots TokenAnnotation qui sont des verbes (posTag débutant par "vb" égal à "bez") ou dont la forme de surface coveredText est "we" ou "our". On ajoutera aussi le nombre de mots contenu dans l'opinion length.

L'algorithme que je propose d'implémenter est le suivant :
  1. récupération d'un index de phrases SentenceAnnotation
  2. parcourir l'index de phrases et pour chacune, 
    1. récupérer un sous index de mots TokenAnnotation
    2. parcourir l'index de mots et pour chaque mot
      1. définir un booléen à vrai si un mot a son trait posTag débutant par "vb" égal à "bez"
      2. définir un booléen à vrai si un mot a son trait forme de surface coveredText est "we" ou "our".
      3. incrémenter un compteur de mots
    3. si les deux booléens sont vrais alors
      1. créer une nouvelle annotation Opinion aux offsets (begin et end) de la phrase courante
      2. initialiser les valeurs begin, end et length de cette annotation
La récupération d'un index de phrases SentenceAnnotation peut se réaliser à l'aide de la méthode getAnnotationIndex appliquée au JCas et qui prend en argument le type d'annotation souhaité. Le parcourir de l'index de phrases peut se réaliser à l'aider d'un Iterator. 
Cela doit donner quelque chose comme :
AnnotationIndex<annotation>  aSentenceAnnotationAnnotationIndex = aJCas.getAnnotationIndex(SentenceAnnotation.type);
Iterator<annotation> aSentenceAnnotationIterator = aSentenceAnnotationAnnotationIndex.iterator(); 
while (aSentenceAnnotationIterator.hasNext()) { 
    SentenceAnnotation aSentenceAnnotation = (SentenceAnnotation) aSentenceAnnotationIterator.next();
    // Faire quelque chose
}
L'ajout du précédent code lève des erreurs de dépendances non importées (AnnotationIndex, Iterator, SentenceAnnotation).  Elles se résolvent très facilement par un left-click sur la petite croix rouge dans la marge de gauche au niveau de chaque erreur détectée et en optant pour le bon import.
Cette action sera à reproduire avec le code à venir.

La récupération d'un index d'annotations couvertes par une autre annotation peut se faire à l'aide de la méthode subiterator qui s'applique sur un index d'annotations et qui prend en argument l'annotation recouvrante.
Elle requiert la création d'un index d'annotations. Pour notre besoin, un index de mots TokenAnnotation peut suffire. Il se construit comme l'index de phrases vu ci-dessus. On décide de récupérer l'index de toutes les annotations pour varier les plaisirs. Il faudra alors ne considérer que les annotations mots qui nous intéressent.
L'instruction suivante réalise cela. On la place au même niveau que la construction d'index de phrases.

AnnotationIndex<annotation> anAnnotationIndex = aJCas.getAnnotationIndex();


A l'intérieur de la boucle, pour chaque phrase on récupère un FSIterator (qui est un iterator un peu spécial sur ce type de structure) sur les annotations couvertes par la phrase courant aSentenceAnnotation.
Cela donne 
FSIterator<annotation> anySubSentenceAnnotationFSIterator = anAnnotationIndex.subiterator(aSentenceAnnotation);
while (anySubSentenceAnnotationFSIterator.hasNext()) {
   Annotation aSubSentenceAnnotation = (Annotation) anySubSentenceAnnotationFSIterator.next();
   // Faire quelque chose
}
Si l'annotation couverte courante aSubSentenceAnnotation est un TokenAnnotation alors on teste si un mot a son trait posTag débutant par "vb" égal à "bez" et si un mot a son trait forme de surface coveredText est "we" ou "our" et l'on compte le mot.
Cela correspond au code ci-dessous

if (aSubSentenceAnnotation.getClass().getName().equalsIgnoreCase("org.apache.uima.TokenAnnotation")) {
    TokenAnnotation aWord = (TokenAnnotation) aSubSentenceAnnotation;
    if (aWord.getPosTag().toLowerCase().startsWith("vb") || aWord.getPosTag().equalsIgnoreCase("bez")) {
        containsAVerb = true;
    }
    if (aWord.getCoveredText().equalsIgnoreCase("our") || aWord.getCoveredText().equalsIgnoreCase("we")) {
        containsAKeyword = true;
    }
    wordCounter++;
}

Pour que ce code fonctionne il faut rajouter quelques déclarations de variables avant la boucle de parcours des annotations couvertes par l'annotation phrase courante.

boolean containsAVerb = false;
boolean containsAKeyword = false;
int wordCounter = 0;
Pour chaque phrase aSentenceAnnotation parcourue on teste si les présences d'un mot clef et d'un verbe sont confirmées. Dans la positive, on créer une nouvelle annotation Opinion. On définit ses traits begin et end en fonction des begin et end de la phrase courante. On définit aussi la longueur length de l'opinion. Et au final on ajoute l'annotation ainsi créée à l'index des annotations à l'aide de la méthode addToIndexes.
Ce qui donne le code suivant :
if ((containsAKeyword) && (containsAVerb)) {    Opinion aOpinion = new Opinion(aJCas);    aOpinion.setBegin(aSentenceAnnotation.getBegin());    aOpinion.setEnd(aSentenceAnnotation.getEnd());    aOpinion.setLength(wordCounter);    aOpinion.addToIndexes();}

Retour au sommaire
 

Définir les types de données (type system) à manipuler au sein d'un composant UIMA

0 vote
Par Nicolas Hernandez le 05/01/2012 à 18:13 Voir l'article
Ce post est en lien avec le post Développer son premier composant UIMA : l'Analysis Engine (AE) 

La première étape pour développer un composant est de définir les types de données qu'il va manipuler. 

Création du descripteur et des types de données à manipuler 

En lien avec notre exercice, nous allons définir le type d'annotation opinionRecognizer.types.Opinion qui correspondra au type des annotations produites par notre composant d'analyse. On lui ajoutera un trait (feature) length qui indiquera le nombre de mots contenu dans l'annotation Opinion courante. Par simplicité nous ne rappellerons pas par la suite le nom de package (opinionRecognizer.types) qui précède le nom du type.

D'abord créons le fichier descripteur :
Les fichiers descripteurs de systèmes de types et d'AE se placent dans le répertoire desc. Pour s'y retrouver ultérieurement parmi les descripteurs qui seront disponibles dans le CLASSPATH, je vous conseille de créer un sous répertoire portant le nom de votre projet (en respectant les conventions de nommage Java), par exemple ici : opinionRecognizer.
  1. Faire un clic-droit sur le répertoire desc (ou sur le sous-répertoire que vous venez de créer si vous avez suivi mon conseil).
  2. Choisir le menu 'New'-'Other...'-'UIMA'-'Type System Descriptor File'.
  3. A l'écran suivant, donner un nom au fichier descripteur de types par exemple opinionRecognizerTS.xml. Là encore par convention et pour s'y retrouver plus tard je vous conseille d'utiliser le suffixe TS pour vos noms de fichier descripteur de systèmes de types.
Ensuite, une fois le fichier créé, ajoutons le type désiré  opinionRecognizer.types.Opinion : 
  1. Dans l'onglet Type System du descripteur, ajouter un type avec le bouton Add Type.
  2. Donner lui le nom de opinionRecognizer.types.Opinion
  3. Le faire hériter du type uima.tcas.Annotation défini par le framework UIMA et qui permet d'hériter des traits begin, end et coveredText. Le trait coveredText est un trait un peu particulier parce que l'on ne peut y accéder qu'en lecture. Il retourne le texte de l'artifact couvert entre les offsets délimités par les valeurs entières begin et end.
Ajoutons aussi une feature au type :
  1. En sélectionnant le type, cliquer sur le bouton Add...
  2. Donner lui le nom de length qui indiquera le nombre de mots contenu
  3. Faire hériter du type simple Integer.
Puisque nous manipulons aussi les types mot, org.apache.uima.TokenAnnotation, et phrase, org.apache.uima.SentenceAnnotation, qui sont des uima.tcas.Annotation et le trait posTag (String) du type TokenAnnotation dont les annotations auront été produits par les composants de prétraitement, il nous faut aussi ajouter ces types. Le faire.

Génération automatique des l'API JAVA du système de types

Une fois le fichier de descripteur de systèmes de type créé et sauvé, une API java permettant de manipuler les types (et les traits) est automatiquement générée. 

Si jamais vous craignez que cette API ne soit pas générer ou que vous vouliez forcer sa re-génération, cliquer sur le bouton JCasGen présent dans l'éditeur du fichier Système de Type.

Les fichiers Opinion.java et Opinion_Type.java sont générés dans le sous répertoire  opinionRecognizer.types du répertoire src du projet. 
Le fichier  Opinion.java  contient le constructeur de type. Noter la présence d'accesseurs pour modifier la valeur des attributs du type Opinion (par exemple les méthodes getLength() et  setLength(int v)).
Retour au sommaire
 

Installation des plugins UIMA-AS sous Eclipse Indigo

0 vote
Par Fabien POULARD le 05/01/2012 à 11:28 Voir l'article

Au sein de Dictanova, nous avons des besoins importants en termes de distribution de la charge de calcul. Le CPM classique d'UIMA (l'organe chargé de l'ordonnancement des traitements) ne répond pas suffisamment à nos besoins et nous nous tournons donc vers UIMA AS (pour UIMA Asynchronous Scaleout) qui offre des possibilités de montées en charge beaucoup plus importantes en permettant notamment de déployer les Analysis Engine dans plusieurs JVM et sur plusieurs machines.

Je décris dans ce billet la procédure que j'ai mise en oeuvre pour installer les plugins UIMA AS sous Eclipse Indigo.

Le problème

Dans le meilleur des mondes, l'installation des plugins UIMA AS devrait se dérouler sans encombre en utilisant le gestionnaire de plugins d'Eclipse. Cependant, si comme moi vous n'aviez jusqu'à présent que les plugins UIMA classiques d'installés et que vous souhaitez installer les plugins UIMA AS, Eclipse vous informera que c'est impossible car une dépendance n'a pas été trouvée.

L'origine de ce problème est la récente release de UIMA 2.4.0, et la mise à jour dans cette version 2.4.0 des plugins alors que les plugins UIMA AS dépendent d'une version 2.3.x. Il est donc nécessaire de revenir à une version 2.3.1 des plugins UIMA avant d'installer les plugins UIMA AS.

Rétrograder les plugins Eclipse Apache UIMA en version 2.3.1 (downgrade) pour installer le plugin UIMA AS

Il n'est pas directement possible de revenir à une version antérieure d'un plugin. Il est nécessaire dans un premier temps de désinstaller le plugin, puis d'installer la version antérieure.

Pour désinstaller le plugin, il faut se rendre dans le menu Aide > À propos d'Eclipse > Détails de l'installation (Help > About Eclipse > Installation Details) comme le montrent les captures d'écran ci-dessous :

eclipse-about-install.png

eclipse-installation-details.png

La fenêtre liste les features actuellement installées. Sélectionnez celles qui correspondent à UIMA puis cliquez sur Désinstaller (Uninstall).

Une fois la désinstallation effectuée et après avoir redémarrer Eclipse, il est possible d'installer la version 2.3.1 des plugins UIMA classiques et dans le même temps le plugin UIMA AS.

Rendez-vous dans le menu Aide > Ajout de nouveaux logiciels (Help > Install new software), indiquez à l'assistant d'utiliser tous les sites à sa disposition et décochez la case indiquant de ne montrer que les dernières versions comme l'illustre la capture d'écran ci-dessous :

eclipse-install-plugins.png

Il suffit ensuite de sélectionner les versions 2.3.1 des différents plugins : UIMA Runtime, UIMA tools et UIMA AS. L'installation devrait alors se dérouler sans accrocs.

Retour au sommaire
 

Développer son premier composant UIMA : l'Analysis Engine (AE)

0 vote
Par Nicolas Hernandez le 04/01/2012 à 18:11 Voir l'article
Objectifs 
Ce post présente ce qu'est le composant élémentaire de toute chaîne de traitement d'analyse UIMA à savoir l'Analysis Engine (AE). Il explique de quoi il se compose techniquement, et comment développer ses différentes parties.
Il s'agit entre autres de prendre en main l'API et les outils UIMA pour réaliser des opérations classiques : telles que définir des types d'annotation, générer automatiquement l'API pour manipuler les types d'annotations définis, récupérer des annotations posées par d'autres annotateurs, consulter et définir les valeurs de traits d'une annotation, créer une nouvelle annotation, créer un fichier XML descripteur et tester un composant.
Pour illustrer cet exercice de développement on cherchera à développer la fonction de reconnaissance de phrases qui expriment une opinion. On posera l'hypothèse qu'une phrase qui contient des verbes et des mots dont la forme de surface comme "we" ou "our" est une expression d'opinion... On utilisera pour cela quelques AE disponibles dans les Apache Uima Addons. On testera l'AE sur les textes présents dans le répertoire UIMA_HOME/examples/data notamment Apache_UIMA.txt. De fait nous travaillerons sur l'anglais pour cet exercice.


Prérequis

Il est nécessaire d'avoir réaliser les tutoriels suivant :
  • Savoir construire une chaîne de traitement sous Eclipse sous la forme d'un Aggregate d'Analysis Engine (AAE)
  • Savoir exécuter une chaîne de traitement (sous Eclipse, en ligne de commande ou via le DocumentAnalyzer GUI)
Tel que l'explique ces précédents posts, il faudra avoir au préalable construit et avoir à disposition une chaîne que nous appellerons de pré-traitement et qui sera composée des Apache UIMA addons suivant : 
  • WhiteSpaceTokenizer qui découpe en mots, TokenAnnotation, et phrases, SentenceAnnotation, un texte fourni en entrée
  • Tagger qui rajoute un trait posTag aux TokenAnnotation
Qu'est ce qu'un composant UIMA de type Analysis Engine et à quoi cela sert ?
Un Analysis Engine (AE) est le composant élémentaire d'une chaîne de traitements UIMA. Il a généralement pour objet une tâche d'analyse (par exemple reconnaître si une phrase est une expression d'une opinion) sur ce que l'on appelle un artifact (par exemple un texte). Il reçoit les résultats d'analyse de précédents composants (par exemple un découpage en phrases, en mots, la reconnaissance des étiquettes grammaticales (nom, verbe, adjectif...)) qui lui servent de base pour ses traitements afin qu'il puisse à son tour associer à l'artifact le résultat d'analyse qu'il produit (par exemple les opinions). On appelle les résultats d'analyse, des meta-données ou des annotations. L'ensemble formé par l'artifact et les méta-données constitue la structure commune d'analyse (CAS ou Common Analysis Structure) et est LA structure de donnée qui transite d'un composant à un autre.

De quoi est constitué un composant UIMA ?
Un composant UIMA est logiquement constitué de trois éléments :
  • la définition des types des données que le composant manipule (lit et crée), on appelle cela le système de types (TS ou type system),
  • le code métier qui réalise la fonction souhaitée,
  • un descripteur indiquant au framework UIMA comment utiliser le composant.
Le système de types et le descripteur du composant sont des fichiers XML que l'on peut éditer avec des interfaces graphiques via les plugins Eclipse de UIMA. La définition du système de type entraîne la génération automatique d'une API pour manipuler les types de données définis au sein du code métier.

L'ensemble de ces éléments doit se trouver accessible via le CLASSPATH de l'application qui exécute. En général, on encapsule ces éléments au sein d'une même archive Jar.

Préparer un projet Java sous Eclipse pour accueillir le développement de votre composant

Il vous faut un projet Java sous Eclipse pour accueillir le développement de votre composant. Différentes options : 
  • vous pouvez utiliser celui précédemment créé pour la chaîne de prétraitement 
  • ou bien vous créez un nouveau projet comme indiquer ici mais il vous faudra alors déclarer une dépendance vers soit le projet qui contient votre chaîne de prétraitement soit vers le jar que vous aurez exporté à partir de celle-ci. Pour déclarer la dépendance au projet, Click droit sur le projet, Build path, Configure build path, Onglet Projects, Add les projets, Ok, OK...
Les étapes du développement
  1. Définition des types de données (type system) à manipuler au sein d'un composant UIMA
  2. Implémentation du code métier d'un composant Analysis Engine
  3. Définition d'un descripteur primitif d'Analysis Engine 

Exécuter votre AE au sein d'un Aggregate

Au sein du répertoire desc/opinionRecognizer créer un descripteur aggregate qui ajoute en pipeline le descripteur de pretraitement et le descripteur de votre AE. N'oubliez pas de changer les capabilities.
Et exécutez le.


Pour aller plus loin
  • la création et la récupération de vues,
  • la définition des paramètres au sein du descripteur et leur accès au sein du code, 
  • la déclaration et la manipulation de ressources partagées par plusieurs instances d'un même AE,
  • le parcours d'index d'annotations à l'aide de contraintes
Remerciement
Le contenu décrit sur cette page s'inspire, adapte, étend le support créé pour le tutoriel qui a eu lieu à UIMA@RMLL'09 ainsi que le tutoriel Getting started de la documentation d'Apache UIMA.
Je vous invite à consulter ces différentes sources pour des informations complémentaires


Retour au sommaire
 

Mise en correspondance de vues via un Aggregate pour interconnecter des composants UIMA (view et sofa name mapping)

0 vote
Par Nicolas Hernandez le 11/12/2011 à 00:58 Voir l'article
Après avoir rappelé quelques notions (que le lecteur averti peut ne pas lire), le post expose brièvement le problème et présente avec un exemple la mise en oeuvre d'une solution de mise en correspondance de vues au sein d'un Aggregate.

Bref rappel de quelques notions
Les chapitres 5 et 6 de la documentation UIMA tutorials_and_users_guides [1;2] rappellent les définitions des notions d'Artifact, Sofa et View.
Brièvement l'Artifact est la chose non structurée que l'on souhaite analyser. Le Sofa (Subject of Analysis) est une représentation de l'Artifact (e.g. HTML). Une annotation metadata est de l'information associée à un Sofa particulier pour en décrire une sous région. La notion de View simplifie les choses quand plusieurs versions de l'Artifact sont nécessaire à différentes étapes de l'analyse (e.g. une version sans balise d'un document XML). Les notions de Sofa et de CAS View sont liées. Dans l'implémentation Apache actuelle (2.4), à chaque Sofa est associé une seule View et à chaque View un seul Sofa. Le nom qui désigne une vue particulière est Sofa name pour des raisons historiques mais cela s'applique indifféremment à View aussi.
L'analyse d'un composant UIMA (AE - Analysis Engine) porte toujours sur une vue. Il peut y en avoir plusieurs. L'AE peut en créer. Les résultats d'analyse peuvent être associés sur les vues traitées ou bien sur les vues créées.
Un AE qui travaille seulement sur une seule vue est appelée "single-view component" et dans le cas contraire "multi-view/multi-sofa component".
Par défaut, si rien n'est spécifié il y a qu'une seule view et celle-ci a le sofa name de "_InitialView".

Problème d'interopérabilité pour interconnecter des composants développés indépendamment
Le développeur d'AE est libre de choisir le nom qu'il souhaite pour désigner ses vues d'entrée et ses vues de sortie.
Cela conduit naturellement à quelques petits soucis lorsque l'on veut interconnecter des composants ayant des noms de vue différents.

Le mécanisme de mise en correspondance des vues au niveau d'un Aggregate
La section 6.4 de la documentation explique et présente différentes situations de mises en correspondance : au sein d'un Aggregate, d'un CPE, d'une application UIMA, pour des services distants. Je rapporte et illustre la solution au sein d'un AE Aggregate.

Pour cela, j'utilise deux composants : l'XmlDetagger présent dans les exemples fournis avec Apache UIMA [4] et le Whitespace Tokenizer des addons de la Sandbox d'Apache UIMA [5].

L'XmlDetagger est un composant multiple vues qui retire les balises d'un document XML. Il lit l'XML d'une vue input nommée "xmlDocument". Le contenu textuel est écrit dans une nouvelle vue output appelée "plainTextDocument".
Le Whitespace Tokenizer est un composant simple vue qui travaille sur la vue par défaut (i.e.  "_InitialView") et qui ajoute à celle-ci des annotations de phrases et de tokens.

Rapidement, je mets en place un projet Java sous Eclipse. J'y ajoute la UIMA Nature, déclare les répertoires desc et resources dans le build path ainsi que les bibliothèques de UIMA [6]. Je rajoute au  le build path les bibliothèques uima-examples [10] et addon whitespace tokenizer [9] qui vont servir pour notre chaîne.

Je crée un descripteur d'AE Aggregate [7] et j'y ajoute by name en pipeline : d'abord l'XmlDetagger  puis le Whitespace Tokenizer (je déclare les bons types à produire en output dans les capabilities).

A la sauvegarde le message suivant s'affiche
Error in AE Descriptor
The Descriptor is invalid for the following reason:
ResourceInitializationException: The output Sofa "plainTextDocument" in component "XmlDetagger" is not mapped to any output Sofa in its containing aggregate, "demoSofaNameMappingAAE". (Descriptor path ...) 

Ce n'est pas réellement une erreur si l'on sait ce que l'on fait. Une vue qui n'est pas utilisée par exemple n'a pas besoin d'être mise en correspondance dans l'Aggregate.
En l'état si on exécute cet AE avec le DocumentAnalyzer [8] sur un document XML quelconque, on obtiendra l'erreur suivante :
org.apache.uima.analysis_engine.AnalysisEngineProcessException: Annotator processing failed. Caused by: org.apache.uima.cas.CASRuntimeException: No sofaFS with name xmlDocument found. 
Vraissemblablement ici il y a des vues qui requièrent d'être mise en correspondance et cela va se faire au niveau de l'Aggregate.

La première chose à faire est d'indiquer que la vue sur laquelle travaille en entrée XmlDetagger, à savoir xmlDocument, correspond à _InitialView dans l'Aggregate. Autrement dit, la vue d'entrée par défaut de l'Aggregate,  _InitialView, doit être mise en correspondance avec la vue d'entrée, xmlDocument, de l'XmlDetagger.
Au sein du descripteur de l'Aggregate, c'est dans la section capabilities que l'on déclare les vues sur lesquelles on travaille (sous section component capabilities) et celles entre lesquelles on réalise des mises en correspondance (sous section sofaMappings).
On ajoute d'abord la vue xmlDocument comme input (entrée) en faisant un add sofa dans la component capabilities.
Ensuite on indique dans la sous section sofaMappings que pour le composant (componentKey) XmlDetagger sa vue (componentSofaName) xmlDocument correspond à la vue dans l'aggregate (aggregateSofaName) _InitialView.
Par les éléments componentKey et componentSofaName on désigne le composant et la vue souhaitée chez celui-ci comme devant intervenir dans un mapping. L'élément aggregateSofaName est la clé partagée entre les éléments sofaMapping.
Cette dernière opération doit se faire pour partie à la main en éditant le source (personnellement je n'y arrive pas via l'éditeur du formulaire). Cela donne
<capability>
...
  <inputSofas>
    <sofaName>xmlDocument</sofaName>
  </inputSofas>
  ...
et
  <sofaMappings>
    <sofaMapping>
      <componentKey>XmlDetagger</componentKey>
      <componentSofaName>xmlDocument</componentSofaName>
      <aggregateSofaName>_InitialView</aggregateSofaName>
    </sofaMapping>
  </sofaMappings>
Si on réexécute l'AE on se rend compte que ce n'est pas encore l'idéal car le Whitespace Tokenizer travaille sur l'_InitialView par défaut et traite donc ce qui correspond à l'xmlDocument au lieu du plainTextDocument.
On rajoute dans l'élément inputSofas
<sofaName>_InitialView</sofaName>
et l'élément suivant
  <sofaMapping>
      <componentKey>WhitespaceTokenizer</componentKey>
      <componentSofaName>_InitialView</componentSofaName>
      <aggregateSofaName>plainTextDocument</aggregateSofaName>
    </sofaMapping>
La vue plainTextDocument du XmlDetagger n'a pas été redéfinie dans l'Aggregate, il n'y a pas d'ambiguité. Ce que produit le XmlDetagger au sein de sa vue output plainTextDocument est accessible sous ce même nom au sein de l'Aggregate qui le met en correspondance avec la vue input du
WhitespaceTokenizer.   
L'exécution fonctionne comme attendu finalement.  

Quelques commentaires supplémentaires

Au niveau de l'Aggregate après mise en correspondance, le nom de la vue du componentSofaName
sera renommé en le nom de la vue déclarée dans aggregateSofaName.
Les vues avec leur nom précédent ne seront plus accessibles. Une fois sérialisée en XMI, on constatera sela en cherchant la valeur de l'attribut sofaID.
Les composants d'Apache OpenNLP sont des composants multiples vues et requièrent en général l'usage de mises en correspondance.


Liens

[1] 5. Annotations, Artifacts & Sofas
[2] 6. Multiple CAS Views
[3] 6.4. Sofa Name Mapping
[4] UIMA_HOME/examples/descriptors/analysis_engine/XmlDetagger.xml
[5] http://uima.apache.org/sandbox.html#whitespace.tokenizer
[6] Créer un projet Eclipse pour le développement d'un composant UIMA
[7] Construire une chaîne de traitement UIMA à partir de composants existants
[8] Exécuter un traitement ou une chaîne de traitement sous UIMA (avec UIMAJ et en local)
[9] UIMA_HOME/addons/annotator/WhitespaceTokenizer/lib/uima-an-wst.jar
[10] UIMA_HOME/lib/uima-examples.jar
Retour au sommaire
 

UIMA sur HADOOP

0 vote
Par Nicolas Hernandez le 08/06/2011 à 15:09 Voir l'article
Faire tourner des applications UIMA sur HADOOP.
  • http://www.isabel-drost.de/hadoop/slides/uimaHadoopHofer.pdf (dans http://wiki.apache.org/hadoop/HadoopPresentations ; illustration avec une application simple et UIMA 2.2.1)
  • https://cwiki.apache.org/UIMA/running-uima-apps-on-hadoop.html
  • Behemoth (is an Apache Licensed open source platform for large scale document processing which allows deploying UIMA applications within Hadoop) 
En Février dernier (2011), IBM a fait un gros coup de pub avec son système Watson qui a gagné le jeu Jeopardy aux Etats-Unis contre des joueurs humains (il s'agit de retrouver la question à partir d'une réponse soumise). Le système utilisait UIMA et HADOOP...
  • https://blogs.apache.org/foundation/entry/apache_innovation_bolsters_ibm_s (annonce officielle)
  • http://blog.reddit.com/2011/02/ibm-watson-research-team-answers-your.html, http://www.enterpriseirregulars.com/32391/beyond-jeopardy-with-ibm-watson-%E2%80%93-quick-analysis (Question/Réponses sur Watson)

Pour aller plus loin

  • http://www.quora.com/IBM-Watson/Whats-the-system-architecture-of-the-IBM-Watson (une comparaison des architectures entre Watson et Wolfram)
  • http://maggon.posterous.com/uima-and-gate-applications-over-hadoop (UIMA et GATE sur HADOOP)


Retour au sommaire
 

Copier une annotation d'une vue à une autre dans UIMA

0 vote
Par Fabien POULARD le 05/06/2011 à 00:59 Voir l'article

Lorsque l'on travaille avec plusieurs vues au sein d'un même CAS, on se retrouve rapidement confronté au besoin de recopier certaines annotations d'une vue vers une autre.

Il est possible de recréer l'annotation et de repositionner tous ses traits sur les mêmes valeurs que l'annotation d'origine. Cette approche est fastidieuse lorsque l'annotation est complexe, que l'on n'en connaît pas tous les traits ou bien lorsque l'on veut copier plusieurs annotations de types différentes.

Une autre approche, beaucoup plus souple, consiste à faire une copie profonde de l'objet Java du CAS correspondant à ladite annotation à l'aide de la méthode clone. Il faut alors penser à modifier la valeur du SOFA associée la nouvelle annotation sous peine de se voir refuser l'ajout de l'annotation copiée à l'index de la nouvelle vue.

La difficulté réside dans le fait que le trait contenant le SOFA n'est pas directement accessible. Il faut utiliser la méthode setFeatureValue pour mettre à jour la valeur :

Feature sofaFeature = annotation.getType().getFeatureByBaseName("sofa");
annotation.setFeatureValue(sofaFeature, view.getSofa());

Voici la méthode que j'utilise désormais pour copier mes annotations d'une vue à une autre :

public static Annotation copyAnnotationToView(Annotation a, JCas view) {
	// To copy the annotation we must process in three steps
	// 1- Clone the annotation from the original view
	Annotation a2 = (Annotation) a.clone();
	// 2- Change the Sofa of the cloned annotation
	Feature sofaFeature = a2.getType().getFeatureByBaseName("sofa");
	a2.setFeatureValue(sofaFeature, view.getSofa());
	// 3- Add this annotation to the indexes of the new view
	a2.addToIndexes(view);
	return a2;
}
Retour au sommaire
 

Construire des modélisations du French Treebank pour le UIMA HMM Tagger

0 vote
Par Nicolas Hernandez le 25/05/2011 à 17:29 Voir l'article
Mise à jour le 1 juin. Précision sur le traitement des mots composés du French Treebank

Ce post décrit la procédure que j'ai mise en place pour construire des modélisations de type HMM (Hidden Markov Model) utilisées par le Tagger de la UIMA sandbox à partir d'un corpus XML qui porte des annotations sur des tokens mots, à savoir le corpus French Treebank.

Ci-dessous je présente 
  • les données d'entraînement utilisées
  • la procédure d'entraînement et sa mise en oeuvre
  • les composants requis
  • le paramétrage des composants 
  • le contenu d'un projet Eclipse autonome que je distribue et qui illustre la phase d'entraînement de modélisations et la phase d'étiquetage à partir de modélisations construites.
  • les opérations à réaliser au sein du projet Eclipse pour entraîner une modélisation ou étiqueter des textes à partir d'une modélisation existante
  • des problèmes qu'il reste à rêgler pour améliorer les modélisations
  • une FAQ
Le projet Eclipse est téléchargeable ici. Il s'agit de l'importer dans Eclipse. La donnée d'entraînement n'est pas distribuée (consulter la page dédiée au corpus pour cela, seul un exemple que j'ai construit est distribué). Les modélisations construites à partir du corpus sont présentes et sont distribuées sous licence Apache v2. Toutes les dépendances requises à l'exécution sont présentes.

Corpus d'entraînement : le French Treebank (training data set)
J'ai utilisé une version UTF-8 de Juillet 2010 du corpus French Treebank. Ce corpus a été construit à l'Université de Paris 7. Consulter Abeille et al, 2003, Building a treebank for French, in Treebanks, Kluwer pour plus de détails. Contacter A. Abeille pour obtenir une version de ce corpus.

L'information annotée dans ce corpus permet de construire (entre autres) des modélisations pour l'analyse morphosyntaxique de token mots telles que de
  • l'étiquetage grammatical (part of speech (pos) tagging), 
  • de la morphologie flexionnelle (inflection analysis (mph)), 
  • de la sous-catégorisation grammaticale (subcategorization analysis (subcat)) 
  • et de la lemmatisation (lemmatization (lemma)).
Je précise que la procédure décrite ici est indépendante du format XML de la donnée d'entraînement dans la mesure où la seule contrainte soit que l'annotation à apprendre doit être décrite à l'aide d'un balisage en ligne XML (c'est-à-dire couvrant la zone de texte annotée). Le composant d'importation (uima-connectors) que j'utilise me permet cela.

Procédure de construction de modélisation (training process)
La procédure de construction de modélisation est la suivante
  1. importation des informations portées par les éléments et attributs XML en annotations génériques au sein d'un CAS représentant le document.
  2. entraînement d'une modélisation HMM à partir de ces annotations
La mise en oeuvre de la procédure
  • utilise un composant générique pour l'importation des informations XML -les informations sont importées sous la forme d'annotations génériques- et implique l'utilisation d'un composant assurant la transformation des annotations génériques en annotations à même d'être prise en compte par le composant suivant d'apprentissage.
  • utilise les paramètres des différents composants pour spécifier 
    • les vues sur lesquelles travailler : la vue dans laquelle est chargée le document XML et celle où se trouve seulement le contenu textuel avec les annotations importées
    • les featurePath qui pointent le trait de l'annotation dont il faut considérer la valeur pour l'apprentissage
Composants utilisés (components)
En pratique j'ai utilisé trois Analysis Engines (AE) appartenant à trois projets distincts pour la phase de construction 
  • le premier, XML2CAS de uima-connectors, permet de parcourir des documents XML et de transformer les éléments et attributs XML en annotations des zones de texte couvertes.
  • le second, annotationMapper de uima-type-mapper, permet de sélectionner et transformer les annotations produites par le uima-connectors en annotations que prendra en entrée le dernier composant d'entraînement (ce projet est aussi appelé uima-annotation-mapper)
  • le troisième, hmmModelTrainer de uima-tagger de la sandbox Apache UIMA, permet d'entraîner une modélisation.
Et j'ai utilisé deux AE distincts pour la phase d'étiquetage
  • le premier, whitespaceTokenizer de whitespace tokenizer, permet de découper un texte en phrases et mots.
  • le second, tagger de uima-tagger de la sandbox Apache UIMA, permet d'étiqueter un texte à l'aide d'une modélisation.
A propos de l'Apache UIMA Sandbox HMM Tagger
  • J'ai utilisé la version présente dans UIMA Annotator Addons 2.3.1 qui corrige une anomalie https://issues.apache.org/jira/browse/UIMA-2106 (rapport d'anomalie + correctif). Cette version qui applique le correctif n'est pas encore distribuée à l'heure de l'écriture de ce message, mais se trouve présente dans le répertoire subversion du Tagger dès la version 1088626. Une version compilée avec les sources est fournie.
  • La partie apprentissage portée par l'AE hmmModelTrainer permet de spécifier le trait de l'annotation (featurePath) à prendre en compte pour l'entraînement. Néanmoins celui-ci ne permet pas de spécifier des contraintes notamment sur la valeur d'autres traits présents dans l'annotation dont on a défini le featurePath. Or ici nous en avons besoin pour sélectionner seulement certaines des annotations produites par le XML2CAS. 
  • La partie étiquetage portée par l'AE tagger ne permet pas à l'heure actuelle de spécifier le trait à annoter. En l'état, quelle que soit la modélisation, le Tagger affecte la valeur prédite au featurePath suivant org.apache.uima.TokenAnnotation:posTag. Ce n'est pas très embêtant si l'on sait ce que l'on fait. Le patch https://issues.apache.org/jira/browse/UIMA-2110  soumis à Apache vise à rendre plus générique le Tagger et résoudre notamment cette limite. Entre autre il permet de spécifier sous forme d'un featurePath le trait de l'annotation auquel il faut donner la valeur prédite par la modélisation. Ce patch n'a pas encore accepté par Apache.
Paramétrage des composants utilisés (parameter settings)

L'AE XML2CASAE 
  • produit des annotations XMLElementAnnotation et XMLAttributeAnnotation pour chaque élément XML rencontré. 
    • Les traits de XMLElementAnnotation permettent de connaître le nom de l'élément XML et les noms et les valeurs de ses attributs. Deux structures de données sont disponibles pour cela, l'une d'elle est un tableau de référence vers des annotations XMLAttributeAnnotation correspondant à chacun de leurs attributs. Les XMLAttributeAnnotation correspondent à une annotation pour chaque attribut. Elles déclarent le nom de l'élément XML qu'elles caractérisent ainsi que le nom et la valeur de l'attribut.
  •  prend les paramètres suivants 
    • nom de la vue à créer avec seulement le contenu textuel du document XML (PlainTextOutputView). La vue où se trouve le document textuel peut aussi être spécifiée si elle est différente de celle par défaut. Ici PlainTextDocument
    • nom des éléments XML à transformer en annotations dans le CAS  (XmlTagsToTurnIntoAnnotation). Ici w, qui correspond à la balise du French Treebank pour mot.
L'AE annotationMapper 
  • prend en paramètre un fichier de règles qui assurent la transformation d'une annotation en une autre (RulesFile). 
  • Globalement ici la règle permet d'une part de sélectionner les annotations XMLAttributeAnnotation qui correspondent à des attributs de mots (balises w) et qui ont un attribut non nul dont on désire utiliser la valeur comme base d'apprentissage (cat, subcat, lemma, mph, ee, ...), et d'autre part de créer pour chacune de ces annotations une annotation manipulable par le composant d'apprentissage avec un trait initialisé à la valeur à apprendre.
  • Ici la règle crée des org.apache.uima.TokenAnnotation et instancie la valeur du trait posTag. On pourrait pour chaque caractéristique apprise changer le trait à considérer. Ce n'est pas très pertinent pour notre construction. Un même trait peut acceillir tour à tour les valeurs du corpus pour chaque entraînement.
  • A noter qu'il est possible de spécifier avec le hmmModelTrainer le featurePath à considérer comme base d'entraînement. Néanmoins celui-ci ne permet pas de spécifier des contraintes notamment sur la valeur d'autres traits présents dans l'annotation dont on a défini le featurePath. Or ici nous en avons besoin pour sélectionner un sous ensemble des XMLAttributeAnnotation parmi celles produites par le XML2CAS. C'est cette situation qui justifie notamment l'utilisation de l'annotationMapper.
L'AE  hmmModelTrainer 
  • prend en paramètre 
    • le  featurePath qui pointe le trait de l'annotation dont il faut considérer la valeur pour l'apprentissage (FeaturePathPOS)
    • le nom et chemin du fichier de modélisation à créer (ModelExportFile)
L'AE  tagger 
  • prend en paramètre 
    • le  featurePath qui pointe le trait de l'annotation qu'il faut instancier avec la valeur prédite par la modélisation (TokenFeaturePath)
    • le nom et chemin du fichier de modélisation à utiliser (ModelFile)
  • Le descripteur qui met en oeuvre cet AE l'utilise plusieurs fois consécutivement avec des jeux de valeurs différentes pour appliquer les différents modélisations. 
  • Nous avons étendu le type org.apache.uima.TokenAnnotation à cet effet. Consulter le fichier desc/FrenchTreebankTS.xml
Contenu du projet Eclipse (Eclipse project description)
Le projet Eclipse est globalement un projet java ayant la UIMA Nature et ses sous répertoires desc et resources  déclarés comme appartenant au build path. Le post suivant décrit comment créer un projet Eclipse en rajoutant dans le build path les dépendances minimales UIMA requises pour exécuter une chaîne de traitement UIMA.
  • Le répertoire lib contient toutes les dépendances requises, celles de uima, celles des AE de notre chaîne (uima-common, uima-connectors, jxpath et uima-type-mapper) avec notamment le whitespace tokenizer(uima-an-wst) de la UIMA Sandbox et la version patchée du HMMTagger (uima-an-tagger). Ces dépendances ont été déclarées dans le .classpath. Elles seront accessibles lors de l'import du projet.
  • Le répertoire data/input contient un sous répertoire FrenchTreebank avec un fichier exemple qui reprend la structure XML d'un fichier du corpus original. Ce fichier a en fait été construit a posteriori à partir d'analyses produites. Il peut dans le cas présent servir de base d'exemple de la chaîne d'entraînement. Le répertoire contient aussi un sous répertoire text-fr avec des textes en français qui peuvent servir pour tester l'étiquetage à partir des modélisations construites.
  • Le répertoire data/output accueille les résultats d'exécution des chaînes de traitement, aussi bien les xmi (xmi) que les modélisations (models).
  • Le répertoire resources/models contient les modélisations construites sur le corpus FrenchTreebank (les jeux d'étiquettes (ou tagset) sont décrits ici)
    • FrenchTreebankPosUimaHmmTaggerModel.dat: part of speech (pos) tagging ('cat' attribute in the French Treebank)
    • FrenchTreebankMphUimaHmmTaggerModel.dat: inflection analysis
    • FrenchTreebankSubcatUimaHmmTaggerModel.dat: subcategorization analysis
    • FrenchTreebankEeUimaHmmTaggerModel.dat: combination of part of speech (pos) tagging, subcategorization analysis and inflection analysis
    • FrenchTreebankLemmaUimaHmmTaggerModel.dat: lemmatization 
  • Le répertoire resources contient aussi un fichier de règle pour la transformation d'annotations en autres. Cela permet de faire le pont entre le composant d'importation et le HMMTrainer. Son nom ici est XML2CAS-to-HMMTagger_annotationMapperRules.xml
  • Le répertoire desc contient 
    • le descripteur de la chaîne utile pour la construction des modélisations à savoir XML2CAS-annotationMapper-HMMTrainModel-FrenchTreeBank-AAE.xml 
    • un descripteurs de chaîne pour l'étiquetage à partir d'une modélisation à savoir wst-FrenchTreebank-HmmTagger-AAE.xml. Celle disponible par défaut dans l'addon convient très bien mais requiert quelques manipulations pour spécifier le bon fichier de modélisation et étiquetera toujours le featurePath org.apache.uima.TokenAnnotation:posTag
    • un descripteur qui sert pour déclarer l'importation d'un Type System étendant le type TokenAnnotation avec de nouveaux attributs susceptibles d'accueillir le résultat d'étiquetage de différentes modélisations (morphologie, lemmatisation, ...) à savoir FrenchTreebankAE.xml qui importe FrenchTreebankTS.xml.
Réaliser la construction d'une modélisation (running the training process)
Le post suivant explique comment exécuter une chaîne de traitement UIMA.

Pour réaliser la construction d'une modélisation, utiliser le descripteur desc/XML2CAS-annotationMapper-HMMTrainModel-FrenchTreeBank-AAE.xml qui constitue un aggregate d'AE présentés ci-dessus en pipeline.
  1. Placer votre corpus sous la forme d'un seul fichier dans le répertoire data/input/FrenchTreebank. Un fichier exemple est fourni. Une manipulation est décrite ci-dessous pour produire un seul fichier à partir des différents fichiers du French Treebank.
  2. Spécifier dans le fichier de règles de l'annotationMapper le nom de l'attribut XML du corpus French Treebank sur lequel vous souhaitez réaliser l'apprentissage (cat, subcat, lemma, mph, ee, ...)
  3. Spécifier le nom du fichier de modélisation à produire dans le paramètre du descripteur 
  4. Lancer le documentAnalyzer (par exemple) pour exécuter la chaîne en spécifiant surtout l'input
Réaliser l'étiquetage à partir d'une modélisation (running the tagging process)
Pour cela utiliser le descripteur desc/wst-FrenchTreebank-HmmTagger-AAE.xml
  1. Les données traitées doivent être des fichiers textes
  2. Spécifier le fichier de modélisation dans le paramètre du descripteur
  3. Spécifier le featurePath de l'annotation à créer avec la valeur prédite par la modélisation
  4. Lancer le documentAnalyzer (par exemple) pour exécuter la chaîne 
Limites actuelles des modélisations
  • Le corpus compte des mots simples et des mots composés, tout deux balisés avec le même élément. Les mots composés imbriquent des mots simples. Ceux-ci n'ont pas avec les mêmes traits que leurs homologues non imbriqués. La construction de ces modélisations a considéré les mots composés mais non les mots simples imbriqués. 
Les mots composés sont de la forme suivante
<w cat="D" ee="D-def-fp" ei="Dfp" lemma="le" mph="fp" subcat="def"></w> <w cat="N" ee="N-C-fp" ei="NCfp" lemma="banque centrale" mph="fp" subcat="C"> <w catint="N">banques</w> <w catint="A">centrales</w> </w>
ou bien 
<w cat="ADV" ee="ADV" ei="ADV" lemma="à tout prix"> <w catint="P">à</w> <w catint="D">tout</w> <w catint="N">prix</w> </w>
La règle de sélection utilisée par l'annotation mapper met des contraintes sur la présence de certains traits. Pour rappel, la contrainte est 
<Contrainte>.[@elementName='w' and @attributeName='cat' and @attributeValue!=""]</Contrainte>
En conséquence, les mots simples composants les mots composés ne sont pas considérés.
    A l'inverse, si l'on ne souhaite pas considérer les mots composés mais les mots simples on peut écrire une contrainte comme celle-ci (on filtre sur la présence d'un caractère blanc)
    <Contrainte>.[@elementName='w' and not(contains(@coveredText,' '))]</Contrainte>
      Mais là effectivement nous n'avons plus accès aux traits du mots composés. Cela ne pose pas de problème pour de l'entrainement de découpeur en mots ou en phrases.
        L'apprentissage d'un étiqueteur morphosyntaxique sur des mots composés ne semble pas non plus poser problème.
          Si vraiment on voulait ne pas considérer les mots simples, on pourrait imaginer une contrainte avec alternative qui exclurait les mots composés et considèrerait les traits des mots simples mais cela ne concernerait pas tous les traits (ici seulement les étiquettes grammaticales : cat et catint).
          • Le processus de lemmatisation via l'algorithme utilisé présente des abérations lorsqu'il ne connaît pas le mot à lemmatiser. En effet, il va lui affecter le lemme d'un mot qu'il connaît et avec lequel il partage le plus grand suffixe commun.
          • ...
          Un seul "gros" fichier d'entraînement
          Le corpus French Treebank est un ensemble de fichiers XML. Utilisant un AE et non un Collection Reader (CR) de uima-connector, il me fallait un seul fichier XML contenant toute la collection. Une petite ligne de shell me résoud le problème
          echo '<?xml version="1.0" encoding="UTF-8"?>' > frenchTreebank.xml ; echo "<ftb>" >> frenchTreebank.xml ; for f in `ls lmf*.xml`; do echo $f; export HEAD=`head -1 $f`; if [ "$HEAD" == '<?xml version="1.0" encoding="UTF-8"?><text>' ]; then echo "<text>" >> frenchTreebank.xml; fi;  cat $f |grep -v "xml version" >> frenchTreebank.xml; done ; echo "</ftb>" >> frenchTreebank.xml 
          Environnement d'exécution
          J'ai réalisé mes traitements sur un système linux via Eclipse.
          J'ai eu à augmenter les capacités de la machine virtuelle Java lors de l'exécution de la chaîne de traitement. Via Eclipse, j'ai rajouté la valeur -Xmx2048m dans le menu Run Configurations > Arguments > VM Arguments.
          La machine que j'utilisais offre 16 Go de RAM et un processeur Intel Xeon W3565 @ 3.20GHz.

          Implémentation de l'algorithme Viterbi du HMM Tagger 
          L'implémentation actuelle (src/main/java/org/apache/uima/examples/tagger/Viterbi.java) fonctionne grosso modo de la sorte (il y a ici d'autres détails techniques cachés) :
          Pour chaque mot phrase d'une phrase on cherche à lui attribuer une probabilité d'étiquette pour cela : 
          on regarde si on connaît la probabilité du mot ou de sa forme en minuscule sinon on cherche le suffixe le plus long en commun avec un mot connu sinon on lui attribue la probabilité d'un mot prédéfini.
          Retour au sommaire
          Page suivante »
          Produit par le BilboPlanet CSS - Xhtml valide Produit par le BilboPlanet Retour au début