L’apprentissage automatique par la pratique – 3ième partie

La seconde partie de ce billet nous a permis de mieux comprendre ce qu’est la mise en cluster ou le « clustering » et d’aborder ainsi la « théorie » relative à l’approche à adopter pour l’analyse de jeu de données Enron rendu public suite à l’affaire éponyme.

Il est donc temps de passer à la mise en œuvre concrète du programme qui se chargera de cette analyse en tant que telle.

Cette troisième et dernière partie traite de l’implémentation concrète du programme qui se chargera d’analyser le jeu de données Enron. Maintenant que nous avons vu dans la partie précédente les fondamentaux sur lesquels Mahout s’appuie, nous allons nous intéresser aux classes disponibles pour les données en entrée ainsi qu’aux points critiques du traitement pour nos données.

Le jeu de données Enron compressé pèse en effet environ 450 Mo et, une fois décompressé et transformé pour que KMeans le traite, le jeu de données pèse alors 10 Go ! De plus, ces 10 Go sont composés de 600 000 petits fichiers texte, l’ensemble des mèls comme nous l’avons abordé dans la structuration.

Pour lire nos mèls, nous avons besoin à présent de mieux comprendre comment Hadoop peut s’adapter à des formats de données en entrée spécifiques, et ce, de façon à traiter correctement les fichiers texte des mèls avec leurs entêtes SMTP. Nous aurons alors besoin d’écrire potentiellement un peu de code.

L’approche suggérée

Hadoop gère le format des données en entrée au travers d’une classe de base appelée InputFormat qui décrit la spécification d’entrée pour un Job Hadoop.

Compte tenu de notre contexte, il conviendra certainement par la suite de trouver une implémentation ou d’en hériter pour préciser à l’exécution d’un job Hadoop le type de la donnée en entrée.

Si l’on observe un peu plus en détail la bibliothèque Hadoop, on y retrouve des implémentations comme le FileInputFormat ou le TextInputFormat comme décrit notamment dans l’ouvrage Hadoop The Definitive Guide 3rd Edition.

image

Chacune de ces classes xxxInputFormat pour la spécification du format de donnée en entrée correspond à un besoin spécifique que nous n’évoquerons pas ici, mais si cela vous intéresse, nous ne saurions vous conseiller la lecture de cet excellent ouvrage Hadoop The Definitive Guide.

image

Vous pouvez également consulter ce tutoriel de référence fait par l’équipe Yahoo qui a conçu Hadoop et dont nous nous sommes permis d’extraire la figure ci-dessous.

La classe CombineFileInputFormat est celle qui nous intéresse plus particulièrement dans notre contexte pour la spécification du format de donnée en entrée.

Vous allez nous dire : pourquoi ne pas choisir une autre classe xxxInputFormat issue de la branche FileInputFormat ? Dans la pratique, notre choix est essentiellement motivé pour des raisons de performances car nous devons traiter 600 000 mèls. Si nous partons du principe de créer un split pour un fichier, il y aura du coup 600 000 split à sérialiser et à envoyer du JobTracker vers les TaskTrackers ; ce qui représente beaucoup trop de transit sur le réseau, alors même que Windows Azure possède un couplage HDInsight/ASV ultra performant…

Le schéma suivant montre le découpage du processus de traitement des entrées d’un Job Map/Reduce, c’est-à-dire qu’Hadoop exécute toutes ces tâches (InputFormat=>Split=>RecordReader) avant de procéder à la phase Map/Reduce.

image

Pour revenir à nos « travaux pratiques », nous avons une archive à traiter en parallèle. Une suggestion que l’on retrouve régulièrement sur le Web pour ce type de problème consiste à extraire dans une seule tâche Map l’archive et de la traiter, perdant ainsi l’avantage d’un traitement parallèle et rétrogradant vers un traitement séquentiel. En effet, l’algorithme utilisé par le programme gzip pour compresser les fichiers, en l’occurrence Deflate, ne permet pas d’extraire le contenu de façon parallèle. Cette solution n’est donc pas celle que nous retenons pour les raisons évoquées.

Notre suggestion consiste donc à séparer l’extraction de l’analyse des contenus, afin de gagner en performances et donc en rapidité d’exécution. Ceci revient ainsi, dans un premier temps, à extraire l’archive Enron sur HDFS (ou ASV) de façon séquentielle lors de l’initialisation du Job Hadoop, puis à distribuer par blocs de plusieurs fichiers le traitement, le regroupement et la transformation de ces fichiers extraits avec Map/Reduce.

