机器学习——K-均值聚类

K-均值聚类算法

 

1.原理介绍

  • K-均值算法是一种聚类算法。聚类算法与分类算法的区别在于分类的目标事先已知,而聚类的类别没有事先定义。聚类算法将相似对象归为同一群落,将不同对象归到不同群落。相似的概念取决于选择的相似度的计算方法。

  • K-均值算法的原理是随机确定 k k k个初始点作为质心(centroid,群中所有点的中心),为每个点找距离最近的质心,将每个点中分到群中。更新质心为该群落所有点的平均值。(k是可选参数)

      伪代码:
      	选择k个点作为起始质心
      	当任意一点的群落发生变化时:
      		对每个数据点:
      			对每个质心:
      				计算质心与数据点的距离
      			将数据点分配到距离最近的群
      	对每一个群落,计算群中所有点的均值并将均值作为质心
    

 
 

2.代码

(1)初始K-均值算法

import numpy as np

def loadData(filename):
    dataMat = []
    f = open(filename)
    for line in f.readlines():
        linec = line.strip().split('\t')
        # map()将float()作用在linec每个元素上,得到一个新的list并返回。即将linec每个元素变为浮点数
        floatLine = list(map(float, linec))
        dataMat.append(floatLine)
    return dataMat

# 计算群之间的欧式距离
def distance(vecA, vecB):
    return np.sqrt(np.sum(np.power(vecA - vecB, 2)))

# 随机质心
def randCent(dataset, k):
    n = np.shape(dataset)[1]
    centroids = np.mat(np.zeros((k, n)))
    for j in range(n):
        minJ = min(dataset[:, j]) # 每一列最小值
        rangeJ = float(max(dataset[:, j]) - minJ) # 每一列最大区间
        centroids[:, j] = minJ + rangeJ * np.random.rand(k, 1) # 随机生成 k×1 个质心
    return centroids

# 求群分配结果和质心
def kMeans(dataset, k, distMeas=distance, createCent=randCent):
    m = np.shape(dataset)[0]
    clusterAssment = np.mat(np.zeros((m, 2))) # 群分配结果的初始矩阵:一列存放群索引值,一列存放误差
    centroids = createCent(dataset, k) # 首先随机选择 k个质心
    clusterChanged = True
    while clusterChanged: # 当任一点的群改变时
        clusterChanged = False
        for i in range(m):# 遍历所有点
            minDist = np.inf
            minIndex = -1
            for j in range(k): # 遍历所有质心
                distJI = distMeas(centroids[j, :], dataset[i, :]) # 第i个点与第j个质心的距离
                if distJI < minDist: # 找到第i个点与所有质心的最短距离及其质心
                    minDist = distJI
                    minIndex = j
            if clusterAssment[i, 0] != minIndex:# 若该点的群落改变,则需要再循环
                clusterChanged = True
            clusterAssment[i, :] = minIndex, minDist ** 2 # 更新该点的分配结果与距离
        for cen in range(k):# 对于每个质心
            ptsInClust = dataset[np.nonzero(clusterAssment[:, 0].A == cen)[0]]#划分为第cen类的所有点
            centroids[cen, :] = np.mean(ptsInClust, axis=0) # 更新质心的值(axis=0表示按列计算均值)
    return centroids, clusterAssment

if __name__ == "__main__":
    datamat = np.mat(loadData('testSet.txt'))
    centriods, clusterAssment = kMeans(datamat,4)
    print(centriods,clusterAssment)

 
 

(2)二分K-均值算法

  • 上面的算法由于 k k k值难以确定以及聚类效果的不确定,可能最后收敛到了局部最小值,而非全局最小值。需要改进

  • 度量聚类效果的一种指标是误差平方和(SSE),误差越小越接近于质心,聚类效果越好。

  • 二分K-均值的算法用于改进以上的算法,其原理是首先将所有点作为一个群,然后二分,再根据某个可以最大程度降低SSE的群落进行二分,直到达到指定 k k k个群为止。

        伪代码:
        	将所有点视为一个群落
        	当群数目 < k 时
        		对于每一个群
        			计算总误差
        			在给定的群上面进行K-均值聚类(k=2)
        			计算将该群落二分后的总误差
        		选择使得误差最小的群进行二分
    
