Boîte à Outils n°3

Une fois en possession de nos fichiers taggés, l'étape de recherche de patrons syntaxiques peut enfin commencer : il s'agit de repérer les suites NOM ADJ et NOM-PREP-NOM dans les fichiers obtenus à la BAO2. On dispose là aussi de plusieurs méthodes :

  • Traitement des sorties Cordial (texte brut) : 3 méthodes Perl utilisant des listes.
  • Traitement des sorties treetagger2xml (XML): requêtes XPath et XSLT.

Les sorties texte brut

Les trois méthodes proposées ici se basent sur le même principe : les programmes prennent en entrée un fichier Cordial et un fichier contenant les motifs à extraire, et consistent à placer les POS dans des listes pour les comparer aux motifs recherchés.

1) Méthode de Serge Fleury

(Télécharger le programme extract-terminologie-cordial-OK.pl)

Cette première méthode consiste à retranscrire les colonnes Token/Lemme/POS des fichiers produits par Cordial en les intégrant dans des listes. On parcourt ensuite les listes de POS pour trouver ceux correspondant au motif du fichier de patrons, sachant que chaque POS est séparé par un '#'.

En prenant l'exemple des motifs NOM ADJ et NOM-PREP-NOM, on aura ainsi :

NOM ADJ : NC[A-Z]+#ADJ[A-Z]+
NOM PREP NOM : NC[A-Z]+#PREP#NC[A-Z]+

Bien que le programme puisse prendre en entrée un fichier contenant plusieurs motifs, on sépare ici les extractions pour obtenir un fichier de sortie pour chaque motif. On aura donc un fichier patron pour le motif NOM ADJ et un autre pour NOM-PREP-NOM.

Le programme de base ne prenant en entrée qu'un seul fichier à la fois (obligeant donc l'utilisateur à le relancer pour chaque sortie Cordial), j'ai donc créé un programme permettant d'automatiser cette tâche, qui reprend la même technique utilisée pour le parcours d'arborescence vue à la BAO1 :

# Création d'un dossier de sorties pour les patrons my $output="Extractions/"; # Normalisation du chemin d'entrée + récupération du fichier motifs dans une variable my $path="$ARGV[0]"; $path=~s/[\/]$//; my $patron="$ARGV[1]"; # placer les fichiers lus dans un tableau opendir(DIR, $path) or die "can't open $path: $!\n"; my @files=readdir(DIR); closedir(DIR); # pour chaque fichier du dossier d'entrée... foreach my $file (@files){ next if $file=~/^\.\.?$/; # Création d'un fichier de sortie pour chaque fichier my $sortie="$output".$file.'-'.$patron; $sortie=~s/\.cnr//; # supression de l'extension .cnr des noms de fichiers de sorties $file=$path."/".$file; # Exécution du programme extract-terminologie-cordial-OK.pl system("perl extract-terminologie-cordial-OK.pl $file $patron > $sortie"); }

(Télécharger le programme d'automatisation)

Après avoir lancé ce programme sur nos deux fichiers motifs, on obtient un dossier "Extractions" contenant la liste des motifs pour chaque fichier :

Exemple de sortie sur la rubrique INTERNATIONAL : NOM_ADJ et NOM-PREP-NOM

2) Méthode de Jean-Michel Daube

(Télécharger le programme bao3_JMD.pl)

Pour cette deuxième méthode, il s'agit de lire le fichier d'entrée jusqu'à une ponctuation forte (annotée PCTF par Cordial), puis de vérifier s'il existe une correspondance avec une des suites recherchées dans le fichier patrons. Celui-ci contient les motifs séparés par des espaces, comme suit :

NOM ADJ : NC.. ADJ..
NOM PREP NOM : NC.. PREP NC..

On obtient en résultat un seul fichier par rubrique, contenant les deux patrons recherchés (on peut aussi choisir de séparer les motifs dans des fichiers différents comme pour la méthode précédente).

Pour prendre en compte tout le dossier de sorties Cordial au lieu d'un seul fichier, j'ai donc réutilisé le programme d'automatisation créé précédemment : il suffit en effet de remplacer le nom du programme dans la commande system() pour le lancer avec ce programme-ci.

Exemple de sortie sur la rubrique IDEES : IDEES-NomAdj_NPN.txt

3) Méthode d'Axel Court

(Télécharger le programme extraction-hash-ngram-modif.pl)

Cette troisième méthode s'appuie sur des ngrams, en commençant par compter le nombre de POS que contient le fichier de motifs. Pour un fichier contenant une suite de plusieurs motifs, le programme produit en sortie un seul fichier qui contient tous les motifs précédés du nom du motif en question, ce qui permet de les séparer tout en les conservant dans un seul fichier.

