人以类聚,物以群分,k-means聚类算法就是体现。数学公式不要,直接用白话描述的步骤就是:

  • 1.随机选取k个质心(k值取决于你想聚成几类)
  • 2.计算样本到质心的距离,距离质心距离近的归为一类,分为k类
  • 3.求出分类后的每类的新质心
  • 4.判断新旧质心是否相同,如果相同就代表已经聚类成功,如果没有就循环2-3直到相同

用程序的语言描述就是:

  • 1.输入样本
  • 2.随机去k个质心
  • 3.重复下面过程知道算法收敛: 
    计算样本到质心距离(欧几里得距离) 
    样本距离哪个质心近,就记为那一类 
  • 计算每个类别的新质心(平均值)
import numpy
import random
import codecs
import copy
import re
import matplotlib.pyplot as plt


def calcuDistance(vec1, vec2):
    # 计算向量vec1和向量vec2之间的欧氏距离
    return numpy.sqrt(numpy.sum(numpy.square(vec1 - vec2)))


def loadDataSet(inFile):
    # 载入数据测试数据集
    # 数据由文本保存,为二维坐标
    inDate = codecs.open(inFile, 'r', 'utf-8').readlines()
    dataSet = list()
    for line in inDate:
        line = line.strip()
        strList = re.split('[ ]+', line)  # 去除多余的空格
        # print strList[0], strList[1]
        numList = list()
        for item in strList:
            num = float(item)
            numList.append(num)
            # print numList
        dataSet.append(numList)

    return dataSet  # dataSet = [[], [], [], ...]


def initCentroids(dataSet, k):
    # 初始化k个质心,随机获取
    return random.sample(dataSet, k)  # 从dataSet中随机获取k个数据项返回


def minDistance(dataSet, centroidList):
    # 对每个属于dataSet的item,计算item与centroidList中k个质心的欧式距离,找出距离最小的,
    # 并将item加入相应的簇类中

    clusterDict = dict()  # 用dict来保存簇类结果
    for item in dataSet:
        vec1 = numpy.array(item)  # 转换成array形式
        flag = 0  # 簇分类标记,记录与相应簇距离最近的那个簇
        minDis = float("inf")  # 初始化为最大值

        for i in range(len(centroidList)):
            vec2 = numpy.array(centroidList[i])
            distance = calcuDistance(vec1, vec2)  # 计算相应的欧式距离
            if distance < minDis:
                minDis = distance
                flag = i  # 循环结束时,flag保存的是与当前item距离最近的那个簇标记

        if flag not in clusterDict.keys():  # 簇标记不存在,进行初始化
            clusterDict[flag] = list()
            # print flag, item
        clusterDict[flag].append(item)  # 加入相应的类别中

    return clusterDict  # 返回新的聚类结果


def getCentroids(clusterDict):
    # 得到k个质心
    centroidList = list()
    for key in clusterDict.keys():
        centroid = numpy.mean(numpy.array(clusterDict[key]), axis=0)  # 计算每列的均值,即找到质心
        # print key, centroid
        centroidList.append(centroid)

    return numpy.array(centroidList).tolist()


def getVar(clusterDict, centroidList):
    # 计算簇集合间的均方误差
    # 将簇类中各个向量与质心的距离进行累加求和

    sum = 0.0
    for key in clusterDict.keys():
        vec1 = numpy.array(centroidList[key])
        distance = 0.0
        for item in clusterDict[key]:
            vec2 = numpy.array(item)
            distance += calcuDistance(vec1, vec2)
        sum += distance

    return sum


def showCluster(centroidList, clusterDict):
    # 展示聚类结果

    colorMark = ['or', 'ob', 'og', 'ok', 'oy', 'ow']  # 不同簇类的标记 'or' --> 'o'代表圆,'r'代表red,'b':blue
    centroidMark = ['dr', 'db', 'dg', 'dk', 'dy', 'dw']  # 质心标记 同上'd'代表棱形
    for key in clusterDict.keys():
        plt.plot(centroidList[key][0], centroidList[key][1], centroidMark[key], markersize=12)  # 画质心点
        for item in clusterDict[key]:
            plt.plot(item[0], item[1], colorMark[key])  # 画簇类下的点

    plt.show()


if __name__ == '__main__':

    inFile = "C:\\Users\\zuo\\Desktop\\test.txt"  # 数据集文件
    dataSet = loadDataSet(inFile)  # 载入数据集
    centroidList = initCentroids(dataSet, 4)  # 初始化质心,设置k=4
    clusterDict = minDistance(dataSet, centroidList)  # 第一次聚类迭代
    newVar = getVar(clusterDict, centroidList)  # 获得均方误差值,通过新旧均方误差来获得迭代终止条件
    oldVar = -0.0001  # 旧均方误差值初始化为-1
    print
    '***** 第1次迭代 *****'
    print
    print
    '簇类'
    for key in clusterDict.keys():
        print
        (key, ' --> ', clusterDict[key])
    print
    ('k个均值向量: ', centroidList)
    print
    ('平均均方误差: ', newVar)
    print()
    showCluster(centroidList, clusterDict)  # 展示聚类结果

    k = 2
    while abs(newVar - oldVar) >= 0.0001:  # 当连续两次聚类结果小于0.0001时,迭代结束
        centroidList = getCentroids(clusterDict)  # 获得新的质心
        clusterDict = minDistance(dataSet, centroidList)  # 新的聚类结果
        oldVar = newVar
        newVar = getVar(clusterDict, centroidList)

        print
        '***** 第%d次迭代 *****' % k
        print
        print
        '簇类'
        for key in clusterDict.keys():
            print
            (key, ' --> ', clusterDict[key])
        print
        ('k个均值向量: ', centroidList)
        print
        ('平均均方误差: ', newVar)
        print()
        showCluster(centroidList, clusterDict)  # 展示聚类结果

        k += 1
-0.798747   2.185588    
2.836520    -2.658556    
-3.837877   -3.253815    
2.096701    3.886007    
-2.709034   2.923887    
3.367037    -3.184789    
-2.121479   -4.232586    
2.329546    3.179764    
-3.284816   3.273099    
3.091414    -3.815232    
-3.762093   -2.432191    
3.542056    2.778832    
-1.736822   4.241041    
2.127073    -2.983680    
-4.323818   -3.938116    
3.792121    5.135768    
-4.786473   3.358547    
2.624081    -3.260715    
-4.009299   -2.978115    
2.493525    1.963710    
-2.513661   2.642162    
1.864375    -3.176309    
-3.171184   -3.572452    
2.894220    2.489128    
-2.562539   2.884438    
3.491078    -3.947487    
-2.565729   -2.012114    
3.332948    3.983102    
-1.616805   3.573188    
2.280615    -2.559444    
-2.651229   -3.103198    
2.321395    3.154987    
-1.685703   2.939697    
3.031012    -3.620252    
-4.599622   -2.185829    
4.196223    1.126677    
-2.133863   3.093686    
4.668892    -2.562705    
-2.793241   -2.149706    
2.884105    3.043438    
-2.967647   2.848696    
4.479332    -1.764772    
-4.905566   -2.911070