Clustering
==========

La notion de clustering recouvre un ensemble de procédés qui permettent, à partir d'une base de données, de regrouper les données similaires en agrégats, ou *clusters*, afin de réduire leur complexité apparente et de mettre à jour leur structure. Les classifications ainsi obtenues peuvent ensuite être utilisées dans diverses applications. Par exemple, 

* En bio-informatique, des méthodes de clustering sont utilisées pour grouper les espèces d'êtres vivants similaires, afin de pouvoir généraliser à tout le groupe les propriétés connues d'une espèce particulière. Par exemple, un traitement efficace contre un certain agent pathogène peut s'avérer aussi efficace à l'égard d'autres micro-organismes du même cluster. 
* Dans le domaine de la vision, le nombre de pixels de différentes nuances de gris dans une image peut être réduit en regroupant plusieurs pixels en un même cluster, et en leur attribuant une nuance de gris commune. 
* La classification des utilisateurs d'un site internet à partir de leurs habitudes de consultation du site peut permettre de concevoir des interfaces différentes pour chaque classe, adaptées à leurs attentes spécifiques. De façon similaire, la classification du contenu d'un site internet peut aider à décider comment diviser ce contenu en rubriques thématiques. 

Dans cette série d'exercices, nous vous proposons de travailler avec deux sortes de données : 

* Une liste de maladies avec leurs symptômes. La classification de ces maladies doit permettre d'identifier des groupes d'affections similaires, susceptibles d'être traitées par des traitements similaires. 
* Une liste d'entreprises actives dans le domaine de l'informatique, dont une classification en différents groupes doit permettre d'identifier les concurrents proches sur le marché.

Modules squelettes
------------------

Voici pour commencer les modules partiellement implémentés qui serviront de base à nos exercices. Le dernier constitue un module de test. 

:download:`clustering.zip <../../squelettes/clustering.zip>`

Module ``.../moteurs_clustering/cluster.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/cluster.py
    :code:

Module ``.../moteurs_clustering/cluster_mean.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/cluster_mean.py
    :code:

Module ``.../moteurs_clustering/cluster_hierarchique.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/cluster_hierarchique.py
    :code:

Module ``.../moteurs_clustering/clustering.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/clustering.py
    :code:

Module ``.../moteurs_clustering/clustering_kmeans.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/clustering_kmeans.py
    :code:

Module ``.../moteurs_clustering/clustering_hierarchique.py`` :

.. include::  ../../squelettes/clustering/moteurs_clustering/clustering_hierarchique.py
    :code:

Module ``.../exemple_clustering.py`` :

.. include::  ../../squelettes/clustering/exemple_clustering.py
    :code:

L'algorithme général de clustering
----------------------------------

Les deux algorithmes de clustering que vous allez implémenter sont des algorithmes itératifs, qui construisent des clusters en suivant l'algorithme général ci-dessous :

::

    clusters <- liste vide

    Clustering(données)
    1.  anciens_clusters <- liste vide
    2.  clusters <- initialise_clusters(données)
    3.  WHILE NOT fini(anciens_clusters) DO
    4.      anciens_clusters <- sauvegarde des clusters
    5.      clusters <- revise_clusters(clusters)
    6.  END WHILE
    END Clustering

La classe ``Cluster``
~~~~~~~~~~~~~~~~~~~~~

La classe ``Cluster`` représente un cluster défini par un nom (optionnel) et par une liste de données. Cette classe contient donc deux attributs :

* ``donnees``: la liste des données du cluster ;
* ``nom``: le nom (optionnel) du cluster.

Cette classe contient aussi des méthodes utilitaires qui permettent d'ajouter des données aux clusters.

La classe ``Clustering``
~~~~~~~~~~~~~~~~~~~~~~~~

La classe ``Clustering`` implémente l'algorithme général de clustering que nous avons vu ci-dessus. La méthode ``itere`` implémente cet algorithme en appelant trois méthodes : ``initialise_clusters``, ``revise_clusters`` et ``fini`` ; elle joue donc le rôle d'un wrapper pour ces trois méthodes. ``itere`` initialise les clusters, puis les révise de manière itérative jusqu'à ce qu'ils soient stabilisés, et ne changent plus d'une itération à l'autre.

