本文主要参考周志华《机器学习》的9.4.1章节,对以k均值聚类为代表的原型聚类做简单介绍,并使用numpy实现kmeans聚类。
在讲kmeans聚类之前,先说一说原型聚类。
原型聚类亦称“ 基于原型的聚类” ,此类算法假设聚类结构能通过一组原型刻画,在现实聚类任务中极为常用.通常情形下,算法先对原型进行初始化,然后对原型进行迭代更新求解.采用不同的原型表示、不同的求解方式,将产生不同的算法。 《机器学习》
包括更为常用的GMM(高斯混合聚类),模糊C均值聚类,都是属于原型聚类的范畴。那么如何理解原型聚类呢?我是这样理解的,所谓原型,先定义一个模型,对这个模型迭代求最优解,最后获取聚类结果。
说到这里,对第一次接触聚类的朋友可能不太好理解。但是不用着急,随着逐渐熟悉,会慢慢理解。
给定样本集D= {X1,X2,...Xn}, “K 均值” (k-means)算法针对聚类所得簇划分c = {C1,C2,. . .,Ck}最小化平方误差,即求下式极小值:
其中上μi表示第i类的中心,这是一个需要估计的参数。双竖线中的内容就是i类中的元素到第i类的中心的距离(欧式距离)。根据上述公式,我们可以这样理解,kmeans算法要做的就是把每一类到其聚类中心的距离和最小化。
但是要如何求解上述问题呢?首先可以想到的是暴力求解,即把每一种聚类组合按照上述公式计算,选取最小的。但是这是一个NP问题,一共有k的n次方中划分方法(包涵有些类为空的情况)。庞大的计算复杂度,不允许这种方式。于是就有了kmeans算法。该方法的核心思想是,先初试化k个聚类中心,然后根据聚类中心划分所有元素(划入离其最近的聚类中心)。接着又根据这一类的元素计算新的聚类中心,反复迭代至聚类中心几乎不变。kmean的本质是EM算法。
算法伪代码如下:
numpy实现如下:
import numpy as np
from sklearn.datasets import load_iris
import seaborn as sns
from sklearn.cluster import KMeans
import matplotlib.pyplot as plt
def distance( a, b, p):
a = np.array(a)
b = np.array(b)
return np.sum((a - b) ** p) ** (1 / p)
class Mykmean(object):
def __init__(self, n_clusters, max_iter, init=""):
self.max_iter = max_iter
self.n_clusters = n_clusters
self.center = 0
self.labels = 0
def fit(self, X):
# 初始化聚类中心 采用平均的方法
center = np.arange(0, self.n_clusters) / (self.n_clusters) + 1 / (2 * self.n_clusters)
temp = []
for i in range(X.shape[1]):
temp.append(center)
self.center = np.max(X, axis=0) * np.array(temp).T
# 初始化类别标记
self.labels = np.zeros((X.shape[0])).astype("int8")
while self.max_iter > 0:
# 根据初试化得聚类中心划分类
for m in range(0, X.shape[0]):
dists = []
for k in range(0, self.n_clusters):
o_dist = distance(X[m, :], self.center[k, :], 2)
dists.append(o_dist)
max_index = dists.index(min(dists))
self.labels[m] = max_index
# 计算新的聚类中心
for k in range(0, self.n_clusters):
temp = X.copy()
temp = np.delete(temp, np.where(self.labels == k), axis=0)
self.center[k, :] = np.mean(temp, axis=0)
self.max_iter -= 1
def pairing(data, truth, label):
datatemp = data.copy()
centerTruth = []
center = []
new_label = np.zeros(label.shape) - 1
for i in range(0, np.max(truth) + 1):
centerTruth.append(list(np.mean(datatemp[np.argwhere(truth == i)], axis=0)))
center.append(list(np.mean(datatemp[np.argwhere(label == i)], axis=0)))
for i in range(0, np.max(truth) + 1):
temp = []
for j in range(0, np.max(truth) + 1):
temp.append(distance(centerTruth[i], center[j], 2))
number = temp.index(min(temp))
print(number)
new_label[label == number] = i
return new_label
def calcu_acc(truth, label):
temp = np.zeros(label.shape)
temp[np.argwhere(label == truth)] = 1
return np.sum(temp) / label.shape[0]
if __name__ == '__main__':
iris = load_iris()
estimator = KMeans(n_clusters=3,)
estimator.fit(iris.data)
label = pairing(iris.data, iris.target, estimator.labels_)
acc = calcu_acc(iris.target, label)
print("kmeans acc is :", acc)
kmeanmodel = Mykmean(3, 100)
kmeanmodel.fit(iris.data)
label = pairing(iris.data, iris.target, kmeanmodel.labels)
acc = calcu_acc(iris.target, label)
print("mykmeans acc is :", acc)
鸢尾花数据测试结果如下:
这里我实现的kmean得到的结果相比label的准确率低于scipy库实现的,主要是因为初始化聚类中心的方式,scipy中默认采用了kmean++的初始化方法,而本文中采用的是平均划分的方法。
这里引出了一个局部最优解的问题,对于非凸问题来说我们的EM算法只能求得其局部的最优解。
tips:ML新手,如果哪里写得不好,大家多多包涵。如果哪里有疑问,欢迎留言讨论。此外,我将继续有间断用numpy实现周志华《机器学习》的算法,欢迎大家关注。