Простой подход к назначению кластеров для новых данных после кластеризации k-средних
Я запускаю кластеризацию k-значений на фрейме данных df1, и я ищу простой подход к вычислению ближайшего центра кластера для каждого наблюдения в новом фрейме данных df2 (с теми же именами переменных). Подумайте о df1 как наборе обучения и df2 на тестовом наборе; Я хочу сгруппировать на обучающем наборе и назначить каждую тестовую точку на правильный кластер.
Я знаю, как это сделать с помощью функции apply
и нескольких простых пользовательских функций (предыдущие сообщения по этой теме обычно предлагают нечто подобное):
df1 <- data.frame(x=runif(100), y=runif(100))
df2 <- data.frame(x=runif(100), y=runif(100))
km <- kmeans(df1, centers=3)
closest.cluster <- function(x) {
cluster.dist <- apply(km$centers, 1, function(y) sqrt(sum((x-y)^2)))
return(which.min(cluster.dist)[1])
}
clusters2 <- apply(df2, 1, closest.cluster)
Однако я готовлю этот пример кластеризации для курса, в котором ученики будут незнакомы с функцией apply
, поэтому я бы предпочел, если бы я мог назначить кластеры df2 со встроенной функцией. Существуют ли какие-либо удобные встроенные функции для поиска ближайшего кластера?
Ответы
Ответ 1
Вы можете использовать пакет flexclust, который имеет реализованный метод predict
для k -средств:
library("flexclust")
data("Nclus")
set.seed(1)
dat <- as.data.frame(Nclus)
ind <- sample(nrow(dat), 50)
dat[["train"]] <- TRUE
dat[["train"]][ind] <- FALSE
cl1 = kcca(dat[dat[["train"]]==TRUE, 1:2], k=4, kccaFamily("kmeans"))
cl1
#
# call:
# kcca(x = dat[dat[["train"]] == TRUE, 1:2], k = 4)
#
# cluster sizes:
#
# 1 2 3 4
#130 181 98 91
pred_train <- predict(cl1)
pred_test <- predict(cl1, newdata=dat[dat[["train"]]==FALSE, 1:2])
image(cl1)
points(dat[dat[["train"]]==TRUE, 1:2], col=pred_train, pch=19, cex=0.3)
points(dat[dat[["train"]]==FALSE, 1:2], col=pred_test, pch=22, bg="orange")
![flexclust plot]()
Существуют также методы преобразования для преобразования результатов из функций кластера типа stats::kmeans
или cluster::pam
в объекты класса kcca
и наоборот:
as.kcca(cl, data=x)
# kcca object of family ‘kmeans’
#
# call:
# as.kcca(object = cl, data = x)
#
# cluster sizes:
#
# 1 2
# 50 50
Ответ 2
Что-то, что я заметил как в подходе в вопросе, так и в подходах flexclust, заключается в том, что они довольно медленные (здесь для тестирования и тестирования установлено 1 миллион наблюдений с двумя функциями).
Установка оригинальной модели выполняется достаточно быстро:
set.seed(144)
df1 <- data.frame(x=runif(1e6), y=runif(1e6))
df2 <- data.frame(x=runif(1e6), y=runif(1e6))
system.time(km <- kmeans(df1, centers=3))
# user system elapsed
# 1.204 0.077 1.295
Решение, посланное в вопросе, выполняется медленно при вычислении назначений кластеров тестового набора, поскольку он отдельно вызывает closest.cluster
для каждой тестовой точки:
system.time(pred.test <- apply(df2, 1, closest.cluster))
# user system elapsed
# 42.064 0.251 42.586
Между тем, пакет flexclust, кажется, добавляет много накладных расходов, независимо от того, преобразуем ли мы оборудованную модель с помощью as.kcca
или поместим новую с помощью kcca
(хотя предсказание в конце намного быстрее)
# APPROACH #1: Convert from the kmeans() output
system.time(km.flexclust <- as.kcca(km, data=df1))
# user system elapsed
# 87.562 1.216 89.495
system.time(pred.flexclust <- predict(km.flexclust, newdata=df2))
# user system elapsed
# 0.182 0.065 0.250
# Approach #2: Fit the k-means clustering model in the flexclust package
system.time(km.flexclust2 <- kcca(df1, k=3, kccaFamily("kmeans")))
# user system elapsed
# 125.193 7.182 133.519
system.time(pred.flexclust2 <- predict(km.flexclust2, newdata=df2))
# user system elapsed
# 0.198 0.084 0.302
Похоже, здесь есть еще один разумный подход: использование быстрого решения k-ближайших соседей, например дерева k-d, для поиска ближайшего соседа каждого контрольного теста в наборе кластерных центроидов. Это можно записать компактно и относительно быстро:
library(FNN)
system.time(pred.knn <- get.knnx(km$center, df2, 1)$nn.index[,1])
# user system elapsed
# 0.315 0.013 0.345
all(pred.test == pred.knn)
# [1] TRUE
Ответ 3
Вы можете использовать ClusterR::KMeans_rcpp()
, использовать RcppArmadillo. Он допускает несколько инициализаций (которые могут быть распараллелены, если доступен Openmp). Помимо оптимальных инициализаций, инициализации quantile_init, random и kmeans ++ можно указать центроиды с помощью параметра CENTROIDS. Время работы и сходимость алгоритма можно настроить с помощью параметров num_init, max_iters и tol.
library(scorecard)
library(ClusterR)
library(dplyr)
library(ggplot2)
## Generate data
set.seed(2019)
x = c(rnorm(200000, 0,1), rnorm(150000, 5,1), rnorm(150000,-5,1))
y = c(rnorm(200000,-1,1), rnorm(150000, 6,1), rnorm(150000, 6,1))
df <- split_df(data.frame(x,y), ratio = 0.5, seed = 123)
system.time(
kmrcpp <- KMeans_rcpp(df$train, clusters = 3, num_init = 4, max_iters = 100, initializer = 'kmeans++'))
# user system elapsed
# 0.64 0.05 0.82
system.time(pr <- predict_KMeans(df$test, kmrcpp$centroids))
# user system elapsed
# 0.01 0.00 0.02
p1 <- df$train %>% mutate(cluster = as.factor(kmrcpp$clusters)) %>%
ggplot(., aes(x,y,color = cluster)) + geom_point() +
ggtitle("train data")
p2 <- df$test %>% mutate(cluster = as.factor(pr)) %>%
ggplot(., aes(x,y,color = cluster)) + geom_point() +
ggtitle("test data")
gridExtra::grid.arrange(p1,p2,ncol = 2)
![enter image description here]()