kmeans python自定义初始聚类中心_聚类算法——K-Means原理、k选择、质心初始化、Python实现...

本文详细介绍了K-Means聚类算法,包括其原理、流程、缺点以及改进方法。通过Python实现展示了K-Means算法过程,并通过实例解释了如何选择初始质心、如何确定最佳K值。文章还探讨了K-Means++的优化策略,用于避免局部最优解。最后,讨论了K-Means在实际应用中可能遇到的问题,如不同初始点导致的聚类结果差异。
摘要由CSDN通过智能技术生成

引入

聚类是一种无监督学习,将相似的样本(对象/实例)归到同一簇(cluster)中。通常用样本的相似度或距离来衡量。eg:天空中的星星,靠得近的星星可以被归为一个星团,而星团之间的星星距离比较远。(CheungRN:聚类算法中常见的几种距离​zhuanlan.zhihu.comae308529ad4c92fdcc492898a5875882.png

)簇内的对象越相似,聚类的效果越好。

硬聚类:一个样本只能属于一个类。

软聚类:一个样本可以以概率属于多个类。

聚类与分类的不同:分类为监督,是监督学习,目标事先已知;而聚类的“类”没有预先定义,是从数据中自动发现的,是无监督学习。也就是说,聚类问题中,给我们的样本只用x,没有y。

k-means表示:该算法可以发现k个不同的簇,且每个簇的中心采用簇内所含值的均值计算而成。属于硬聚类。。常见的聚类算法还有:层次聚类。

K-means算法

1967年MacQueen提出

流程

K-means缺点K的选择需要事先预定。

K个初始质心的位置选择对聚类结果和运行时间都有很大影响。

不能保证全局最优,可能是局部最优解。

K-means改进

如何确定K?

一、手肘法思想:随着聚类数K的增大,样本划分更加精细,那么所有样本的聚类误差(SSE)会逐渐变小:

equation?tex=SSE%3D%5Csum_%7Bi%3D1%7D%5Ek%5Csum_%7Bx%5Cin+D_i%7D%7Cx-%5Cmu_i%7C%5E2

——当K值小于真实聚类数时,K的增加会对聚类效果产生很大影响,故SSE下降幅度很大;

——当K值大于真实聚类数时,K的增加不会对聚类效果产生很大影响,故SSE下降幅度将会趋于平缓;整个SSE-K图为一个手肘型。

二、轮廓系数法思想:类中样本距离越近,类间样本距离越远,聚类效果越好。用平均轮廓系数来衡量。

类中不相似度:ai的平均,体现凝聚度。ai表示样本xi到同类中其他样本的平均距离。ai越小,表明类中样本不相似度越低,凝聚度越高,越应该聚为一类。(among)

类间不相似度:bi的最小值,体现分离度。bi表示样本xi到其他类中所有样本的平均距离。bi越大,表明内间不相似程度越高,分离度越高,越不应该聚为一个类。(between)。最近类:

equation?tex=C_j%3D%5Cmin%5Climits_%7BD_k%7D%5Cfrac%7B1%7D%7Bn%7D%5Csum_%7Bx%5Cin+D_k%7D%7Cx-x_i%7C%5E2

Dk为要找的最近类,x是最近类里的全部样本,n是最近类里的全部样本的个数。某一个样本点xi的轮廓系数:

选SSE还是轮廓系数?

如何初始化质心

K-means++

随机初始化质心可能导致算法迭代很慢,K-means++是对K-mean随机初始化质心的一个优化,具体步骤如下:随机选取一个点作为第一个聚类中心。

计算所有样本与第一个聚类中心的距离。

选择出上一步中距离最大的点作为第二个聚类中心。

迭代:计算所有点到与之最近的聚类中心的距离,选取最大距离的点作为新的聚类中心。

终止条件:直到选出了这k个中心。

只需要随机取第一个聚类中心即可。

然后按照最远优先原则来选新的聚类中心

如何克服局部最优解

K-means实例应用——Python实现

import numpy as np

def loadDataSet(fileName):

''':param fileName: 文件名字:return: 矩阵'''

dataMat = []

fr = open(fileName)

for line in fr.readlines():

curLine = line.strip().split('\t')

fltLine = list(map(float, curLine))

dataMat.append(fltLine)

return dataMat

def distEclud(vecA, vecB):

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

def randCent(dataSet, k):

'''构建一个包含k个随机质心的集合(k行n列,n表示数据的维度/特征个数),只需要保证质心在数据边界里面就可以了:param dataSet: 输入数据集:param k: 质心个数:return:'''

# 得到数据样本的维度

n = np.shape(dataSet)[1]

# 初始化为一个(k,n)的全零矩阵

centroids = np.mat(np.zeros((k, n)))

# 遍历数据集的每一个维度

for j in range(n):

# 得到该列数据的最小值,最大值

minJ = np.min(dataSet[:, j])

maxJ = np.max(dataSet[:, j])

# 得到该列数据的范围(最大值-最小值)

rangeJ = float(maxJ - minJ)

