L'erreur de Jérôme

Je m'excuse par avance auprès de Jérôme pour construire toute mon argumentation et ce billet sur une erreur qu'il a faite :) Pour information, Jérôme travaille en tant qu'ingénieur de recherche au sein de notre équipe de recherche, et est très certainement la personne de l'équipe la plus impliquée et la plus compétente sur UIMA. Voilà pour la mise en garde, je peux désormais mettre en place l'inquisition et prononcer l'auto da fé de sa méthode !

L'erreur dont je parle est celle présentée dans ce billet. En gros, Jérôme nous explique dans ce billet que pour compter le nombre de mots dans tous les documents d'une collection, à l'aide d'un AE, on procède en en trois temps :

  1. dans la méthode initialize() on prépare la structure qui va nous permettre de collecter les données
  2. dans la méthode process() on compte les termes et on alimente la structure initialisée dans le initialize()
  3. dans la méthode collectionProcessComplete() on fait un bilan de ce que l'on a collecté dans la fameuse structure et on renvoie tout à l'utilisateur.

D'une manière générale, cela revient à dire que l'on peut définir une structure persistante dans un AE qui nous permette de stocker des informations relevant de tous les CAS. Ce raisonnement est entièrement faux, même s'il fonctionne en général.

Du CPE et des Processing pipelines

Tout d'abord il est peut-être nécessaire de rappeler ce qu'est un CPE. Il s'agit de l'ensemble des composants (collection reader, analysis engines et CAS consumers) agencés pour produire et traiter les différents CAS. On peut en quelque sorte considérer qu'un CPE est une chaîne de traitement instanciée.

Un CPE est orchestré par un CPM, ce dernier va se charger de la mise en œuvre de la chaîne : instanciation des composants, appel des méthodes de l'API, distribution des CAS, suivi des erreurs, collecte d'informations statistiques sur le déroulement du traitement.

Voilà donc pour la théorie générale, qui peut se résumer à ce schéma que j'emprunte gentiment aux gens d'Apache :

Représentation schématique d'un CPE

Au regard de ce schéma on pourrait penser que la méthode décrite par Jérôme fonctionne puisqu'on a l'impression que les CAS produits ne passent au travers que d'une seule instance des AE. Si tel était le cas, l'intérêt de UIMA en termes de déploiement serait limité puisque cela reviendrait à transmettre la sortie d'un composant à l'entrée du composant suivant dans la chaîne. Une sorte pipe entre les différentes briques logicielles.

Il faut se pencher un peu plus profondément dans la configuration du CPE pour se rendre compte que le fonctionnement est bien mieux pensé que cela. Ainsi, on trouve dans la section ''casProcessors'' des descripteurs (XML) de CPE, un attribut d'intérêt : processingUnitThreadCount.

L'attribut processingUnitThreadCount spécifie le nombre de Processing pipelines (trad: chaînes de traitement ?) répliquées. Une processing pipeline est composée de la séquence d'AE définissant le traitement à opérer. Ces séquences (et donc les AE associés) sont dupliqués autant de fois que le nombre précisé par l'attribut processingUnitThreadCount, chacune s'exécutant dans son propre thread indépendamment des autres.

Un petit schéma, encore une fois gentiment emprunté aux gens d'Apache, vaut mieux qu'un long discours :

Représentation schématique, plus précise, d'un CPE

Le CPM prélève les CAS produit par le collection reader, et stockées dans la queue d'entrée (de taille casPoolSize), et les distribue dans les différentes processing pipelines. Les CAS étant distribuées entre les différentes chaînes, un CAS donné ne passera que par une seule de ces chaînes : c'est ici que la méthode de Jérôme échoue ! En effet, chaque AE dupliqué ne traite qu'un sous ensemble des CAS produits et alimente sa propre structure interne, sans connaissance des informations stockées dans les structures des autres instances de ce même AE.

L'avènement des ressources externes

Au sein de l'équipe nous avons déjà discuté brièvement des ressources externes. Cependant le contenu des discussions se résumait plus au mois à les ressources on ne sait pas trop à quoi ça sert, par contre c'est ennuyeux car il faut éditer les descripteurs pour les préciser, donc on préfère les paramètres. Nous sommes passés à côté de l'essence même de ces dernières.

La section 1.5.4 de la documentation ''Tutorials and users guides'' décrit le fonctionnement et l'utilisation des ressources externes.

Sometimes you may want an annotator to read from an external file – for example, a long list of keys and values that you are going to build into a HashMap. You could, of course, just introduce a configuration parameter that holds the absolute path to this resource file, and build the HashMap in your annotator's initialize method. However, this is not the best solution for three reasons:

Les ressources externes ont deux avantages majeurs (à mon avis) :

  • Elles sont partagées entre toutes les instances du même AE (cf. section 1.5.4.4);
  • Elles peuvent implémenter une interface particulière et ainsi embarquer un comportement ;

Il serait donc préférable dans le cas de Jérôme :

  • soit d'empêcher le déploiement en parallèle de plusieurs instances du même AE ;
  • soit de passer par une ressource (une ArrayList par exemple)

J'ai la lâcheté de ne pas proposer de correction, mais l'objectif de ce billet était simplement d'expliquer pourquoi l'utilisation de ressources externes pouvait se justifier et pourquoi la combinaison de structures internes et d'appel à la méthode collectionProcessComplete n'était pas toujours suffisant.