1+ #coding=utf-8
2+ #Author:Harold
3+ #Date:2021-1-27
4+ #Email:zenghr_zero@163.com
5+
6+ '''
7+ 数据集:iris
8+ 数据集数量:150
9+ -----------------------------
10+ 运行结果:
11+ ARI:0.57
12+ 运行时长:0.0060s
13+ '''
14+
15+ import numpy as np
16+ import time
17+ import random
18+ from scipy .special import comb
19+ import matplotlib .pyplot as plt
20+ % matplotlib inline
21+
22+
23+ #定义加载数据的函数,这里以鸢尾花数据集为例
24+ def load_data (file ):
25+ '''
26+ INPUT:
27+ file - (str) 数据文件的路径
28+
29+ OUTPUT:
30+ Xarray - (array) 特征数据数组
31+ Ylist - (list) 类别标签列表
32+
33+ '''
34+ Xlist = [] #定义一个列表用来保存每条数据
35+ Ylist = [] #定义一个列表用来保存每条数据的类别标签
36+ fr = open (file )
37+ for line in fr .readlines (): #逐行读取数据,鸢尾花数据集每一行表示一个鸢尾花的特征和类别标签,用逗号分隔
38+ cur = line .split (',' )
39+ label = cur [- 1 ]
40+ X = [float (x ) for x in cur [:- 1 ]] #用列表来表示一条特征数据
41+ Xlist .append (X )
42+ Ylist .append (label )
43+ Xarray = np .array (Xlist ) #将特征数据转换为数组类型,方便之后的操作
44+ print ('Data shape:' , Xarray .shape )
45+ print ('Length of labels:' , len (Ylist ))
46+ return Xarray , Ylist
47+
48+
49+ #定义标准化函数,对每一列特征进行min-max标准化,将数据缩放到0-1之间
50+ #标准化处理对于计算距离的机器学习方法是非常重要的,因为特征的尺度不同会导致计算出来的距离倾向于尺度大的特征,为保证距离对每一列特征都是公平的,必须将所有特征缩放到同一尺度范围内
51+ def Normalize (Xarray ):
52+ '''
53+ INPUT:
54+ Xarray - (array) 特征数据数组
55+
56+ OUTPUT:
57+ Xarray - (array) 标准化处理后的特征数据数组
58+
59+ '''
60+ for f in range (Xarray .shape [1 ]):
61+ maxf = np .max (Xarray [:, f ])
62+ minf = np .min (Xarray [:, f ])
63+ for n in range (Xarray .shape [0 ]):
64+ Xarray [n ][f ] = (Xarray [n ][f ]- minf ) / (maxf - minf )
65+ return Xarray
66+
67+
68+ #定义计算两条数据间的距离的函数,这里计算的是欧式距离
69+ def cal_distance (xi , xj ):
70+ '''
71+ INPUT:
72+ Xi - (array) 第i条特征数据
73+ Xj - (array) 第j条特征数据
74+
75+ OUTPUT:
76+ dist - (float) 两条数据的欧式距离
77+
78+ '''
79+ dist = 0
80+ for col in range (len (xi )):
81+ dist += (xi [col ]- xj [col ]) ** 2
82+ return dist
83+
84+
85+ #定义计算类中心的函数,以当前类中所包含数据的各个特征均值作为新的新的类中心
86+ def cal_groupcenter (group , Xarray ):
87+ '''
88+ INPUT:
89+ group - (list) 类所包含的数据列表
90+ Xarray - (array) 特征数据数组
91+
92+ OUTPUT:
93+ center - (array) 新的类中心
94+
95+ '''
96+ center = np .zeros (Xarray .shape [1 ])
97+ for i in range (Xarray .shape [1 ]):
98+ for n in group :
99+ center [i ] += Xarray [n ][i ] #计算当前类中第i个特征的数据之和
100+ center = center / Xarray .shape [0 ] #计算各个特征的均值
101+ return center
102+
103+
104+ #定义计算调整兰德系数(ARI)的函数,调整兰德系数是一种聚类方法的常用评估方法
105+ def Adjusted_Rand_Index (group_dict , Ylist , k ):
106+ '''
107+ INPUT:
108+ group_dict - (dict) 类别字典
109+ Ylist - (list) 类别标签列表
110+ k - (int) 设定的类别数
111+
112+ OUTPUT:
113+ (int) 调整兰德系数
114+
115+ '''
116+ group_array = np .zeros ((k , k )) #定义一个数组,用来保存聚类所产生的类别标签与给定的外部标签各类别之间共同包含的数据数量
117+ ylabel = list (set (Ylist )) #Ylist保存的标签为字符串,用ylabel来保存各个标签,在y_dict中类别以标签在ylabel列表中的索引值来表示类
118+ y_dict = {i :[] for i in range (k )} #定义一个空字典,用来保存外部标签中各类所包含的数据,结构与group_dict相同
119+ for i in range (len (Ylist )):
120+ y_dict [ylabel .index (Ylist [i ])].append (i )
121+ #循环计算group_array的值
122+ for i in range (k ):
123+ for j in range (k ):
124+ for n in range (len (Ylist )):
125+ if n in group_dict [i ] and n in y_dict [j ]:
126+ group_array [i ][j ] += 1 #如果数据n同时在group_dict的类别i和y_dict的类别j中,group_array[i][j]的数值加一
127+ RI = 0 #定义兰德系数(RI)
128+ sum_i = np .zeros (3 ) #定义一个数组,用于保存聚类结果group_dict中每一类的个数
129+ sum_j = np .zeros (3 ) #定义一个数组,用于保存外部标签y_dict中每一类的个数
130+ for i in range (k ):
131+ for j in range (k ):
132+ sum_i [i ] += group_array [i ][j ]
133+ sum_j [j ] += group_array [i ][j ]
134+ if group_array [i ][j ] >= 2 :
135+ RI += comb (group_array [i ][j ], 2 ) #comb用于计算group_array[i][j]中两两组合的组合数
136+ ci = 0 #ci保存聚类结果中同一类中的两两组合数之和
137+ cj = 0 #cj保存外部标签中同一类中的两两组合数之和
138+ for i in range (k ):
139+ if sum_i [i ] >= 2 :
140+ ci += comb (sum_i [i ], 2 )
141+ for j in range (k ):
142+ if sum_j [j ] >= 2 :
143+ cj += comb (sum_j [j ], 2 )
144+ E_RI = ci * cj / comb (len (Ylist ), 2 ) #计算RI的期望
145+ max_RI = (ci + cj ) / 2 #计算RI的最大值
146+ return (RI - E_RI ) / (max_RI - E_RI ) #返回调整兰德系数的值
147+
148+
149+ #定义k均值聚类函数
150+ def Kmeans (Xarray , k , iters ):
151+ '''
152+ INPUT:
153+ Xarray - (array) 特征数据数组
154+ k - (int) 设定的类别数
155+ iters - (int) 设定的迭代次数
156+
157+ OUTPUT:
158+ group_dict - (dict) 类别字典
159+ scores - (int) 每次迭代的ARI得分列表
160+
161+ '''
162+ center_inds = random .sample (range (Xarray .shape [0 ]), k ) #从特征数据中随机抽取k个数据索引
163+ centers = [Xarray [ci ] for ci in center_inds ] #将这k个数据索引所对应的特征数据作为初始的k个聚类中心
164+ scores = [] #定义一个空列表用来保存每次迭代的ARI得分
165+ for i in range (iters ):
166+ group_dict = {i :[] for i in range (k )} #定义一个空字典,用于保存聚类所产生的所有类别,其中字典的键为类别标签,值为类别所包含的数据列表,以索引表示每条数据
167+ print ('{}/{}' .format (i + 1 , iters ))
168+ #循环计算每条数据到各个聚类中心的距离
169+ for n in range (Xarray .shape [0 ]):
170+ dists = [] #保存第n条数据到各个聚类中心的距离
171+ for ci in range (k ):
172+ dist = cal_distance (Xarray [n ], centers [ci ])
173+ dists .append (dist )
174+ g = dists .index (min (dists )) #取距离最近的中心所在的类
175+ group_dict [g ].append (n ) #将第n条数据的索引n保存到g类
176+ print (group_dict )
177+ for i in range (k ):
178+ centers [i ] = cal_groupcenter (group_dict [i ], Xarray ) #根据每一类所包含的数据重新计算类中心
179+ scores .append (Adjusted_Rand_Index (group_dict , Ylist , k )) #将该轮迭代的ARI得分保存到scores列表
180+ return group_dict , scores
181+
182+
183+ if __name__ == "__main__" :
184+ Xarray , Ylist = load_data ('..\iris.data' ) #加载数据
185+ start = time .time () #保存开始时间
186+ Xarray = Normalize (Xarray ) #对特征数据进行标准化处理
187+ k = 3 #设定聚类数为3
188+ iters = 2 #设定迭代次数为2
189+ group_dict , scores = Kmeans (Xarray , k , iters ) #进行k均值聚类
190+ end = time .time () #保存结束时间
191+ print ('Time:' , end - start )
192+ plt .plot (range (iters ), scores ) #绘制ARI得分折线图
0 commit comments