Author

TRA BI

Introduction à la fonction kmeans

On peut toujours trouver légitime de partager en paquets un ensemble de points même régulièrement répartis dans l’espace. Le problème est de ne pas faire d’erreurs grossières, lesquelles se voient bien en dimension 2, mais se cachent sans peine en dimension quelconque

On rappelle que la méthode des K-means consiste à faire évoluer la constitution des K groupes d’une partition des individus en les associant au centre de groupe le plus proche et en mettant à jour ces centres de classes en fonction des nouveaux groupes constitués.

La fonction kmeans est une routine permettant d’aborder un autre type de problème de clustering : le nombre de classes à former étant fixé à K, on cherche à créer K groupes d’individus aussi homogènes que possible. Etant donné une partition de départ en K groupes, on détermine pour chaque groupe son centre de gravité puis on reforme les groupes en associant ensemble les points qui sont les plus proches d’un centre de gravité. La procédure est itérée jusqu’à satisfaction d’un critère d’arrêt (généralement la stabilisation des groupes). Il faut bien comprendre qu’à l’issue des itérations, on espère avoir une partition de bonne qualité, mais il n’y a aucune garantie d’optimalité globale (par rapport à l’inertie intraclasse)

La fonction kmeans implémente le schéma des méthodes des centres mobiles et en propose plusieurs déclinaisons :

Elle prend en entrée les points et les centres

de classes initiaux

Code
D <- matrix(c(0,0,0,1,0,2,2,1,3,1,3,2,0,5,1,4,-1,4), ncol = 2, byrow = TRUE)
C <- matrix(c(0,4,3/2,2.4,1,4/5), ncol = 2, byrow = TRUE)
kmeans(D,C)
K-means clustering with 3 clusters of sizes 3, 3, 3

Cluster means:
      [,1]     [,2]
1 0.000000 4.333333
2 2.666667 1.333333
3 0.000000 1.000000

Clustering vector:
[1] 3 3 3 2 2 2 1 1 1

