最简单的K-means算法原理和实践教程

0x00 算法简介

K-means算法是传统的机器学习算法中非监督学习算法中的一种。

算法思想:从空间中K个点为中心进行分类,对最靠近他们的对象归类。通过不断的迭代,依次得到不同类的中心,直到最后所有类的中心不再变化。

0x01 算法流程

输入:k,data[n]。

输出:满足方差最小标准的k个聚类。

  • 选择k个初始中心点,例如c[0]=X[0] , … , c[k-1]=X[k-1]
  • 对于X[0]….X[n],分别与c[0]…c[k-1]比较,假定与c[i]差值最少,就标记为i
  • 对于所有标记为i点,重新计算c[i]={ 所有标记为i的样本的每个特征的均值}
  • 重复(2)(3),直到所有c[i]值的变化小于给定阈值或者达到最大迭代次数。

在数据域内(以彩色显示)随机产生k个初始“中心”(在这种情况下,k = 3)。



通过将每个观察值与最近的中心相关联来创建k个群集。 这里的分区表示由中心生成的Voronoi图。



k个分类的中心成为新的中心。



重复步骤2和3直到收敛。





0x02 中心的初始化

选择适当的初始质心是基本K-means算法的关键步骤。常见的方法是随机的选取初始中心,但是这样分类的质量常常很差。处理选取初始质心问题的一种常用技术是:多次运行,每次使用一组不同的随机初始中心,然后选取具有最小SSE(误差的平方和)的分组集。这种策略简单,但是效果可能不好,这取决于数据集和寻找的分组的个数。

第二种有效的方法是,取一个样本,并使用层次聚类技术对它聚类。从层次聚类中提取K个分类,并用这些类的质心作为初始中心。该方法通常很有效,但仅对下列情况有效:

  • 样本相对较小,例如数百到数千(层次聚类开销较大)
  • K相对于样本大小较小

第三种选择初始中心的方法,随机地选择第一个点,或取所有点的质心作为第一个点。然后,对于每个后继初始质心,选择离已经选取过的初始质心最远的点。使用这种方法,确保了选择的初始质心不仅是随机的,而且是散开的。但是,这种方法可能选中离群点。此外,求离当前初始质心集最远的点开销也非常大。为了克服这个问题,通常该方法用于点样本。由于离群点很少(多了就不是离群点了),它们多半不会在随机样本中出现。计算量也大幅减少。

第四种方法是基于Canopy Method的聚类算法,将聚类过程分为两个阶段

  • 聚类最耗费计算的地方是计算对象相似性的时候,Canopy Method在第一阶段选择简单、计算代价较低的方法计算对象相似性,将相似的对象放在一个子集中,这个子集被叫做Canopy,通过一系列计算得到若干CanopyCanopy之间可以是重叠的,但不会存在某个对象不属于任何Canopy的情况,可以把这一阶段看做数据预处理
  • 在各个Canopy 内使用传统的聚类方法(如K-means),不属于同一Canopy 的对象之间不进行相似性计算。

从这个方法起码可以看出两点好处:首先,Canopy不要太大且Canopy之间重叠的不要太多的话会大大减少后续需要计算相似性的对象的个数;其次,类似于K-means这样的聚类方法是需要人为指出K的值的,通过第一步得到的Canopy个数完全可以作为这个K值,一定程度上减少了选择K的盲目性。

0x03 实际计算

我们将通过实际的计算得到相应的结果,这里我们使用第一种初始化方法,我们这里K=2。首先,我们先假设A(1, 3)B(4, 3)C(2, 4)D(3,1 )四个点,我们取AB作为初始的中心



我们可以得到坐标的矩阵 [ 1 4 2 3 3 3 4 1 ]

由我们前面的假设我们此时的中心为

  • e 1 = ( 1 , 3 ) e 2 = ( 4 , 3 )

我们分别计算各点离各中心点的欧氏距离,例如AB的距离

  • A B = ( 1 4 ) 2 + ( 3 3 ) 2 = 3

同样的,A剩余各点的距离分别是[0, 3, 1.41, 2.83],而B和各点的距离[3, 0, 2.24, 2.24],最后得到一个距离矩阵

  • D = [ 0 3 1.41 2.83 3 0 2.24 2.24 ]

通过这个距离矩阵,我们得到相应的分组矩阵(我们将距离两个中心点最小的距离标记为1)

  • G = [ 1 0 1 0 0 1 0 1 ]

通过分组矩阵和坐标矩阵,我们可以计算出新的中心点的位置,例如,我们看到分组矩阵中,AC分为一组,那么我们通过计算AC中点的坐标( ( 1 + 2 2 , 3 + 4 2 ) )。最后我们得到了新的中心点

  • e 1 = ( 1.5 , 3.5 ) e 2 = ( 3.5 , 2 )

对应的结果如下图



接着我们重复上述过程,我们计算出新的距离矩阵

  • D = [ 0.71 2.55 0.71 2.92 2.69 1.12 2.5 1.12 ]

