K-means原理与Python实现

k-means算法

K-均值聚类算法(k-means clustering algorithm)是一种无监督聚类算法。本文前部分介绍算法原理及优缺点,后面通过Python代码实现一个简版的k-means算法。

优缺点

  • 优点:简洁快速,算法的关键在于初始中心的选择和距离度量。
  • 缺点
    1. K值(聚类的数目)需要事先确定。
    2. 聚类结果对初始类中心的选取较为敏感。
    3. 容易陷入局部最优。
    4. 只能发现球型簇
  • 时间复杂度O(tKmn)O(tKmn),其中,t为迭代次数,K为簇的数目,m为记录数,n为维数。
  • 空间复杂度O((m+k)n)O((m+k)n),其中,K为簇的数目,m为记录数,n为维数。

算法流程

  • 1.随机给定各簇中心 $ c_1, c_2,…, c_n $

  • 2.计算各样本点x1,x2,...,xnx_1, x_2, ..., x_n到簇中心的距离(一般采用欧式距离),将样本点归类到距离最小的簇中,公式如下:

    yiargminxicy2,i=1,2,...,n y_i \leftarrow argmin\|x_i - c_y\|^2, i = 1,2,...,n

    ​ 其中,argminargmin是使目标函数取最小值时的变量值

  • 3.更新各簇中心$ c_1, c_2,…, c_n $

    ci=1nyi:yiyxi    y=1,2,...,c c_i = \frac{1}{ny}\sum_{i:y_i\in y}x_i \ \ \ \ 其中,y=1,2,...,c

    ​ 上式中,nyny为簇y的样本总数

  • 4.重复步骤2、3,直到达到收敛精度即可

停止条件

  • 达到预先设置的最大迭代次数。
  • 聚类中心不再发生变化。
  • 相邻两次聚类结果中差值变化小于某一个阈值。

