【机器学习】k-means聚类原理及python实现
1、k-means原理
2、改进的kmenas-------二分k-means
3、实例----对地图上的点进行聚类
本节完整代码可戳:https://github.com/LisaPig/machineLearning/tree/master/k_means
聚类是一种无监督的学习,它将相似的对象归到同一个簇中。它有点像全自动分类。聚类方法几乎可以应用于所有对象,簇内的对象越相似,聚类的效果越好。本章要学习一种称为K-均值( K-means)聚类的算法。之所以称之为K-均值是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。下面会逐步介绍该算法的更多细节。
K-均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心( 即簇中所有点的中心)来描述。
1. K-均值算法的工作流程?
首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。
上述过程的伪代码表示如下:
创建k个点作为起始质心(经常是随机选择)
当任意一个点的簇分配结果发生改变时
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇中所有点的均值并将均值作为质心
代码实现:
2、K-均值聚类的优缺点:
优点:容易实现。
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
适用数据类型:数值型数据。
3、聚类和分类的区别?
聚类与分类的最大不同在于,分类的目标事先已知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类( unsupervisedclassification)。
4.最基本的k-means函数实现聚类:
# -*- coding:utf-8 -*-
"""
@author:Lisa
@file:k_means.py
@time:2018/7/14 0014下午 8:27
"""
from numpy import *
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
"""
函数:读取数据集文件
输出:读取到的数据集(列表形式)
"""
def loadDataSet(filename):
dataMat=[]
fr=open(filename)
for line in fr.readlines():
curLine=line.strip().split('\t')
floatLine=list(map(float,curLine) ) #将每一行的数据映射成float型
dataMat.append(floatLine)
return dataMat
"""
函数:计算欧式两个向量之间的距离
输入:两个向量vecA、vecB
"""
def distEclud(vecA,vecB):
return np.sqrt(np.sum(np.power(vecA-vecB , 2)))
"""
函数:k个质心随机初始化
返回值:随机初始化的k个质心数据点
说明:此函数为k-means函数初始化k个质心
注意:质心的每一维度的取值范围应 确保在数据的边界之内
"""
def randCentroids(dataSet,k):
m,n=np.shape(dataSet)
centroids=np.mat(np.zeros( (k,n)))
for i in range(n):
minI=np.min(dataSet[:,i])
rangeI=float( np.max(dataSet[:,i]) - minI)
centroids[:,i]=minI+ rangeI*np.random.rand(k,1)
return centroids
"""
函数:k-means函数
说明:此为kmeans最基本的函数,后续会在此基础上有改进版----二分K-Means
"""
def k_means(dataSet,k,distCompute=distEclud,creatCentroids=randCentroids):
m,n=np.shape(dataSet)
clusterAssign=np.mat(np.zeros( (m,2))) #存储分配的簇和误差
centroids=creatCentroids(dataSet,k)
clusterChanged=True
clusChangedCount=0
#为每个样本分配簇,直到簇分配不发生变化-》稳定态
while clusterChanged:
clusterChanged =False
clusChangedCount =clusChangedCount+1
for i in range(m):
minDist = np.Inf
minIndex = -1
for j in range(k):
dist=distCompute(centroids[j,:],dataSet[i,:])
if dist
分类结果:
考虑上图中testSet.txt(4类)的聚类结果,还是挺好的。但是在一个包含三个簇的数据集上运行K-均值算法之后的结果,点的簇分配结果值没有那么准确,如下图。 K-均值算法收敛但聚类效果较差的原因是, K-均值算法收敛到了局部最小值,而非全局最小值(局部最小值指结果还可以但并非最好结果,全局最小值是可能的最好结果)。
5、那么,如何评价聚类结果好坏?
一种用于度量聚类效果的指标是SSE( Sum of Squared Error,误差平方和),对应程序中clusterAssment矩阵的第一列之和。 SSE值越小表示数据点越接近于它们的质心,聚类效果越好。因为对误差取了平方,因此更加重视那些远离中心的点。
一种肯定可以降低SSE值的方法是增加簇的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
6、那么,如何解决k-means不能收敛到全局最小值的问题?
那么如何对上图的结果进行改进?可以对生成的簇进行后处理,一种方法是将具有最大SSE值的簇划分成两个簇。具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-均值(k=2)
为克服K-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分K-均值( bisecting K-means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。
二分K-均值算法的伪代码形式如下:
将所有点看成一个簇
当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行K-均值聚类( k=2)
计算将该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作
代码实现:
"""
函数:二分K-Means聚类算法
说明:是在基本k-means算法的基础上改进的二分k-means算法->优点是可以避免陷入局部最小值,而非全局最小值
算法步骤:
1.将所有点看成一个簇,求质心
2.当簇数目小于k时
对于每一个簇
计算总误差
在给定的簇上面进行k-均值聚类(k=2),即一分为2
计算将该簇一分为二后的总误差
选择使得误差最小的那个簇进行划分操作
"""
def bi_K_means(dataSet,k,distCompute=distEclud):
m,n=np.shape(dataSet)
#初始化
clusterAssign=np.mat(np.zeros( (m,2) ))
#创建初始的一个簇
centroid0=np.mean(dataSet,axis=0).tolist()[0]
centroidList=[centroid0]
#绘制初始划分簇
draw(dataSet, np.mat(centroidList), clusterAssign, k=len(centroidList))
for i in range(k):
clusterAssign[i,1]=distCompute(np.mat(centroid0), dataSet[i,:]) **2
while (len(centroidList)
实现聚类的结果:
二分k_means分类过程展示:(完整划分过程)
完整代码可戳:https://github.com/LisaPig/machineLearning/tree/master/k_means
实例----对俄勒冈州波特兰市夜生活娱乐地点进行聚类
有一个包含格式化地理坐标的列表(places.txt),接下来可以对这些俱乐部进行聚类。(此处省去了《机器学习实战》中的获取地理坐标等步骤,直接从书中得到places.txt这一处理后的包含地理坐标的列表文件)。
注意:绘制结果图时,为了画出这些簇,首先创建一幅图和一个矩形,然后使用该矩形来决定绘制图的哪一部分。接下来构建一个标记形状的列表用于绘制散点图。后边会使用唯一的标记来标识每个簇。下一步使用imread()函数基于一幅图像来创建矩阵 ,然后使用imshow()绘制该矩阵。接下来,在同一幅图上绘制一张新的图,这允许你使用两套坐标系统并且不做任何缩放或偏移。紧接着,遍历每一个簇并将它们一一画出来。标记类型从前面创建的scatterMarkers列表中得到。使用索引i % len(scatterMarkers)来选择标记形状 ,这意味着当有更多簇时,可以循环使用这些标记。最后使用十字标记来表示簇中心并在图中显示
代码:
# -*- coding:utf-8 -*-
"""
@author:Lisa
@file:cluster_example.py
@note:利用k-means实现地理坐标的聚类
@time:2018/7/16 0016下午 9:39
"""
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from k_means import *
"""
函数:球面距离计算
原理:利用球面余弦定理计算球面上两点的距离
"""
def distSLC(vecA,vecB):
a=np.sin(vecA[0,1]*np.pi/180) *np.sin(vecB[0,1]*np.pi/180)
b=np.cos(vecA[0,1]*np.pi/180) *np.cos(vecB[0, 1] * np.pi /180)*\
np.cos(np.pi*vecB[0,0]-vecA[0,0])/180
return np.arccos(a+b)*6371.0
"""
函数:从文件中读取数据列表
注意:要根据文本格式来读取
"""
def loadSet(fileName):
dataList=[]
fr=open(fileName)
for line in fr.readlines():
curLine=line.split('\t')
dataList.append([ float(curLine[4]),float(curLine[3])]) #第4和5列分别代表纬度和经度
return np.mat(dataList)
"""
函数:绘制聚类点分布
"""
def draw(dataMat,centroids,clustAssign,k):
fig = plt.figure()
rect = [0.1, 0.1, 0.8, 0.8] # 创建矩形
# 创建不同标记图案
scatterMarkers = ['s', 'o', '^', '8', 'p', \
'd', 'v', 'h', '>', '<']
axprops = dict(xticks=[], yticks=[])
ax0 = fig.add_axes(rect, label='ax0', **axprops)
imgP = plt.imread('data\\Portland.png') # 导入地图
ax0.imshow(imgP)
ax1 = fig.add_axes(rect, label='ax1', frameon=False)
for i in range(k):
ptsInCurrCluster = dataMat[np.nonzero(clustAssign[:, 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(centroids[:, 0].flatten().A[0], centroids[:, 1].flatten().A[0], marker='+', s=300)
plt.show()
if __name__=='__main__':
dataMat=loadSet('data\\places.txt')
k=3
centroids,clusterAssign=bi_K_means(dataMat,k)
draw(dataMat,centroids,clusterAssign,k)
聚类结果:(k=5)
(K=3):
k值不同,聚类结果不同,可多试几次,寻找最佳k值。
---------------------------------------------------------------- end ------------------------------------------------------------------
参考:
1、《机器学习实战》
2、https://blog.csdn.net/sinat_17196995/article/details/70332664