1. K-Means聚类算法

K-Means算法的伪代码如下所示:

创建k个点作为起始质心(经常是随机选择)
当任意一个点的簇分配结果发生改变时
    对数据集中的每个数据点
        对每个质心
            计算质心与数据点之间的距离
        将数据点分配到距其最近的簇
    对每一个簇,计算簇中所有点的均值并将其均值作为质心
In [1]:
import kMeans
In [2]:
from numpy import *
In [3]:
dataMat = mat(kMeans.loadDataSet('testSet.txt'))
In [4]:
min(dataMat[:,0])
Out[4]:
matrix([[-5.379713]])
In [5]:
min(dataMat[:,1])
Out[5]:
matrix([[-4.232586]])
In [6]:
max(dataMat[:,1])
Out[6]:
matrix([[ 5.1904]])
In [7]:
max(dataMat[:,0])
Out[7]:
matrix([[ 4.838138]])
In [8]:
kMeans.randCent(dataMat, 2)
Out[8]:
matrix([[-4.56034256, -2.25146239],
        [ 1.20441377,  2.68181644]])
In [9]:
kMeans.distEclud(dataMat[0], dataMat[1])
Out[9]:
5.184632816681332
In [10]:
reload(kMeans) #注意在写kMeans的时候,重新计算centroid的循环与计算每个点最近质心的循环平级,而不是嵌套(即注意缩进)
Out[10]:
<module 'kMeans' from 'kMeans.pyc'>
In [11]:
dataMat = mat(kMeans.loadDataSet('testSet.txt'))
In [12]:
myCentroids, clustAssing = kMeans.kMeans(dataMat, 4)
m = 80
[[-2.28373583 -3.578145  ]
 [ 3.04131824 -0.947461  ]
 [-1.36109787  1.97698553]
 [-4.43759314 -3.119445  ]]
[[-1.7671259  -3.2956472 ]
 [ 3.10074744 -0.95429022]
 [-0.83070823  3.10911097]
 [-4.04883533 -2.77633633]]
[[-1.46335425 -3.40428925]
 [ 3.10012512 -1.31169504]
 [-0.59196673  3.13360545]
 [-3.89646064 -2.78844243]]
[[-1.05904467 -3.687141  ]
 [ 3.13545143 -1.61935713]
 [-0.40420449  3.08176623]
 [-3.74393844 -2.75935387]]
[[-0.28093075 -3.880518  ]
 [ 3.06500667 -2.04804633]
 [-0.17288957  3.07096154]
 [-3.61853111 -2.81946867]]
[[-0.28093075 -3.880518  ]
 [ 3.09814284 -2.43041226]
 [-0.02298687  2.99472915]
 [-3.61853111 -2.81946867]]
[[-0.28093075 -3.880518  ]
 [ 3.09814284 -2.43041226]
 [-0.02298687  2.99472915]
 [-3.61853111 -2.81946867]]
numIt = 7
In [13]:
print shape(dataMat)
(80, 2)
In [14]:
print shape(dataMat[:,0].flatten())
(1, 80)
In [15]:
dataMat[:,0].flatten().A[0]
Out[15]:
array([ 1.658985, -3.453687,  4.838138, -5.379713,  0.972564, -3.567919,
        0.450614, -3.487105,  2.668759, -3.156485,  3.165506, -2.786837,
        4.208187, -2.123337,  0.704199, -0.39237 ,  2.831667, -0.790153,
        2.943496, -3.195883,  2.336445, -1.786345,  2.190101, -3.403367,
        1.778124, -1.688346,  2.592976, -4.007257,  2.257734, -2.679011,
        0.939512, -3.674424,  2.046259, -3.18947 ,  4.372646, -2.579316,
        1.889034, -0.798747,  2.83652 , -3.837877,  2.096701, -2.709034,
        3.367037, -2.121479,  2.329546, -3.284816,  3.091414, -3.762093,
        3.542056, -1.736822,  2.127073, -4.323818,  3.792121, -4.786473,
        2.624081, -4.009299,  2.493525, -2.513661,  1.864375, -3.171184,
        2.89422 , -2.562539,  3.491078, -2.565729,  3.332948, -1.616805,
        2.280615, -2.651229,  2.321395, -1.685703,  3.031012, -4.599622,
        4.196223, -2.133863,  4.668892, -2.793241,  2.884105, -2.967647,
        4.479332, -4.905566])
In [16]:
myCentroids
Out[16]:
matrix([[-0.28093075, -3.880518  ],
        [ 3.09814284, -2.43041226],
        [-0.02298687,  2.99472915],
        [-3.61853111, -2.81946867]])
In [17]:
myCentroids[:,0].flatten().A[0]
Out[17]:
array([-0.28093075,  3.09814284, -0.02298687, -3.61853111])
In [18]:
myCentroids[:,1].flatten().A[0]
Out[18]:
array([-3.880518  , -2.43041226,  2.99472915, -2.81946867])

