Projet encadré 2
BOITES A OUTILS

BAO 3
Extraction de patrons morphosyntaxiques et de relations de dépendance

A partir des fichiers XML étiquetés avec TreeTagger, nous allons extraire des patrons morphosyntaxiques. 

Nous allons également utiliser les fichiers étiquetés avec UDpipe et reformatés en XML pour extraire des relations de dépendance car ce dernier, contrairement au fichier TreeTagger, indique la position du gouverneur et la relation syntaxique qui lie les deux tokens. 

Pour chacune des deux extractions, nous allons utiliser quatre méthodes différentes : 
- une avec Perl
- une avec Python
- une avec XQuery (via BaxeX)
- une avec des feuilles de styles XSLT.

SCRIPTS POUR L'EXTRACTION DES PATRONS MORPHOSYNTAXIQUES

LANGAGE INFORMATIQUE METHODE MODE D'EMPLOI DU LANCEMENT DU SCRIPT DANS LE TERMINAL TELECHARGEMENT DU SCRIPT ENTIER  COMMENTE
Perl Expressions régulières  perl bao3_extract_patron.pl fichier-tt.xml PATRON BAO3_EXTRACT_PATRON.PL
Python Lecture globale + expressions régulières python3 bao3_extract_patron_v3.py fichier-tt.xml PATRON1 -- PATRON2 -- PATRON3 -- ... -- PATRONn bao3_extract_patron_v3.py
Python Lecture avec un buffer + expressions régulières python3 bao3_extract_patron_v2.py fichier-tt.xml PATRON1 -- PATRON2 -- PATRON3 -- ... -- PATRONn bao3_extract_patron_v2.py
XSLT Feuille de style xsltproc feuille_style.xsl nom_corpus.xml > nom_fichier.txt NOM-PREP-NOM-PRP-TT.xsl
XQuery Requête Utilisation de BaseX NOM-PRP-NOM-PRP.xq

SCRIPTS POUR L'EXTRACTION DES RELATIONS DE DEPENDENCE

LANGAGE INFORMATIQUE MODE D'EMPLOI DU LANCEMENT DU SCRIPT DANS LE TERMINAL TELECHARGEMENT DU SCRIPT ENTIER COMMENTE
Perl perl bao3-extract-relation-udpipe.pl fichier-ud.xml nom_relation > nom_fichier_sortie.txt BAO3-EXTRACT-RELATION-UDPIPE.PL
Python python3 bao3_extract_relation_v2.py fichier-ud.xml nom_relation bao3_extract_relation_v2.py
XSLT xsltproc feuille_style.xsl fichier-ud.xml > nom_fichier.txt relation-OBJ-UD-xslt.xsl
XQuery Utilisation de BaseX extract-OBJ-udpipe.xq

DEROULEMENT GLOBAL DES SCRIPTS EN PYTHON

Les scripts entiers et commentés peuvent être téléchargés plus haut.

EXTRACTION DES PATRONS MORPHOSYNTAXIQUES

1. Lecture global du fichier et expressions régulières

Notre programme prend en entrée :
- un fichier XML étiqueté avec TreeTagger
- un ou plusieurs patrons séparés par des " -- "

Le programme prend d'abord l'intégralité des patrons (depuis le deuxième item de la liste d'arguments jusqu'à la fin) et les met dans une liste. Chaque item, y compris les "--" sont donc un élément de la liste. Par exemple pour 3 patrons rentrés (NOM ADJ -- NOM PRP NOM -- ADJ NOM), la liste va ressemble à ça :

["NOM","ADJ","--","NOM","PRP","NOM","--","ADJ","NOM"]

On fait passer cette liste dans une fonction appelée "segmentation_patrons()". Elle va segmenter en sous-liste chaque patron. Pour cela, elle va tout simplement parcourir la liste en entrée et ajouter dans une liste tampon chaque item tant qu'elle ne rencontre pas d'item "--". Quand c'est le cas, ça signifie qu'un nouveau patron commence. Elle ajoute la liste tampon à la liste des listes de patrons et elle vide la liste tampon. La fonction continue ainsi jusqu'à ce que tous les patrons aient été segmentés en sous-listes. Cette liste de listes, si on reprend l'exemple du dessus, va ressembler à ça :

