机器学习实战笔记7—K-Means

注:此系列文章里的部分算法和深度学习笔记系列里的内容有重合的地方,深度学习笔记里是看教学视频做的笔记,此处文章是看《机器学习实战》这本书所做的笔记,虽然算法相同,但示例代码有所不同,多敲一遍没有坏处,哈哈。(里面用到的数据集、代码可以到网上搜索,很容易找到。)。Python版本3.6

机器学习十大算法系列文章:

机器学习实战笔记1—k-近邻算法

机器学习实战笔记2—决策树

机器学习实战笔记3—朴素贝叶斯

机器学习实战笔记4—Logistic回归

机器学习实战笔记5—支持向量机

机器学习实战笔记6—AdaBoost

机器学习实战笔记7—K-Means

机器学习实战笔记8—随机森林

机器学习实战笔记9—人工神经网络

此系列源码在我的GitHub里:https://github.com/yeyujujishou19/Machine-Learning-In-Action-Codes

一,算法原理:

参考上一篇文章:深度学习基础课程1笔记-Kmeans算法(聚类)

二,算法的优缺点:

优点:容易实现

缺点:可能收敛到局部最小值,在大规模数据集上收敛速度较慢

适用数据类型:数值型数据。

三,实例代码:

代码一:K-means算法支持函数

from numpy import *
import numpy as np
import matplotlib.pyplot as plt

#K-means算法支持函数

#文本数据解析函数
def loadDataSet(fileName):      #general function to parse tab -delimited floats
    dataMat = []                #assume last column is target value
    fr = open(fileName)
    for line in fr.readlines():
        curLine = line.strip().split('\t')
        fltLine = list(map(float,curLine))  #将每一行的数据映射成float型
        dataMat.append(fltLine)
    return dataMat

#计算两个向量的欧氏距离
def distEclud(vecA, vecB):
    return sqrt(sum(power(vecA - vecB, 2))) #la.norm(vecA-vecB)

#生成k个随机质心(质心满足数据边界之内)
def randCent(dataSet, k):
    # 得到数据样本的维度
    n = shape(dataSet)[1]
    # 初始化为一个(k,n)的矩阵
    centroids = mat(zeros((k,n)))
    # 遍历数据集的每一维度
    for j in range(n):
        # 得到该列数据的最小值
        minJ = min(np.array(dataSet)[:,j])
        # 得到该列数据的范围(最大值-最小值)
        rangeJ = float(max(np.array(dataSet)[:,j]) - minJ)
        # k个质心向量的第j维数据值随机为位于(最小值,最大值)内的某一值
        centroids[:,j] = mat(minJ + rangeJ * random.rand(k,1))
    return centroids

#测试
datMat=loadDataSet('testSet.txt') #加载数据
centroids=randCent(datMat, 3) #随机产生三个质心
print(centroids) #打印质心

代码结果:

机器学习实战笔记7—K-Means_第1张图片

代码二:k-均值聚类算法

所有支持函数正常运行之后,就可以准备实现完整的K-均值算法了。该算法会创建k个质心,然后将每个点分配到最近的质心,再重新计算质心。这个过程重复数次,直到数据点的簇分配结果不再改变为止。

