K-means

参考内容:机器学习实战 第10章 利用K-均值聚类算法对未标注数据分组
作者:VultureEye

1. 聚类

  聚类是一种无监督的学习,他将相似的对象归到一个簇中。它有点像全自动分类。聚类方法几乎可以应用于所有对象中,簇内的对象越相似,聚类的效果越好。我们将要介绍的是最简单的聚类方法:K-means。之所以称之为K-means(K-均值)是因为它可以发现k个不同的簇,且每个簇的质心采用簇中所有样本的均值计算而成。下面会逐步介绍该算法的更多细节。
  在介绍K-means之前,先讨论一下簇识别(cluster identification)。簇识别给出聚类结果的含义。假定有一些数据,现在将相似数据归到一起,簇识别会告诉我们这些簇到底都是些什么。聚类和分类的最大不同在于,分类的目标事先已知,而聚类则不一样。因为其产生的结果与分类相同,而只是类别没有预先定义,聚类有时也被称为无监督分类(unsupervised classification)
  聚类分析试图将相似对象归入同一簇,将不相似对象归到不同簇。相似这一概念取决于所选择的距离计算方法。而到底使用哪种距离计算方法取决于具体的应用。一般有5种常见的距离:
这里写图片描述
这里写图片描述

本文采用余弦距离(Cosine)

2. K-means

K-means聚类算法
经验风险函数:(误差平方和SSE)
这里写图片描述
优点:容易实现
缺点:可能收敛到局部最优(对当前的初始质心来说是最优),在大规模数据集上收敛较慢(数据量大,维度高会造成距离计算)
适用数据类型:数值型数据

2.1 算法流程

  首先,随机选择k个样本点作为初始质心。然后将数据集中的每个点分配到一个簇中,具体来讲,为每个点找距其最近的质心,并将其分配给该质心所对应的簇。这一步完成之后,每个簇的质心更新为该簇所有点的平均值。

伪代码
随机选择k个点作为起始质心
  当任意一个点的簇分配结果发生改变时
    对数据集中的每个数据点
      对每个质心
        计算质心与数据点之间的距离
      将数据点分配到距其最近的簇
    对每一个簇,计算簇中所有点的均值并将均值作为新质心

上面提到的“最近”质心的说法,意味着需要进行某种距离计算。可以选用任意距离度量方法。但数据集上K-means算法的性能会受到所选距离计算方法的影响。下面给出K-means算法的python3实现

2.2 算法实现

'''算法实现代码'''
from numpy import *
import pandas as pd
import random as rd #为了避免和numpy中的random模块产生冲突

def loadDataSet(fileName):
    "读取数据文件"  
    data = []  # assume last column is target value
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float, curLine))  # map all elements to float()
        data.append(fltLine)
        dataMat = mat(data)
    return dataMat


def distance(vecA, vecB):
    "计算距离"
    #return sqrt(sum(power(vecA - vecB, 2))) 欧式距离
    return (vecA*vecB.T)[0,0]/(sqrt((vecA*vecA.T)[0,0])*sqrt((vecB*vecB.T)[0,0])) #余弦距离(越大距离约近)

def randCent(dataSet, k, m):
    "随机选k个样本作为初始质点,k为聚类数,m为样本数"
    rand_num = rd.sample([x for x in range(m)],k)  # 从m个样本中选出k个作为初始聚类中心
    centroids = dataSet[rand_num,:]
    return centroids

def kMeans(dataSet, k, distMeas=distance, createCent=randCent):
    "K-means迭代主函数"
    m = shape(dataSet)[0] #计算样本总数
    clusterAssment = mat(zeros((m, 2)))  # create mat to assign data points
    # to a centroid, also holds SE of each point
    centroids = createCent(dataSet, k, m)
    clusterChanged = [True for x in range(m)]
    minDist = [-inf for x in range(m)] # 初始化样本离最近质心距离
    minIndex = [-1 for x in range(m)] # 初始化样本所属簇
    while True in clusterChanged:# 任意一个点的簇分配结果发生改变就进行循环
        clusterChanged = [False for x in range(m)]
        for i in range(m):  # for each data point assign it to the closest centroid
            for j in range(k):
                distJI = distMeas(centroids[j, :], dataSet[i, :])
                if distJI > minDist[i]:# 距离比当前最小距离更近则进行更新(由于用是余弦距离,值越大越近)
                    minDist[i] = distJI
                    minIndex[i] = j
            if clusterAssment[i, 0] != minIndex[i]: clusterChanged[i] = True
            clusterAssment[i, :] = minIndex[i], minDist[i] ** 2
        print(centroids)
        for cent in range(k):  # recalculate centroids
            ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]  # get all the point in this cluster
            centroids[cent, :] = mean(ptsInClust, axis=0)  # assign centroid to mean
    return centroids, clusterAssment

