Wikipedia est une incroyable source d’information, de données et plus généralement d’actes langagiers (utilisation du langage). Ce dernier point est très important pour nous autres chercheurs en traitement automatique des langues. En effet, nous avons besoin d’observer de très nombreux exemples d’utilisation du langage, que ce soit de manière automatique ou manuelle. Pour ce faire, nous compilons de vastes exemples d’utilisation du langage sous forme de corpus.
La création de composants UIMA permettant d’accéder et tirer parti de Wikipédia offrirait de nouvelles perspectives au traitement des langues en offrant un accès aisé à cette formidable ressource que représente l’encyclopédie libre. Je compte m’atteler à la création de tels composants et vais tâcher de publier plusieurs billets décrivant ma démarche en cours.
Voici le quatrième billet qui discute de l’analyse syntaxique du contenu des pages wiki pour la suppression des balises Wiki.
Il existe plusieurs initiatives de programmes permettant d’interpréter la syntaxe de MediaWiki. Il y a le code PHP utilisé par MediaWiki et qui sert de référence, mais également plusieurs autres initiatives notamment en Java.
La plupart des initiatives Java ont pour objectif de produire des version XHTML ou PDF des pages en syntaxe wiki et ne donnent pas accès à l’arbre syntaxique de la page. Un programme en C basé sur Flex/Bison et développé dans le cadre du projet MediaWiki offre cette possibilité.
Parmi les initiatives en Java qui semblent intéressantes pour le collection reader UIMA :
J’ai tout d’abord essayé Bliki pour traiter la syntaxe de MediaWiki. Ce dernier s’est toutefois avéré assez complexe, notamment au niveau de son architecture, de telle façon que je n’ai pas réussi à obtenir ce que je voulais. Je me suis alors tourné vers WikiModel qui offre un analyseur syntaxique (Wiki Event Model (WEM)) au fonctionnement proche de SAX.
Le gros avantage de WikModel est qu’il offre directement un parseur pour MediaWiki : org.wikimodel.wem.mediawiki.MediaWikiParser. Le parseur analyse du code Wiki brute et, comme un parseur SAX classique, lance des évènements à une instance implémentant l’interface org.wikimodel.wem.IWemListener. J’ai choisi de créer une classe MediawikiCasConverter implémentant cette interface et gérant elle même le lancement du parseur sur les textes brutes extraits des révisions.
import org.wikimodel.wem.mediawiki.MediaWikiParser import org.wikimodel.wem.IWemListener; ... public class MediawikiCasConverter implements IWemListener { public MediawikiCasConverter() { theParser = new MediaWikiParser(); } public void runParser(String rawWikiText) throws WikiParserException { // We use a string reader to parse the raw wiki texte StringReader reader = new StringReader(rawWikiText); // Parsing theParser.parse(reader, this); } // IWemListener methods below ... }
Les méthodes du IWemListener sont de deux types. Les premières sont des méthodes one shot, ie elles sont appelées une seule fois lorsqu’un élément est rencontré. Ce sont notamment celles qui concernent le texte et leur mise en forme :
Les secondes sont des méthodes à la SAX en deux temps : begin et end. Ce sont notamment celles qui concernent la structuration du texte :
La plupart de ces méthodes ne font que des appels à une fonction de plus haut niveau qui collecte les données textuelles de la page et qui maintient l’index du CAS : addToContent. C’est notamment le cas pour la méthode onWord qui est appelée lorsque le parseur rencontre un nouveau mot :
/** * This method adds the string in parameter into the collected content * and then increment the offset by the size of this string. * * @param str the string to be added to the content */ protected void addToContent(String str) { if ( str != null ) { theTextContent.append(str); theOffset += str.length(); } } ... /** Called when a word is encountered */ public void onWord(String str) { addToContent(str); }
J’ai choisi de transposer une partie des informations contenues dans les pages de Wikipedia sous la forme d’annotations. J’ai ainsi ajouté les types :
En ce qui concerne les liens, j’ai choisi pour le moment d’ignorer les images qui me semblent apporter plus de bruit qu’autre chose.
La mise en place de ces annotations se fait assez facilement :
Cette approche permet de dédier toute la partie analyse à une classe. Si jamais l’on veut ignorer certaines annotations (ou toutes), il suffit de les filtrer au moment de leur récupérer par getAnnotations. L’extrait de code ci-dessous illustre ce fonctionnement pour les titres :
/** * When we encounter a new header, we create an annotation for it. */ public void beginHeader(int headerLevel, WikiParameters params) { // Jump a line addToContent("\n\n"); // Create the annotation Header header = new Header(theCas); header.setLevel(headerLevel); header.setBegin( theOffset ); // Add it to the list theHeadersAnnotations.add( header ); // Add it as header of the last unclosed section if ( theUnclosedSections.size() > 0 ) { Section section = theUnclosedSections.get( theUnclosedSections.size()-1 ); section.setTitle(header); } } /** * Add the ending value of the last started header. */ public void endHeader(int headerLevel, WikiParameters params) { // Retrieve the last header Header header = theHeadersAnnotations.get( theHeadersAnnotations.size()-1 ); // Update its ending value header.setEnd( theOffset ); // Jump a line addToContent("\n\n"); }
Je me suis rendu compte que le parseur posait des problèmes (heap overflow) sur des pages de certains espaces de noms. J’ai donc choisi de n’appliquer l’analyse syntaxique qu’aux pages de l’espace de nom 0 (espace de nom principal). Ceci est toutefois facilement modifiable dans la méthode uima.wikipedia.mwdumper.ThreadedMWDumper.writeStartPage.
Il reste pas mal d’améliorations possibles : prendre en compte toutes les mises en formes/structurations, prendre en charge les macros, rendre l’analyse syntaxique configurable. Toutefois, le composant est dans l’état actuel aussi complet et performant que je le souhaitais à l’origine. Il ne reste plus qu’à le tester de manière intensive. J’ai d’ailleurs deux trois cobayes en tête pour ça.
Pas de nouvelle version du composant pour l’instant. Étant donné que les dépendances se sont multipliées, et que le composant est désormais suffisamment fonctionnel pour se voir estampillé 1.0 ou quelque chose du genre, je vais travailler un peu plus son packaging.
Wikipedia est une incroyable source d’information, de données et plus généralement d’actes langagiers (utilisation du langage). Ce dernier point est très important pour nous autres chercheurs en traitement automatique des langues. En effet, nous avons besoin d’observer de très nombreux exemples d’utilisation du langage, que ce soit de manière automatique ou manuelle. Pour ce faire, nous compilons de vastes exemples d’utilisation du langage sous forme de corpus.
La création de composants UIMA permettant d’accéder et tirer parti de Wikipédia offrirait de nouvelles perspectives au traitement des langues en offrant un accès aisé à cette formidable ressource que représente l’encyclopédie libre. Je compte m’atteler à la création de tels composants et vais tâcher de publier plusieurs billets décrivant ma démarche en cours.
Voici le troisième billet qui discute du filtrage des données à charger : articles, révision, ...
L’outil mwdumper inclut un certain nombre de filtres commandables à partir de la ligne de commande :
Voir le billet Insérer plusieurs copies locales de Wikipedia dans une base PostgreSQL pour un cas d’utilisation de ces filtres. La combinaison de ces filtres permet de contrôler avec précision les données à charger, nous allons donc nous en contenter.
On peut classer les filtres en deux familles : ceux qui nécessitent des paramètres et ceux qui n’en nécessitent pas. J’ai donc choisi pour le moment d’utiliser des paramètres booléens pour les filtres sans paramètre, et des paramètres chaînes de caractères pour les autres.
L’activation des paramètres booléens suffit à activer les filtres correspondant : IgnoreTalks et LatestRevisionOnly.
Pour activer les autres filtres il suffit de les configurer en renseignant les champs dédiés. Si les champs restent non renseignés alors les filtres ne sont pas activés : ConfigNamespacesFilter, ConfigTitleMatch, ConfigListFilter, ConfigExactListFilter, ConfigRevisionListFilter, ConfigBeforeTimestampFilter, ConfigAfterTimestampFilter.
Voici une nouvelle version du collection reader pour Wikipédia, estampillée 0.2, qui ne prend toujours pas en compte la syntaxe, mais qui permet de filtrer le type de contenu à charger à partir du dump XML. Pour utiliser le composant, il est nécessaire d’avoir le jar de mwdumper dans le classpath.
Wikipedia est une incroyable source d’information, de données et plus généralement d’actes langagiers (utilisation du langage). Ce dernier point est très important pour nous autres chercheurs en traitement automatique des langues. En effet, nous avons besoin d’observer de très nombreux exemples d’utilisation du langage, que ce soit de manière automatique ou manuelle. Pour ce faire, nous compilons de vastes exemples d’utilisation du langage sous forme de corpus.
La création de composants UIMA permettant d’accéder et tirer parti de Wikipédia offrirait de nouvelles perspectives au traitement des langues en offrant un accès aisé à cette formidable ressource que représente l’encyclopédie libre. Je compte m’atteler à la création de tels composants et vais tâcher de publier plusieurs billets décrivant ma démarche en cours.
Voici le second billet qui discute du chargement d’un dump XML de l’encyclopédie libre et la distribution du contenu sous forme de CAS.
La base de données contenant toutes les pages de Wikipédia et autre sites de la fondation WikiMedia est automatiquement archivée très régulièrement. Les dumps de la base sont disponibles sur http://download.wikimedia.org/. Vous trouverez ainsi :
Il est également possible de télécharger l’intégralité de l’encyclopédie dépourvue des balises wiki : http://download.wikimedia.org/nowiki/.
Le billet Créer un miroir Wikipédia explique comment construire une copie locale de l’encyclopédie à partir de ces différents dumps. Nous nous donnons comme objectif d’exploiter ces dumps directement sans passer par une importation en base de données.
MWDumper est un outil écrit en Java par les développeurs de MediaWiki qui permet d’importer/exporter une base MediaWiki à partir de/vers une base de données. Cet outil a l’avantage de travailler directement sur la version compressée du dump XML, réduisant ainsi le besoin en espace disque disponible. Cet outil s’utilise en ligne de commande et offre un certain nombre de paramètres permettant notamment de filtrer les articles à exporter ou bien le format SQL à prendre en charge.
Une version compilée de l’outil est disponible sur download.wikipedia.org, mais il y a de forte chance qu’elle soit dépassée. Le plus sûr est de compiler l’outil depuis les sources.
Une partie du code de cet outil va nous faciliter la tâche pour la réalisation d’un collection reader pour UIMA, notamment les classes :
Il nous faut toutefois noter que XmlDumpReader repose sur un analyseur SAX, ce qui n’est pas directement utilisable dans un Collection Reader d’UIMA. En effet, dans UIMA c’est le Collection Reader qui contrôle l’exécution, alors qu’à l’opposé l’analyseur SAX contrôle sa propre exécution. La seule interaction avec le parseur est l’emploi des hooks.
L’idée est de faire tourner l’analyseur SAX dans un thread différent de celui du collection reader. Ensuite à l’aide d’un mécanisme de sémaphore (ou de monitor en Java), on contrôle l’exécution du parseur pour produire les CAS. Cette méthode permet d’utiliser presque directement l’outil de MediaWiki, ainsi si la syntaxe évolue, l’outil de MediaWiki évoluera également certainement et ce sera donc normalement transparent pour le collection reader.
Voici une première version du collection reader pour Wikipédia qui ne prend pas du tout en compte la syntaxe, mais charge seulement le contenu des pages dans un CAS. Il y a encore beaucoup de travail pour obtenir quelque chose d’intéressant et d’utilisable, d’où l’estampillage 0.1. Pour utiliser le composant, il est nécessaire d’avoir le jar de mwdumper dans le classpath.
Il y a tout un tas d’améliorations que j’aimerais apporter (filtrage, analyse de la syntaxe wiki, ...) mais l’idée est dans un premier temps de fournir une version brute de fonderie qui fonctionne pour qu’elle puisse être testée. Le jar contient notamment le code Java, donc il ne faut pas hésiter à y jeter un oeil.
Le descripteur est dans le Jar distribué, il ne prend en paramètre que le chemin vers le dump XML à charger. Peu importe l’extension (.gz, .bz, ...), il devrait pouvoir s’en sortir. Si ce n’est pas le cas, laissez un commentaire ! Je n’ai fait que peu de tests pour le moment, je ne sais pas trop comment le composant tient la route sur des gros dumps, mais la première remarque est qu’il est nécessaire d’augmenter la taille du tas de la jvm (-Xmx=512m pour commencer). En effet, certaines pages sont assez imposantes de par leur grand nombre de révisions, et pour l’instant toutes les révisions sont chargées dans chaque CAS.
Wikipedia est une incroyable source d’information, de données et plus généralement d’actes langagiers (utilisation du langage). Ce dernier point est très important pour nous autres chercheurs en traitement automatique des langues. En effet, nous avons besoin d’observer de très nombreux exemples d’utilisation du langage, que ce soit de manière automatique ou manuelle. Pour ce faire, nous compilons de vastes exemples d’utilisation du langage sous forme de corpus.
La création de composants UIMA permettant d’accéder et tirer parti de Wikipédia offrirait de nouvelles perspectives au traitement des langues en offrant un accès aisé à cette formidable ressource que représente l’encyclopédie libre. Je compte m’atteler à la création de tels composants et vais tâcher de publier plusieurs billets décrivant ma démarche en cours.
Voici le premier billet qui discute de la création d’un Type System approprié pour représenter les pages de Wikipedia.
L’encyclopédie Wikipédia contient tout un tas d’informations mais je me concentrerai particulièrement sur le contenu même : les articles. Je propose de classer les informations concernant ces derniers en quatre grandes catégories :
On peut considérer la Wikipédia comme un ensemble de pages liées selon trois grandes relations :
Les catégories structurent l’encyclopédie à la manière d’une ontologie ou d’un thésaurus. Une page peut appartenir à une ou plusieurs catégories. De plus, les catégories sont hiérarchisées, l’appartenance à une catégorie implique l’appartenance à toutes les catégories parentes.
Les liens interlangues marquent les pages en relation de traduction avec la page courante.
Les liens internes permettent au sein d’une page de faire référence à d’autres pages de l’encyclopédie, il s’agit d’une structuration qui n’a qu’un impact local. La signification de ces références est très variable et difficilement identifiable.
Le principe de l’encyclopédie collaborative est que tout le monde peut modifier un article, ce qui amène à de multiples révisions de ces derniers. Lorsque vous cliquez sur l’onglet Historique d’un article, vous pouvez visionner ces différentes révisions, leur auteur, et éventuellement un commentaire précisant l’apport de la révision.
Le contenu d’un article se structure à l’aide de titres, de paragraphes ainsi que de divers éléments de mise en forme : [tableaux], [formules], [listes], ...
Parmi les éléments de mise en forme, on peut également noter l’existence de plusieurs macros pour les [modèles], [dates], [liens interwiki], ...
Je ne crois personnellement pas à l’existence de Type Systems génériques pouvant être partagés par tous les composants. De mon point de vue, un Type System va être approprié au traitement d’une tâche particulière, chercher à en produire un suffisamment générique pour toutes les tâches seraient une perte de temps et mènerait certainement à un TS difficile à utiliser.
Personnellement, je vais utiliser Wikipedia dans le cadre de ma thèse sur la détection de dérivation de texte. Ce qui m’intéresse c’est donc de générer un CAS par article dans lequel on retrouvera les différentes révisions dudit article. Je m’intéresse donc :
Après réflexion j’en suis donc arrivé à cette modélisation :
Cette modélisation ne correspond pas exactement aux besoins d’un TS UIMA. Ainsi, les relations de composition se manifestent par un phénomène de couverture des annotations, entraînant la disparition des attributs correspondant.
Vous trouverez le descripteurs correspondant à ce TS ici.
L’annonce est parue hier sur les listes concernées, la nouvelle version du framework Apache UIMA (Unstructured Information Management Architecture) est sortie, estampillée 2.3 !
Au programme de cette nouvelle version, beaucoup de corrections de bug come d’habitude, mais surtout l’intégration de UIMA-AS qui permet une meilleure distribution des calculs entre plusieurs serveurs et l’ajout de plusieurs addons :
Cette nouvelle mouture d’UIMA tire désormais parti des génériques de Java 5, ce qui va permettre de limiter les casting aventureux pour récupérer les annotations, itérateurs, ... :) Cela signifie toutefois que la version 5 de Java est désormais le requis minimum pour utiliser UIMA.
Vous trouverez plus de détails sur cette version dans les notes de release :
Ce billet explique comment sélectionner des annotations en appliquant des filtres sur les indexes des CASes. On prend l'exemple d'un Analysis Engine qui filtre le mot « saperlipopette » dans des textes.
La plate-forme UIMA offre des facilités pour filtrer les annotations parmi l'index d'un CAS. J'explique rapidement comment faire fonctionner ce mécanisme de filtrage en trois temps :
La partie générique du code ci-dessous correspond à la façon de créer des contraintes qui permettront d'appliquer des filtres sur les annotations. Il s'agit de créer des contraintes sur la valeur de certains traits d'annotations. Une contrainte peut-être vue avec UIMA comme la donnée d'un chemin de traits (feature path) et d'une contrainte de base (contrainte numérique entière, contrainte numérique flottante, contrainte textuelle, etc). Le mécanisme interne à UIMA consiste vérifier si la valeur du chemin de traits de chaque annotation sélectionnée satisfait effectivement la relation définie par la contrainte de base. C'est ce que fait la méthode getConstraint dans la classe ci-dessous.
La méthode getBasicConstraint renvoie la contrainte de base en fonction de la valeur et plus précisément de son type. Cette méthode s'appuie alors sur deux méthodes getIntContraint et getStringConstraint qui renvoie la contrainte de base en fonction de la relation voulue entre le chemin de traits et la valeur précédente, à savoir une relation d'égalité ou d'inégalité.
En outre, l'Analysis Engine ci-dessous définit une méthode getType qui renvoie le type à partir de son nom et une méthode getFeaturePath qui renvoie le chemin de traits correspondant au type d'annotation et au nom de ce trait.
L'Analysis Engine Filtering suivant réalise les opérations décrites ci-dessus et les met en œuvre afin d'afficher les mots « saperlipopette ». On suppose ainsi que le Type System de cet Analysis Engine contient un type d'annotation fr.free.rocheteau.jerome.Mot qui possède un trait value correspondant au texte couvert par ces mots.
public class Filtering extends JCasAnnotator_ImplBase {
private Type getType (JCas cas,String name) {
Type type = cas.getTypeSystem().getType(name);
if (type == null) {
type = cas.getTypeSystem().getType(name);
if (type == null) {
type = cas.getTypeSystem().getType(name);
if (type == null) {
String msg = "Unknown type " + name);
System.out.println(msg);
}
}
}
return type;
}
private FeaturePath getFeaturePath(JCas cas,Type type,String feat) {
Feature feature = type.getFeatureByBaseName(feat);
FeaturePath path = cas.createFeaturePath();
path.addFeature(feature);
return path;
}
private int EQ = 0;
private int LT = 1;
private int LEQ = 2;
private int GEQ = 3;
private int GT = 4;
private FSConstraint getStringConstraint(ConstraintFactory factory,int type,String string) {
FSStringConstraint constraint = factory.createStringConstraint();
if (type == this.EQ) {
constraint.equals(string);
} else {
return null;
}
return constraint;
}
private FSConstraint getIntConstraint(ConstraintFactory factory,int type,Integer integer) {
FSIntConstraint constraint = factory.createIntConstraint();
if (type == this.EQ) {
constraint.eq(integer);
} else if (type == this.LT) {
constraint.lt(integer);
} else if (type == this.LEQ) {
constraint.leq(integer);
} else if (type == this.GT) {
constraint.gt(integer);
} else if (type == this.GEQ) {
constraint.geq(integer);
} else {
return null;
}
return constraint;
}
private FSConstraint getBasicConstraint(ConstraintFactory factory,int type,Object value) {
if (value instanceof java.lang.String) {
return this.getStringConstraint(factory, type,(String) value);
} else if (value instanceof java.lang.Integer) {
return this.getIntConstraint(factory,type,(Integer) value);
} else {
return null;
}
}
private FSMatchConstraint getConstraint(JCas cas,Type type,String feat,int typ,Object value) {
ConstraintFactory factory = cas.getConstraintFactory();
FSConstraint constraint = this.getBasicConstraint(factory,typ,value);
FeaturePath path = this.getFeaturePath(cas,type,feat);
FSMatchConstraint filter = factory.embedConstraint(path,constraint);
return filter;
}
public void initialize(UimaContext context) throws ResourceInitializationException {
}
public void process(JCas cas) throws AnalysisEngineProcessException {
Type type = this.getType(cas,"fr.free.rocheteau.jerome.Mot");
FSMatchConstraint filter = this.getConstraint(cas,type,"value",this.EQ,"saperlipopette");
FSIterator iter = cas.getAnnotationIndex(type).iterator();
FSIterator res = cas.createFilteredIterator(iter,filter);
while (res.hasNext()) {
Annotation a = res.next();
System.out.println(a.toString(2));
}
}
}
Ce billet explique comment récupérer les descripteurs XML de Type Systems, Collection Readers, d'Analysis Engines, de CAS Consumers ou d'autres descripteurs XML pour des composants UIMA à partir de répertoires ou d'archives JAR contenus dans le CLASSPATH d'une application Java.
Les outils pour UIMA requiert pour l'instant que les descripteurs XML des composants se trouvent sur le système de fichiers local. Dans la terminologie UIMA, cela s'appelle « importer un composant par son emplacement » (import by location). Or, la plate-forme UIMA permet également de récupérer des composants par leur nom s'ils sont contenus dans le CLASSPATH (import by name). Malheureusement les outils UIMA ne fournissent pas (encore) cette fonctionnalité. Je propose une solution qui s'effectue en deux temps.
Tout d'abord, il s'agit récupérer la liste des éléments contenus dans le CLASSPATH, à parcourir ces éléments et à sélectionner les fichiers XML qu'ils contiennent. Seuls deux types d'élements sont traités : les archives JAR et les répertoires sur le système de fichiers local. C'est la fonction de la méthode setFiles dans l'exemple ci-dessous. Cette méthode fait appel aux deux méthodes addFile pour construire la liste des fichiers XML contenus dans les éléments du CLASSPATH.
Ensuite, il s'agit de parcourir cette liste de fichiers XML et de construire un descripteur XML d'un Analysis Engine à partir des méthodes fournies par la plate-forme UIMA. Si on réussit à obtenir un tel descripteur, on l'ajoute à la liste des Analysis Engines disponibles dans le CLASSPATH. Sinon on tente de construire un descripteur XML d'un Collection Reader qui est lui-même ajouté à la liste des Collection readers disponibles dans le CLASSPATH. C'est le rôle de la méthode setComponents dans l'exemple ci-dessous qui fait appel aux méthodes addAnalysisEngine et addCollectionReader. On a commencé par les Analysis Engines étant donné qu'il représente la majorité des descripteurs XML pour UIMA. On pourrait imaginer étendre la méthode setComponents pour détecter les Type Systems, les CAS Consumers et les autres types de composants UIMA.
public class ComponentDetection {
private List collectionReaders;
private void setCollectionReaders() {
this.collectionReaders = new ArrayList();
}
private List getCollectionReaders() {
return this.collectionReaders;
}
private void addCollectionReader(String name) throws IOException, InvalidXMLException {
XMLInputSource source = new XMLInputSource(name);
XMLParser parser = UIMAFramework.getXMLParser();
CollectionReaderDescription desc = parser.parseCollectionReaderDescription(source);
this.getCollectionReaders().add(desc);
}
private List analysisEngines;
private void setAnalysisEngines() {
this.analysisEngines = new ArrayList();
}
private List getAnalysisEngines() {
return this.analysisEngines;
}
private void addAnalysisEngine(String name) throws IOException, InvalidXMLException {
XMLInputSource source = new XMLInputSource(name);
XMLParser parser = UIMAFramework.getXMLParser();
AnalysisEngineDescription desc = parser.parseAnalysisEngineDescription(source);
this.getAnalysisEngines().add(desc);
}
private List files;
private void setFiles() {
this.files = new ArrayList();
String classPath = System.getProperty("java.class.path");
String pathSeparator = System.getProperty("path.separator");
String[] pathElements = classPath.split(pathSeparator);
for (String pathElement : pathElements) {
try {
JarFile jarFile = new JarFile(pathElement);
this.addFile(jarFile);
} catch (IOException e) {
File file = new File(pathElement);
if (file.exists()) {
if (file.isDirectory()) {
addFile(file);
}
}
} catch (SecurityException e) {
System.err.println(e.getMessage());
}
}
}
private List getFiles() {
return this.files;
}
private void addFile(JarFile jar) {
Enumeration entries = jar.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.isDirectory()) {
String name = entry.getName();
if (name.endsWith(".xml")) {
try {
ClassLoader cl = ClassLoader.getSystemClassLoader();
URL url = cl.getResource(name);
URI uri = url.toURI();
files.add(uri);
} catch (URISyntaxException e) {
System.err.println(e.getMessage());
}
}
}
}
}
private void addFile(File file) {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File f : files) {
addFile(f);
}
} else {
String name = file.getName();
if (name.endsWith(".xml")) {
try {
URI uri = new URI(file.getAbsolutePath());
files.add(uri);
} catch (URISyntaxException e) {
System.err.println(e.getMessage());
}
}
}
}
private void setComponents() {
this.setAnalysisEngines();
this.setCollectionReaders();
for (URI file : this.getFiles()) {
String name = file.toString();
try {
this.addAnalysisEngine(name);
} catch (Exception e) {
try {
this.addCollectionReader(name);
} catch (Exception ee) { }
}
}
}
public ComponentDetection() {
this.setFiles();
this.setComponents();
}
}
<:pre> Ce billet explique comment réaliser un Analysis Engine qui calcule les occurrences des termes dans des documents avec UIMA et qui les affiche à la fin du traitement de la collection.
Nous définissons tout d'abord ce qu'est une occurrence avant de réaliser le composant qui calcule l'occurrence des termes dans un document.
La classe Java qui correspond à cette définition se nomme Occurence. Elle possède :
Le code de cette classe Java est le suivant :
private class Occurence implements Comparable{
private int id;
private String term;
private int occ;
public Occurence(int id,String term){
this.id = id;
this.term = term;
this.occ = 1;
}
public boolean incr(int id,String term) {
if (this.id == id && this.term.equals(term)) {
this.occ++;
return true;
} else {
return false;
}
}
public String toString() {
String string = "The term '" + this.term + "'";
string += " has " + this.occ + " occurences";
string += " in the " + this.id + "-th document.";
return string;
}
public int compareTo(Occurence occ) {
int diff = 0;
String fst, snd;
Integer i,j;
i = this.id;
j = occ.id;
diff = i.compareTo(j);
if (diff == 0) {
fst = this.term;
snd = occ.term;
diff = fst.compareTo(snd);
if (diff == 0) {
i = this.occ;
j = occ.occ;
diff = i.compareTo(j);
return -diff;
} else { return diff; }
} else { return diff; }
}
}
L'Analysis Engine qui calcule les occurrences de termes dans un document possède deux attributs :
Elle possède une méthode add qui incrémente le nombre d'occurrences d'un terme dans un document si celui-ci existe ou qui ajoute une occurrence de ce terme dans ce document à la liste globale.
L'initialisation de l'Analysis Engine, à l'aide de la méthode initialize, consiste à définir le nombre de document traités à 0 et de créer une liste vide pour les occurrences de termes dans les documents. Cette liste sera persistante au cours de l'analyse complète de la collection !
Le cœur de l'analyse, à l'aide de la méthode process, consiste, pour chaque document, à parcourir les annotations dont le type correspond aux termes souhaités (dans l'exemple ci-dessous il s'agit de mots caractérisés par le type d'annotation Mot) et à ajouter cette occurrence avec le texte couvert par cette annotation et l'identifiant du document en cours d'analyse.
Lorsque l'ensemble des documents de la collection est traitée, cet Analysis Engine affiche alors, à l'aide de la méthode collectionProcessComplete, les occurrences des termes dans les documents (d'où l'utilité de la méthode toString de la class Occurence) à partir de la liste globale triée (d'où la nécessité de la méthode compareTo de la classe Occurence).
La classe Java de cet Analysis Engine est le suivant :
public class OccurenceAnalyser extends JCasAnnotator_ImplBase {
private int num;
private List occurences;
private void add(int num,String term) {
boolean done = false;
for (Occurence occ : this.occurences) {
done = done || occ.incr(num,term);
}
if (!done) {
Occurence occ = new Occurence(num,term);
this.occurences.add(occ);
}
}
public void initialize(UimaContext context) throws ResourceInitializationException {
this.num = 0;
this.occurences = new ArrayList();
}
public void process(JCas cas) throws AnalysisEngineProcessException {
this.num++;
Type type = cas.getTypeSystem().getType("fr.free.rocheteau.jerome.Mot");
FSIterator iter = cas.getAnnotationIndex(type).iterator();
while (iter.hasNext()) {
Annotation annotation = iter.next();
String term = annotation.getCoveredText();
this.add(num,term);
}
}
public void collectionProcessComplete() throws AnalysisEngineProcessException {
Collections.sort(this.occurences);
for (Occurence occ : this.occurences) {
System.out.println(occ);
}
}
}
Update 20100212: Validé pour Apache UIMA Version 2.2.2-incubating Binary sous linux et Version 2.3.0-incubatingINSTALLATION
2. Puis l'installer en la désarchivant dans votre le répertoire parent de votre UIMA_HOME car l'archive contient le répertoire apache-uima qui contient le répertoire addons. De cette manière addons se retrouvera dans votre apache-uima.export UIMAADDONSDISTANTDIR=uimaj-annotator-addons-2.3.0-incubatingexport UIMAADDONSARCHIVEFILE=uimaj-annotator-addons-2.3.0-incubating-bin.zipwget "http://mirror.mkhelif.fr/apache/incubator/uima/binaries/${UIMAADDONSDISTANTDIR}/${UIMAADDONSARCHIVEFILE}"
CONFIGURATIONSunzip "${UIMAADDONSARCHIVEFILE}" -d "$UIMA_HOME/.."