Le programme d'origine produisant par défaut un fichier "resultat.txt", j'ai supprimé l'étape de création de ce fichier ainsi que certains messages qu'il affichait sur l'entrée standard pendant l'exécution. Ceci fait, pour lancer le programme automatiquement, il suffit ainsi de modifier le nom du programme dans la commande system() du programme d'automatisation comme pour la méthode 2).

Pour la recherche des suites NOM ADJ et NOM-PREP-NOM, on aura ainsi dans le fichier de motifs :

NOM ADJ (masculins): NCMS#ADJMS
NOM ADJ (féminins):NCFS#ADJFS
NOM PREP NOM : NCMS PREP NCMS

Exemple de sortie sur la rubrique TECHNOLOGIES : TECHNOLOGIES-pos-ngram.txt

Les sorties XML

XPath

(Télécharger le programme BAO3-RB.pl et sa version modifiée)

Cette méthode consiste à intégrer des requêtes XPath dans un programme Perl, via le module XML::XPath. Comme précédemment, j'ai ajouté des modifications sur un programme disponible sur le site du cours (fourni par M. Rachid Belmouhoub). Au départ, celui-ci prend en entrée un fichier XML de la sortie BAO2 et un fichier texte contenant les patrons de recherche, et produit en sortie un fichier texte par patron.

Afin de ne pas avoir à lancer manuellement le programme sur chaque fichier, j'ai donc modifié légèrement le programme pour qu'il prenne un dossier en entrée et non un fichier, et qu'il traite chaque fichier contenu dans celui-ci. Voici le corps du programme :

# Stockage des arguments dans des variables my $dossier= shift @ARGV; my $patterns_file= shift @ARGV; # création d'un dossier de sorties my $sortie="BAO3/"; opendir(DIR, $dossier) or die "Erreur d'ouverture de repertoire: $!\n"; my @files = readdir(DIR); closedir(DIR); # Traitement sur chaque fichier du dossier foreach my $file(@files){ if ($file=~/([^\/\.]+?)_tagged\.xml$/){ # récupération du nom de rubrique my $rub=$1; # Ouverture du fichiers de motifs open(PATTERNSFILE, $patterns_file) or die "can't open $patterns_file: $!\n"; # lecture du fichier contenant les motifs, un motif par ligne (ex : NOM ADJ) while (my $ligne = <PATTERNSFILE>) { &extract_pattern($file,$ligne,$rub); } close(PATTERNSFILE); } }

Le programme est constitué de deux fonctions : &construit_XPath et &extract_pattern.
&construit_XPath crée le chemin Xpath en s'aidant d'une liste de tokens correspondant à chaque élément du motif recherché. Ceci se fait avec la commande split, séparant les éléments du motif par un espace :

