一、K-means聚类算法

1 K-means算法的相关描述

聚类是一种无监督学习,它将相似的对象归到同一簇中。聚类的方法几乎可以应用所有对象,簇内的对象越相似,聚类的效果就越好。K-means算法中的k表示的是聚类为k个簇,means代表取每一个聚类中数据值的均值作为该簇的中心,或者称为质心,即用每一个的类的质心对该簇进行描述。

聚类和分类最大的不同在于,分类的目标是事先已知的,而聚类事先不知道目标变量是什么,类别没有像分类那样被预先定义出来,所以,聚类有时也叫无监督学习。

聚类分析试图将相似的对象归入同一簇,将不相似的对象归为不同簇,那么,显然需要一种合适的相似度计算方法,我们已知的有很多相似度的计算方法,比如欧氏距离,余弦距离,汉明距离等。事实上,我们应该根据具体的应用来选取合适的相似度计算方法。

当然,任何一种算法都有一定的缺陷,没有一种算法时完美的,有的只是人类不断追求完美,不断创新的意志。K-means算法虽然有效,但是容易受到初始簇质心的情况而影响,有可能陷入局部最优解。为了解决这个问题,可以使用另外一种称为二分K-means的聚类算法(后面会讲到)。二分K-means算法首先将所有数据点分为一个簇;然后使用K-means(k=2)对其进行划分;下一次迭代时,选择使得SSE下降程度最大的簇进行划分;重复该过程,直至簇的个数达到指定的数目为止。实验表明,二分K-means算法的聚类效果要好于普通的K-means聚类算法。

2 K-means算法的工作流程

首先,随机确定k个初始点的质心;然后将数据集中的每一个点分配到一个簇中,即为每一个点找到距其最近的质心,并将其分配给该质心所对应的簇;该步完成后,每一个簇的质心更新为该簇所有点的平均值。伪代码如下:

创建k个点作为起始质心,可以随机选择(位于数据边界内)当任意一个点的簇分配结果发生改变时对数据集中每一个点对每个质心计算质心与数据点之间的距离将数据点分配到距其最近的簇对每一个簇,计算簇中所有点的均值并将均值作为质心

再看实际的代码:

#导入numpy库
from numpy import *
#K-均值聚类辅助函数#文本数据解析函数
def numpy import *dataMat=[]fr=open(fileName)for line in fr.readlines():curLine=line.strip().split('\t')#将每一行的数据映射成float型fltLine=map(float,curLine)dataMat.append(fltLine)return dataMat#数据向量计算欧式距离
def distEclud(vecA,vecB):return sqrt(sum(power(vecA-vecB,2)))#随机初始化K个质心(质心满足数据边界之内)
def randCent(dataSet,k):#得到数据样本的维度n=shape(dataSet)[1]#初始化为一个(k,n)的矩阵centroids=mat(zeros((k,n)))#遍历数据集的每一维度for j in range(n):#得到该列数据的最小值minJ=min(dataSet[:,j])#得到该列数据的范围(最大值-最小值)rangeJ=float(max(dataSet[:,j])-minJ)#k个质心向量的第j维数据值随机为位于(最小值,最大值)内的某一值centroids[:,j]=minJ+rangeJ*random.rand(k,1)#返回初始化得到的k个质心向量return centroids#k-均值聚类算法
#@dataSet:聚类数据集
#@k:用户指定的k个类
#@distMeas:距离计算方法,默认欧氏距离distEclud()
#@createCent:获得k个质心的方法,默认随机获取randCent()
def kMeans(dataSet,k,distMeas=distEclud,createCent=randCent):#获取数据集样本数m=shape(dataSet)[0]#初始化一个(m,2)的矩阵clusterAssment=mat(zeros((m,2)))#创建初始的k个质心向量centroids=createCent(dataSet,k)#聚类结果是否发生变化的布尔类型clusterChanged=True#只要聚类结果一直发生变化,就一直执行聚类算法,直至所有数据点聚类结果不变化while clusterChanged:#聚类结果变化布尔类型置为falseclusterChanged=False#遍历数据集每一个样本向量for i in range(m):#初始化最小距离最正无穷;最小距离对应索引为-1minDist=inf;minIndex=-1#循环k个类的质心for j in range(k):#计算数据点到质心的欧氏距离distJI=distMeas(centroids[j,:],dataSet[i,:])#如果距离小于当前最小距离if distJI<minDist:#当前距离定为当前最小距离;最小距离对应索引对应为j(第j个类)minDist=distJI;minIndex=j#当前聚类结果中第i个样本的聚类结果发生变化:布尔类型置为true,继续聚类算法if clusterAssment[i,0] !=minIndex:clusterChanged=True#更新当前变化样本的聚类结果和平方误差clusterAssment[i,:]=minIndex,minDist**2#打印k-均值聚类的质心print centroids#遍历每一个质心for cent in range(k):#将数据集中所有属于当前质心类的样本通过条件过滤筛选出来ptsInClust=dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]#计算这些数据的均值(axis=0:求列的均值),作为该类质心向量centroids[cent,:]=mean(ptsInClust,axis=0)#返回k个聚类,聚类结果及误差return centroids,clusterAssment

