Réseaux de neurones avec R (#1)
On parle beaucoup de l’utilisation des réseaux de neurones dans des domaines variés (diagnostic médical, analyse financière, reconnaissance d’image ou vocale etc…). Les fondements mathématiques sont connus depuis longtemps et les premières mise en oeuvre comme le perceptron datent des années 50. Ce n’est pourtant que ces dernières années que ces techniques se sont démocratisées grâce notamment aux capacités de calcul des processeurs actuels et à la profusion des données disponibles (nécessaires à leur entrainement).
On trouve sur internet d’innombrables sources d’information sur les ANN (Artificial Neural Network). Une bonne introduction se trouve sur Wikiversité.
Dans les grandes lignes, un ANN est constitué d’une succession de couches de neurones interconnectés. Chaque interconnexion est caractérisée par un poids synaptique, et chaque neurone met en oeuvre une fonction d’activation.
La phase d’apprentissage consiste dans l’ajustement progressif des poids synaptique en utilisant un algorithme de rétropropagation (back-propagation) du gradient.
La structure du réseau (nombre de couches, nombre de neurones et fonctions d’activation) est en revanche fixée à priori.
Ici, je me suis intéressé à l’implémentation des ANN avec R. Pour cela, j’ai utilisé des données mises à disposition sur le site de l’UCI et relatives au diagnostic du cancer du sein pour une cohorte du Wisconsin.
Plusieurs jeux de données sont disponibles, je me suis initialement intéressé au dataset breast-cancer-wisconsin.data dont le format est détaillé ici: breast-cancer-wisconsin.names.
Celui-ci contient 699 instances. 9 attributs numériques sont exploitables. 2/3 des enregistrements correspondent à des tumeurs bégnines, 1/3 a des tumeurs malignes.
> breastcancer <- read.csv(url("https://archive.ics.uci.edu/ml/machine-learning-databases/breast-cancer-wisconsin/breast-cancer-wisconsin.data"), header=FALSE)
> summary(breastcancer)
V1 V2 V3 V4 V5 V6 V7 V8 V9
Min. : 61634 Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.000 Min. : 1.000 1 :402 Min. : 1.000 Min. : 1.000
1st Qu.: 870688 1st Qu.: 2.000 1st Qu.: 1.000 1st Qu.: 1.000 1st Qu.: 1.000 1st Qu.: 2.000 10 :132 1st Qu.: 2.000 1st Qu.: 1.000
Median : 1171710 Median : 4.000 Median : 1.000 Median : 1.000 Median : 1.000 Median : 2.000 2 : 30 Median : 3.000 Median : 1.000
Mean : 1071704 Mean : 4.418 Mean : 3.134 Mean : 3.207 Mean : 2.807 Mean : 3.216 5 : 30 Mean : 3.438 Mean : 2.867
3rd Qu.: 1238298 3rd Qu.: 6.000 3rd Qu.: 5.000 3rd Qu.: 5.000 3rd Qu.: 4.000 3rd Qu.: 4.000 3 : 28 3rd Qu.: 5.000 3rd Qu.: 4.000
Max. :13454352 Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.000 Max. :10.000 8 : 21 Max. :10.000 Max. :10.000
(Other): 56
V10 V11
Min. : 1.000 Min. :2.00
1st Qu.: 1.000 1st Qu.:2.00
Median : 1.000 Median :2.00
Mean : 1.589 Mean :2.69
3rd Qu.: 1.000 3rd Qu.:4.00
Max. :10.000 Max. :4.00
>
On opère quelques ajustements sur le dataset :
- Suppression de la premiere colonne (V1) qui correspond à un ID patient.
- Binarisation (0/1) de la colonne V11 qui correspond au diagnostic (codification initiale: 2 pour une tumeur benigne, 4 pour une tumeur maligne)
- Suppression des lignes pour lesquelles le champ V7 est inconnu (« ? »)
- Conversion de la colonne V7 de facteur à numérique.
- Centrage/réduction à l’aide de la fonction « scale » de toutes les valeurs du dataframe (sauf celles de la colonne V11 qui est binarisée)
> breastcancer <- breastcancer[-1]
> breastcancer$V11 <- ifelse(breastcancer$V11==2,0,1)
> breastcancer <- breastcancer[-which(breastcancer$V7=="?"),]
> breastcancer$V7 <- as.numeric(levels(breastcancer$V7))[breastcancer$V7]
Warning message:
NAs introduced by coercion
> breastcancer[,-10] <- scale(breastcancer[,-10])[,]
> summary(breastcancer)
V2 V3 V4 V5 V6 V7 V8 V9
Min. :-1.2203 Min. :-0.7017 Min. :-0.7412 Min. :-0.6389 Min. :-1.0050 Min. :-0.6983 Min. :-0.9981 Min. :-0.6125
1st Qu.:-0.8658 1st Qu.:-0.7017 1st Qu.:-0.7412 1st Qu.:-0.6389 1st Qu.:-0.5552 1st Qu.:-0.6983 1st Qu.:-0.5899 1st Qu.:-0.6125
Median :-0.1568 Median :-0.7017 Median :-0.7412 Median :-0.6389 Median :-0.5552 Median :-0.6983 Median :-0.1817 Median :-0.6125
Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000 Mean : 0.0000
3rd Qu.: 0.5523 3rd Qu.: 0.6033 3rd Qu.: 0.5972 3rd Qu.: 0.4084 3rd Qu.: 0.3444 3rd Qu.: 0.6738 3rd Qu.: 0.6347 3rd Qu.: 0.3703
Max. : 1.9703 Max. : 2.2345 Max. : 2.2702 Max. : 2.5029 Max. : 3.0434 Max. : 1.7716 Max. : 2.6758 Max. : 2.3358
V10 V11
Min. :-0.3481 Min. :0.0000
1st Qu.:-0.3481 1st Qu.:0.0000
Median :-0.3481 Median :0.0000
Mean : 0.0000 Mean :0.3499
3rd Qu.:-0.3481 3rd Qu.:1.0000
Max. : 4.8461 Max. :1.0000
>
On divise ensuite le dataset breastcancer en un échantillon d’apprentissage (70%) et un échantillon de test (30%). L’appel a set.seed permet d’obtenir des résultats reproductibles.
> set.seed(1234) > sub <- sample(nrow(breastcancer), floor(nrow(breastcancer) * 0.70)) > breastcancer_train <- breastcancer[sub,] > breastcancer_test <- breastcancer[-sub,] >
A ce stade, un premier ANN est créé à l’aide du package nnet qui est inclus par défaut dans la distribution R. Ce package ne permet néanmoins que la création d’un réseau simple contenant une unique couche cachée.
Ici, on fixe arbitrairement le nombre de neurones de cette couche cachée à 5. Il n’y a pas de règle particulière pour déterminer ce nombre ni plus généralement la topologie du réseau de neurones que l’on construit. C’est l’expérience et la connaissance du domaine d’étude qui permettent de déterminer une topologie adaptée. D’autre part, il existe plusieurs méta-paramètres qui peuvent aussi être adaptés. Généralement, on essaie plusieurs configuration et on choisit celle offrant la meilleure performance sur l’échantillon de test.
> library(nnet)
> nn_model <- nnet(V11 ~ ., data=breastcancer_train, size=5)
# weights: 56
initial value 114.785410
iter 10 value 12.000001
final value 12.000000
converged
> pred <- predict(nn_model, newdata=breastcancer_test, supplemental_cols=c("V11"))
>
Le vecteur pred contient la sortie de la fonction d’activation (sigmoide) du neurone de sortie. On binarise le résultat avec un seuil à 0.5:
> pred.bin <- ifelse(pred>0.5,1,0)
> table(pred.bin, breastcancer_test$V11)
pred.bin 0 1
0 122 5
1 5 73
>
Le taux de réussite des prédictions avec ce modèle est de l’ordre de 95% (122+73)/(122+73+5+5).
C’est excellent mais… pour cet exemple simple, une régression logistique toute simple permet aussi d’atteindre un résultat similaire!!
> breastcancer_train$V11 <- as.factor(breastcancer_train$V11)
> breastcancer_test$V11 <- as.factor(breastcancer_test$V11)
> log_model <- glm(V11 ~ ., family=binomial, data=breastcancer_train)
> step(log_model, dir="backward", trace=0)
Call: glm(formula = V11 ~ V2 + V4 + V5 + V7 + V8 + V9, family = binomial,
data = breastcancer_train)
Coefficients:
(Intercept) V2 V4 V5 V7 V8 V9
-1.4256 1.7191 1.5855 0.8481 0.9593 1.1630 0.8326
Degrees of Freedom: 477 Total (i.e. Null); 471 Residual
Null Deviance: 610.8
Residual Deviance: 68.99 AIC: 82.99
> log_model <- glm(V11 ~ V2 + V4 + V5 + V7 + V8 + V9, family=binomial, data=breastcancer_train)
> pred <- predict(log_model, breastcancer_test)
> levels(breastcancer_test$V11)[1]
[1] "0"
> pred.bin <- ifelse(pred > 0.5,1,0)
> table(pred.bin, breastcancer_test$V11)
pred.bin 0 1
0 124 8
1 3 70
>
En fait, c’est lorsque le nombre de prédicteurs devient important que les ANN tirent réellement leur épingle du jeu.
To be continued…