sub construit_XPath{ # On récupère la ligne du motif recherché my $local_ligne=shift @_; # Initialisation du chemin XPath my $search_path=""; # Suppression d'un éventuel retour à la ligne chomp($local_ligne); # On élimine un éveltuel retour chariot hérité de windows $local_ligne=~ s/\r$//; # Construction d'un tableau dont chaque élément = un élément du motif recherché my @tokens=split(/ /,$local_ligne);

On commence ensuite la construction du chemin XPath, en sélectionnant le premier element qui contient parmi ses fils un attribut type. On crée en plus une boucle, pour prendre en compte les différents éléments composant le motif :

$search_path="//element[contains(data[\@type=\"type\"],\"$tokens[0]\")]"; my $i=1; while ($i < $#tokens) { $search_path.="[following-sibling::element[1][contains(data[\@type=\"type\"], \"$tokens[$i]\")]"; $i++; } my $search_path_suffix="]"; # chemin XPath final $search_path.="[following-sibling::element[1][contains(data[\@type=\"type\"],\"" .$tokens[$#tokens]."\")]".$search_path_suffix; return ($search_path,@tokens); }

On se sert ensuite de cette fonction dans &extract_pattern, chargée de trouver les balises contenant ceux du motif (NOM, ADJ...).

sub extract_pattern { # fichier et nom de rubrique en arguments my $file=shift; my $rub=shift; # On récupère la ligne du motif recherché my $ext_pat_ligne= shift @_; # construire le chemin Xpath pour trouver dans le bon "element" my ($search_path,@tokens) = &construit_XPath($ext_pat_ligne); # création du nom du fichier sortie pour chaque motif en utilisant la fonction join my $match_file = $sortie.$rub."-extract-".join('_', @tokens).".txt"; open(MATCHFILE,">:encoding(UTF-8)", "$match_file") or die "can't open $match_file: $!\n"; # création de l'objet XML::XPath pour explorer le fichier de sortie tree-tagger XML my $xp = XML::XPath->new( filename => $dossier."/".$file ) or die "big trouble";

On continue en parcourant les noeuds correspondant au motif avec findnodes :

# chercher les "elements" qui satisfont le chemin construit en haut foreach my $noeud ( $xp->findnodes($search_path)) { my $form_xpath=""; # chercher le fils data dont l'attribut type est string $form_xpath="./data[\@type=\"string\"]"; my $following=0; # compteur pour l'extraction des éléments suivants du motif # ecrire dans le fichier le contenu textuel au moyen de la méthode string_value print MATCHFILE $xp->find($form_xpath,$noeud)->get_node(1)->string_value," "; # On descend dans chaque noeud element du bloc while ( $following < $#tokens) { $following++; # calculer le chemin qui va se trouver dans le troisieme fils data my $following_elmt="following-sibling::element[".$following."]"; $form_xpath=$following_elmt."/data[\@type=\"string\"]"; # concatenation # impression de l'élément suivant du motif suivi d'un retour à la ligne print MATCHFILE $xp->find($form_xpath,$noeud)->get_node(1)->string_value," "; print MATCHFILE "\n"; } print MATCHFILE "\n"; } close(MATCHFILE); }

En essayant le programme sur le dossier de sortie BAO2 avec un seul motif (NOM ADJ), on constate que le temps de traitement est assez long : une trentaine de minutes ! A noter aussi que les fichiers de plus de 20 Mo ne peuvent être traités.

Exemple de résultat : SOCIETE-extract-NOM_ADJ.txt

XSLT

Ce problème de taille de fichiers ne se pose pas avec XSLT. Cette méthode produit en résultat une page HTML contenant un tableau des motifs recherchés. Pour le motif NOM ADJ, les noms seront affichés en rouge et les adjectifs en bleu. Pour NOM-PREP-NOM, les noms seront toujours en rouge et les prépositions en vert.

La difficulté réside ici dans la prise en compte des NOM différents pour ces deux motifs : pour cela, j'ai choisi d'inclure les requêtes XPath identifiant ces motifs directement dans l'attribut match des templates. Les balises <element> étant celles qui contiennent les tags, on a donc comme chemins :

Pour le motif NOM ADJ :

<xsl:template match="element[contains(data[1], 'NOM')][following-sibling::element[1] [contains(data[1], 'ADJ')]]">

Pour le motif NOM PREP NOM :

<xsl:template match="element[contains(data[1], 'NOM')][following-sibling::element[1] [contains(data[1], 'PRP')]][following-sibling::element[2][contains(data[1], 'NOM')]]">

Pour attribuer à chaque élément sa couleur, on utilise la balise <font> et son attribut color sur la valeur du 3eme attribut data (data[3]), qui correspond dans l'arborescence à celui contenant les tags. Sachant cela, voyons maintenant nos deux templates complets :

<xsl:template match="element[contains(data[1], 'NOM')][following-sibling::element[1] [contains(data[1], 'ADJ')]]"> <font color="#DF0101"><xsl:value-of select="./data[3]"/></font> <xsl:text> </xsl:text> <font color="#013ADF"><xsl:value-of select="following-sibling::element[1]/data[3]"/> </font><br/> </xsl:template> <xsl:template match="element[contains(data[1], 'NOM')][following-sibling::element[1] [contains(data[1], 'PRP')]][following-sibling::element[2][contains(data[1], 'NOM')]]"> <font color="#DF0101"><xsl:value-of select="./data[3]"/></font> <xsl:text> </xsl:text> <font color="#04B404"><xsl:value-of select="following-sibling::element[1]/data[3]"//> </font><xsl:text> </xsl:text><font color="#DF0101"> <xsl:value-of select="following-sibling::element[2]/data[3]"/> </font><br/> </xsl:template>

On applique ensuite ces templates entre des balises HTML en sélectionnant pour chacun le chemin vers la racine : "./EXTRACTION/file/items/item/*/element[chemins XPath respectifs vus ci-dessus]". L'étoile permet de sélectionner aussi bien les balises <element> sous les noeuds <title> que <description>. Voyons comment sont appliqués ces templates dans un tableau (les attributs de mise en forme n'ont pas été inclus ici, pour plus de clarté) :

<xsl:template match="/"> <html> <body> <table> <tr> <th>Extraction de patrons</th> </tr> <tr> <td><b>NOM</b> <b>ADJ</b></td> <td><b>NOM</b> <b>PREP</b> <b>NOM</b></td> </tr> <tr> <td><xsl:apply-templates select="./EXTRACTION/file/items/item/*/element [contains(data[1], 'NOM')][following-sibling::element[1][contains(data[1], 'ADJ')]]"/></td> <td><xsl:apply-templates select="./EXTRACTION/file/items/item/*/element [contains(data[1], 'NOM')][following-sibling::element[1][contains(data[1], 'PRP')]] [following-sibling::element[2][contains(data[1], 'NOM')]]"/></td> </tr> </table> </body> </html> </xsl:template>

Voici un exemple de résultat sur le fichier EUROPE_tagged.xml avec cette feuille de style.

Une fois en possession de nos motifs syntaxiques, nous pouvons enfin passer à la dernière étape de ce projet : la visualisation en graphes.