KMeans聚类

代价函数:

J ( c , u ) = ∑ i = 1 k ∣ ∣ x ( i ) − u c ( i ) ∣ ∣ 2 J(c,u)=\sum_{i=1}^k||x(i)-u_{c^{(i)}}||^2 J(c,u)=i=1kx(i)uc(i)2

u c ( i ) u_{c^{(i)}} uc(i)表示第i个类的均值。 我们希望代价函数最小,直观的来说,各类内的样本越相似,其与该类均值间的误差平方越小,对所有类所得到的误差平方求和,即可验证分为k类时,各聚类是否是最优的

算法流程

输入:待聚类的样本X、预先给定的聚类数K 输出:样本X中每个样本被分到的类、最终的所有聚类中心 流程:

  1. 初始化K个聚类中心作为最初的中心
  2. 循环每个样本,计算其与K个聚类中心的距离,将该样本分到距离最小的那个聚类中心
  3. 将每个聚类中的样本均值作为新的聚类中心
  4. 重复步骤2和3直到聚类中心不再变化

性能分析

  • 优点

    • 是解决聚类问题的一种经典算法,简单、快速
    • 对处理大数据集,该算法是相对可伸缩和高效率的。它的复杂度是 O ( n k t ) O(nkt) O(nkt),其中, n n n是所有对象的数目, k k k 是簇的数目, t t t 是迭代的次数。通常 k < < n k<<n k<<n t < < n t<<n t<<n
    • 当结果簇是密集的,而簇与簇之间区别明显时, 它的效果较好
  • 缺点

    • 在簇的平均值被定义的情况下才能使用,这对于处理符号属性的数据不适用
    • 必须事先给出K
    • 对初值敏感,对于不同的初始值,可能会导致不同结果
    • 何初始化聚类中心
  • 随机选择K个样本点

  • KMeans++

    1. 从输入的数据点集合中随机选择一个点作为第一个聚类中心𝜇1
    2. 对于数据集中的每一个点𝑥𝑖,计算它与已选择的聚类中心中最近聚类中心的距离
      D ( x i ) = a r g m i n ∣ ∣ x i − μ r ∣ ∣ 2 2 D(x_i)=argmin||x_i-\mu_r||_2^2 D(xi)=argminxiμr22, r = 1 , 2 , . . . k s e l e c t e d r=1,2,...kselected r=1,2,...kselected
    3. 选择一个新的数据点作为新的聚类中心,选择的原则是:𝐷(𝑥)较大的点,被选取作为聚类中心的概率较大
    4. 重复2和3直到选择出k个聚类质心
    5. 利用这k个质心来作为初始化质心去运行标准的K-Means算法

Python实现

import numpy as np

from base import ClusterMixin, UnsupervisedModel
from utils.distances import euclidean_distance
from exceptions import NotFittedError


def euclidean_distance(x1, x2):
    """欧氏距离 d(x,y)=( ∑(xi-yi)^2 )^0.5
    Parameters
    ----------
        x1, x2: n维空间的两个样本点
    Return
    ------
        返回点x1和x2的欧氏距离
    """
    return np.sqrt(np.sum((x1 - x2) ** 2))

class KMeans(object):

    def fit(self, X, k=8, max_iter=100000,
            distance=euclidean_distance):
        """
        :param X: 训练集, (n_samples, n_features)
        :param k: 将X聚成k类
        :param max_iter: 最大迭代次数,到达max_iter则停止迭代
        :param distance: 距离函数,默认欧氏距离,支持多种距离函数
        :return: self
                 self.centeriods: 质心, (k)
                 self.labels:     存放每个点对应的类
        """
        # 初始化质心
        self._init_centriods(X, k, method=init_centriods_method)
        centriods_changed = True
        self.labels = np.zeros(len(X))  # 用于存放每个样本点对应的类
        count = 0
        # 当 质心变化时进入循环
        while centriods_changed:
            # 对每个样本,计算其属于哪个类
            for i, x in enumerate(X):
                # distances = 当前点x 和 所有质心 的距离
                distances = np.array(
                    [distance(x, self.centriods[i]) for i in range(k)])
                self.labels[i] = distances.argmin()
            # 对每个类,更新其质心
            updated_centriods = np.array(
                [np.mean(X[self.labels == i], axis=0) for i in range(k)])
            if updated_centriods.tolist() == self.centriods.tolist():
                print('已收敛,停止迭代')
                centriods_changed = False
            else:
                self.centriods = updated_centriods
            count += 1
            if count == max_iter:
                print('达到最大迭代次数,停止迭代')
                return self
        return self

    def _init_centriods(self, X: np.ndarray, k: int):
        """初始化聚类中心"""
        indices = np.random.choice(len(X), k, replace=False)
        self.centriods = X[indices]
        return self

    @staticmethod
    def _get_nearest_class(sample, centers):
        """点sample离centers中哪个质心更近,返回哪个质心的索引"""
        return np.argmin(np.sqrt(np.sum((centers - sample) ** 2, axis=1)))

    def predict(self, X):
        """预测
        
        :param X: 需要预测的数据集,(n_samples, n_features)
        :return: 数据集每个点分到的类, (n_samples)
        """
        # fit之后会添加属性labels和centriods,遂可根据这个判断是否模型已经训练
        if not hasattr(self, 'labels') or not hasattr(self, 'centriods'):
            raise NotFittedError('fit first, then predict')
        return np.array([self._get_nearest_class(x, self.centriods) for x in X])


if __name__ == "__main__":
    X = np.array([[1, 1], [1, 2], [2, 1], [1, 10],
                  [2, 10], [2, 9], [9, 9], [9, 10]])
    model = KMeans()
    model.fit(X, k=3)
    print(model.centriods)  # 最终的质心
    print(model.labels)  # 训练集每个样本点对应的类
    print(model.predict(np.array([[1, 0], [11, 12]])))  # 预测两个点

结果如下:

已收敛,停止迭代
[[4.6 9.6]
 [2.  1. ]
 [1.  1.5]]
[0. 0. 0. 1. 1. 1. 2. 2.]
[1 0]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值