``Clustering`` impose une structure générale que ses sous-classes sont contraintes de respecter. Afin d'obtenir le comportement désiré pour le clustering :math:`k`-means et le clustering hiérarchique, les méthodes ``initialise_clusters``, ``revise_clusters`` et ``fini`` doivent cependant être implémentées différemment dans chaque sous-classe.

Exercice 1 : Le clustering :math:`k`-means (clustering de partitionnement)
--------------------------------------------------------------------

L'algorithme :math:`k`-means part d'une liste de noyaux, qui sont des données autour de chacune desquelles sera construit un cluster. Au cours d'une itération, chaque donnée est réaffectée au cluster du noyau duquel elle est le plus proche. Chaque cluster est ensuite recentré autour d'un nouveau noyau. L'algorithme se termine quand l'ensemble des noyaux n'a pas changé d'une itération à la suivante. Ils existe plusieurs méthodes pour recentrer un noyau, qui donnent lieu à différentes variantes de l'algorithme. Dans cet exercice, nous appliquerons une variante adaptée à l'usage de données discrètes, qui est aussi appelée algorithme des :math:`k`-médoïdes.

La classe ``ClusterMean``
~~~~~~~~~~~~~~~~~~~~~~~~~

La classe ``ClusterMean`` étend la classe mère ``Cluster`` avec un attribut ``noyau`` qui doit aussi faire partie de la liste des données du cluster. Le constructeur initialise cet attribut au premier élément de la liste de données.

La méthode principale à implémenter est ``centre``, qui met à jour le noyau d'un cluster afin que celui-ci soir au centre des données du cluster. Le nouveau noyaux sera la donnée qui minimise la somme quadratique des distances aux autres données du cluster. En d'autres termes, le noyau :math:`x_{n}` du cluster :math:`C` doit minimiser l'expression suivante : 

.. math:: 
    \sum_{x \in C} d(x_n, x)^2

C'est pour cette raison que la méthode prend un argument ``dist_f``, qui est la fonction de distance entre deux données d'un cluster. La distance que nous utiliserons sera calculée comme le nombre d'attributs différents dans les tuples représentant les éléments à comparer (voir ``exemple_clustering.py``).

À plusieurs reprises, vous allez devoir parcourir une liste pour trouver l'élément qui minimise une certaine fonction. En Python, ceci peut être implémenté en une seule ligne, grâce à la fonction ``min(liste, key=fonction)``, qui prend en paramètres une liste d'éléments et une fonction que l'on cherche à minimiser. Par exemple, pour trouver l'élément de la liste ``[1, 2, -3]`` qui a le plus petit carré, on peut utiliser la ligne de code ``m = min([1, 2, -3], key=lambda x: x**2)``. 

La classe ``ClusteringKMeans``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

La classe ``ClusteringKMeans`` étend la classe mère ``Clustering`` afin d'implémenter l'algorithme :math:`k`-means. Elle ajoute deux nouveaux attributs :

* ``k``: le nombre des clusters à construire ;
* ``dist_f``: la fonction de distance entre deux éléments.

Le constructeur de la classe prend ainsi en arguments le nombre de clusters souhaités et la fonction de distance.

Comme nous l'avons vu ci-dessus, les méthodes principales à implémenter sont ``initialise_clusters``, ``revise_clusters`` et ``fini``. La méthode ``initialise_clusters`` prend en argument la liste des données à regrouper. Elle initialise la liste ``self.clusters`` de sorte que celle-ci contienne :math:`k` clusters avec comme noyaux les :math:`k` premiers éléments de la liste de données. Tous les autres éléments sont affectés au premier cluster.

La méthode ``revise_clusters`` s'exécute en deux étapes :

* Calcul des nouveaux clusters : initialisation des clusters avec uniquement les noyaux, calcul des distances de chaque élément aux noyaux et ajout de l'élément dans le cluster le plus proche ;
* Pour chaque cluster ainsi obtenu, le noyau est mis à jour afin d'être au centre du cluster. 

La méthode ``fini`` prend en argument la liste des anciens clusters. Elle compare cette liste avec les clusters actuels, afin de tester si l'algorithme a convergé. Afin d'implémenter cette méthode, vous pouvez comparer les listes de noyaux de ces deux ensembles de clusters : les noyaux ne changent pas si et seulement si les clusters restent les mêmes. 

**Astuce:**  Afin d'implémenter ces deux dernières méthodes, vous pouvez vous aider des méthodes ``vide`` de la classe ``ClusterMean``, qui vide un cluster avec l'option de garder son noyau, et ``noyaux`` de la classe ``ClusteringKMeans``, qui retourne les noyaux d'une liste de clusters.

