原理解析
今天,我们来讲一下Kmeans,一种无监督聚类算法,也是最为经典的基于划分的聚类方法,它的思想是:对于给定的样本集,按照样本之间的距离大小,将样本集划分为K个簇。让簇内的点尽量紧密的连在一起,而让簇间的距离尽量的大。
实现流程如下:
1、先确定数据集聚类个数k;
2、在数据集中随机选取k个数据,作为初始质心;
3、计算数据集中每个样本到每个质心的距离,把样本划分到
距离最小的质心所属的类别;
4、根据聚类结果,重新计算质心,当本次计算的质心与上一次质心完全一样(或者收敛)时,停止迭代;
否则更新质心,继续执行步骤2、3、4。
流程比较简单,这里要说的是注意几个问题:
1、步骤1确定数据集聚类个数k,很显然,当数据集D是高维数据时,没办法很快确定合适的k;
2、步骤2随机选取数据作为初始质心,可以想象,当质心之间距离很近的时候,将需要迭代很多次,会影响收敛速度,甚至不能收敛,只能得到局部最小值;
3、步骤3中计算距离,我们应该想到,距离受到量纲的影响,在使用时,需要考虑一下标准化;
然后就是基于所有样本,因此会对异常数据比较敏感;
4、当数据集很大时,计算的时间复杂度很高。
可见优化的点很多(优化部分我们明天再写,今天只实现最基础的Kmeans)。
代码实现
基于上面描述的过程,我们来写python实现代码:
import numpy as np
from sklearn.datasets import make_blobs
from matplotlib import pyplot as plt
#无监督算法,学习过程就是训练质心的位置,进行聚类
class Kmeans:
def __init__(self,k):
self.k = k
def calc_distance(self,x1,x2):
diff = x1 - x2
distances = np.sqrt(np.square(diff).sum(axis=1))
return distances
def fit(self,x):
self.x = x
m,n = self.x.shape
#随机选定k个数据作为初始质心,不重复选取
self.original_ = np.random.choice(m,self.k,replace=False)
#默认类别是从0到k-1
self.original_center = x[self.original_]
while True:
#初始化一个字典,以类别作为key,赋值一个空数组
dict_y = {}
for j in range(self.k):
dict_y[j] = np.empty((0,n))
for i in range(m):
distances =self.calc_distance(x[i],self.original_center)
#把第i个数据分配到距离最近的质心,存放在字典中
label = np.argsort(distances)[0]
dict_y[label] = np.r_[dict_y[label],x[i].reshape(1,-1)]
centers = np.empty((0,n))
#对每个类别的样本重新求质心
for i in range(self.k):
center = np.mean(dict_y[i],axis=0).reshape(1,-1)
centers = np.r_[centers,center]
#与上一次迭代的质心比较,如果没有发生变化,则停止迭代(也可考虑收敛时停止)
result = np.all(centers == self.original_center)
if result == True:
break
else:
#继续更新质心
self.original_center = centers
def predict(self,x):
y_preds = []
m,n = x.shape
for i in range(m):
distances =self.calc_distance(x[i],self.original_center)
y_pred = np.argsort(distances)[0]
y_preds.append(y_pred)
return y_preds
实例演示
下面我们来生成一些数据集,来看看我们写的Kmeans聚类效果:
#下面是我今天找到的一个很有代表性的数据集,哈哈,为什么这么说,后面会用到
x,y = make_blobs(centers=5,random_state=20,cluster_std=1)
plt.scatter(x[:,0],x[:,1])
plt.show()
从图像上看,我们适合把这些数据分成5个类别,跑一下代码看一下结果:
model = Kmeans(k=5)
model.fit(x)
y_preds = model.predict(x)
plt.scatter(x[:,0],x[:,1],c=y_preds)
plt.show()
,聚类得挺完美的,等一下,有人跑出不一样的结果:
哈哈,要的就是这个效果(大家多重复执行一下上面这段代码,就会发现结果各种各样),为什么呢?
答案就在我们刚才说的几个问题上,就是步骤2的问题,
具体怎么优化我们明天写。
sklearn对比
上面的问题,sklearn中的Kmeans算法,会不会也出现呢?
我们来验证一下:
from sklearn.cluster import KMeans
model_1 = KMeans(n_clusters=5)
y_pred_1 = model_1.fit_predict(x)
plt.scatter(x[:,0],x[:,1],c=y_pred_1)
plt.show()
可以发现,不管执行多少次,聚类效果都很稳定,都很好。(原因解析,请看:链接: 手写算法-python代码实现Kmeans++以及优化)
总结
K-Means是个简单实用的聚类算法,现在对K-Means的优缺点做一个总结。
K-Means的主要优点有:
1、原理比较简单,实现也是很容易,收敛速度快。
2、聚类效果较优。
3、算法的可解释度比较强。
4、主要需要调参的参数仅仅是簇数k。
K-Means的主要缺点有:
1、K值的选取不好把握
2、对于不是凸的数据集比较难收敛
3、如果各隐含类别的数据不平衡,比如各隐含类别的数据量严重失衡,或者各隐含类别的方差不同,则聚类效果不佳。
4、采用迭代方法,得到的结果只是局部最优。
5、对噪音和异常点比较的敏感。