BàO
[ Boîte à outils 2 ]

BÀO 2



Objectifs

La BàO1 a produit 2 sorties : 1 fichier TXT et 1 fichier XML contenant les titres et les descriptions des fils RSS du Monde pour l'année 2018. Dans cette deuxième étape, nous allons étiqueter ces données à l'aide de deux étiqueteurs, Treetagger et Talismane.

Les outils

Attention : l'exécution de ces programmes demande beaucoup de ressources.

Prenez vos chargeurs, c'est parti !



Treetagger

L'étiquetage en POS

Treetagger prend en entrée un texte tokenisé sur une colonne et produit en sortie un étiquetage en parties du discours (POS) séparé par des tabulations et semblable à ceci.

          andreafrancescomonaco ~ : tree-tagger -token -lemma ~/tree-tagger/french.par tree.txt 
  reading parameters ...
  tagging ...
Ceci  PRO:DEM ceci
est VER:pres  être
un  DET:ART un
exemple NOM exemple
de  PRP de
segmentation  NOM segmentation
en  PRP en
mots  NOM mot
graphiques  ADJ graphique
   finished.
andreafrancescomonaco ~ : 
        

L'étape de segmentation en mots graphiques est donc cruciale pour avoir des bons résultats au niveau d'étiquetage. Pour cette raison, nous avons inséré dans la chaîne de traitement le script perl ut8-tokenize.pl permettant de générer la donnée d'entrée de Treetagger.

Après avoir testé notre corpus, nous avons remarqué que la plupart des erreurs de tokenisation sont dues aux caractères non alphanumériques comme l'apostrophe et les doubles quotes qui sont susceptibles d'être transformées par les logiciels de traitement de texte dans leurs version "typographique". Pour pallier aux pertes d'information morpho-syntaxique, nous avons intégré cette reconversion dans la fonction cleaning(), vue précédemment en BÀO 1.

Treetagger 2 XML

Une fois la sortie Treetagger optimisée, nous allons la structurer en XML afin de l'insérer à la place du contenu textuel non étiqueté du fichier XML généré en BAO 1. Pour ce faire, nous utilisons le script treetagger2xml-utf8.pl fourni pendant le cours par M. Serge Fleury.

Afin de générer un XML valable, nous avons modifié ce script en ajoutant la fonction entites_xml() précédemment vue en BÀO1. En fait, les rubriques choisies (média et France) présentent des caractères réservés par le langage XML qui auraient pu causer des problèmes de validation, comme l'esperluette dans l'occurrence "AT&T" ou encore "Science & vie". Ce transcodage intervient donc après la génération de l'étiquetage. Voici un aperçu de la sortie proprement transcodée en entités XML:

          <?xml version='1.0' encoding='UTF-8'?>
<file>
  <name>Andrea F. Monaco</name>
  <fileid>2018/1/01/19-00-00/0,2-3236,1-0,0.xml</fileid>
  <pubDate>Mon, 01 Jan 2018 18:18:12 +0100</pubDate>
  <item n="1">
    <title>
      <document>
        <article>
          <element>
            <data type="type">ADJ</data>

            <!-- TRANSCODAGE DES ENTITÉS XML -->
            
            <data type="lemma">AT&amp;T</data>
            <data type="string">AT&amp;T</data>
          </element>
          <element>
            <data type="type">NOM</data>
            <data type="lemma">face</data>
            <data type="string">face</data>
          </element>
          <element>
            <data type="type">PRP:det</data>
            <data type="lemma">au</data>
            <data type="string">au</data>
          </element>
          <element>
            <data type="type">NOM</data>
            <data type="lemma">gouvernement</data>
            <data type="string">gouvernement</data>
          </element>
          <element>
            <data type="type">ADJ</data>
            <data type="lemma">américain</data>
            <data type="string">américain</data>
          </element>
          <element>
        ...
        


Talismane

L'étiquetage en dépendances syntaxiques

Pour produire la sortie Talismane nous n'avons effectué aucun traitement préalable sur les données. En effet, cet analyseur syntaxique a été conçu pour parcourir en autonomie les quatre étapes classiques du traitement linguistique : la segmentation en mots, le découpage en phrases, l'étiquetage, et le parsing (repérage et étiquetage des dépendances syntaxiques entre les mots). Par conséquent, nous avons directement appelé ce programme à travers la fonction system(), permettant l'interaction entre notre script et des programmes externes via le Shell.