2. 使用后处理提高聚类性能

K-Means算法会收敛,但聚类效果也可能会较差。原因是K-Means算法收敛到了局部最小值,而非全局最小值。
一种用于度量聚类效果的指标是SSE(Sum of Squared Error, 误差平方和),对应代码kMeans.py中kMeans函数的clusterAssment矩阵的第二列之和。
SSE值越小表示数据点越接近它们的质心,聚类效果也越好。因为对误差取了平方,所以更重视那些远离中心的点。
一种肯定能降低SSE的方法是增加簇的个数,但这会违背聚类的目标。聚类的目标是在保持簇数目不变的情况下提高簇的质量

对于K-Means算法陷入局部最小值的情况,有以下的改进方法。可以对生成的簇进行后处理,将具有最大SSE值的簇划分成两个簇。(具体实现时可以将最大簇包含的点过滤出来,然后再上面继续运行K-Means,k值取2)

为保持簇总数不变,我们可以合并两个簇。有两种可以量化的方法:合并最近的质心,或者合并两个使得SSE增幅最小的质心。第一种方法通过计算所有质心之间的距离,然后合并距离最近的两个点来实现。第二种方法需要合并两个簇然后计算总的SSE值,但这种方法需要尝试所有可能的两个簇,不断重复该处理过程,直到找到合并最佳的两个簇为止。

3. 二分K-Means算法

该算法的提出是为了改进K-Means算法。
该算法首先将所有点作为一个簇,然后将该簇一分为二。之后选择其中一个簇继续进行划分,选择哪一个簇进行划分取决于对其划分是否可以最大程度降低SSE的值。上述基于SSE的划分过程不断重复,直到得到用户指定的簇数目为止。另一种做法是选择SSE最大的簇进行划分,直到簇数目达到用户指定的数目为止。

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

将所有点看成一个簇
当簇数目小于k时
对于每一个簇
    计算总误差
    在给定的簇上面进行K-Means聚类(k=2)
    计算该簇一分为二之后的总误差
选择使得误差最小的那个簇进行划分操作
In [19]:
reload(kMeans)
Out[19]:
<module 'kMeans' from 'kMeans.pyc'>
In [20]:
dataMat3 = mat(kMeans.loadDataSet('testSet2.txt'))
In [21]:
centList, myNewAssments = kMeans.biKmeans(dataMat3, 3)
centList: [[-0.15772275000000002, 1.2253301166666664]]
-------------------
m = 60
[[ 0.93680474 -0.87084665]
 [-1.3277349   3.46607079]]
[[ 0.63042504 -1.33843332]
 [-0.84735206  3.46862312]]
[[ 0.02053813 -2.21845543]
 [-0.26853357  3.36606168]]
[[-0.32150057 -2.62473743]
 [-0.06953469  3.29844341]]
[[-0.45965615 -2.7782156 ]
 [-0.00675605  3.22710297]]
[[-0.45965615 -2.7782156 ]
 [-0.00675605  3.22710297]]
numIt = 6
sseSplit, and notSplit:  453.033489581 0.0
the bestCentToSplit is:  0
the len of bestClustAss is:  60
[matrix([[-0.45965615, -2.7782156 ]]), matrix([[-0.00675605,  3.22710297]])]
-------------------
m = 20
[[-1.26405367 -2.209896  ]
 [ 0.19848727 -3.24320436]]
[[-1.1836084 -2.2507069]
 [ 0.2642961 -3.3057243]]
[[-1.12616164 -2.30193564]
 [ 0.35496167 -3.36033556]]
[[-1.12616164 -2.30193564]
 [ 0.35496167 -3.36033556]]
numIt = 4
sseSplit, and notSplit:  12.7532631369 423.876240137
m = 40
[[-3.06779095  3.33769884]
 [ 2.76275171  3.12704005]]
[[-2.94737575  3.3263781 ]
 [ 2.93386365  3.12782785]]
[[-2.94737575  3.3263781 ]
 [ 2.93386365  3.12782785]]
numIt = 3
sseSplit, and notSplit:  77.5922493178 29.1572494441
the bestCentToSplit is:  1
the len of bestClustAss is:  40
[matrix([[-0.45965615, -2.7782156 ]]), matrix([[-2.94737575,  3.3263781 ]]), matrix([[ 2.93386365,  3.12782785]])]
In [22]:
centList
Out[22]:
[matrix([[-0.45965615, -2.7782156 ]]),
 matrix([[-2.94737575,  3.3263781 ]]),
 matrix([[ 2.93386365,  3.12782785]])]
In [23]:
centList[0][:,0]
Out[23]:
matrix([[-0.45965615]])