k-means是一个十分简单的聚类算法,它的思路非常简明清晰,所以经常拿来当做教学。下面就来讲述一下这个模型的细节操作。
内容
- 模型原理
- 模型收敛过程
- 模型聚类个数
- 模型局限
1. 模型原理
将某一些数据分为不同的类别,在相同的类别中数据之间的距离应该都很近,也就是说离得越近的数据应该越相似,再进一步说明,数据之间的相似度与它们之间的欧式距离成反比。这就是k-means模型的假设。
有了这个假设,我们对将数据分为不同的类别的算法就更明确了,尽可能将离得近的数据划分为一个类别。不妨假设需要将数据{xi}聚为k类,经过聚类之后每个数据所属的类别为{ti},而这k个聚类的中心为{μi}。于是定义如下的损失函数:
k-means模型的目的是找寻最佳的{ti},使损失函数最小,之后就可以对聚类中心{μi}直接计算了。由此可见,它既是聚类的最终结果,也是需要估算的模型参数。
2. 模型收敛过程
在k-means的损失函数中存在两个未知的参数:一个是每个数据所属的类别{ti};一个是每个聚类的中心{μi}。这两个未知的参数是相互依存的:如果知道每个数据的所属类别,那么类别的所有数据的平均值就是这个类别的中心;如果知道每个类别的中心,那么就是计算数据与中心的距离,再根据距离的大小可以推断出数据属于哪一个类别。
根据这个思路,我们可以使用***EM算法***(最大期望算法)来估计模型的参数。具体操作如下:
- 首先随机生成k个聚类中心点
- 根据聚类中心点,将数据分为k类。分类的原则是数据离哪个中心点近就将它分为哪一类别。
- 再根据分好的类别的数据,重新计算聚类的类别中心点。
- 不断的重复2和3步,直到中心点不再变化。如下图所示:
3. 模型聚类个数
对于非监督学习,训练数据是没有标注变量的。那么除了极少数的情况,我们都是无从知道数据应该被分为几类。k-means算法首先是随机产生几个聚类中心点,如果聚类中心点多了,会造成过拟合;如果聚类中心点少了,会造成欠拟合,所以聚类中心点是很关键的,在这里使用误差平方的变化和来评价模型预测结果好不好。当聚类个数小于真实值时,误差平方和会下降的很快;当聚类个数超过真实值时,误差平方和虽然会继续下降,但是下降的速度会缓减,而这个转折点就是最佳的聚类个数了。
4. 模型局限
k-means是非常简单的模型,但是它也有两个明显的缺陷,或者说它有两种运用场景不能使用,第一是非均质的数据,因为,模型使用欧氏距离衡量数据间的相似度,因此它要求数据在各个维度上都是均质。第二是不同类别内部方差不相同。模型假设不同类别的内部方差是大致相等的。
下面使用鸢尾花数据集进行实战。
引入包:
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
import seaborn as sns
%matplotlib inline
观察数据:
data = sns.load_dataset("iris")
data.head()
sns.pairplot(data, hue='species')
观察两两变量中聚类个数:
def trainModel(data, clusterNum):
"""
使用KMeans对数据进行聚类
"""
# max_iter表示EM算法迭代次数,n_init表示K-means算法迭代次数,algorithm="full"表示使用EM算法。
model = KMeans(n_clusters=clusterNum, max_iter=100, n_init=10, algorithm="full")
model.fit(data)
return model
def computeSSE(model, data):
"""
计算聚类结果的误差平方和
"""
wdist = model.transform(data).min(axis=1)
sse = np.sum(wdist ** 2)
return sse
if __name__ == "__main__":
col = [['petal_width', 'sepal_length'], ['petal_width', 'petal_length'], ['petal_width', 'sepal_width'], ['sepal_length', 'petal_length'],
['sepal_length', 'sepal_width'], ['petal_length', 'sepal_width']]
for i in range(6):
fig = plt.figure(figsize=(8, 8), dpi=80)
ax = fig.add_subplot(3, 2, i+1)
sse = []
for j in range(2, 6):
model = trainModel(data[col[i]], j)
sse.append(computeSSE(model, data[col[i]]))
ax.plot(range(2,6), sse, 'k--', marker="o",
markerfacecolor="r", markeredgecolor="k")
ax.set_xticks([1,2,3,4,5,6])
title = "clusterNum of %s and %s" % (col[i][0], col[i][1])
ax.title.set_text(title)
plt.show()
通过这个图,我们基本上可以判断出应该分为三类,这也与实际情况是相同的。我们选择一组进行可视化聚类结果。
petal_data = data[['petal_width', 'petal_length']]
model = trainModel(petal_data, 3)
fig = plt.figure(figsize=(6,6), dpi=80)
ax = fig.add_subplot(1,1,1)
colors = ["r", "b", "g"]
ax.scatter(petal_data.petal_width, petal_data.petal_length, c=[colors[i] for i in model.labels_],
marker="o", alpha=0.8)
ax.scatter(model.cluster_centers_[:, 0], model.cluster_centers_[:, 1], marker="*", c=colors, edgecolors="white",
s=700., linewidths=2)
yLen = petal_data.petal_length.max() - petal_data.petal_length.min()
xLen = petal_data.petal_width.max() - petal_data.petal_width.min()
lens = max(yLen+1, xLen+1) / 2.
ax.set_xlim(petal_data.petal_width.mean()-lens, petal_data.petal_width.mean()+lens)
ax.set_ylim(petal_data.petal_length.mean()-lens, petal_data.petal_length.mean()+lens)
ax.set_ylabel("petal_length")
ax.set_xlabel("petal_width")
这个效果是很好的,与实际的情况一致!