Boîte à outils 3 : extraction de patrons et de relations de dépendance
Le but de cette Boîte à Outils est de rechercher et d'extraire des patrons et relations de dépendance à partir des données textuelles étiquetées dans la BAO2.
Plus précisément, il s'agit de:
- extraire des patrons avec Perl, Python, XSLT et XQuery (ces deux dernières méthodes sont traitées dans le cours 'Documents Structurés'). On travaille à partir des fichiers XML étiquetés avec TreeTagger.
- extraire des relations de dépendance avec Perl, XSLT, XQuery. On travaille à partir de la sortie UDpipe reformatée en XML.
PARTIE 1: EXTRATION DE PATRONS
Pour chaque rubrique, quatre patrons obligatoires sont à extraire:
NOM PREP NOM PREP
VERBE DET NOM
NOM ADJ
ADJ NOM
Il faut aussi extraire deux patrons facultatifs de longueur 3, sélectionnés en fonction de la rubrique. Pour international, je suis partie du principe qu'il
y était souvent questions de chefs d'Etat, de personnalités politiques et diplomatiques, donc de noms propres. Par conséquent, j'ai choisi les patrons NOM PREP NAM
(par exemple "sortie des Britanniques") et NOM NAM NAM (par exemple "président Joe Biden").
Pour l'économie, j'ai considéré qu'il y avait beaucoup de chiffres (pourcentages, sommes, dates, etc.). Par conséquent, j'ai choisi le patron
VERBE NUM NOM. Il y a également de nombreux noms d'organisations, par exemple 'Banque de France', qui suivent le patron NOM PREP NAM. Je l'ai donc choisi comme deuxième patron pour
cette rubrique. Enfin, dans la rubrique Idées, de nombreuses personnalités donnent leur point de vue sur un sujet. J'ai donc pensé que le patron NOM NAM NAM serait pertinent (par exemple
"économiste Thomas Piketty"). J'ai choisi comme deuxième patron NAM PREP NOM.
Script Perl commenté
#!/usr/bin/perl
#--------------------------------------------------------------------
#pour lancer le script depuis le terminal:
#perl programme.pl fichierTT patron
#exemple de patron: DET NC ADJ
#le fichierTT est un texte étiqueté et lemmatisé par treetagger
#pour les patrons, voir le site
#--------------------------------------------------------------------
use utf8;
binmode STDOUT,":utf8";
#--------------------------------------------------------------------
#le nom de la rubrique à traiter est le premier argument; on l'enlève de la liste des arguments
my $fileatraiter=shift @ARGV;
#le patron correspond aux arguments restants
my @PATRON=@ARGV;
open my $input, "<:encoding(utf-8)",$fileatraiter;
#on lit l'ensemble du fichier et on ajoute le contenu dans une liste
my @LISTE=<$input>;
close($input);
#tant qu'on n'a pas fini de lire le texte, on récupère la ligne et on l'enlève de la liste (la liste sera vide quand on aura lu tout le texte)
while (my $ligne=shift @LISTE) {
# si la ligne contenue dans $ligne correspond au premier du patron $PATRON[0]
my $terme="";
#on cherche le patron dans la ligne grâce à une expression régulière
if ($ligne=~/<element><data type="type">$PATRON[0](:[^<]+?)?<\/data><data type="lemma">[^<]+?<\/data><data type="string">([^<]+?)<\/data><\/element>/) {
#on récupère le premier groupe de capture (la forme)
$terme=$terme.$2;
#on crée une variable pour la longueur du patron
my $longueur=1;
#et l'indice du patron où on se trouve
my $indice=1;
# on lit autant de ligne qu'il y a d'élément dans le patron et on teste chaque patron
while (($LISTE[$indice-1]=~/<element><data type="type">$PATRON[$indice](:[^<]+?)?<\/data><data type="lemma">[^<]+?<\/data><data type="string">([^<]+?)<\/data><\/element>/) and ($indice <= $#PATRON)) {
#on incrémente l'indice et la longueur
$indice++;
#et on récupère la forme
$terme.=" ".$2;
$longueur++;
}
#si on a parcouru tout le patron
if ($longueur == $#PATRON + 1) {
#on ajoute le terme (donc le patron complet) dans un dictionnaire ; cela nous sert à compter les fréquences
$dicoPatron{$terme}++;
$nbTerme++;
}
}
}
open my $fileResu,">:encoding(UTF-8)","NOMPREPNOMPREP-3210.txt";
print $fileResu "$nbTerme éléments trouvés\n";
#on parcourt le dictionnaire contenant toutes les expressions trouvées avec le patron et on l'écrit dans le fichier de sortie
foreach my $patron (sort {$dicoPatron{$b} <=> $dicoPatron{$a} } keys %dicoPatron) {
print $fileResu "$dicoPatron{$patron}\t$patron\n";
}
close($fileResu);
Pour télécharger le script Perl : Script Perl
Script Python commenté
#pour lancer le script depuis le terminal:
#python3 bao3.py fichier_tt patron
#exemple de patron : ADJ NOM
from typing import List
import re
import sys
#on définit une fonction pour extraire le patron du fichier d'entrée
def extract(corpus_file: str, output_file, patron: List[str]):
#on crée un buffer: c'est une liste contenant des tuples de couples forme, POS.
#à mesure que l'on avance dans la lecture du fichier d'entrée, le buffer se vide d'une ligne et en ajoute une nouvelle. Cela permet de ne pas lire tout le fichier d'un coup,
#mais aussi de s'assurer de ne pas passer à côté de patrons
#pour ne pas avoir d'erreur au début du programme (lorsqu'on videra le buffer), on remplit la liste avec autant de tuples qu'il y a d'élément dans le patron
buf = [("---", "---")] * len(patron)
#on ouvre le fichier d'entrée
with open(corpus_file) as corpus:
#on crée un dictionnaire pour compter les fréquences
frequences = {}
#à chaque nouvelle ligne qu'on lit
for line in corpus:
#on enlève le premier tuple du buffer
buf.pop(0)
#on cherche grâce à une expression régulière la forme et le POS
match = re.match('<element><data type="type">([^<]+?)</data><data type="lemma">[^<]+?</data><data type="string">([^<]+?)</data></element>', line)
if match:
#s'il y a match, on récupère le POS et la forme et on les ajoute sous forme de tuple au buffer
tag = match.group(1)
forme = match.group(2)
buf.append((tag,forme))
#sinon, cela signifie qu'on change de ligne: dans ce cas on réinitialise le buffer
else:
buf = [("---", "---")] * len(patron)
ok = True
terme = ""
#on parcourt le patron
for i, gat in enumerate(patron):
#si le patron correspond au premier élément du tuple dans le buffer
if gat == buf[i][0]:
#alors on stocke le deuxième élément du tuple (la forme du mot)
terme = terme + buf[i][1] + f"/{gat} "
#sinon, on sort de la boucle
else:
ok = False
#si le premier élément du buffer correspond bien au patron
if ok:
#on vérifie si l'expression est déjà dans notre dictionnaire
#si non, on l'ajoute au dictionnaire et on lui associe la fréquence 1
if terme not in frequences:
frequences[terme] = 1
#si oui, on incrémente sa fréquence
else:
frequences[terme] += 1
#une fois qu'on a tout parcouru, on tri le dictionnaire par ordre croissant de fréquence
for exp in sorted(frequences, key=frequences.get, reverse=True):
#et on écrit dans le fichier de sortie la fréquence et l'expression
output_file.write(str(frequences[exp]) +" "+ str(exp) + "\n")
if __name__ == "__main__":
#le nom du fichier d'entrée est le premier argument
corpus_file = sys.argv[1]
#le patron correspond aux arguments suivants (donc à partir du deuxième)
patron = sys.argv[2:]
#pour pouvoir mettre le nom du patron dans le nom du fichier de sortie
#on récupère dans une string les éléments du patron (sans espace)
name = ""
for pos in patron:
name += pos
#on ouvre le fichier de sortie et on appelle la fonction
output_file = open(f"{name}-3210.txt", "w", encoding="utf-8")
extract(corpus_file, output_file, patron)
output_file.close()
Pour télécharger le script Python : Script Python
Pour télécharger le fichier XSL (extrait les quatre patrons obligatoires) : Fichier XSLT
Pour télécharger le fichier XQuery (extrait les quatre patrons obligatoires) : Fichier XQuery
On obtient en sortie six fichiers TXT par rubrique (un pour chaque patron). Ils contiennent toutes les occurrences du patron et leur fréquence:
- Pour la rubrique International (3210) : ADJ NOM NOM ADJNOM NAM NAMNOM PREP NAMNOM PREP NOM PREPVERBE DET NOM
- Pour la rubrique Economie (3234): ADJ NOMNOM ADJNAM PREP NOMVERBE NUM NOMNOM PREP NOM PREPVERBE DET NOM
- Pour la rubrique Idées (3232): ADJ NOMNOM ADJNAM PREP NOMNOM NAM NAMNOM PREP NOM PREPVERBE DET NOM
On peut maintenant faire une comparaison des patrons entre les rubriques.
En ce qui concerne le patron ADJ NOM, on voit que les bigrammes les plus fréquents sont complètement différents selon la rubrique.
- International: D'une part, on voit que la vie politique des différents pays n'a pas été perturbée par la pandémie puisqu'il y a bien eu des élections, comme le montrent les expressions "nouveau président" et "ancien président". D'autre part, le contexte de pandémie a donné naissance à des mouvements contestaires face aux restrictions sanitaires, notamment en Europe. C'est ce que montre l'expression "nouvelles manifestations". L'Afrique semble avoir été particulièrement touchée car une série d'articles s'intule "L'Afrique face à sa deuxième vague".
- Economie: L'économie a été particulièrement affectée par le contexte de crise sanitaire. On le voit à travers les expressions "plan de relance" et "pleine crise". L'expression "grande entreprise" semble plus neutre. Cependant, si l'on va voir en contexte, il s'agit souvent de féminisation de postes de dirigeant dans ce type d'entreprise ou de leur impact sur l'environnement. L'économie est donc indissociable des problématiques de société actuelles en France.
- Idées: Cette rubrique semble complètement détachée du contexte de pandémie. "collectif d'universitaires", "grande école" et "dernier ouvrage" font partie des séquences ADJ NOM les plus fréquentes. Elles semblent montrer que la rubrique Idées est plutôt culturelle et liée au milieu universitaire. Il s'agit souvent de relayer des revendications ou des opinions sur des problèmes sociétaux.
En ce qui concerne le patron NOM ADJ, les rubriques sont beaucoup plus homogènes. On retrouve dans les trois le bigramme "crise sanitaire". Dans la rubrique International, les autres expressions fréquentes sont spécifiques au contexte diplomatique. On y retrouve notamment "Union européenne", "président Américain", "Commission Européenne" ou encore "affaires étrangères". Les rubriques Idées et Economie sont étonnamment assez proches. On y retrouve dans les deux cas l'expression "passe sanitaire", qui a eu un impact parfois négatif sur l'économie (notamment les secteurs de la restauration et du tourisme) mais a aussi donné lieu à des mouvements de contestation sociale importants. On retrouve de même l'expression "transition écologique", qui représente à la fois un défi pour les entreprises et un sujet de société important, donnant lieu à de nombreux débats.
En ce qui concerne le patron NOM PREP NOM PREP, il aurait fallu faire un patron un peu plus long pour être exploitable. Bien souvent, l'expression est tronquée et on ne peut pas savoir de quoi il s'agit. On trouve ainsi dans les patrons les plus fréquents "rédacteur en chef au", "dizaines de milliers de" ou encore "projet de loi de".
En ce qui concerne le patron VER DET NOM, on voit là encore se dessiner les spécificités de chaque sujet.
- International: Le patron est assez difficile à analyser dans cette rubrique car il s'agit d'expressions très vagues et fréquentes. On peut cependant remarquer qu'il y est souvent question de dynamiques de pouvoir et de gouvernements ("former un gouvernement", "quitter le pouvoir", "prendre le contrôle", "reprendre le contrôle", "pris le pouvoir"...)
- Economie: On retrouve des verbes propre à l'économie comme "d’annuler la dette", "répercuter la hausse" ou "financer le plan". On voit aussi que les intervenants dans cette rubrique sont avant tout des journalistes et des juristes ("explique le journaliste", "explique le juriste"). Le verbe "expliquer" semble d'ailleurs indiquer qu'il s'agit d'analyse objectives et factuelles.
- Idées: Dans cette rubrique, les intervenants sont principalement des professeurs, sociologues, philosophes et politistes. L'emploi du verbe "estimer" tend à démontrer que ces spécialistes font une analyse subjective du sujet. Cela prouve que la rubrique Idées est avant tout une rubrique d'opinion.
Enfin concernant les patrons facultatifs:
- International: Le patron NOM NAM NAM contient essentiellement des noms de chefs d'Etat. Etonnamment, la personnalité la plus citée est Kaïs Saïed, le président tunisien, devant Joe Biden, Angela Merkel et Jair Bolsonaro. Grâce au patron NOM PREP NAM, on voit se dessiner les événements majeurs de l'année 2021. Ceux-ci sont souvent tronqués car le patron n'est pas complet (il aurait fallu choisir NOM PRE NAM NAM), mais on réussi malgré tout à les reconstituer: "jours de Joe", "meurtre de George", "l’Etat de New", "gouvernement de Boris"...
- Economie: Les patrons choisis confirment la spécificité de cette rubrique. On y retrouve de nombreuses données chiffrées grâce au patron DET NUM NOM. On y voit aussi l'omniprésence de la pandémie avec des expression telles que "pandémie de Covid-19", "l’épidémie de Covid-19", "crise du Covid-19" dans le patron NAM PREP NOM.
- Idées: Les patrons choisis montrent que la rubrique Idées est très hétérogène. Dans le patron NOM NAM NAM, on voit apparaît des personnalités provenant de milieux très différents: chefs d'Etat, économistes, botanistes, géographes, sociologues, avocats, anthropologues...
PARTIE 2: EXTRATION DE RELATIONS DE DEPENDANCE
#!/usr/bin/perl
#----------------------------------------------------------------------------------
# Pour lancer le script dans le terminal:
# perl bao3-p2.pl sortie-up-rubrique.xml "relation" > fichier_sortie.txt
# En entrée : sortie UDPIPE formatée en XML + une relation syntaxique
# En sortie la liste triée des couples Gouv,Dep en relation
#----------------------------------------------------------------------------------
use strict;
use utf8;
binmode STDOUT, ':utf8';
#-------------------------------------------------------------------------------------
#
# my $rep="$ARGV[0]";
#la relation à extraire est le deuxième argument
my $relation="$ARGV[1]";
#on crée une table de hachage pour y stocker les couples dépendant-gouverneur et leur fréquence
my %dicoRelation=();
#-------------------------------------------------------------------------------------
# on découpe le texte par phrase (liste d'items annotés et potentiellement dépendants)
$/="</p>";
#on ouvre le fichier d'entrée (premier argument)
open my $IN ,"<:encoding(utf8)","$ARGV[0]";
#tant qu'on n'a pas fini de lire le fichier
while (my $phrase=<$IN>) {
#-------------------------------------------------------------------------------------
# on traite chaque "paragraphe" en le decoupant sur "items" (grâce au retour à la ligne). La longueur de la liste est donc égale au nombre de tokens dans la phrase.
my @LIGNES=split(/\n/,$phrase);
#on crée un indice qui sera incrémenté tant qu'il sera inférieur ou égal à la longueur de la liste LIGNES
for (my $i=0;$i<=$#LIGNES;$i++) {
# si la ligne lue contient la relation, on ira chercher le dep puis le gouv
if ($LIGNES[$i]=~/<item><a>([^<]+)<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>([^<]+)<\/a><a>[^<]*$relation[^<]*<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/i) {
#on stocke le POS du dep, le POS du gouv et la forme du dep
my $posDep=$1;
my $posGouv=$3;
my $formeDep=$2;
# on cherche maintenant la forme du gouv: soit il est avant le dep, soit il est après
if ($posDep > $posGouv) {
for (my $k=0;$k<$i;$k++) {
#si la ligne k contient le POS du gouverneur dans sa première balise <a>
if ($LIGNES[$k]=~/<item><a>$posGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
#on stocke la forme
my $formeGouv=$1;
#on ajoute la forme du gouverneur et du dépendant à la table de hachage
$dicoRelation{"$formeGouv $formeDep"}++;
}
}
}
#idem si le gouverneur est après le dep
else {
for (my $k=$i+1;$k<=$#LIGNES;$k++) {
if ($LIGNES[$k]=~/<item><a>$posGouv<\/a><a>([^<]+)<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><a>[^<]+<\/a><\/item>/) {
my $formeGouv=$1;
$dicoRelation{"$formeGouv $formeDep"}++;
}
}
}
}
}
}
close ($IN);
# on imprime la liste des couples Gouv,Dep et leur fréquence...
foreach my $relation (sort {$dicoRelation{$b}<=>$dicoRelation{$a}} (keys %dicoRelation)) {
print "$relation\t$dicoRelation{$relation}\n";
}
Pour télécharger le script Perl : Script Perl
Pour télécharger le fichier XSL : Fichier XSLT
Pour télécharger le fichier XSL : Fichier XQuery
On obtient en sortie un fichier par rubrique et par relation:
- Pour la rubrique International (3210) : OBJNSUBJ
- Pour la rubrique Economie (3234): OBJNSUBJ
- Pour la rubrique Idées (3232): OBJNSUBJ
En ce qui concerne la relation NSUBJ, les résultats sont cohérents avec ce qui a été dit précédemment. Dans la rubrique International, on trouve de nombreuses expressions liées au pouvoir
et aux gouvernements comme "président doit", "ministre doit", "ministre estime" ou "gouvernement appelé". D'autres expressions se réfèrent à des événements tragiques
("personnes tuées", "personnes mortes", "bilan s'alourdit"). On peut supposer qu'il s'agit des victimes de la pandémie ou de conflits. Enfin, on retrouve dans les paires de mots les plus fréquents "président russe" et "Etats-Unis vont". Cela semble montrer l'escalade des tensions entre ces deux puissances au cours de l'année.
Dans la rubrique Economie, on trouve des paires de mots très générales qui ne sont pas réellement spécifiques à l'année 2021 ("qui fait", "qui veut", "il s'agit"...). On peut cependant
déceler l'arrivée du vaccin contre la Covid-19 et les questions que cela a posé sur le lieu de travail, à travers des expressions comme "l'équipe vaccinée".
Enfin dans la rubrique Idées, on remarque l'importance de l'obligation ("France doit", "qui doit", "communauté doit", "combat doit", "l'Europe doit"...).
Cela confirme encore une fois que les participants à cette rubrique donnent leur opinion et expriment leur point de vue
sur ce qui devrait être fait dans le pays ou dans le monde pour répondre à certains problèmes.
L'expression la plus fréquente est "dessin signé", ce qui indique peut-être la présence d'illustrations ou de caricatures dans le journal.
En ce qui concerne la relation OBJ, on retrouve là encore des observations similaires. Dans la rubrique International, il y a de nombreuses personnalités politiques
("Vladimir Poutine, "l'opposant Alexeï", "der Leyen"...). On y voit également des problématiques internationales comme l'environnement ("réduire émissions"), la pandémie ("se vacciner")
ou ce que je suppose être la crise migratoire ("en mer", "mer migrants").
La rubrique économie comprend essentiellement des noms de journalistes : "observe Philippe" se réfère à Philippe Escande, éditorialiste économique au Monde, "observe Laurence" à
Laurence Girard, et "observe Jean-Michel" à Jean-Michel Bezat. Les autres expressions fréquentes se réfèrent au contexte économique difficile suite à la pandémie ("relancer l'économie",
"supprimer postes", "lève millions").
La rubrique Idées apporte peu d'éléments nouveaux par rapport aux patrons. On y voit la diversité des intervenants (philosophes, sociologues, professeurs, économistes,
politistes). Les autres paires de mots fréquentes sont très générales : "faire face", "mettre fin", "jouer rôle"...