extraction de patrons BAO3

Description

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.


Première partie extraire les patrons

les patrons à extraire pour cette bao3 :
ADJ NOM
NOM ADJ
DET NOM PRP DET NOM
NOM PRP NOM PRP
NOM PRP NOM
VER DET NOM

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


patron-perl
                                
# 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.


patron python
                                    
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.


Résultats

Commande Script
ex : perl extract-patrons.pl ../../../bao2/perl/sortiexml-slurp_TT_3246.txt.xml 3246 NOM ADJVoir le script perl en entier
ex : python3 extract-patrons.py ../../../bao2/perl/sortiexml-slurp_TT_3246.txt.xml 3246 NOM ADJ Voir le script python en entier
----------------------------------------------------------------------------Voir le code Xslt en entier
----------------------------------------------------------------------------Voir le code Xquery en entier

Deuxième partie extraire les dépendances

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é.

Résultats

Commande Script
ex : perl extract-relation-udpipe.pl ../sortieudpipe-slurp_3246.txt.xml nsubjVoir le script perl en entier
ex : python3 extract-obj.py ../sortieudpipe-slurp_3246.txt.xml Voir le script python en entier
-----------------------------------------------------------------------Voir le code Xslt en entier
-----------------------------------------------------------------------Voir le code Xquery en entier
Rubrique 3210 : International
Rubrique 3244 : Planète
Rubrique 3246 : Culture