[["NOM","ADJ"],["NOM","PRP","NOM"],["ADJ","NOM"]]

Pour pouvoir nommer les fichiers de résultat, on récupère : 
1. le numéro de la rubrique qui se trouve dans le nom du fichier XML
2. le patron.

Pour que le nommage des fichiers soient jolis, on fait passer la liste des listes de patrons dans une fonction appelée "pretty_names()". Elle va renvoyer une liste avec autant d'items qu'il y a de patrons et chaque item aura été formaté comme suit :

["NOM-ADJ","NOM-PRP-NOM","ADJ-NOM"]

Une fois tout ceci effectué, on commence une boucle qui fera autant de tours qu'il y a de patrons. Dans cette boucle, on ouvre un fichier en écriture, qui contiendra dans son nom le numéro de la rubrique, tt (pour TreeTagger) et le nom du patron (qui provient de la liste renvoyée par pretty_names()).

On lance la fonction "extract_patron()" sur le corpus et le patron. 

Cette fonction fait tout d'abord une copie du corpus dans "corpus_". En effet, la méthode que nous allons utiliser pour parcourir le fichier va supprimer la première ligne du fichier lorsqu'elle a fini de l'examiner. Cette méthode est pratique car elle permet de ne pas avoir à gérer indépendamment l'indice de l'élément du patron qu'on cherche à matcher et l'indice de la ligne dans laquelle on veut matcher cet élément : le premier élément du patron sera toujours la première ligne du corpus, le deuxième élément toujours la deuxième ligne, etc.

On fait donc une copie pour ne jamais avoir à modifier le corpus original. Si on modifiait le corpus original, le deuxième patron de notre liste de patrons ne pourraient rien matcher car le corpus est vide.

On initialise un booléen. 

On initialise une string vide dans laquelle on va mettre la forme de l'élément de la ligne observée si sa POS match avec le patron.  Si la POS ne match plus, le booléen deviendra "False" et on sortira de la boucle. Comme ça, si le début et la fin de notre patron match avec le corpus, mais que le milieu du patron ne match pas, on ne va pas le retenir.  On ne va, par ailleurs, pas inutilement continuer à parcourir le patron si dès le début, la ligne ne match pas avec le premier élément du patron.  

Si on finit de parcourir tout le patron et que le booléen est toujours "True", ça veut dire que tout le patron a matché et on ajoute notre string à notre fichier de résultat. On la vide ensuite pour pouvoir passer à la ligne suivante. 

On recommence la même chose pour tous les patrons.

On aura en sortie un fichier TXT par patrons. 

Avec la commande time du terminal, on peut savoir que ce programme prend environ 1 minute pour extraire 6 patrons.

2. Lecture du fichier avec un buffer + expressions régulières

La deuxième version du programme en Python prend les mêmes éléments en entrée et les fichiers de sortie sont les mêmes également.

La seule chose qui change est la façon dont on parcourt le fichier. Dans la première version, on lisait le fichier dans sa globalité et on retirait petit à petit les lignes. 

Dans la deuxième version, on ne charge pas le fichier en entier. On lit le corpus ligne par ligne. On rajoute dans un buffer de liste de tuples la POS et la forme de l'élément de chaque ligne. Le buffer fait la longueur de notre patron. On regarde ensuite si les POS contenues dans le buffer correspondent à notre patron. 

Si c'est le cas, on écrit les formes contenues dans le buffer dans notre fichier. On vide ensuite le buffer et on recommence.  

Cette méthode est beaucoup plus rapide que la première. Pour rechercher 6 patrons d'un seul coup, le programme prend environ 4 secondes. On peut savoir ça en utilisant la commande "time" dans le terminal.  Alors que le premier prend plus d'une minute.

