Apprentissage sur des données textuelles

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.

Préambule

Nous allons travailler avec un ensemble de textes qui se nomme « 20 newsgroups ».

Lecture du jeu d'exemples

scikit contient tout un ensemble de fonctions prêtes à l'usage pour charger des fichiers de données textuelles.

Chargement des textes

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à.

Les textes

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.

La classe des textes

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.

Représentation des textes sous forme vectorielle

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 :

  1. on découpe chaque message (chaque message est une chaîne de caractères) en une séquence de mots ;
  2. on construit l'ensemble de tous les mots présents dans l'ensemble des textes ;
  3. on compte le nombre d'occurences de chaque mot dans chaque texte ;
  4. on place ces décomptes dans une (grande) matrice dans laquelle chaque ligne correspond à un texte, chaque colonne correspond à un mot.

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.