欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【聚类分析】Kmeans算法理解及R语言实现

程序员文章站 2022-07-14 21:00:05
...

本文中代码及数据源请访问我的github:https://github.com/HelloMrChen

一、基本解释(算法思想、名词解释)

算法:

1、在总体中随机选择k个值作为初始质心。

2、计算每个样本点到每个质心的距离(一般为欧式距离),将每个点指派到最近的质心点,形成K个聚类。

3、重新这个簇的样本点的平均值,作为簇的新质心。

4、重复2-3直至质心不再发生变化,或者只发生很微小的变化。


名词解释:

关于距离的定义请参考:数学算法中的各种距离


二、数学推导


三、R中自带包及案例介绍

在R语言中,只需要调用knn包中的kmeans函数,主要参数有数据集以及要分的类别数量。

下面是调用Kmeans函数对经典数据集iris进行聚类的代码

 
#R语言代码
# 用R自带的Kmeans包实现Kmeans聚类

#读取iris 数据集
## 设置路径为AlgorithmPractise-R,读取当前工作目录子文件夹格式,windows格式参考 "D:\\MySpace\\机器学习\\R\\Rsourse"
iris<-read.csv("./DataSource/iris.data.csv")

#处理数据集,去掉分类数据列class
#剩余参数有四个:sepal萼片的长宽、petal 花瓣的长宽
newIris<-iris[1:4]  

#调用kmeans,将样本分为3类
kc<-kmeans(newIris,3) 

kc$cluster

#查看
fitted(kc) #此函数用于查看之前样本点通过模型之后的拟合值,这里其实就是样本点所属分类簇质心的坐标
table(iris$class,kc$cluster) 


#聚类的可视化

#按照萼片的长宽来显示
plot(newIris[c("sepal.length","sepal.width")],
     col=kc$cluster,pch=as.integer(iris$class))

#按照花瓣的长宽来显示
plot(newIris[c("petal.length","petal.width")],
     col=kc$cluster,pch=as.integer(iris$class))

#以上参数中 col 代表颜色,这里用聚类结果kc$cluster 来显示,pch (plot charater简称),用之前的分类来显示
#这样能清楚的看出通过聚类后的分类和之前分类的区别

#画出分类的中心点,col代表三种颜色、pch代表中心点形状、cex代表大小
points(kc$centers,col=1:3,pch=16,cex=2)


以下为其中一个输出结果:

【聚类分析】Kmeans算法理解及R语言实现

四、用R语言自定义函数来实现Kmeans功能


 
iris<-read.csv("./DataSource/iris.data.csv")
#欧式距离计算函数
Eudist<-function(x,y){
  distance<-apply(sqrt((x-y)^2),1,sum)
  return (distance)
}

#初始质心的计算
initPoints<-function(dataset,k){
#这里主要的思想是中心点的维度为原始数据集的列数,中心点的个数为要分类的个数K
  n=ncol(dataset)
  centerPointSet<-matrix(data = NA,nrow = k,ncol = n)
  for (i in 1:n){
    range=max(dataset[,i])-min(dataset[,i])
    centerPointSet[,i]=min(dataset[,i])+range*runif(k) #这里需要要转换思维,这里的min(dataset[,i])、runif(k)以及我们的结果都是k维的向量,只有range是常数
  }
  return(centerPointSet)
}

#自定义Kmeans的函数函数体
customKmeans<-function(dataSet,k){
  #下面要建一个记录矩阵,矩阵行数跟样本点数相同,列数为两列,第一列记录该样本点所属的类别,第二列记录误差值
  m=nrow(dataSet) #请注意,这里既代表了样本文件有几行,也代表了有几个样本点
  pointProperty=matrix(data=0,nrow = m,ncol = 2,byrow = FALSE,dimnames = NULL) 
  
  #初始质心
  centroid=initPoints(dataSet,k)
  colnames(centroid)=colnames(dataSet)
  
  #设置递归结束位,即当质心还变化的时候,则继续递归,当然这里也可以改为变化范围的限制,即当质心只发生微小变化的时候,就停止递归
  changeFlag=TRUE
  while(changeFlag){
    changeFlag=FALSE
    for (i in 1:m) {
      #该for循环的次数为数据集的样本的个数,每行都是一个样本点,对每个样本点计算到每个质心的字段距离,判断属于那个簇,以及更新分类标志位
      minDist=Inf #预设每个样本点离所有质心的最短距离为正无穷
      minIndex=-1 #预设每个样本点的分类为-1,其实也就是初始是没有分类
      for (j in 1:k) {
        #该for循环的循环次数为质心的个数,即计算第i个样本点到所有质心的距离,并记录下最短距离是多少,以及离哪个质心最近
        distIJ=Eudist(dataSet[i,],centroid[j,]) #计算样本中第i个点与第j个质心之间的欧式距离
        if(is.na(distIJ)) distIJ = Inf
        if(distIJ<minDist){
          minDist=distIJ  #每次求距离都存入minDist中,如果发现更小的距离,则替换  
          minIndex=j  #并替换该样本点所属分类为质心点的序号
        }
        
        if(pointProperty[i,1]!= minIndex) {#如果该样本点的分类发生了变化
          changeFlag=TRUE #说明质心还在变,总体分类还没趋于稳定
          pointProperty[i,1]= minIndex #更新该样本点的分类标志位
        }
        pointProperty[i,2]=minDist^2  #计算误差值 
      }
    }
    #本函数中是每更新一个样本分类即更新每个质心的坐标
    for(cent in 1:k){
      #该for循环的作用是找到新划分到第K个簇中的样本点,然后通过求均值求出最新的质心
     newCluster=dataSet[which(pointProperty[,1]==cent),] #从数据集所有样本中拿出目前标志位中标识属于第k类的样本点
     centroid[k,]=apply(newCluster,2,mean)  #因为样本点的维度即样本点的列,所以通过计算该类的均值,计算出该类新的质心
     #通过循环后,我们重新计算了k个质心
    }
    #while函数结束,质心不再发生变化
  }
  out = list(pointProperty = pointProperty ,centroid = centroid) #将聚类中的各种分析结果按照list类型输出,当然也可以继续自定义该输出结果
  return (out)
  
#自定义kmeans函数结束
}

newIris=iris[1:4]
km<-customKmeans(newIris,3)



五、自定义函数模型评估