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
TreeTagger
est un outil d'annotation de textes capable d'effectuer une analyse en parties du discours (POS) et lemmes. Nous allons nous s'en servir pour enrichir la sortie XML avec ces informations linguistiques, ce qui nous permettra d'extraire les patrons morpho-syntaxiques du corpus récolté (BàO3 et BàO4).Talismane
est un analyseur syntaxique qui permet de passer d'un texte brut à un réseau de dépendances syntaxiques en format CoNLL. Il est écrit intégralement en Java : il fonctionne donc sur tous les systèmes d'exploitation et est facilement intégrable à d'autres applications.
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.
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&T</data>
<data type="string">AT&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>
...
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 _
Par rapport à BAO1, nous avons nous avons apporté les modifications suivantes :
media
, france
, etc.)cleaning()
) assurant la normalisation de la ponctuation, des guillemets et des caractères spéciaux présents dans le corpusXMLTTtagging()
et etiquettage_talismane()
permettant d'appeler des programmes externes pendant la phase d'étiquetage££debuttitre££
) et des descriptions (££debutdescription£
) dans la sortie générée par TalismaneComme 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/&/&/g;
$title =~ s/</</g;
$title =~ s/>/>/g;
$title =~ s/"/"/g;
$title =~ s/'/'/g;
$title =~ s/’/'/g;
# cleaning <description>
$description =~ s/\n//g;
$description =~ s/'/'/g;
$description =~ s/"/"/g;
$description =~ s/&#39;/'/g;
$description =~ s/&#34;/"/g;
$description =~ s/&/&/g;
$description =~ s/</</g;
$description =~ s/>/>/g;
$description =~ s/"/"/g;
$description =~ s/'/'/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;
}
© 2019 Andrea Francesco Monaco