from re import findall
filename = input('Saisissez un fichier conll:\n> ')
patterns = input('Saisissez les patrons (séparés par un /):\n> ')
Nous demandons à l'utilisateur de saisir le nom du fichier à traiter et les patrons à extraire.
Variables générées : filename (nom du fichier d'entrée), patterns (patrons saisis par l'utilisateur)
# on récupère les patrons saisis
patterns_list_input = patterns.split('/')
# on nettoie les saisies pour corriger les potentielles erreurs de frappe
patterns_list = list(map(lambda x: findall(r'\w+', x), patterns_list_input))
# on initialise les deux dictionnaires
patterns_dico = {}
patrons_dico = {}
for pattern in patterns_list:
patrons_dico[' '.join(pattern)] = []
try:
# s'il existe déjà un patron qui commence par le même POS tag, on met à jour la liste
patterns_dico[pattern[0]].append(pattern)
except KeyError:
# sinon on crée une entrée
patterns_dico[pattern[0]] = [pattern]
Cette étape est consacrée à reformuler la saisie de l'utilisateur pour que le programme puisse gérer plusieurs patrons en même temps. L'astuce est de créer un dictionnaire permettant de réindexer/regrouper les patrons par leur premier POS tag, appelé patterns_dico.
D'abord nous nettoyons la saisie avec une expression régulière, au cas où l'utilisateur a mis des espaces en trop entre les POS tag, par exemple. Après les deux premières lignes, nous avons une liste de patron, chacun présenté sous forme d'une liste également. Ensuite nous initialisons patterns_dico. Nous prenons le premier POS tag de chaque patron et nous regardons s'il existe déjà dans les clés du patterns_dico. Si oui, nous ajoutons une valeur à l'entrée correspondante, sinon nous créons une nouvelle entrée.
Pour gérer les résultats d'extraction, nous utilisons également un dictionnaire patrons_dico. Dans ce dictionnaire, chaque patron prend la place de la clé et les expressions trouvées seront la valeur.
Variables générées : patterns_doc (dictionnaire de patrons), patrons_dico (dictionnaire de résultats)
# on récupère le contenu du fichier
with open(filename, 'r', encoding='utf-8', newline='\n') as fichier:
contenu = fichier.read()
# on sépare les phrases
sentence_list = contenu.split('\n\n')
sentence_list.pop()
Nous lisons le contenu du fichier et nous le séparons en phrase. Dans un fichier conll, les phrases sont séparées avec une suite de deux saut de ligne. Par contre il y a aussi un double saut de ligne à la fin du fichier, il faut donc l'enlever pour pas qu'il gêne les traitements suivants. A l'issu de cette étape, nous avons une liste des phrases.
Variables générées : sentence_list (liste de phrases)
# on traite phrase par phrase
for sentence in sentence_list:
lines = sentence.split('\n')
# on filtre les commentaires
words = list(filter(lambda x: x and x.startswith("#") is False, lines))
# on ignore les phrases à un token
if len(words) == 1:
continue
# on génère les listes de tokens et de tags
tokens = []
tags = []
# on lit chaque ligne de tableau et on récupère les champs qui nous intéressent
for word in words:
champs = word.split('\t')
tokens.append(champs[1])
tags.append(champs[3])
# on parcourt la liste de tags
for i, tag in enumerate(tags):
# pour chaque tag, on regarde s'il est présent dans la liste des POS tags initiaux des patrons
if tag in patterns_dico.keys():
# si oui on récupère les patrons correspondants
pat_list = patterns_dico[tag]
# pour chaque patron on calcule sa longueur et on choppe une suite de même longueur dans tags
for ele in pat_list:
pat_leng = len(ele)
# si le patron est identique à la suite, on récupère le n-gram et on l'ajoute dans le dictionnaire
if tags[i:i+pat_leng] == ele:
n_gram = ' '.join(tokens[i:i+pat_leng])
key = ' '.join(ele)
patrons_dico[key].append(n_gram)
Nous traitons phrase par phrase. D'abord nous filtrons les commentaires, les lignes vides et les phrases n'ayant qu'un seul token. Ensuite nous extrayons la liste de tokens et de POS tags, appelées tokens et tags. Nous regardons d'abord s'il existe une suite de POS tag identique à un des patrons cherchés, si oui nous extrayons ensuite le n-gram correspondant.
Nous prenons appui sur le dictionnaire patterns_dico généré précédemment. Nous parcourons tags, et nous regardons chaque tag s'il est présent dans la liste de clé du patterns_dico. L'idée est que si aucune des clés n'est présente dans une phrase, ce n'est pas la peine de regarder les n-grams, ainsi le temps de recherche peut être réduit ainsi que la dépense de mémoire. En revanche, si nous en trouvons dans les clés, nous prenons la liste de patrons pat_list et nous comparons un par un. Pour chaque patron, nous calculons sa longueur, nous tranchons un n-gram de la même longueur dans tags et nous comparons les deux. S'ils sont pareils, nous allons chercher le token à l'index correspondant dans tokens et nous tranchons le n-gram. Ce n-gram est finalement enregistré dans patrons_dico sous l'entrée de son patron.
# on ouvre le fichier de sortie
with open('sortie_non_trie.txt', 'w', encoding='utf-8', newline='\n') as sortie:
# on parcourt le dictionnaire
for pattern, patrons in patrons_dico.items():
for patron in patrons:
# on écrit les patrons
sortie.write(patron + '\n')
Nous écrivons d'abord le résultat tel qu'il est. C'est pour préparer un corpus pour la BAO4 om nous allons générer des graphes de mots, nous avons besoin de toutes les occurrences.
# on initialise le compteur
# à noter qu'il compte le nombre total des patrons trouvés, sans distinction entre les patterns
countresult = 0
# pour chaque entrée dans patrons_dico
for pattern, patrons in patrons_dico.items():
# on inisitalise un dictionnaire temporaire
tmp = {}
for patron in patrons:
# on compte le nombre d'occurrences de chaque patron
try:
tmp[patron] += 1
except KeyError:
tmp[patron] = 1
# on trie les entrées
tmplist = sorted(tmp.items(), key=lambda x: x[1], reverse=True)
# on met à jour le compteur
countresult += len(patrons)
# on met à jour le dictionnaire
patrons_dico[pattern] = tmplist
Ensuite nous trions et comptons les patrons extraits. Nous prenons chaque entrée dans patrons_dico et nous trions les patrons stockés dans la valeur qui sera finalement remplacé par le résultat trié.
# on écrit le résultat dans un fichier
with open('sortie_trie.txt', 'w', encoding='utf-8', newline='\n') as sortie:
sortie.write('{} éléments trouvés\n'.format(countresult))
for pattern, patrons in patrons_dico.items():
sortie.write("Type de pattern: {}\n\n".format(pattern))
tmp = list(map(lambda x: sortie.write("{0}\t{1}\n".format(x[1], x[0])), patrons))
sortie.write('\n\n')
Dernière étape, nous écrivons le contenu du patrons_dico dans un fichier de sortie. Nous indiquons le patron cherché, les expressions et leur nombre d'occurrences. Nous pouvons également paramétrer le format d'écriture pour qu'il ne garde que les informations qui nous intéressent.