11
11
12
12
13
13
# 从文本中构建矩阵,加载文本文件,然后处理
14
- def loadDataSet (fileName ): # 通用函数,用来解析以 tab 键分隔的 floats(浮点数)
14
+ def loadDataSet (fileName ): # 通用函数,用来解析以 tab 键分隔的 floats(浮点数)
15
15
dataMat = []
16
16
fr = open (fileName )
17
17
for line in fr .readlines ():
18
18
curLine = line .strip ().split ('\t ' )
19
- fltLine = map (float ,curLine ) # 映射所有的元素为 float(浮点数)类型
19
+ fltLine = map (float , curLine ) # 映射所有的元素为 float(浮点数)类型
20
20
dataMat .append (fltLine )
21
21
return dataMat
22
22
23
23
24
24
# 计算两个向量的欧式距离(可根据场景选择)
25
25
def distEclud (vecA , vecB ):
26
- return sqrt (sum (power (vecA - vecB , 2 ))) # la.norm(vecA-vecB)
26
+ return sqrt (sum (power (vecA - vecB , 2 ))) # la.norm(vecA-vecB)
27
27
28
28
29
29
# 为给定数据集构建一个包含 k 个随机质心的集合。随机质心必须要在整个数据集的边界之内,这可以通过找到数据集每一维的最小和最大值来完成。然后生成 0~1.0 之间的随机数并通过取值范围和最小值,以便确保随机点在数据的边界之内。
30
30
def randCent (dataSet , k ):
31
- n = shape (dataSet )[1 ] # 列的数量
32
- centroids = mat (zeros ((k ,n ))) # 创建k个质心矩阵
33
- for j in range (n ): # 创建随机簇质心,并且在每一维的边界内
34
- minJ = min (dataSet [:,j ]) # 最小值
35
- rangeJ = float (max (dataSet [:,j ]) - minJ ) # 范围 = 最大值 - 最小值
36
- centroids [:,j ] = mat (minJ + rangeJ * random .rand (k ,1 )) # 随机生成
31
+ n = shape (dataSet )[1 ] # 列的数量
32
+ centroids = mat (zeros ((k , n ))) # 创建k个质心矩阵
33
+ for j in range (n ): # 创建随机簇质心,并且在每一维的边界内
34
+ minJ = min (dataSet [:, j ]) # 最小值
35
+ rangeJ = float (max (dataSet [:, j ]) - minJ ) # 范围 = 最大值 - 最小值
36
+ centroids [:, j ] = mat (minJ + rangeJ * random .rand (k , 1 )) # 随机生成
37
37
return centroids
38
38
39
39
@@ -42,59 +42,77 @@ def randCent(dataSet, k):
42
42
# 这个过程重复数次,知道数据点的簇分配结果不再改变位置。
43
43
# 运行结果(多次运行结果可能会不一样,可以试试,原因为随机质心的影响,但总的结果是对的, 因为数据足够相似,也可能会陷入局部最小值)
44
44
def kMeans (dataSet , k , distMeas = distEclud , createCent = randCent ):
45
- m = shape (dataSet )[0 ] # 行数
46
- clusterAssment = mat (zeros ((m , 2 ))) # 创建一个与 dataSet 行数一样,但是有两列的矩阵,用来保存簇分配结果
47
- centroids = createCent (dataSet , k ) # 创建质心,随机k个质心
45
+ m = shape (dataSet )[0 ] # 行数
46
+ clusterAssment = mat (zeros (
47
+ (m , 2 ))) # 创建一个与 dataSet 行数一样,但是有两列的矩阵,用来保存簇分配结果
48
+ centroids = createCent (dataSet , k ) # 创建质心,随机k个质心
48
49
clusterChanged = True
49
50
while clusterChanged :
50
51
clusterChanged = False
51
- for i in range (m ): # 循环每一个数据点并分配到最近的质心中去
52
- minDist = inf ; minIndex = - 1
52
+ for i in range (m ): # 循环每一个数据点并分配到最近的质心中去
53
+ minDist = inf
54
+ minIndex = - 1
53
55
for j in range (k ):
54
- distJI = distMeas (centroids [j ,:],dataSet [i ,:]) # 计算数据点到质心的距离
55
- if distJI < minDist : # 如果距离比 minDist(最小距离)还小,更新 minDist(最小距离)和最小质心的 index(索引)
56
- minDist = distJI ; minIndex = j
57
- if clusterAssment [i , 0 ] != minIndex : # 簇分配结果改变
58
- clusterChanged = True # 簇改变
59
- clusterAssment [i , :] = minIndex ,minDist ** 2 # 更新簇分配结果为最小质心的 index(索引),minDist(最小距离)的平方
56
+ distJI = distMeas (centroids [j , :],
57
+ dataSet [i , :]) # 计算数据点到质心的距离
58
+ if distJI < minDist : # 如果距离比 minDist(最小距离)还小,更新 minDist(最小距离)和最小质心的 index(索引)
59
+ minDist = distJI
60
+ minIndex = j
61
+ if clusterAssment [i , 0 ] != minIndex : # 簇分配结果改变
62
+ clusterChanged = True # 簇改变
63
+ clusterAssment [
64
+ i , :] = minIndex , minDist ** 2 # 更新簇分配结果为最小质心的 index(索引),minDist(最小距离)的平方
60
65
print centroids
61
- for cent in range (k ): # 更新质心
62
- ptsInClust = dataSet [nonzero (clusterAssment [:, 0 ].A == cent )[0 ]] # 获取该簇中的所有点
63
- centroids [cent ,:] = mean (ptsInClust , axis = 0 ) # 将质心修改为簇中所有点的平均值,mean 就是求平均值的
66
+ for cent in range (k ): # 更新质心
67
+ ptsInClust = dataSet [nonzero (
68
+ clusterAssment [:, 0 ].A == cent )[0 ]] # 获取该簇中的所有点
69
+ centroids [cent , :] = mean (
70
+ ptsInClust , axis = 0 ) # 将质心修改为簇中所有点的平均值,mean 就是求平均值的
64
71
return centroids , clusterAssment
65
72
73
+
66
74
# 二分 KMeans 聚类算法, 基于 kMeans 基础之上的优化,以避免陷入局部最小值
67
75
def biKMeans (dataSet , k , distMeas = distEclud ):
68
76
m = shape (dataSet )[0 ]
69
- clusterAssment = mat (zeros ((m ,2 ))) # 保存每个数据点的簇分配结果和平方误差
70
- centroid0 = mean (dataSet , axis = 0 ).tolist ()[0 ] # 质心初始化为所有数据点的均值
71
- centList = [centroid0 ] # 初始化只有 1 个质心的 list
72
- for j in range (m ): # 计算所有数据点到初始质心的距离平方误差
73
- clusterAssment [j ,1 ] = distMeas (mat (centroid0 ), dataSet [j ,:])** 2
74
- while (len (centList ) < k ): # 当质心数量小于 k 时
77
+ clusterAssment = mat (zeros ((m , 2 ))) # 保存每个数据点的簇分配结果和平方误差
78
+ centroid0 = mean (dataSet , axis = 0 ).tolist ()[0 ] # 质心初始化为所有数据点的均值
79
+ centList = [centroid0 ] # 初始化只有 1 个质心的 list
80
+ for j in range (m ): # 计算所有数据点到初始质心的距离平方误差
81
+ clusterAssment [j , 1 ] = distMeas (mat (centroid0 ), dataSet [j , :])** 2
82
+ while (len (centList ) < k ): # 当质心数量小于 k 时
75
83
lowestSSE = inf
76
- for i in range (len (centList )): # 对每一个质心
77
- ptsInCurrCluster = dataSet [nonzero (clusterAssment [:,0 ].A == i )[0 ],:] # 获取当前簇 i 下的所有数据点
78
- centroidMat , splitClustAss = kMeans (ptsInCurrCluster , 2 , distMeas ) # 将当前簇 i 进行二分 kMeans 处理
79
- sseSplit = sum (splitClustAss [:,1 ]) # 将二分 kMeans 结果中的平方和的距离进行求和
80
- sseNotSplit = sum (clusterAssment [nonzero (clusterAssment [:,0 ].A != i )[0 ],1 ]) # 将未参与二分 kMeans 分配结果中的平方和的距离进行求和
81
- print "sseSplit, and notSplit: " ,sseSplit ,sseNotSplit
84
+ for i in range (len (centList )): # 对每一个质心
85
+ ptsInCurrCluster = dataSet [nonzero (
86
+ clusterAssment [:, 0 ].A == i )[0 ], :] # 获取当前簇 i 下的所有数据点
87
+ centroidMat , splitClustAss = kMeans (
88
+ ptsInCurrCluster , 2 , distMeas ) # 将当前簇 i 进行二分 kMeans 处理
89
+ sseSplit = sum (splitClustAss [:, 1 ]) # 将二分 kMeans 结果中的平方和的距离进行求和
90
+ sseNotSplit = sum (
91
+ clusterAssment [nonzero (clusterAssment [:, 0 ].A != i )[0 ],
92
+ 1 ]) # 将未参与二分 kMeans 分配结果中的平方和的距离进行求和
93
+ print "sseSplit, and notSplit: " , sseSplit , sseNotSplit
82
94
if (sseSplit + sseNotSplit ) < lowestSSE :
83
95
bestCentToSplit = i
84
96
bestNewCents = centroidMat
85
97
bestClustAss = splitClustAss .copy ()
86
98
lowestSSE = sseSplit + sseNotSplit
87
99
# 找出最好的簇分配结果
88
- bestClustAss [nonzero (bestClustAss [:,0 ].A == 1 )[0 ],0 ] = len (centList ) # 调用二分 kMeans 的结果,默认簇是 0,1. 当然也可以改成其它的数字
89
- bestClustAss [nonzero (bestClustAss [:,0 ].A == 0 )[0 ],0 ] = bestCentToSplit # 更新为最佳质心
90
- print 'the bestCentToSplit is: ' ,bestCentToSplit
100
+ bestClustAss [nonzero (bestClustAss [:, 0 ].A == 1 )[0 ], 0 ] = len (
101
+ centList ) # 调用二分 kMeans 的结果,默认簇是 0,1. 当然也可以改成其它的数字
102
+ bestClustAss [nonzero (bestClustAss [:, 0 ].A == 0 )[0 ],
103
+ 0 ] = bestCentToSplit # 更新为最佳质心
104
+ print 'the bestCentToSplit is: ' , bestCentToSplit
91
105
print 'the len of bestClustAss is: ' , len (bestClustAss )
92
106
# 更新质心列表
93
- centList [bestCentToSplit ] = bestNewCents [0 ,:].tolist ()[0 ] # 更新原质心 list 中的第 i 个质心为使用二分 kMeans 后 bestNewCents 的第一个质心
94
- centList .append (bestNewCents [1 ,:].tolist ()[0 ]) # 添加 bestNewCents 的第二个质心
95
- clusterAssment [nonzero (clusterAssment [:,0 ].A == bestCentToSplit )[0 ],:]= bestClustAss # 重新分配最好簇下的数据(质心)以及SSE
107
+ centList [bestCentToSplit ] = bestNewCents [0 , :].tolist ()[
108
+ 0 ] # 更新原质心 list 中的第 i 个质心为使用二分 kMeans 后 bestNewCents 的第一个质心
109
+ centList .append (
110
+ bestNewCents [1 , :].tolist ()[0 ]) # 添加 bestNewCents 的第二个质心
111
+ clusterAssment [nonzero (clusterAssment [:, 0 ].A == bestCentToSplit )[
112
+ 0 ], :] = bestClustAss # 重新分配最好簇下的数据(质心)以及SSE
96
113
return mat (centList ), clusterAssment
97
114
115
+
98
116
def testBasicFunc ():
99
117
# 加载测试数据集
100
118
datMat = mat (loadDataSet ('input/10.KMeans/testSet.txt' ))
@@ -112,6 +130,7 @@ def testBasicFunc():
112
130
# 最后测试一下距离计算方法
113
131
print ' distEclud(datMat[0], datMat[1])=' , distEclud (datMat [0 ], datMat [1 ])
114
132
133
+
115
134
def testKMeans ():
116
135
# 加载测试数据集
117
136
datMat = mat (loadDataSet ('input/10.KMeans/testSet.txt' ))
@@ -123,6 +142,7 @@ def testKMeans():
123
142
124
143
print 'centroids=' , myCentroids
125
144
145
+
126
146
def testBiKMeans ():
127
147
# 加载测试数据集
128
148
datMat = mat (loadDataSet ('input/10.KMeans/testSet2.txt' ))
@@ -131,6 +151,7 @@ def testBiKMeans():
131
151
132
152
print 'centList=' , centList
133
153
154
+
134
155
if __name__ == "__main__" :
135
156
136
157
# 测试基础的函数
0 commit comments