【K-means聚类】数学建模

2022年7月2日 下午2:36 数学建模

一、题目

给定10名成年男子运动员的100米、1500米、跳远、铅球、标枪成绩,设计算法对其运动水平进行高水平--低水平分类。

100米 1500米 跳远 铅球 标枪
10.92 03:42.2 5.24 16.37 76.94
11.45 03:58.3 5.68 12.58 57.21
10.35 03:49.9 6.3 13.51 47.39
12.91 04:28.4 6.67 15.21 76.19
13.03 04:36.5 4.99 18.95 57.54
11.67 04:08.1 7.03 12.44 44.44
11.21 03:56.8 6.66 12.21 56.35
12.90 04:12.7 5.84 14.86 63.80
11.31 03:51.1 5.53 13.61 63.80
10.53 03:55.5 6.02 12.80 50.49

二、问题重述

给定十个人的五项运动成绩数值,设计算法为其运动水平进行分类为“高水平“和”低水平“。

三、解决方法

本题用到了k-means聚类的方法,基于欧式距离的聚类算法,认为两个目标的距离越近,相似度越大,然后可以将其分为若干类,在这里是分为两类,分别为代表“高水平”和“低水平”。

3.1 初始化

将数据导入,这里将数据原封不动的存入data中,并且也设置name存放名字。

data = {
    '甲': [10.92, 222.2, 5.24, 16.37, 76.94],
    '乙': [11.45, 238.3, 5.68, 12.58, 57.21],
    '丙': [10.35, 229.9, 6.30, 13.51, 47.39],
    '丁': [12.91, 268.4, 6.67, 15.21, 76.19],
    '戊': [13.03, 276.5, 4.99, 18.95, 57.54],
    '己': [11.67, 248.1, 7.03, 12.44, 44.44],
    '庚': [11.21, 236.8, 6.66, 12.21, 56.35],
    '辛': [12.90, 252.7, 5.84, 14.86, 63.80],
    '壬': [11.31, 231.1, 5.53, 13.61, 63.80],
    '癸': [10.53, 235.5, 6.02, 12.80, 50.49]
}

name = ['甲', '乙', '丙', '丁', '戊', '己', '庚', '辛', '壬', '癸']

因为100米和1500米成绩数值是越低越好,跳远、铅球和标枪是越高越好,所以为了统一,可以选择对后三项乘以-1来变为负数,这样的话五项属性都是越低越好。

    cluster = 2 #分为两簇
    dimension = 5 #五项成绩,维度为5
    total_cluster = len(data) #总共有多少个人

    for i in data:
        data[i][2] = data[i][2]   * (-1.0)
        data[i][3] = data[i][3]   * (-1.0)
        data[i][4] = data[i][4]   * (-1.0)
    #取反处理

3.2 函数介绍

这里一开始需要选择两个点作为质心,用getClusterId()函数即可随机出两个数,如果相同的话则继续随机,直到不相同为止。

def getClusterId(name):
    x,y = random.randint(0, 9), random.randint(0, 9)#随机两个数,代表找两个人作为质心
    while x==y:#如果相同,那么继续随机,直到不相同为止
        x,y = random.randint(0, 9), random.randint(0, 9)
    return name[x], name[y]

算法需要计算欧氏距离,也就是上述所言,距离越近代表相似度越大,就越可能归为一类,欧氏距离:$dis\left(x,y\right)=\sqrt{\sum_{i=1}^{n}\left(x_i-y_i\right)^2}$

def getDistance(data, cur, tar):
    res = 0.0
    for i in range(len(cur)):#枚举所有的成绩
        res += (data[cur][i] - tar[i])**2#计算两个人的距离
    return res**0.5#欧几里得距离

3.3 K-means聚类算法

一开始需要随机选出两个点作为质心,来计算所有的数据到质心的距离进而进行下一步处理。

    x,y = getClusterId(name)
    cluster_center1,cluster_center2 = data[x],data[y]
    #print(cluster_center1,'\n',cluster_center2,sep='')

然后设置距离的列表,用于存放所有数据分别到所有质心的距离。

    dis = [[0.0]*cluster for v in range(total_cluster)]