#k-均值聚类算法
#@dataSet:聚类数据集
#@k:用户指定的k个类
#@distMeas:距离计算方法,默认欧氏距离distEclud()
#@createCent:获得k个质心的方法,默认随机获取randCent()
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
    # 获取数据集样本数
    m = shape(dataSet)[0]
    # 初始化一个(m,2)的矩阵
    clusterAssment = mat(zeros((m,2)))
    # 创建初始的k个质心向量
    centroids = createCent(dataSet, k)
    # 聚类结果是否发生变化的布尔类型
    clusterChanged = True
    # 只要聚类结果一直发生变化,就一直执行聚类算法,直至所有数据点聚类结果不变化
    while clusterChanged:
        # 聚类结果变化布尔类型置为false
        clusterChanged = False
        # 遍历数据集每一个样本向量
        for i in range(m):
            # 初始化最小距离最正无穷;最小距离对应索引为-1
            minDist = inf; minIndex = -1
            # 循环k个类的质心
            for j in range(k):
                # 计算数据点到质心的欧氏距离
                distJI = distMeas(np.array(centroids)[j,:],np.array(dataSet)[i,:])
                # 如果距离小于当前最小距离
                if distJI < minDist:
                    # 当前距离定为当前最小距离;最小距离对应索引对应为j(第j个类)
                    minDist = distJI; minIndex = j
            # 当前聚类结果中第i个样本的聚类结果发生变化:布尔类型置为true,继续聚类算法
            if clusterAssment[i,0] != minIndex: clusterChanged = True
            # 更新当前变化样本的聚类结果和平方误差
            clusterAssment[i,:] = minIndex,minDist**2
        # 打印k-均值聚类的质心
        print (centroids)
        for cent in range(k):
            # 将数据集中所有属于当前质心类的样本通过条件过滤筛选出来
            ptsInClust = np.array(dataSet)[nonzero(clusterAssment[:,0].A==cent)[0]]
            # 计算这些数据的均值(axis=0:求列的均值),作为该类质心向量
            centroids[cent,:] = mean(ptsInClust, axis=0)
    # 返回k个聚类,聚类结果及误差
    return centroids, clusterAssment

#测试
datMat=loadDataSet('testSet.txt')
myCentroids, clustAssing =kMeans(datMat, 4)

#画图
fig = plt.figure()
ax1 = fig.add_subplot(111)

#画出四个质心
for data in myCentroids:
    x = np.array(data)[0, 0]
    y = np.array(data)[0, 1]

    ax1.scatter(x, y, c='r', marker='x')

#按类别画出点
for i in range(len(datMat)):

    x = datMat[i][0]
    y = datMat[i][1]
    label=np.array(clustAssing[i])[0, 0]
    if (label == 0):
        ax1.scatter(x, y, c='r', marker='^')
    if (label == 1):
        ax1.scatter(x, y, c='g', marker='s')
    if (label == 2):
        ax1.scatter(x, y, c='b', marker='o')
    if (label == 3):
        ax1.scatter(x, y, c='y', marker='D')

#显示所画的图
plt.show()

代码结果:

机器学习实战笔记7—K-Means_第2张图片

代码三:二分k-均值算法

二分K-means算法首先将所有点作为一个簇,然后将簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇取决于对其进行划分是否能够最大程度的降低SSE的值。上述划分过程不断重复,直至划分的簇的数目达到用户指定的值为止。

二分K-means算法的伪代码如下:

将所有点看成一个簇
当簇数目小于k时
对于每一个簇
    计算总误差
    在给定的簇上面进行k-均值聚类(k=2)
    计算将该簇一分为二之后的总误差
选择使得总误差最小的簇进行划分

代码:

#二分K-均值聚类算法
#@dataSet:待聚类数据集
#@k:用户指定的聚类个数
#@distMeas:用户指定的距离计算方法,默认为欧式距离计算
def biKmeans(dataSet,k,distMeas=distEclud):
    # 获得数据集的样本数
    m = shape(dataSet)[0]
    # 将所有的点看成是一个簇
    # clusterAssment 存储 (所属的中心编号,距中心的距离)的列表
    clusterAssment = mat(zeros((m,2)))

    centroid0 = mean(dataSet,axis=0).tolist()[0]
    # centList 存储聚类中心
    centList = [centroid0]
    # 遍历每个数据集样本
    for j in range(m):
        clusterAssment[j,1] = distMeas(mat(centroid0),np.array(dataSet)[j,:]) ** 2
    # 当簇小于数目k时
    while len(centList) < k:
        lowestSSE = inf
        for i in range(len(centList)):
            # 得到dataSet中行号与clusterAssment中所属的中心编号为i的行号对应的子集数据。
            ptsInCurrCluster = np.array(dataSet)[nonzero(clusterAssment[:,0].A == i)[0],:]
            # 在给定的簇上进行K-均值聚类,k值为2
            centroidMat,splitClustAss = kMeans(ptsInCurrCluster,2,distMeas)
           # 计算将该簇划分成两个簇后总误差
            sseSplit = sum(splitClustAss[:,1])
            sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A != i)[0],1])
           # 选择使得误差最小的那个簇进行划分
            if sseSplit + sseNotSplit < lowestSSE:
                bestCentToSplit = i
                bestNewCents = centroidMat.copy()
                bestClustAss = splitClustAss.copy()
                lowestSSE = sseSplit + sseNotSplit

       # 将需要分割的聚类中心下的点进行1划分
       # 新增的聚类中心编号为len(centList)
        bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
        bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
        clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:] = bestClustAss

        # 更新被分割的聚类中心的坐标
        centList[bestCentToSplit] = bestNewCents[0,:]
        # 增加聚类中心
        centList.append(bestNewCents[1,:])

    return centList,clusterAssment

