机器学习之聚类
聚类是一种无监督学习,它将相似的对象归到同一个簇中。聚类和分类的最大不同在于,分类的目标事先已知,而聚类的结果类别没有预先定义,聚类和分类的结果相同。
在无监督学习中,训练样本的标记信息是未知的,目标是通过对无标记的训练样本的学习来揭示数据的内在性质和规律,如数据的聚类特征。聚类试图将数据集中的样本划分为若干个不相交的子集,每个子集称为一个簇。好的聚类结果呈现同一簇的样本尽可能彼此相似,不同簇的样本尽可能不同,换言之,即聚类分析试图将相似对象归入同一簇,将不相似对象归到不同簇。
几种聚类算法:
K均值算法:
对于一个样本集,假定聚类数为3,算法开始,随机选取三个样本作为初始均值向量,计算所有数据样本与这三个样本的距离,根据最短距离的原理将当前所有数据样本划分为三个簇。从当前计算的三个簇中计算新的均值向量,更新初始的三个样本均值,并遍历所有样本数据,计算所有样本数据与当前均值的距离。就这样不断迭代,直到最后两轮的结果相同,即停止计算。得到最终的簇划分。
学习向量量化
学习向量量化LVQ,是假设数据样本带有类别标记。学习的过程利用样本的这些监督信息来辅助聚类。给定一个带有标记的样本集,算法随机选取一个有标记的训练样本,找出与其距离最近的原型向量,并根据两者的类别标记是否一致来对原型向量进行相应的更新。若xj与原型向量pi最接近,则更新原型向量pi使xj与之更为靠拢,即新的原型向量与xj更接近,类似的,若pi与xj的类别标记不同,则更新后的原型向量pi与xj更加远离。直到算法达到最大迭代轮数或原型向量更新很小甚至不再更新为止,这时对任意样本x,它将被划入与其距离最近的原型向量代表的簇中,由此形成了对样本空间R的簇划分。
高斯混合聚类
与K均值、LVQ用原型向量来刻画聚类结构不同,高斯混合聚类采用概率模型来表达聚类原型。根据被选择的混合成分的概率密度函数进行采样,生成相应的样本。当高斯分布已知时,先计算每个样本属于每个高斯成分的后验概率,然后通过更新模型参数(均值向量、协方差矩阵、混合系数),根据以上步骤进行迭代,直到算法达到最大迭代轮数或似然函数增长很少甚至不再增长为止。
密度聚类
此算法假定聚类结构能通过样本分布的紧密程度确定。首先找出样本的邻域并确定核心对象集合A,从该集合中任选样本集中的一个核心对象作为种子,由此出发确定相应的聚类簇C,然后将A中包含的C从A中去除,再从更新过后的A中找出新的核心对象作为种子生成下一个聚类簇,直到A为空。
层次聚类
层次聚类试图在不同层次对数据集进行划分,从而形成树形的聚类结构,AGNES是一种采用自底而上聚合策略的层次聚类算法,它先将数据集中的每个样本看做一个初始聚类簇,然后在算法运行的每一步中找到距离最近的两个聚类簇进行合并,该过程不断重复,直至达到预设的聚类簇个数(k可以定义4,5,6,7等)。
下面就K-均值聚类算法做实战分析
K-均值聚类
优点:容易实现
缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。
适用数据类型:数值型数据
K-均值算法的伪代码:
创建k个点作为起始质心(经常是随机选择)
当任意一个点的簇分配结果发生改变时
对数据集中的每个数据点
对每个质心
计算质心与数据点之间的距离
将数据点分配到距其最近的簇
对每个簇,计算簇中所有点的均值并将均值作为质心
首先我们介绍几个K-均值算法中要用到的辅助函数和K-均值算法:
from numpy import * #将文本文件导入到一个 def loadDataSet(fileName): 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 sqrt(sum(power(vecA - vecB, 2))) #构建一个包含k个质心的集合 def randCent(dataSet, k): n = shape(dataSet)[1] centroids = mat(zeros((k,n))) #构建簇质心 for j in range(n): minJ = min(dataSet[:,j]) rangeJ = float(max(dataSet[:,j]) - minJ) centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) return centroids #看一下这三个函数的实际效果 #导入二维矩阵的样例 datMat = mat(loadDataSet('F:\\python入门\\文件\\machinelearninginaction\\Ch10\\testSet.txt')) #测试样例数据是否成功导入 #取第一列的最小值 first_min = min(datMat[:,0]) print('第一列的最小值为:',first_min) #取第一列的最大值 first_max = max(datMat[:,0]) print('第一列的最大值为:',first_max) #取第二列的最小值 second_min = min(datMat[:,1]) print('第二列的最小值为:',second_min) #取第二列的最大值 second_max = max(datMat[:,1]) print('第二列的最大值为:',second_max) #测试randCent()函数 random_data = randCent(datMat,2) print('生成的随机数在min和max之间:\n',random_data) #测试distEclud()函数,datMat[0]为第一行数据,datMat[1]为第二行数据 dist_1 = distEclud(datMat[0],datMat[1]) print("两个点:",datMat[0],",",datMat[1]) print('计算两个点之间的欧式距离:',dist_1) #K-均值聚类算法 #kMeans()函数接受了4个输入参数,只有数据集和簇的数目是必选的。 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): #m为样本数据集数据点的总数 m = shape(dataSet)[0] #clusterAssment为簇分配结果矩阵,包含两列,一列记录簇索引值,第二列存储误差(当前点到簇质心的距离) clusterAssment = mat(zeros((m,2))) centroids = createCent(dataSet, k) clusterChanged = True while clusterChanged: #创建一个标志变量,如果该值为true,则继续迭代 clusterChanged = False #遍历所有数据找到距离每个点最近的质心 for i in range(m): minDist = inf; minIndex = -1 for j in range(k): distJI = distMeas(centroids[j,:],dataSet[i,:]) if distJI < minDist: minDist = distJI; minIndex = j if clusterAssment[i,0] != minIndex: clusterChanged = True clusterAssment[i,:] = minIndex,minDist**2 #print(centroids) #更新质心的位置 for cent in range(k): #通过数组过虑来获取给定簇的所有点 ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] #计算所有点的均值,axis=0为列方向进行均值计算 centroids[cent,:] = mean(ptsInClust, axis=0) #返回所有的类质心与点的分配结果 return centroids, clusterAssment #K-均值算法的调用 myCentroids,clustAssing = kMeans(datMat,4) print('四个质心分别为:\n',myCentroids) print('记录每行数据聚类的结果(簇索引值,当前点到簇质心的距离):\n',clustAssing)
结果:
第一列的最小值为: [[-5.379713]] 第一列的最大值为: [[4.838138]] 第二列的最小值为: [[-4.232586]] 第二列的最大值为: [[5.1904]] 生成的随机数在min和max之间: [[ 0.31629197 1.34820276] [-4.47610482 1.38931651]] 两个点: [[1.658985 4.285136]] , [[-3.453687 3.424321]] 计算两个点之间的欧式距离: 5.184632816681332 四个质心分别为: [[ 2.65077367 -2.79019029] [-3.53973889 -2.89384326] [-2.46154315 2.78737555] [ 2.6265299 3.10868015]] 记录每行数据聚类的结果(簇索引值,当前点到簇质心的距离): [[ 3. 2.3201915 ] [ 2. 1.39004893] [ 0. 7.46974076] [ 1. 3.60477283] [ 3. 2.7696782 ] [ 2. 2.80101213] [ 0. 5.10287596] [ 1. 1.37029303] [ 3. 2.29348924] [ 2. 0.64596748] [ 0. 1.72819697] [ 1. 0.60909593] [ 3. 2.51695402] [ 2. 0.13871642] [ 0. 9.12853034] [ 0. 10.63785781] [ 3. 2.39726914] [ 2. 3.1024236 ] [ 0. 0.40704464] [ 1. 0.49023594] [ 3. 0.13870613] [ 2. 0.510241 ] [ 0. 0.9939764 ] [ 1. 0.03195031] [ 3. 1.31601105] [ 2. 0.90820377] [ 0. 0.54477501] [ 1. 0.31668166] [ 3. 0.21378662] [ 2. 4.05632356] [ 0. 4.44962474] [ 1. 0.41852436] [ 3. 0.47614274] [ 2. 1.5441411 ] [ 0. 6.83764117] [ 1. 1.28690535] [ 3. 4.87745774] [ 2. 3.12703929] [ 0. 0.05182929] [ 1. 0.21846598] [ 3. 0.8849557 ] [ 2. 0.0798871 ] [ 0. 0.66874131] [ 1. 3.80369324] [ 3. 0.09325235] [ 2. 0.91370546] [ 0. 1.24487442] [ 1. 0.26256416] [ 3. 0.94698784] [ 2. 2.63836399] [ 0. 0.31170066] [ 1. 1.70528559] [ 3. 5.46768776] [ 2. 5.73153563] [ 0. 0.22210601] [ 1. 0.22758842] [ 3. 1.32864695] [ 2. 0.02380325] [ 0. 0.76751052] [ 1. 0.59634253] [ 3. 0.45550286] [ 2. 0.01962128] [ 0. 2.04544706] [ 1. 1.72614177] [ 3. 1.2636401 ] [ 2. 1.33108375] [ 0. 0.19026129] [ 1. 0.83327924] [ 3. 0.09525163] [ 2. 0.62512976] [ 0. 0.83358364] [ 1. 1.62463639] [ 3. 6.39227291] [ 2. 0.20120037] [ 0. 4.12455116] [ 1. 1.11099937] [ 3. 0.07060147] [ 2. 0.2599013 ] [ 0. 4.39510824] [ 1. 1.86578044]]
二分K-均值算法
为了克服K-均值算法收敛于局部最小值问题,有人提出了另一个称为二分K-均值的算法。该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。
二分K-均值算法的伪代码形式如下:
将所有点看成一个簇 当簇数目小于k时 对于每一个簇 计算总误差 在给定的簇上面进行K-均值聚类(k=2) 计算将该簇一分为二之后的总误差 选择使得误差最小的那个簇进行划分操作
二分K-均值聚类算法实例:
from numpy import * #将文本文件导入到一个 def loadDataSet(fileName): 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 sqrt(sum(power(vecA - vecB, 2))) #构建一个包含k个质心的集合 def randCent(dataSet, k): n = shape(dataSet)[1] centroids = mat(zeros((k,n))) #构建簇质心 for j in range(n): minJ = min(dataSet[:,j]) rangeJ = float(max(dataSet[:,j]) - minJ) centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1)) return centroids #K-均值算法 def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent): #m为样本数据集数据点的总数 m = shape(dataSet)[0] #clusterAssment为簇分配结果矩阵,包含两列,一列记录簇索引值,第二列存储误差(当前点到簇质心的距离) clusterAssment = mat(zeros((m,2))) centroids = createCent(dataSet, k) clusterChanged = True while clusterChanged: #创建一个标志变量,如果该值为true,则继续迭代 clusterChanged = False #遍历所有数据找到距离每个点最近的质心 for i in range(m): minDist = inf; minIndex = -1 for j in range(k): distJI = distMeas(centroids[j,:],dataSet[i,:]) if distJI < minDist: minDist = distJI; minIndex = j if clusterAssment[i,0] != minIndex: clusterChanged = True clusterAssment[i,:] = minIndex,minDist**2 #print(centroids) #更新质心的位置 for cent in range(k): #通过数组过虑来获取给定簇的所有点 ptsInClust = dataSet[nonzero(clusterAssment[:,0].A==cent)[0]] #计算所有点的均值,axis=0为列方向进行均值计算 centroids[cent,:] = mean(ptsInClust, axis=0) #返回所有的类质心与点的分配结果 return centroids, clusterAssment #二分K-均值聚类算法 def biKmeans(dataSet, k, distMeas=distEclud): m = shape(dataSet)[0] #创建一个矩阵存储数据集中每个点的簇分配结果及平方误差 clusterAssment = mat(zeros((m,2))) centroid0 = mean(dataSet, axis=0).tolist()[0] centList =[centroid0] for j in range(m): clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2 #循环体不断对簇进行划分,直到得到想要的簇数目为止 while (len(centList) < k): #将最小SSE设置为无穷大 lowestSSE = inf #遍历列表centList中的每一个簇 for i in range(len(centList)): #对于每一个簇,将该簇中的所有点看成一个小的数据集ptsInCurrCluster ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:] #将ptsInCurrCluster输入到函数kMeans()中(k=2),会生成两个质心簇 centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) #将所有簇的误差加总,作为本次划分的误差 sseSplit = sum(splitClustAss[:,1]) sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) print("再分后的误差和, 没分之前的误差之和: ",sseSplit,sseNotSplit) # 如果该划分的SSE值最小,则本次划分被保存 if (sseSplit + sseNotSplit) < lowestSSE: bestCentToSplit = i bestNewCents = centroidMat bestClustAss = splitClustAss.copy() lowestSSE = sseSplit + sseNotSplit bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit print('the bestCentToSplit is: ',bestCentToSplit) print('the len of bestClustAss is: ', len(bestClustAss)) centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0] centList.append(bestNewCents[1,:].tolist()[0]) clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss #返回质心列表与簇分配结果 return mat(centList), clusterAssment #看一下这三个函数的实际效果 #导入二维矩阵的样例 datMat = mat(loadDataSet('F:\\python入门\\文件\\machinelearninginaction\\Ch10\\testSet2.txt')) centList,myNewAssments = biKmeans(datMat,3) print('三个质心分别为:\n',centList) print('记录每行数据聚类的结果(簇索引值,当前点到簇质心的距离):\n',myNewAssments)
结果:
再分后的误差和, 没分之前的误差之和: 453.0334895807502 0.0 the bestCentToSplit is: 0 the len of bestClustAss is: 60 再分后的误差和, 没分之前的误差之和: 77.59224931775066 29.15724944412535 再分后的误差和, 没分之前的误差之和: 12.753263136887313 423.8762401366249 the bestCentToSplit is: 0 the len of bestClustAss is: 40 三个质心分别为: [[-2.94737575 3.3263781 ] [-0.45965615 -2.7782156 ] [ 2.93386365 3.12782785]] 记录每行数据聚类的结果(簇索引值,当前点到簇质心的距离): [[2.00000000e+00 1.45461050e-01] [0.00000000e+00 6.80213825e-01] [1.00000000e+00 1.02184582e+00] [2.00000000e+00 1.34548760e+00] [0.00000000e+00 1.35376464e+00] [1.00000000e+00 3.87167519e+00] [2.00000000e+00 8.37259951e-01] [0.00000000e+00 2.20116272e-01] [1.00000000e+00 3.53809057e+00] [2.00000000e+00 7.44081160e+00] [0.00000000e+00 5.28070040e+00] [1.00000000e+00 2.56674394e-02] [2.00000000e+00 1.11946529e+00] [0.00000000e+00 1.67890884e-01] [1.00000000e+00 2.11734245e+00] [2.00000000e+00 1.49635209e+00] [0.00000000e+00 4.93628241e+00] [1.00000000e+00 9.76749869e-03] [2.00000000e+00 1.32453845e-01] [0.00000000e+00 6.39346045e-01] [1.00000000e+00 9.41791924e-01] [2.00000000e+00 1.72445523e+00] [0.00000000e+00 7.50682798e-01] [1.00000000e+00 1.48785604e-01] [2.00000000e+00 3.00429548e+00] [0.00000000e+00 5.15437527e+00] [1.00000000e+00 1.80316434e+00] [2.00000000e+00 2.74825782e+00] [0.00000000e+00 4.66860313e-01] [1.00000000e+00 1.28807718e+00] [2.00000000e+00 1.76804356e+00] [0.00000000e+00 3.54002368e+00] [1.00000000e+00 2.12516750e+00] [2.00000000e+00 1.14812052e+00] [0.00000000e+00 1.78247878e+00] [1.00000000e+00 8.79445646e-01] [2.00000000e+00 3.23315472e+00] [0.00000000e+00 7.43934371e-01] [1.00000000e+00 2.36276631e+00] [2.00000000e+00 2.59370616e-01] [0.00000000e+00 1.82015977e+00] [1.00000000e+00 2.10599050e+00] [2.00000000e+00 2.94567602e+00] [0.00000000e+00 2.49952822e+00] [1.00000000e+00 1.54957269e+00] [2.00000000e+00 9.45169633e-01] [0.00000000e+00 2.91966903e+00] [1.00000000e+00 1.13851139e+00] [2.00000000e+00 5.09476462e+00] [0.00000000e+00 1.64971118e+00] [1.00000000e+00 1.98934951e-01] [2.00000000e+00 1.50301593e+00] [0.00000000e+00 2.13359760e-01] [1.00000000e+00 2.16005416e+00] [2.00000000e+00 2.63462894e+00] [0.00000000e+00 7.60898177e-02] [1.00000000e+00 2.60198288e-01] [2.00000000e+00 3.05416591e-03] [0.00000000e+00 3.16776316e+00] [1.00000000e+00 1.61040000e+00]]