引入
聚类
- 是一种无监督学习,将相似的样本(对象/实例)归到同一簇(cluster)中。通常用样本的相似度或距离来衡量。eg:天空中的星星,靠得近的星星可以被归为一个星团,而星团之间的星星距离比较远。
- 簇内的对象越相似,聚类的效果越好。
- 硬聚类:一个样本只能属于一个类。
- 软聚类:一个样本可以以概率属于多个类。
- 聚类与分类的不同:分类为监督,是监督学习,目标事先已知;而聚类的“类”没有预先定义,是从数据中自动发现的,是无监督学习。也就是说,聚类问题中,给我们的样本只用xx,没有yy。
- k-means表示:该算法可以发现k个不同的簇,且每个簇的中心采用簇内所含值的均值计算而成。属于硬聚类。。常见的聚类算法还有:层次聚类。
K-means算法
1967年MacQueen提出
流程
- 选择适当的k。
- 初始化k个质心
- 计算每个样本到质心的距离。
- 按最小距离聚类,将数据集D分配到k个子数据集。
- 迭代以下步骤直至收敛:(假设在第t步)
1)重新计算质心:,求均值。
2)重新计算每个样本到每个质心的距离(遍历样本,扫描质心,两个for循环嵌套)。
3)按最小距离聚类,将数据集DD重新分配到kk个子数据集。
4)如果迭代收敛或符合停止条件(达到最大迭代次数or所有样本分类不再变化),则输出聚类结果。否则t=t+1返回步骤5.1)迭代。
K-means缺点
- K的选择需要事先预定。
- K个初始质心的位置选择对聚类结果和运行时间都有很大影响(K-Means++/biKmeans)。
- 不能保证全局最优,可能是局部最优解。
- 对异常值敏感。(改进1:离群点监测的LOF算法,先去除离群点;改进2:该求K中值法。)
K-means改进
如何确定K?
一、手肘法思想:
随着聚类数K的增大,样本划分更加精细,那么所有样本的聚类误差(SSE)会逐渐变小:
- 当K值小于真实聚类数时,K的增加会对聚类效果产生很大影响,故SSE下降幅度很大;
-
当K值大于真实聚类数时,K的增加不会对聚类效果产生很大影响,故SSE下降幅度将会趋于平缓;整个SSE-K图为一个手肘型。
二、轮廓系数法
- 思想:类中样本距离越近,类间样本距离越远,聚类效果越好。用平均轮廓系数来衡量。
- 类中不相似度:的平均,体现凝聚度。表示样本到同类中其他样本的平均距离。越小,表明类中样本不相似度越低,凝聚度越高,越应该聚为一类。(among)
- 类间不相似度:的最小值,体现分离度。表示样本到其他类中所有样本的平均距离。越大,表明内间不相似程度越高,分离度越高,越不应该聚为一个类。(between)。最近类:
为要找的最近类,x是最近类里的全部样本,n是最近类里的全部样本的个数。
4.某一个样本点xixi的轮廓系数:
1)接近1,则说明样本聚类合理;(理想状况,越小越好,越大越好)
2)接近-1,则说明样本应该分为其他的类;
3)接近0,则说明样本在两个类的边界上。
5.将所有点的轮廓系数求平均,就是该聚类结果的总轮廓系数。选择平均轮廓系数最大对应的K即可。
如何初始化质心
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()