K-means聚类
前言
k均值聚类算法(k-means clustering algorithm)是一种迭代求解的聚类分析算法,其步骤是,预将数据分为K组,则随机选取K个对象作为初始的聚类中心,然后计算每个对象与各个种子聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心。聚类中心以及分配给它们的对象就代表一个聚类。每分配一个样本,聚类的聚类中心会根据聚类中现有的对象被重新计算。这个过程将不断重复直到满足某个终止条件。终止条件可以是没有(或最小数目)对象被重新分配给不同的聚类,没有(或最小数目)聚类中心再发生变化,误差平方和局部最小。一、K-means的算法思路
- 任意选k个样本作为初始的簇中心。
- 分别计算每个样本到这k个簇中心的距离,将该样本划分到距离最近的簇中心属于的那个簇。
- 所有样本分类完毕后, 如分类结果与上次相同,则聚类结束。
- 若分类结果与上次不同,取每个簇的各属性平均值对应的点为该簇新的簇中心,重复上述操作。
二、代码实现
程序主要有6个部分:
- read_xslx(xslx_path) #读取excel文件
- standardization(v,mu,sigma) #对一个数据规范化
- standardize(data) #对数据集规范化
- calDistance(testingrow, trainingrow) #计算样本间距离
- Kmeans(dataset, k, centers, label) #K-means算法部分
- showData(dataSet) #聚类结果可视化
1. 读取excel文件
#读取excel文件
####################################################################################################################
def read_xslx(xslx_path):
dataSet = [] # 先声明一个空list
data = xlrd.open_workbook(xslx_path) # 读取文件
table = data.sheet_by_index(0) # 按索引获取工作表,0就是工作表1
for i in range(table.nrows): # table.nrows表示总行数
line = table.row_values(i) # 读取每行数据,保存在line里面,line是list
dataSet.append(line) # 将line加入到data中,data是二维list
dataSet = np.array(dataSet) # 将data从二维list变成数组
return dataSet
####################################################################################################################
2.对一个数据规范化
#规范化处理函数
#####################################################################################
def standardization(v,mu,sigma):
v = (v - mu)/sigma
return v
#####################################################################################
3. 对数据集规范化
#规范化数据
####################################################################################################################
def standardize(data):
numTotal = data.shape[0] # 记录测试数据集总条数
attr1 = data[:, 0].astype(float) #标准化过程
attr2 = data[:, 1].astype(float)
attr3 = data[:, 2].astype(float)
attr4 = data[:, 3].astype(float)
attr5 = data[:, 4].astype(float)
attr6 = data[:, 5].astype(float)
Data = data
mu1 = np.mean(attr1)
sigma1 = np.std(attr1)
for i in range(0,numTotal):
Data[:, 0][i] = standardization(attr1[i],mu1,sigma1)
mu2 = np.mean(attr2)
sigma2 = np.std(attr2)
for i in range(0,numTotal):
Data[:, 1][i] = standardization(attr2[i],mu2,sigma2)
mu3 = np.mean(attr3)
sigma3 = np.std(attr3)
for i in range(0,numTotal):
Data[:, 2][i] = standardization(attr3[i],mu3,sigma3)
mu4 = np.mean(attr4)
sigma4 = np.std(attr4)
for i in range(0,numTotal):
Data[:, 3][i] = standardization(attr4[i],mu4,sigma4)
mu5 = np.mean(attr5)
sigma5 = np.std(attr5)
for i in range(0,numTotal):
Data[:, 4][i] = standardization(attr5[i],mu5,sigma5)
mu6 = np.mean(attr6)
sigma6 = np.std(attr6)
for i in range(0,numTotal):
Data[:, 5][i] = standardization(attr6[i],mu6,sigma6)
return Data
####################################################################################################################
4. 计算样本间距离
#计算距离
####################################################################################################################
def calDistance(testingrow, trainingrow):
dist = ((float(testingrow[0]) - float(trainingrow[0])) ** 2 + (float(testingrow[1]) - float(trainingrow[1])) ** 2 \
+ (float(testingrow[2]) - float(trainingrow[2])) ** 2 + (float(testingrow[3]) - float(trainingrow[3])) ** 2\
+ (float(testingrow[4]) - float(trainingrow[4])) ** 2 + (float(testingrow[5]) - float(trainingrow[5])) ** 2) ** 0.5
return dist
####################################################################################################################
5. K-means算法部分
#K-means
####################################################################################################################
def Kmeans(dataset, k, centers, label):
within = 0 #类内差异
between = 0 #类间差异
numTotal = dataset.shape[0] # 记录测试数据集总条数
attrSum = np.zeros((k,6))
SampleNum = np.linspace(0,0,k)
for i in range(0, numTotal): # 遍历每个样本
distance = [] # 记录该样本和各簇中心的距离
for j in range(0, k):
distance.append(calDistance(dataset[i], centers[j])) # 计算距离
pos = distance.index(min(distance))
within += (min(distance))**2 #计算类内差异
dataset[i][-1] = centers[pos][-1] # 将该样本标记为与其距离最近的簇中心的标签
attrSum[pos] = attrSum[pos] + dataset[i][:-1].astype(float) # 累加该簇的各属性值,为之后求平均做准备
SampleNum[pos] = SampleNum[pos] + 1 #记录各簇的样本个数,为之后求平均做准备
for i in range(0, k): #计算类间差异
for j in range(i, k):
between += (calDistance(centers[i], centers[j]))**2
if((label == dataset[:,-1].astype(float)).all()): # 判断标签有无变化,以判断聚类是否已收敛
return dataset, within, between
else:
next_label = dataset[:,-1].astype(float) # 新的标签
for i in range(0, k):
for j in range(0, 6):
centers[i][j] = attrSum[i][j]/SampleNum[i] #新簇中心的属性值
centers[i][6] = i #新簇中心的标签
next_centers = centers
return Kmeans(dataset, k, next_centers, next_label) #新的一次迭代
####################################################################################################################
6. 聚类结果可视化
#聚类结果可视化
####################################################################################################################
def showData(dataSet):
label_pred = dataSet[:,-1]
mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
# 这里'or'代表中的'o'代表画圈,'r'代表颜色为红色,后面的依次类推
j = 0
for i in label_pred:
plt.plot([float(dataSet[j][0])], [float(dataSet[j][3])], mark[int(i)], markersize=4)
j += 1
plt.show()
####################################################################################################################
完整代码
# -*- coding: utf-8 -*- 支持文件中出现中文字符
#########################################################################
"""
Created on Mon Nov 23 19:28:00 2020
@author: ixobgnew
代码功能描述:(1)读取xlsx文件
(2)数据规范化处理
(3)计算数据欧氏距离
(4)K-means聚类算法
(5)聚类结果可视化
"""
#####################################################################
import xlrd
import numpy as np
import random
import matplotlib.pyplot as plt
#读取excel文件
####################################################################################################################
def read_xslx(xslx_path):
dataSet = [] # 先声明一个空list
data = xlrd.open_workbook(xslx_path) # 读取文件
table = data.sheet_by_index(0) # 按索引获取工作表,0就是工作表1
for i in range(table.nrows): # table.nrows表示总行数
line = table.row_values(i) # 读取每行数据,保存在line里面,line是list
dataSet.append(line) # 将line加入到data中,data是二维list
dataSet = np.array(dataSet) # 将data从二维list变成数组
return dataSet
####################################################################################################################
#规范化处理函数
#####################################################################################
def standardization(v,mu,sigma):
v = (v - mu)/sigma
return v
#####################################################################################
#规范化数据
####################################################################################################################
def standardize(data):
numTotal = data.shape[0] # 记录测试数据集总条数
attr1 = data[:, 0].astype(float) #标准化过程
attr2 = data[:, 1].astype(float)
attr3 = data[:, 2].astype(float)
attr4 = data[:, 3].astype(float)
attr5 = data[:, 4].astype(float)
attr6 = data[:, 5].astype(float)
Data = data
mu1 = np.mean(attr1)
sigma1 = np.std(attr1)
for i in range(0,numTotal):
Data[:, 0][i] = standardization(attr1[i],mu1,sigma1)
mu2 = np.mean(attr2)
sigma2 = np.std(attr2)
for i in range(0,numTotal):
Data[:, 1][i] = standardization(attr2[i],mu2,sigma2)
mu3 = np.mean(attr3)
sigma3 = np.std(attr3)
for i in range(0,numTotal):
Data[:, 2][i] = standardization(attr3[i],mu3,sigma3)
mu4 = np.mean(attr4)
sigma4 = np.std(attr4)
for i in range(0,numTotal):
Data[:, 3][i] = standardization(attr4[i],mu4,sigma4)
mu5 = np.mean(attr5)
sigma5 = np.std(attr5)
for i in range(0,numTotal):
Data[:, 4][i] = standardization(attr5[i],mu5,sigma5)
mu6 = np.mean(attr6)
sigma6 = np.std(attr6)
for i in range(0,numTotal):
Data[:, 5][i] = standardization(attr6[i],mu6,sigma6)
return Data
####################################################################################################################
#计算距离
####################################################################################################################
def calDistance(testingrow, trainingrow):
dist = ((float(testingrow[0]) - float(trainingrow[0])) ** 2 + (float(testingrow[1]) - float(trainingrow[1])) ** 2 \
+ (float(testingrow[2]) - float(trainingrow[2])) ** 2 + (float(testingrow[3]) - float(trainingrow[3])) ** 2\
+ (float(testingrow[4]) - float(trainingrow[4])) ** 2 + (float(testingrow[5]) - float(trainingrow[5])) ** 2) ** 0.5
return dist
####################################################################################################################
#K-means
####################################################################################################################
def Kmeans(dataset, k, centers, label):
within = 0 #类内差异
between = 0 #类间差异
numTotal = dataset.shape[0] # 记录测试数据集总条数
attrSum = np.zeros((k,6))
SampleNum = np.linspace(0,0,k)
for i in range(0, numTotal): # 遍历每个样本
distance = [] # 记录该样本和各簇中心的距离
for j in range(0, k):
distance.append(calDistance(dataset[i], centers[j])) # 计算距离
pos = distance.index(min(distance))
within += (min(distance))**2 #计算类内差异
dataset[i][-1] = centers[pos][-1] # 将该样本标记为与其距离最近的簇中心的标签
attrSum[pos] = attrSum[pos] + dataset[i][:-1].astype(float) # 累加该簇的各属性值,为之后求平均做准备
SampleNum[pos] = SampleNum[pos] + 1 #记录各簇的样本个数,为之后求平均做准备
for i in range(0, k): #计算类间差异
for j in range(i, k):
between += (calDistance(centers[i], centers[j]))**2
if((label == dataset[:,-1].astype(float)).all()): # 判断标签有无变化,以判断聚类是否已收敛
return dataset, within, between
else:
next_label = dataset[:,-1].astype(float) # 新的标签
for i in range(0, k):
for j in range(0, 6):
centers[i][j] = attrSum[i][j]/SampleNum[i] #新簇中心的属性值
centers[i][6] = i #新簇中心的标签
next_centers = centers
return Kmeans(dataset, k, next_centers, next_label) #新的一次迭代
####################################################################################################################
#聚类结果可视化
####################################################################################################################
def showData(dataSet):
label_pred = dataSet[:,-1]
mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
# 这里'or'代表中的'o'代表画圈,'r'代表颜色为红色,后面的依次类推
j = 0
for i in label_pred:
plt.plot([float(dataSet[j][0])], [float(dataSet[j][3])], mark[int(i)], markersize=4)
j += 1
plt.show()
####################################################################################################################
k = 8
Data113 = read_xslx(r'e:/Table/机器学习/1123/attribute_113.xlsx')
Data114 = read_xslx(r'e:/Table/机器学习/1123/attribute_114.xlsx')
data = np.vstack((Data113[1:,1:],Data114[1:,1:])) #合并数据集
Data = standardize(data) #数据规范化
numTotal = Data.shape[0] # 记录测试数据集总条数
ram_center = [] #记录初始随机簇中心
pos = random.sample(range(0,numTotal), k) #产生初始随机的簇中心
for i in range(0, k):
Data[pos[i]][-1] = i #簇中心的标签
ram_center.append(Data[pos[i]]) #簇中心的属性值
label = Data[:,-1].astype(float) #初始标签
Data_kmenas, within, between = Kmeans(Data, k, ram_center, label) #调用K-means算法
quality = within/between
print(quality)
showData(Data_kmenas)
运行结果
聚类的质量可以用类内差异和类间差异之比,即 w / b来表示,该值越小,聚类效果越好。
1)k=2时
w / b = 54.438898140288025
2)k=3时
w / b= 13.602127888428452
3)k=4时
w / b= 6.997101980295051
总结
K-means算法是解决聚类问题的一种经典算法,有着简单快速的优点。同时对于处理大数据集,该算法是相对可伸缩和高效率的。此外,当结果簇是密集的时候,算法实现的效果会较好。
但是,K-means算法也有着不少确定缺点。使用该算法一方面要先给出k,一方面也要先选择出随机的初始值,但这个初始值的选择最后会导致不同的聚类结果,也就是说算法对初值敏感。虽然这个问题可以通过多次聚类来缓解,但仍不能根本上解决。此外,该算法也不适合于发现非凸面形状的簇或者大小差别很大的簇。而且,它对于噪声和孤立点数据是敏感的。