注意相似度的计算方法默认的是欧氏距离计算
  算法中,k个类的初始化方式为随机初始化,并且初始化的质心必须在整个数据集的边界之内,这可以通过找到数据集每一维的最大值和最小值;然后最小值+取值范围*0到1的随机数,来确保随机点在数据边界之内。

在实际的K-means算法中,采用计算质心-分配-重新计算质心的方式反复迭代,算法停止的条件是,当然数据集所有的点分配的距其最近的簇不在发生变化时,就停止分配,更新所有簇的质心后,返回k个类的质心(一般是向量的形式)组成的质心列表,以及存储各个数据点的分类结果和误差距离的平方的二维矩阵。

上面返回的结果中,之所以存储每个数据点距离其质心误差距离平方,是便于后续的算法预处理。因为K-means算法采取的是随机初始化k个簇的质心的方式,因此聚类效果又可能陷入局部最优解的情况,局部最优解虽然效果不错,但不如全局最优解的聚类效果更好。所以,后续会在算法结束后,采取相应的后处理,使算法跳出局部最优解,达到全局最优解,获得最好的聚类效果。

可以看一个聚类的例子:




3 后处理提高聚类性能

  当我们观察聚类的结果图时,发现聚类的效果没有那么好,如上图所示,K-means算法在k值选取为3时的聚类结果,我们发现,算法能够收敛但效果较差。显然,这种情况的原因是,算法收敛到了局部最小值,而并不是全局最小值,局部最小值显然没有全局最小值的结果好。

那么,既然知道了算法已经陷入了局部最小值,如何才能够进一步提升K-means算法的效果呢?

度量聚类效果的指标是SSE,即误差平方和, 为所有簇中的全部数据点到簇中心的误差距离的平方累加和

SSE的值如果越小,表示数据点越接近于它们的簇中心,即质心,聚类效果也越好。因为,对误差取平方后,就会更加重视那些远离中心的数据点。

改善聚类效果的做法就是降低SSE

如何在保持簇数目不变的情况下提高簇的质量呢?

一种方法是:我们可以将具有最大SSE值得簇划分为两个簇**(SSE最大的簇一般情况下,意味着簇内的数据点距离簇中心较远)**,具体地,可以将最大簇包含的点过滤出来并在这些点上运行K-means算法,其中k设为2.

同时,当把最大的簇(上图中的下半部分)分为两个簇之后,为了保证簇的数目是不变的,我们可以再合并两个簇。具体地:

一方面我们可以合并两个最近的质心所对应的簇,即计算所有质心之间的距离,合并质心距离最近的两个质心所对应的簇。

