聚类方法属于挖掘数据纵向结构的方法(假设为m*n矩阵,m为数据维度或者属性,n为数据个数),依据其特征的相似度或者距离进行归并到若干个“类”或者“簇”的过程。聚类属于无监督学习。常用的两种聚类算法:层次聚类(分聚合以及分裂两种算法)、k均值聚类算法。
聚类的相似度或距离,有4种方法:闵可夫斯基距离,马哈拉诺比斯距离,相关系数,夹角余弦。通过定义距离之后就可以定义族或者簇,并定义类与类之间的距离。
层次聚类假设类别之间存在层次结构,将样本聚到层次化的类中,这里不细讨论,主要描述k均值聚类算法。
k均值聚类算法是一个迭代的过程,每次迭代包括两个步骤,首先选择k个类中心,将样本指派到最近的中心的类中,得到一个聚类结果;然后更新每个类的样本的均值,作为类的新的中心;重复以上步骤,直到收敛为止。
伪代码如下:
创建k个点作为起始质心(通常是随机选择)
当任意一个点的簇分配结果发生改变时
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每一个簇,计算簇汇总所有点的均值并将其作为质心
python代码实现如下:
(注意代码里的数据行列意义与开始定义有所不同,每行代表各个数据,每列代表数据的维度或属性,下同)
import numpy as np
#距离计算
def dist_eclud(vec_a,vec_b):
return np.sqrt(np.sum(np.power(vec_a - vec_b,2)))
#构造质心
def rand_cent(data_set,k):
n = np.shape(data_set)[1]
centroids = np.mat(np.zeros((k,n)))
for j in range(n):
min_j = min(data_set[:,j])
range_j = float(max(data_set[:,j]) - min_j)
centroids[:,j] = min_j + range_j * np.random.rand(k,1)
return centroids
#k均值算法
def k_means(data_set,k,dist_meas = dist_eclud,create_cent = rand_cent):
m = np.shape(data_set)[0]
cluster_assment = np.mat(np.zeros((m,2)))
centroids = create_cent(data_set,k)
cluster_changed = True
while cluster_changed:
cluster_changed = False
for i in range(m):
min_dist = np.inf
min_index = -1
#寻找最近的质心
for j in range(k):
dist_ji = dist_meas(centroids[j,:],data_set[i,:])
if dist_ji < min_dist:
min_dist = dist_ji
min_index = j
if cluster_assment[i,0] != min_index:
cluster_changed = True
cluster_assment[i,:] = min_index,min_dist**2
#更新质心的位置
for cent in range(k):
pts_in_clust = data_set[np.nonzero(cluster_assment[:,0].A == cent)[0]]
centroids[cent,:] = np.mean(pts_in_clust,axis = 0)
return centroids,cluster_assment
由于k均值聚类算法是一个收敛到局部最小值而非全局最小值的算法,因此作出改良,在保持簇数目不变的情况下提高簇的质量,通过对簇的划分,计算损失函数sse是否降到最低,划分过程不断重复,直到得道指定的簇数目为止。二分k均值伪代码如下:
将所有点看成一个簇
当簇数目小于k时
对每一个簇
计算总误差
在给定的簇上面进行k均值分类(k=2)
计算将该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作
python代码实现如下:
#二分k均值
def bi_kmeans(data_set,k,dist_meas = dist_eclud):
m = np.shape(data_set)[0]
cluster_assment = np.mat(np.zeros((m,2)))
#创建一个初始簇
centroid0 = np.mean(data_set,axis = 0).tolist()[0]
cent_list = [centroid0]
for j in range(m):
cluster_assment[j,1] = dist_meas(np.mat(centroid0),data_set[j,:]) **2
while len(cent_list) < k:
lowest_sse = np.inf
for i in range(len(cent_list)):
#尝试划分每一个簇
pts_in_curr_cluster = data_set[np.nonzero(cluster_assment[:,0].A == i)[0],:]
centroid_mat,split_clust_ass = k_means(pts_in_curr_cluster,2,dist_meas)
sse_split = np.sum(split_clust_ass[:,1])
sse_not_split = np.sum(cluster_assment[np.nonzero(cluster_assment[:,0].A != i)[0],1])
print("sse_split,and not split :",sse_split,sse_not_split)
if sse_split + sse_not_split < lowest_sse:
best_cent_to_split = i
best_new_cents = centroid_mat
best_clust_ass = split_clust_ass.copy()
lowest_sse = sse_split + sse_not_split
#更新簇的分配结果
best_clust_ass[np.nonzero(best_clust_ass[:,0].A == 1)[0],0] = len(cent_list)
best_clust_ass[np.nonzero(best_clust_ass[:,0].A == 0)[0],0] = best_cent_to_split
print("the best cent to split is :",best_cent_to_split)
print("the len of best clust ass is :",len(best_clust_ass))
cent_list[best_cent_to_split] = best_new_cents[0,:]
cent_list.append(best_new_cents[1,:])
cluster_assment[np.nonzero(cluster_assment[:,0].A == best_cent_to_split)[0],:] = best_clust_ass
return cent_list,cluster_assment