Mahout se fondant sur Hadoop, il utilise certaines classes du Framework afin de fonctionner de la manière la plus optimale avec Map/Reduce.

Comme les traitements et les calculs demandés par le clustering, et l’apprentissage automatique d’une façon générale, sont gourmands en ressources, nous avons besoin d’une implémentation performante.

Pour cela, Hadoop apporte dans sa bibliothèque de classes un type particulier nommé SequenceFile. Il s’agit d’un fichier binaire qui, afin d’être lu rapidement, contient des clés et valeurs associées. Les clés et valeurs représenteront nos mèls à vectoriser par la suite.

Deux choix s’offrent à nous, à savoir :

  1. Un utilisateur en clé avec pour valeur tous ses mèls envoyés, ainsi on pourra regrouper les utilisateurs par similarité et sujet(s) abordé(s).
  2. Un mèl (son nom et son chemin) en clé et le contenu de ce mèl afin de détecter les types de mèls et leurs thèmes pour faire un moteur de regroupement des mèls d’une boite de réception par theme.

Nous allons revenir sur ce choix mais avant cela, intéressons-nous au format de donnée en entrée, histoire de rentrer dans l’implémentation à proprement parler, et de continuer sur cette voie ;)

Sa mise en oeuvre dans la pratique

InputFormat

Le format de donnée en entrée pour un Job Map/Reduce peut dans certains cas être du texte, un dossier composé de sous-dossier composé de fichiers texte, de XML, de JSON, ou dans le format d’une Base de données précise, ou être au format binaire (un flux vidéo par exemple).

La classe héritant du type InputFormat qui est précisée dans la configuration du Job (au niveau du Driver) décrit la spécification d’entrée de la donnée, ou autrement dit « explique » quel type de donnée sera fourni en entrée, mais aussi la récupère et la découpe dans une logique particulière afin que l’information arrive par paquet dans la phase de Map/Reduce.

Dans notre cas, nous devons traiter une archive au format TAR GZ qui nous est donnée en entrée. Il va falloir d’abord extraire le contenu en entier car ce type d’archive ne permet pas d’être extrait en parallèle compte tenu de l’algorithme de compression Deflate. Une fois tout le contenu extrait sur le système de fichier d’Hadoop (HDFS, ASV, ou autres), nous allons récupérer les chemins de tous les mèls, les regrouper par paquets, puis les renvoyés par l’intermédiaire d’un type objet,  l’InputSplit en l’occurrence.

Un InputSplit correspondra à un paquet de fichiers à traiter, et typiquement le Framework Hadoop s’en sert pour connaitre le nombre de tâches de type « Map » à gérer. L’InputSplit est une vue logique envoyée aux worker-nodes (tasktracker) indiquant où se trouve physiquement la donnée qu’ils ont à traiter.

La donnée n’est pas stockée dans cet objet, ce dernier contient au contraire juste une référence. En effet, une fois que la méthode getSplits() de l’InputFormat spécifique est appelée, une collection d’InputSplit est récupérée et ensuite chacun d’eux sera sérialisé et envoyé à un TaskTracker par le réseau.

Dans l’approche suggérée ci-avant, notre format de donnée d’entrée InputFormat hérite de la classe CombineFileInputFormat qui intègre déjà une logique de récupération de fichiers en entrée et de regroupement pour le fournir aux TaskTrackers.

Il convient alors de surchager les deux méthodes principales, à savoir :

  1. La méthode getSplits() qui s’occupe de construire un tableau d’InputSplit retourné ensuite,
  2. Et la méthode createRecordReader() qui se charge d’instancier correctement le bon RecordReader associé au format que nous voulons. L’InputFormat constitue donc une fabrique (Factory).

Pour cela, nous allons devoir écrire un nouvel InputSplit spécifique, qui se charge de stocker les références des mèls sur HDFS ainsi que leur taille, et nous définirons ensuite le RecordReader qui permet d’exploiter ce type d’InputSplit sur mesure.

Voici nos différentes classes :

  • EnronInputFormat extends CombineFileInputFormat : classe Factory qui récupère toutes les adresses des données à traiter et ensuite les regroupe dans des splits, vues logiques, qui sont envoyés aux RecordReader lisant ce split.
  • EnronCombineFileSplit extends InputSplit implements Writable : classe qui stocke dans une Map 1000 références de fichiers. La clé de la Map est un chemin de fichier et sa valeur associée est un Long indiquant la taille en byte du fichier. Cette classe doit implémenter l’interface Writable car elle sera sérialisée.
  • EnronRecordReader extends RecordReader : classe qui implémente l’interface RecordReader définissant comment le split est découpé en morceaux fournis à la méthode map() du Mapper. Elle doit récupérer chaque fichier à l’adresse donnée en itérant sur l’objet Map du EnronCombineFileSplit et ensuite transformer son contenu en un objet Text qui sera envoyé à la méthode map().

RecordReader

Pour visualiser le processus de soumission des records au Mapper, le pseudo code suivant illustre ce procéder :

K key = reader.createKey() ;
V value = reader.createValue() ;
while(reader.next(key, value)){
mapper.map(key, value, output) ;
}

La méthode next() (ou nextKeyValue() dans la nouvelle version de Map/Reduce) retourne un booléen :

  • vrai si un nouveau record est stocké dans les variables key et value.
  • faux s’il n’y plus aucun record à envoyer car le split est traité dans sa globalité.

Cette méthode implémente donc la logique qui récupère le fichier et le rentre en clé valeur. Pour cela, nous récupérons en premier lieu le fichier à partir de l’entrée courante du split, puis nous le chargeons dans un objet Stream qui nous permettra ensuite de le transformer en chaine de caractère, qui servira de paramètre à l’instance d’un objet Hadoop de type Text.

Une fois le texte récupéré, deux solutions s’offres à nous comme évoqué précédemment :

  • Soit nous optons pour l’adresse mèl de la personne à l’origine du mèl - la ligne commençant par l’entête SMTP « From » - comme clé.
  • Soit nous optons pour le chemin et le nom du fichier comme clé.

Dans les deux approches possibles, nous utilisons toujours le contenu du mèl comme valeur.

Encore une fois, le choix dépend justement de la manière dont vous voulez regrouper le contenu, par personne (et donc par adresse mèl) ou bien par document. Comme il nous faut faire un choix à ce stade, nous optons au final vis-à-vis de cette illustration pour la seconde option : le regroupement du contenu par type document.

Driver

Le driver est le point central du programme sous Hadoop, le Driver est la classe contenant le main. On y place l’instance du Job et la configuration de ce Job et ensuite on imbrique si besoin d’autres tâches, d’autres Job.

Dans cette classe d’entrée, le déroulement est séquentiel, mais les Jobs lancés sont exécutés en Map/Reduce. Cela permet d’attendre la fin de la transformation des mèls en sequencefile avant de les vectoriser (normal !).

VectorSparse

Le vecteur Sparse est l’implémentation que nous avons retenue pour représenter notre texte.

Nous aurons donc un vecteur représentant un document constitué d’autant de dimensions qu’il possède de mots différents. On utilise les méthodes que nous avons décrites précédemment c’est-à-dire :

  • TF-IDF pour pondérer les mots,
  • N-Gramme couplé à une fonction de vraisemblance de Mahout LLR (Log Likelihood Ratio),
  • Et une normalisation pour que nos vecteurs soient un petit peu plus homogènes.

Dans la pratique, la classe SparseVectorsFromSequenceFiles se charge de tout pour nous. Cette classe est un Driver appart en tiers qui utilise plusieurs autres Drivers pour mettre en œuvre ce que nous venons de décrire.

Elle se trouve dans l’archive Java mahout-core.jar et est disponible dans le package org.apache.mahout.vectorizer.

Nous avons quelques arguments à lui passer avant de l’exécuter via une classe utilitaire d’Hadoop nommée ToolRunner avec sa méthode run() :