以上代码定义了三个辅助函数 loadDataSet、distEclud和randCent分别用来接收数据文件、计算点到质心距离和初始化质心。kMeans是算法主函数,用来实现算法的迭代过程。具体的函数调用过程如下:
这里写图片描述

2.3 算法测试

2.3.1 生成聚类结果

首先生成一个数据txt文件:
(包含19个样本点,且每个样本点都有两个维度的特征)

1.658985 4.285136
-3.453687 3.424321
4.838138 -1.151539
-5.379713 -3.362104
0.972564 2.924086
-3.567919 1.531611
0.450614 -3.302219
-3.487105 -1.724432
2.668759 1.594842
-3.156485 3.191137
3.165506 -3.999838
-2.786837 -3.099354
4.208187 2.984927
-2.123337 2.943366
0.704199 -0.479481
-0.392370 -3.963704
2.831667 1.574018
-0.790153 3.343144
2.943496 -3.357075

然后编写如下测试代码用于测试算法(聚类数k=3)

'''测试代码''
if __name__ == '__main__':
    data = loadDataSet(r'C:\Users\61087\Desktop\data.txt')
    center, label = kMeans(data, 3)
    print(center)
    print(label)

输出结果如下:(输出了6个矩阵)

[[ 0.704199 -0.479481]
[-3.487105 -1.724432]
[ 4.208187 2.984927]]
[[ 2.4203906 -2.4580304 ]
[-3.04343162 -0.13239487]
[ 1.9250015 2.78435883]]
[[ 1.95159717 -2.708976 ]
[-3.42215471 0.414935 ]
[ 1.9250015 2.78435883]]
[[ 1.95159717 -2.708976 ]
[-3.42215471 0.414935 ]
[ 1.9250015 2.78435883]]
[[ 2. 0.94554539]
[ 1. 0.62363431]
[ 0. 0.87317429]
[ 1. 0.99016814]
[ 2. 0.92159088]
[ 1. 0.92104563]
[ 0. 0.7796078 ]
[ 1. 1. ]
[ 2. 0.99388175]
[ 1. 0.61418508]
[ 0. 0.99796002]
[ 1. 0.86297544]
[ 2. 1. ]
[ 1. 0.46024732]
[ 0. 1. ]
[ 0. 0.56226799]
[ 2. 0.98803224]
[ 2. 0.44849303]
[ 0. 0.99665918]]

2.3.2 结果解释

第一个矩阵
                   [[ 0.704199 -0.479481]
                    [-3.487105 -1.724432]
                    [ 4.208187 2.984927]]
表示随机选取的三个初始质心(每行对应一个质心),后面的第2-4个矩阵分别表示每轮迭代后新的质心,程序在第3轮迭代后停止,也就是经过3轮k-means迭代,样本的簇分配不发生变化,算法收敛,此时每个簇的质心为
                   [[ 1.95159717 -2.708976 ]
                    [-3.42215471 0.414935 ]
                    [ 1.9250015 2.78435883]]
输出结果的最后一个矩阵存放了每个样本的簇分配结果以及对应的距离的平方。具体来说,该矩阵每行对应一个样本,第一个元素存放的是该样本的簇分配结果,第二个元素存放的是该样本和对应簇质心的距离的平方,对欧氏距离来说,这个量越小表示样本离它对应的质心越近,效果越好,但对我们所采用的余弦距离来说,这个量越大表示样本离它对应的质心越近,聚类效果越好。

2.3.3 聚类图

为了更直观的看到聚类结果,我们编写一个函数plot_scatter去画出聚类结果图:

def plot_scatter(data,center,label):
    '''先画质心数据'''
    X = center[:,0]
    y = center[:,1]
    plt.scatter(X,y,marker='x',c='r',label='centroids')
    '''再画不同簇样本,不同簇样本用不同形状表示'''
    "先画簇0的样本点(找到簇分配结果中簇为0的样本所对应的行Index,然后再作用到data中,拿到对应的样本数据)"
    cluster_0 = data[nonzero(label[:,0]==0)[0]]
    X = cluster_0[:,0]
    y = cluster_0[:,1]
    plt.scatter(X,y,marker='v',c='b',label='cluster 0')
    "画簇1的样本点"
    cluster_0 = data[nonzero(label[:,0]==1)[0]]
    X = cluster_0[:,0]
    y = cluster_0[:,1]
    plt.scatter(X,y,marker='o',c='k',label='cluster1')
    "画簇2的样本点"
    cluster_0 = data[nonzero(label[:,0]==2)[0]]
    X = cluster_0[:,0]
    y = cluster_0[:,1]
    plt.scatter(X,y,marker='s',c='y',label='cluster 2')
    plt.legend(loc = 'best')
    plt.show()

并在主函数里调用这个绘图函数:

    plot_scatter(data,center,label)

作得聚类图入下:

这里写图片描述

  至此,我们完成了K-means算法的实现以及测试

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值