我正在尝试在R中编写我的第一个kmeans算法。我在这个领域是新手,所以请不要因为我没有看到明显的问题而批评我。
目前,算法接受两个向量x
和y
,计算每个数据点到聚类中心的距离,并将距离其中心最近的聚类分配给该数据点。当分配没有变化,因此聚类中心也没有变化时,算法就会停止。
# 样本数据 set.seed(100)xval <- rnorm(12, mean = rep(1:3, each = 4), sd = 0.2)yval <- rnorm(12, mean = rep(c(1,2,1), each = 4), sd = 0.2)# Kmeans函数kclus <- function(x, y, nclus) { # 以随机聚类中心开始 xcen <- runif(n = nclus, min = min(x), max = max(x)) ycen <- runif(n = nclus, min = min(y), max = max(y)) # 数据点和聚类分配在“data”中 # 聚类坐标在“clus”中 data <- data.frame(xval = x, yval = y, clus = NA) clus <- data.frame(name = 1:nclus, xcen = xcen, ycen = ycen) finish <- FALSE while(finish == FALSE) { # 为每个数据点分配距离最近的聚类 for(i in 1:length(x)) { dist <- sqrt((x[i]-clus$xcen)^2 + (y[i]-clus$ycen)^2) data$clus[i] <- which.min(dist) } xcen_old <- clus$xcen ycen_old <- clus$ycen # 计算新的聚类中心 for(i in 1:nclus) { clus[i,2] <- mean(subset(data$xval, data$clus == i)) clus[i,3] <- mean(subset(data$yval, data$clus == i)) } # 如果聚类坐标没有变化,则停止循环 if(identical(xcen_old, clus$xcen) & identical(ycen_old, clus$ycen)) finish <- TRUE } data}# 对样本数据应用kmeans函数cluster <- kclus(xval, yval, 4)# 绘制结果ggplot(cluster, aes(xval, yval, color = as.factor(clus))) + geom_point()
到目前为止,这运行得相当好。但是我不知道如何强制算法使用特定的聚类数量。我的kclus()
函数中已经实现了nclus
参数,但我不知道如何利用它。
对于给定的样本数据,算法只给了我三个聚类。我想强制它返回四个聚类。
这里有人能给我一些建议吗?
非常感谢,Marcus
回答:
你实现的算法并不总是给你三个聚类,可能是因为你没有运行足够多次。这里是对你的代码的轻微修改,我们将能够看到聚类输出的数量取决于聚类中心的初始化(这些中心是随机选择的,可以通过random.seed来控制):
# 样本数据 set.seed(100)xval <- rnorm(12, mean = rep(1:3, each = 4), sd = 0.2)yval <- rnorm(12, mean = rep(c(1,2,1), each = 4), sd = 0.2)# 带有random.seed初始化的Kmeans函数kclus <- function(x, y, nclus, random.seed=123) { set.seed(random.seed) # 以随机聚类中心开始 xcen <- runif(n = nclus, min = min(x), max = max(x)) ycen <- runif(n = nclus, min = min(y), max = max(y)) # 数据点和聚类分配在“data”中 # 聚类坐标在“clus”中 data <- data.frame(xval = x, yval = y, clus = NA) clus <- data.frame(name = 1:nclus, xcen = xcen, ycen = ycen) finish <- FALSE while(finish == FALSE) { # 为每个数据点分配距离最近的聚类 for(i in 1:length(x)) { dist <- sqrt((x[i]-clus$xcen)^2 + (y[i]-clus$ycen)^2) data$clus[i] <- which.min(dist) } xcen_old <- clus$xcen ycen_old <- clus$ycen # 计算新的聚类中心 for(i in 1:nclus) { clus[i,2] <- mean(subset(data$xval, data$clus == i)) clus[i,3] <- mean(subset(data$yval, data$clus == i)) } # 如果聚类坐标没有变化,则停止循环 if(identical(xcen_old, clus$xcen) & identical(ycen_old, clus$ycen)) finish <- TRUE } data}# 使用默认的随机种子123,你应该能够重现结果# 如你所见,在这种情况下,没有数据点被分配到第四个聚类cluster <- kclus(xval, yval, 4)cluster.centers <- aggregate(.~clus, cluster, mean)ggplot(cluster, aes(xval, yval, color = as.factor(clus))) + geom_point(size=5) + geom_point(data=cluster.centers, aes(xval, yval, col=as.factor(clus)), pch=8, size=5)
# 使用不同的随机种子12运行# 如你所见,在这种情况下,算法输出4个聚类,其中第二个聚类只有一个数据点被分配 cluster <- kclus(xval, yval, 4, 12) cluster.centers <- aggregate(.~clus, cluster, mean) ggplot(cluster, aes(xval, yval, color = as.factor(clus))) + geom_point(size=5) + geom_point(data=cluster.centers, aes(xval, yval, col=as.factor(clus)), pch=8, size=5)
# 使用不同的随机种子12345运行# 如你所见,在这种情况下,算法输出2个聚类,所有数据点都被分配到第一个和第二个聚类 cluster <- kclus(xval, yval, 4, 12345) cluster.centers <- aggregate(.~clus, cluster, mean) ggplot(cluster, aes(xval, yval, color = as.factor(clus))) + geom_point(size=5) + geom_point(data=cluster.centers, aes(xval, yval, col=as.factor(clus)), pch=8, size=5)
从上面的例子可以看出,聚类是否在收敛时没有分配点取决于初始中心位置和数据分布。一般来说,如果kmeans结束时有一个聚类中心为空,这意味着如果你试图强制将一个点分配到空聚类中,可能会导致聚类质量变差,这是你不希望看到的。
你可以尝试以下几种方法:
- 首先,你可以多次运行你的算法,每次使用不同的随机初始化中心,然后选择聚类质量最高的结果(通过SSE等衡量)。
- 第二,你可以尝试使用Kmeans++进行更智能的初始化。
- 一个不太好的选择是修改你的算法,确保在重新分配聚类时保证每个聚类(k=4)至少有一个点被分配(如果没有,则不重新分配)。
- 最后,你可以尝试其他算法,例如层次聚类,它通过树状图为你提供更多的灵活性,让你可以选择想要的聚类数量。