机器学习算法之K-means

    上篇文章分析了关于KNN最近邻算法,K-means算法和KNN算法有许多的相似之处。下面从K-means的概念、思想、优缺点方面来阐述下K-means算法

一、概念

    K-means算法是很典型的基于距离的聚类算法,采用距离作为相似性的评价指标,即认为两个对象的距离越近,其相似性就越大。其中K就是用户指定的参数也就是将数据集分为多少个簇,簇是对象的集合,其中每个对象到定义该簇的原型的距离比其他簇的原型距离更近;而means就是均值的意思即质点的位置可以通过划归到该簇的样本均值(中心位置)来确定。

    分类算法:分类算法是一中典型的监督学习算法,主要用于将测试样本分出是哪一个已知类别中。

    聚类算法:聚类算法是一种典型的无监督学习算法,主要用于将相似的样本自动归到一个类别中。

    当然这里的距离的选择可以分为如下距离(图片来源 https://blog.csdn.net/taoyanqi8932/article/details/53727841)


二、思想

K-means算法的核心思想是事先确定常数K,常数K翌维着最终的聚类类别数,首先随机的选定初始点为质心,并通过计算每一个样本与质心的之间的相似度(距离远近)将样本点归属到最相似的簇中。接着通过一定的优化(已经划分到该簇的样本的中间位置作为下一个质心)质心,直到质心不再改变或者改变位置很小,最终确定了每个样本所属的类别。

三、优缺点及改进策略

1、优点

    1) 算法快速、简单

    2)对于大数据集有较高的效率

    3) 时间复杂近于线性,适合挖掘大规模数据集。K-means聚类算法的时间复杂度时0(nkt),n代表数据容量、k代表簇的数目、t代表迭代的次数。

2、缺点

    1) K-means中K的选取会对数据结果有较大影响

    2) 初始质心的选取问题也会影响结果

    3) 数据量大、并且迭代次数多时时间开销比较大。

3、改进策略

    为了克服K-means算法收敛于局部最小值的问题,提出了一种二分K-means(bisecting K-means)。例如目前将数据化成了2个簇,而要求3个簇,那么在算法进行时计算其每个簇对应得SSE(误差平方和)值,将SSE最小的簇划成两个簇。这样就有3个簇了。下面会附K-means以及优化后的代码及测试文件。

四、python实现

1、分类算法

#! -*- coding:utf-8 -*-
import numpy as np


def centroid(data):
    """找出所给数据的质心"""
    # print("data is:",data)
    return np.mean(data, 0)


def sse(data):
    """计算给定数据的SSE(误差平方和)"""
    u = centroid(data)
    return np.sum(np.linalg.norm(data - u, 2, 1))

#K-means算法
class KMeansClusterer:
    """The standard k-means clustering algorithm."""

    def __init__(self, data=None, k=2, min_gain=0.01, max_iter=100,
                 max_epoch=10, verbose=True):
        """Learns from data if given."""
        if data is not None:
            self.fit(data, k, min_gain, max_iter, max_epoch, verbose)

    def fit(self, data, k=2, min_gain=0.01, max_iter=100, max_epoch=10,
            verbose=True):
        """Learns from the given data.

        Args:
            data:      The dataset with m rows each with n features
            k:         The number of clusters
            min_gain:  Minimum gain to keep iterating
            max_iter:  Maximum number of iterations to perform
            max_epoch: Number of random starts, to find global optimum
            verbose:   Print diagnostic message if True

        Returns:
            self
        """
        # Pre-process
        self.data = np.matrix(data)
        self.k = k
        self.min_gain = min_gain

        #为全局最优执行多个随机初始化
        min_sse = np.inf
        for epoch in range(max_epoch):

            # 随机初始化K质心
            indices = np.random.choice(len(data), k, replace=False)  ##从0到99随机选取10个不重复的数字
            u = self.data[indices, :]

            # Loop
            t = 0
            old_sse = np.inf
            while True:
                t += 1

                # Cluster assignment
                C = [None] * k

                for x in self.data:
                    #np.linalg.norm(x,ord=None,axis=None,keepdims=False) 该函数的作用是就范数(向量的长度)
                                #ord = 2 二级范数 ord = 1 一级范数 axis = 1表示按行向量处理 axis = 2表示按列向量处理
                    #np.argmin(x) 将x排序返回下标
                    j = np.argmin(np.linalg.norm(x - u, 2, 1))
                    #将数据集中属于这一类(距离这个10个随机质心最近)的都存下来,
                    # 构建成一个向量,np.vstack((c[j],x),axis=0)将x以一行添加到c[j]里面
                    C[j] = x if C[j] is None else np.vstack((C[j], x))

                # 更新质心(将属于这一类的数据的X和Y相加求平均)
                for j in range(k):
                    u[j] = centroid(C[j])

                # 迭代次数阈值
                if t >= max_iter:
                    break

                #优化(该类的质心与数据的距离之和)
                new_sse = np.sum([sse(C[j]) for j in range(k)])
                #前后两次距离之和的差值(微调)
                gain = old_sse - new_sse
                if verbose:
                    line = "Epoch {:2d} Iter {:2d}: SSE={:10.4f}, GAIN={:10.4f}"
                    print(line.format(epoch, t, new_sse, gain))
                #微调的结果小于预先设置的阈值即使没达到最大迭代次数也中断循环
                if gain < self.min_gain:
                    if new_sse < min_sse:
                        min_sse, self.C, self.u = new_sse, C, u
                    break
                #更新距离之和
                else:
                    old_sse = new_sse

            if verbose:
                print('')  # blank line between every epoch

        return self