EXTRACTION DES RELATIONS DE DEPENDANCE

Le programme en Python pour l'extraction de dépendance prend en entrée un fichier étiqueté avec UDpipe reformaté en XML et le nom d'une relation.

Le programme va utiliser deux buffers : 
- un buffer pour la phrase 
- un buffer pour les couples (lemmes du dépendant - position du gouverneur dans la phrase)

Il va parcourir notre fichier ligne par ligne. Pour chaque ligne, on va assigner au contenu qui se trouve entre les balises <a>...</a> les noms de variables suivants : 
1. idx (pour l'index du mot dans la phrase)
2. word (pour la forme du token)
3. lemma (pour le lemme du token)
4. tag (pour la Part of Speech du token)
5. _ (information qui ne nous intéresse pas)
6. _ (information qui ne nous intéresse pas)
7. position_gouv (pour la position du gouverneur dans la phrase)
8. rel (pour le nom de la relation qu'entretient ce token avec son gouverneur.

fields=re.findall("<a>([^<]+)</a>", line)
idx, word, lemma, tag, _, _, position_gouv, rel, _, _ = fields

A chaque mot, on rentre dans notre buffer de phrase (un dictionnaire) l'index du mot pour clé avec comme valeur son lemme.

sent_buf[idx] = lemma

Si le nom de la relation du mot de la ligne est celui qui nous intéresse, on met dans notre buffer de relation le couple (lemme du dépendant - position du gouverneur dans la phrase).

Dans la variable "couples" qui est un ensemble, on met le lemme du dépendant et le lemme du gouverneur qu'on a pu récupérer grâce à notre buffer de phrase et à sa position conservé dans le buffer de relation.

On écrit tous ces couples dans notre fichier en précisiant la relation. 

Ce programme prend environ 1 seconde pour une relation.

EXPLICATION DU FORMAT DE REQUETE XQUERY

EXTRACTION DES PATRONS MORPHOSYNTAXIQUES

XQuery est une langage qui permet de faire des requêtes sur des bases de données XML. 

Avant de pouvoir l'utiliser, il faut régler un problème présent dans les fichiers XML de TreeTagger. En effet, les entités nommées "&amp;" sont écrites sans le point-virgule. Parfois, on peut même trouver des "&amp;amp". On écrit un petit script en Python pour pouvoir régler ce problème. 

Dans l'exemple à télécharger plus haut (pour le patron NOM PRP NOM PRP), on va effectuer la même instruction pour tous les noeuds "element". Le double slash permet d'indiquer qu'on veut trouver ce noeud peu importe où il se trouve dans l'arboresence. Ca nous évite d'avoir à écrire tout le chemin. 

for $element in collection(corpus-annotation-tt-3214_correction_entite)//element

"let" permet d'attribuer une valeur à une variable. 

La variable "frere1" est le premier frère suivant de "element", "frere2" le deuxième frère suivant et ainsi de suite pour la longueur de tout le patron.

let $frere:=$element/following-sibling::element[1]
let $frere2:=$element/following-sibling::element[2]
let $frere3:=$element/following-sibling::element[3]

Dans l'arborescence, comme montré plus haut, les noeuds "element" ont plusieurs noeuds-fils "data". Si le premier noeud-fils "data", la POS, de l'élément sélectionné est NOM, que le premier noeud-fils "data" de "frere1" est "PRP" et ainsi de suite, on sélectionne le troisième fils "data", la forme, de chaque élément et on les joint avec un espace. 

Pour trouver un autre patron morphosyntaxique, il suffit d'ajuster le nombre de "frère" à la longueur du patron et de remplacer les XX dans les "contains(text(),"XX"))".

EXTRACTION DES RELATIONS DE DEPENDANCE

Pour chaque noeud <item> qui contient dans son huitième noeud-fils <a> (relation) "obj", son deuxième noeud-fils <a>  (forme) est mis dans la variable $depforme. Sa position (son premier noeud-fils <a>) est mis dans $positionSource. Et la position du gouverneur (septième noeud-fils <a>) est mis dans la variable $positionCible.

for $item in collection(corpus-annotation-ud-3214.udpipe)//item
where contains($item/a[8]/text(),'obj')
let $depforme:=$item/a[2]/text()
let $positionSource:=$item/a[1]
let $positionCible:=$item/a[7]

Si le gouverneur se trouve avant le dépendant, alors on cherche le gouverneur dans les frères précédents (preceding-sibling) de l'item. Sinon, dans ses frères suivants (following-sibling). On garde la forme du gouverneur dans la variable $noeudC. 

On renvoie la forme du gouverneur suivi d'une flèche et de la forme du dépendant.

Pour les autres relations, il suffit de changer le "obj" de la ligne 2 de la requête par le nom d'une autre relation. 

RESULTATS

Des quatre méthodes, les programmes en Python semblent être les plus rapides et les plus maléables. Il est très facile de changer la relation de dépendance ou le patron morphosyntaxique que l'on veut étudier, contrairement à XSLT ou XQuery où il faut réécrire la feuille de style/requête. Par ailleurs, l'automatisation du nommage des fichiers de sortie et leur type est beaucoup plus facile à gérer et nous pouvons rechercher plusieurs patrons à la fois. C'est pourquoi c'est la méthode que j'ai conservée pour les résultats suivants, bien qu'au final, les quatre méthodes donnent des résultats très similaires, voire complètement identiques en termes de données.

RUBRIQUE CINEMA

PATRON/RELATION LANGAGE DU SCRIPT UTILISE FICHIER EN ENTREE TELECHARGEMENT DU RESUTLAT
ADJ NOM Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_ADJ-NOM.txt
NOM ADJ Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_NOM-ADJ.txt
NOM KON NOM Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_NOM-KON-NOM.txt
NOM PRP NOM PRP Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_NOM-PRP-NOM-PRP.txt
VER DET NOM Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_VER-DET-NOM.txt
VER PRP DET NOM Python XML (TreeTagger) extract_patron_3476_tt_lecture_buffer_VER-PRP-DET-NOM.txt
RELATION OBJ Python UDpipe (reformaté en XML) extract_relation_3476_ud_obj.txt
RELATION NSUBJ Python UDpipe (reformaté en XML)
extract_relation_3476_ud_nsubj.txt

RUBRIQUE EUROPE

PATRON/RELATION LANGAGE DU SCRIPT UTILISE FICHIER EN ENTREE TELECHARGEMENT DU RESUTLAT
ADJ NOM Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_ADJ-NOM.txt
NOM ADJ Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_NOM-ADJ.txt
NOM KON NOM Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_NOM-KON-NOM.txt
NOM PRP NOM PRP Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_NOM-PRP-NOM-PRP.txt
VER DET NOM Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_VER-DET-NOM.txt
VER PRP DET NOM Python XML (TreeTagger) extract_patron_3214_tt_lecture_buffer_VER-PRP-DET-NOM.txt
RELATION OBJ Python UDpipe (reformaté en XML) extract_relation_3214_ud_obj.txt
RELATION NSUBJ Python UDpipe (reformaté en XML)
extract_relation_3214_ud_nsubj.txt

RUBRIQUE LIVRES

PATRON/RELATION LANGAGE DU SCRIPT UTILISE FICHIER EN ENTREE TELECHARGEMENT DU RESUTLAT
ADJ NOM Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_ADJ-NOM.txt
NOM ADJ Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_NOM-ADJ.txt
NOM KON NOM Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_NOM-KON-NOM.txt
NOM PRP NOM PRP Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_NOM-PRP-NOM-PRP.txt
VER DET NOM Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_VER-DET-NOM.txt
VER PRP DET NOM Python XML (TreeTagger) extract_patron_3260_tt_lecture_buffer_VER-PRP-DET-NOM.txt
RELATION OBJ Python UDpipe reformaté en XML extract_relation_3260_ud_obj.txt
RELATION NSUBJ Python UDpipe (reformaté en XML)
extract_relation_3260_ud_nsubj.txt