Exercice 2 : Le clustering hiérarchique
---------------------------------------

À la différence du clustering de partitionnement, le clustering hiérarchique par agglomération construit une classification en clusters de plus en plus larges, qui peut se présenter sous la forme d'un dendrogramme. La classification hiérarchique ainsi obtenue est organisée en groupes et en sous-groupes, afin de discerner les agrégats de similarité grossière des agrégats de similarité plus fine. 

L'algorithme part d'un ensemble de clusters ne contenant chacun qu'une seule donnée, et, lors de chaque itération, fusionne les deux clusters les plus similaires. L'algorithme se termine quand toutes les données ont été regroupées en un seul cluster. La mesure de similitude entre deux clusters peut être calculée de différentes façons. Dans cet exercice, nous vous en proposons deux : la distance *single-link* et la distance *complete-link*. La distance single-link définit la similitude de deux clusters comme la plus courte distance entre deux données de ces clusters. À l'inverse, le complete-link considère la plus longue distance. 

La classe ``ClusterHierarchique``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

La classe ``ClusterHierarchique`` représente un cluster (un noeud) dans le dendrogramme (un arbre binaire) construit par le clustering hiérarchique. Elle contient deux nouveaux attributs :

* ``gauche``: le sous-cluster de gauche ;
* ``droite``: le sous-cluster de droite.

La classe ``ClusteringHierarchique``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

La classe ``ClusteringHierarchique`` implémente le clustering hiérarchique. Elle étend la classe mère ``Clustering`` avec deux nouveaux attributs :

* ``type_lien``: le type de distance entre deux clusters (``'single'`` ou ``'complete'``) ;
* ``dist_f``: la fonction de distance entre deux données.

Le constructeur de la classe prend en arguments la fonction de distance entre deux données et le type de distance entre deux clusters.

Les méthodes principales que vous devez implémenter sont ``initialise_clusters``, ``revise_clusters``, ``clusters_distances`` et ``fini``. La méthode ``initialise_clusters`` initialise la liste ``self.clusters`` de sorte qu'elle contienne un cluster (un noeud) pour chaque donnée de la liste passée en argument.

La méthode ``revise_clusters`` cherche les deux clusters les plus proches et les fusionne en un seul grâce à la méthode ``fusion`` de la même classe, qui retourne le nouveau noeud. Elle les retire ensuite de la liste des clusters pour y ajouter le produit de leur fusion. Cette méthode s'appuie sur la méthode ``calcule_distance``, qui doit, selon le type de lien indiqué, retourner le minimum ou le maximum des distances entre chaque paire des données des deux clusters. Selon que la distance entre deux clusters est définie comme le minimum des distances entre chaque paire d'éléments ou comme le maximum, on obtient en effet une distance de type *single-link* ou de type *complete-link*.

Rappelons que c'est la méthode ``Clustering.itere`` qui implémente l'algorithme général de clustering, et qui joue le rôle d'un wrapper pour les méthodes ``initialise_clusters`` et ``revise_clusters``. Dans le cas du clustering hiérarchique, ``itere`` va donc initialiser les clusters, puis les fusionner de manière itérative jusqu'à ce qu'il ne reste qu'un seul élément dans la liste ``self.clusters``. Cet élément constituera la racine de la hiérarchie construite par le clustering. La méthode ``fini``, quant à elle, arrête l'algorithme quand la taille de la liste des clusters est réduite à un seul élément.

Test du programme
~~~~~~~~~~~~~~~~~

Une fois que vous avez terminé l'implémentation des méthodes manquantes, il ne vous reste plus qu'à tester ces deux algorithmes sur les deux exemples fournis : maladies et profits d'enterprises. 

Pour le clustering :math:`k`-means, faites varier le nombre de clusters (ainsi que l'initialisation des clusters et leurs noyaux, si vous êtes motivés). Que pensez-vous de la qualité des clusters ? Les données classifiées dans un même cluster sont-elles toujours très similaires ? Est-il facile de choisir le nombre de clusters ?

Pour le clustering hiérarchique, quelles différences obtenez-vous si vous alternez entre les méthodes  *single-link* et *complete-link* ? Que pensez-vous de la qualité des clusters ainsi obtenus, en comparaison avec l'algorithme :math:`k`-means ?