Voici un aperçu de la sortie CoNLL produite par Talismane.

            
  ## titre : 2018/1/01/19-00-00/0,2-3236,1-0,0.xml
  1 ££debuttitre££  _ NC  NC    0 _ 0 _

  1 Médias  média NC  NC  n=p|g=m 0 _ 0 _
  2 : : PONCT PONCT   1 ponct 1 ponct
  3 en  en  P P   0 _ 0 _
  4 Espagne Espagne NPP NPP n=s|g=f 3 prep  3 prep
  5 , , PONCT PONCT   4 ponct 4 ponct
  6 la  la  DET DET n=s|g=f 8 det 8 det
  7 fausse  faux  ADJ ADJ n=s|g=f 8 mod 8 mod
  8 sortie  sortie  NC  NC  n=s|g=f 0 _ 0 _
  9 du  de  P+D P+D n=s|g=m 8 dep 8 dep
  10  patron  patron  NC  NC  n=s|g=m 9 prep  9 prep
  11  de  de  P P   8 dep 8 dep
  12  Prisa _ NPP NPP   11  prep  11  prep
  13  Juan  Juan  NPP NPP n=s|g=m 12  mod 12  mod
  14  Luis  Luis  NPP NPP n=s|g=m 12  mod 12  mod
  15  Cebrian _ NPP NPP   12  mod 12  mod
  16  . . PONCT PONCT   15  ponct 15  ponct
  
  1 § § PONCT PONCT   0 ponct 0 ponct
  
  1 ££fintitre££  _ NC  NC    0 _ 0 _
  
  1 ££debuttitre££  _ NC  NC    0 _ 0 _
  
  1 France  France  NPP NPP n=s|g=f,m 4 suj 4 suj
  2 Musique _ NPP NPP   1 mod 1 mod
  3 se  se  CLR CLR n=p,s|p=3 4 aff 4 aff
  4 dote  doter V V n=s|t=P,S|p=1,3 0 root  0 root
  5 d  de  P P   4 mod 4 mod
  6 une une DET DET n=s|g=f 7 det 7 det
  7 salle salle NC  NC  n=s|g=f 5 prep  5 prep
  8 de  de  P P   4 mod 4 mod
  9 concerts  concert NC  NC  n=p|g=m 8 prep  8 prep
  10  virtuelle virtuel ADJ ADJ n=s|g=f 4 mod 4 mod
  11  . . PONCT PONCT   10  ponct 10  ponct
  
  1 § § PONCT PONCT   0 ponct 0 ponct
  
  1 ££fintitre££  _ NC  NC    0 _ 0 _
          
        


Le script

BAO2.pl

Par rapport à BAO1, nous avons nous avons apporté les modifications suivantes :

Coût d'exécution

Comme déjà mentionné, l'exécution de programmes d'étiquetage sur des corpus aussi volumineux nécessite beaucoup de ressources. Pour la rubrique "Média", la plus petite des deux analysées, les temps d'exécution sont d'environ 193 minutes, soit 3 heures et 13 minutes. Quant à la rubrique "France", cela a pris environ 5 heures.

