kmeans算法及其优化改进
kmeans聚类算法
算法原理
kmeans的算法原理其实很简单
我用一个最简单的二维散点图来做解释
如上图,我们直观的看到该图可聚成两个分类,我们分别用红点和蓝点表示
下面我们模拟一下Kmeans是怎么对原始的二维散点图做聚类的
首先,随机初始化2个聚类中心(一般是在随机选择两个样本点作为聚类中心)至于什么是聚类中心呢,我们暂且压下不表,现在就把它当成一个点就好。
然后我们就去把所有离红色点进的样本点标成红色,把离蓝色点近的样本点标成蓝色
然后我们重新设定聚类中心的位置,设在哪呢。红色聚类中心就设在现在的红色点的中心(均值),蓝色聚类中心就设在现在的蓝色点的中心(均值),样本颜色重新设为黑色
然后我们继续把所有离红色点进的样本点标成红色,把离蓝色点近的样本点标成蓝色
其实我们现在看来,红色和蓝色已经很分明了,已经达到最初的效果了,但是机器没有我们的眼睛,机器没办法直观的看到现在已经可以停止了。
所以它会继续按照计算均值的方法重新设定聚类中心
然后继续把所有离红色点进的样本点标成红色,把离蓝色点近的样本点标成蓝色
然后继续按照计算均值的方法重新设定聚类中心,但到这里机器会发现我们设定的聚类中心和之前的聚类中心的位置几乎没有改变,这说明我们的算法收敛了,每个样本的类别基本已经确定了。于是算法终止,聚类完成。其实在这个地方有两个指标来表示是否终止,一是计算聚类中心位置的变化,二是计算样本聚类的变化,二者是等价的。
算法步骤
-
选定K个聚类中心
-
计算每个样本点到K个聚类中心的距离,将样本分类设定为距离最小的聚类中心对应分类
-
计算每个分类集合的样本均值,并将其作为新的聚类中心
-
重复2,3步骤,直到新的聚类中心与原聚类中心的距离小于设定的阈值即可
算法实现
导入包
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
定义模型
class Kmeans:
def __init__(self, k, max_iter=300, thresh=1e-5):
self.k = k
self.thresh = thresh
self.max_iter = max_iter
def random_centroid_init(self,X):
# 随机选取K个样本作为聚类中心
return X[np.random.choice(X.shape[0], size=self.k)]
def dist(self, x):
return [np.linalg.norm(x - c) for c in self.centroids]
def fit_predict(self, X):
# 初始化聚类中心
self.centroids = self.random_centroid_init(X)
for _ in range(self.max_iter):
# 涂色
y_pred = np.array([np.argmin(self.dist(x)) for x in X])
# 计算新的聚类中心
new_centroids = self.centroids.copy()
for i in range(self.k):
new_centroids[i] = np.mean(X[y_pred==i],axis=0)
# 如果聚类中心位置基本没有变化,那么终止
if np.max(np.abs(new_centroids - self.centroids)) < self.thresh:
break
# 否则更新聚类中心,重复上述步骤
self.centroids = new_centroids
return y_pred
生成数据集
X, y = make_blobs(n_samples=1000, n_features=2, centers=3)
训练
model = KMeans(3, 1e-2)
y_pred = model.fit_predict(X)
可视化结果
plt.figure()
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.subplot(122)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()
优化改进
好讲完Kmeans算法,我们再想一想Kmeans算法有什么问题.
聚类中心初始化
看下面两张图,它是上面的程序偶尔可能运行出来的结果,左图是运气不好的错误聚类,右边是运气好的正确聚类
程序的两次运行唯一的区别就是聚类中心初始化是随机的,那么现在问题就出在这里
再看我们前面介绍的例子,假如我们的聚类中心是在这个地方
于是,绿线下方都被分到红色,绿线上方都分到蓝色,我们一求均值,重新设定聚类中心,发现位置也没有太大改变,于是我们得到的聚类就是这样的
很分明,但是显然不是我们希望的聚类
那么这个问题怎么解决,也就是说,随机初始化聚类中心是不好的,我们应该怎样初始化聚类中心?
Kmeans++算法
为了解决初始化的问题,Kmeans++算法有这样的策略
第一,初始的聚类中心一定是在样本中选,这个与Kmeans算法不同,尽管kmeans算法我们一般也是这样做的,但在算法中并没有对其有实际的限制。而Kmeans++的算法流程中,只有在样本中选才能进行这个算法
第二,选择的K个聚类中心需要尽可能的远。
算法过程
首先随机选取第一个聚类中心,计算各个样本点到距离最近的聚类中心的距离。
让距离越远的点被选择为新的聚类中心的概率越大,重复上述步骤,直到选择出所有的聚类中心。
算法实现
这里怎么将距离的大小体现在选择的概率上其实有很多方式,最极端的一种就是,距离最大的概率为1,其余为0。
实现如下
def max_centroid_init(self,X):
centroids = []
centroids.append(X[np.random.choice(X.shape[0])])
for i in range(self.k-1):
index = np.argmax([np.min([np.linalg.norm(x - c) for c in centroids]) for x in X])
centroids.append(X[index])
return np.array(centroids)
比较温和一点的是这样,以距离/(距离和)作为每个样本被选择的概率
例如现在有3个样本离自己的聚类中心的距离分别为 5 , 10 , 10 5,10,10 5,10,10,其和为 25 25 25。
我们随机一个 0 0 0到 25 25 25之间的数 n u m b e r number number
如果这个数在 0 0 0到 5 5 5以内,我们选择第一个样本,在 5 5 5到 15 15 15以内我们选择第二个样本
其实就是从第一个样本开始遍历, n u m b e r = n u m b e r − D ( x ) number = number-D(x) number=number−D(x), n u m b e r number number 什么时候小于0,此时遍历到的样本就是新的聚类中心
实现如下
def soft_centroid_init(self,X):
centroids = []
centroids.append(X[np.random.choice(X.shape[0])])
for i in range(self.k-1):
D = [np.min([np.linalg.norm(x - c) for c in centroids]) for x in X]
number = np.random.choice(np.sum(D))
for i,d in enumerate(D):
number -= d
if number<0:
centroids.append(X[i])
break
return np.array(centroids)
其它的改进优化我就不介绍了,我暂时不关注性能问题
改进全部代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
class Kmeans:
def __init__(self, k, init='pp-soft', max_iter=300, thresh=1e-5):
self.k = k
self.thresh = thresh
self.max_iter = max_iter
self.init = init
def random_centroid_init(self,X):
# 随机选取K个样本作为聚类中心
return X[np.random.choice(X.shape[0], size=self.k)]
def max_centroid_init(self,X):
centroids = []
centroids.append(X[np.random.choice(X.shape[0])])
for i in range(self.k-1):
index = np.argmax([np.min(self.dist(x)) for x in X])
centroids.append(X[index])
return np.array(centroids)
def soft_centroid_init(self,X):
centroids = []
centroids.append(X[np.random.choice(X.shape[0])])
for i in range(self.k-1):
D = [np.min(self.dist(x)) for x in X]
number = np.random.choice(int(np.sum(D)))
for i,d in enumerate(D):
number -= d
if number<0:
centroids.append(X[i])
break
return np.array(centroids)
def dist(self, x):
return [np.linalg.norm(x - c) for c in self.centroids]
def fit_predict(self, X):
# 初始化聚类中心
if self.init == 'random':
self.centroids = self.random_centroid_init(X)
elif self.init == 'pp-max':
self.centroids = self.max_centroid_init(X)
else:
self.centroids = self.soft_centroid_init(X)
for _ in range(self.max_iter):
# 涂色
y_pred = np.array([np.argmin(self.dist(x)) for x in X])
# 计算新的聚类中心
new_centroids = self.centroids.copy()
for i in range(self.k):
new_centroids[i] = np.mean(X[y_pred==i],axis=0)
# 如果聚类中心位置基本没有变化,那么终止
if np.max(np.abs(new_centroids - self.centroids)) < self.thresh:
break
# 否则更新聚类中心,重复上述步骤
self.centroids = new_centroids
return y_pred
X, y = make_blobs(n_samples=1000, n_features=2, centers=3)
model = Kmeans(3)
y_pred = model.fit_predict(X)
plt.figure()
plt.subplot(121)
plt.scatter(X[:, 0], X[:, 1], c=y)
plt.subplot(122)
plt.scatter(X[:, 0], X[:, 1], c=y_pred)
plt.show()