# -*- coding: utf-8 -*- # --- # jupyter: # jupytext_format_version: '1.2' # jupytext_formats: ipynb,py # kernelspec: # display_name: Python 3 # language: python # name: python3 # language_info: # codemirror_mode: # name: ipython # version: 3 # file_extension: .py # mimetype: text/x-python # name: python # nbconvert_exporter: python # pygments_lexer: ipython3 # version: 3.5.2 # --- # # k-means # ## Theory # # 由于具有出色的速度和良好的可扩展性,K-Means聚类算法算得上是最著名的聚类方法。K-Means算法是一个重复移动类中心点的过程,把类的中心点,也称重心(centroids),移动到其包含成员的平均位置,然后重新划分其内部成员。 # # K是算法计算出的超参数,表示类的数量;K-Means可以自动分配样本到不同的类,但是不能决定究竟要分几个类。 # # K必须是一个比训练集样本数小的正整数。有时,类的数量是由问题内容指定的。例如,一个鞋厂有三种新款式,它想知道每种新款式都有哪些潜在客户,于是它调研客户,然后从数据里找出三类。也有一些问题没有指定聚类的数量,最优的聚类数量是不确定的。 # # K-Means的参数是类的重心位置和其内部观测值的位置。与广义线性模型和决策树类似,K-Means参数的最优解也是以成本函数最小化为目标。K-Means成本函数公式如下: # $$ # J = \sum_{k=1}^{K} \sum_{i \in C_k} | x_i - u_k|^2 # $$ # # $u_k$是第$k$个类的重心位置,定义为: # $$ # u_k = \frac{1}{|C_k|} \sum_{x \in C_k} x # $$ # # # 成本函数是各个类畸变程度(distortions)之和。每个类的畸变程度等于该类重心与其内部成员位置距离的平方和。若类内部的成员彼此间越紧凑则类的畸变程度越小,反之,若类内部的成员彼此间越分散则类的畸变程度越大。 # # 求解成本函数最小化的参数就是一个重复配置每个类包含的观测值,并不断移动类重心的过程。 # 1. 首先,类的重心是随机确定的位置。实际上,重心位置等于随机选择的观测值的位置。 # 2. 每次迭代的时候,K-Means会把观测值分配到离它们最近的类,然后把重心移动到该类全部成员位置的平均值那里。 # 3. 若达到最大迭代步数或两次迭代差小于设定的阈值则算法结束,否则重复步骤2。 # # # + % matplotlib inline import matplotlib.pyplot as plt import numpy as np X0 = np.array([7, 5, 7, 3, 4, 1, 0, 2, 8, 6, 5, 3]) X1 = np.array([5, 7, 7, 3, 6, 4, 0, 2, 7, 8, 5, 7]) plt.figure() plt.axis([-1, 9, -1, 9]) plt.grid(True) plt.plot(X0, X1, 'k.'); # - # 假设K-Means初始化时,将第一个类的重心设置在第5个样本,第二个类的重心设置在第11个样本.那么我们可以把每个实例与两个重心的距离都计算出来,将其分配到最近的类里面。计算结果如下表所示: # ![data_0](images/data_0.png) # # 新的重心位置和初始聚类结果如下图所示。第一类用X表示,第二类用点表示。重心位置用稍大的点突出显示。 # # # C1 = [1, 4, 5, 9, 11] C2 = list(set(range(12)) - set(C1)) X0C1, X1C1 = X0[C1], X1[C1] X0C2, X1C2 = X0[C2], X1[C2] plt.figure() plt.title('1st iteration results') plt.axis([-1, 9, -1, 9]) plt.grid(True) plt.plot(X0C1, X1C1, 'rx') plt.plot(X0C2, X1C2, 'g.') plt.plot(4,6,'rx',ms=12.0) plt.plot(5,5,'g.',ms=12.0); # 现在我们重新计算两个类的重心,把重心移动到新位置,并重新计算各个样本与新重心的距离,并根据距离远近为样本重新归类。结果如下表所示: # # ![data_1](images/data_1.png) # # 画图结果如下: C1 = [1, 2, 4, 8, 9, 11] C2 = list(set(range(12)) - set(C1)) X0C1, X1C1 = X0[C1], X1[C1] X0C2, X1C2 = X0[C2], X1[C2] plt.figure() plt.title('2nd iteration results') plt.axis([-1, 9, -1, 9]) plt.grid(True) plt.plot(X0C1, X1C1, 'rx') plt.plot(X0C2, X1C2, 'g.') plt.plot(3.8,6.4,'rx',ms=12.0) plt.plot(4.57,4.14,'g.',ms=12.0); # 我们再重复一次上面的做法,把重心移动到新位置,并重新计算各个样本与新重心的距离,并根据距离远近为样本重新归类。结果如下表所示: # ![data_2](images/data_2.png) # # 画图结果如下: # C1 = [0, 1, 2, 4, 8, 9, 10, 11] C2 = list(set(range(12)) - set(C1)) X0C1, X1C1 = X0[C1], X1[C1] X0C2, X1C2 = X0[C2], X1[C2] plt.figure() plt.title('3rd iteration results') plt.axis([-1, 9, -1, 9]) plt.grid(True) plt.plot(X0C1, X1C1, 'rx') plt.plot(X0C2, X1C2, 'g.') plt.plot(5.5,7.0,'rx',ms=12.0) plt.plot(2.2,2.8,'g.',ms=12.0); # 再重复上面的方法就会发现类的重心不变了,K-Means会在条件满足的时候停止重复聚类过程。通常,条件是前后两次迭代的成本函数值的差达到了限定值,或者是前后两次迭代的重心位置变化达到了限定值。如果这些停止条件足够小,K-Means就能找到最优解。不过这个最优解不一定是全局最优解。 # # # ## Program # + # This line configures matplotlib to show figures embedded in the notebook, # instead of opening a new window for each figure. More about that later. # If you are using an old version of IPython, try using '%pylab inline' instead. # %matplotlib inline # import librarys from numpy import * import matplotlib.pyplot as plt import pandas as pd # Load dataset names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class'] dataset = pd.read_csv("iris.csv", header=0, index_col=0) dataset.head() # - #对类别进行编码,3个类别分别赋值0,1,2 dataset['class'][dataset['class']=='Iris-setosa']=0 dataset['class'][dataset['class']=='Iris-versicolor']=1 dataset['class'][dataset['class']=='Iris-virginica']=2 def originalDatashow(dataSet): #绘制原始的样本点 num,dim=shape(dataSet) marksamples=['ob'] #样本图形标记 for i in range(num): plt.plot(datamat.iat[i,0],datamat.iat[i,1],marksamples[0],markersize=5) plt.title('original dataset') plt.xlabel('sepal length') plt.ylabel('sepal width') plt.show() # + {"scrolled": true} #获取样本数据 datamat = dataset.loc[:, ['sepal-length', 'sepal-width']] # 真实的标签 labels = dataset.loc[:, ['class']] #原始数据显示 originalDatashow(datamat) # - def randChosenCent(dataSet,k): """初始化聚类中心:通过在区间范围随机产生的值作为新的中心点""" # 样本数 m=shape(dataSet)[0] # 初始化列表 centroidsIndex=[] #生成类似于样本索引的列表 dataIndex=list(range(m)) for i in range(k): #生成随机数 randIndex=random.randint(0,len(dataIndex)) #将随机产生的样本的索引放入centroidsIndex centroidsIndex.append(dataIndex[randIndex]) #删除已经被抽中的样本 del dataIndex[randIndex] #根据索引获取样本 centroids = dataSet.iloc[centroidsIndex] return mat(centroids) # + def distEclud(vecA, vecB): """算距离, 两个向量间欧式距离""" return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB) def kMeans(dataSet, k): # 样本总数 m = shape(dataSet)[0] # 分配样本到最近的簇:存[簇序号,距离的平方] (m行 x 2 列) clusterAssment = mat(zeros((m, 2))) # step1: 通过随机产生的样本点初始化聚类中心 centroids = randChosenCent(dataSet, k) print('最初的中心=', centroids) # 标志位,如果迭代前后样本分类发生变化值为Tree,否则为False clusterChanged = True # 查看迭代次数 iterTime = 0 # 所有样本分配结果不再改变,迭代终止 while clusterChanged: clusterChanged = False # step2:分配到最近的聚类中心对应的簇中 for i in range(m): # 初始定义距离为无穷大 minDist = inf; # 初始化索引值 minIndex = -1 # 计算每个样本与k个中心点距离 for j in range(k): # 计算第i个样本到第j个中心点的距离 distJI = distEclud(centroids[j, :], dataSet.values[i, :]) # 判断距离是否为最小 if distJI < minDist: # 更新获取到最小距离 minDist = distJI # 获取对应的簇序号 minIndex = j # 样本上次分配结果跟本次不一样,标志位clusterChanged置True if clusterAssment[i, 0] != minIndex: clusterChanged = True clusterAssment[i, :] = minIndex, minDist ** 2 # 分配样本到最近的簇 iterTime += 1 sse = sum(clusterAssment[:, 1]) print('the SSE of %d' % iterTime + 'th iteration is %f' % sse) # step3:更新聚类中心 for cent in range(k): # 样本分配结束后,重新计算聚类中心 # 获取该簇所有的样本点 ptsInClust = dataSet.iloc[nonzero(clusterAssment[:, 0].A == cent)[0]] # 更新聚类中心:axis=0沿列方向求均值。 centroids[cent, :] = mean(ptsInClust, axis=0) return centroids, clusterAssment # - # 进行k-means聚类 k = 3 # 用户定义聚类数 mycentroids, clusterAssment = kMeans(datamat, k) # + def datashow(dataSet, k, centroids, clusterAssment): # 二维空间显示聚类结果 from matplotlib import pyplot as plt num, dim = shape(dataSet) # 样本数num ,维数dim if dim != 2: print('sorry,the dimension of your dataset is not 2!') return 1 marksamples = ['or', 'ob', 'og', 'ok', '^r', '^b', ' len(marksamples): print('sorry,your k is too large,please add length of the marksample!') return 1 # 绘所有样本 for i in range(num): markindex = int(clusterAssment[i, 0]) # 矩阵形式转为int值, 簇序号 # 特征维对应坐标轴x,y;样本图形标记及大小 plt.plot(dataSet.iat[i, 0], dataSet.iat[i, 1], marksamples[markindex], markersize=6) # 绘中心点 markcentroids = ['o', '*', '^'] # 聚类中心图形标记 label = ['0', '1', '2'] c = ['yellow', 'pink', 'red'] for i in range(k): plt.plot(centroids[i, 0], centroids[i, 1], markcentroids[i], markersize=15, label=label[i], c=c[i]) plt.legend(loc='upper left') plt.xlabel('sepal length') plt.ylabel('sepal width') plt.title('k-means cluster result') # 标题 plt.show() # 画出实际图像 def trgartshow(dataSet, k, labels): from matplotlib import pyplot as plt num, dim = shape(dataSet) label = ['0', '1', '2'] marksamples = ['ob', 'or', 'og', 'ok', '^r', '^b', '