同样的,我们算出对应的分组矩阵

  • G = [ 1 0 1 0 0 1 0 1 ]

通过分组矩阵和坐标矩阵,我们可以计算出新的中心点的位置

  • e 1 = ( 1.5 , 3.5 ) e 2 = ( 3.5 , 2 )

我们发现中心点的位置没变,那么迭代结束。

0x04 代码

python3.x环境写的代码

import numpy as np

class K_means:
    def __init__(self, data, k, max_iterations):
        self.data = data
        self.k = k
        self.max_iterations = max_iterations
        self.iterations = 0
        self.centroids = None
        self.oldCentroids = None
        self.dataSet = None

    '''
    shouldStop(oldCentroids, centroids, iterations, max_iterations)
    作用:判断迭代停止
    oldCentroids:旧的中心点
    centroids:新的中心点
    iterations:已经迭代的次数
    max_iterations:最大迭代次数
    return:bool True停止 False继续
    '''
    def __shouldStop(self):
        if self.iterations > self.max_iterations:
            return True
        return np.array_equal(self.oldCentroids, self.centroids)


    '''
    getLabelFromClosestCentroid(dataSetRow, centroids)
    作用:获取最近中心点的类别
    dataSetRow:
    centroids:
    return:对应的类别
    '''
    def __getLabelFromClosestCentroid(self, dataSetRow):   
        label = self.centroids[0, -1]#获取第一个中心点的类别
        minDist = np.linalg.norm(dataSetRow - self.centroids[0, :-1])#求欧氏距离,这个函数是二范数
        for i in range(1, self.centroids.shape[0]):
            dist = np.linalg.norm(dataSetRow - self.centroids[i, :-1])
            if dist < minDist:
                minDist = dist
                label = self.centroids[i, -1]
        print("minDist: ", minDist)
        return label


    '''
    updateLabels(dataSet, centroids)
    作用:更新分类信息
    dataSet:数据
    centroids:中心点
    '''
    def __updateLabels(self):
        numPoints, numDim = self.dataSet.shape
        for i in range(0, numPoints):
            self.dataSet[i, -1] = self.__getLabelFromClosestCentroid(self.dataSet[i, :-1])

    '''
    getCentroids(dataSet, k)
    作用:计算中心点
    dataSet:数据
    k:k类
    return:中心点
    '''
    def __getCentroids(self):
        result = np.zeros((self.k, self.dataSet.shape[1]))
        for i in range(1, self.k + 1):
            oneCluster = self.dataSet[self.dataSet[:, -1] == i, :-1]
            result[i - 1, :-1] = np.mean(oneCluster, axis = 0)
            result[i - 1, -1] = i

        return result

    '''
    K_means(data, k, max_Iterator)
    作用:计算K_means聚类
    data:输入的数据
    k:聚类的个数
    max_Iterator:最大的迭代次数
    return:分类的结果
    '''
    def predict(self):
        numPoints, numDim = self.data.shape#点的个数,维度

        #增加一列
        self.dataSet = np.zeros((numPoints, numDim + 1))
        self.dataSet[:, :-1] = self.data

        #随机初始化中心点
        self.centroids = self.dataSet[np.random.randint(numPoints, size=self.k), :]
        self.centroids = self.dataSet[0:2, :]
        #初始化中心点的类别    
        self.centroids[:, -1] = range(1, self.k + 1)


        while not self.__shouldStop():
            print("iterations: ", self.iterations)
            print("dataSet: ", self.dataSet)
            print("centroids: ", self.centroids)
            #复制初始化的中心点
            self.oldCentroids = np.copy(self.centroids)
            self.iterations += 1
            #更新类别
            self.__updateLabels()
            #计算新的中心点
            self.centroids = self.__getCentroids()

        return self.dataSet

if __name__ == '__main__':
    data = np.array([[1,3], [4, 3], [2, 4], [3, 1]])
    k = 2
    max_iterations = 10
    p = K_means(data, k, max_iterations)
    print(p.predict())

通过前面的学习,我想你应该对这个代码有所理解了吧O(∩_∩)O。K-means是一个机器学习中很原始的方法,很多人觉得这个办法已经过时了,那么我将在后续的教程里,介绍一些很前沿的技术中使用K-means的地方。

最后我想介绍的是,如果你安装了scikit-learnpip install scikit-learn)的话,那么下面就很简单了:

from sklearn.cluster import KMeans
import numpy as np
import matplotlib.pyplot as plt
X = np.array([[1,3], [4, 3], [2, 4], [3, 1]])
kmeans = KMeans(n_clusters=2, init='random').fit_predict(X)
plt.scatter(X[:, 0], X[:, 1], c=kmeans, s=100)
plt.show()
print(kmeans)

reference:

https://en.wikipedia.org/wiki/K-means_clustering

https://blog.csdn.net/qll125596718/article/details/8243404/

文中如有任何问题,希望大家指出,谢谢!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/79794193