另一方面,我们可以合并两个使得SSE增幅最小的簇,显然,合并两个簇之后SSE的值会有所上升,那么为了最好的聚类效果,应该尽可能使总的SSE值小,所以就选择合并两个簇后SSE涨幅最小的簇。具体地,就是计算合并任意两个簇之后的总得SSE,选取合并后最小的SSE对应的两个簇进行合并。这样,就可以满足簇的数目不变。

二,二分K-means算法

二分K-means算法首先将所有点作为一个簇,然后将簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇取决于对其进行划分是否能够最大程度的降低SSE的值。上述划分过程不断重复,直至划分的簇的数目达到用户指定的值为止。

二分K-means算法的伪代码如下:

将所有点看成一个簇
当簇数目小于k时
对于每一个簇计算总误差在给定的簇上面进行k-均值聚类(k=2)计算将该簇一分为二之后的总误差
选择使得总误差最小的簇进行划分

也可以选择SSE最大的簇进行划分,知道簇数目达到用户指定的数目为止。下面看具体的代码:

#二分K-均值聚类算法
#@dataSet:待聚类数据集
#@k:用户指定的聚类个数
#@distMeas:用户指定的距离计算方法,默认为欧式距离计算
def biKmeans(dataSet,k,distMeas=distEclud):#获得数据集的样本数m=shape(dataSet)[0]#初始化一个元素均值0的(m,2)矩阵clusterAssment=mat(zeros((m,2)))#获取数据集每一列数据的均值,组成一个长为列数的列表centroid0=mean(dataSet,axis=0).tolist()[0]#当前聚类列表为将数据集聚为一类centList=[centroid0]#遍历每个数据集样本for j in range(m):#计算当前聚为一类时各个数据点距离质心的平方距离clusterAssment[j,1]=distMeas(mat(centroid0),dataSet[j,:])**2#循环,直至二分k-均值达到k类为止while (len(centList)<k):#将当前最小平方误差置为正无穷lowerSSE=inf#遍历当前每个聚类for i in range(len(centList)):#通过数组过滤筛选出属于第i类的数据集合ptsInCurrCluster=\dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#对该类利用二分k-均值算法进行划分,返回划分后结果,及误差centroidMat,splitClustAss=\kMeans(ptsInCurrCluster,2,distMeas)#计算该类划分后两个类的误差平方和sseSplit=sum(splitClustAss[:,1])#计算数据集中不属于该类的数据的误差平方和sseNotSplit=\sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])#打印这两项误差值print('sseSplit,and notSplit:',%(sseSplit,sseNotSplit))#划分第i类后总误差小于当前最小总误差if(sseSplit+sseNotSplit)<lowerSSE:#第i类作为本次划分类bestCentToSplit=i#第i类划分后得到的两个质心向量bestNewCents=centroidMat#复制第i类中数据点的聚类结果即误差值bestClustAss=splitClustAss.copy()#将划分第i类后的总误差作为当前最小误差lowerSSE=sseSplit+sseNotSplit#数组过滤筛选出本次2-均值聚类划分后类编号为1数据点,将这些数据点类编号变为#当前类个数+1,作为新的一个聚类bestClustAss[nonzero(bestClustAss[:,0].A==1)[0],0]=\    len(centList)#同理,将划分数据集中类编号为0的数据点的类编号仍置为被划分的类编号,使类编号#连续不出现空缺bestClustAss[nonzero(bestClustAss[:,0].A==0)[0],0]=\    bestCentToSplit#打印本次执行2-均值聚类算法的类print('the bestCentToSplit is:',%bestCentToSplit)#打印被划分的类的数据个数print('the len of bestClustAss is:',%(len(bestClustAss)))#更新质心列表中的变化后的质心向量centList[bestCentToSplit]=bestNewCents[0,:]#添加新的类的质心向量centList.append(bestNewCents[1,:])#更新clusterAssment列表中参与2-均值聚类数据点变化后的分类编号,及数据该类的误差平方clusterAssment[nonzero(clusterAssment[:,0].A==\bestCentToSplit)[0],:]=bestClustAss#返回聚类结果return mat(centList),clusterAssment

