K-Means聚类分析及其Python实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/chai_zheng/article/details/78193252

聚类是机器学习问题中无监督学习的一个典型例子。在实际中,并非所有样本都可以贴上标签,在数据量极为庞大的时候,比如视频帧标注,对每个样本都进行贴注标签需要耗费极大精力。在无监督问题中,训练样本是没有标签的,如何对无标签训练样本进行学习,发现其内在的分布结构,同样是学术界和工业界赖以追求解决的一个问题,也是机器学习的一个未来发展方向。

聚类将给定的样例集划分为若干个互不相交的子集。直观来看,好的聚类结果,一定表现出簇内相似度高、簇间相似度低的特征。那么如何来量化这个所谓的“相似度”,我们一般采用的方法是计算样本间的“距离”。

给定两个样本 xi=(xi1,xi2,...,xin) xj=(xj1,xj2,...,xjn) ,其Minkowski距离定义为:

distmk(xi,xj)=(u=1nxiuxjup)1p

p=2 时,上式便是我们非常熟悉的欧氏距离,也就是 xixj 的二范数:
disted(xi,xj)=xixj2=u=1nxiuxju2

通过上式,我们可以量化两个样本之间的距离。对于样本的某些属性,如果其取值连续,比如降水量等,可以直接按照上式计算;如果属性离散,比如像出行方式这样的属性,我们一般采用VDM(Value Difference Metric)来计算,其本质是利用某一簇样本中某属性上取值为a的样本个数占所有样本中该属性取值为a的样本的比例,来计算这一属性中两个离散值之间的距离。那么,当样本中含有连续和离散两种混合属性的时候,对连续属性采用Minkowski距离计算,对离散属性采用VDM距离计算,将其累加,从而得到两个样本的距离。

有了距离来衡量样本间的相似度,接下来我们介绍一下K-Means算法。该算法的主要目的是针对划分后的簇,最小化其平方误差,这个误差代表了簇内所有样本围绕簇均值向量的一个紧密程度,误差越小,簇的所有样本都很集中,簇内样本的相似度也就越高,具体表示如下:

E=i=1kxCixμi22

其中, μi=1|Ci|xCix ,代表了该簇的均值向量。然而,最小化上式是一个NP-hard问题,因此,K-Means算法采用贪心算法,通过迭代优化来求解上式。


K-Means

Python源码

# !/usr/bin/env python3
# coding=utf-8
"""
K-means
Author  :Chai Zheng
Blog    :http://blog.csdn.net/chai_zheng/
Github  :https://github.com/Chai-Zheng/Machine-Learning
Email   :[email protected]
Date    :2017.10.8
"""

import random
import numpy as np
from sklearn import preprocessing

#选择初始均值向量
def selectInitMeanVec(Data,k):
    indexInitMeanVec = random.sample(range(m),k)
    initMeanVec = Data[indexInitMeanVec,:]
    return initMeanVec

#计算距离并归入簇中
def calcDistance(Data,k,MeanVec):
    Dist = np.zeros((k,1))
    Label = np.zeros((m,1))
    for i in range(m):
        for j in range(k):
            a = Data[i,:]-MeanVec[j,:]
            Dist[j] = np.sqrt(sum(a**2))
        Label[i] = np.argmin(Dist)
    return Label

#更新均值向量
def updateMeanVec(Data,Label,k,oldMeanVec):
    newMeanVec = np.zeros((k,n))
    numSamples = np.zeros((k,1),dtype = int)
    for i in range(k):
        num = 0
        D = np.zeros((k,0))
        for j in range(m):
            if Label[j] == i:
                D = np.append(D,Data[j,:])
                num += 1
        numSamples[i] = num
        D = np.reshape(D,(-1,n))
        newMeanVec[i,:] = np.mean(D,axis=0)
        #如果本次更新后某一簇中无样本,取上一次均值向量为本次均值向量
        if num == 0:
            newMeanVec[i,:] = oldMeanVec[i,:]
    return newMeanVec,numSamples

if __name__ == '__main__':
    data = np.loadtxt("Wine.txt",delimiter=',')[:,1:14]
    Data = preprocessing.scale(data)
    k = 3
    global m,n
    m,n = Data.shape
    initMeanVec = selectInitMeanVec(Data,k)
    oldMeanVec = initMeanVec.copy()
    Label = calcDistance(Data,k,initMeanVec)
    for i in range(200):
        newMeanVec,numSamples = updateMeanVec(Data,Label,k,oldMeanVec)
        oldMeanVec = newMeanVec.copy()
        Label = calcDistance(Data,k,newMeanVec)
        print('---第%d轮迭代完成'%(i+1))
    print(Label)
    print(numSamples)

在测试程序的时候,我采用了葡萄酒三类别数据集。这样可以方便验证我们的聚类效果。

已知三类的数量分别是59、71、48,K-Means给出的聚类结果分别是59、65+3+3、48。代表了Class1的59个样本和Class3的48个样本被完全正确聚类,Class2聚类对了65个,其余6个被误分至其他类。

总体来看,相比于原始数据的类别,我们得到的结果中三类共178个样本只有6个被错误聚类,其中两类完全正确,可见K-Means的聚类效果之突出。

猜你喜欢

转载自blog.csdn.net/chai_zheng/article/details/78193252