【ML-7】聚类算法--K-means和k-mediods/密度聚类/层次聚类
目录
- 简述
- K-means聚类
- 密度聚类
- 层次聚类
一、简述
聚类算法是常见的无监督学习(无监督学习是在样本的标签未知的情况下,根据样本的内在规律对样本进行分类)。
在监督学习中我们常根据模型的误差来衡量模型的好坏,通过优化损失函数来改善模型。而在聚类算法中是怎么来度量模型的好坏呢?聚类算法模型的性能度量大致有两类:
1)将模型结果与某个参考模型(或者称为外部指标)进行对比,个人觉得认为这种方法用的比较少
2)另一种是直接使用模型的内部属性,比如样本之间的距离(闵可夫斯基距离)来作为评判指标,这类称为内部指标。
其中内部指标如下:
公式解释如下:
距离定义
二、K-means聚类
K-means算法是聚类算法的一种,实现起来比较简单,效果也不错。K-means的思想很简单,对于给定的样本集,根据样本之间距离的大小将样本划分为K个簇(在这里K是需要预先设定好的)
思路:在进行划分簇时要尽量让簇内的样本之间的距离很小,让簇与簇之间的距离尽量大。
在给定的数据集D的条件下,将数据集划分为K类,则K-means的数学模型可以表示:
其中Ci为第i类的集合,μi为第i类的簇心(该簇内所有样本的均值,也称为均值向量)
2.1 算法的流程:
(数据来源 机器学习-周志华版)
主要核心有两步:
- 计算样本到各簇心的距离,将样本划分到距离最小的那一簇中;
- 在将所有的样本都划分完之后,计算各簇心的样本均值向量,然后将该均值向量设为新的簇心。而迭代的终止条件是当前均值向量和簇心是基本一致。
中止条件:迭代次数、最小平方误差MSE、簇中心点变化率
K-Means采用的启发式方式很简单,下图就可以形象的描述过程
2.2 对于K-means有两个可以优化的地方:
1)在初始化时,随机选择K个样本作为初始的簇心,倘若此时随机的结果不好,比如两个随机的簇心挨得很近,这可能会导致模型收敛的速度减慢,常见的解决方式:K-Means++的优化。
- 先随机选取一个样本作为簇心,计算样本到该簇心的距离;
- 随机选择下一个簇心,此时选取时会倾向于选择和与最近簇心之间距离较大的样本作为簇心,直到选择完K个簇心。
2)每次迭代时都要计算所有样本和所有簇心之间的距离,若此时样本很大,或者簇心很多时,计算代价是非常大的。
方法一:Mini Batch K-Means--因此每次随机抽取一小批样本来更新当前的簇心(通过无放回随机采样);
采用小规模的数据子集(每次训练使用的数据集是在训练算法的时候随机抽取的数据子集)减少计算时间,同时试图优化目标函数;Mini Batch K-Means算法可以减少K-Means算法的收敛时间,而且产生的结果效果只是略差于标准K-Means算法
方法二: elkan K-Means--利用了两边之和大于等于第三边,以及两边之差小于第三边的三角形性质,来减少距离的计算。
2.3 K-means算法的优点:
1)原理简单,实现容易,且收敛速度也较快。
2)聚类效果较好,而且可解释性较强。
3)参数很少,需要调的参数只有簇的个数K。
2.4 K-means算法的缺点:
1)K值的选取比较难
2)对于非凸数据集收敛比较难
3)如果隐含类别的数据不平衡,则聚类效果不佳,比如隐含类型的方差不同,方差较大的类中的样本可能会被聚类到其他类别中,在聚类时原则上没啥影响,但是聚类或者说无监督学习大多时候都是一些预训练,聚类后的数据可能之后会被用于其他的分类回归模型中
4)对噪声和异常点比较敏感
5)迭代得到的结果只是局部最优
2.5 二分K-Means算法
解决K-Means算法对初始簇心比较敏感的问题,二分K-Means算法是一种弱化初始质心的一种算法,具体思路步骤如下:
- 将所有样本数据作为一个簇放到一个队列中
- 从队列中选择一个簇进行K-means算法划分,划分为两个子簇,并将子簇添加到队列中
- 循环迭代第二步操作,直到中止条件达到(聚簇数量、最小平方误差、迭代次数等)
- 队列中的簇就是最终的分类簇集合
从队列中选择划分聚簇的规则一般有两种方式;分别如下:
- 对所有簇计算误差和SSE(SSE也可以认为是距离函数的一种变种),选择SSE最大的聚簇进行划分操作(优选这种策略)
- 选择样本数据量最多的簇进行划分操作
2.6 k-medoids
k-medoids 和 k-means 不一样的地方在于中心点的选取,在 k-means 中,我们将中心点取为当前 cluster 中所有数据点的平均值。并且我们已经证明在固定了各个数据点的 assignment 的情况下,这样选取的中心点能够把目标函数 J 最小化。然而在 k-medoids 中,我们将中心点的选取限制在当前 cluster 所包含的数据点的集合中。换句话说,在 k-medoids 算法中,我们将从当前 cluster 中选取这样一个点——它到其他所有(当前 cluster 中的)点的距离之和最小——作为中心点。k-means 和 k-medoids 之间的差异就类似于一个数据样本的均值 (mean) 和中位数 (median) 之间的差异:前者的取值范围可以是连续空间中的任意值,而后者只能在给样本给定的那些点里面选。
二、密度聚类与DBSCAN
DBSCAN(Density-Based Spatial Clustering of Applications with Noise,具有噪声的基于密度的聚类方法)是一种很典型的密度聚类算法,和K-Means,BIRCH这些一般只适用于凸样本集的聚类相比,DBSCAN既可以适用于凸样本集,也可以适用于非凸样本集。
同一类别的样本,他们之间是紧密相连的,通过样本密度来考察样本之间的可连接性,并基于可连接样本不断扩展聚类簇以获得最终的聚类结果。因此密度聚类也是不需要提前设置簇数K的值的。DBSCAN是基于一组领域来描述样本集的紧密程度的。
密度聚类方法的指导思想: 只要样本点的密度大于某个阈值,则将该样本添加到最近的簇中。不太恰当的比喻:类似传销,画圈找点发展下线,比较适合做检测,找到离散点
DBSCAN算法相关的概念:
给定数据集D{x1, x2, ......xn}
1)ϵ- 邻域:对于xi ,其邻域中包含了所有与xi 的距离小于ϵ 的样本
2)核心对象:对于任一样本xj∈D,如果其ϵ-邻域对应的N ϵ (xj)至少包含MinPts个样本,即如果|N ϵ (xj)| ≥ MinPts,则xj是核心对象
3)密度直达:如果xi 位于xj 的ϵ-邻域中,且xj 是核心对象。
4)密度可达:对于xi和xj,如果存在样本样本序列p1, p2,..., pT,满足p1=xi,pT=xj,且pt+1由pt 密度直达,则称xj 由xi 密度可达。也就是说,密度可达满足传递性。此时序列中的传递样本p1,p2,...,pT−1均为核心对象,因为只有核心对象才能使其他样本密度直达。注意密度可达也不满足对称性,这个可以由密度直达的不对称性得出
5)密度相连:对于xi和xj,如果存在核心对象样本xk,使xi和xj均由xk密度可达,则称xi和xj密度相连。注意密度相连关系是满足对称性的.
如下图所示,对于给定的MinPts=3,x1是核心对象,x2由x1密度直达,x3由x1密度可达,x3由x4密度相连。
DBSCAN的核心思想:
有密度可达关系导出的最大的密度相连样本集合。即分类簇C必须满足:
1)连接性:xi∈ C,可以直接推出xi 与xj 密度相连,同簇内的元素必须满足密度相连
2)最大性:xi∈ C,xj 有xi 密度可达可以推出xj ∈C
DBSCAN算法:
数据来源 机器学习-周志华版
1)根据预先给定的(ϵ,MinPts)确定核心对象的集合
2)从核心对象集合中任选一核心对象,找出由其密度可达的对象生成聚类簇
3)从核心对象中除掉已选的核心对象和已分配到簇中的核心对象(这说明在一个簇内肯能存在多个核心对象)
4)重复2,3 步骤,直到所有的核心对象被用完
DBSACN算法理解起来还是比较简单的,但是也存在一些问题:
1)在所有的核心对象都被用完之后,可能还是会存在一些样本点没有被分配到任何簇中,此时我们认为这些样本点是异常点
2)对于有的样本可能会属于多个核心对象,而且这些核心对象不是密度直达的,那么在我们的算法中事实上是采用了先来先到的原则
与K-means算法相比,DBSCAN算法有两大特点
一是不需要预先设定簇数K的值
二是DBSCAN算法同时适用于凸集和非凸集,而K-means只适用于凸集,在非凸集上可能无法收敛。对于DBSCAN算法适用于稠密的数据或者是非凸集的数据,此时DBSCAB算法的效果要比K-means算法好很多。因此若数据不是稠密的,我们一般不用DBSCAN算法。
DBSCAN算法的优点:
1)可以对任意形状的稠密数据进行聚类(包括非凸集)
2)可以在聚类是发现异常点,对异常点不敏感
3)聚类结果比较稳定,不会有什么偏倚,而K-means中初始值对结果有很大的影响
DBSCAN算法的缺点:
1)样本集密度不均匀时,聚类间距相差很大时,聚类效果不佳
2)样本集较大时,收敛时间长,可以用KD树进行最近邻的搜索
3)需要调试的参数比K-means多些,需要去调试ϵ 和MinPts参数(联合调参,不同的组合的结果都不一样)
四、层次聚类和AGNES算法
层次(Hierarchical methods)聚类试图在不同的层次上对数据集进行划分,从而形成树形的聚类结构,传统的层次聚类算法主要分为两大类算法:
- 凝聚的层次聚类:AGNES算法(AGglomerative NESting)==>采用自底向上的策略。最初将每个对象作为一个簇,然后这些簇根据某些准则被一步一步合并,两个簇间的距离可以由这两个不同簇中距离最近的数据点的相似度来确定;聚类的合并过程反复进行直到所有的对象满足簇数目。
- 分裂的层次聚类:DIANA算法(DIvisive ANALysis)==>采用自顶向下的策略。首先将所有对象置于一个簇中,然后按照某种既定的规则逐渐细分为越来越小的簇(比如最大的欧式距离),直到达到某个终结条件(簇数目或者簇距离达到阈值)。
本文重点介绍:AGNES
4.1AGNES的核心思想:
- 先将数据集中的每个样本看作一个初始聚类簇
- 每次迭代时找出距离最近的两个簇进行合并,依次迭代知道簇类的个数达到我们指定的个数K为止。
这种方法的好处是随着簇类数量的减少,需要计算的距离也会越来越少,而且相对K-means,不需要考虑初始化时随机簇心对模型到来的影响。在这里主要有三种计算策略:
4.2 AGNES具体的算法流程:
(1) 将每个对象看作一类,计算两两之间的最小距离;
(2) 将距离最小的两个类合并成一个新类;
(3) 重新计算新类与所有类之间的距离;
(4) 重复(2)、(3),直到所有类最后合并成一类。
数据来源 机器学习-周志华版
优点:
1,距离和规则的相似度容易定义,限制少;
2,不需要预先制定聚类数;
3,可以发现类的层次关系;
4,可以聚类成其它形状
缺点:
2,奇异值也能产生很大影响;
3,算法很可能聚类成链状
关于层次聚类,除了AGNES算法之外,还有BIRCH算法,BIRCH算法适用于数据量大,簇类K的数量较多的情况下,这种算法只需要遍历一遍数据集既可以完成聚类,运行速度很快。BIRCH算法利用了一个类似于B+树的树结构来帮助我们快速聚类,一般我们将它称为聚类特征数(简称CF Tree),BIRCH算法属于自上向下的层次聚类算法(根据数据集的导入自上而下不断的分裂加层来构建CF 树),CF 树中的每个叶节点就对应着一个簇。因此BIRCH算法事实上就是在构建一颗树,构建完之后,树的叶节点就是对应的簇,叶节点中的样本就是每个簇内的样本。BIRCH适用于大样本集,收敛速度快,且不需要设定簇数K的值,但是要设定树的结构约束值(比如叶节点中样本的个数,内节点中样本的个数),此外BIRCH算法对于数据特征维度很大的样本(比如大于20维)不适合。
BIRCH算法(平衡迭代削减聚类法):聚类特征使用3元组进行一个簇的相关信息,通过构建满足分枝因子和簇直径限制的聚类特征树来求聚类,聚类特征树其实是一个具有两个参数分枝因子和类直径的高度平衡树;分枝因子规定了树的每个节点的子女的最多个数,而类直径体现了对这一类点的距离范围;非叶子节点为它子女的最大特征值;聚类特征树的构建可以是动态过程的,可以随时根据数据对模型进行更新操作。
优缺点:
适合大规模数据集,线性效率;
只适合分布呈凸形或者球形的数据集、需要给定聚类个数和簇之间的相关参数;
说明:
1、K-Means和KNN差异和相似:
- 区别:K-Means是无监督学习的聚类算法,没有样本输出;而KNN是监督学习的分类算法,有对应的类别输出。KNN基本不需要训练,对测试集里面的点,只需要找到在训练集中最近的k个点,用这最近的k个点的类别来决定测试点的类别。而K-Means则有明显的训练过程,找到k个类别的最佳质心,从而决定样本的簇类别。
- 相似点:两个算法都包含一个过程,即找出和某一个点最近的点。两者都利用了最近邻(nearest neighbors)的思想。
2、k-Means和K-Medoids
k-Means |
K-Medoids |
初始据点随机选取 |
初始随机据点限定在样本点中 |
取出同一类别的所有样本,求每一列的平均值,得到新的中心向量, 使用Means(均值)作为聚点,对outliers(极值)很敏感. |
遍历中心样本,该中心样本划分出来的该簇样本,遍历该簇样本,找出离所有样本距离最小的样本,代替旧中心。使用Medoids(中位数)作为聚点。 |
中心点不一定是序列上的点 |
中心点一定在序列类,并且距离各点最小 |
对数据要求高,要求数据点处于欧式空间中 |
可适用类别(categorical)类型的特征 |
时间复杂度:O(n*k*t),t为迭代次数 |
时间复杂度:O(n^2 *k*t),t为迭代次数 |
K-Means 算法对小规模数据集较高效(efficient for smaller data sets) |
K-Medoids算法对大规模数据性能更好,但伸缩性较差 |
相同点 |
|
都有可能陷入局部最优解的困境之中 |
|
K的含义相同,都需要开始人为设定簇数目 |
|
都是无监督算法 |
3、k-Means和K-Medoids的算法实现:
k-Means:
1 # Author:yifan 2 #K-means 3 from numpy import * 4 import time 5 import matplotlib.pyplot as plt 6 import numpy as np 7 8 # euclDistance函数计算两个向量之间的欧氏距离 9 def euclDistance(vector1, vector2): 10 return sqrt(sum(power(vector2 - vector1, 2))) 11 # initCentroids选取任意数据集中任意样本点作为初始均值点 12 # dataSet: 数据集, k: 人为设定的聚类簇数目 13 # centroids: 随机选取的初始均值点 14 def initCentroids(dataSet, k): 15 numSamples, dim = dataSet.shape 16 centroids = zeros((k, dim)) #k行dim的0矩阵 17 for i in range(k): 18 index = int(random.uniform(0, numSamples)) #从0到numSamples中随机选取一个数字 19 centroids[i, :] = dataSet[index, :] #随机选出k个数字,即为本函数的目的 20 # print(centroids) 21 return centroids 22 23 # kmeans: k-means聚类功能主函数 24 # 输入:dataSet-数据集,k-人为设定的聚类簇数目 25 # 输出:centroids-k个聚类簇的均值点,clusterAssment-聚类簇中的数据点 26 def kmeans(dataSet, k): 27 numSamples = dataSet.shape[0] 28 clusterAssment = mat(zeros((numSamples, 2))) 29 # clusterAssment第一列存储当前点所在的簇 30 # clusterAssment第二列存储点与质心点的距离 31 clusterChanged = True #用于遍历的标记 32 ## 步骤一: 初始化均值点 33 centroids = initCentroids(dataSet, k) 34 while clusterChanged: 35 clusterChanged = False 36 ## 遍历每一个样本点 37 for i in range(numSamples): 38 minDist = 100000.0 # minDist:最近距离,初始定一个较大的值 39 minIndex = 0 # minIndex:最近的均值点编号 40 ## 步骤二: 寻找最近的均值点 41 for j in range(k): 42 distance = euclDistance(centroids[j, :], dataSet[i, :]) #每个点和中心点的距离,共有k个值 43 if distance < minDist: 44 #循环去找最小的那个 45 minDist = distance 46 minIndex = j 47 ## 步骤三: 更新所属簇 48 if clusterAssment[i, 0] != minIndex: 49 clusterChanged = True 50 clusterAssment[i, :] = minIndex, minDist**2 #记录序号和点与质心点的距离 51 ## 步骤四: 更新簇的均值点 52 for j in range(k): 53 pointsInCluster = dataSet[nonzero(clusterAssment[:, 0] == j)[0]] #当前属于j类的序号 54 print(pointsInCluster) 55 print('ddddd') 56 # print(clusterAssment[:, 0]) 57 centroids[j, :] = mean(pointsInCluster, axis = 0) #按照 列计算均值 58 print ('Congratulations, cluster complete!') 59 return centroids, clusterAssment 60 61 # showCluster利用pyplot绘图显示聚类结果(二维平面) 62 # 输入:dataSet-数据集,k-聚类簇数目,centroids-聚类簇的均值点,clusterAssment-聚类簇中数据点 63 def showCluster(dataSet, k, centroids, clusterAssment): 64 numSamples, dim = dataSet.shape 65 if dim != 2: 66 print ("Sorry, the dimension of your data is not 2!") 67 return 1 68 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr'] 69 if k > len(mark): 70 return 1 71 # 画出所有的样本点 72 for i in range(numSamples): 73 markIndex = int(clusterAssment[i, 0]) 74 plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex]) 75 mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb'] 76 # 标记簇的质心 77 for i in range(k): 78 plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12) 79 plt.show() 80 81 82 ## step 1: 构造数据 83 matrix1=np.random.random((12,2)) 84 matrix2=np.random.random((12,2)) 85 matrix3=np.random.random((12,2)) 86 matrix4=np.random.random((12,2)) 87 for i in range(12): 88 matrix2[i,0] = matrix2[i,0]+2 89 matrix3[i,1] = matrix3[i,1]+2 90 matrix4[i,:] = matrix4[i,:]+2 91 dataSet = np.vstack((matrix1,matrix2,matrix3,matrix4)) 92 # print(dataSet) 93 ## step 2: 开始聚类... 94 # dataSet = mat(dataSet) 95 k = 4 centroids, clusterAssment = kmeans(dataSet, k) ## step 3: 显示聚类结果 showCluster(dataSet, k, centroids, clusterAssment)
K-Medoids:
1 # Author:yifan 2 from numpy import * 3 import time 4 import matplotlib.pyplot as plt 5 import numpy as np 6 7 # euclDistance函数计算两个向量之间的欧氏距离 8 def euclDistance(vector1, vector2): 9 return sqrt(sum(power(vector2 - vector1, 2))) 10 # initCentroids选取任意数据集中任意样本点作为初始均值点 11 # dataSet: 数据集, k: 人为设定的聚类簇数目 12 # centroids: 随机选取的初始均值点 13 def initCentroids(dataSet, k): 14 numSamples, dim = dataSet.shape 15 centroids = zeros((k, dim)) #k行dim的0矩阵 16 for i in range(k): 17 index = int(random.uniform(0, numSamples)) #从0到numSamples中随机选取一个数字 18 centroids[i, :] = dataSet[index, :] #随机选出k个数字,即为本函数的目的 19 # print(centroids) 20 return centroids 21 #定义每个点到其他点的距离和。步骤四的更新簇的均值点时会用到 22 def costsum( vector1,matrix1): 23 sum = 0 24 for i in range(matrix1.shape[0]): 25 sum += euclDistance(matrix1[i,:], vector1) 26 return sum 27 # kmediod: k-mediod聚类功能主函数 28 # 输入:dataSet-数据集,k-人为设定的聚类簇数目 29 # 输出:centroids-k个聚类簇的均值点,clusterAssment-聚类簇中的数据点 30 def kmediod(dataSet, k): 31 numSamples = dataSet.shape[0] 32 clusterAssment = mat(zeros((numSamples, 2))) 33 # clusterAssment第一列存储当前点所在的簇 34 # clusterAssment第二列存储点与质心点的距离 35 clusterChanged = True #用于遍历的标记 36 ## 步骤一: 初始化均值点 37 centroids = initCentroids(dataSet, k) 38 while clusterChanged: 39 clusterChanged = False 40 ## 遍历每一个样本点 41 for i in range(numSamples): 42 minDist = 100000.0 # minDist:最近距离,初始定一个较大的值 43 minIndex = 0 # minIndex:最近的均值点编号 44 ## 步骤二: 寻找最近的均值点 45 for j in range(k): 46 distance = euclDistance(centroids[j, :], dataSet[i, :]) #每个点和中心点的距离,共有k个值 47 if distance < minDist: 48 #循环去找最小的那个 49 minDist = distance 50 minIndex = j 51 ## 步骤三: 更新所属簇 52 if clusterAssment[i, 0] != minIndex: 53 clusterChanged = True 54 clusterAssment[i, :] = minIndex, minDist**2 #记录序号和点与质心点的距离 55 ## 步骤四: 更新簇核心点 56 for j in range(k): 57 pointsInCluster = dataSet[nonzero(clusterAssment[:, 0] == j)[0]] #当前属于j类的序号 58 mincostsum = costsum(centroids[j,:],pointsInCluster) 59 for point in range(pointsInCluster.shape[0]): 60 cost = costsum( pointsInCluster[point, :],pointsInCluster) 61 if cost < mincostsum: 62 mincostsum = cost 63 centroids[j, :] = pointsInCluster[point, :] 64 print ('Congratulations, cluster complete!') 65 return centroids, clusterAssment 66 67 # showCluster利用pyplot绘图显示聚类结果(二维平面) 68 # 输入:dataSet-数据集,k-聚类簇数目,centroids-聚类簇的均值点,clusterAssment-聚类簇中数据点 69 def showCluster(dataSet, k, centroids, clusterAssment): 70 numSamples, dim = dataSet.shape 71 if dim != 2: 72 print ("Sorry, the dimension of your data is not 2!") 73 return 1 74 mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr'] 75 if k > len(mark): 76 return 1 77 # 画出所有的样本点 78 for i in range(numSamples): 79 markIndex = int(clusterAssment[i, 0]) 80 plt.plot(dataSet[i, 0], dataSet[i, 1], mark[markIndex]) 81 mark = ['Dr', 'Db', 'Dg', 'Dk', '^b', '+b', 'sb', 'db', '<b', 'pb'] 82 # 标记簇的质心 83 for i in range(k): 84 plt.plot(centroids[i, 0], centroids[i, 1], mark[i], markersize = 12) 85 plt.show() 86 87 88 ## step 1: 构造数据 89 matrix1=np.random.random((12,2)) 90 matrix2=np.random.random((12,2)) 91 matrix3=np.random.random((12,2)) 92 matrix4=np.random.random((12,2)) 93 for i in range(12): 94 matrix2[i,0] = matrix2[i,0]+2 95 matrix3[i,1] = matrix3[i,1]+2 96 matrix4[i,:] = matrix4[i,:]+2 97 dataSet = np.vstack((matrix1,matrix2,matrix3,matrix4)) 98 # print(dataSet) 99 ## step 2: 开始聚类... 100 # dataSet = mat(dataSet) 101 k = 4 102 centroids, clusterAssment = kmediod(dataSet, k) ## step 3: 显示聚类结果 showCluster(dataSet, k, centroids, clusterAssment)
结果均为;
附件一:手写推导过程练习