4.3 Études de cas
4.3.3 Illustration relationnelle : les données lesmis
Le jeu de données lesmis
7est un graphe représentant les liens entre les personnages du roman Les Misérables de Victor Hugo. Un sommet correspond à un personnage, une arête signie que les 2 personnages reliés apparaissent dans un même chapitre.
Figure 4.20 Graphique de co apparition des personnages des Misérables
Pour pouvoir utiliser ces données pour le cas 'relational' de l'algorithme, une matrice de dissimilarité a été calculée grâce à la fonction shortest.path du package igraph. À partir du graphe 4.20, une mesure de dissimilarité est dénie, qui correspond au chemin le plus court entre les 2 personnages : cette dissimilarité est donc constituée de valeurs entières et positives.
mis . som <- trainSOM (x.data= dissim . lesmis , type =" relational ")
La distribution des personnages sur la carte est accessible soit avec un tableau, résultat de la fonction table de R :
7source :http://people.sc.fsu.edu/~jburkardt/datasets/sgb/jean.dat
table( mis . som$clustering )
## 1 2 3 4 5 6 7 9 11 12 14 15 18 20 21 22 24 25
## 6 7 3 2 8 1 3 3 3 7 3 3 2 6 6 5 3 6
Soit graphiquement, en appelant la fonction plot.somRes (voir gure 4.21) ou encore en utilisant une palette de couleur sur le graphe de départ (gure 4.22).
Figure 4.21 Répartition des personnages sur la carte
Figure 4.22 Visualisation de la classication sur le graphe
La gure 4.23 représente le graphe projeté sur la grille : la taille de chaque sommet est proportionnelle au
nombre de personnages classés dans le neurone et l'épaisseur de l'arête entre 2 neuronnes dépend du nombre
d'arêtes entre personnages des 2 classes sur la gure 4.20.
Figure 4.23 Graphe projeté sur la grille
Les prols des observations peuvent, entre autres, être visualisés par un graphique de type barplot,
présenté en gure 4.24.
Figure 4.24 Prols des personnages
Etant donné le faible nombre d'observations dans la majorité des classes, il devient pertinent de procéder à une super classication :
sc. mis <- superClass ( mis .som , k =6)
Cela permet d'obtenir un nombre de classes réduit, qui produit donc des résultats plus proches de la réalité.
Outre le dendrogramme traditionnel produit par la graphique dendrogram (voir la gure iris-sc-dendro
pour exemple), SOMbrero propose également une version en 3 dimensions de celui-ci via le graphique
dendro3d (gure 4.25).
Figure 4.25 Dendrogramme en 3 dimensions de la super classication
Ajouter l'information de la super classication au graphe de départ permet d'idener (gure 4.26) :
• deux des personnages principaux, Javert et Valjean, qui ont un rôle central dans le roman (super classe 1) ;
• les personnages qui interviennent uniquement dans l'histoire de l'évêque Myriel (super classe 2) ;
• Fantine et les personnages de son histoire (super classe 3) ;
• Marius et sa famille (Mme Pontmercy, sa mère, le Lieutenant Gillenormand, son père, etc) mais éga-lement Cosette, avec qui il aura une aventure (super classe 4) ;
• Gavroche, l'enfant abandonné des Thénardiers, et les personnages relatifs à son histoire (super classe 5) ;
• la famille Thénardier (super classe 6).
Figure 4.26 Visualisation de la super classication sur le graphe
Chapitre 5
Conclusion
Ainsi, j'ai eectué mon stage de n d'études Génie Informatique et Statistique pour le compte du labo-ratoire SAMM. Cette longue immersion dans le monde de l'entreprise m'a permis de mettre et pratique et d'approfondir mes connaissances théoriques, notamment en programmation et plus particulièrement en programmation R. Durant cette période, je me suis également confrontée aux réalités du monde du travail.
La réalisation du package SOMbrero m'a donné le loisir d'accomplir diverses tâches, principalement : acquisition de connaissances sur la méthode des cartes auto organisatrices, développement des scripts et documentations R, rédaction de guides utilisateurs et progrès sur le plan de la communication orale, dûs à de multiples présentations de mon travail en interne.
Cette expérience enrichissante et complète m'a oert une bonne préparation à mon insertion profession-nelle et m'a rassurée quant à mes capacités dans le domaine informatique. En outre, j'ai pu aborder un aspect important du monde du travail actuel à savoir le travail à distance puisqu'une de mes encadrantes était basée à Paris.
Enn, je tiens à souligner les bonnes conditions, matérielles et humaines, dans lesquelles s'est déroulé
mon stage.
Bibliographie
[1] R Development Core Team. R : A Language and Environment for Statistical Computing. Vienna, Austria, 2012. ISBN 3-900051-07-0.
[2] L. Bendhaïba, M. Olteanu, and N. Villa-Vialaneix. SOMbrero : Cartes auto-organisatrices stochastiques pour l'intégration de données décrites par des tableaux de dissimilarités. In 2èmes Rencontres R, pages 12, Lyon, France, 2013.
[3] T. Kohonen. Self-Organizing Maps, 3rd Edition, volume 30. Springer, Berlin, Heidelberg, New York, 2001.
[4] M. Cottrell, P. Letremy, and E. Roy. Analyzing a contingency table with Kohonen maps : a factorial correspondence analysis. In Proceedings of IWANN'93, J. Cabestany, J. Mary, A. Prieto (Eds.), Lecture Notes in Computer Science, pages 305311. Springer Verlag, 1993.
[5] M. Olteanu, N. Villa-Vialaneix, and M. Cottrell. On-line relational som for dissimilarity data. In P.A.
Estevez, J. Principe, P. Zegers, and G. Barreto, editors, Advances in Self-Organizing Maps (Proceedings of WSOM 2012), volume 198 of AISC (Advances in Intelligent Systems and Computing), pages 1322, Santiago, Chile, 2012. Springer Verlag, Berlin, Heidelberg.
[6] C. Genolini. Construire un package. Classique ou S4. Technical report, 2009.
Annexe A
calculateRadius <- function( the .grid, radius .type , ind .t, maxit ) {
## TODO: implement other radius types
# ind.t: iteration index
method =" euclidean "))[ the . neuron ,]the . nei <- which( the .dist <=1) } else {
the . dist <- as.matrix( dist ( the .grid $coord , diag=TRUE , upper=TRUE , method =" maximum "))[ the . neuron ,]
the . nei <- which( the .dist <= radius )
# Functions to manipulate objects in the input space
distEuclidean <- function(x,y) { sqrt(sum((x-y) ^2) )
}
distRelationalProto <- function( proto1 , proto2 , x.data) { -0.5*t( proto1 - proto2 )%*%x.data%*%( proto1 - proto2 )
}
calculateProtoDist <- function( prototypes , the .grid, type , complete=FALSE , x.data= NULL ) {
if (! complete) {
all. nei <- sapply(1:prod( the .grid $ dim),selectNei , the .grid= the .grid, radius =1) all. nei <- sapply(1:prod( the .grid $ dim), function( neuron )
setdiff(all. nei [[ neuron ]], neuron )) if ( type!=" relational ") {
# euclidean case
distances <- sapply(1:prod( the .grid $ dim), function( one . neuron ) { apply( prototypes [all. nei [[ one . neuron ]] ,] ,1 , distEuclidean ,
y= prototypes [ one . neuron ,]) } })else {
distances <- sapply(1:prod( the .grid $ dim), function( one . neuron ) { apply( prototypes [all. nei [[ one . neuron ]] ,] ,1 , distRelationalProto ,
proto2 = prototypes [ one . neuron ,], x.data=x.data) })if (sum(unlist( distances ) <0) >0)
warning(" some of the relational 'distances ' are negatives \n plots , qualities , super - clustering ... may not work!",
immediate .= TRUE , call.= TRUE ) } }else {
if ( type ==" relational ") {
# non euclidean case
distances <- apply( prototypes ,1,function( one . proto ) {
apply( prototypes , 1, distRelationalProto , proto2 = one .proto , x.data=x.data)
})if (sum( distances <0) >0)
warning(" some of the relational 'distances ' are negatives \n plots , qualities , super - clustering ... may not work!",
immediate .= TRUE , call.= TRUE )
} else distances <- as.matrix( dist ( prototypes , upper=TRUE , diag= TRUE )) }
distances }
## Functions used during training of SOM
# Step 2: Preprocess data ("korresp" case)
korrespPreprocess <- function( cont .table) {both . profiles <- matrix(0, nrow=nrow( cont .table)+ncol( cont .table),
# Best column to complete row profiles
best .col <- apply( both . profiles [1:nrow( cont .table), 1:ncol( cont .table)], 1,which.max)
both . profiles [1:nrow( cont .table), (ncol( cont .table) +1) :ncol( both . profiles )] <-both . profiles [ best .col+nrow( cont .table),
(ncol( cont .table) +1) :ncol( both . profiles )]
# Best row to complete col profiles
best .row <- apply( both . profiles [(nrow( cont .table) +1) :
(nrow( cont .table)+ncol( cont .table)),
(ncol( cont .table) +1) :
rownames( both . profiles ) <- c(rownames( cont .table),colnames( cont .table)) colnames( both . profiles ) <- c(colnames( cont .table),rownames( cont .table)) return( both . profiles )
}
# Step 3: Initialize prototypes
initProto <- function( parameters , norm .x.data, x.data) { if (is.null( parameters$proto0 )) {
if ( parameters$init . proto ==" random ") { if ( parameters$type ==" relational ") {
prototypes <- t(apply(matrix(runif(prod( parameters$the .grid $dim, nrow( norm .x.data))), nrow=prod( parameters$the .grid $ dim)), 1, function(x)x/ sum(x)))
} else {
# both numeric and korresp
prototypes <- sapply(1:ncol( norm .x.data), function( ind ){
runif(prod( parameters$the .grid $ dim), min=min( norm .x.data[, ind ]) , max=max( norm .x.data[, ind ]))}) } }else if ( parameters$init . proto ==" obs ") {
if ( parameters$type ==" korresp "| parameters$type ==" numeric ") { prototypes <- norm .x.data[sample(1:nrow( norm .x.data),
prod( parameters$the .grid $ dim), replace= TRUE ) ,]
} else if ( parameters$type ==" relational ") {
prototypes <- matrix(0, nrow=prod( parameters$the .grid $ dim), ncol=ncol( norm .x.data))
prototypes [cbind(1:nrow( prototypes ),
sample(1:ncol( prototypes ),nrow( prototypes ), replace= TRUE ))] <- 1
} } } else {
prototypes <- switch( parameters$scaling ,
" unitvar "=scale( parameters$proto0 ,
center =apply(x.data,2,mean), scale=apply(x.data,2,sd)),
" center "=scale( parameters$proto0 ,
center =apply(x.data,2,mean), scale= FALSE ),
" none "=as.matrix( parameters$proto0 ),
" chi2 "=as.matrix( parameters$proto0 )) }return( prototypes )
}
# Step 5: Randomly choose an observation
selectObs <- function( ind .t, ddim , type ) {oneObsAffectation <- function(x.new, prototypes , type , x.data= NULL ) {
if ( type ==" relational ") {
the . neuron <- which.min( prototypes %*%x.
new-0.5* diag( prototypes %*%x.data%*%
t( prototypes )))
} else the . neuron <- which.min(apply( prototypes , 1, distEuclidean , y=x.new)) the . neuron
}
# Step 7: Update of prototypes
prototypeUpdate <- function(type , the .nei , epsilon , prototypes , rand .ind , sel . obs ) {
# Step 8: calculate intermediate energy
# TODO: It would probably be better to implement a function 'distEltProto'
calculateClusterEnergy <- function( cluster , x.data, clustering , prototypes ,parameters , radius ) {
if ( parameters$type ==" numeric " || parameters$type ==" korresp ") { if ( parameters$radius . type ==" letremy ") {
the . nei <- selectNei ( cluster , parameters$the .grid, radius ) if (sum( clustering %in% the . nei ) >0) {
return(sum((x.data[which( clustering %in% the . nei ) ,]-outer(rep(1,sum( clustering %in% the . nei )),
prototypes [ cluster ,]) ) ^2) ) } }
} else if ( parameters$type ==" relational ") { if ( parameters$radius . type ==" letremy ") {
the . nei <- selectNei ( cluster , parameters$the .grid, radius ) if (sum( clustering %in% the . nei ) >0) {
return(sum( prototypes %*%x.data[,which( clustering %in% the . nei )] -0.5*
diag( prototypes %*%x.data%*%t( prototypes )))) } }
} }
calculateEnergy <- function(x.data, clustering , prototypes , parameters , ind .t) { if ( parameters$type ==" numeric " || parameters$type ==" korresp ") {
if ( parameters$radius . type ==" letremy ") {
radius <- calculateRadius ( parameters$the .grid, parameters$radius .type , ind .t, parameters$maxit )
return(sum(unlist(sapply(1:nrow( prototypes ), calculateClusterEnergy , x.data=x.data, clustering = clustering ,
prototypes = prototypes , parameters = parameters , radius = radius )))/
nrow(x.data)/ nrow( prototypes )) } }else if ( parameters$type ==" relational ") { if ( parameters$radius . type ==" letremy ") {
radius <- calculateRadius ( parameters$the .grid, parameters$radius .type , ind .t, parameters$maxit )
return(sum(unlist(sapply(1:nrow( prototypes ), calculateClusterEnergy , x.data=x.data, clustering = clustering ,
prototypes = prototypes , parameters = parameters , radius = radius )))/
nrow(x.data)/ nrow( prototypes )) } }
}
##### Main function
################################################################################
trainSOM <- function (x.data, ...) { param .args <- list(...)
## Step 1: Parameters handling
if (!is.matrix(x.data)) x.data <- as.matrix(x.data, rownames. force = TRUE )
# Default dimension: nb.obs
/10 with minimum equal to 5 and maximum to 10
if (is.null( param .args $dimension )) {if (!is.null( param .args $type ) && param .args $type ==" korresp ") param .args $dimension
<-c(max(5,min(10 ,ceiling(sqrt((nrow(x.data)+ncol(x.data))/10) ))), stop(" data do not match chosen SOM type (' relational ')\n", call.= TRUE )
# Initialize parameters and print
parameters <- do.call(" initSOM ", param .args) if ( parameters$verbose ) {
cat("Self - Organizing Map algorithm ...\ n") print. paramSOM ( parameters )
}
# Check proto0 also now that the parameters have been initialized
if (!is.null( param .args $proto0 )) {if (( param .args $type ==" korresp ")&&
(!identical (dim( param .args $proto0 ),
as.integer(c(prod( param .args $dimension ), ncol(x.data)+nrow(x.data)))))) { stop(" initial prototypes dimensions do not match SOM parameters :
in the current SOM , prototypes must have ", prod( param .args $dimension ), " rows and ",
ncol(x.data)+nrow(x.data), " columns \n", call.= TRUE ) } else if (!identical (dim( param .args $proto0 ),
as.integer(c(prod( param .args $dimension ), ncol(x.data))))) {
stop(" initial prototypes dimensions do not match SOM parameters : in the current SOM , prototypes must have ",
prod( param .args $dimension ), " rows and ", ncol(x.data), " columns \n", call.= TRUE ) } }
## Step 2: Preprocess the data
# Scaling
norm .x.data <- switch( parameters$scaling ,
" unitvar "=scale(x.data, center =TRUE , scale= TRUE ),
" center "=scale(x.data, center =TRUE , scale= FALSE ),
" none "=as.matrix(x.data),
" chi2 "= korrespPreprocess (x.data))
## Step 3: Initialize prototypes
prototypes <- initProto ( parameters , norm .x.data, x.data)
# Step 4: Iitialize backup if needed
if( parameters$nb.save>1) {backup <- list()
backup$prototypes <- list()
backup$clustering <- matrix(ncol= parameters$nb.save, nrow=nrow( norm .x.data)) backup$energy <- vector(length= parameters$nb.save)
backup$steps <- round(seq(1, parameters$maxit ,length= parameters$nb.save) ,0) }
## Main Loop: from 1 to parameters
$maxit
for ( ind .t in 1: parameters$maxit ) {## Step 5: Randomly choose an observation
rand . ind <- selectObs ( ind .t, dim(x.data), parameters$type ) sel . obs <- norm .x.data[ rand .ind ,]
## Step 6: Assignment step
# For the "korresp" type, cut the prototypes and selected observation
if ( parameters$type ==" korresp ") {if ( ind .t%%2==0) {
cur. obs <- sel . obs [1:ncol(x.data)]
cur. prototypes <- prototypes [ ,1:ncol(x.data)]
} else {
cur. obs <- sel . obs [(ncol(x.data) +1) :ncol( norm .x.data)]
cur. prototypes <- prototypes [,(ncol(x.data) +1) :ncol( norm .x.data)]
} }else {
cur. prototypes <- prototypes cur. obs <- sel . obs
}
# Assign
winner <- oneObsAffectation (cur.obs , cur. prototypes , parameters$type , norm .x.data)
## Step 7: Representation step
# Radius value
radius <- calculateRadius ( parameters$the .grid, parameters$radius .type , ind .t, parameters$maxit )
the . nei <- selectNei ( winner , parameters$the .grid, radius )
# TODO: scale epsilon with a parameter???
epsilon <- 0.3/(1+0.2*ind .t/ prod( parameters$the .grid $ dim))
# Update
prototypes [ the .nei ,] <- prototypeUpdate ( parameters$type , the .nei , epsilon , prototypes , rand .ind , sel . obs )
## Step 8: Intermediate backups (if needed)
if ( parameters$nb.save==1) {warning("nb. save can not be 1\n No intermediate backups saved ", immediate .= TRUE , call.= TRUE )
}if ( parameters$nb.save>1) { if( ind .t %in% backup$steps ) {
out . proto <- switch( parameters$scaling ,
" unitvar "=scale( prototypes ,
center =-apply(x.data,2,mean)/ apply(x.data,2,sd),
scale=1/ apply(x.data,2,sd)),
" center "=scale( prototypes ,
center =-apply(x.data,2,mean), scale= FALSE ),
" none "= prototypes ,
" chi2 "= prototypes ) colnames( out . proto ) <- colnames( norm .x.data)
rownames( out . proto ) <- 1:prod( parameters$the .grid $ dim) res <- list(" parameters "= parameters , " prototypes "= out .proto ,
" data "=x.data) class( res ) <- " somRes "
ind .s <- match( ind .t, backup$steps ) backup$prototypes [[ ind .s]] <- out . proto
backup$clustering [, ind .s] <- predict. somRes (res , x.data) backup$energy [ ind .s] <- calculateEnergy ( norm .x.data,
backup$clustering [, ind .s], prototypes , parameters , ind .t) }if ( ind .t== parameters$maxit ) {
clustering <- backup$clustering [, ind .s]
if ( parameters$type ==" korresp ") {
names( clustering ) <- c(colnames(x.data), rownames(x.data)) } else names( clustering ) <- rownames(x.data)
energy <- backup$energy [ ind .s]
} }else if ( ind .t== parameters$maxit ) { out . proto <- switch( parameters$scaling ,
" unitvar "=scale( prototypes ,
center =-apply(x.data,2,mean)/
apply(x.data,2,sd),
scale=1/ apply(x.data,2,sd)),
" center "=scale( prototypes ,
center =-apply(x.data,2,mean), scale= FALSE ),
" none "= prototypes ,
" chi2 "= prototypes )
res <- list(" parameters "= parameters , " prototypes "= out .proto ,
" data "=x.data) class( res ) <- " somRes "
clustering <- predict. somRes (res , x.data) if ( parameters$type ==" korresp ") {
names( clustering ) <- c(colnames(x.data), rownames(x.data)) } else names( clustering ) <- rownames(x.data)
energy <- calculateEnergy ( norm .x.data, clustering , prototypes , parameters , ind .t)
} }
colnames( out . proto ) <- colnames( norm .x.data)
rownames( out . proto ) <- 1:prod( parameters$the .grid $ dim) if ( parameters$nb.save<=1) {
res <- list(" clustering "= clustering , " prototypes "= out .proto ,
" energy "= energy , " data "=x.data, " parameters "= parameters ) } else {
if ( parameters$type ==" korresp ") {
rownames( backup$clustering ) <- c(colnames(x.data), rownames(x.data)) } else rownames( backup$clustering ) <- rownames(x.data)
res <- list(" clustering "= clustering , " prototypes "= out .proto ,
" energy "= energy , " backup "= backup , " data "=x.data,
" parameters "= parameters ) }class( res ) <- " somRes "
return( res ) }
## S3 methods for somRes class objects
################################################################################
print. somRes <- function(x, ...) {
cat(" Self - Organizing Map object ...\ n")
cat(" ", x$parameters$mode, " learning , type :", x$parameters$type ,"\n") cat(" ", x$parameters$the .grid $ dim[1] ,"x",
x$parameters$the .grid $ dim[2] ,
" grid with ",x$parameters$the .grid $topo, " topology \n") }
summary. somRes <- function( object , ...) { cat("\ nSummary \n\n")
cat(" Class : ", class( object ),"\n\n") print( object )
cat("\n Final energy :", object$energy ,"\n") if ( object$parameters$type ==" numeric ") {
cat("\n ANOVA : \n")
res .anova <- as.data.frame(t(sapply(1:ncol( object$ data), function( ind ) { c(round(summary(aov( object$ data[, ind ]~as.factor( object$clustering )))
[[1]][1 ,4] , digits =3) ,
round(summary(aov( object$ data[, ind ]~as.factor( object$clustering ))) [[1]][1 ,5] , digits =8) )
})))names( res .anova) <- c("F", " pvalue ")
res .anova $significativity <- rep("",ncol( object$ data)) res .anova $significativity [ res .anova $" pvalue " <0.05] <- "*"
res .anova $significativity [ res .anova $" pvalue " <0.01] <- "**"
res .anova $significativity [ res .anova $" pvalue " <0.001] <- "***"
rownames( res .anova) <- colnames( object$ data) cat("\n Degrees of freedom : ",
summary(aov( object$ data[ ,1]~as.factor( object$clustering ))) [[1]][1 ,1] ,
"\n\n")
if ( chisq . res$p.value <0.001) sig <- "***"
cat("\n ", chisq . res$method , ":\n\n")
cat(" X- squared : ", chisq . res$statistic , "\n") cat(" Degrees of freedom : ", chisq . res$parameter , "\n") cat(" p- value : ", chisq . res$p.value , "\n") cat(" significativity : ", sig , "\n")
} }
predict. somRes <- function( object , x.new, ...) {
if (is.null(dim(x.new))) x.new <- matrix(x.new,nrow=1, dimnames=list(1,
colnames( object$ data))) if( object$parameters$type!=" korresp ") {
norm .x.new <- switch( object$parameters$scaling ,
" unitvar "=scale(x.new,
center =apply( object$data,2,mean), scale=apply( object$data,2,sd)),
" center "=scale(x.new,
center =apply( object$data,2,mean), scale= FALSE ),
" none "=as.matrix(x.new)) norm . proto <- switch( object$parameters$scaling ,
" unitvar "=scale( object$prototypes ,
center =apply( object$data,2,mean), scale=apply( object$data,2,sd)),
" center "=scale( object$prototypes ,
center =apply( object$data,2,mean), scale= FALSE ),
" none "= object$prototypes ) winners <- apply( norm .x.new, 1, oneObsAffectation ,
prototypes = norm .proto , type = object$parameters$type ,
x.data= object$ data) } else {
if (!identical (as.matrix(x.new), object$ data))
warning(" For 'korresp ' SOM , predict . somRes function can only be called on the original data set \n' object ' replaced ",
call.= TRUE )
norm .x.data <- korrespPreprocess ( object$ data)
winners . rows <- apply( norm .x.data[1:nrow( object$ data) ,1:ncol( object$ data)], 1, oneObsAffectation ,
prototypes = object$prototypes [ ,1:ncol( object$ data)], type = object$parameters$type )
winners . cols <- apply( norm .x.data[(nrow( object$ data) +1) :ncol( norm .x.data), (ncol( object$ data) +1) :ncol( norm .x.data)], 1, oneObsAffectation ,
prototypes = object$prototypes [,(ncol( object$ data) +1) : ncol( norm .x.data)], type = object$parameters$type )
winners <- c( winners .cols , winners . rows ) }winners
}
protoDist . somRes <- function( object , mode=c(" complete "," neighbors "), ...) { mode <- match. arg (mode)
complete <- (mode==" complete ")
if ( object$parameters$type ==" relational ") { x.data <- object$ data
} else x.data <- NULL
distances <- calculateProtoDist ( object$prototypes , object$parameters$the .grid, object$parameters$type , complete, x.data) return( distances )
}
protoDist <- function( object , mode,...) { UseMethod(" protoDist ")
}