Understanding the Role of Hidden Layer Neurons in Perceptron

Page 1 sur 8Lecteur de document UniversityLib

Understanding the Role of Hidden Layer Neurons in Perceptron

Artificial Intelligence/Machine Learning · course

Voir tous les documents en intelligence artificielle et données

1 Objectif

Dans cet exemple, nous allons essayer d’expliciter le rôle des neurones de la couche cachée du

perceptron. Nous utiliserons des données générées artificiellement. Nous nous en tenons à 2

variables explicatives afin de pouvoir projeter

les données dans

le plan et ainsi décrire

concrètement le comportement de l’approche. Nous proposerons une petite routine de détection

automatique du nombre optimal de neurones avec le logiciel R (package nnet).

2 Données

Le fichier « artificial.xls » contient deux jeux de données de n = 2000 observations décrites par p = 2

variables X1 (définie dans [-0.5 ; 0.5]) et X2 ([0 ; 1]). La variable cible Y est binaire {pos, neg}.

Le premier ensemble « data2 » correspond à une liaison fonctionnelle relativement simple, elle est

définie de la manière suivante :

Si (0.1 * X2 > X1²) Alors Y = pos Sinon Y = neg

Le concept sous-jacent à « data4 » est un peu plus complexe

Si (X1 < 0)

Alors Si (0.5 * X2 > 0.5 - |X1|) Alors Y = pos Sinon Y = neg

Sinon Si (X2 > X1 et 1 – X2 > X1) Alors Y = pos Sinon Y = neg

Bien évidemment, nous ne disposons pas de ces informations dans les études réelles. Nous

comptons justement sur le système d’apprentissage pour tenter de les reconstituer ou, tout du

moins, de les approximer. L’idée de cet exemple pratique est de situer le comportement du

perceptron sur des données dont on connaît les tenants et aboutissants.

3 Perceptron avec R – Package « nnet »

Notre étude est rendue facile par le très faible nombre de descripteurs (p = 2) qui permet de projeter

les observations dans le plan et, ainsi, d’identifier le bon paramétrage. En pratique, cette démarche

n’est pas tenable, surtout lorsque le nombre de descripteurs augmente (p > 2). Il faut définir une

stratégie générique qui permet d’identifier automatiquement le bon nombre de neurones sur un

jeu de données. R est un outil privilégié pour cela. Tout simplement parce qu’il est très facile de

programmer des actions complexes avec quelques lignes de codes bien senties.

Dans cette section, dans un premier temps, nous reproduisons l’étude ci-dessus (sur « data2 ») pour

décrire la mise en œuvre du perceptron multicouche avec le package nnet. Puis, dans un second

temps, nous mettons en place un dispositif pour spécifier le nombre adéquat de neurones de la

couche cachée pour le fichier « data4 » où le concept à apprendre est plus complexe c.-à-d. il faut

plus de droites séparatrices, mais combien ?

3.1

La procédure nnet du package éponyme

3.1.1

Importation et définition des échantillons

Nous avons transformé la feuille Excel « data2 » en un fichier texte avec séparateur tabulation. Nous

l’importons dans R avec la commande read.table(). Nous affichons les caractéristiques des variables

avec la commande summary().

#loading the data file "data2"

data2 <- read.table(file="artificial2d_data2.txt",sep="\t",dec=".",header=T)

#number of instances

n <- nrow(data2)

print(n)

#summary

print(summary(data2))

Nous obtenons :

Nous partitionnons aléatoirement les données en deux échantillons de taille égale : la première, dite

d’apprentissage, servira à la construction du réseau ; la seconde, dite de test, sera utilisée pour

évaluer les performances en généralisation.

#splitting in train and test samples

set.seed(5)

index <- sample(n,1000)

data2.train <- data2[index,]

data2.test <- data2[-index,]

print(summary(data2.train))

print(summary(data2.test))

Publicité

La commande set.seed() permet de fixer la valeur d’initialisation du générateur de nombres

aléatoires. Ainsi, lecteur devrait obtenir des résultats identiques à ceux décrits dans ce tutoriel.

Nous constatons que les caractéristiques des deux échantillons sont très proches. Ceci veut dire que

la subdivision ayant été réalisée de manière aléatoire. Il faudrait s’inquiéter dans le cas contraire.

3.1.2 Construction du modèle

Nous utilisons le package « nnet » dans ce tutoriel. Il présente l’avantage d’être particulièrement

