【问题标题】:Simple approach to assigning clusters for new data after k-means clusteringk-means 聚类后为新数据分配聚类的简单方法
【发布时间】:2014-01-04 10:53:03
【问题描述】:

我在数据框 df1 上运行 k-means 聚类,我正在寻找一种简单的方法来计算新数据框 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,我会更喜欢。有没有方便的内置函数来查找最近的集群?

【问题讨论】:

标签: r k-means


【解决方案1】:

您可以使用flexclust 包,它为k-means 实现了predict 方法:

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")

还有一些转换方法可以将stats::kmeanscluster::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 方法,我注意到的一点是它们相当慢(这里以训练和测试集为基准,有 100 万个观察值,每个观察值有 2 个特征)。

    拟合原始模型相当快:

    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 
    

    同时,无论我们是使用as.kcca 转换拟合模型还是使用kcca 自己拟合新模型,flexclust 包似乎都会增加很多开销(尽管最后的预测要快得多)

    # 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
    

    【讨论】:

    • 这个答案非常有价值。在 k-means 模型上使用 predict() 所涉及的开销简直太疯狂了。为我处理一小部分栅格需要 1.5 小时。通过使用您的集群中心方法,我能够在不到 15 秒的时间内运行该过程。非常感谢。
    • 当我运行这个时,两种方法的所有预测都会导致两种方法的集群成员资格 = 1,即使有 3 个集群as_tibble(pred.test) %&gt;% group_by(value) %&gt;% count()
    • @JeffParker 你确定你运行的代码完全符合我的回答吗?当我运行as_tibble(pred.test) %&gt;% group_by(value) %&gt;% count() 时,我得到三个类,每个类的元素数量大致相同。如果您无法使其正常工作,我建议您发布一个新问题,而不是在 cmets 中提问。
    【解决方案3】:

    你可以使用ClusterR::KMeans_rcpp()函数,使用RcppArmadillo。它允许多个初始化(如果 Openmp 可用,则可以并行化)。除了optimal_init、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)
    

    【讨论】:

      猜你喜欢
      • 2021-01-14
      • 2011-10-04
      • 2018-02-27
      • 1970-01-01
      • 2013-02-07
      • 2015-04-11
      • 2020-10-10
      • 2020-08-28
      • 2021-12-10
      相关资源
      最近更新 更多