Construire l'index inversé et les autres données relatives à l'indexation d'une collection de documents fournie (cacm.zip), à rapatrier et dézipper dans votre répertoire de travail pour le projet. Vous devez également rapatrier le fichier de mots outils suivant common_words.txt. Pour cela, on a va procéder en deux étapes :
INSTALLATION DE LA LIBRAIRIE nltk : Pour découper les chaines de caractères en mots, le code fournit ci-dessous utilise 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). Cherchez comment faire pour installer une librairie python dans votre environnement.
Je fournis un programme indexer_gendico.py qui construit le vocabulaire de tous les termes qui apparaissent dans un corpus de textes qui sont dans un seul répertoire. Ce programme, à partir d'un nom de répertoire, d'un fichier de mots-outils, et du nom de fichier de sauvegarde du vocabulaire, génère le vocabulaire avec les pdf sous format idf de chacun de ces termes.
Attention: pour que ce code fonctionne dans votre répertoire, il faut mettre à jour les lignes 24 (répertoire des documents), 25 (nom du fichier des mots outils, common_words.txt), et 25 (nom du fichier d'export du vocabulaire).
Ce code ouvre chaque fichier texte du corpus, le découpe en mots, enlève les mots outils, racinise les mots restants par l'algorithme de Porter, puis détermine tous termes qui apparaissent au moins une fois, et stock chaque terme et son pdf sous form d'idf.
La liste des fonctions est:
**Fonctions d'entrée-sortie :** chargeMotsOutils(fstopname) charge un fichier d'antidictionnaire, qui contient un terme par ligne exportjsonvoc(filenamejson) Exporte le dictionnaire sous forme d'un fichier jsoin avec idf loaddocFile(filename) Lit un fichier et retourne son contenu texte sous forme de chaine de caractères en minuscule **Fonctions de traitements des données :** stringtokenize(chaine) Lit une chaine de caractère et renvoie une liste de tokens (mots) en utilisant une fonction Tokenize de la librairie nltk filtreMotsOutils(liste) Prend en entrée une liste de mots et en filtre les mots outils et retourne la liste nettoyée mot2racine(mot) Prend en entrée un mot, et renvoie sa racine, calculée à l'aide du PorterStemmer anglais de la librairie nltk listeundoc2voc(liste) Lit une liste de racines de termes (d'un document) et modifie le dictionnaire global //voc// correspondant (mot -> nb de docs). Passe par un dictionnaire local qui stocke les termes qui apparaissent, et met ensuite à jour le vocabulaire global. rawdocs2voc() traitement des fichiers contenus dans DOSSIERDOCUMENTS pour obtenir les termes du vocabulaire dans //voc// avec le nombre de docs dans lesquels ils apparaissent idf_voc() Modifie le vocabulaire pour stocker les pdf sous forme d'idf (cf. cours)
Pour créer l'index inversé, on pa passer par plusieurs étapes, dans un NOUVEAU fichier python, appelé indexer_genindex.py, qui peut bien entendu réutiliser et modifier des fonctions de indexer_gendico.py:
0. Ce fichier devra charger le vocabulaire généré en partie 1 (indication sur l'utilisation de json plus bas).
1. On va écrire une programme qui construit la représentation vectorielle (au sens RI) de chaque document, d'après le modèle vectoriel de Salton vu en cours. Le type de représentation choisi est : tf.idf. (on pose donc que nd=1 (cf. transparent 23)). On veut obtenir un “gros” dictionnaire python qui, pour chaque document (clé: le nom du fichier), va stocker un dictionnaire python qui a pour chaque terme (clé) la valeur ptf.pdf.
AIDE: vous pouvez vous inspirer de la fonction listeundoc2voc du fichier fourni, en se rappelant que cette fonction stocke dans “dicolocal” le tf des termes présents. Nous volons stocker, pour chaque document, un dictionnaire pythonde couples (terme, tf.idf du terme dans d). On peut obtenir le tf de tous les termes présents, avant de multiplier ces tf par l'idf provenant du vocabulaire généré.
Par exemple, si le vocabulaire contient 5 termes de t1 à t5 et qu'un document "CACM_123" 'contient 3 fois le terme "t3" et 2 fois le terme "t1", en supposant le l'idf de "t1" est 2.6 et "t3" est 7.61 . Dans le cas où on opte pour une représentation tf.idf, le "vecteur" (dictionnaire) associé au document "CACM_123" est le suivant : [ "t1":5.2 ; "t3":22.83 ] et dans le gros dictionnaire de vecteurs on a : [ "CACM_123": [ "t1":5.2 ; "t3":22.83 ]]
Un "vrai" exemple: "CACM-1011": {"logarithm": 5.181783550292085, "complex": 3.137681375057558, "number": 2.2028583950544753, "algorithm": 0.8920854338854537, "b3": 6.97354301952014, "cacm": 0.00031215857909170155, "august": 2.3750618216828454, "collen": 7.379008127628304}
2. Rajouter au code précédent un traitement qui, à partir du vocabulaire et des vecteurs des documents, construit l'index inversé de ses termes (cas statique), puis le sauvegarde sur disque.
Indication : utiliser encore une fois un dictionnaire pour stocker cet index en mémoire, et le sauver dans un fichier json (utiliser la fonction dump de la library json). Dans ce cas, le dictionnaire a comme clé le terme, et comme contenu un autre dictionnaire ayant comme clé l'identifiant du document et la valeur tf.idf du terme dans le documemt. On a donc un dictionnaire de : [terme: [ document: valeur tf.idf]] .
Exemple : le poids du terme "penni" pour le document CACM-847 est 14,7 . Ce terme apparaît 2 fois dans le document CACM-847 et son idf est de 7.37
3. Pour préparer le traitement des requêtes, il vous est demandé de rajouter, lors du traitement de chaque document le calcul de la norme du vecteur de chaque document : il est égal à la racine carrée de la somme des carrées des valeurs de chaque dimension du vecteur. Ici encore, un dictionnaire est utilisable, avec comme clé l'identifiant de document, et en valeur la norme calculée. On calcule donc bien une valeur par document. Se baser sur la représentation construite en question 1.. Vous devrez ensuite stocker ce dictionnaire dans un fichier sur disque dans au format json.
Exemple : la norme de CACM-1022 est 12,05
Sauvegarde/écriture de variables python dans des fichiers json. On importe en début de programme la librairie python json :
import json
On sauve une variable V dans un fichier “monfichier.json” en écrivant :
with open("monfichier.json", 'w') as fp: json.dump(V, fp) fp.close()
C'est ce qui est fait dans le code fourni.
On lit une variable V d'un fichier “monfichier.json” en écrivant :
with open('monfichier.json', 'r') as fp: V = json.load(fp) fp.close()
* 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
* 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;
Ce code utilise la librarie nltk, et plus particulièrement le découpage en tokens par RegexpTokenizer et la troncature de mots par l'algorithme de Porter :
from nltk.stem.porter import * from nltk import word_tokenize from nltk.tokenize import RegexpTokenizer
pour découper les mots par l'appel à :
tokenizer = RegexpTokenizer('[A-Za-z]\w{1,}')
qui utilise un expression régulière qui découpe en token et garde les mots commençant par une lettre appelé dans la fonction string2list.
Exemple d'utilisation 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']
* 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()