【3】无监督学习--3--聚类--基于距离--kmeans

一、简介

k-means algorithm算法是一种得到最广泛使用的基于划分的聚类算法,把n个对象分为k个簇,以使簇内具有较高的相似度。相似度的计算根据一个簇中对象的平均值来进行。它与处理混合正态分布的最大期望算法很相似,因为他们都试图找到数据中自然聚类的中心。

算法首先随机地选择k个对象,每个对象初始地代表了一个簇的平均值或中心。对剩余的每个对象根据其与各个簇中心的距离,将它赋给最近的簇,然后重新计算每个簇的平均值。这个过程不断重复,直到准则函数收敛。

$$V = \sum_{i=1}^{k} \sum_{x_j \in S_i} (x_j - \mu_i)^2$$

它假设对象属性来自于空间向量,并且目标是使各个群组内部的均方误差总和最小。假设有k个群组Si, i=1,2,…,k。μi是群组Si内所有元素xj的重心,或叫中心点。

术语“k-means”最早是由James MacQueen在1967年提出的,这一观点可以追溯到1957年 Hugo Steinhaus所提出的想法。1957年,斯图亚特·劳埃德最先提出这一标准算法,当初是作为一门应用于脉码调制的技术,直到1982年,这一算法才在贝尔实验室被正式提出。1965年, E.W.Forgy发表了一个本质上是相同的方法,1975年和1979年,Hartigan和Wong分别提出了一个更高效的版本。

二、算法

k-means算法以k为参数,把n个对象分成k个簇,使簇内具有较高的相似度,而簇间的相似度较低。

其处理过程如下:

  1. 随机选择k个点作为初始的聚类中心;
  2. 对于剩下的点,根据其与聚类中心的距离,将其归入最近的簇
  3. 对每个簇,计算所有点的均值作为新的聚类中心
  4. 重复2、3直到聚类中心不再发生改变

算法描述

输入:簇的数目k;包含n个对象的数据集D。

输出:k个簇的集合。

方法:

从D中任意选择k个对象作为初始簇中心;

repeat;

根据簇中对象的均值,将每个对象指派到最相似的簇;

更新簇均值,即计算每个簇中对象的均值;

计算准则函数;

until准则函数不再发生变化。

算法的性能分析

1)优点

(1)k-平均算法是解决聚类问题的一种经典算法,算法简单、快速。

(2)对处理大数据集,该算法是相对可伸缩的和高效率的,因为它的复杂度大约是O(nkt),其中n是所有对象的数目,k是簇的数目,t是迭代的次数。通常k«n。这个算法经常以局部最优结束。

(3)算法尝试找出使平方误差函数值最小的k个划分。当簇是密集的、球状或团状的,而簇与簇之间区别明显时,它的聚类效果很好。

2)缺点

(1)k-平均方法只有在簇的平均值被定义的情况下才能使用,不适用于某些应用,如涉及有分类属性的数据不适用。

(2)要求用户必须事先给出要生成的簇的数目k。

(3)对初值敏感,对于不同的初始值,可能会导致不同的聚类结果。

(4)不适合于发现非凸面形状的簇,或者大小差别很大的簇。

(5)对于"噪声"和孤立点数据敏感,少量的该类数据能够对平均值产生极大影响。

三、K-means的应用

官网例子;

	from sklearn.externals import joblib
>>> from sklearn.cluster import KMeans
>>> import numpy as np
>>> X = np.array([[1, 2], [1, 4], [1, 0],
...               [4, 2], [4, 4], [4, 0]])

#调用kmeans类
>>> kmeans = KMeans(n_clusters=2, random_state=0).fit(X)

#9个中心
print clf.cluster_centers_

#每个样本所属的簇
>>> kmeans.labels_
array([0, 0, 0, 1, 1, 1], dtype=int32)


#用来评估簇的个数是否合适,距离越小说明簇分的越好,选取临界点的簇个数
print clf.inertia_

#进行预测
>>> kmeans.predict([[0, 0], [4, 4]])
array([0, 1], dtype=int32)
>>> kmeans.cluster_centers_
array([[ 1.,  2.],
       [ 4.,  2.]]) 
	
#保存模型
joblib.dump(clf , 'c:/km.pkl')

#载入保存的模型
clf = joblib.load('c:/km.pkl')

#用来评估簇的个数是否合适,距离越小说明簇分的越好,选取临界点的簇个数
for i in range(5,30,1):
    clf = KMeans(n_clusters=i)
    s = clf.fit(feature)
    print i , clf.inertia_