# k个质心向量的第j维数据值随机为位于(最小值,最大值)内的某一值

centroids[:, j] = minJ + rangeJ * np.random.rand(k, 1) # random.rand(行,列)产生这个形状的矩阵,且每个元素in [0,1)

# 返回初始化得到的k个质心向量

return centroids

def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):

''':param dataSet: 输入的数据集:param k: 聚类的个数,可调:param distMeas: 计算距离的方法,可调:param createCent: 初始化质心的位置的方法,可调:return: k个类质心的位置坐标,样本所处的类&到该类质心的距离'''

# 获取数据集样本数

m = np.shape(dataSet)[0]

# 初始化一个(m,2)全零矩阵,用来记录没一个样本所属类,距离类中心的距离

clusterAssment = np.mat(np.zeros((m, 2)))

# 创建初始的k个质心向量

centroids = createCent(dataSet, k)

# 聚类结果是否发生变化的布尔类型

clusterChanged = True

# 终止条件:所有数据点聚类结果不发生变化

while clusterChanged:

# 聚类结果变化布尔类型置为False

clusterChanged = False

# 遍历数据集每一个样本向量

for i in range(m):

# 初始化最小距离为正无穷,最小距离对应的索引为-1

minDist = float('inf')

minIndex = -1

# 循环k个类的质心

for j in range(k):

# 计算数据点到质心的欧氏距离

distJI = distMeas(centroids[j, :], dataSet[i, :])

# 如果距离小于当前最小距离

if distJI < minDist:

# 当前距离为最小距离,最小距离对应索引应为j(第j个类)

minDist = distJI

minIndex = j

# 当前聚类结果中第i个样本的聚类结果发生变化:布尔值置为True,继续聚类算法

if clusterAssment[i, 0] != minIndex:

clusterChanged = True

# 更新当前变化样本的聚类结果和平方误差

clusterAssment[i, :] = minIndex, minDist**2

# 打印k-means聚类的质心

# print(centroids)

# 遍历每一个质心

for cent in range(k):

# 将数据集中所有属于当前质心类的样本通过条件过滤筛选出来

ptsInClust = dataSet[np.nonzero(clusterAssment[:, 0].A == cent)[0]]

# 计算这些数据的均值(axis=0:求列均值),作为该类质心向量

centroids[cent, :] = np.mean(ptsInClust, axis=0)

# 返回k个聚类,聚类结果及误差

return centroids, clusterAssment

import matplotlib.pyplot as plt

def plotDataSet(filename):

# 导入数据

datMat = np.mat(loadDataSet(filename))

# 进行k-means算法其中k为4

myCentroids, clustAssing = kMeans(datMat, 4)

clustAssing = clustAssing.tolist()

myCentroids = myCentroids.tolist()

xcord = [[], [], [], []]

ycord = [[], [], [], []]

datMat = datMat.tolist()

m = len(clustAssing)

for i in range(m):

if int(clustAssing[i][0]) == 0:

xcord[0].append(datMat[i][0])

ycord[0].append(datMat[i][1])

elif int(clustAssing[i][0]) == 1:

xcord[1].append(datMat[i][0])

ycord[1].append(datMat[i][1])

elif int(clustAssing[i][0]) == 2:

xcord[2].append(datMat[i][0])

ycord[2].append(datMat[i][1])

elif int(clustAssing[i][0]) == 3:

xcord[3].append(datMat[i][0])

ycord[3].append(datMat[i][1])

fig = plt.figure()

ax = fig.add_subplot(111)

# 绘制样本点

ax.scatter(xcord[0], ycord[0], s=20, c='b', marker='*', alpha=.5)

ax.scatter(xcord[1], ycord[1], s=20, c='r', marker='D', alpha=.5)

ax.scatter(xcord[2], ycord[2], s=20, c='c', marker='>', alpha=.5)

ax.scatter(xcord[3], ycord[3], s=20, c='k', marker='o', alpha=.5)

# 绘制质心

ax.scatter(myCentroids[0][0], myCentroids[0][1], s=100, c='k', marker='+', alpha=.5)

ax.scatter(myCentroids[1][0], myCentroids[1][1], s=100, c='k', marker='+', alpha=.5)

ax.scatter(myCentroids[2][0], myCentroids[2][1], s=100, c='k', marker='+', alpha=.5)

ax.scatter(myCentroids[3][0], myCentroids[3][1], s=100, c='k', marker='+', alpha=.5)

plt.title('DataSet')

plt.xlabel('X')

plt.show()

---------------------------------【补充】调用代码和数据集---------------------------------

# 测试一下函数

dataMat = np.mat(loadDataSet('testSet.txt'))

print('min x:%f' % min(dataMat[:, 0]))

print('min y:%f' % min(dataMat[:, 1]))

print('max x:%f' % max(dataMat[:, 0]))

print('man y:%f' % max(dataMat[:, 1]))

for i in range(10):

testRandCent = randCent(dataMat, 2)

print('No%drandom center is ' % i)

print(testRandCent)

# 测试欧几里得距离函数

print(distEclud(dataMat[0], dataMat[1]))