K值的确定

  1. 经验法: 在实际工作中,结合业务的场景和需求,来决定几类确定K值

  2. 肘部法则: 肘部法则通过成本函数来刻画的,其是通过将不同K值的成本函数刻画出来,随着K值的增大,平均畸变程度的改善效果会不断降低。因此。在找出K值增大的过程中,畸变程度下降幅度最大的位置所对应的K较为合理。(注:成本函数为各类的畸变程度之和与其内部成员位置距离的平方和,最优解是成本函数最小化为目标。公式表示为
    J=k=1kickixick2 J=\sum_{k=1}^k\sum_{i\in c_k}^i|x_i-c_k|^2
    其中,ckc_k是第k个质心的位置。

  3. 规则法: k=n/2k=\sqrt{n/2}

K-means算法Python实现

下面代码将k-means封装成类,实例化对象后调用fit()和transform()方法实现对数据的训练和转化,talk is checp, show you the code!

import numpy as np
from collections import Counter
import matplotlib.pyplot as plt


class Kmeans():

    def __init__(self, n_clusters=8, max_iter=10, tol=1e-4):
        self.__n_cluster = n_clusters
        self.__max_iter = max_iter
        self.__tol = tol
        self.__cluster_centers_ = None
        self.__n_iter_ = 0

        self.__label = None

        self.info = ''

    @property
    def cluster_centers_(self):
        return self.__cluster_centers_

    @property
    def n_iter(self):
        return self.__n_iter_

    @property
    def label(self):
        return self.__label

    def __generate_cluster_centers(self, data):
        """随机生成类中心"""
        m, n = data.shape
        col_max_value = data.max(axis=0)
        col_min_value = data.min(axis=0)

        cluster_centers = np.empty(shape=[self.__n_cluster, n])
        for col_index in range(n):
            col_value = np.random.uniform(col_min_value[col_index],
                                          col_max_value[col_index],
                                          self.__n_cluster)
            cluster_centers[:, col_index] = col_value
        return cluster_centers

    def __calu_distance(self, sample, cluster_center):
        """
        计算一个样本sample到一个聚类中心的距离(欧式距离)
        :param sample: 输入的一个样本
        :return: 距离
        """
        return np.sqrt(np.sum(np.square(cluster_center - sample)))

    def __category(self, sample):
        """
        计算一个sample所属的簇
        :param sample:
        :return: 簇索引
        """
        distance_list = []
        for cluster_center_index in range(self.__n_cluster):
            cluster_center = self.__cluster_centers_[cluster_center_index, :]
            distance_list.append(self.__calu_distance(sample=sample, cluster_center=cluster_center))

        return distance_list.index(min(distance_list))

    def __update_cluster_centers(self, data, label):
        """
        计算类的中心
        :param label:
        :return:
        """
        cluster_centers = np.empty(shape=[self.__n_cluster, data.shape[1]])
        for cluster_index in range(self.__n_cluster):
            sample_index_array = np.where(label == cluster_index)
            samples = data[sample_index_array]
            center = samples.mean(axis=0)
            cluster_centers[cluster_index] = center
        return cluster_centers

    def __is_terminate(self, cluster_centers, label):
        """
        是否满足终止条件(1. 达到最大迭代次数; 2. 簇中心不再变化; 3. 两次聚类结果差值小于某一个阈值
        :param label:
        :return:
        """
        diff = self.__calu_diff(label)
        if self.__n_iter_ >= self.__max_iter:
            self.info = '达到最大迭代次数: ' + str(self.__max_iter) + ',聚类结束。'
            return True

        if (self.__cluster_centers_ == cluster_centers).all():
            self.info = '两次聚类簇中心未发生变化,聚类结束。'
            return True

        if diff <= self.__tol:
            self.info = '聚类结果差值为 {},小于 {},聚类结束。'.format(diff, self.__tol)
            return True

        self.__label = label
        return False

    def __calu_diff(self, label):
        """
        计算两次聚类结果的差值
        :param label:  最近的一次聚类结果
        :return: 差值占比
        """
        ans = label == self.__label
        length = len(ans)
        ans_dict = Counter(ans)
        diff_num = ans_dict.get(False)
        if diff_num == None:
            return 0
        else:
            return diff_num / length

    def fit(self, data):
        """
        将数据分簇,并计算簇的中心
        :param data:  数据
        :return:
        """
        label = np.array([np.nan] * data.shape[0])
        self.__cluster_centers_ = self.__generate_cluster_centers(data=data)
        while True:
            # 将每个样本划分到最近的簇中
            for sample_index in range(data.shape[0]):
                category = self.__category(sample=data[sample_index])
                label[sample_index] = category

            # 更新簇中心
            cluster_centers = self.__update_cluster_centers(data=data, label=label)
            self.__n_iter_ += 1
            # 检查是否满足终止条件
            if self.__is_terminate(cluster_centers=cluster_centers, label=label):
                self.__cluster_centers_ = cluster_centers
                self.print_info()
                break
            self.__cluster_centers_ = cluster_centers
            self.__label = label.copy()

    def transform(self, data):
        """
        计算每个数据到各聚类中心的距离
        :param data:
        :return:
        """
        ans = np.empty([data.shape[0], self.__n_cluster])
        for index in range(data.shape[0]):
            tmp = []
            for center in self.__cluster_centers_:
                tmp.append(self.__calu_distance(sample=data[index], cluster_center=center))
            center_array = np.array(tmp)
            ans[index] = center_array
        return ans

    def predict(self, data):
        """
        预测数据所属的簇
        :param data:  待预测数据
        :return: 数据所属类别
        """
        distiance = self.transform(data)
        cluster_labels = np.argmax(distiance, axis=1)
        return cluster_labels

    def print_info(self):
        print('clustering is terminate!!!')
        print('结束原因:{}'.format(self.info))
        print('迭代次数 {}'.format(self.__n_iter_))
        print("簇中心:\n", self.__cluster_centers_)

随机生成正太分布的数据

def generate_test_data(size=(120, 2), clusters=3, loc=10, scale=10):
    cluster_center = np.array([[10, 10], [50, 100], [100, 50]])
    data = np.empty(shape=[0, size[1]])
    for cluster_index in range(clusters):
        new_data_x = np.random.normal(loc=cluster_center[cluster_index][0],
                                      scale=scale, size=[int(size[0] / clusters),1])
        new_data_y = np.random.normal(loc=cluster_center[cluster_index][1],
                                      scale=scale, size=[int(size[0] / clusters), 1])
        new_data = np.concatenate((new_data_x, new_data_y), axis=1)
        data = np.concatenate([data, new_data], axis=0)
    # print(data.shape)
    return data

聚类结果可视化函数

def show_data(data, label, cluster_centers=[]):
    filled_markers = ('o', 'v', '^', '<', '>', '8', 's', 'p', 'h', 'H', 'D', 'd', 'P', 'X')
    filled_colors = ('black', 'green', 'darkorchid', 'darkred', 'darksalmon', 'darkslategray')

    plt.figure()
    for sample, sample_label in zip(data, label):
        plt.scatter(sample[0], sample[1], marker=filled_markers[sample_label],
                    color=filled_colors[sample_label], s=40, label='cluster_' + str(sample_label))

    if len(cluster_centers) > 0:
        for center in cluster_centers:
            plt.scatter(center[0], center[1], marker='*', color='red', s=80)
    # plt.legend(scatterpoints=1)
    plt.show()

主函数调用

if __name__ == '__main__':
	model = Kmeans(n_clusters=3, max_iter=10000)
    data = generate_test_data(scale=15)
    model.fit(data)
    label = model.label.astype('int')
    centers = model.cluster_centers_
    show_data(data, label, centers)

运行上面代码段,输出下面结果

clustering is terminate!!!
结束原因:两次聚类簇中心未发生变化,聚类结束。
迭代次数 5
簇中心:
 [[100.46756833  48.52330939]
 [ 49.11200803 101.26172269]
 [  9.27754808   9.65780098]]

可视化结果

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章