Prendre en main le traitement d'une collection de documents avec le langage python, et illustrer empiriquement la loi de Zipf.
On considère la collection de documents CACM qui contient les titres, auteurs et résumés (lorsque ces derniers sont disponibles) d'un ensemble d'articles scientifiques, ainsi qu'un ensemble de requêtes et de jugements de pertinence. L'ensemble des documents est disponible à l'adresse suivante: http://ir.dcs.gla.ac.uk/resources/test_collections/cacm/ Vous rapatriez le fichier tar.gz et vous le décompressez dans votre répertoire de travail.
(En cas de panne, vous pouvez accéder par : http://mrim.imag.fr/User/philippe.mulhem/RICM4/cacm.tar.gz)
Le répertoire comprend 6 fichiers, cacm.all, cite.info, common_words, qrels.text, query.text et README. Le README contient des informations sur les fichiers précédents.
La collection contient 3204 documents, dont le texte est disponible dans le fichier cacm.all . Dans ce fichier texte, chaque document est séparé par une balise .I (suivie de l'identificant du document) qui contient le titre du documents (balise .T), les auteurs (balise .A), la date de publication (balise .B), l'abstract (balise .W), des mots-clefs (balise .K) ainsi que d'autres informations que nous ignorerons (en particulier des informations par rapport aux articles cités dans la balise .X). Certaines informations (par exemple abstract ou mots-clefs) peuvent ne pas être disponibles pour certains documents.
Extrait du fichier cacm.all
.I 39 .T The Secant Method for Simultaneous Nonlinear Equations .W A procedure for the simultaneous solution of a system of not-necessarily-linear equations, a generalization of the secant method for a single function of one variable, is given. .B CACM December, 1959 .A Wolfe, P. .N CA591203 JB March 22, 1978 3:39 PM .X 39 4 39 16 5 39 39 5 39 39 5 39 39 5 39
Pour chaque document qui commence par une balise .I, la fonction python suivante extrait les contenu des balises .T, .A, .W et .B en les mettant dans un fichier portant le nom CACM-XX où XX est le numéro associé à la balise .I :
def ExtractionDesFichiers(infile,outpath): fileHandler = open (infile, "r") debut = True while True: line = fileHandler.readline() if not line : break; if line[0:2] == '.I': if not debut: f.close() debut = False a,b = line.split(" ") print "processing file CACM-"+b[:-1] f = open(outpath+"CACM-"+b[:-1],"w+") if line[:-1] == '.T' or line[:-1] == '.A' or line[:-1] == '.W' or line[:-1] == '.B': out = True while out: line = fileHandler.readline() if not line : break; if line[:-1] == '.N' or line[:-1] == '.X' or line[:-1] == '.K' or line[:-1] == '.I': out = False break elif line[:-1] != '.T' and line[:-1] != '.A' and line[:-1] != '.W' and line[:-1] != '.B': f.write(line[:-1]+"\n") fileHandler.close()
Cette fonction a deux paramètres (des chaînes de caractères) : infile le nom de fichier en entrée (cacm.all), et outpath le nom du répertoire de sortie dans lequel un fichier est créé par document de la collection.
Appel de la fonction dans du code python (en supposant outpathname existant avant de lancer le code):
ExtractionDesFichiers(inpfilename,outpathname)
Vous pouvez rapatrier ce code (python 2.x split_cacm.py.zip, python 3.X en split_cacm3.zip).
Les autres fichiers fournis ne nous servirons pas pour cette séance, mais j'en donne quelques détails. Le fichier query.text contient le texte des requêtes. Chaque requête est précédée d'une balise .I (suivi de l'identifiant de la requête), le texte de la requête est contenu dans la balise .W, la balise .N donne l'auteur de la requête (à ignorer). Certaines requêtes sont données avec une balise .A qui représente des auteurs spécifiques pour les articles à renvoyer. Le fichier qrels.text contient les identifiants des documents pertinents pour les requêtes. Chaque ligne donne un couple d'idientifiants (requête, document pertinent pour la requête). Ce fichier ne nous sera pas utile. Enfin, le fichier common_words contient les mots fréquents de la collection, et nous servira de stop-list dans les prochains TPs. Le fichier cite.info contient des informations relatives au codage des citations (balise .X) dans le fichier cacm.all. Ce fichier est ignoré dans la suite.
Le langage python est un langage interprété fortement typé. Il prend en charge les expressions régulières avec le module re (regular expression). Vous pouvez trouver une référence à python en https://docs.python.org/2.7/. Tous les exemples dans la suite sont en python 2.7, mais rien ne vous empêche de passer à des versions ultérieures. Dans ce cas, la grande différence est qu'en 3.x on utilise print(…) et en 2.x print …
* Les structures de contrôles sont les mêmes qu'en langage C avec syntaxe un peu différente.
* Le mot-clef def permet de définir des fonctions en python: ⇒ exemple de fonction de la fonction FonctionTest sans paramètre
def FonctionTest
⇒ Appel de fonction :
FonctionTest
* Pour ouvrir un fichier en lecture :
fich = open("Chemin/Nom de fichier", "r")
* Pour lire tout le contenu d'un fichier dans une chaîne de caractères :
chaine_lue = open_file.read()
* Pour afficher toutes les lignes d'un fichier texte en enlevant les caractères de fin de ligne et les espaces:
for line in fich: print(line.strip())
* Pour ouvrir un fichier en écriture avec effacement :
fich = open("Chemin/Nom de fichier", "w")
* Pour écrire dans un fichier et passer à la ligne :
fich.write("toto"+"\n")
* Pour fermer un fichier ouvert :
fich.close()
* Pour ouvrir un répertoire dans une variable directory (terminant par un '/' final) et lister ses fichiers, on peut utiliser :
import os ... for filename in os.listdir(directory): print "processing :"+directory+filename
* Pour découper les chaines de caractères en mots, on utilisera la librairie python nltk, qu'on installe dans un terminal en lançant la commande 'pip install –user nltk' (pour python 2.x, et “pip3” au lieu de “pip” pour les python 3.x). Pour découper les mots, on créée un : tokenizer = RegexpTokenizer('[A-Za-z]\w{1,}') qui utilise un expression régulière qui découpe en token et garde les mots commencant par une lettre, suivi d'au moins une lettre ou d'un chiffre, et les place dans le tableau words, suivant leur ordre d'apparition.
Utiisation d'un tokenizer :
from nltk.tokenize import RegexpTokenizer ... words = "This is random 23 we are going to split a apart" tokenizer = RegexpTokenizer('[A-Za-z]\w{1,}') words2 = tokenizer.tokenize(words) print (words2)
Affiche ['This', 'is', 'random', 'we', 'are', 'going', 'to', 'split', 'apart']
* Une structure de donnée très utile en python est le dictionnaire, elle va nous servir dans notre projet. Un dictionnaire python est un tableau dont les indices ne sont pas des entiers mais des chaînes de caractères.
dico={} dico["toto"]=12 print dico["toto"]
affiche 12, car 12 est la valeur stockée pour l'identifiant “toto”. Une telle structure va nous permettre de stocker le nombre d'occurrences d'un mot.
* Pour savoir si un mot stocké dans la variable word est dans le dictionnaire dico on écrit :
if word in dico: # le mot est dedans else: # le mot n'est pas dedans
On va se servir de tels tels tests lors du passage en revue des fichiers : la première fois que l'on rencontre un mot (il n'est pas dans dico) on initialise sa valeur à 1. Si le mot est dans dico, alors on incrémente de 1 son nombre d'occurrences.
* Pour afficher tous les couples clé/valeur d'un dictionnaire, on utilise :
for i in dico print i, dico[i] # affiche la clé et la valeur
* Il est possible de passer en revue les éléments d'un dictionnaire python en le triant par ses valeurs décroissantes en utilisant :
from operator import itemgetter ... for elem in reversed(sorted(dico.items(), key = itemgetter(1))): print elem #elem est un couple (clé,valeur)
Pour sortir d'un for avant la fin de toutes les boucles, même si ce n'est pas recommendé au niveau algorithmique, vous pouvez ici utiliser l'instruction :
break;
* Il ne faut pas oublier que python est très sensible aux tabulations, car il n'utilise pas de séparateurs comme les parenthèses du language C.
* NOTE IMPORTANTE : ne pas oublier de passer les mots en minuscule avant de calculer le nombre d'occurrences en utilisant :
s2 = s1.lower()