【机器学习】k-means聚类原理及python实现

【机器学习】k-means聚类原理及python实现

1、k-means原理

2、改进的kmenas-------二分k-means

3、实例----对地图上的点进行聚类

              本节完整代码可戳:https://github.com/LisaPig/machineLearning/tree/master/k_means

        聚类是一种无监督的学习,它将相似的对象归到同一个簇中。它有点像全自动分类。聚类方法几乎可以应用于所有对象,簇内的对象越相似,聚类的效果越好。本章要学习一种称为K-均值( K-means)聚类的算法。之所以称之为K-均值是因为它可以发现k个不同的簇,且每个簇的中心采用簇中所含值的均值计算而成。下面会逐步介绍该算法的更多细节。
 

一、k-means原理

        K-均值是发现给定数据集的k个簇的算法。簇个数k是用户给定的,每一个簇通过其质心( 即簇中所有点的中心)来描述。


1. K-均值算法的工作流程?

        首先,随机确定k个初始点作为质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。
        上述过程的伪代码表示如下:
                       创建k个点作为起始质心(经常是随机选择)
                       当任意一个点的簇分配结果发生改变时
                                对数据集中的每个数据点
                                        对每个质心
                                                计算质心与数据点之间的距离
                                        将数据点分配到距其最近的簇
                                对每一个簇,计算簇中所有点的均值并将均值作为质心

代码实现:

【机器学习】k-means聚类原理及python实现_第1张图片
 

2、K-均值聚类的优缺点
        优点:容易实现。
        缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
        适用数据类型:数值型数据。

3、聚类和分类的区别?

         聚类与分类的最大不同在于,分类的目标事先已知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类( unsupervisedclassification)。
 

4.最基本的k-means函数实现聚类:

# -*- coding:utf-8 -*-
"""
@author:Lisa
@file:k_means.py
@time:2018/7/14 0014下午 8:27
"""
from numpy import *
import numpy as np
import matplotlib.pyplot as plt
import matplotlib


"""
函数:读取数据集文件
输出:读取到的数据集(列表形式)
"""
def loadDataSet(filename):
    dataMat=[]
    fr=open(filename)
    for line in fr.readlines():
        curLine=line.strip().split('\t')
        floatLine=list(map(float,curLine) )   #将每一行的数据映射成float型
        dataMat.append(floatLine)
    return dataMat


"""
函数:计算欧式两个向量之间的距离
输入:两个向量vecA、vecB
"""

def distEclud(vecA,vecB):
    return np.sqrt(np.sum(np.power(vecA-vecB , 2)))

"""
函数:k个质心随机初始化
返回值:随机初始化的k个质心数据点
说明:此函数为k-means函数初始化k个质心
注意:质心的每一维度的取值范围应 确保在数据的边界之内
"""
def randCentroids(dataSet,k):
    m,n=np.shape(dataSet)
    centroids=np.mat(np.zeros( (k,n)))
    for i in range(n):
        minI=np.min(dataSet[:,i])
        rangeI=float( np.max(dataSet[:,i]) - minI)
        centroids[:,i]=minI+ rangeI*np.random.rand(k,1)
    return centroids

"""
函数:k-means函数
说明:此为kmeans最基本的函数,后续会在此基础上有改进版----二分K-Means
"""
def k_means(dataSet,k,distCompute=distEclud,creatCentroids=randCentroids):
    m,n=np.shape(dataSet)
    clusterAssign=np.mat(np.zeros( (m,2))) #存储分配的簇和误差
    centroids=creatCentroids(dataSet,k)
    clusterChanged=True
    clusChangedCount=0

    #为每个样本分配簇,直到簇分配不发生变化-》稳定态
    while clusterChanged:
        clusterChanged =False
        clusChangedCount =clusChangedCount+1
        for i in range(m):
            minDist = np.Inf
            minIndex = -1
            for j in range(k):
                dist=distCompute(centroids[j,:],dataSet[i,:])
                if dist

分类结果:

                                         【机器学习】k-means聚类原理及python实现_第2张图片

 

         考虑上图中testSet.txt(4类)的聚类结果,还是挺好的。但是在一个包含三个簇的数据集上运行K-均值算法之后的结果,点的簇分配结果值没有那么准确,如下图。 K-均值算法收敛但聚类效果较差的原因是, K-均值算法收敛到了局部最小值,而非全局最小值(局部最小值指结果还可以但并非最好结果,全局最小值是可能的最好结果)。
 

                                          【机器学习】k-means聚类原理及python实现_第3张图片

      5、那么,如何评价聚类结果好坏?

      一种用于度量聚类效果的指标是SSE( Sum of Squared Error,误差平方和),对应程序中clusterAssment矩阵的第一列之和。 SSE值越小表示数据点越接近于它们的质心,聚类效果越好。因为对误差取了平方,因此更加重视那些远离中心的点。

       一种肯定可以降低SSE值的方法是增加簇的个数,但这违背了聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量。
 

      6、那么,如何解决k-means不能收敛到全局最小值的问题?

     那么如何对上图的结果进行改进?可以对生成的簇进行后处理,一种方法是将具有最大SSE值的簇划分成两个簇。具体实现时可以将最大簇包含的点过滤出来并在这些点上运行K-均值(k=2)
 

二、改进版kmeans--------二分k-means

          为克服K-均值算法收敛于局部最小值的问题,有人提出了另一个称为二分K-均值( bisecting K-means)的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。
        二分K-均值算法的伪代码形式如下:
             将所有点看成一个簇
             当簇数目小于k时

                     对于每一个簇
                               计算总误差
                               在给定的簇上面进行K-均值聚类( k=2)
                               计算将该簇一分为二之后的总误差
                      选择使得误差最小的那个簇进行划分操作

代码实现:

"""
函数:二分K-Means聚类算法
说明:是在基本k-means算法的基础上改进的二分k-means算法->优点是可以避免陷入局部最小值,而非全局最小值
算法步骤: 
1.将所有点看成一个簇,求质心
2.当簇数目小于k时
   对于每一个簇
       计算总误差
       在给定的簇上面进行k-均值聚类(k=2),即一分为2
       计算将该簇一分为二后的总误差
   选择使得误差最小的那个簇进行划分操作
"""
def bi_K_means(dataSet,k,distCompute=distEclud):
    m,n=np.shape(dataSet)
    #初始化
    clusterAssign=np.mat(np.zeros( (m,2) ))
    #创建初始的一个簇
    centroid0=np.mean(dataSet,axis=0).tolist()[0]
    centroidList=[centroid0]
    #绘制初始划分簇
    draw(dataSet, np.mat(centroidList), clusterAssign, k=len(centroidList))

    for i in range(k):
        clusterAssign[i,1]=distCompute(np.mat(centroid0), dataSet[i,:]) **2
    while (len(centroidList)

实现聚类的结果:

              【机器学习】k-means聚类原理及python实现_第4张图片

二分k_means分类过程展示:(完整划分过程)

【机器学习】k-means聚类原理及python实现_第5张图片

完整代码可戳:https://github.com/LisaPig/machineLearning/tree/master/k_means

三、实例----对地图上的点进行聚类

实例----对俄勒冈州波特兰市夜生活娱乐地点进行聚类

            有一个包含格式化地理坐标的列表(places.txt),接下来可以对这些俱乐部进行聚类。(此处省去了《机器学习实战》中的获取地理坐标等步骤,直接从书中得到places.txt这一处理后的包含地理坐标的列表文件)。

           注意:绘制结果图时,为了画出这些簇,首先创建一幅图和一个矩形,然后使用该矩形来决定绘制图的哪一部分。接下来构建一个标记形状的列表用于绘制散点图。后边会使用唯一的标记来标识每个簇。下一步使用imread()函数基于一幅图像来创建矩阵 ,然后使用imshow()绘制该矩阵。接下来,在同一幅图上绘制一张新的图,这允许你使用两套坐标系统并且不做任何缩放或偏移。紧接着,遍历每一个簇并将它们一一画出来。标记类型从前面创建的scatterMarkers列表中得到。使用索引i % len(scatterMarkers)来选择标记形状 ,这意味着当有更多簇时,可以循环使用这些标记。最后使用十字标记来表示簇中心并在图中显示

代码:

# -*- coding:utf-8 -*-
"""
@author:Lisa
@file:cluster_example.py
@note:利用k-means实现地理坐标的聚类
@time:2018/7/16 0016下午 9:39
"""

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from k_means import *

"""
函数:球面距离计算
原理:利用球面余弦定理计算球面上两点的距离
"""
def distSLC(vecA,vecB):
    a=np.sin(vecA[0,1]*np.pi/180) *np.sin(vecB[0,1]*np.pi/180)
    b=np.cos(vecA[0,1]*np.pi/180) *np.cos(vecB[0, 1] * np.pi /180)*\
      np.cos(np.pi*vecB[0,0]-vecA[0,0])/180

    return np.arccos(a+b)*6371.0

"""
函数:从文件中读取数据列表
注意:要根据文本格式来读取
"""
def loadSet(fileName):
    dataList=[]
    fr=open(fileName)
    for line in fr.readlines():
        curLine=line.split('\t')
        dataList.append([ float(curLine[4]),float(curLine[3])]) #第4和5列分别代表纬度和经度
    return np.mat(dataList)

"""
函数:绘制聚类点分布
"""
def draw(dataMat,centroids,clustAssign,k):
    fig = plt.figure()
    rect = [0.1, 0.1, 0.8, 0.8]  # 创建矩形
    # 创建不同标记图案
    scatterMarkers = ['s', 'o', '^', '8', 'p', \
                      'd', 'v', 'h', '>', '<']
    axprops = dict(xticks=[], yticks=[])
    ax0 = fig.add_axes(rect, label='ax0', **axprops)
    imgP = plt.imread('data\\Portland.png')  # 导入地图
    ax0.imshow(imgP)
    ax1 = fig.add_axes(rect, label='ax1', frameon=False)
    for i in range(k):
        ptsInCurrCluster = dataMat[np.nonzero(clustAssign[:, 0].A == i)[0], :]
        markerStyle = scatterMarkers[i % len(scatterMarkers)]
        ax1.scatter(ptsInCurrCluster[:, 0].flatten().A[0], ptsInCurrCluster[:, 1].flatten().A[0], marker=markerStyle,
                    s=90)
    ax1.scatter(centroids[:, 0].flatten().A[0], centroids[:, 1].flatten().A[0], marker='+', s=300)
    plt.show()

if __name__=='__main__':
    dataMat=loadSet('data\\places.txt')
    k=3
    centroids,clusterAssign=bi_K_means(dataMat,k)
    draw(dataMat,centroids,clusterAssign,k)

聚类结果:(k=5)

                            【机器学习】k-means聚类原理及python实现_第6张图片

(K=3):

                             【机器学习】k-means聚类原理及python实现_第7张图片

      k值不同,聚类结果不同,可多试几次,寻找最佳k值。

----------------------------------------------------------------    end    ------------------------------------------------------------------

参考:

1、《机器学习实战》

2、https://blog.csdn.net/sinat_17196995/article/details/70332664

你可能感兴趣的:(机器学习)