在上述算法中,直到簇的数目达到k值,算法才会停止。在算法中通过将所有的簇进行划分,然后分别计算划分后所有簇的误差。选择使得总误差最小的那个簇进行划分。划分完成后,要更新簇的质心列表,数据点的分类结果及误差平方。具体地,

假设划分的簇为m(m<k)个簇中的第i个簇,那么这个簇分成的两个簇后,其中一个取代该被划分的簇,成为第i个簇,并计算该簇的质心;此外,将划分得到的另外一个簇,作为一个新的簇,成为第m+1个簇,并计算该簇的质心。此外,算法中还存储了各个数据点的划分结果和误差平方,此时也应更新相应的存储信息。这样,重复该过程,直至簇个数达到k。

通过上述算法,之前陷入局部最小值的的这些数据,经过二分K-means算法多次划分后,逐渐收敛到全局最小值,从而达到了令人满意的聚类效果。

三,示例:对地图上的点进行聚类

现在有一个存有70个地址和城市名的文本,而没有这些地点的距离信息。而我们想要对这些地点进行聚类,找到每个簇的质心地点,从而可以安排合理的行程,即质心之间选择交通工具抵达,而位于每个质心附近的地点就可以采取步行的方法抵达。显然,K-means算法可以为我们找到一种更加经济而且高效的出行方式。

1 通过地址信息获取相应的经纬度信息

那么,既然没有地点之间的距离信息,怎么计算地点之间的距离呢?又如何比较地点之间的远近呢?

我们手里只有各个地点的地址信息,那么如果有一个API,可以让我们输入地点信息,返回该地点的经度和纬度信息,那么我们就可以通过球面距离计算方法得到两个地点之间的距离了。而Yahoo!PlaceFinder API可以帮助我们实现这一目标。获取地点信息对应经纬度的代码如下:

#Yahoo!PlaceFinder API
#导入urllib
import urllib
#导入json模块
import json#利用地名,城市获取位置经纬度函数
def geoGrab(stAddress,city):#获取经纬度网址apiStem='http://where.yahooapis.com/geocode?'#初始化一个字典,存储相关参数params={}#返回类型为jsonparams['flags']='J'#参数appidparams['appid']='ppp68N8t'#参数地址位置信息params['location']=('%s %s', %(stAddress,city))#利用urlencode函数将字典转为URL可以传递的字符串格式url_params=urllib.urlencode(params)#组成完整的URL地址apiyahooApi=apiStem+url_params#打印该URL地址print('%s',yahooApi)#打开URL,返回json格式的数据c=urllib.urlopen(yahooApi)#返回json解析后的数据字典return json.load(c.read())from time import sleep
#具体文本数据批量地址经纬度获取函数
def massPlaceFind(fileName):#新建一个可写的文本文件,存储地址,城市,经纬度等信息fw=open('places.txt','wb+')#遍历文本的每一行for line in open(fileName).readlines();#去除首尾空格line =line.strip()#按tab键分隔开lineArr=line.split('\t')#利用获取经纬度函数获取该地址经纬度retDict=geoGrab(lineArr[1],lineArr[2])#如果错误编码为0,表示没有错误,获取到相应经纬度if retDict['ResultSet']['Error']==0:#从字典中获取经度lat=float(retDict['ResultSet']['Results'][0]['latitute'])#维度lng=float(retDict['ResultSet']['Results'][0]['longitute'])#打印地名及对应的经纬度信息print('%s\t%f\t%f',%(lineArr[0],lat,lng))#将上面的信息存入新的文件中fw.write('%s\t%f\t%f\n',%(line,lat,lng))#如果错误编码不为0,打印提示信息else:print('error fetching')#为防止频繁调用API,造成请求被封,使函数调用延迟一秒sleep(1)#文本写入关闭fw.close()

在上述代码中,

