K-Means算法
给定样本集
D=x1,x2,...,xm
,假定聚类的簇划分
C=C1,C2,...,Ck
。k-means
算法的目标是最小化平均距离:
K-Means算法采用贪心策略,通过迭代优化来近似求解。原理是:
假定一组向量做为所有簇的簇均值向量,然后根据假设的簇均值向量给出了数据集D的一个簇划分,再根据这个簇划分来计算真实的簇均值向量(这是一个迭代过程)。
算法步骤:
算法示意图如下:
算法特点
优点
- 简单并且很有限的聚类方法
- 算法收敛的很快
- 相对高效和高扩展性。算法复杂度为
O(t⋅k⋅n)
- t为迭代次数 k为中心点个数 n为样本个数
缺点
- 需要指定聚类中心个数k
- 需要先验和主观知识
- 可能会陷入局部最优(实际过程中,可以选择多个不同初始化点)
- 对噪点比较敏感
不一样的初始点,聚类结果可能不同,如下图:
Python实现K-means算法
代码:所有代码见我的github
数据集
这里为了省事,直接使用sklearn包下的make_blobs函数生成数据,该函数的原理是在给定的中心点周围产生采样若干数据,采样函数是正态分布(可指定正态函数标准差)。
产生数据代码如下:
from sklearn.datasets.samples_generator import make_blobs
def create_data(centers, num=100, std=0.7):
'''
生成用于聚类的数据集
:param centers: 聚类的中心点组成的数组。如果中心点是二维的,则产生的每个样本都是二维的。
:param num: 总的样本数
:param std: 生成数据簇中样本的标准差
:return: 用于聚类的数据集。是一个元组,第一个元素为样本集,第二个元素为样本集的真实簇分类标记
'''
X, labels_true = make_blobs(n_samples=num, centers=centers, cluster_std=std)
return X, labels_true
随机产生的数据如下:
使用matplotlib绘制出来
def plot_data(*data): ''' 绘制用于聚类的数据集 :param data: 可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记 :return: None ''' X, labels_true = data labels = np.unique(labels_true) # np.unique保留array中不同的数值,即保留所有labels fig = plt.figure() ax = fig.add_subplot(1,1,1) colors = 'rgbyckm' # 每个簇的样本标记不同的颜色 for i, label in enumerate(labels): position = labels_true == label # 选取出不同label的所有点的index ax.scatter(X[position, 0], X[position, 1], label="cluster %d"%label, color=colors[i%len(colors)], s=1) # 绘制数据 设置size=1 ax.legend(loc="best", framealpha=0.5) ax.set_xlabel("X[0]") ax.set_ylabel("Y[1]") ax.set_title("data") plt.show() if __name__=='__main__': centers=[[1,1],[5,6],[1,10],[10,20]] # 用于产生聚类的中心点 X,labels_true=create_data(centers,400,0.5) # 产生用于聚类的数据集
结果如下:
Python下手写K-means算法
这里贴出了k-means算法部分:
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
m = np.shape(dataSet)[0]
clusterAssment = np.mat(np.zeros((m, 2))) # 用于分配数据点
centroids = createCent(dataSet, k) # 随机生成k个均值向量
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m): #为每个数据点分配到最近的均值向量上
minDist = np.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, :] = int(minIndex), minDist ** 2
for cent in range(k): # recalculate centroids
ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]] # get all the point in this cluster
centroids[cent, :] = np.mean(ptsInClust, axis=0) # assign centroid to mean
return centroids, clusterAssment
程序结果图如下:
方块点的是聚类后的均值向量,聚类的效果看起来还可以~
使用scikit-learn包下Kmeans类
K-means函数原型
KMeans是scikit-learn提供的 k 均值聚类算法模型,定义如下:
def __init__(self, n_clusters=8, init='k-means++', n_init=10,
max_iter=300, tol=1e-4, precompute_distances='auto',
verbose=0, random_state=None, copy_x=True,
n_jobs=1, algorithm='auto'):
参数介绍
参数 | description |
---|---|
n_clusters | int, optional, default: 8 指定分类簇的中心数量. |
max_iter | int, default: 300 单轮k均值算法,最大迭代次数 |
n_init | int, default: 10 指定算法运行次数,每一次都会选择一组不同的初始化均值向量,算法最后会选择最佳的分类簇作为最终结果 |
init | {‘k-means++’, ‘random’ or an ndarray} 指定初始化均值向量的策略 - k-means++: 该初始化策略选择的初始化均值向量相互之间都距离较远,它的效果较好。 - random:从数据集中随机选择K个样本作为初始化均值向量 - 或者提供一个形状(n_clusters, n_features)的数据作为初始化均值向量 k均值算法总能够收敛,但是其收敛程度情况依赖于初始化的均值。有可能收敛到局部极小值。因此通常都是用多组初始化均值来计算若干次,选用最好的结果。K-means++在一定程度解决这个问题 |
algorithm | “auto”, “full” or “elkan”, default=”auto” 使用的算法。 |
precompute_distances | {‘auto’, True, False} 指定是否提前计算好样本之间的距离.(提前算,需要更多的内存,但是速度快) auto : if n_samples * n_clusters > 12 million,就不提钱计算了 True:提前计算 False:不提前计算 |
tol | float, default: 1e-4 指定判断算法收敛的阈值 |
n_jobs | int 指定并行运算的CPU数量 |
random_state | integer or numpy.RandomState, optional 指定RandomState实例 |
verbose | int, default 0 是否打印日志 |
copy_x | boolean, default True True:不会改变数据值 False:改变原始数据,用于节省内存,返回时返回原始数据,可能会有精度损失 |
属性介绍
属性 | description |
---|---|
cluster_centers_ | array, [n_clusters, n_features] 分类簇的均值向量 |
labels_ | 每个样本簇的簇标记 |
inertia_ | float 所有样本距样本簇中心的距离和 |
方法介绍
方法 | description |
---|---|
fit(self, X, y=None) | 训练模型 |
predict(self, X) | 预测样本所属的簇 |
fit_predict(self, X, y=None) | 训练模型并预测样本所属簇 |
score(self, X, y=None) | Opposite of the value of X on the K-means objective |
测试K-Means簇数量影响
K-means算法中聚类簇数量的选择是非常重要的,下面我们测试不同的聚类簇下各个指标的变化。
def test_Kmeans_nclusters(*data):
'''
测试 KMeans 的聚类结果随 n_clusters 参数的影响
:param data: 可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记
:return: None
'''
X,labels_true=data
nums=range(1,50)
ARIs=[]
Distances=[]
for num in nums:
clst=cluster.KMeans(n_clusters=num)
predicted_labels=clst.fit_predict(X)
ARIs.append(adjusted_rand_score(labels_true,predicted_labels))
Distances.append(clst.inertia_)
## 绘图
fig=plt.figure()
ax=fig.add_subplot(1,2,1)
ax.plot(nums,ARIs,marker="+")
ax.set_xlabel("n_clusters")
ax.set_ylabel("ARI")
ax=fig.add_subplot(1,2,2)
ax.plot(nums,Distances,marker='o')
ax.set_xlabel("n_clusters")
ax.set_ylabel("inertia_")
fig.suptitle("KMeans")
plt.show()
输出:
聚类簇数量选择4时,
同时注意到,聚类簇数量从1到4这个过程,样本到簇中心的距离和迅速下降,而从4以后,基本不变了。
测试K-means算法运行次数和选择中心均值向量策略的影响
测试k-means++
和random
两个初始策略,测试1到50次下算法运行结果。
def test_Kmeans_n_init(*data):
'''
测试 KMeans 的聚类结果随 n_init 和 init 参数的影响
:param data: 可变参数。它是一个元组。元组元素依次为:第一个元素为样本集,第二个元素为样本集的真实簇分类标记
:return: None
'''
X,labels_true=data
nums=range(1,50)
ARIs_k=[]
Distances_k=[]
ARIs_r=[]
Distances_r=[]
for num in nums:
clst=cluster.KMeans(n_init=num,init='k-means++') # 使用k-mean++策略
predicted_labels=clst.fit_predict(X)
ARIs_k.append(adjusted_rand_score(labels_true,predicted_labels))
Distances_k.append(clst.inertia_)
clst=cluster.KMeans(n_init=num,init='random') # 使用random策略
predicted_labels=clst.fit_predict(X)
ARIs_r.append(adjusted_rand_score(labels_true,predicted_labels))
Distances_r.append(clst.inertia_)
## 绘图
fig=plt.figure()
ax=fig.add_subplot(1,2,1)
ax.plot(nums,ARIs_k,marker="+",label="k-means++")
ax.plot(nums,ARIs_r,marker="+",label="random")
ax.set_xlabel("n_init")
ax.set_ylabel("ARI")
ax.set_ylim(0,1)
ax.legend(loc='best')
ax=fig.add_subplot(1,2,2)
ax.plot(nums,Distances_k,marker='o',label="k-means++")
ax.plot(nums,Distances_r,marker='o',label="random")
ax.set_xlabel("n_init")
ax.set_ylabel("inertia_")
ax.legend(loc='best')
fig.suptitle("KMeans")
plt.show()
输出如下: