03-0005 Kmeans算法(Python)

1.介绍

k-平均算法(英文:k-means clustering):
源于信号处理中的一种向量量化方法,现在则更多地作为一种聚类分析方法流行于数据挖掘领域。k-平均聚类的目的是:把 个点(可以是样本的一次观察或一个实例)划分到k个聚类中,使得每个点都属于离他最近的均值(此即聚类中心)对应的聚类,以之作为聚类的标准。
来自于维基百科[校园网进不去了]

K均值聚类算法:
是先随机选取K个对象作为初始的聚类中心。然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。
来自于百度百科

Kmeans是比较简单的无监督聚类算法,思想正如以上所说,没有任何疑义。就单一的Kmeans算法而言,终止条件会是一个难点,其他都好说

2.流程

没有绘制流程图,在计算过程中,思想不变的前提之下,可以作调整的点有两个,一个是终止条件,一个是划分条件。
在此处,划分条件是距离最小,终止条件是聚类中心不变。
也是因此实验被简化很多,由于Python下float若经过运算可能会出现逻辑上相等但实际上不等的情况,故这里的聚类中心计算出来的都是整形的值。

  1. 样本初始化,聚类中心初始化。[这里初始化一切自己需要的值]
  2. 求出每一个样本对每一个聚类中心的距离,取最小的作为该样本的分类。
  3. 对于每一类[簇]求一个中心点,作为新的聚类中心。
  4. 判断聚类中心是否改变,若改变回到步骤2,否则结束。

3.优缺点

3.1优点

  1. 原理简单,收敛较快,容易实现,代码量小
  2. 需要调整的参数少
  3. 聚类效果比较好

3.2缺点

  1. 需要分成几类?k未知。
  2. 对于不是凸的数据集比较难收敛。[这点我还不太懂]
  3. 对于数据本身的要求比较高。[相性不合的不好分 ]
  4. 初始聚类中心的选择上有一定难度。[选不好会出现簇元素为0的情况]

4.源码

我有尽量的减少圈复杂度以及代码行数,但显然,这不是一个好的办法,因为代码变得很难懂,而且还不想写注释,我觉得身为一个程序员既要有理解任何晦涩难懂代码的能力又要有写出简洁明了代码的能力,但是我写完代码之后,就有点o((⊙﹏⊙))o···,好惨淡的人生~
能用一行解决的问题,绝对不用两行解决,为了以后能更好额读懂自己写的代码,还是坚持写完了注释。

import sys
from numpy import *
import numpy as np

# 对all_input进行处理,获取所需聚成k类、k个初始聚类中心center、需聚类的点集数据dataMat
def dataGet(all_input):
	# 数据初始化,python对应赋值的操作,习惯就好
	# l:		输入数据的长度
	# k:		聚类中心的个数
	# s:		样本点的个数
	# center:	用于存储聚类中心的列表
	# dataMat:	用于存储样本点的列表
	[l,k,s,center,dataMat]=[len(all_input),int(all_input[0]),int(all_input[0])*2+1,[],[]]
	[center,dataMat]=[[all_input[i+1:i+3] for i in range(0,int(all_input[0])*2,2)],[all_input[i+1:i+3] for i in range(s,l-1,2)]]
	return k,center,dataMat

# 计算距离,可选择欧几里得距离,或自行设计距离公式,输入为两个点
def distEclud(vecA, vecB):
	# 利用公式返回范数,这里也就是两点之间的距离
	# 参考:https://zhuanlan.zhihu.com/p/33217726
	# 参考:https://blog.csdn.net/weixin_41043240/article/details/79540034
	return np.linalg.norm(vecA-vecB)