simple à utiliser. Ses sorties sont en revanche très sobres, voire laconiques. Nous pouvons quand

même obtenir les poids synaptiques du perceptron.

#loading the nnet package

library(nnet)

#multilayer perceptron, 2 neurons into the hidden layer

set.seed(10)

model2 <- nnet(Y ~ .,skip=FALSE,size=2,data=data2.train,maxit=300,trace=F)

print(model2)

print(summary(model2))

La commande nnet() construit le réseau à partir de l’échantillon d’apprentissage. Nous fixons le

nombre d’itérations maximum à 300 (maxit = 300) pour nous assurer de la stabilité des résultats. La

valeur par défaut (maxit = 100) n’est apparemment pas suffisante pour nos données. L’option « skip

= FALSE » indique la présence d’une couche cachée, et « size = 2 » définit le nombre de neurones.

La variable cible étant binaire, nous avions constaté que les deux sorties étaient strictement

complémentaires. De ce fait, R n’en décrit qu’une seule. Nous obtenons le réseau suivant :

bias =

const

-0.83

const

178.04

-0.29

h1

-226.91

output

13.06

-2.04

-9.28

-1.90

X1 =

input 1

X2 =

input 2

-255.67

h2

3.1.3 Evaluation

Nous élaborons une fonction pour l’évaluation. Elle prend en entrée les données à utiliser et le

modèle. Ce dernier se charge de produire la prédiction à l’aide de la commande predict(). Nous

construisons par la suite la matrice de confusion à partir de la confrontation entre la cible observée

et la prédiction. Nous en déduisons le taux d’erreur (taux de mauvais classement).

#evaluating the model

err.rate <- function(test.data,model){

#prediction on the test set

pred <- predict(model,newdata=test.data,type="class")

#confusion matrix

mc <- table(test.data$Y,pred)

#error rate

error <- 1-sum(diag(mc))/sum(mc)

#output

return(error)

}

Appliquée sur l’échantillon test (1000 observations) et le réseau construit dans la section précédente,

#printing the error rate on the test set

print(err.rate(data2.test,model2))

Publicité

Nous obtenons un taux d’erreur de 0.004.

5.2 Algorithme pour le paramétrage automatique du réseau

Notre objectif dans cette section est de mettre en place une procédure très simple de détection du

nombre « optimal » de neurones de la couche cachée. Elle doit être générique c.-à-d. s’appliquer

quel que soit le nombre de variables prédictives. Nous l’utiliserons alors pour apprendre la fonction –

le concept - associant (X1, X2) à Y pour les données « data4 ».

5.2.1 Algorithme de paramétrage automatique

Les données sont subdivisées en 2 échantillons : apprentissage et validation. Nous construisons

différentes hypothèses sur l’échantillon d’apprentissage, dans notre cas il s’agit du nombre de

neurones différents dans la couche cachée (1, ce qui revient à un perceptron simple ; puis 2, puis 3,

etc.), que nous évaluons sur l’échantillon test. La solution correspond à la configuration la plus

simple, c.-à-d. avec le plus petit nombre de neurones, permettant de minimiser le taux d’erreur en

généralisation.

Le programme tient en une boucle passant en revue différentes valeurs de « k », nombre de

neurones dans la couche cachée. Nous avons fixé ex ante le nombre maximal de neurones à tester (K

= 20). C’était le plus simple à implémenter. Mais nous pouvons également imaginer des stratagèmes

plus élaborés (ex. s’arrêter dès que le taux d’erreur ne décroît plus).

#detecting the right number of neurons in the hidden layer

K <- 20

res <- numeric(K)

for (k in 1:K){

set.seed(10)

model <- nnet(Y ~ .,skip=FALSE,size=k,data=data4.train,maxit=300,trace=F)

error <- err.rate(data4.test,model)

res[i] <- error

}

Le nom de la variable cible doit être Y dans ce petit programme. En revanche, il n’y a aucune

limitation concernant le nombre et le nom des prédicteurs.

5.2.2 Application sur les données « data4 »

Pour vérifier l’efficacité du programme, nous l’appliquons sur les données « datat4 » dont nous ne

sommes pas censés connaître le nombre adéquat de neurones (voir Section 2). Nous montrons ici le

processus complet : l’importation des données, leur subdivision en apprentissage-validation, la mise

en œuvre de la détection, l’affichage des résultats (nombre de neurones vs. taux d’erreur en

validation) dans un graphique.

#loading the data file "data4"