Argument Description
-i Spécifie le chemin d’entrée dans lequel se trouvent nos documents en SequenceFile
-o Spécifie le chemin de sortie dans lequel seront stockés plusieurs fichiers et dossiers : . tf-vectors/ . tfidf-vectors/ . df-count/ . tokenized-documents/ . dictionary.file-0 . frequency.file-0
-chunk Précise la taille en Mo du chunk. Comme nous ne pouvons pas charger en entier les collections de documents volumineux dans la mémoire durant la vectorisation, il nous est donc possible de découper le dictionnaire en plusieurs chunk de taille spécifiée. Il est recommandé que la valeur ne dépasse pas 80% de la Java Heap Size, à savoir la taille mémoire de la JVM
-wt Spécifie la façon de pondérer les mots à utiliser, soit term frequency (tf) ou tfidf. Lorsqu’on utilise tfidf, la première phase consiste à avoir l’occurrence des termes c’est-à-dire tf, donc celle-ci sera aussi exécutée avant de procéder au calcul avec l’IDF
-s Spécifie le minimum que doit avoir un mot dans tout le corpus de mèls ou de documents pour être pris en compte et apparaître dans le dictionnaire
-md Spécifie le nombre de mèls minimum dans lequel doit apparaître un mot pour être pris en compte
-x Précise le nombre maximum de mèls dans lequel le mot apparaît, pour éviter d’avoir dans le résultat final trop de mots vides
-nq Spécifie la taille qu’auront les N-Grammes (et donc l’utilisation de la méthode N-GRAM). Si la valeur est de deux, le Driver génèrera des bi-grammes sur tout le corpus. Ces bi-grammes seront utilisés pour augmenter la pondération de certains mots. Si cette méthode augmente considérablement le temps de traitement, elle améliore également la qualité des clusters. La valeur par défaut, si vous ne spécifiez pas cette option est de 1 : aucun n-gramme ne sera généré
-ml Définit la valeur minimum du calcul de vraisemblance pour être considéré dans le calcul de pondération. Cette option ne fonctionne que lorsque des n-grammes sont générés, logique… Les N-Grammes les plus significatifs ont des scores très élevés, comme par exemple 1000, les moins des scores moins élevés
-n La normalisation P-Norm demande une valeur intrinsèquement liée à la méthode de mesure des similarités utilisée lors de l’exécution de l’algorithme de clustering. Donc il faut déjà savoir quelle mesure nous utiliserons pour entrer la valeur de normalisation adéquate
-nr Spécifie le nombre de Reducer, afin d’accélérer la finalisation du Job. Vous pouvez complétez cette option par le nombre de nœuds que vous avez sur votre cluster Hadoop
-a Précise La classe qui sera utilisée pour analyser (parser/découper) le contenu de chaque fichier mèl. Dans le contexte de notre exemple avec le jeu de données Enron, il y en a une tout indiquée : MailArchivesClusteringAnalyzer

Point important à noter : Mahout ne manipule que des nombres, chose qui n’est pas évidente lorsque nous avons des documents texte. Nos clés sont en effet des chaines de caractères et nos vecteurs sont composés de dimensions de mot.

Comme Mahout ne manipule que des nombres, encore une fois, à la fin de cette étape, un dictionnaire est généré. Il contient tous les mots qui auront passés les filtres avec succès de manière indexé, c’est-à-dire qu’un mot possèdera un index précis dans ce dictionnaire, et cet index remplacera le mot formant une dimension.

Ainsi :

mel_parlant_denron.txt => ["enron" :15, "work" :3, "money" :48]

devient :

265 => [487 :15, 78559 :3, 9563 :48]

avec un dictionnaire :


487 enron

9563 money

78559 work
...

Et le fichier mel_parlant_denron.txt sera le document n°265.

Lucene Analyzer

Afin d’augmenter l’efficacité d’un algorithme de clustering sur un jeu de données en particulier, il existe deux éléments importants permettant d’augmenter la qualité du résultat :

  1. Augmenter le poids des mots dans le vecteur,
  2. Créer une distance plus appropriée.

Une bonne technique de pondération peut promouvoir les bonnes caractéristiques d’un objet, et une mesure de distance appropriée peut aider à rassembler les caractéristiques similaires.

Un bon vecteur de document possède les mots qui le caractérisent le plus, avec un poids plus lourd assigné aux plus importants. Avec les données sous forme de texte, il existe deux façons d’augmenter la qualité :

  1. En supprimer les « pollutions »,
  2. En utilisant une technique de pondération adequate.

Les mèls ne sont pas des documents de bonne qualité, et ceux du jeu de données Enron possèdent des entêtes SMTP que nous ne voulons pas analyser. Ces entêtes ne nous servent que dans la phase précédente, lorsque l’on définit ce qu’est un document, à savoir :

  • Soit une adresse mèl à laquelle on associe des mèls,
  • Soit un fichier et son contenu de texte.

De plus, le texte de ces mèls peut être incomplet, composé d’abréviations ou d’acronymes, ou potentiellement avec des fautes d’orthographe ou de grammaire (comme dans tout document…). Faire du clustering avec des données ayant beaucoup de pollutions s’avère donc difficile. Pour avoir une qualité optimale, la donnée doit en premier être nettoyée de ces erreurs.

