K-Means
K−Means 是一个 无监督 的 聚类 算法
什么是 聚类
俗话说"物以类聚,人以群分",扯远了扯远了,这是考试复习
- 聚类算法 试图将相似对象归入同一簇,将不相似对象归到不同簇
- 是一种无监督学习( unsupervisedlearning ),简单地说就是把相似的对象归到同一簇中。簇内的对象越相似,聚类的效果越好。
基本思想
个人觉得可以跳过,直接看算法步骤
在数据集中根据一定策略选择 K 个点作为每个簇的 初始中心,然后观察剩余的数据,将数据划分到距离这 K 个点最近的簇中,也就是说将数据划分成K个簇完成一次划分,但形成的新簇并不一定是最好的划分,因此生成的新簇中,重新计算每个簇的中心点,然后在重新进行划分,直到每次划分的结果保持不变。
在实际应用中往往经过很多次迭代仍然达不到每次划分结果保持不变,甚至因为数据的关系,根本就达不到这个终止条件,实际应用中往往采用变通的方法设置一个最大迭代次数,当达到最大迭代次数时,终止计算。
样本点间的距离(99%
详见 KNN 中 “样本点间距离的选取”
欧氏距离(99%
E(X,Y)=i=1∑n(xi−yi)2
算法步骤
- 选择初始化的 k 个样本作为初始聚类中心 a=a1,a2,…,ak ;
- 针对数据集中每个样本 xi 计算它到 k 个聚类中心的距离并将其分到距离最小的聚类中心所对应的类中;
- 针对每个类别 aj ,重新计算它的聚类中心 $ a_ {j} $ = $ \frac {1}{|c_ {i}|} $ $ \sum _ {x\in c_ {i}} $ (即属于该类的所有样本的质心);
- 重复上面 2 3 两步操作,直到达到某个中止条件(迭代次数、最小误差变化等)。
收敛证明(跳过吧
首先看一下 K−Means 的目标函数(损失函数)
J=i=1∑ci=1∑Nrij⋅ν(xj,μi)
其中:
ν(xj,μi)=∥xj−μi∥2,rnk={10 if xn∈k else
为了求极值,我们令损失函数求偏导数且等于 0:
∂μk∂J=2i=1∑Nrik(xi−μk)=0
k 是指第 k 个中心点,于是我们有:
μk=∑i=1Nrik∑i=1Nrikxi
可以看出,新的中心点就是所有该类的质心。
算法的缺点就是,容易陷入局部极小值,这也是 K−Means 有时会得到局部最优解的原因。
代码实现、手搭(99%
计算训练集到质心的欧氏距离
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def calcDis(dataSet, centroids, k): """计算训练集到质心的欧拉距离
Args: dataSet (array): 训练集 centroids (array): 质心 k (int): 质心个数
Returns: array: 训练集到各个质心的距离 """ clalist=[] for data in dataSet: diff = np.tile(data, (k, 1)) - centroids squaredDiff = diff ** 2 squaredDist = np.sum(squaredDiff, axis=1) distance = squaredDist ** 0.5 clalist.append(distance) clalist = np.array(clalist) return clalist
|
更新质心
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| def classify(dataSet, centroids, k): """更新k个质心
Args: dataSet (array): 训练集 centroids (array): 初始质心 k (int): 质心个数
Returns: array, array: 质心变化量,新质心 """ clalist = calcDis(dataSet, centroids, k) minDistIndices = np.argmin(clalist, axis=1) newCentroids = pd.DataFrame(dataSet).groupby(minDistIndices).mean() newCentroids = newCentroids.values changed = newCentroids - centroids return changed, newCentroids
|
使用 K−Means 进行迭代计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| def kmeans(dataSet, k): """使用k-means进行分类
Args: dataSet (array): 训练集 k (int): 质心个数
Returns: array, array: 质心, 分类结果 """ centroids = random.sample(dataSet, k) changed, newCentroids = classify(dataSet, centroids, k) while np.any(changed != 0): changed, newCentroids = classify(dataSet, newCentroids, k) centroids = sorted(newCentroids.tolist()) cluster = [] clalist = calcDis(dataSet, centroids, k) minDistIndices = np.argmin(clalist, axis=1) for i in range(k): cluster.append([]) for i, j in enumerate(minDistIndices): cluster[j].append(dataSet[i]) return centroids, cluster
|
关于 K-Means 的一些问题
- k值的选取
k−means 算法要求事先知道数据集能分为几群,主要有两种方法定义 k。
- elbowmethod (也就是常说的 手肘法)通过绘制 k 和 损失函数(即平方误差和) 的关系图,选拐点处的 k 值。
- 经验选取人工据经验先定几个 k,多次随机初始化中心选经验上最适合的。
通常都是以经验选取,因为实际操作中拐点不明显,且 elbowmethod 效率不高。
- K−means 算法中初始点的选择对最终结果的影响
k−means 选择的初始点不同获得的最终分类结果也可能不同,随机选择的中心会导致 K−means 陷入局部最优解。
- 使用 k−means 之前一定要先对数据进行归一化操作
因为数据在不同维度上的量级不同,使用原始数据容易造成某一维度对结果影响过大。
- 空聚类的处理
如果所有的点在指派步骤都未分配到某个簇,就会得到空簇。
如果这种情况发生,则需要某种策略来选择一个替补质心,否则的话,平方误差将会偏大。
- 一种方法是选择一个距离当前任何质心最远的点。这将消除当前对总平方误差影响最大的点。
- 另一种方法是从具有最大 SEE 的簇中选择一个替补的质心。这将分裂簇并降低聚类的总 SEE。如果有多个空簇,则该过程重复多次。另外编程实现时,要注意空簇可能导致的程序 bug。