聚类是机器学习问题中无监督学习的一个典型例子。在实际中,并非所有样本都可以贴上标签,在数据量极为庞大的时候,比如视频帧标注,对每个样本都进行贴注标签需要耗费极大精力。在无监督问题中,训练样本是没有标签的,如何对无标签训练样本进行学习,发现其内在的分布结构,同样是学术界和工业界赖以追求解决的一个问题,也是机器学习的一个未来发展方向。
聚类将给定的样例集划分为若干个互不相交的子集。直观来看,好的聚类结果,一定表现出簇内相似度高、簇间相似度低的特征。那么如何来量化这个所谓的“相似度”,我们一般采用的方法是计算样本间的“距离”。
给定两个样本
当
通过上式,我们可以量化两个样本之间的距离。对于样本的某些属性,如果其取值连续,比如降水量等,可以直接按照上式计算;如果属性离散,比如像出行方式这样的属性,我们一般采用VDM(Value Difference Metric)来计算,其本质是利用某一簇样本中某属性上取值为a的样本个数占所有样本中该属性取值为a的样本的比例,来计算这一属性中两个离散值之间的距离。那么,当样本中含有连续和离散两种混合属性的时候,对连续属性采用Minkowski距离计算,对离散属性采用VDM距离计算,将其累加,从而得到两个样本的距离。
有了距离来衡量样本间的相似度,接下来我们介绍一下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的聚类效果之突出。