Boîte à Outils : série 3

"Easy things should be easy, and hard things should be possible."

pluriTAL  M1
  • Accueil
  • BaO 1
  • BaO 2
  • BàO 3
  • BaO 4
  • Analyse
  • A Propos

BàO 3 : Extraction de Patrons Morphosyntaxiques

I. Spécification

Boîte à Outils série 3

Extraction des patrons morphosyntaxiques dans les étiquetages produits avec Cordial/Treetagger

Données

  • Entrée :
    • les sorties au format XML de l'étiquetage (via Treetagger) issues de la Boîte à Outils Série 2
    • les sorties "brutes" de l'étiquetage (via Cordial) issues de la Boîte à Outils Série 2
  • Sortie : Des listes des patrons morphosyntaxiques triés par ordre de fréquence d'occurrence (1 fichier txt par motif et par rubrique)

II. Méthodes et Outils

2.1 Méthodes(Script Perl)

Dans cette phase, nous adaptons le script fourni par M. Serge FLEURY. Voilà le script de départ :

                                
#!/usr/bin/perl
#----------------------------------
# Ouverture des fichiers en lecture
#----------------------------------
open (FICTAG, $ARGV[0]) or die ("probleme sur ouverture de la sortie CORDIAL...");
open (FICPOS, $ARGV[1]) or die ("probleme sur ouverture du fichier des patrons...");
#-----------------------------------------
# on stocke les patrons dans une liste....
#-----------------------------------------
my @listedespatrons=();
while (my $lignepos = <FICPOS>) {
    chomp($lignepos);
    push(@listedespatrons,$lignepos);
}
close(FICPOS);
#---------------------------
# Initialisation des listes
#--------------------------
my @malignesegmentee = ();
my @listedetokens = ();
my @listedelemmes = ();
my @listedepos = ();
#-------------------------------------------
# Lecture du fichier de tags ligne par ligne
#-------------------------------------------
while (my $ligne = <FICTAG>) {
    #----------------------------------------------------------------------------------
    # On ne s'occupe pas des lignes qui ne respectent pas la modèle mot tab mot tab mot
    #----------------------------------------------------------------------------------
    if ($ligne =~ /^[^\t]+\t[^\t]+\t[^\t]+$/) {
	#-------------------------------------------
	# Suppression du caractère de saut de ligne
	chomp($ligne);
	#-------------------------------------------
	# Remplissage des listes
	@malignesegmentee = split(/\t/, $ligne);
	push(@listedetokens, $malignesegmentee[0]);
	push(@listedelemmes, $malignesegmentee[1]);
	push(@listedepos, $malignesegmentee[2]);
	#-------------------------------------------
    }
}
close(FICTAG);
#-----------------------------------
# on va maintenant parcourir les POS
# et les TOKENS en //
#----------------------------------------------------------------------------------------
# 1. on cree une liste tmp des POS que l'on va parcourir en supprimant le premier element 
#    a chaque fois
#----------------------------------------------------------------------------------------
my @tmplistedespos=@listedepos;
my $indice=0;
while (my $pos =shift(@tmplistedespos)) {
    foreach my $patron (@listedespatrons) {
	#-----------------------------------
	# on segmente le patron pour connaitre
	# son premier element
	my @listedeterme=split(/\#/,$patron);
	#-----------------------------------
	# on teste si l'element courant POS correspond au premier element du patron...
	if ($pos=~/$listedeterme[0]/) {
	    # si c'est OK...
	    # on regarde maintenant s'il y a correspondance pour la suite...
	    my $verif=0;
	    for (my $i=0;$i<=$#listedeterme-1;$i++) {
		if ($tmplistedespos[$i]=~/$listedeterme[$i+1]/) { 
		    #Le suivant est bon aussi...
		    $verif++ ;
		}
		else {
		    # ici : $tmplistedespos[$i] differe de $listedeterme[$i+1]...
		}
	    }
	    #------------------------------------------------------------------------
	    # si verif est egal au nb d'element du patron c'est qu'on a tt reconnu... 
	    # on imprime les tokens en // aux POS : astuce $indice permet de garder le 
	    # le // entre POS et TOKEN....
	    #------------------------------------------------------------------------
	    if ($verif == $#listedeterme) { 
		#print "Correspondance sur $patron \n";
		for (my $i=0;$i<=$#listedeterme;$i++) {
		    print $listedetokens[$indice+$i]," ";
		}
		print "\n";
	    }
	}
    }
    $indice++;
    # on avance dans la liste des POS et des TOKEN en //
}
                                
                            

2.2 Outils

2.2.1 XQuery [BaseX]

2.2.1 XSLT [oXygen 16.1]

III. Solution

3.1 Script Perl

Adaptation du script

  • Traitement des sorties au format XML de l'étiquetage (via Treetagger) issues de la Boîte à Outils Série 2.
    Nous ajoutons une option -t au script. Par défault, le programme traite les fichiers comme sorties de Cordial [BàO2]; quand cette option est activée, le script traite les fichiers comme sorties de TreeTagger [BàO2].
  • Impression des résultats séparément en utilisant la table de hashage.
  • Optimisation de lecture du fichier de motifs.
                                
#!/usr/bin/perl
use warnings;
use strict;
use Getopt::Std;
# use Data::Dumper;
use open IO => ':encoding(UTF-8)';#tree-tagger
use vars '$opt_t';

my $MODIF="2018-05-16";
my $DOC=<<DOCUMENTATION;
    ____________________________________________________________________________

    NOM :   Boîte à Outils 3      
    MODIFICATION :
            $MODIF
    USAGE : 
            perl Bao_3.pl [OPTION] FICHIER-A-EXTRAIRE FICHIER-DE-MOTIF
    DESCRIPTION:
            Le programme prend en entree le nom du fichier à traiter et le nom
            du fichier de motifs.
            Le programme construit en sortie pour chaque motif un fichier txt de 
            patrons morphosyntaxiques.

            -t
                exige un fichier de motifs correspondant aux jeux 
                d'etiquettes de tree-tagger français, et un fichier XML de
                textes etiquetes par tree-tagger.

            par default, le script demande d'un fichier de motifs correspondant
            au Cordial, et d'un fichier de texte etiquetes par Cordial.
    ____________________________________________________________________________

DOCUMENTATION

getopts('t');

if (@ARGV!=2) {
    die $DOC;
}

#------------------------------------------------------------------------------
# Ouverture des fichiers en lecture
# si option -t est active, le fichier d'entree sera traite comme sortie de CORDIAL(txt-iso-8859-15)
# sinon, le fichier d'entree sera traite comme sortie de TreeTagger(XML-utf8)
#------------------------------------------------------------------------------
my $FHTAG;
if(defined($opt_t))
{
    open ($FHTAG,"<", $ARGV[0]) or die ("probleme sur ouverture de la sortie de BaO2...");
}
else
{
    open ($FHTAG,"<:encoding(iso-8859-15)", $ARGV[0]) or die ("probleme sur ouverture de la sortie de BaO2...");
}
open (my $FHPOS,"<", $ARGV[1]) or die ("probleme sur ouverture du fichier des patrons...");
#---------------------------------------------------------------------------
# 1. on localise (desinitialise) le variable global $/ pour permettre a l'operateur
#    <..> de lire l'ensemble de texte dans une chaine de caracs
#    typique usage de local; my est illegal pour les variables de ponctuation
#    (comme $_ $/ $")
# 2. on segmente la chaine (par '\n') en liste de patrons
# astuce : la boucle (comme while(my $var = <FH>)) est couteux pour 
#          la meme tache; split() est plus efficace
#---------------------------------------------------------------------------
my $mesPatrons = do { local $/; <$FHPOS> };
# $mesPatrons=~ s/\r//g;
my @listePatrons = split('\n', $mesPatrons);
close($FHPOS);
#---------------------------
# Initialisation des listes
#--------------------------
my @maLigneSegmentee = ();
my @listeTokens = ();
my @listePOS = ();
#------------------------------------------------------------------------------
# Lecture du fichier de tags ligne par ligne
# extraction des tokens et des pos, puis les stocker dans les listes
#------------------------------------------------------------------------------
while (my $ligne = <$FHTAG>) {
    chomp($ligne);
    if(defined($opt_t))
    {
        if ($ligne =~ m/<element><data type=\"type\">([^<]+)<\/data><data type=\"lemma\">[^<]+<\/data><data type=\"string\">([^<]+)<\/data><\/element>/) 
        {
            push(@listeTokens, $2);
            push(@listePOS, $1);
        }
    }
    else
    {
        @maLigneSegmentee = split("\t", $ligne);
        if (scalar(@maLigneSegmentee)==3)
        {
            push(@listeTokens, $maLigneSegmentee[0]);
            push(@listePOS, $maLigneSegmentee[2]);
        }
    }
}
close($FHTAG);

#---------------------------------------------------
# on va maintenant parcourir les POS et les TOKENS
#----------------------------------------------------------------------------------------
# 1. on cree une liste tmp des POS que l'on va parcourir en supprimant le premier element 
#    a chaque fois
# 2. on cree un dictionnaire de termes (table de hashage) pour stocker les termes trouves  
#----------------------------------------------------------------------------------------
my @tmpListePOS=@listePOS;
my $indice=0;
my %terminologie;
while (my $pos = shift(@tmpListePOS)) {
    foreach my $patron (@listePatrons) {
	#-----------------------------------
	# on segmente le patron pour connaitre
	# son premier element
	my @listeTerme = split('#',$patron);
	#-----------------------------------
	# on teste si l'element courant POS correspond au premier element du patron...
	if ($pos=~/$listeTerme[0]/) {
	    # si c'est OK...
	    # on regarde maintenant s'il y a correspondance pour la suite...
	    my $verif=0;
	    for (my $i=0;$i<=$#listeTerme-1;$i++) {
		if ($tmpListePOS[$i]=~/$listeTerme[$i+1]/) { 
		    #Le suivant est bon aussi...
		    $verif++ ;
		}
	    }
	    #------------------------------------------------------------------------
	    # si verif est egal au nb d'element du patron c'est qu'on a trouve un terme
	    # on enchaine les tokens en terme; puis ajoute le terme au dict de termes 
	    #------------------------------------------------------------------------
	    if ($verif == $#listeTerme) { 
		my $termTrouve="";
		for (my $i=0;$i<=$#listeTerme;$i++) {
		    $termTrouve.=$listeTokens[$indice+$i]." ";
		}
 		$termTrouve=~ s/ $/\n/g;
                push(@{$terminologie{$patron}},$termTrouve);
	    }
	}
    }
    $indice++;
    # on avance dans la liste des POS et des TOKEN
}
#-----------------------------------
# Impression de resultats
#-----------------------------------
# alt: print Dumper(\%terminologie);

while ((my $patron, my $terms) = each %terminologie){
    $patron =~ s/#/_/g;
    open(my $FH, ">", "$patron.txt");
    print $FH "\n\n------ $patron ------\n\n";
    foreach my $term (@$terms) { print $FH $term; }
    close($FH);
}                                
                            
telecharger

Motifs

Selon le jeu d'étiquettes de chaque outil d'étiquetage, nous avons proposé pour chacun trois motifs de requêtes :
[NOM ADJ], [NOM, PREP NOM], et [NOM PREP DET NOM].

MOTIF_CORDIAL
  • NC[^ ]+#ADJ[^ ]+
  • NC[^ ]+#PREP#NC[^ ]+
  • NC[^ ]+#PREP#DET[^ ]+#NC[^ ]+
telecharger

MOTIF_TREETAGGER
  • NOM#ADJ
  • NOM#PRP[^ ]*#NOM
  • NOM#PRP[^ ]*#DET[^ ]*#NOM
telecharger

3.2 XQuery

NOM_ADJ

                                
(:BAO3_XQUERY_NomAdj.xq Sur les fichiers étiquetés avec treetagger (par rubrique a priori), Construire une requête pour extraire les patrons morpho-syntaxiques NOM ADJ:)
(:les patrons sont écrits respectivement dans des fichiers nommés par rubrique:)
   let $base := collection("TREE-TAGGER")/base
   for $rubrique in distinct-values($base/@rubrique)
   let $fName := concat("./Patrons/", $rubrique, "_XQUERY_NomAdj.xml")
   let $params := <output:serialization-parameters xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
   <output:method value='xml'/>
  <output:omit-xml-declaration value="no"/>
</output:serialization-parameters>
   return 
     file:write($fName,
       <patrons rubrique="{$rubrique}" type="NOM_ADJ">
       {
         for $ele1 in $base[@rubrique=$rubrique]/etiquetage/fichier/element
         let $ele2 := $ele1/following-sibling::element[1]
         where $ele1/data[1]="NOM" and $ele2/data[1]="ADJ"
         return <patron>{$ele1/data[3]/text()," ", $ele2/data[3]/text()}</patron>
       }
       </patrons>, $params)                                
                                
                            
telecharger

NOM_PRP_NOM

                                
(:BAO3_XQUERY_NomPrpNom.xq Sur les fichiers étiquetés avec treetagger (par rubrique a priori), Construire une requête pour extraire les patrons morpho-syntaxiques NOM PRP NOM:)
(:les patrons sont écrits respectivement dans des fichiers nommés par rubrique:)
   let $base := collection("TREE-TAGGER")/base
   for $rubrique in distinct-values($base/@rubrique)
   let $fName := concat("./Patrons/", $rubrique, "_XQUERY_NomPrpNom.xml")
   let $params := <output:serialization-parameters xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
   <output:method value='xml'/>
  <output:omit-xml-declaration value="no"/>
</output:serialization-parameters>
   return 
     file:write($fName,
       <patrons rubrique="{$rubrique}" type="NOM_PRP_NOM">
       {
         for $ele1 in $base[@rubrique=$rubrique]/etiquetage/fichier/element
         let $ele2 := $ele1/following-sibling::element[1]
         let $ele3 := $ele1/following-sibling::element[2]
         where $ele1/data[1]="NOM" and $ele2/data[1][contains(.,"PRP")] and $ele3/data[1]="NOM"
         return <patron>{$ele1/data[3]/text()," ", $ele2/data[3]/text()," ", $ele3/data[3]/text()}</patron>
       }
       </patrons>, $params)                                
                                
                            
telecharger

NOM_PRP_DET_NOM

                                
(:BAO3_XQUERY_NomPrpDetNom.xq Sur les fichiers étiquetés avec treetagger (par rubrique a priori), Construire une requête pour extraire les patrons morpho-syntaxiques NOM PRP DET NOM:)
(:les patrons sont écrits respectivement dans des fichiers nommés par rubrique:)
   let $base := collection("TREE-TAGGER")/base
   for $rubrique in distinct-values($base/@rubrique)
   let $fName := concat("./Patrons/", $rubrique, "_XQUERY_NomPrpDetNom.xml")
   let $params := <output:serialization-parameters xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
   <output:method value='xml'/>
  <output:omit-xml-declaration value="no"/>
</output:serialization-parameters>
   return 
     file:write($fName,
       <patrons rubrique="{$rubrique}" type="NOM_PRP_DET_NOM">
       {
         for $ele1 in $base[@rubrique=$rubrique]/etiquetage/fichier/element
         let $ele2 := $ele1/following-sibling::element[1]
         let $ele3 := $ele1/following-sibling::element[2]
         let $ele4 := $ele1/following-sibling::element[3]
         where $ele1/data[1]="NOM" and $ele2/data[1][contains(.,"PRP")] and $ele3/data[1][contains(.,"DET")] and $ele4/data[1]="NOM"
         return <patron>{$ele1/data[3]/text()," ", $ele2/data[3]/text()," ", $ele3/data[3]/text()," ", $ele4/data[3]/text()}</patron>
       }
       </patrons>, $params)                                
                                
                            
telecharger

3.3 XSLT

                                
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="text" encoding="utf-8"/>
    <!-- Bao3 extraction de 3 patrons morpho-syntaxiques sur les fichiers -->
    <!--(head -n 4 $nomFic; tail -n +5 $nomFic | sort | uniq -ic | sort -gr) > $nomFicSort-->
    <xsl:template match="/">
        <xsl:variable name="rubrique" select="base/@rubrique"/>
        <xsl:result-document href="{concat($rubrique,'_XSLT_NomAdj.txt')}">
            <xsl:text>
                
----------------Nom Adj-------------------
                
                
</xsl:text>
            <xsl:apply-templates select="base/etiquetage/fichier/element" mode="NomAdj"/>
        </xsl:result-document>
        <xsl:result-document href="{concat($rubrique,'_XSLT_NomPrpNom.txt')}">
            <xsl:text>
                
----------------Nom Prp Nom-----------------
                                
</xsl:text>
            <xsl:apply-templates select="base/etiquetage/fichier/element" mode="NomPrpNom"/>
        </xsl:result-document>

        <xsl:result-document href="{concat($rubrique,'_XSLT_NomPrpDetNom.txt')}">
            <xsl:text>
                
----------------Nom Prp Det Nom----------------
                                
</xsl:text>
            <xsl:apply-templates select="base/etiquetage/fichier/element" mode="NomPrpDetNom"/>
        </xsl:result-document>
    </xsl:template>

    <xsl:template match="element" mode="NomAdj">
        <xsl:variable name="ele1" select="."/>
        <xsl:variable name="ele2" select="following-sibling::element[1]"/>
        <xsl:if test="($ele1/data[1]='NOM') and ($ele2/data[1]='ADJ')">
            <xsl:value-of select="concat($ele1/data[3],' ',$ele2/data[3])"/>
            <xsl:text>
</xsl:text>
        </xsl:if>
    </xsl:template>

    <xsl:template match="element" mode="NomPrpNom">
        <xsl:variable name="ele1" select="."/>
        <xsl:variable name="ele2" select="following-sibling::element[1]"/>
        <xsl:variable name="ele3" select="following-sibling::element[2]"/>
        <xsl:if
            test="($ele1/data[1]='NOM') and ($ele2/data[1][matches(.,'PRP')]) and ($ele3/data[1]='NOM')">
            <xsl:value-of select="concat($ele1/data[3],' ',$ele2/data[3],' ',$ele3/data[3])"/>
            <xsl:text>
</xsl:text>
        </xsl:if>
    </xsl:template>

    <xsl:template match="element" mode="NomPrpDetNom">
        <xsl:variable name="ele1" select="."/>
        <xsl:variable name="ele2" select="following-sibling::element[1]"/>
        <xsl:variable name="ele3" select="following-sibling::element[2]"/>
        <xsl:variable name="ele4" select="following-sibling::element[3]"/>
        <xsl:variable name="ele5" select="following-sibling::element[4]"/>
        <xsl:if
            test="($ele1/data[1]='NOM') and ($ele2/data[1][contains(.,'PRP')]) and ($ele3/data[1][contains(.,'DET')]) and ($ele4/data[1]='NOM')">
            <xsl:value-of
                select="concat($ele1/data[3],' ',$ele2/data[3],' ',$ele3/data[3],' ',$ele4/data[3])"/>
            <xsl:text>
</xsl:text>
        </xsl:if>
    </xsl:template>

</xsl:stylesheet>
                                
                            
telecharger

3.4 ESSAIS

Nous avons tenté plusieurs combinaisons de tests via les solutions supra. Les combinaisons varient en fonction de solutions, de fichiers de motifs, et de paramètres du Treetagger.

Essai 1

Echantillon : tous les textes extraits et étiquetés dans la BàO2 via Cordial ;
Nombre de rubriques : 14;
Paramètre : Solution Perl + MOTIF_CORDIAL+Fichier étiqueté via Cordial.

Essai 2

Echantillon : tous les textes extraits et étiquetés dans la BàO2 via TREETAGGER;
Nombre de rubriques : 14;
Paramètre : Solution Perl + MOTIF_TREETAGGER+Fichier étiqueté via Treetagger (french-oral-utf-8.par).

Essai 3

Echantillon : tous les textes extraits et étiquetés dans la BàO2 via TREETAGGER;
Nombre de rubriques : 14;
Paramètre : Solution Perl + MOTIF_TREETAGGER+Fichier étiqueté via Treetagger (french-utf8.par).

Essai 4

Echantillon : tous les textes extraits et étiquetés dans la BàO2 via TREETAGGER;
Nombre de rubriques : 14;
Paramètre : Solution XSLT +Fichier étiqueté via Treetagger (french-utf8.par).

Essai 5

Echantillon : tous les textes extraits et étiquetés dans la BàO2 via TREETAGGER;
Nombre de rubriques : 14;
Paramètre : Solution XQUERY +Fichier étiqueté via Treetagger (french-utf8.par).

Nous avons donc pour chaque rubrique : 3(motif) x 5(paramètre) = 15 (liste de patron)
Nous détaillerons notre exploration dans la rubrique ANALYSE.

IV. Résultats

4.1 CORDIAL

Exemple

823353 [NC-ADJ] SORT 823353 [NC-PREP-NC] SORT 823353 [NC-PREP-DET-NC] SORT
3208 [NC-ADJ] SORT 3208 [NC-PREP-NC] SORT 3208 [NC-PREP-DET-NC] SORT

Téléchargement

3208 3210 3214 3224 3232
3236 3242 3244 3246 3260
3476 3546 651865 823353

4.2 TREE-TAGGER

Exemple

823353 [NOM-ADJ] SORT 823353 [NOM-PRP-NOM] SORT 823353 [NOM-PRP-DET-NOM] SORT
3208 [NOM-ADJ] SORT 3208 [NOM-PRP-NOM] SORT 3208 [NOM-PRP-DET-NOM] SORT

Téléchargement

3208 3210 3214 3224 3232
3236 3242 3244 3246 3260
3476 3546 651865 823353

4.3 TREE-TAGGER [fichier paramètre : ORAL]

Exemple

823353 [NOM-ADJ] SORT 823353 [NOM-PRP-NOM] SORT 823353 [NOM-PRP-DET-NOM] SORT
3208 [NOM-ADJ] SORT 3208 [NOM-PRP-NOM] SORT 3208 [NOM-PRP-DET-NOM] SORT

Téléchargement

3208 3210 3214 3224 3232
3236 3242 3244 3246 3260
3476 3546 651865 823353

4.4 XQUERY

Exemple

823353 [NOM-ADJ] SORT 823353 [NOM-PRP-NOM] SORT 823353 [NOM-PRP-DET-NOM] SORT
3208 [NOM-ADJ] SORT 3208 [NOM-PRP-NOM] SORT 3208 [NOM-PRP-DET-NOM] SORT

Téléchargement

3208 3210 3214 3224 3232
3236 3242 3244 3246 3260
3476 3546 651865 823353

4.4 XSLT

Identique à 4.4 XQuery (et 4.2 Tree-Tagger)

Téléchargement

3208 3210 3214 3224 3232
3236 3242 3244 3246 3260
3476 3546 651865 823353

© JIANG Chunyang & XU Yizhou. All rights reserved. | Design by TEMPLATED.