Classification supervisée multi-classes par perceptron multi-couches

Ce TP consiste à mettre en œuvre le perceptron multi-couches (PMC) dans une tâche de classification supervisée multi-classes.
On utilise la bibliothèque neuralnet de R qui permet de manipuler des perceptrons à plusieurs couches cachées et plusieurs neurones de sortie. En outre, la fonction d'activation est, au choix, la fonction logisitique ou la tangente hyperbolique. Nous utiliserons cette dernière qui est généralement préférable à la fonction logistique.
Avant de pouvoir l'utiliser, vous taperez la commande library (neuralnet).

Entraînement et utilisation d'un PMC

Les exemples doivent être fournis dans un data.frame. Pour l'utilisation que l'on va en faire ici, on peut considérer qu'un data.frame est une matrice dont les colonnes portent un nom, celui de l'attribut.
Reprenons les jeux d'exemples du TP précédent. Nous le chargeons dans R à l'aide de la commande suivante :

df <- read.table ("https://philippe-preux.github.io/ensg/miashs/l3-rnf/tps/pmc/expe-2-classes-1.1/40.exemples.-1.1.txt", header = T)

df est composé de 40 lignes et 3 colonnes. Ces 3 colonnes portent les noms x, y et classe.

Apprentissage des poids

L'apprentissage des poids se fait par une commande ressemblant à :

pmc <- neuralnet (classe ~ x + y, data = df, hidden = 5, act.fct = "tanh", rep = 10)

Prédiction à l'aide d'un PMC

La prédiction se fait avec la fonction compute() :

p <- ifelse (compute (pmc, xx)$net.result < 0, -1, 1)

xx est une matrice contenant les données pour lesquelles on veut effectuer une prédiction. (Comme dans le TP précédent.)
compute (pmc, xx)$net.result calcule la prédiction qui, pour chaque donnée, est un nombre compris entre -1 et 1. Ce nombre est transformé en -1 ou 1 ensuite, par la fonction ifelse (... < 0, -1, 1) : un nombre inférieur à 0 est transformé en -1, et un nombre positif est transformé en 1.

Le résultat contient autant de colonnes que de neurones dans la couche de sortie.

Comme on a effectué 10 entraînement (paramètre rep=10 dans l'appel de la fonction neuralnet()), on a 10 réseaux de neurones dans pmc. Pour chacun, on dispose de son erreur mesurée sur le jeu d'entraînement et des poids qui ont été appris. L'erreur de chaque entraînement est donnée dans pmc$result.matrix[1,] (donc, la première ligne de pmc$result.matrix[1,] qui est une matrice contenant l'erreur, les poids et d'autres informations, avec une colonne par entraînement).

Visualisation du PMC

neuralnet permet de représenter graphiquement le PMC : plot (pmc)pmc est le résultat d'un appel à neuralnet().

Application

Exercice

On va utiliser le jeu de données iris. C'est un jeu de données célèbre en statistiques. Il comprend 150 exemples de fleurs de 3 espèces d'iris. Chaque fleur est décrite par 4 attributs, longueur et largeur des spéales et des pétales. En fonction de ces 4 attributs, on doit déterminer l'espèce de la plante, setosa, virginica ou versicolor.
C'est donc un problème à 3 classes. On va utiliser un PMC ayant 3 neurones dans la couche de sortie.

Les iris sont disponibles dans R. Il faut seulement transformer la classe du jeu de données pour que l'information soit compatible avec son utilisation dans neuralnet(). Il y a 3 classes ; on va créer 3 attributs binaires, 1 attribut par classe. L'attribut setosa vaudra 1 si l'exemple est de classe setosa, 0 sinon ; même chose pour les 2 autres classes, virginica et versicolor.
On procède ainsi :

iris.pour.neuralnet <- iris
iris.pour.neuralnet$setosa <- ifelse (iris$Species == "setosa", 1, -1)
iris.pour.neuralnet$virginica <- ifelse (iris$Species == "virginica", 1, -1)
iris.pour.neuralnet$versicolor <- ifelse (iris$Species == "versicolor", 1, -1)

Il y a 3 neurones dans la couche de sortie. Dans le cas d'une classification multi-classes, le neurone dont la sortie est la plus élevée indique la classe prédite. (Rappel : pour la classification mutli-classes, on utilise des neurones à activation linéaire en couche de sortie.)
Pour chaque donnée, on détermine la sortie la plus active, donc la classe prédite.

Si vous avez bien réalisé ce qui a été demandé plus haut, pmc.iris.sorties est une matrice ayant 150 lignes (1 ligne par donnée) et 3 colonnes (1 colonne par neurone de sortie).
Pour connaître les 3 valeurs en sortie du PMC pour la première donnée, on peut taper pmc.iris.sorties [1, ]. J'obtiens le résultat suivant :

[1]  0.9998718761059  0.0002791028565 -0.0001389213175

c'est-à-dire que le premier neurone de sortie est le plus actif. Cette sortie est associée à la classe setosa, qui est la classe de la donnée 1 (iris$classe [1]).
Pour la donnée 51, c'est la sortie 3 qui est la plus active, sortie qui correspond à la classe versicolor, qui est la classe de la donnée 51 (iris$classe [51]).
Même principe pour la donnée 101 qui est de classe virginica.
On peut déterminer la classe prédite pour la donnée 17 (ou toute autre) de la manière suivante :

names (iris.pour.neuralnet [6:8]) [which.max (pmc.iris.sorties [17, ])]

On peut déterminer les exemples dont la classe est mal prédite :

y.predite <- rep ("", times = nrow (iris))
for (i in 1:nrow (iris))
  y.predite [i] <- names (iris.pour.neuralnet [6:8]) [which.max (pmc.iris.sorties [i, ])]
which (y.predite != iris$Species)