调用KMeans方法所需参数:

  • n_clusters:用于指定聚类中心的个数

  • init:初始聚类中心的初始化方法;k-means++’ 会由程序自动寻找合适的n_clusters;

    类型:可以是function 可以是array(random or ndarray) 默认值:采用k-means++(一种生成初始质心的算法)

    kmeans++:种子点选取的第二种方法。 kmedoids(PAM,Partitioning Around Medoids)

    能够解决kmeans对噪声敏感的问题。kmeans寻找种子点的时候计算该类中所有样本的平均值,如果该类中具有较为明显的离群点,会造成种子点与期望偏差过大。例如,A(1,1),B(2,2),C(3,3),D(1000,1000),显然D点会拉动种子点向其偏移。这样,在下一轮迭代时,将大量不该属于该类的样本点错误的划入该类。

    为了解决这个问题,kmedoids方法采取新的种子点选取方式,1)只从样本点中选;2)选取标准能够提高聚类效果,例如上述的最小化J函数,或者自定义其他的代价函数。但是,kmedoids方法提高了聚类的复杂度。

          init : {'k-means++', 'random' or an ndarray}
          Method for initialization, defaults to 'k-means++':
         'k-means++' : selects initial cluster centers for k-mean clustering in a smart way to speed up convergence. See section Notes in k_init for more details.
    
  • max_iter:最大的迭代次数,这里设置最大迭代次数为300;

  • 一般调用时只用给出n_clusters即可,init

  • n_init设为10意味着进行10次随机初始化,选择效果最好的一种来作为模型; 类型:整数型(int)

    默认值:10

    目的:每一次算法运行时开始的centroid seeds是随机生成的, 这样得到的结果也可能有好有坏. 所以要运行算法n_init次, 取其中最好的。

    n_init : int, default: 10 Number of time the k-means algorithm will be run with different centroid seeds. The final results will be the best output of n_init consecutive runs in terms of inertia.

  • tol:float形,默认值= 1e-4,与inertia结合来确定收敛条件; 容忍的最小误差,当误差小于tol就会退出迭代(算法中会依赖数据本身)

  • n_jobs:指定计算所用的进程数;

  • verbose 参数设定打印求解过程的程度,值越大,细节打印越多;

  • copy_x:布尔型,默认值=True。当我们precomputing distances时,将数据中心化会得到更准确的结果。如果把此参数值设为True,则原始数据不会被改变。如果是False,则会直接在原始数据 上做修改并在函数返回值时将其还原。但是在计算过程中由于有对数据均值的加减运算,所以数据返回后,原始数据和计算前可能会有细小差别。

  • precompute_distances: 这个参数会在空间和时间之间做权衡,如果是True 会把整个距离矩阵都放到内存中,auto 会默认在数据样本大于featurs*samples 的数量大于12e6 的时候False,False时核心实现的方法是利用Cpython 来实现的

    类型:布尔型(auto,True,False) 默认值:“auto”

  • random_state: 随机生成器的种子 ,和初始化中心有关 ;类型:整型或numpy(RandomState, optional);默认值:None

其它参数:

  • data:加载的数据
  • label:聚类后各数据所属的标签
  • axis: 按行求和
  • fit_predict():计算簇中心以及为簇分配序号

其他属性

  1. cluster_centers_:向量,[n_clusters, n_features] Coordinates of cluster centers (每个簇中心的坐标??);
  2. Labels_:每个样本对应的簇类别标签
  3. inertia_:float,每个点到其簇的质心的距离之和。

对于非监督机器学习,输入的数据是样本的特征,clf.fit(X)就可以把数据输入到分类器里。;用分类器对未知数据进行分类,需要使用的是分类器的predict方法。

案例一

数据介绍:

K-means的应用

现有1999年全国31个省份城镇居民家庭平均每人全年消费性支出的八个主 要变量数据,这八个变量分别是:食品、衣着、家庭设备用品及服务、医疗 保健、交通和通讯、娱乐教育文化服务、居住以及杂项商品和服务。利用已 有数据,对31个省份进行聚类。

##实验目的:

通过聚类,了解1999年各个省份的消费水平在国内的情况。

使用算法: K-means聚类算法 • 实现过程:

1.建立工程,导入sklearn相关包

import numpy as np
from sklearn.cluster import KMeans

关于一些相关包的介绍:

NumPy是Python语言的一个扩充程序库。支持高级大量的维度数组与矩阵运算,此外也针对数组 运算提供大量的数学函数库。 使用sklearn.cluster.KMeans可以调用K-means算法进行聚类

#coding:UTF-8

import sys
reload(sys)
sys.setdefaultencoding('utf-8')

import numpy as np
from sklearn.cluster import KMeans


def loadData(filePath):
    fr = open(filePath,'r+')
    lines = fr.readlines()
    retData = []
    retCityName = []
    for line in lines:
        items = line.strip().split(",")
        retCityName.append(items[0])
        retData.append([float(items[i]) for i in range(1,len(items))])
    return retData,retCityName


if __name__ == '__main__':
    data,cityName = loadData('课程数据/聚类/31省市居民家庭消费水平-city.txt')  #数据集的位置 https://pan.baidu.com/s/1kV0uFrh
    km = KMeans(n_clusters=4)
    label = km.fit_predict(data)
    expenses = np.sum(km.cluster_centers_,axis=1)
    #print(expenses)
    CityCluster = [[],[],[],[]]
    for i in range(len(cityName)):
        CityCluster[label[i]].append(cityName[i])
    for i in range(len(CityCluster)):
        print("Expenses:%.2f" % expenses[i])
        print(CityCluster[i])

四、 拓展&&改进

计算两条数据相似性时,Sklearn 的K-Means默认用的是欧式距离。虽然还有余弦相 似度,马氏距离等多种方法,但没有设定计算距离方法的参数。

如果想要自定义计算距离的方式时,可以 更改此处源码。 def euclidean_distances(): 建 议 使 用 scipy.spatial.distance.cdist 方 法 源码地址: https://github.com/scikit-learn/scikit-learn/blob/master/sklearn/metrics/pairwise.py

使用形式:scipy.spatial.distance.cdist(A, B, metric=‘cosine’)

https://docs.scipy.org/doc/scipy/reference/generated/scipy.spatial.distance.cdist.html#scipy.spatial.distance.cdist

参考资料:

这里是一个广告位,,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn