Avec ce TP, nous allons apprendre comment on peut traiter des textes en langue naturelle pour ensuite réaliser de la classification supervisée sur ces textes, en Python. Pour cela, on va utiliser la bibliothèque scikit qui contient de mulitples algorithmes pour appliquer des méthodes d'apprentissage automatique en Python. Nous utiliserons Python2 pendant ce TP.
Ce TP utilise un certain jeu de données. Naturellement, il faut être capable d'utiliser n'importe quel jeu de textes, pas seulement celui utilisé ici.
Nous allons travailler avec un ensemble de textes qui se nomme « 20 newsgroups ».
scikit contient tout un ensemble de fonctions prêtes à l'usage pour charger des fichiers de données textuelles.
Pour charger les données, on tape :
from sklearn.datasets import fetch_20newsgroups categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] vingt_train = fetch_20newsgroups (subset='train', categories=categories, shuffle=True, random_state=42)
C'est très long (environ 5 minutes.) Pas d'inquiétude ; lisez la suite pendant ce temps-là.
On peut regarder les données que l'on vient de charger. Elles sont dans un objet dénommé vingt_train.
On peut taper :
vingt_train.target_names
qui va afficher la liste des catégories présentes dans le jeu de données. On retrouve celles que l'on a spécifiées précédemment.
Les textes se trouvent dans vingt_train.data que l'on peut considérer comme une liste de textes. Pour savoir combien il y en a, on tape donc :
len (vingt_train)
Pour voir un texte, on tape :
vingt_train. data [0]
qui affiche le texte composant le premier message. La lecture n'en est pas très aisée car les passages à la ligne ne sont pas réalisés ; dans ce texte, les passages à la ligne sont symbolisés par la suite de caractères \n.
On y voit plus clair en tapant :
print (vingt_train.data[0])
qui affiche le contenu du premier message en passant bien à la ligne.
Pour chaque exemple, la classe est dans les listes vingt_train.target_names et vingt_train.target.
vingt_train.target_names est la liste des noms des catégories pour l'ensemble des exemples.
vingt_train.target contient la même information mais codée dans l'ordre où les noms de classes apparaissent dans l'objet categories que l'on a créé précédemment.
On l'a vu en cours, chaque texte est représenté par un sac de mot, qui s'implante sous la forme d'un vecteur ; chaque composante du vecteur correspond à un mot.
Le principe est le suivant :
Tout cela se fait en quelques instructions Python :
from sklearn.feature_extraction.text import CountVectorizer vingt_train_decomptes = CountVectorizer (). fit_transform (vingt_train.data)
En tapant vingt_train_decomptes.shape, on obtient les dimensions de cette matrice. Sans surprise, on retrouve le même nombre de lignes qu'il y a de textes. Le nombre de colonnes est le nombre de mots distincts présents dans cet ensemble de textes.
On l'a vu, les décomptes ne fournissent pas une représentation adéquate. La représentation tf.idf est bien meilleure. On transforme très facilement les décomptes en tf.idf par :
from sklearn.feature_extraction.text import TfidfTransformer vingt_train_tfidf = TfidfTransformer().fit_transform(vingt_train_decomptes)
vingt_train_tfidf est une nouvelle matrice, de la même taille que vingt_train_decomptes, organisée de la même manière. Seulement, les valeurs sont différentes ; au lieu de décomptes, ce sont maintenant les tf.idf.
On peut vérifier que la taille des deux matrices est la même par vingt_train_tfidf.shape.
On peut être curieux et vouloir voir comment est représenté un texte, i.e. les décomptes ou les valeurs tf.idf. Comme vous avez pu le constater précédemment, le nombre de mots (le nombre de colonnes) est assez grand. On a vu en cours que chaque texte ne contient généralement qu'un petit nombre de mots et que donc, les vecteurs et la matrice sont creux (dans l'exercice que nous sommes en train de réaliser, moins de 0,5% des éléments de la matrice sont non nuls). Aussi, scikit utilise une représentation particulière de la matrice pour ne pas stocker ces valeurs nulles.
Comme il s'agit de matrice particulière, optimisée pour que les traitements soient rapides et l'encombrement en mémoire minimal, regarder leur contenu n'est pas aussi simple que l'on pourrait l'espérer ; la notation est un peu compliquée.
Par exemple, pour voir le premier décompte du texte numéro 0, on tape :
vingt_train_decomptes.getrow(0).toarray()[0][0]
Pour voir les 100 premiers décomptes du texte numéro 0, on tape :
vingt_train_decomptes.getrow(0).toarray()[0][0:100]
On a vu que le produit scalaire revêt une importance cruciale. On peut calculer le produit scalaire entre deux textes, c'est-à-dire entre deux lignes d'une matrice creuse comme suit :
texte1 = 34 texte2 = 176 vingt_train_decomptes.getrow (texte1).toarray()[0].dot(vingt_train_decomptes.getrow(texte2).toarray()[0])
C'est lourd je vous l'accorde, mais il suffit de mettre le bon nom de matrice (ici c'est vingt_train_decomptes, mais ça pourrait être vingt_train_tfidf), et de mettre l'indice des textes dans les variables texte1 et texte2.