====== 1. But du TP ======
Prendre en main le traitement d'une collection de documents avec le langage python, et illustrer empiriquement la loi de Zipf.
====== 2. Déroulement ======
===== Travail attendu =====
- Rapatriement et préparation des données : Vous devez commencer par rapatrier les données CACM et le code fourni (cf. texte "La collection utilisée..." ci-dessous) puis modifier le programme cacm_splt.py qui contient la fonction *ExtractionDesFichiers*, afin de découper le fichier cacm.all rapatriée en une liste de fichiers dans un **répertoire cible**, par exemple "/....../collection/", que vous choisissez (il faut créer ce répertoire avant de lancer le code). Vérifier ensuite sur un ou deux fichiers exemples que le résultat est correct.
- Écrire un programme python **tokenize_cacm.py**, qui ouvre un à un les fichiers CACM-XX du répertoire "collection/" rempli à l'étape précédente (le répertoire est passé en paramètre) et qui, pour chaque fichier, ne garde que les mots qui commencent par une lettre et qui ne contiennnent ensuite que des lettres ou des chiffres, puis qui écrit le résultat (**en minuscule, un mot par ligne**) dans un fichier portant le même nom que celui de départ en y ajoutant l'extension **.tok**, dans un nouveau répertoire (nom en paramètre), par exemple "collection_tokens/", à créer avant de lancer votre programme. Utiliser ici la fonction Tokenizer décrite plus bas (partie 4).
- Écrire un programme python zipf.py qui, à partir de tous les fichiers collection_tokens/CACM-XX.tok créés à l'étape précédente : 1. calcule la fréquence d'apparition de tous les termes de la collection (utiliser un dictionnaire python avec la chaîne de caractères comme clé et le nombre d'occurrences comme valeur) et les affiche, 2. les affiche par nombre d'apparition décroissant (affichage long, mais c'est normal),
- Modifier le code pour n'afficher QUE les 10 termes les plus fréquents dans l'ordre décroissant, puis ajouter a) l'affichage du nombre d'occurrences de ces mots, b) la taille du vocabulaire (//My// du cours), et c) la valeur lambda théorique calculée (cf. le cours). (valeur de lambda attendue : +/- 18880)
- Préparation du prochain TP : générer un dictionnaire python qui contient comme clé le nom de fichier, et comme valeur un autre dictionnaire qui contient, comme pour votre code Zipf, les mots qui apparaissent dans ce fichier avec leur nombre d'occurrence. Afficher ce dictionnaire pour vérifier les calculs.
===== La collection utilisée et son traitement préliminaire =====
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.
====== 4. Détails techniques (à lire avant de commencer) ======
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()