# k-means 聚类算法,输入为需要聚类的点集、聚集成几类、距离公式,需同学补充完成,返回最终中心点集centroids、点集所属标志矩阵clusterAssment
def kMeans(dataSet,k,center, distMeans =distEclud):
	# 分别对于数据进行初始化
	# m: 					dataSet的元素个数,有几行
	# centroidslist: 		用于保存上次的聚类中心,以便判断[[x,y]]
	# centroids: 			存储聚类中心,当前循环的聚类中心[[x,y]]
	# clusterAssment: 		存储每一个点所属的类别,以及到该类别的距离[[label,dis]]
	# mat(zeros((m,2)): 	生成一个有m行两列的全零矩阵
	# mat(center):			将列表转化为矩阵
	[m,centroidslist]=[len(dataSet),[]]
	[centroids,clusterAssment]=[mat(center),mat(zeros((m,2)))]

	# 当聚类中心不发生改变时,终止循环。列表可以直接进行比较。免去了写循环的操作。后者转化为了列表
	while centroidslist!=centroids.tolist():
		# 赋值操作
		# centroidslist: 	保存当前的循环的聚类中心,一边与下轮循环比较
		# count: 			用于存储每个聚类中心的横、纵坐标的累加和及该簇元素个数。[sum_x,sum_y,count]
		[centroidslist,count]=[centroids.tolist(),mat(zeros((k,3)))]
		for i in range(m):
			# 初始化该点到聚类中心的距离为无穷大,此处为999999.0
			clusterAssment[i,1]=999999.0
			for j in range(k):
				# 获取距离
				dis=distMeans(centroids[j,:],dataSet[i,:])
				# 只要有更小的,就进行替换
				if(dis<clusterAssment[i,1]):
					[clusterAssment[i,1],clusterAssment[i,0]]=[dis,j]

			# 为了不让系统多次进行类型转化,此处将语句提取出来
			# 对于count中的数值,进行更新
			clu=int(clusterAssment[i,0])
			[count[clu,0],count[clu,1],count[clu,2]]=[count[clu,0]+dataSet[i,0],count[clu,1]+dataSet[i,1],count[clu,2]+1]

		# 更近聚类中心,如果有出现分母为零的情况,说明该聚类中心没有聚到任何点,[簇内无元素]
		# 这是初始聚类中心选取的不合理,是数据不好,也是Kmeans算法的缺点
		# 若有分母为零,则将其设为无穷大,999999.0
		# Error:can not convert Nan to integer
		for i in range(k):
			[centroids[i,0],centroids[i,1]]=[count[i,0]/count[i,2],count[i,1]/count[i,2]] if count[i,2]!=0 else [999999,999999]
		pass

	return centroids, clusterAssment

def main(all_input):
	print("input:",all_input)
	k,center,dataMat=dataGet(all_input) # 外部可以直接调用
	myCentroids,clustAssing = kMeans(mat(dataMat),k,center)#得到聚类中心,以及数据分别属于哪一类的标志矩
	print("ouput:",clustAssing[:,0].flatten()[0])#输出每个样本点属于哪类
	print()

# 输入
# 第一个元素表示有几个聚类中心,向后查几对
# 紧接着的一个元素是有几个样本,向后查几对,刚好到末尾
all_input1=[2,5,7,1,5,4,1,2,3,4,5,6,7,8]
all_input2=[3,0,0,5,7,1,5,6,1,2,3,4,5,6,7,8,12,23,11,45]
all_input3=[4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4]
all_input4=[2,5,8,9,6,3,2,1,4,5,6,9]
main(all_input1)
main(all_input2)
main(all_input3)
main(all_input4)

# 输出
# input: [2, 5, 7, 1, 5, 4, 1, 2, 3, 4, 5, 6, 7, 8]
# ouput: [[1. 1. 0. 0.]]

# input: [3, 0, 0, 5, 7, 1, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 12, 23, 11, 45]
# ouput: [[0. 0. 2. 2. 1. 1.]]

# input: [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
# ouput: [[0. 0. 0. 0.]]

# input: [2, 5, 8, 9, 6, 3, 2, 1, 4, 5, 6, 9]
# ouput: [[1. 0. 0.]]

# [Finished in 0.4s]

5.结果

input: [2, 5, 7, 1, 5, 4, 1, 2, 3, 4, 5, 6, 7, 8]
ouput: [[1. 1. 0. 0.]]

input: [3, 0, 0, 5, 7, 1, 5, 6, 1, 2, 3, 4, 5, 6, 7, 8, 12, 23, 11, 45]
ouput: [[0. 0. 2. 2. 1. 1.]]

input: [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
ouput: [[0. 0. 0. 0.]]

input: [2, 5, 8, 9, 6, 3, 2, 1, 4, 5, 6, 9]
ouput: [[0. 0. 0.]]

[Finished in 0.4s]

6.总结

在该程序运行过程中本来是不应该出现某簇中无元素的现象的,而且代码中直接将其设置为无穷大的方法并不可取,只是课程需要,应付一下,应该找到某种策略比如取中心点或者向着某一个簇心移动。
但是如果按照自己的策略进行运行,虽然有结果,但是所分类别的编号会有所改变,在某云上跑的时候就会失败,故而采取等于无穷大的方法,使其结果唯一。

在写代码的过程中很多地方是简化的写法,也因此来了兴致,去看了看其他博文,推荐一下:
一行 Python 代码
Python的22个编程技巧
python的30个编程技巧
Python if 和 for 的多种写法

python if else for 写在一行的形式:

a=[i if i%2==0 else 2 for i in range(10)]
# [0, 2, 2, 2, 4, 2, 6, 2, 8, 2]

但是一直都只能返回一个值,不能有进行批量返回。
话说昨天看了《复联4》,笑罢哭罢。[2019年4月25日 9:30~1:30]

发布了14 篇原创文章 · 获赞 9 · 访问量 4292

猜你喜欢

转载自blog.csdn.net/qq_37766828/article/details/89555331
今日推荐