#测试
datMat=loadDataSet('testSet2.txt')
myCentroids, clustAssing=biKmeans(datMat,3)
print(myCentroids)
print(clustAssing)
# myCentroids, clustAssing =kMeans(datMat, 4)
#
#画图
fig = plt.figure()
ax1 = fig.add_subplot(111)

#画出四个质心
for data in myCentroids:
    x = np.array(data)[0, 0]
    y = np.array(data)[0, 1]

    ax1.scatter(x, y, c='r', marker='x')

#按类别画出点
for i in range(len(datMat)):

    x = datMat[i][0]
    y = datMat[i][1]
    label=np.array(clustAssing[i])[0, 0]
    if (label == 0):
        ax1.scatter(x, y, c='r', marker='^')
    if (label == 1):
        ax1.scatter(x, y, c='g', marker='s')
    if (label == 2):
        ax1.scatter(x, y, c='b', marker='o')
    if (label == 3):
        ax1.scatter(x, y, c='y', marker='D')

#显示所画的图
plt.show()

代码结果:

机器学习实战笔记7—K-Means_第3张图片

代码四:算法应用:对地图上的点进行聚类

现在有一个存有70个地址和城市名的文本,而没有这些地点的距离信息。而我们想要对这些地点进行聚类,找到每个簇的质心地点,从而可以安排合理的行程,即质心之间选择交通工具抵达,而位于每个质心附近的地点就可以采取步行的方法抵达。显然,K-means算法可以为我们找到一种更加经济而且高效的出行方式。

 书上给出的yahooAPI的baseurl已经改变,github上有oauth2供python使用,但是yahoo的BOOS GEO好像OAuth2验证出了问题,虽然写了新的placeFinder调用api的代码,仍然会有403错误。

随书代码中已经给出place.txt,所以直接调用,这里略过获取数据的步骤。

#draw function
import matplotlib
import matplotlib.pyplot as plt
def clusterClubs(numClust=3):#参数:希望得到的簇数目
    datList = []
    for line in open('places.txt').readlines():#获取地图数据
        lineArr = line.split('\t')
        datList.append([float(lineArr[4]), float(lineArr[3])])#逐个获取第四列和第五列的经纬度信息
    datMat = mat(datList)
    myCentroids, clustAssing = biKmeans(datMat, numClust)
    #draw
    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)

    #画质心
    for data in myCentroids:
        x = np.array(data)[0, 0]
        y = np.array(data)[0, 1]
        ax1.scatter(x, y, c='r', marker='+', s=300)
    plt.show()

#测试
clusterClubs(3)

 

代码结果:

k=3

机器学习实战笔记7—K-Means_第4张图片

k=5

机器学习实战笔记7—K-Means_第5张图片

 

总结 

       聚类是一种无监督聚类算法,无监督指的是事先不知道所需要查找的内容(无目标变量)。聚类将数据点归入多个簇中,相似的数据点归入到同一个簇。有很多不同的方法来计算相似性。广泛使用的是K-均值算法:通过指定k值,随机分配k个质心,然后计算每个数据点到各个质心的距离,将点分配到距离最近的质心,重新计算每个簇的均值更新质心,反复迭代直到质心不在变化。(算法有效但初始k值不容易确定) 
      另一种是二分K-均值算法:首先将所有点作为一个簇,然后采用k=2的K-均值算法进行划分,下一次迭代时选择两个簇中 SSE(平方误差)最大的簇进行再次划分,直到簇数目达到给定的k值。二分K-均值的算法要优于K-均值算法,不容易收敛到局部最小。
 

欢迎扫码关注我的微信公众号

你可能感兴趣的:(ML)