Rubrique Média


          
  #!/usr/bin/perl
  <<DOC;
  -------------------------------------------------------------------------------
  Nom : ANDREA F. MONACO

  BAO2.pl -- nettoie et extrait le contenu textuel des balises <title> et 
  <description> des fichiers XML-RSS en entrée et produit trois sorties: 
  
    - un fichier texte brut
    - un fichier XML étiqueté en POS (TreeTagger).
    - un fichier au format CoNLL étiqueté en dépendances syntaxiques (Talismane).
  
  nom_rubrique : france, media, international
  Mode d emploi : perl BAO2.pl chemin_corpus nom_rubrique (ex.: france)
  -------------------------------------------------------------------------------
  DOC
  use XML::RSS;
  use strict;
  
  use utf8;
  binmode STDOUT, ":utf8";
  
  my $folder="$ARGV[0]";
  my $rubrique="$ARGV[1]";
  # -----------------------------------------------------------------------------
  # PREPARING ARGUMENTS
  my $n_rubrique = $rubrique;
  my %rub_conv = ("france"=>"3224", "media"=>"3236", "international"=>"3210");
  my $rubrique = $rub_conv{"$rubrique"};
  # on s'assure que le répertoire ne se termine pas par "/"
  $folder=~ s/[\/]$//;
  # -----------------------------------------------------------------------------
  my %doublons;
  my $i=1;
  # -----------------------------------------------------------------------------
  # OPENING FILES FOR WRITING
  open my $txt, ">:encoding(UTF-8)", "./sorties/txt/BAO2-$folder-$n_rubrique.txt";
  open my $xml, ">:encoding(UTF-8)", "./sorties/xml/BAO2-$folder-$n_rubrique.xml";
  open my $talismane, ">:encoding(UTF-8)", "./sorties/tagged/BAO2-$folder-$n_rubrique.txt";
  # -----------------------------------------------------------------------------
  # PRINTING XML HEADERS
  print $xml "<?xml version='1.0' encoding='UTF-8'?>\n";
  print $xml "<file>\n";
  print $xml "\t<name>Andrea F. Monaco</name>\n";
  # -----------------------------------------------------------------------------
  # RETRIEVING AND PROCESSING XML FILES
  &finding_files($folder);
  # -----------------------------------------------------------------------------
  # CLOSING FILES
  print $xml "</file>\n";
  close $xml;
  close $txt;
  close $talismane;
  exit;
  # -----------------------------------------------------------------------------
  # RETRIEVING AND PROCESSING XML FILES
  sub finding_files {
    # annee : /2018 ($folder)
      my $path = shift(@_);
      # on récupère la liste des fichiers de $folder
      opendir(DIR, $path) or die "can't open $path: $!\n";
      my @files = readdir(DIR);
      closedir(DIR);
      # on ordonne la liste de fichiers
      foreach my $file (sort { $a <=> $b } @files) {
      next if $file =~ /^\.\.?$/;
      $file = $path."/".$file;
      # /mois/jour/horaire...
      if (-d $file) {
        print "$file\t↵\n";
        &finding_files($file);  #recurse!
      }
      # ...les fichiers ont été trouvé !
      if (-f $file) {
          if ($file =~ /$rubrique.+\.xml$/) {
            print $xml "\t<fileid>$file</fileid>\n";  
            print $i++," : $file \n";
            # --------------------------------------------------------------
            # XML::RSS VERSION (BAO1)
            my $rss=new XML::RSS;
            eval {$rss->parsefile($file); };
          if( $@ ) {
            $@ =~ s/at \/.*?$//s;
            print STDERR "\nERROR in '$file':\n$@\n";
          } 
          else {
            my $count = 1;
            # le fichier a été parsé, on récupère les données
            my $date = $rss->{'channel'}->{'pubDate'};
            print $xml "\t<pubDate>$date</pubDate>\n";
            my $talismane_title = "";
            my $talismane_description = "";
              foreach my $item (@{$rss->{'items'}}) {
                # pour chaque item on extrait les infos de <title> et <description>
                my $description = $item->{'description'};
                my $title = $item->{'title'};
                my($title, $description) = &cleaning($title, $description);
                if (exists $doublons{$title}) {
                  $doublons{$title}++;
              }
              else {
                $doublons{$title}=1;
                # --------------------------------------------------------------
                # BAO2      
                $title =~ s/ +$//g;
                $description =~ s/ +$//g;     
                my $title_tal = $title;
                my $description_tal = $description;
                $title_tal =~ s/([…\.\?\!]+)/$1\n\n/g;
                $title_tal =~ s/(\n)+ +/$1/g; 
                $description_tal =~ s/([…\.\?\!]+)/$1\n\n/g;
                $description_tal =~ s/(\n)+ +/$1/g;
                $talismane_title = $talismane_title."££debuttitre££\n\n".$title_tal."§\n\n££fintitre££\n\n" ;
                $talismane_description = $talismane_description."££debutdescription££\n\n".$description_tal."§\n\n££findescription££\n\n" ;
                print $txt "#$title\n";
                print $txt "$description\n\n";
                print $xml "\t<item n='$count'>\n" ;
                my ($TTtitle, $TTdescription) = &XMLTTtagging($title, $description);
                print $xml "\t\t<title>$TTtitle</title>\n" ;
                print $xml "\t\t<description>$TTdescription</description>\n";
                print $xml "\t</item>\n";
                $count++;
                }
            }
            print $talismane "## titre : ".$file."\n";
            my $tagged_talismane = &etiquettage_talismane($talismane_title);
            print $talismane "$tagged_talismane";
            print $talismane "## description : ".$file."\n";
            my $tagged_talismane = &etiquettage_talismane($talismane_description);
            print $talismane "$tagged_talismane";
          }   
        }
      }
    }
  } # finding_files
  # -----------------------------------------------------------------------------
  # CLEANING <TITLE> AND <DESCRIPTION TEXT CONTENT
  sub cleaning {
  
    my ($title, $description) = @_;
  
    # cleaning <title>
    $title .= "." ;
    $title =~ s/ +\././g;
    $title =~ s/<[^>]+>//g;
    $title =~ s/&amp;/&/g;
    $title =~ s/&lt;/</g;
    $title =~ s/&gt;/>/g;
    $title =~ s/&quot;/"/g;
    $title =~ s/&apos;/'/g;
    $title =~ s/’/'/g;
  
    # cleaning <description>
    $description =~ s/\n//g;
    $description =~ s/&#39;/'/g;
    $description =~ s/&#34;/"/g;
    $description =~ s/&#38;#39;/'/g;
    $description =~ s/&#38;#34;/"/g;
    $description =~ s/&amp;/&/g;
    $description =~ s/&lt;/</g;
    $description =~ s/&gt;/>/g;
    $description =~ s/&quot;/"/g;
    $description =~ s/&apos;/'/g;
    $description =~ s/<[^>]+>//g;
    $description =~ s/’/'/g;
    $description =~ s/”/"/g;
    $description =~ s/“/"/g;
  
    return $title, $description;
  
  } # cleaning
  
  sub XMLTTtagging {
  
    my ($title, $description) = @_;
  
    open my $tmp, ">:encoding(utf8)", "./tmp/tmp.txt";
    print $tmp $title;
    close $tmp;
  
    system("perl ./treetagger/tokenise-utf8.pl -f ./tmp/tmp.txt | tree-tagger -quiet -token -lemma -no-unknown ./treetagger/french-utf8.par > ./tmp/temp-tagged-text.txt");
    system("perl ./treetagger/treetagger2xml-utf8.pl ./tmp/temp-tagged-text.txt utf8");
  
    local $/=undef;
    open my $tagged, "<:encoding(utf8)", "./tmp/temp-tagged-text.txt.xml";
    my $tagged_title = <$tagged>;
  
    close $tmp;
  
    $tagged_title =~ s/<\?xml.+?>//;
  
    #----------------------------------------------------
  
    open my $tmp, ">:encoding(UTF-8)", "./tmp/tmp.txt";
    print $tmp $description;
    close $tmp;
  
    system("perl ./treetagger/tokenise-utf8.pl -f ./tmp/tmp.txt | tree-tagger -quiet -token -lemma -no-unknown ./treetagger/french-utf8.par > ./tmp/temp-tagged-text.txt");
    system("perl ./treetagger/treetagger2xml-utf8.pl ./tmp/temp-tagged-text.txt utf8");
  
    open my $tagged, "<:encoding(UTF-8)", "./tmp/temp-tagged-text.txt.xml";
    my $tagged_description = <$tagged>;
  
    close $tmp;
  
    $tagged_description =~ s/<\?xml.+?>//;
  
    return $tagged_title, $tagged_description;
  
  } # XMLTTtagging
  
  sub etiquettage_talismane {
  
    my $var = shift @_;
    open my $tmp, ">:encoding(UTF-8)", "./tmp/bao1_test.txt";
    print $tmp $var;
    close $tmp;
    system("java -Xmx1G -Dconfig.file=talismane/talismane-fr-5.0.4.conf -jar talismane/talismane-core-5.1.2.jar --analyse --sessionId=fr --encoding=UTF8 --inFile=./tmp/bao1_test.txt --outFile=./tmp/bao1_test.tal");
    my $lefil;
  
    local $/=undef;
    open my $tagged, "<:encoding(UTF-8)", "./tmp/bao1_test.tal";
    $lefil = <$tagged>;
    close $tagged;
  
    return $lefil;
  }