机器学习实验报告5-K-means 算法
4.1 k-means算法简介 聚类分析,作为机器学习领域中的一种无监督学习方法,在数据探索与知识发现过程中扮演着举足轻重的角色。它能够在没有先验知识或标签信息的情况下,通过挖掘数据中的内在结构和规律,将数据对象自动划分为多个类别或簇。每个簇内的对象具有高度的相似性,而不同簇间的对象则表现出明显的差异性。 在众多聚类算法中,K-means算法因其简单高效而备受青睐。K-means算法的基本思想是:通过迭代的方式,将数据划分为K个不同的簇,并使得每个数据点与其所属簇的质心(或称为中心点、均值点)之间的距离之和最小。 K-means算法的优点在于其直观易懂、计算速度快且易于实现。然而,它也存在一些局限性,如对初始簇质心的选择敏感、可能陷入局部最优解以及需要预先设定聚类数K等。因此,在实际应用中,我们需要根据具体的问题和数据特点来选择合适的聚类算法,并可能需要对算法进行优化或改进以适应特定的需求。 4.2 算法的基本原理 (1)类和分类 类指的是具有相似性的集合。而分类是根据给定的已知类别的样本,训练出来一个模型,使该模型能够对未知类别的样本进行分类。故分类属于有监督学习。有监督学习是指提前由人工对训练数据做好了分类,训练样本同时包含有特征和类别信息。有监督学习类似于先学习带有标准答案的复习题,学习到知识规律以后,再去参加考试。 (2)聚类 对于一些给定的样本,不知道样本之间的关系,不知道他们是否属于同一类,也不知道到底可以分为多少类。此时,通过聚类把未知类别的数据,分为多个类别。聚类目的在于把相似的东西聚在一起,因此聚类属于无监督学习。 (3)K-均值聚类思想 K-均值(K-Means)是发现给定数据集的K个簇的算法。簇个数K是用户给定的,每一个簇通过其质心(即簇中所有点的中心)来描述。其核心思想是将数据集中的n个对象划分为K个聚类,使得每个对象到其所属聚类的中心(或称为均值点、质心)的距离之和最小。这里所说的距离通常指的是欧氏距离,但也可以是其他类型的距离度量。通过迭代的方式不断优化聚类结果,使得每个聚类内的对象尽可能紧密,而不同聚类间的对象则尽可能分开。 (4)算法步骤 K-means算法作为一种强大的无监督学习工具,具有简单易懂、计算效率高和易于实现等优点在多个领域有着广泛的应用,下面我们将详细探讨K-means算法的实现步骤。K-means算法的执行过程通常包括以下四个步骤。 a.初始化,选择K个初始聚类中心。在算法开始时,需要随机选择K个数据点作为初始的聚类中心。这些初始聚类中心的选择对最终的聚类结果有一定的影响,因此在实际应用中,通常会采用一些启发式的方法来选择较好的初始聚类中心,如K-means++算法。 b.分配,将每个数据点分配给最近的聚类中心。对于数据集中的每个数据点,计算其与每个聚类中心的距离,并将其分配给距离最近的聚类中心。这一步通常使用欧氏距离作为距离度量,计算公式如下(1)。其中,x是数据点,ci是第i个聚类中心,d是数据的维度,xj和cij分别是x和ci在第j维上的值。 c.更新,重新计算每个聚类的中心。对于每个聚类,重新计算其聚类中心,新的聚类中心是该聚类内所有数据点的均值,计算公式如下(2)。其中,Si是第i个聚类的数据点集合,|Si|是该集合中数据点的数量。 d.迭代,重复分配和更新步骤,直到满足终止条件。重复执行分配和更新步骤,直到满足某种终止条件。常见的终止条件包括,聚类中心不再发生显著变化:即新的聚类中心与旧的聚类中心之间的距离小于某个预设的阈值。达到最大迭代次数:为了避免算法陷入无限循环,通常会设置一个最大迭代次数作为终止条件。在迭代过程中,算法会不断优化聚类结果,使得每个聚类内的对象更加紧密,而不同聚类间的对象更加分散。最终,当满足终止条件时,算法停止迭代并输出最终的聚类结果。 4.3 算法实例 K-means算法作为一种强大的无监督学习工具,在多个领域有着广泛的应用。本实验以对俄勒冈州的波特兰地区地图上的点进行聚类为例,基于k-means算法实现,将这些地方进行聚类的最佳策略,这样就可以安排交通工具抵达这些簇的质心,然后步行到每个内地址。实例操作可分为以下五个过程。
通过调用geoGrab函数来实现收集数据,geoGrab函数的作用是收集数据,通过调用雅虎地理编码 API(Yahoo Geocoding API),将输入的街道地址和城市名称转换为对应的地理坐标(经纬度等位置信息),返回 API 响应的 JSON 格式数据,包含地址对应的地理编码信息(如坐标、地址匹配度等)。 相关代码如下: def geoGrab(stAddress, city): apiStem = 'http://where.yahooapis.com/geocode?' #create a dict and constants for the goecoder params = {} params['flags'] = 'J'#JSON return type params['appid'] = 'aaa0VN6k' params['location'] = '%s %s' % (stAddress, city) url_params = urllib.parse.urlencode(params) yahooApi = apiStem + url_params #print url_params print (yahooApi) c=urllib.parse.urlopen(yahooApi) return json.loads(c.read())
准备数据中,只保留经纬度信息。我们可以通过调用massPlaceFind函数。该函数其作用是批量处理地址数据,通过调用geoGrab函数获取地理坐标(经纬度),并将结果写入文件。该函数适用于批量处理地址数据,生成带地理坐标的数据集,常用于地理信息系统(GIS)、数据分析或地图可视化等场景。 相关代码如下: def massPlaceFind(fileName): fw = open('places.txt', 'w') for line in open(fileName).readlines(): line = line.strip() lineArr = line.split('\t') retDict = geoGrab(lineArr[1], lineArr[2]) if retDict['ResultSet']['Error'] == 0: lat = float(retDict['ResultSet']['Results'][0]['latitude']) lng = float(retDict['ResultSet']['Results'][0]['longitude']) print("%s\t%f\t%f" % (lineArr[0], lat, lng)) fw.write('%s\t%f\t%f\n' % (line, lat, lng)) else: print ("error fetching") sleep(1) fw.close()
使用Matplotlib来构建一个二维数据图,其中包含簇与地图。我们通过调用clusterClubs函数来实现。clusterClubs函数,其核心功能是对地点的经纬度数据进行聚类分析,并在地图背景上可视化聚类结果。该函数适用于地理数据的聚类分析与可视化,常用于城市规划、商业选址、轨迹分析等场景,帮助快速识别数据中的空间分布模式。 图1 调用函数 图2 二维数据图 相关代码如下: def clusterClubs(numClust=5): datList = [] for line in open('places.txt').readlines(): lineArr = line.split('\t') datList.append([float(lineArr[4]), float(lineArr[3])]) datMat = matrix(datList) myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC) fig = plt.figure() rect=[0.1,0.1,0.8,0.8] scatterMarkers=['s', 'o', '^', '8', 'p', \'d', 'v', 'h', '>', '<'] axprops = dict(xticks=[], yticks=[]) ax0=fig.add_axes(rect, label='ax0', **axprops) imgP = plt.imread('Portland.png') ax0.imshow(imgP) ax1=fig.add_axes(rect, label='ax1', frameon=False) for i in range(numClust): ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A==i)[0],:] markerStyle = scatterMarkers[i % len(scatterMarkers)] ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0], ptsInCurrCluster[:,1].flatten().A[0], marker=markerStyle, s=90) ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300) plt.show()
通过调用biKmeans()函数来测试算法。该函数实现了二分K均值算法(Bisecting K-Means),用于对数据集进行聚类分析。其核心作用是通过 “逐步分裂簇” 的策略,将数据集划分为指定数量的簇(k个)。 图3 测试算法 相关代码如下: def biKmeans(dataSet, k, distMeas=distEclud): m = shape(dataSet)[0] clusterAssment = matrix(zeros((m,2))) centroid0 = mean(dataSet, axis=0).tolist()[0] centList =[centroid0] #create a list with one centroid for j in range(m):#calc initial Error clusterAssment[j,1] = distMeas(matrix(centroid0), dataSet[j,:])**2 while (len(centList) < k): lowestSSE = inf for i in range(len(centList)): ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]#get the data points currently in cluster i centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas) sseSplit = sum(splitClustAss[:,1])#compare the SSE to the currrent minimum sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1]) print("sseSplit, and notSplit: ",sseSplit,sseNotSplit) if (sseSplit + sseNotSplit) < lowestSSE: bestCentToSplit = i bestNewCents = centroidMat bestClustAss = splitClustAss.copy() lowestSSE = sseSplit + sseNotSplit bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList) #change 1 to 3,4, or whatever 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]#replace a centroid with two best centroids centList.append(bestNewCents[1,:].tolist()[0]) clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss#reassign new clusters, and SSE return matrix(centList), clusterAssment
我们通过使用clusterClubs函数来输出包含簇及簇中心的地图。clusterClubs函数,其核心功能是对地点的经纬度数据进行聚类分析,并在地图背景上可视化聚类结果。操作过程与(3)分析数据相同。 图4 簇及簇中心图 相关代码如下: def clusterClubs(numClust=5): datList = [] for line in open('places.txt').readlines(): lineArr = line.split('\t') datList.append([float(lineArr[4]), float(lineArr[3])]) datMat = matrix(datList) myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC) fig = plt.figure() rect=[0.1,0.1,0.8,0.8] scatterMarkers=['s', 'o', '^', '8', 'p', \ 'd', 'v', 'h', '>', '<'] axprops = dict(xticks=[], yticks=[]) ax0=fig.add_axes(rect, label='ax0', **axprops) imgP = plt.imread('Portland.png') ax0.imshow(imgP) ax1=fig.add_axes(rect, label='ax1', frameon=False) for i in range(numClust): ptsInCurrCluster = datMat[nonzero(clustAssing[:,0].A==i)[0],:] markerStyle = scatterMarkers[i % len(scatterMarkers)] ax1.scatter(ptsInCurrCluster[:,0].flatten().A[0], ptsInCurrCluster[:,1].flatten().A[0], marker=markerStyle, s=90) ax1.scatter(myCentroids[:,0].flatten().A[0], myCentroids[:,1].flatten().A[0], marker='+', s=300) plt.show() |