算法简介
聚类是针对特定样本,根据他们特征的相似度或者距离,将其归并到若干个“类”或“簇”。
这篇文章简单介绍KMeans聚类算法的过程,并仿sklean,实现一个简化版的的KMeans算法。
算法过程图示
为了演示过程,通过图例来更直观的感受一下KMeans的计算过程。
首先这里有8个点。我们要把这8个点分为2类。
假设我们要分为2类。那么我们先假设2个中心点是 。如下图:
根据2个中心点的中垂线。把点分为2类,线左边的是一类,右边的是一类:
重新计算2类的中心点,如图:
根据2个中心点,再次把所有点分为2类:
再次计算中心点:
如此,发现中心点已经不再变化了,是不是很简单呢。
整个过程就不断的重复2件事情:
(1)根据中心点,计算所有点的分类
(2)计算每个类的新的中心点
直到收敛。
这篇文章只介绍通俗易懂的过程,和自己实现的代码,至于算法模型和算法步骤,大家可以自行阅读书籍,或者查看其他博客。推荐李航博士的《统计学习方法》。
自己实现KMeans算法
本文按照上述代码,仿sklean,实现了一个简化版的KMeans算法, 可保存为KMeans.py 并引用:
import numpy as np
from math import sqrt
from collections import Counter
from sklearn.metrics import accuracy_score
import random
class KMeans:
def __init__(self, n_clusters=3, random_state=0):
assert n_clusters >=1, " must be valid"
self._n_clusters = n_clusters
self._random_state = random_state
self._X = None
self._center = None
self.cluster_centers_ = None
def distance(self, M, N):
return (np.sum((M - N) ** 2, axis = 1))** 0.5
def _generate_labels(self, center, X):
return np.array([np.argmin(self.distance(center, item)) for item in X])
def _generate_centers(self, labels, X):
return np.array([np.average(X[labels == i], axis=0) for i in np.arange(self._n_clusters)])
def fit_predict(self, X):
k = self._n_clusters
# 设置随机数
if self._random_state:
random.seed(self._random_state)
# 生成随机中心点的索引
center_index = [random.randint(0, X.shape[0]) for i in np.arange(k)]
center = X[center_index]
# print('init center: ', center)
n_iters = 1e3
while n_iters > 0:
# 记录上一个迭代的中心点坐标
last_center = center
# 根据上一批中心点,计算各个点所属的类
labels = self._generate_labels(last_center, X)
self.labels_ = labels
# 新的中心点坐标
center = self._generate_centers(labels, X)
# print('n center: ', center)
# 暴露给外头的参数
# 中心点
self.cluster_centers_ = center
# 返回节点对应的分类 {0, 1, ..., n}
# 如果新计算得到的中心点,和上一次计算得到的点相同,说明迭代已经稳定了。
if (last_center == center).all():
self.labels_ = self._generate_labels(center, X)
break
n_iters = n_iters - 1
return self
我们来看看实际效果吧,这里我绘制了很密集的点阵。来观察分类是否有遗漏。
import numpy as np
import matplotlib.pyplot as plt
from KMeans import KMeans
from sklearn.datasets import load_iris
t1 = np.linspace(-1, 1.5, 100)
t2 = np.linspace(-1, 1.5, 100)
X = np.array([(x, y) for x in t1 for y in t2])
plt.figure(figsize=(10, 10))
clf = KMeans(n_clusters=6, random_state=None)
clf.fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=clf.labels_)
center = clf.cluster_centers_
plt.scatter(center[:, 0], center[:, 1],marker="*",s=200)
实际效果如下:
注意:这里的色块其实是密集的点阵,而他们到各中心的分类也都是非常符合我们的预期的。
接下来,我们用该算法来实现以下鸢尾花数据集的分类吧。
首先,我们先导入数据集,看看实际点在图上的效果:
import numpy as np
import matplotlib.pyplot as plt
from KMeans import KMeans
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
X = X[:, 2:]
y = iris.target
plt.figure(figsize=(12, 12))
plt.scatter(X[:, 0], X[:, 1])
plt.grid()
plt.xlim(0, 7)
plt.ylim(0, 7)
center = clf.cluster_centers_
样本分布如下:
接下来,我们调用自己写的KMeans算法,对样本进行分类,由于鸢尾花的数据集本来就是分为3类的,所以我们设置k=3。
import numpy as np
import matplotlib.pyplot as plt
from KMeans import KMeans
from sklearn.datasets import load_iris
iris = load_iris()
X = iris.data
X = X[:, 2:]
y = iris.target
clf = KMeans(n_clusters=3)
s = clf.fit_predict(X)
plt.figure(figsize=(12, 12))
plt.scatter(X[:, 0], X[:, 1], c=clf.labels_)
plt.grid()
plt.xlim(0, 7)
plt.ylim(0, 7)
center = clf.cluster_centers_
plt.scatter(center[:, 0], center[:, 1],marker="*",s=200)
结果如下:
非常符合预期。
总结
KMeans算法是迭代算法,不能保证全局最优,具体表现,和初始化的参数有关。初始中心点的选取,对结果会有一定影响。
k均值算法的k值需要预先指定,而在实际应用中,最有的类别数K是不确定的。解决这个问题的一个方法,就是尝试用不同的k值聚类,检查各个聚类的表现来推断最优的K值。一般来说,类别数变小时,平均直径会增加;类别数变大超过某个值后,平均直径变化不明显。这个值就是我们要找的k值。