聚类分析将数据划分成有意义或有用的簇。如果目标是划分成有意义的簇,则簇应当捕获数据的自然结构。
聚类是一种无监督学习方法,因为只是根据样本的相似度或距离将其进行归类,而类或簇事先并不知道。
常用的聚类算法包括:层次聚类、K均值聚类、DBSCAN。在这篇文章中我们简单介绍一下基本KMeans算法的实现。
因为基本KMeans算法的原理比较简单,这里就不详细解释了,感兴趣的朋友可以查阅李航老师的《统计学习方法(第2版)》。
基本KMeans算法可以用下面的伪代码表示:
选择K个点作为初始质心。
repeat
将每个点指派到最近的质心,形成K个簇。
重新计算每个簇的质心。
util 质心不发生变化。
下面我们给出Python的代码实现,代码中使用的数据集为鸢尾花数据集,距离采用欧氏距离,算法评价标准为误差平方和(Sum of squared error)。
首先,鸢尾花数据集的格式如下:
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3.0,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5.0,3.6,1.4,0.2,Iris-setosa
然后,具体的基本KMeans代码实现如下:
import numpy as np
import pandas as pd
def load_dataset(path):
"""
加载聚类训练集,此处以鸢尾花数据集为例
Args:
path: 训练集路径,默认格式为csv
"""
csv_data = pd.read_csv(path, header=None)
dataset = csv_data.loc[:, 0:3]
labels = csv_data.loc[:, 4]
return dataset, labels
def euclidean_distances(vec_x, vec_y):
"""
计算两个向量的欧氏距离
Args:
vec_x: 样本向量x
vec_y: 样本向量y
"""
return np.sqrt(np.sum(np.power(np.asarray(vec_x) - np.asarray(vec_y), 2)))
class KMeans(object):
"""K-均值聚类算法类"""
def __init__(self, n_clusters, samples):
"""
Args:
n_clusters: int, 可选, 默认为5, 类簇数量
"""
self.n_clusters = n_clusters
self.centroids = self.init_centroids(samples)
def init_centroids(self, samples):
"""
初始化质心
Args:
samples: 样本矩阵, shape = [n_samples, n_features]
"""
samples = np.asmatrix(samples)
n_features = np.shape(samples)[1]
centroids = np.matrix(np.zeros((self.n_clusters, n_features)))
# 质心的每个维度值的范围应该为所有样本该维度的最小值和最大值之间
for i in range(n_features):
min_feature = min(samples[:, i]) # 样本某个特征维度的最小值
max_feature = max(samples[:, i]) # 样本某个特征维度的最大值
# 得到均匀分布的所有质心某个维度的值
centroids[:, i] = np.random.uniform(min_feature, max_feature, (self.n_clusters, 1))
return centroids
def fit(self, samples):
"""
聚类函数
Args:
samples: 待聚类的样本, shape = [n_samples, n_features]
"""
samples = np.asmatrix(samples)
n_samples, n_features = np.shape(samples)
# 创建矩阵保存每个样本的簇分配结果,矩阵行索引表示样本序号,
# 第一列数据表示样本所属簇的索引,第二列表示该样本到其所属簇的质心的距离
cluster_assignment = np.matrix(np.zeros((n_samples, 2)))
cluster_changed = True # 表示所有样本的簇分配结果是否发生改变的标志
while cluster_changed:
cluster_changed = False
# 将每个样本分配到最近的质心
for i in range(n_samples):
min_dist = float('inf') # 与各个质心的最近距离
min_index = -1 # 距离最近的质心的索引
for j in range(self.n_clusters):
# 寻找最近的质心
dist = euclidean_distances(samples[i, :], self.centroids[j, :])
if dist < min_dist:
min_dist = dist
min_index = j
if cluster_assignment[i, 0] != min_index:
cluster_changed = True
cluster_assignment[i, :] = min_index, min_dist ** 2
# 更新质心的位置
for n in range(self.n_clusters):
# 获取每个簇所有样本的向量并求均值即为新的质心
points_in_cluster = samples[np.nonzero(cluster_assignment[:, 0].A == n)[0]]
self.centroids[n, :] = np.mean(points_in_cluster, axis=0)
return self.centroids, cluster_assignment
if __name__ == '__main__':
iris, _ = load_dataset('./data/iris.txt')
kmeans = KMeans(3, iris)
centroids, cls_ass = kmeans.fit(iris)
上述代码实现的是基本KMeans算法。基本KMeans算法容易收敛到局部最小值,而非全局最小值。改进的方式包括初始质心时不再随机初始化,而是选择K-means++算法初始质心;还有一种就是二分KMeans算法。这两种是比较常见的,当然也有别的改进方法,这里只简单介绍一下这两种方法的思想,后续有时间也会给出Python实现。
初始化簇表,使之包含由所有的点组成的簇。
repeat
从簇表中取出一个簇
{对选定的簇进行多次二分“试验”。}
for i = 1 to 实验次数 do
使用基本KMeans,二分选定的簇
end for
从二分试验中选择具有最小总误差平方和(SSE)的两个簇
将这两个簇添加到簇表中。
util 簇表中包含K个簇。
待分裂的簇有很多选取方法,可以选取样本数量最多的簇,也可以选取具有最大SSE的簇,或者使用一个结合样本数量和SSE的标准进行选择。不同的选择会产生不同的簇。
最后我还想说的是,其实KMeans看似简单,但要把它完全搞透还是要下一番功夫的。比如KMeans的迭代算法实际上是一种EM算法,然后还有避免KMeans陷入局部最优的其他改进方法。这些东西也不是一篇文章能够写完的,后续有机会我还会继续更新。这是我的第一篇KMeans学习笔记,给自己mark一下。
《数据挖掘导论》8.2 K均值.Pang-Ning Tan 著
《机器学习实战》10.1 K-均值聚类算法. Peter Harrington 著
《百面机器学习 算法工程师带你去面试》第五章 非监督学习 01 K均值聚类.诸葛越 主编.葫芦娃 著