1.关于分类和聚类
kmeans属于聚类算法中的一种。分类和聚类是不同的概念。虽然两者的目的都是对数据进行分类,但是却有一定的区别。
- 分类是按照某种标准给对象贴标签,再根据标签来区分归类;
- 聚类是事先没有给出标签,刚开始并不知道如何对数据分类,完全是算法自己来判断各条数据之间的相似性,相似的就放在一起。
在聚类的结论出来之前,不能知道每一类有什么特点,最后一定要根据聚类的结果通过人的经验来分析才能知道聚成的这一类大概有什么特点。简言之,聚类就是“物以类聚、人以群分”的原理。
2.基本概念
简单来说,kmeans算法的原理就是:给定K的值,K代表要将数据分成的类别数,然后根据数据间的相似度将数据分成K个类,也称为K个簇(cluster)。这就是kmeans算法名字中k的由来。
度量数据相似度的方法一般是用数据点间的距离来衡量,比如欧式距离、汉明距离、曼哈顿距离等等。
一般来说,我们使用欧式距离来度量数据间的相似性。所谓的欧式距离便是我们日常使用的距离度量方法。比如对于二维平面上的两个点A(x1,y1)和B(x2,y2),两者间的欧式距离就为。
而对于每一个簇,我们用簇中所有点的中心来描述,该中心也称为质心(centroid)。我们通过对簇中的所有数据点取均值(mean)的方法来计算质心,这也是kmeans算法名字中mean的由来。
3.算法描述
3.1.自然语言描述
1.创建K个点作为初始质心(通常是随机选择)
2.当任意一个点的簇分类结果发生改变时
2.1对数据的每一个点,计算每一个质心与该数据点的距离,将数据点分配到距其最近的簇
2.2对于每一个簇,计算簇中所有点的均值并将均值作为质心
3.2.伪代码描述
kmeans(dataset, k)
输入:数据集dataset, k的值(大于0的整数)
输出:包含质心的集合,簇分配结果
(1) 选择k个数据点作为质心(通常是随机选取)
(2) 当任意一个点的簇分类结果发生改变时
a) For each 数据集中的点
For each 质心集合的质心
计算数据点与质心的距离
将数据点分配到距其最近的簇,更新簇分配结果
b) For each簇
计算簇中所有点的均值并将均值作为质心
4.算法实现
4.1.python实现及相关解释
注:以下代码参考自《机器学习实战》
#-*-coding:utf-8-*-
from numpy import *
def distEclud(vecA, vecB):
'''
输入:向量A,向量B
输出:两个向量的欧式距离
描述:计算两个向量的欧式距离
'''
return sqrt(sum(power(vecA - vecB, 2)))
def randCent(dataSet, k):
'''
输入:数据集、K
输出:包含K个随机质心(centroid)的集合
描述:为给定数据集生成一个包含K个随机质心的集合
'''
n = shape(dataSet)[1] #得到数据集的列数
centroids = mat(zeros((k,n))) #得到一个K*N的空矩阵
for j in range(n): #对于每一列(对于每一个数据维度)
minJ = min(dataSet[:,j]) #得到最小值
rangeJ = float(max(dataSet[:,j]) - minJ) #得到当前列的范围
centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) #在最小值和最大值之间取值
#random模块的rand(a,b)函数返回a行b列的随机数(范围:0-1)
#在这里mat()加不加都无所谓,但是为了避免赋值右边的变量类
#型不是matrix,还是加上比较好
return centroids
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
'''
输入:数据集,k,计算向量间距离的函数名,随机生成k个随机质心的函数名
输出:包含质心的集合,簇分配结果矩阵
描述:kmeans算法实现
'''
m = shape(dataSet)[0] #数据集的行数,即数据的个数
clusterAssment = mat(zeros((m,2)))#簇分配结果矩阵
#第一列储存簇索引值
#第二列储存数据与对应质心的误差
centroids = createCent(dataSet, k)#先随机生成k个随机质心的集合
clusterChanged = True
while clusterChanged: #当任意一个点的簇分配结果改变时
clusterChanged = False
for i in range(m): #对数据集中的每一个数据
minDist = inf; minIndex = -1
for j in range(k): #对于每一质心
distJI = distMeas(centroids[j,:],dataSet[i,:])#得到数据与质心间的距离
if distJI < minDist: #更新最小值
minDist = distJI; minIndex = j
#若该点的簇分配结果改变
if clusterAssment[i,0] != minIndex: clusterChanged = True
clusterAssment[i,:] = minIndex,minDist**2
#print centroids
for cent in range(k): #对于每一个簇
ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]]#通过数组过滤得到簇中所有数据
#.A 方法将matrix类型元素转化为array类型
#在这里也可以不加
centroids[cent,:] = mean(ptsInClust, axis=0) #将质心更新为簇中所有数据的均值
#axis=0表示沿矩阵的列方向计算均值
return centroids, clusterAssment
以上便是kmeans的python实现。可以看出,代码写得是相当的简洁朴实,有许多地方是值得思考和借鉴的。
比如对于上面的randCent函数的实现,该函数的作用是:为给定数据集生成一个包含K个随机质心的集合。
那么具体要怎么实现该函数呢?对于一般的数据集,我们一般是以每一行代表一个数据样本,每一列代表数据维度(或数据属性)。K个随机质心的集合就需要K*N(N为数据维度数目,也是数据集的列数)的矩阵来存储。那么要如何要随机选取呢?
一种比较简单粗暴的方法就是:对于每个质心的N个维度的值依次进行随机选取,这样需要两个for循环来实现了,当然选取的值要保证在数据集的范围之内(若取在数据集范围之外,比如取在一个距离数据集所有样本都很远的点,那计算欧式距离还有什么意义?)。实现如下:
def randCent(dataSet, k):
'''
输入:数据集、K
输出:包含K个随机质心(centroid)的集合
描述:为给定数据集生成一个包含K个随机质心的集合
'''
n = shape(dataSet)[1] #得到数据集的列数
m = shape(dataSet)[0] #得到数据集的行数
centroids = mat(zeros((k,n))) #得到一个K*N的空矩阵
for i in range(k):
for j in range(n): #对于每一列(对于每一个数据维度)
minJ = min(dataSet[:,j]) #得到当前列最小值
rangeJ = float(max(dataSet[:,j]) - minJ) #得到当前列的范围
centroids[i,j] = minJ + rangeJ * random.rand(1) #在最小值和最大值之间取值
#random模块的rand(a,b)函数返回a行b列的随机数(范围:0-1)
return centroids
而借用numpy矩阵的方便性,我们可以节省掉一个for循环:
def randCent(dataSet, k):
'''
输入:数据集、K
输出:包含K个随机质心(centroid)的集合
描述:为给定数据集生成一个包含K个随机质心的集合
'''
n = shape(dataSet)[1] #得到数据集的列数
centroids = mat(zeros((k,n))) #得到一个K*N的空矩阵
for j in range(n): #对于每一列(对于每一个数据维度)
minJ = min(dataSet[:,j]) #得到最小