data4 <- read.table(file="artificial2d_data4.txt",sep="\t",dec=".",header=T)

#splitting in train and test samples

data4.train <- data4[index,]

data4.test <- data4[-index,]

#detecting the right number of neurons in the hidden layer

K <- 20

res <- numeric(K)

for (k in 1:K){

set.seed(10)

model <- nnet(Y ~ .,skip=FALSE,size=k,data=data4.train,maxit=300,trace=F)

#print(model)

error <- err.rate(data4.test,model)

res[k] <- error

}

plot(1:K,res,type="b",xlab="Neurons into the hidden layer",ylab="Test error rate")

Nous obtenons une courbe particulièrement édifiante. A partir de « k = 4 » neurones dans la couche

intermédiaire, nous reproduisons parfaitement le concept.

e

t

a

r

r

o

r

Publicité

r

e

t

s

e

T

5

3

.

0

0

3

.

0

5

2

.

0

0

2

.

0

5

1

.

0

0

1

.

0

5

0

.

0

0

0

.

0

5

10

15

Neurons into the hidden layer

2

0

On serait tenté de parler de sur-apprentissage pour k = 14. Il faut surtout y voir un artefact lié aux

fluctuations d’échantillonnage. Il serait plus approprié de passer par la validation croisée pour

obtenir des résultats plus stables, mieux lissées. Mais, nous ne souhaitons pas alourdir l’exposé au

risque de semer le lecteur en cours de route dans le contexte de cet exemple pratique.

5.2.3 Vérification et représentation graphique

Est-ce que vraiment k = 4 neurones est la bonne solution ici ? Pour le savoir, et puisque que nous

n’avons que 2 descripteurs, nous projetons les observations étiquetées dans le plan.

#graphical representation of the instances

plot(data4$X1,data4$X2, pch = 21, bg = c("blue","red")[unclass(data4$Y)], main="Data4 problem")

Oui, il faut clairement une combinaison de 4 droites séparatrices pour isoler les « pos » des « neg ».

Data4 problem

2

X

$

4

Publicité

a

t

a

d

0

.

1

8

.

0

6

.

0

4

.

0

2

.

0

0

.

0

(4)

(1)

-0.4

-0.2 (2)

0.0

(3)

data4$X1

0.2

0.4

5.3 Sélection de variables et paramétrage du réseau

Nous nous sommes placés dans une configuration très favorable dans cet exemple. Nous avions

un nombre réduit de variables prédictives et nous savions à l’avance qu’elles étaient toutes

pertinentes. Dans les études réelles, la situation est un peu plus corsée. Il faut à la fois détecter

les variables adéquates et identifier la bonne architecture du réseau.

A priori, la généralisation de notre approche à ce type de situation ne pose pas de problème. Il suffit

de mettre en place une double boucle imbriquée permettant de combiner la recherche des bonnes

variables prédictives et du nombre de neurones de la couche intermédiaire. Attention cependant au

sur apprentissage. En multipliant les hypothèses à tester, nous augmentons le risque de trouver des

solutions faussement optimales. De plus, l’échantillon de test tel que nous l’avons définie dans notre

démarche devient partie prenante de l’apprentissage puisqu’il sert à discerner la meilleure solution

parmi les très nombreuses combinaisons « variables – architecture » qu’on lui a soumises.

De fait, il faudrait plutôt prévoir 3 échantillons dans ce cas : le premier, apprentissage, sert à

l’apprentissage des modèles ; le second, validation (ou « sélection » même serait-on tenté de dire),

sert à sélectionner le meilleur modèle ; le troisième, test, ne sert qu’à mesurer les performances en

prédiction du modèle sélectionné. Ce dernier, n’intervenant qu’en dernier recours et n’ayant jamais

participé au processus d’apprentissage/sélection, permet ainsi d’obtenir une estimation non biaisée

des performances en prédiction du modèle sélectionné.

6 Conclusion

Le perceptron bénéficie d’une capacité très favorable en apprentissage supervisé, à juste titre au

regard de ses performances. Même si ces dernières années, si nous réfèrons aux publications dans

les revues et les conférences spécialisées, les méthodes ensemblistes et les SVM (support vector

machine, de manière plus générique les méthodes à noyaux « kernel machines ») semblent

bénéficier d’une plus grande popularité au sein de la communauté du data mining (oui, les effets de

mode sont partout, y compris dans la recherche). Il semble que ce soit moins vrai en ce qui concerne

leur utilisation dans les études réelles.