这里需要引入两个列表,$cluster_type\left[i\right]$和$pre_cluster_type\left[i\right]$分别代表i的当前类型和前一个类型。之所以这么做是可以用于优化,当类型不变的时候,通过我们的算法可以得出质心是不会发生变化的,因为质心也就是当前簇的平均值。

    cluster_type = [ [0.0] for v in range(total_cluster) ]
    pre_cluster_type = cluster_type.copy()

然后我们设置最大迭代次数max_iter=1000(后面通过运行发现,实际上短短几次就可以结束迭代)。

    max_iter = 1000

然后正式开始迭代:

  • 1) 计算距离。
    遍历所有的数据,对所有质心求距离,会得到一个$10\ast2$的二维数组$dis\left[i\right]\left[j\right]$代表第i个人到与第j个的距离。
        for i in range(len(data)):
            x = getDistance(data,name[i],cluster_center1)
            y = getDistance(data,name[i],cluster_center2)
            #print(x,y)
            dis[i] = [x,y]
  • 2) 将数据归类。
    对于每一个人,比较其到二者的距离,取最小的放入1类;最大的则放入2类,并且统计个数,以便最后取平均值。
        numType1 = 0
        numType2 = 0 #统计个数
        #print(cluster_center1)
        #print(cluster_center2)
        for i in range(len(data)):
            if dis[i][0] < dis[i][1]:#距离较小的
                cluster_type[i] = 1#归为1类
                numType1 += 1#1类数量+1
            else:
                cluster_type[i] = 2#归为2类
                numType2 += 1#2类数量+1
        print('u=',u)
        print('pre:',pre_cluster_type)
        print('cur:',cluster_type)

  • 3) 判断是否迭代结束。
if cluster_type == pre_cluster_type:#如果两次的类型一样,那么结束循环
            break
        pre_cluster_type = cluster_type.copy()#否则pre继承这一次的结果,以便下一次判断
  • 4) 求和取平均,得到新的质心。
    如果迭代还在继续,则取每一簇的平均值作为新的质心,作用于下一次迭代。
sigma1 = [ 0.0 for v in range(dimension) ]
        sigma2 = [ 0.0 for v in range(dimension) ]


        for i in range(len(data)):
            for j in range(dimension):
                if cluster_type[i] == 1:
                    sigma1[j] += data[name[i]][j]#加入至对应类型的和
                else:
                    sigma2[j] += data[name[i]][j]#加入至对应类型的和

        #print('sigma1:',sigma1,'\nsigma2:',sigma2,sep='')

        for i in range(dimension):
            sigma1[i] /= numType1
            sigma2[i] /= numType2#对每一簇除以总个数即为平均数
        #sigma1和sigma2即为新的质心
        cluster_center1 = sigma1.copy()
        cluster_center2 = sigma2.copy()

四、运行结果

运行结果可以看出,这次运行随机到了四次,第四次的时候两类不变,推出迭代。
结果是$\left[2,2,2,1,1,2,2,1,2,2\right]$,也就是{甲,乙,丙,己,庚,壬,癸}和{丁,戊,辛}两类。
因为这是有一定的随机算法,所以进行多次实验。

第二次实验可以看出,迭代到了第二次发现类跟上一次不变,推出迭代,结果是$\left[1,1,1,2,2,1,1,2,1,1\right]$,与上一次答案虽然不一样,但是分出来的类也是一样的,同样是{甲,乙,丙,己,庚,壬,癸}和{丁,戊,辛}两类。
再进行10次运行可以发现,迭代次数到$u=\lbrace 1,2,3,1,5,1,2,1,1\rbrace$截至,经过多次实验,发现一般不超过5次即可迭代结束,原因是数据量太少,只有10个人;也发现有很多一次两次,原因也是因为分成的两类分别为7个和3个,这样也有数据量太少和分布有些许不均匀有关系,所以也很有可能选择质心就是我们所需要的,一次即可。

五、总结与分析

我们通过结果以及规律(常识),可以选出两类中的{甲,乙,丙,己,庚,壬,癸}为“高水平”运动员,而{丁,戊,辛}为“低水平”运动员。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注