Dans cette boîte à outils, nous allons utiliser les fichiers de sortie de la bào 2, et extraire les informations pour chaque fichier par rubriques.
Sur les fichiers étiquetés par Treetagger, nous allons extraire les différents patrons. Sur les fichiers étiquetés par UDpipe et transformés en format .xml grâce au programme perl udpipe2xml-version-sans-titrevsdescription-v2.pl, nous allons extraire les dépendances.
La logique pour extraire les patrons en perl est différente de celle en python.
En perl, on parcourt chaque ligne du fichier, on vérifie si le "type" d'item correspond au premier élément du patron, si c'est le cas, on va vérifier si les prochains items correspondent à la suite du patron.
L'image ci-dessous illustre la logique du script perl quand on veut chercher le patron DET NOM PREP
# parcourir chqaue ligne du fichier, et rejetter la première ligne déjà lue
while (my $ligne=shift @LISTE) {
# variable pour le groupe de formes qui correpond au patron
my $terme="";
# si la ligne contenue dans $ligne correspond au premier du patron $PATRON[0]
# impoortant ! parfois l'annotation de type est par ex VER:pper, dans ce cas là le regex /$PATRON[0]/ seul ne suiffit pas
if ($ligne=~/<element><data type="type">($PATRON[0]|$PATRON[0]:[^<]+?)<\/data><data type="lemma">[^<]+?<\/data><data type="string">([^<]+?)<\/data><\/element>/) {
# récupérer cet premier élément qui correspond
$terme=$terme.$2;
my $longueur=1;
my $indice=1;
# alors il faut que je lise autant de ligne qu'il y a dans le patron et tester chaque terme du patron...
# décalage d'1 entre l'index de liste et l'index de la liste $PATRON pour la comparaison, pour que on ne compare exactement le même nombre que la longeur de la liste $PATRONS d'éléments
while (($LISTE[$indice-1]=~/<element><data type="type">($PATRON[$indice]|$PATRON[$indice]:[^<]+?)<\/data><data type="lemma">[^<]+?<\/data><data type="string">([^<]+?)<\/data><\/element>/) and ($indice <= $#PATRON)) {
$indice++;
# récupérer la forme de cet élément qui correspond à un élément de patron
$terme.=" ".$2;
$longueur++;
}
# un dernier contrôle pour vérifier si le nombre d'élement dans le groupe de formes
# qui est censé correspondre au patron égale au nombre d'élément dans la liste $PATRON
# on enregistre le groupe de formes qui coresspond au patron dans la liste dicoPatron
if ($longueur == $#PATRON + 1) {
$dicoPatron{$terme}++;
$nbTerme++;
}
}
}
En python, on crée une variable liste "buf" qui a la même longueur que celle du patron cherché, et en parcourant la liste et arrivant à une nouvelle ligne, on utilise la fonction pop pour enlever le premier élément de la liste, et ajouter (append) le "type" de la nouvelle ligne dans cette liste buf. Comme cela la liste buf garde toujours la même longueur que celle du patron. Pour chaque ligne on compare directement la liste "buf" avec la liste du patron.
L'image ci-dessous illustre la logique du script python avec l'exemple patron à chercher DET NOM PREP.
def extract(corpus_file: str, patron: List[str]):
dic_bufs = dict()
# création de liste buf
buf = [("---", "---")] * len(patron)
with open(corpus_file) as corpus:
#parcourir toutes les lignes du fichier
for line in corpus:
# enlever le premier élément dans la liste buf
buf.pop(0)
# on ne travaille que sur les lignes qui concernent le contenu d'un item comme celui de ci-dessous, non pas les autres balises
match = re.match('<element><data type="type">([^<]+?)</data><data type="lemma">[^<]+?</data><data type="string">([^<]+?)</data></element>', line)
if match:
tag = match.group(1)
forme = match.group(2)
# on ajoute le match de cette nouvelle ligne dans le buf pour que ca garde toujours la même longueur que celle de la liste patron
buf.append((tag,forme))
else:
buf = [("---", "---")] * len(patron)
ok = True
terme = ""
# pour chaque ligne on compare le buf et la liste de patron avec un variable ok
for i, gat in enumerate(patron):
if buf[i][0].startswith(gat):
terme = terme + buf[i][1] + " "
else:
ok = False
# si ok, cela veut dire que le buf correspond au patron, alors qu'on l'ajoute dans le patron
if ok:
if terme not in dic_bufs:
dic_bufs[terme] = 1
else:
dic_bufs[terme] += 1
return dic_bufs
Nous avons aussi utilisé XSLT et XQUERY pour extraire les patrons.
Commande | Script |
---|---|
ex : perl extract-patrons.pl ../../../bao2/perl/sortiexml-slurp_TT_3246.txt.xml 3246 NOM ADJ | |
ex : python3 extract-patrons.py ../../../bao2/perl/sortiexml-slurp_TT_3246.txt.xml 3246 NOM ADJ | |
---------------------------------------------------------------------------- | |
---------------------------------------------------------------------------- |
Dans cette partie, nous allons utiliser les fichiers étiquetés par UDpipe puis transformés en XML. Pour extraire un gouverneur et un dépendant reliés par une relation, il faut utiliser le contenu à l'intérieur de la 7ème balise <a> dans les balises du dépendant qui indique l'index du gouverneur.
Le script perl et le script Python ne font pas l'extraction de dépendance de la même manière. En Perl, on découpe d'abord le contenu du fichier par phrase (séparer le texte grâce à la balise <p>), puis on parcourt chaque phrase, si on trouve un dépendant ayant une relation ciblée à la 8ème balise a, alors qu'on va parcourir chaque élément de la phrase jusqu'à ce que l'index d'élément corresponde à l'index du gouverneur indiqué dans les balises du dépendant.
Le bout de code ci-dessous explique le déroulement de script Perl.
# le texte découpé en phrase
while (my $phrase=<$IN>) {
#-------------------------------------------------------------------------------------
# on traite chaque "paragraphe" en le decoupant "items"
my @LIGNES=split(/\n/,$phrase);
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) {
#récuoérer la position de dépendant
my $posDep=$1;
# récupérer la position de gouverneur
my $posGouv=$3;
# récupérer la dorme de dépendant
my $formeDep=$2;
# comparer la position de dépendant et de gouverneur, si me dépendant apprait après le gouverneur
if ($posDep > $posGouv) {
# déclencher une boucle qui parcourt tous les éléments avant l'index de dépendant
for (my $k=0;$k<$i;$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;
# enregistrer les formes de gouverneur et de dépendant dans un dictionnaire
$dicoRelation{"$formeGouv --> $formeDep"}++;
}
}
}
# le gouverneur apparait après le dépendant
else {
# déclencher un e boucle qui cherche à partir de l'index du dépendant jusqu'à la fin de ce paragraphe pour matcher le gouverneur
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;
# enregistrer les formes de gouverneur et de dépendant dans un dictionnaire
$dicoRelation{"$formeGouv --> $formeDep"}++;
}
}
}
}
}
}
En python, nous avons utilisé une autre méthode. Pour chaque phrase, on crée deux variables importantes, la première est un dictionnaire qui enregistre pour chaque élément son index(clé) et son lemme(valeur), la deuxième variable est une liste de dépendants qui enregistre, pour chaque item ayant une relation ciblée, son lemme et l'index du gouverneur (lemme, head). A la fin de la phrase, on parcourt la liste de dépendants et trouve, pour chaque dépendant, le lemme de son gouverneur dans le dictionnaire grâce à l'index du gouverneur.
l'extrait de code explique le déroulement de script python
# dictionnaire qui enregistre l'index et le lemme pour chaque item
sent_buf = {}
# liste distincte qui enregistre pour chaque item ayant une indication de relation ciblés (8ème balise a ) et l'index de son gouverneur (7ème balise a)
obj_buf = []
couples = set()
dic_frequence = dict()
# parcourir chaque ligne du fichier
for line in Path(fic).read_text().split("\n"):
# le début de phrase
if line.startswith("<item>"):
# matcher tous les balises a
fields = re.findall("<a>([^<]+)</a>", line)
# nommer les attributs avec les contenus des balises a matchés
idx, word, lemma, tag, _, _, head, rel, _, _ = fields
# pour chaque élément, le dictionnaire enregistre l'index comme clé, et le lemme comme valeur
sent_buf[idx] = lemma
# si un élément est un dépendant en ayant l'indication de la relation ciblée.
if rel == 'obj':
# on enregistre l'index de gouverneur et le lemme de ce dépendant dans la liste obj_buf
obj_buf.append((lemma, head))
# à la fin de phrase
if line == "</p>":
# on parcourt la liste obj_buf
for obj_lemma, head in obj_buf:
# trouver le lemme de gouverneur dans le dictionnaire, et mettre dans le variable couple le lemme de gouverneur et de dépendant
couples.add((f"{sent_buf[head]}", f"{obj_lemma}"))
# enregistrer la fréquence de la dépendance pour un affichage plus clair
cle = str(sent_buf[head] + '->' + obj_lemma)
if cle not in dic_frequence :
dic_frequence[cle] = 1
else :
dic_frequence[cle] += 1
# vider le dictionnaire et la liste buf pour la phrase prochaine
obj_buf = []
sent_buf = {}
Pour extraire les éléments en dépendances, nous pouvons aussi utiliser XSLT et XQuery qu'on a réalisés dans le cadre du cours dfocument structuré.
Commande | Script |
---|---|
ex : perl extract-relation-udpipe.pl ../sortieudpipe-slurp_3246.txt.xml nsubj | |
ex : python3 extract-obj.py ../sortieudpipe-slurp_3246.txt.xml | |
----------------------------------------------------------------------- | |
----------------------------------------------------------------------- |