引入
聚类是一种无监督学习,将相似的样本(对象/实例)归到同一簇(cluster)中。通常用样本的相似度或距离来衡量。eg:天空中的星星,靠得近的星星可以被归为一个星团,而星团之间的星星距离比较远。(CheungRN:聚类算法中常见的几种距离zhuanlan.zhihu.com
)簇内的对象越相似,聚类的效果越好。
硬聚类:一个样本只能属于一个类。
软聚类:一个样本可以以概率属于多个类。
聚类与分类的不同:分类为监督,是监督学习,目标事先已知;而聚类的“类”没有预先定义,是从数据中自动发现的,是无监督学习。也就是说,聚类问题中,给我们的样本只用x,没有y。
k-means表示:该算法可以发现k个不同的簇,且每个簇的中心采用簇内所含值的均值计算而成。属于硬聚类。。常见的聚类算法还有:层次聚类。
K-means算法
1967年MacQueen提出
流程
K-means缺点K的选择需要事先预定。
K个初始质心的位置选择对聚类结果和运行时间都有很大影响。
不能保证全局最优,可能是局部最优解。
K-means改进
如何确定K?
一、手肘法思想:随着聚类数K的增大,样本划分更加精细,那么所有样本的聚类误差(SSE)会逐渐变小:
——当K值小于真实聚类数时,K的增加会对聚类效果产生很大影响,故SSE下降幅度很大;
——当K值大于真实聚类数时,K的增加不会对聚类效果产生很大影响,故SSE下降幅度将会趋于平缓;整个SSE-K图为一个手肘型。
二、轮廓系数法思想:类中样本距离越近,类间样本距离越远,聚类效果越好。用平均轮廓系数来衡量。
类中不相似度:ai的平均,体现凝聚度。ai表示样本xi到同类中其他样本的平均距离。ai越小,表明类中样本不相似度越低,凝聚度越高,越应该聚为一类。(among)
类间不相似度:bi的最小值,体现分离度。bi表示样本xi到其他类中所有样本的平均距离。bi越大,表明内间不相似程度越高,分离度越高,越不应该聚为一个类。(between)。最近类:
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/BV1Ta4y1Y7C5www.bilibili.com
参考文献