首先创建一个字典,字典里面存储的是通过URL获取经纬度所必要的参数,即我们想要的返回的数据格式flogs=J;获取数据的appid;以及要输入的地址信息(stAddress,city)。然后,通过urlencode()函数帮助我们将字典类型的信息转化为URL可以传递的字符串格式。最后,打开URL获取返回的JSON类型数据,通过JSON工具来解析返回的数据。且在返回的结果中,当错误编码为0时表示,得到了经纬度信息,而为其他值时,则表示返回经纬度信息失败。

注意在代码中,每次获取完一个地点的经纬度信息后,延迟一秒钟。这样做的目的是为了避免频繁的调用API,请求被封掉的情况。

2 对地理位置进行聚类

我们已经得到了各个地点的经纬度信息,但是我们还要选择计算距离的合适的方式。我们知道,在北极每走几米的经度变化可能达到数十度,而沿着赤道附近走相同的距离,带来的经度变化可能是零。这是,我们可以使用球面余弦定理来计算两个经纬度之间的实际距离。具体代码如下:

#球面距离计算及簇绘图函数
def distSLC(vecA,vecB):#sin()和cos()以弧度未输入,将float角度数值转为弧度,即*pi/180a=sin(vecA[0,1]*pi/180)*sin(vecB[0,1]*pi/180)b=cos(vecA[0,1]*pi/180)*cos(vecB[0,1]*pi/180)*\cos(pi*(vecB[0,0]-vecA[0,0])/180)return arcos(a+b)*6371.0import matplotlib
import matplotlib.pyplot as plt#@numClust:聚类个数,默认为5
def clusterClubs(numClust=5):datList=[]#解析文本数据中的每一行中的数据特征值for line in open('places.txt').readlines():lineArr=line.split('\t')datList.append([float(lineArr[4]),float(lineArr[4])])datMat=mat(datList)#利用2-均值聚类算法进行聚类myCentroids,clusterAssing=biKmeans(datMat,numClust,\distMeas=distSLC)#对聚类结果进行绘图fig=plt.figure()rect=[0.1,0.1,0.8,0.8]scatterMarkers=['s','o','^','8'.'p',\'d','v','h','>','<']axprops=dict(xticks=[],ytick=[])ax0=fig.add_axes(rect,label='ax0',**axprops)imgP=plt.imread('Portland.png')ax0.imshow(imgP)ax1=fig.add_axes(rect,label='ax1',frameon=False)for i in range(numClust):ptsInCurrCluster=datMat[nonzero(clusterAssing[:,0].A==i)[0],:]markerStyle=scatterMarkers[i % len(scatterMarkers))]ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0],\ptsInCurrCluster[:,1].flatten().A[0],\marker=markerStyle,s=90)ax1.scatter(myCentroids[:,0].flatten().A[0],\myCentroids[:,1].flatten().A[0],marker='+',s=300)#绘制结果显示plt.show()

最后,将聚类的结果绘制出来:


https://www.cnblogs.com/zy230530/p/7029025.html

K-means聚类算法实战相关推荐

  1. k means聚类算法_一文读懂K-means聚类算法

    1.引言 什么是聚类?我们通常说,机器学习任务可以分为两类,一类是监督学习,一类是无监督学习.监督学习:训练集有明确标签,监督学习就是寻找问题(又称输入.特征.自变量)与标签(又称输出.目标.因变量) ...

  2. k means聚类算法_K-Means 聚类算法 20210108

    说到聚类,应先理解聚类和分类的区别 聚类和分类最大的不同在于:分类的目标是事先已知的,而聚类则不一样,聚类事先不知道目标变量是什么,类别没有像分类那样被预先定义出来. K-Means 聚类算法有很多种 ...

  3. OpenCV官方文档 理解k - means聚类

    理解k - means聚类 目标 在这一章中,我们将了解k - means聚类的概念,它是如何工作等. 理论 我们将这个处理是常用的一个例子. t恤尺寸问题 考虑一个公司要发布一个新模型的t恤. 显然 ...

  4. k均值聚类算法(K Means)及其实战案例

    算法说明 K均值聚类算法其实就是根据距离来看属性,近朱者赤近墨者黑.其中K表示要聚类的数量,就是说样本要被划分成几个类别.而均值则是因为需要求得每个类别的中心点,比如一维样本的中心点一般就是求这些样本 ...

  5. 机器学习实战-61:K均值聚类算法(K-Means)

    K均值聚类算法(K-Means) 深度学习原理与实践(开源图书)-总目录,建议收藏,告别碎片阅读! 机器学习分为监督学习.无监督学习和半监督学习(强化学习).无监督学习最常应用的场景是聚类(clust ...

  6. OpenCV的k - means聚类 -对图片进行颜色量化

    OpenCV的k - means聚类 目标 学习使用cv2.kmeans()数据聚类函数OpenCV 理解参数 输入参数 样品:它应该的np.float32数据类型,每个特性应该被放在一个单独的列. ...

  7. K-Means(K均值聚类算法)

    K-Means(K均值聚类算法) 1.前言 要学习聚类算法就要知道聚类学习算法是什么,为什么要学习聚类学习聚类学习算法,有什么用途,下面就简单的做一下介绍,并且详细的说明k-means均值聚类学习算法 ...

  8. C-means聚类算法实战 — 地表植被分类/数字聚类

    C-means聚类算法实战 - 地表植被分类/数字聚类 文章目录 C-means聚类算法实战 --- 地表植被分类/数字聚类 一.C均值算法简介 二.sklearn中make_blobs的用法简介 三 ...

  9. k均值聚类算法python_K均值和其他聚类算法:Python快速入门

    k均值聚类算法python This post was originally published here 这篇文章最初发表在这里 Clustering is the grouping of obje ...

  10. VTK:K均值聚类用法实战

    VTK:K均值聚类用法实战 程序输出 程序完整源代码 程序输出 程序完整源代码 #include <vtkActor.h> #include <vtkDoubleArray.h> ...

最新文章

  1. 一条命令安装Windows Subsystem for Linux
  2. java api中的设计模式_Java API 设计模式之策略(Strategy)
  3. 2020双十一实时大屏_双十一实时“战报”来了,你贡献了多少?
  4. ASP.NET内置对象的总结
  5. linux系统怎样指定gpu运行,linux服务器如何指定gpu以及用量
  6. 本人的博客只是工作期间随手记录的笔记而已,所以不会很详尽,由此给您带来的不便,恳请多多包涵~...
  7. Can't create directory 'E:\Repositories\***\db\transactions\138-41.txn':
  8. JS操作地址栏Location
  9. 如何将footer标签固定在底部_如何让footer标签置于页面最底部
  10. Python 进阶 —— x = x+1 vs x += 1
  11. 用Python在图片上添加注释信息
  12. 配置追踪者—Scylla v1(***测试工具)
  13. java集成Cplex:Cplex下载、IDEA环境搭建、docker部署
  14. php社保个税计算器 V20180925
  15. 中国本土八大会计师事务所简介
  16. PredRNN++: Towards A Resolution of the Deep-in-Time Dilemma in Spatiotemporal Predictive Learning 翻译
  17. tanh函数图像以及求导
  18. 计算机桌面出现模糊窗口,显示屏模糊,教您怎么解决电脑屏幕模糊
  19. CSS通过blur实现高斯模糊
  20. 单片机 LCD1602显示实验

热门文章

  1. 【路径规划】基于人工势场法、蝙蝠优化算法、人工鱼群算法、果蝇优化算法的路径规划(Matlab代码实现)
  2. 利用windows优化大师,将cmd加入鼠标右键菜单
  3. office2019 下载安装在其他盘非系统盘
  4. 《跟着小吴哥学python》之 11 Python执行shell命令
  5. PMP考试的一次通过率真实的是多少?
  6. 小米2013招聘笔试题:朋友圈
  7. 【活动】我的第一台MAC电脑
  8. vs2005关于:无法启动调试 绑定句柄无效 的解决办法 [整理]
  9. Infiniband vs 以太网Ethernet 对比
  10. 为什么学编程的人大多数都去了深圳和北京?