聚类
无监督学习
机器学习可以分为监督学习和无监督学习,监督学习中每个样本都有属于的已知类别,比如色泽为青绿,甜度高的西瓜为好瓜,而无监督学习则没有已知的类别,只有样本的特征值是已知的,此时就不是分类和回归的任务,聚类成为这些样本的一种学习
聚类
聚类试图将数据集中的样本划分为若干个不相交的子集,每个子集称为一个簇,通过这样的划分,每个簇可能对于于一些潜在的类别
形式化的说,假定样本集 D D D={ x 1 , x 2 ⋯ , x m x_1,x_2\cdots,x_m x1,x2⋯,xm}包含m个无标记样本,每个样本 x i = ( x i 1 , ⋯ , x i n ) x_i=(x_{i1},\cdots,x_{in}) xi=(xi1,⋯,xin)是一个n维特征向量,则聚类算法将样本集 D D D划分为k个不相交的簇{ C l ∣ l = 1 , 2 , ⋯ , k C_l|l=1,2,\cdots,k Cl∣l=1,2,⋯,k},其中 C l ∩ C i = ∅ ( l ≠ i ) C_l\cap Ci=\emptyset \space(l\neq i) Cl∩Ci=∅ (l=i),且 D = ∪ l = 1 k C l D=\cup_{l=1}^kC_l D=∪l=1kCl。
下面介绍聚类的几种基本类型
-
k均值算法:
-
在D中随机选择k个样本作为初始均值向量{ u 1 , u 2 , ⋯ , u k u_1,u_2,\cdots,u_k u1,u2,⋯,uk}
-
计算每个样本 x j x_j xj与各个均值向量 u i ( 1 ≤ i ≤ k ) u_i(1\leq i \leq k) ui(1≤i≤k)的距离(无说明为欧式距离): d i j = ∣ ∣ x j − u i ∣ ∣ 2 d_{ij}=||x_j-u_i||_2 dij=∣∣xj−ui∣∣2
-
选出最短的距离,并将该样本并入向量 u i u_i ui确定的簇
-
当所有样本的计算一次后,对于每个簇,更新其新的均值向量
u i , = 1 ∣ C l ∣ ∑ x ∈ C l x u_i^,=\frac{1}{|C_l|}\sum _{x\in C_l}x ui,=∣Cl∣1∑x∈Clx
-
若有一个更新后的均值向量与原来的均值向量不同,则回到步骤2
-
若所有均值向量稳定后,则得到每个均值向量所确定的簇{ C 1 , C 2 , ⋯ , C k C_1,C_2,\cdots,C_k C1,C2,⋯,Ck},对于每个簇当中的样本 x x x, x x x与当前簇的均值向量距离最小
-
-
二分k均值算法:(优化版k均值算法)
-
算出所有样本的均值向量,将所有样本初始归为一个簇
-
选出一个合适的均值向量,其对应的簇使用k均值算法,其中k=2,去掉原来的均值向量,加入两个新的均值向量
如何选出合适的均值向量二分:选出二分后能使总样本方差减少最多的均值向量
-
当现有的均值向量小于k时,重复步骤2
实现:
from numpy import * import matplotlib.pyplot as plt def loadDataSet(fileName): dataMat = [] fr = open(fileName) for line in fr.readlines(): curLine = line.strip().split('\t') fltLine = map(float,curLine) dataMat.append(list(fltLine)) return mat(dataMat) def distEclud(vecA,vecB): return sqrt(sum(power(vecA-vecB,2))) def randCenter(dataSet,k): n = shape(dataSet)[1] #每个样本是n维特征向量 centroids = mat(zeros((k,n))) #初始化随机质心 for j in range(n): minJ = min(dataSet[:,j]) maxJ = max(dataSet[:,j]) rangeJ = float(maxJ - minJ) #随机质心保证在数据集内部 centroids[:,j] = minJ + rangeJ * random.rand(k,1) #随机矩阵0~1 return centroids def KMeans(dataSet,k,distMeas=distEclud,createCent=randCenter): #第一个参数数据集,第二个参数k,第三个参数使用的距离函数,第四个参数如何生成原始质心 m = shape(dataSet)[0] clusterAssment = mat(zeros((m,2))) #记录每个样本属于哪个簇以及距离 centroids = createCent(dataSet,k) #初始质心 clusterChanged = True while clusterChanged: 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 for cent in range(k): #更新质心的位置 ptsInClust = dataSet[nonzero(clusterAssment[:,0].A == cent)[0]] #取出该簇中所有的样本 centroids[cent,:] = mean(ptsInClust,axis=0) #取均值,mean(x)返回一个实数,mean(x,axis=0)对每列取均值返回1*n矩阵 #mean(x,axis=1)对每行取均值返回m*1矩阵 return centroids,clusterAssment def biKMeans(dataSet,k,distMeas=distEclud): #二分k均值算法 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 #计算方差 print(centList) while (len(centList) < k): #当质心序列小于k时 lowestSSE = inf #记录最小方差 for i in range(len(centList)): #遍历质心序列里所有质心,选出应该二分的质心 pstInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A == i)[0],:] #属于该质心的数据集 centroidMat,splitClustAss = KMeans(pstInCurrCluster,2,distMeas) #调用k均值算法函数,k=2 SseSplit = sum(splitClustAss[:,1]) #计算分裂后的方差和 SseNoSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A != i)[0],1]) #不属于该质心的数据集的方差 print("SSE_split,SSE_Nosplit:",SseSplit,SseNoSplit) if (SseSplit + SseNoSplit) < lowestSSE:#整个数据集方差最小 bestCentTosplit = i #记录分裂的质心下标 bestNewCents = centroidMat #分裂后的两个质心 bestClusterAss = splitClustAss.copy() #分裂后数据集的信息[属于哪个簇(只有0 1 两个值)因为k=2,方差] lowestSSE = SseSplit +SseNoSplit print(lowestSSE) print(bestCentTosplit) bestClusterAss[nonzero(bestClusterAss[:,0].A == 1)[0],0] = \ len(centList) #属于分裂后0簇的数据集实际上属于len(centList)簇,更新其簇号 bestClusterAss[nonzero(bestClusterAss[:,0].A == 0)[0],0] = \ bestCentTosplit #分裂后1簇的数据集实际上属于原来分裂的那个簇的簇号 centList[bestCentTosplit] = bestNewCents[0,:].tolist()[0] #将分裂后的两个新质心添加到质心序列中 centList.append(bestNewCents[1,:].tolist()[0]) #值得注意的是分裂的那个质心应该被覆盖掉 clusterAssment[nonzero(clusterAssment[:,0].A == \ bestCentTosplit)[0],:] = bestClusterAss #更新最终的样本信息 print(centList) return mat(centList),clusterAssment def main(): dataSet = loadDataSet('1.txt') k=3 centroids,clusterAssment = biKMeans(dataSet,k) #centroids,clusterAssment = KMeans(dataSet,k) Color = ['b','g','r','y','black'] for cent in range(k): subSet = dataSet[nonzero(clusterAssment[:,0] == cent)[0]] plt.scatter(subSet[:,0].tolist(),subSet[:,1].tolist(),color=Color[cent]) plt.scatter(centroids[:,0].tolist(),centroids[:,1].tolist(),marker='+') plt.show() if __name__ == '__main__': main()
-