Un projet Apache est spécialisé dans l’analyse de texte et fourni des outils : Lucene. Ce projet est utilisé dans de nombreux projets de moteurs de recherche sémantique (SolR, Elastic Search, etc.).

Bref, Mahout intègre certaines parties de ce projet, et s’intéresse plus particulièrement à un type afin de filtrer le texte qui sera vectorisé. Ceci est exécuté par un Lucene Analyzer. Mahout possède plusieurs implémentations de Lucene Analyzer.

Le StopWordAnalyzer qui, comme son nom le laisse supposer, s’occupe de supprimer des vecteurs les mots vides à partir d’un dictionnaire de langue (existants en Anglais). Mais aussi, l’archive Java mahout-integration.jar contient un analyseur spécialement conçu pour filtrer les mèls type comme ceux du jeu de données Enron. Bonne nouvelle !

Clustering Driver

Afin d’exécuter l’algorithme de Clustering, Mahout fournit un Driver pour chaque algorithme (qu’il s’agisse de clustering, de classification ou de recommandation.

L’utilisation de KMeans s’effectue donc par le biais de la classe KMeansDriver (dans le package org.apache.mahout.clustering).

Cependant, avant de lancer KMeans, nous voulons connaitre les centres des clusters, le nombre et leur emplacement respectif. Nous utiliserons pour ce faire Canopy comme nous l’avons expliqué auparavant.

CanopyDriver est la classe à instancier dans l’outil ToolRunner d’Hadoop. Le nombre de centres ne dépend que du choix de la mesure de distance ainsi deux valeurs de distance limite (T1 et T2) :

  • T2 spécifie la longueur du rayon du cercle (= le cluster),
  • T1 représente un rayon plus grand qui servira à recalculer le centre du cluster par rapport à ce plus grand rayon.

Ces deux valeurs sont difficiles à estimer et dépendent de la mesure de distance. Il convient de s’assurer que T1 > T2.

La science n’est pas exacte, et il s’agit donc plus du tâtonnement qu’autre chose…

Il nous faudra donc lancer l’algorithme peut être plusieurs fois avant de trouver une valeur appropriée pour T1 et T2 qui génèrera assez de centres.

La mesure de distance cosinus donne des valeurs entre 0 et 1, ne l’oubliez pas.

CanopyDriver prend en paramètre donc 5 options :

Argument Description
-i Spécifie le chemin d’entrée, le dossier contenant les vecteurs (soit tf-vectors, soit tfidf-vectors). Dans notre cas, nous utiliserons la technique TF-IDF et donc en conséquence le chemin se terminera par tfidf-vectors
-o Spécifie le chemin où seront générés et stockés les centres
-dm Spécifie la classe d’implémentation de notre mesure de distance, soit CosineDistanceMeasure pour le jeu de données Enron
-t1 Précise la valeur de T1
-t2 Précise la valeur de T2

KMeansDriver peut ensuite être invoqué en lui fournissant tout le travail de préparation que nous avons effectué :

Argument Description
-i Spécifie le chemin d’entrée, le dossier contenant les vecteurs (soit tf-vectors, soit tfidf-vectors). Dans notre cas, nous utiliserons la technique TF-IDF et donc en conséquence le chemin se terminera par tfidf-vectors
-o Spécifie le chemin final où seront stockés les clusters sous forme de SequenceFile
-dm Spécifie la classe d’implémentation de notre mesure de distance, à savoir CosineDistanceMeasure pour le jeu de données Enron
-c Spécifie le dossier contenant les centres, générés la phase précédente par Canopy
-x Spécifie le nombre d’itération que fera KMeans, afin d’affiner au maximum le résultat et les clusters. Généralement une valeur de 20 itérations suffit à la majorité des cas d’usages
-k Cette option n’est pas à spécifier si nous utilisons des centres générés par Canopy. Cette option n’est utile que lorsque l’on passe par la génération aléatoire avec RandomSeedGenerator
-cd Précise la limite de convergence, une valeur qui spécifie que le centre d’un cluster est stable et ne varie plus, afin d’arrêter les itérations. Dans le cadre d’une mesure de distance par cosinus, une valeur proche de 1 sera choisie car le résultat tend vers 1 si deux vecteurs pointent la une direction similaire

DistanceMeasure

Si les vecteurs sont de bonne qualité, notamment compte tenu de l’utilisation des bonnes techniques de pondération et des bons filtres (Analyzer), la qualité des groupes résultant de l’exécution de l’algorithme de clustering se trouve dans le choix approprié de la mesure de distance. Dans Mahout, la classe de base se nomme DistanceMeasure, et toutes ses implémentations calculent la distance entre deux vecteurs, appliquant une définition variable de « distance ».

Il en existe 5, plus ou moins efficaces selon le type de donnée qu’on utilise. Celle que nous avons présenté précédemment, en l’occurrence, la distance Euclidienne (classe EuclideanDistanceMeasure), n’est pas très efficace sur du texte.

Par expérience, l’utilisation de la mesure de distance cosinus s’avère la plus adaptée (CosineDistanceMeasure).

Cette implémentation définit la distance entre deux vecteurs par rapport à l’origine du plan (0,0) et calcule le cosinus de l’angle entre l’origine et chaque vecteur. (Rappelez-vous les vecteurs sont des points avec coordonnées en mathématiques). Ceci permet de calculer en réalité l’angle entre ces deux vecteurs et de savoir s’ils pointent vers la même direction. Cette mesure ne prend pas en compte la longueur des vecteurs à l’inverse de la distance euclidienne.

ClusterDumper

Enfin, le dernier driver qui nous sera utile est le ClusterDumper qui permettra d’afficher le résultat de manière « compréhensible à l’homme ». En effet, le résultat de KMeans se trouve dans un format SequenceFile (encore !), avec que des chiffres représentant les Clusters.

Dans le package org.apache.mahout.utils.clustering, on trouve cette classe avec les paramètres suivants :

Argument Description
-i Spécifie le chemin du dossier de la dernière itération de KMeans, celui finançant par « -final »
-o Le chemin n’est pas obligatoire, si vous ne spécifiez pas le dump se fera dans la console, sinon il faut lui donner le chemin d’un système de fichier local (« c:/result.txt »)
-d Spécifie le chemin du dictionnaire. Généré à l’étape de vectorisation, il se situe dans le dossier de sortie de la vectorisation et se nomme « dictionnary.file-0 ». Le ClusterDumper va remplacer les nombres contenus dans les vecteurs par les mots qui correspondent
-n Précise le nombre de clusters à afficher
-b Spécifie le nombre de mots pour chaque cluster à afficher, les classant par ordre décroissant, le mot avec le plus haut score sera en premier

En guise de conclusion

Nous avons terminé pour notre analyse de l’affaire Enron. Comme à l’accoutumée, nous joignons à ce billet le code source correspondant.

Au travers de ce billet, nous avons pu aborder différents concepts et approches du clustering, et ce, même si au final, nous n’avons utilisé qu’un seul algorithme, plutôt simple mais tout de même efficace, en l’occurrence KMeans.

Dans la pratique, il existe plusieurs types de « clustering » :

  • Le clustering exclusif où un élément n’est assigné exclusivement qu’à un groupe, KMeans en fait partie.
  • Le clustering de « chevauchement » (overlapping) où un élément peut être assigné à plusieurs clusters. Fuzzy KMeans en fait partie.
  • Le clustering hiérarchique qui spécifie des groupes et des sous-groupes, générant une hiérarchie à la manière d’un arbre.
  • Le clustering probaliste qui spécifie qu’un modèle probabiliste est une structure particulière ou une distribution de collections de points dans le plan. Un tel Clustering essaie de trouver un modèle adéquate au jeu de données qu’il analyse. Ce clustering s’avère très puissant mais aussi plutôt compliqué.

Pour ce qui est de ce dernier type, une des implémentations de Mahout s’appelle Dirichlet, et sa variante pour l’analyse textuelle est LDA (ou CVB dans Mahout version supérieure à 0.5). Ainsi, LDA ne requiert pas en entrée le nombre K centres, ni les limites de mesure de distance. Il détecte automatiquement le modèle par le biais de probabilités.

Une des possibilités à terme serait d’utiliser LDA sur le jeu de données d’Enron afin de mieux définir les paramètres de KMeans. Cet algorithme basé sur la détection de modèle est un excellent outil de découverte des jeux de données de texte, et permet ensuite de faire fonctionner un algorithme plus rapide et moins complexe (comme KMeans) avec les bons paramètres.

Nous aurons donc l’occasion de revenir sur le sujet :)

job_enron.zip