#二分K-均值
def bikMeans(dataset, k, distMeas=distance):
    m = np.shape(dataset)[0]
    clusterAssment = np.mat(np.zeros((m, 2)))  # 储存群分配结果与平方误差
    centriod0 = np.mean(dataset, axis=0).tolist()[0]  # 初始质心:整个数据集的平均值
    centList = [centriod0]  # 用于保存质心的列表
    for j in range(m):  # 计算每个点到质心的距离/误差
        clusterAssment[j, 1] = distMeas(np.mat(centriod0), dataset[j, :]) ** 2
    while (len(centList) < k):  # 直到分为k个群为止
        lowestSSE = np.inf
        for i in range(len(centList)):  # 遍历所有质心
            ptsCluster = dataset[np.nonzero(clusterAssment[:, 0].A == i)[0], :]  # 第i个群的数据集
            centriodMat, splitClustAss = kMeans(ptsCluster, 2, distMeas)  # 二分第i个群
            sseSplit = np.sum(splitClustAss[:, 1])  # 计算第i个群划分后的SSE
            # 除第i个群之外的SSE
            sseNotSplit = np.sum(clusterAssment[np.nonzero(clusterAssment[:, 0].A != i)[0], 1])
            if (sseSplit + sseNotSplit) < lowestSSE:  # 若划分后的SSE比当前最小SSE小,则进行二分
                bestCenToSplit = i
                bestNewCents = centriodMat
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit
        # 更新群的分配结果(在二分中划分为0的数据索引值不变(原群的索引值),划分为1的数据索引值更新为新值)
        bestClustAss[np.nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
        bestClustAss[np.nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCenToSplit
        clusterAssment[np.nonzero(clusterAssment[:, 0].A == bestCenToSplit)[0], :] = bestClustAss
        # 更新质心(更新旧群的质心,添加新群的质心)
        centList[bestCenToSplit] = bestNewCents[0, :]
        centList.append(bestNewCents[1, :])
        cent = []
        for i in range(len(centList)):
            cen = centList[i].tolist()[0]
            cent.append(cen)
            centriodList = np.mat(cent)
    return centriodList, clusterAssment

if __name__ == "__main__":
    datamat = np.mat(loadData('testSet2.txt'))
    centList, clusterAssment = bikMeans(datamat, 3)
    print(centList)

 
 

3.实例

  • 对地理坐标进行聚类
import matplotlib.pyplot as plt

def distSLC(vecA, vecB):  # 给定经纬度,返回地球表面两点的距离
    a = np.sin(vecA[0, 1] * np.pi / 180) * np.sin(vecB[0, 1] * np.pi / 180)
    b = np.cos(vecA[0, 1] * np.pi / 180) * np.cos(vecB[0, 1] * np.pi / 180) * \
        np.cos(np.pi * (vecB[0, 0] - vecA[0, 0]) / 180)
    return np.arccos(a + b) * 6371.0

def clusterClubs(cluster_num=5):
    dataList = []
    for line in open('places.txt').readlines():
        lineArr = line.split('\t')
        dataList.append([float(lineArr[4]), float(lineArr[3])])
    datamat = np.mat(dataList)
    centriod, clustAssing = bikMeans(datamat, cluster_num, distMeas=distSLC)
    fig = plt.figure()
    # 代表figure中左,右,宽,高的绘制比例
    rect = [0.1, 0.1, 0.8, 0.8]
    scatterMarkers = ['s', 'o', '^', '8', 'p', 'd', 'v', 'h', '>', '<']
    axprops = dict(xticks=[], yticks=[])
    # add_axes()可以叠加图形,第一层用于设置背景图,同时设置坐标体系为空值,方便后面散点图的坐标体系设置
    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)  # frameon用于叠加图层,为False不遮挡下一层
    for i in range(cluster_num):  # 用不同形状标记不同群并绘制出来
        pstInCurrCluster = datamat[np.nonzero(clustAssing[:, 0].A == i)[0], :]
        markerStyle = scatterMarkers[i % len(scatterMarkers)]  # 第i个群选择标记列表第i个形状
        ax1.scatter(pstInCurrCluster[:, 0].flatten().A[0], \
                    pstInCurrCluster[:, 1].flatten().A[0], marker=markerStyle, s=90)
    # 用“+”代表质心,画质心的散点图
    ax1.scatter(centriod[:, 0].flatten().A[0], centriod[:, 1].flatten().A[0], marker='+', s=300)
    plt.show()

if __name__ == "__main__":
    clusterClubs(5)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值