myCentroids, clustAssing = kMeans(dataMat, 4)

print(myCentroids)

print(clustAssing)

dataMat2 = np.mat(loadDataSet('testSet2.txt'))

myCentroids2, clustAssing2 = kMeans(dataMat2, 4)

print(myCentroids2)

print(clustAssing2)

print('SSE of simple k-means:%f' % sum(clustAssing2[:, 1]))

sumSSE = 0

for i in range(40):

myCentroids2, clustAssing2 = kMeans(dataMat2, 4)

sumSSE += sum(clustAssing2[:, 1])

avgSSE = sumSSE/40

print('Avg(40) SSE of simple k-means:%f' % avgSSE)

plotDataSet('testSet.txt')

plotDataSet('testSet2.txt')

testSet.txt

1.6589854.285136

-3.4536873.424321

4.838138-1.151539

-5.379713-3.362104

0.9725642.924086

-3.5679191.531611

0.450614-3.302219

-3.487105-1.724432

2.6687591.594842

-3.1564853.191137

3.165506-3.999838

-2.786837-3.099354

4.2081872.984927

-2.1233372.943366

0.704199-0.479481

-0.392370-3.963704

2.8316671.574018

-0.7901533.343144

2.943496-3.357075

-3.195883-2.283926

2.3364452.875106

-1.7863452.554248

2.190101-1.906020

-3.403367-2.778288

1.7781243.880832

-1.6883462.230267

2.592976-2.054368

-4.007257-3.207066

2.2577343.387564

-2.6790110.785119

0.939512-4.023563

-3.674424-2.261084

2.0462592.735279

-3.1894701.780269

4.372646-0.822248

-2.579316-3.497576

1.8890345.190400

-0.7987472.185588

2.836520-2.658556

-3.837877-3.253815

2.0967013.886007

-2.7090342.923887

3.367037-3.184789

-2.121479-4.232586

2.3295463.179764

-3.2848163.273099

3.091414-3.815232

-3.762093-2.432191

3.5420562.778832

-1.7368224.241041

2.127073-2.983680

-4.323818-3.938116

3.7921215.135768

-4.7864733.358547

2.624081-3.260715

-4.009299-2.978115

2.4935251.963710

-2.5136612.642162

1.864375-3.176309

-3.171184-3.572452

2.8942202.489128

-2.5625392.884438

3.491078-3.947487

-2.565729-2.012114

3.3329483.983102

-1.6168053.573188

2.280615-2.559444

-2.651229-3.103198

2.3213953.154987

-1.6857032.939697

3.031012-3.620252

-4.599622-2.185829

4.1962231.126677

-2.1338633.093686

4.668892-2.562705

-2.793241-2.149706

2.8841053.043438

-2.9676472.848696

4.479332-1.764772

-4.905566-2.911070

testSet2.txt

3.2751542.957587

-3.3444652.603513

0.355083-3.376585

1.8524353.547351

-2.0789732.552013

-0.993756-0.884433

2.6822524.007573

-3.0877762.878713

-1.565978-1.256985

2.4416110.444826

-0.6594873.111284

-0.459601-2.618005

2.1776802.387793

-2.9209692.917485

-0.028814-4.168078

3.6257462.119041

-3.9123631.325108

-0.551694-2.814223

2.8558083.483301

-3.5944482.856651

0.421993-2.372646

1.6508213.407572

-2.0829023.384412

-0.718809-2.492514

4.5136233.841029

-4.8220114.607049

-0.656297-1.449872

1.9199014.439368

-3.2877493.918836

-1.576936-2.977622

3.5981431.975970

-3.9773294.900932

-1.791080-2.184517

3.9146543.559303

-1.9101084.166946

-1.226597-3.317889

1.1489463.345138

-2.1138643.548172

0.845762-3.589788

2.6290623.535831

-1.6407172.990517

-1.881012-2.485405

4.6069993.510312

-4.3664624.023316

0.765015-3.001270

3.1219042.173988

-4.0251394.652310

-0.559558-3.840539

4.3767544.863579

-1.8743084.032237

-0.089337-3.026809

3.9977872.518662

-3.0829782.884822

0.845235-3.454465

1.3272243.358778

-2.8899493.596178

-0.966018-2.839827

2.9607693.079555

-3.2755181.577068

0.639276-3.412840

这个数据集比较理想,聚类情况比较好。但是对于有些数据集,由于K值选择、初始点随机选择这些很有可能导致算法产生局部最优解。

这一副图通过同一个数据集,多次调用上述K-means算法,但是产生了不同的效果。

一些问题初始设置k=4,为什么到了后面怎么只聚成了3个类?

为什么有些大一点的cluster分成了两类?有些是一类?

都是因为随机初始点造成的!!

接下来需要解决的问题(见下一篇文章)怎么避免由于随机化初始点而造成的局部最优解问题?上面有提到K-means++是一种解决办法。

如何实现选取最佳K的代码?

讲解视频请移步:https://www.bilibili.com/video/BV1Ta4y1Y7C5​www.bilibili.com

参考文献

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值