#K-means优化算法
class BisectingKMeansClusterer:
    """Bisecting k-means clustering algorithm.

    It internally uses the standard k-means algorithm with k=2.
    """

    def __init__(self, data, max_k=10, min_gain=0.1, verbose=True):
        """Learns from data if given."""
        if data is not None:
            self.fit(data, max_k, min_gain, verbose)

    def fit(self, data, max_k=10, min_gain=0.1, verbose=True):
        """Learns from given data and options.

        Args:
            data:     The dataset with m rows each with n features
            max_k:    Maximum number of clusters
            min_gain: Minimum gain to keep iterating
            verbose:  Print diagnostic message if True

        Returns:
            self
        """

        self.kmeans = KMeansClusterer()
        self.C = [data, ]
        self.k = len(self.C)
        self.u = np.reshape(
            [centroid(self.C[i]) for i in range(self.k)], (self.k, 2))

        if verbose:
            print("k={:2d}, SSE={:10.4f}, GAIN={:>10}".format(
                self.k, sse(data), '-'))

        while True:
            # pick a cluster to bisect
            sse_list = [sse(data) for data in self.C]
            old_sse = np.sum(sse_list)
            data = self.C.pop(np.argmax(sse_list))
            # bisect it
            self.kmeans.fit(data, k=2, verbose=False)
            # add bisected clusters to our list
            self.C.append(self.kmeans.C[0])
            self.C.append(self.kmeans.C[1])
            self.k += 1
            self.u = np.reshape(
                [centroid(self.C[i]) for i in range(self.k)], (self.k, 2))
            # check sse or k
            sse_list = [sse(data) for data in self.C]
            new_sse = np.sum(sse_list)
            gain = old_sse - new_sse
            if verbose:
                print("k={:2d}, SSE={:10.4f}, GAIN={:10.4f}".format(
                    self.k, new_sse, gain))
            if gain < min_gain or self.k >= max_k:
                break

        return self

    注意:

    该算法中的核心是:

        1) 随机生成10个质点;

        2) 将所有的数据与该10个质点求距离,距离哪个质心最近就划分到该质心的类别中;

        3)    更新质心,将属于该簇的的数据通过求均值来得出新的质心  

2、测试代码

    1) K-means

import argparse

import matplotlib.pyplot as plt
import numpy as np

from ClassifyAlg.Kmeans.kmeansAlg1.Class.kmeans import BisectingKMeansClusterer, KMeansClusterer

def main():
    # Parse the arguments
    parser = argparse.ArgumentParser(description='Run the k-means clustering algorithm.', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument( '-f', '--datafile', type=str, default='data.txt', help='The data file containing m x n matrix')
    parser.add_argument( '-k', '--k', type=int, default=10,help='Number of clusters')
    parser.add_argument('-g', '--min-gain', type=float, default=0.1,help='Minimum gain to keep iterating')
    parser.add_argument( '-t', '--max-iter', type=int, default=100,help='Maximum number of iterations per epoch')
    parser.add_argument('-e', '--epoch', type=int, default=10,help='Number of random starts, for global optimum')
    parser.add_argument('-v', '--verbose', default=0, action='store_true', help='Show verbose info')
    args = parser.parse_args()

    # prepare data
    data = np.loadtxt(args.datafile)

    # initialize clusterer
    c = KMeansClusterer(data, k=args.k, max_iter=args.max_iter, max_epoch=args.epoch, verbose=args.verbose)

    # the result
    plt.figure(1)
    # plot the clusters in different colors
    for i in range(c.k):
        #C[:,0] 第一列(x) C[:,1]第二列()
        plt.plot(c.C[i][:, 0], c.C[i][:, 1], 'x')
    # plot the centroids in black squares
    plt.plot(c.u[:, 0], c.u[:, 1], 'ks')
    plt.show()


if __name__ == '__main__':
    main()    

    2)bisecting K-means

import argparse

import matplotlib.pyplot as plt
import numpy as np

from ClassifyAlg.Kmeans.kmeansAlg1.Class.kmeans import BisectingKMeansClusterer, KMeansClusterer

def main():
    # Parse the arguments
    parser = argparse.ArgumentParser(description='Run the bisecting k-means clustering algorithm.',formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument( '-f', '--datafile', type=str, default='data.txt',help='The data file containing m x n matrix')
    parser.add_argument( '-k', '--max-k', type=int, default=10, help='Maximum number of clusters')
    parser.add_argument( '-e', '--min-gain', type=float, default=0.1, help='Minimum gain to keep iterating')
    parser.add_argument( '-v', '--verbose', default=0, action='store_true',help='Show verbose info')
    args = parser.parse_args()

    # prepare data
    data = np.loadtxt(args.datafile)

    # initialize clusterer
    c = BisectingKMeansClusterer(data, max_k=args.max_k, min_gain=args.min_gain, verbose=args.verbose)

    # the result
    plt.figure(1)
    # plot the clusters in different colors
    for i in range(c.k):
        plt.plot(c.C[i][:, 0], c.C[i][:, 1], 'x')
    # plot the centroids in black squares
    plt.plot(c.u[:, 0], c.u[:, 1], 'ks')
    plt.show()


if __name__ == '__main__':
    main()

3、测试结果


五、KNN和K-means



源码及数据的githup:https://github.com/munikarmanish/kmeans (我也是看大神的代码,然后理解的解析的 :) :)

猜你喜欢

转载自blog.csdn.net/bingjia103126/article/details/80509846