Within cluster sum of squares by cluster:
[1] 2.666667 1.333333 2.000000
 (between_SS / total_SS =  85.2 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

il est aisé d’observer les classes formées en jouant sur les couleurs des points :

Code
KM <- kmeans(D,C)
couleur <- c("blue", "red", "orange2")
plot(D, asp = 1, pch = 19, col = couleur[KM$cluster])
points(KM$centers, pch = 8, col = couleur, cex = 2)

  • l’object cluster contient l’affectation des points aux classes et
  • l’objet centers fournit les coordonnées des centres de classes.

Elle prend en entrée les points et le nombre de classes

L’argument des centres de classes initiaux peut être remplacé par un simple entier, le nombre de classes à former. Dans ce cas, les centres de classes initiaux sont choisis aléatoirement.

le résultat est fortement lié aux points initiaux. Afin d’observer cela, reprenez les données utilisées en 2.3 et faites plusieurs essais pour observer les variations du R2

Code
 a<- kmeans(D,3)
a$betweenss/a$totss # la valeur du R2
[1] 0.8516484

La fonction kmeans(x, centers, iter.max = 10, nstart = 1, algorithm = c("Hartigan-Wong", "Lloyd", "Forgy","MacQueen"), trace=FALSE) propose 4 versions classiques de la méthode des K-means : la version de Lloyd puis celle de Forgy qui diffèrent sur le choix des centres initiaux, puis les versions de Mac Queen et de Hartigan & Wong qui permettent de garantir la production de K classes

Le risque des centres initiaux mal choisis

Code
set.seed(11103)
C1 <- mvrnorm(120, c(0,0), matrix(c(0.5,0,0,0.5),2,2))
C2 <- mvrnorm(40, c(4,2), matrix(c(0.15,0,0,0.15),2,2))
C3 <- mvrnorm(40, c(4,-2), matrix(c(0.15,0,0,0.15),2,2))
C4 <- mvrnorm(40, c(-4,3), matrix(c(0.15,0,0,0.15),2,2))
P <- rbind(C1, C2, C3, C4)
CK <- matrix(c(-6,0,2,0,4,4,10,-1), ncol = 2, byrow = TRUE)
Code
kmeans(P, CK)

ce jeu de centre ne nous permet pas d’avoir 4 classes

Code
res4 <- kmeans(P, CK, algorithm ="Lloyd")
res4
K-means clustering with 4 clusters of sizes 40, 120, 80, 0

Cluster means:
         [,1]        [,2]
1 -4.02910907  2.96498129
2  0.09842884  0.01031509
3  3.99568399 -0.08277690
4         NaN         NaN

Clustering vector:
  [1] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 [38] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 [75] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[112] 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[186] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[223] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

Within cluster sum of squares by cluster:
[1]  12.94407 128.39487 348.33175   0.00000
 (between_SS / total_SS =  81.1 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

On obtient 3 classes en sortie

Observons dans l’objet centers le nombre de centres pour lesquels aucune coordonnée n’a pu être calculée

Code
res4$centers
         [,1]        [,2]
1 -4.02910907  2.96498129
2  0.09842884  0.01031509
3  3.99568399 -0.08277690
4         NaN         NaN

Testons la nullité de la taille des clusters

Code
res4$size
[1]  40 120  80   0

Transformons notre commande afin d’obtenir les groupes formés au bout d’une seule itération et représentons-les en associant à chaque groupe une couleur distincte.

Code
CK <- res4$centers[-4,]
a <- kmeans(P, CK, algorithm ="Lloyd")
a
K-means clustering with 3 clusters of sizes 40, 120, 80

Cluster means:
         [,1]        [,2]
1 -4.02910907  2.96498129
2  0.09842884  0.01031509
3  3.99568399 -0.08277690

Clustering vector:
  [1] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 [38] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 [75] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[112] 2 2 2 2 2 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[149] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[186] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[223] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

Within cluster sum of squares by cluster:
[1]  12.94407 128.39487 348.33175
 (between_SS / total_SS =  81.1 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      
Code
a$centers == CK
  [,1] [,2]
1 TRUE TRUE
2 TRUE TRUE
3 TRUE TRUE

nous avons concervé nos centres


représentez-les en associant à chaque groupe une couleur distincte.

Code
couleur <- c("blue", "red", "orange2")
plot(P, asp = 1, pch = 19, col = couleur[a$cluster])
points(a$centers, pch = 8, col = couleur, cex = 2)

graphiquement on remarque que 3 classes n’est pas le nombre de classe idéale pour notre base de donnée. La raison de la dimunition du nombre de classe était que : nos centres initiaux étaient mal choisis

La version de Forgy propose une amélioration en choisissant les centres de classes aléatoirement parmi les individus. Ceci limite le risque de vidage de classes. La meilleure garantie reste implémentée dans les variantes de Mac Queen et de Hartigan-Wong qui mettent à jour les centres de classes dès qu’un point change de groupe.

on reprent donc avec vieux père Forgy en choisissant 4 points aléatoirement parmi les individus

Code
b <- kmeans(P, 4, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green")
plot(P, asp = 1, pch = 19, col = couleur[b$cluster])
points(b$centers, pch = 8, col = couleur, cex = 2)

Stabilité des classes construites

Il est assez facile d’observer une certaine diversité comme illustré sur la figure ci-dessous (variante de Forgy)

Code
par(mfrow = c(1,3))
b <- kmeans(P, 5, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green", "yellow")
plot(P, asp = 1, pch = 19, col = couleur[b$cluster])
points(b$centers, pch = 8, col = couleur, cex = 2)


b <- kmeans(P, 5, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green","yellow")
plot(P, asp = 1, pch = 19, col = couleur[b$cluster])
points(b$centers, pch = 8, col = couleur, cex = 2)

b <- kmeans(P, 5, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green", "yellow")
plot(P, asp = 1, pch = 19, col = couleur[b$cluster])
points(b$centers, pch = 8, col = couleur, cex = 2)

Afin de maximiser les chances d’identifier un partitionnement présentant la variance intragroupe la plus faible, il est possible de réaliser un grand nombre d’essais et de conserver la partition d’inertie intraclasse minimale. C’est l’objet de l’argument nstart de la fonction kmeans.

maintenant étudions la variabilité en utilisant l’argument nstart avec une grande valeur (e.g. 100 ou 1000)

Code
par(mfrow = c(1,3))
b <- kmeans(P, 5, nstart = 1000, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green", "yellow")
plot(P, asp = 1, pch = 19, col = couleur[b$cluster])
points(b$centers, pch = 8, col = couleur, cex = 2)


c <- kmeans(P, 5, nstart = 1000, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green","yellow")
plot(P, asp = 1, pch = 19, col = couleur[c$cluster])
points(c$centers, pch = 8, col = couleur, cex = 2)

d <- kmeans(P, 5, nstart = 1000, algorithm ="Forgy")
couleur <- c("blue", "red", "orange2", "green", "yellow")
plot(P, asp = 1, pch = 19, col = couleur[d$cluster])
points(d$centers, pch = 8, col = couleur, cex = 2)

on observe qu’il y a pratiquement pas de variabilité dans les sorties produites ?

on vérifie vite fait pour voir

Code
all((sort(b$centers)==sort(c$centers))&(sort(c$centers)==sort(d$centers)))
[1] TRUE

donc nos sortie sont stable car toutes les kmeans ont les mêmes centroïdes

Performances des variantes des kmeans

La méthode de Lloyd présente le risque de construire des classes vides. De ce point de vue, celle de Forgy, Mac Queen et Hartigan & Wong sont meilleures. Ces deux dernières présentent la particularité de recalculer le centre de classes dès qu’une classe est modifiée. Quid de leurs performances ? La méthode de Hartigan & Wong est réputée être la meilleure.

Code
big <- rbind(iris, iris, iris, iris, iris, iris, iris, iris)
T <- sapply(seq(from=100,to=1000,by=100),function(n) {system.time(
  kmeans(big[sort(sample(1:nrow(big),n)),-5], 4, nstart = 100, algorithm ="MacQueen")
)})
y <- T[3,]
plot(seq(from=100,to=1000,by=100),y)
lines(seq(from=100,to=1000,by=100),y)

L’hypothèse d’une complexité linéaire semble-t-elle vraisemblable ?

je sais pas (vous même voyez)

Parallélisation du code

R permet de tirer profit des machines dotées d’une architecture multicœurs. Le fait de trouver un intérêt à la multiplication des essais avec la fonction kmeans nous fournit un terrain d’expérimentation des bienfaits de la parallélisation de code dans le cadre relativement simple d’une machine multicœurs.

Code
library(parallel)
detectCores()

X<-big[1:150,-5]
T2 <- system.time( KM <- kmeans(X,50,iter.max=300,nstart=128))

system.time(mclapply(rep(128/detectCores(), detectCores()), function(ess)
kmeans(X, 50, iter.max = 300, nstart = ess), mc.cores = detectCores())) -> T3