Kmeans算法
1. 聚类算法族谱
这篇文章主要介绍了:聚类算法的特点,样本间距离,簇之间距离的计算方法以及衡量聚类算法性能的算法。
2. Kmeans算法简介
KMeans算法是一种动态聚类方法,其基本思路为:
- 先粗略地进行预分类;
- 然后再逐步调整;
- 直到把类分得比较合理为止。
这种分类方法较之系统聚类法,具有计算量较小、占用计算机存贮单元少、方法简单等优点,所以更适用于大样本的聚类分析。 该算法常用于向量量化和图像分隔等领域。
2.1 数据准备
假设有样本集数据 D = { x 1 , x 2 , . . . , x m } D=\{\boldsymbol{x_1}, \boldsymbol{x_2},...,\boldsymbol{x_m}\} D={x1,x2,...,xm},每个样本数据有 d d d个维度的向量。
2.2 评判标准
如果
∣
C
i
∣
|C_i|
∣Ci∣是第
i
i
i聚类
C
i
C_i
Ci的样本数量,则该类样本均值(
μ
i
\boldsymbol{\mu_i}
μi)有
μ
i
=
1
∣
C
i
∣
∑
x
∈
C
i
x
(2.1)
\boldsymbol{\mu_i} = \frac{1}{|C_i|}\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x} \tag{2.1}
μi=∣Ci∣1x∈Ci∑x(2.1)
在整个数据集上的误差(误差平方和)有:
E
=
∑
i
=
1
C
∑
x
∈
C
i
(
x
−
μ
i
)
(2.2)
E = \sum_{i=1}^C\sum_{\boldsymbol{x} \in C_i}(\boldsymbol{x}-\boldsymbol{\mu_i}) \tag{2.2}
E=i=1∑Cx∈Ci∑(x−μi)(2.2)
2.3 求解思路
如果把某一样本
x
′
\boldsymbol{x'}
x′从
C
i
C_i
Ci类移动到
C
j
C_j
Cj类,则仅仅这两类发生了变化,而其他类不发生变化,所以,经过调整后,
C
i
C_i
Ci类移动前的均值(
μ
i
\boldsymbol{\mu_i}
μi)与移动后的均值(
μ
i
′
\boldsymbol{\mu_i'}
μi′)均值的变化如下所示:
{
μ
i
′
=
1
∣
C
i
∣
−
1
(
∑
x
∈
C
i
x
−
x
′
)
μ
i
=
1
∣
C
i
∣
(
∑
x
∈
C
i
x
)
(2.3)
\begin{aligned} \left\{\begin{matrix} \boldsymbol{\mu_i'}=&\frac{1}{|C_i|-1}(\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-\boldsymbol{x}') \\ \boldsymbol{\mu_i}=&\frac{1}{|C_i|}(\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}) \end{matrix}\right. \tag{2.3} \end{aligned}
{μi′=μi=∣Ci∣−11(∑x∈Cix−x′)∣Ci∣1(∑x∈Cix)(2.3)
用
μ
i
′
−
μ
i
\boldsymbol{\mu_i'}-\boldsymbol{\mu_i}
μi′−μi可得:
μ
i
′
−
μ
i
=
∑
x
∈
C
i
x
−
x
′
∣
C
i
∣
−
1
−
∑
x
∈
C
i
x
∣
C
i
∣
=
∣
C
i
∣
∑
x
∈
C
i
x
−
∣
C
i
∣
x
′
−
∣
C
i
∣
∑
x
∈
C
i
x
+
∑
x
∈
C
i
x
(
∣
C
i
∣
−
1
)
∣
C
i
∣
=
−
∣
C
i
∣
x
′
+
∑
x
∈
C
i
x
(
∣
C
i
∣
−
1
)
∣
C
i
∣
=
−
x
′
+
μ
i
∣
C
i
∣
−
1
μ
i
′
=
μ
i
+
−
x
′
+
μ
i
∣
C
i
∣
−
1
(2.4)
\begin{aligned} \boldsymbol{\mu_i'}-\boldsymbol{\mu_i}=&\frac{\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-\boldsymbol{x}'}{|C_i|-1} - \frac{\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{|C_i|} \\ =&\frac{|C_i|\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}-|C_i|\boldsymbol{x'}-|C_i|\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}+\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{(|C_i|-1)|C_i|}\\ =&\frac{-|C_i|\boldsymbol{x'}+\sum_{\boldsymbol{x} \in C_i}\boldsymbol{x}}{(|C_i|-1)|C_i|}\\ =&\frac{-\boldsymbol{x'}+\boldsymbol{\mu_i}}{|C_i|-1}\\ \boldsymbol{\mu_i'}=&\boldsymbol{\mu_i}+\frac{-\boldsymbol{x'}+\boldsymbol{\mu_i}}{|C_i|-1}\\ \tag{2.4} \end{aligned}
μi′−μi====μi′=∣Ci∣−1∑x∈Cix−x′−∣Ci∣∑x∈Cix(∣Ci∣−1)∣Ci∣∣Ci∣∑x∈Cix−∣Ci∣x′−∣Ci∣∑x∈Cix+∑x∈Cix(∣Ci∣−1)∣Ci∣−∣Ci∣x′+∑x∈Cix∣Ci∣−1−x′+μiμi+∣Ci∣−1−x′+μi(2.4)
同理,对于
C
j
C_j
Cj类均值则变化为:
μ
j
′
=
μ
j
+
x
′
−
μ
j
∣
C
j
∣
+
1
(2.5)
\begin{aligned} \boldsymbol{\mu_j'}=&\boldsymbol{\mu_j}+\frac{\boldsymbol{x'}-\boldsymbol{\mu_j}}{|C_j|+1} \tag{2.5} \end{aligned}
μj′=μj+∣Cj∣+1x′−μj(2.5)
两类各自的平方和发生的变化如下:
{
E
i
=
∑
x
∈
C
i
∣
∣
x
−
μ
i
∣
∣
2
E
i
′
=
∑
x
∈
C
i
∣
∣
x
−
μ
i
′
∣
∣
2
−
∣
∣
x
′
−
μ
i
′
∣
∣
2
(2.6)
\begin{aligned} \left\{\begin{matrix} E_i=&\sum_{\boldsymbol{x} \in C_i}||\boldsymbol{x}-\boldsymbol{\mu_i}||^2 \\ E_i'=&\sum_{\boldsymbol{x} \in C_i}||\boldsymbol{x}-\boldsymbol{\mu_i'}||^2-||\boldsymbol{x'}-\boldsymbol{\mu_i'}||^2 \end{matrix}\right. \tag{2.6} \end{aligned}
{Ei=Ei′=∑x∈Ci∣∣x−μi∣∣2∑x∈Ci∣∣x−μi′∣∣2−∣∣x′−μi′∣∣2(2.6)
得出
E
i
′
=
E
i
−
∣
C
i
∣
∣
C
i
∣
−
1
∣
∣
x
′
−
μ
i
∣
∣
2
(2.7)
\begin{aligned} E_i'=&E_i-\frac{|C_i|}{|C_i|-1}||\boldsymbol{x'}-\boldsymbol{\mu_i}||^2 \tag{2.7} \end{aligned}
Ei′=Ei−∣Ci∣−1∣Ci∣∣∣x′−μi∣∣2(2.7)
同理
E
j
′
=
E
j
+
∣
C
j
∣
∣
C
j
∣
+
1
∣
∣
x
′
−
μ
j
∣
∣
2
(2.8)
\begin{aligned} E_j'=&E_j+\frac{|C_j|}{|C_j|+1}||\boldsymbol{x'}-\boldsymbol{\mu_j}||^2 \tag{2.8} \end{aligned}
Ej′=Ej+∣Cj∣+1∣Cj∣∣∣x′−μj∣∣2(2.8)
如果在调整的过程中,
E
i
′
>
E
j
′
E_i'>E_j'
Ei′>Ej′,则说明该步骤有 利于减小整体误差。
3. 算法描述
3.1 算法流程
- 初始化 k k k个聚类,计算各个类的均值( μ i , i = 1 , 2 , . . . , C \mu_i,i=1,2,...,C μi,i=1,2,...,C)以及总体的误差平方和( J c J_c Jc)。
- 随机取一个样本
x
′
\boldsymbol{x'}
x′,其对应的分类为
C
i
′
C_i'
Ci′
- 如果
- D ∣ C i ′ ∣ = = 1 D\ |C_i'|==1 D ∣Ci′∣==1:返回上一步,重新选择样本;
- 否则
- 计算将
x
′
\boldsymbol{x'}
x′移出第
i
i
i类误差平方和的变化量
ρ
i
\rho_i
ρi
ρ i = ∣ C i ∣ ∣ C j − 1 ∣ ∣ ∣ x ′ − μ i ∣ ∣ 2 \rho_i = \frac{|C_i|}{|C_j-1|}||\boldsymbol{x'}-\boldsymbol{\mu_i}||^2 ρi=∣Cj−1∣∣Ci∣∣∣x′−μi∣∣2 - 从所有分类中找出将样本
x
′
\boldsymbol{x'}
x′移入第
j
j
j类,误差最小的误差平方和
ρ
j
\rho_j
ρj。
ρ j = ∣ C j ∣ ∣ C j + 1 ∣ ∣ ∣ x ′ − μ j ∣ ∣ 2 \rho_j = \frac{|C_j|}{|C_j+1|}||\boldsymbol{x'}-\boldsymbol{\mu_j}||^2 ρj=∣Cj+1∣∣Cj∣∣∣x′−μj∣∣2 - 如果 ρ i > ρ j \rho_i > \rho_j ρi>ρj(移出成本大于移入成本):则把 x ′ \boldsymbol{x'} x′移入到第 j j j类中。
- 重新计算发生变化两类的均值以及总体误差平方和。
- 计算将
x
′
\boldsymbol{x'}
x′移出第
i
i
i类误差平方和的变化量
ρ
i
\rho_i
ρi
- 如果
- 若连续 N N N代总体误差平方和不改变,则停止,否则转向步骤2。
算法流程1中初始化 k k k个聚类的步骤
选择代表向量
- 经验选择法
- 所有样本随机分成 C C C类,计算每类重心。
- “密度”法选择
- 随机选择 C C C个样本作为代表向量
- 从
(
c
−
1
)
(c-1)
(c−1)聚类划分问题的解中产生
c
c
c聚类划分问题的代表点。思路如下:
- 先把整个样本空间看成一个类,计算均值;
- 选择最远的点作为第二类的代表向量;
- 再计算 ∣ C ∣ − 1 |C|-1 ∣C∣−1个点的均值,依次类推选择第三类的代表向量。
一般选择方法2和方法4。
初始类划分
- 其余点离哪个代表点近则归为该类。
- 其余点离哪个代表点近则归为该类,然后重新计算该类的均值,直到归类完毕。
- 现将数据标准化,用
y
i
j
y_{ij}
yij表示标准化后第
i
i
i个样本的第
j
j
j个特征,令
S U M ( i ) = ∑ j = 1 d y i j (3.1-1) SUM(i)=\sum_{j=1}^dy_{ij} \tag{3.1-1} SUM(i)=j=1∑dyij(3.1-1)
M A = max i S U M ( i ) (3.1-2) MA=\max_i SUM(i) \tag{3.1-2} MA=imaxSUM(i)(3.1-2)
M I = min i S U M ( i ) (3.1-3) MI=\min_i SUM(i) \tag{3.1-3} MI=iminSUM(i)(3.1-3)
若将样本分为 C C C类,则对每个样本计算
( C − 1 ) [ S U M ( i ) − M I ] ( M A − M I ) + 1 \frac{(C-1)[SUM(i)-MI]}{(MA-MI)}+1 (MA−MI)(C−1)[SUM(i)−MI]+1
该值最接近的整数 k k k,则将第 i i i个样本归为第 k k k类。
4. 代码实现
代码地址
KMeans类实现
class KMeans(object):
def __init__(self, cluster_num=5):
'''
初始化各簇的均值向量
:param cluster_num: 簇的数量
'''
self.cluster_num = cluster_num
self.nums = 0
self.features = 0
self.cluster = {}
for i in range(self.cluster_num):
self.cluster[i] = {
'mean_vec': None,
'data': None
}
def __initArgs__(self, data):
'''
随机选择cluster_num个样本值作为初始化的均值向量
:param data: 数据集
:return:
'''
self.nums, self.features = data.shape
index = np.random.randint(1, self.nums, size=self.cluster_num)
for key, value in enumerate(index):
self.cluster[key]['mean_vec'] = data[value]
self.cluster[key]['data'] = np.array([data[value]])
def fit(self, data, iteration):
# 1. 随机选取 cluster_num 个向量作为簇均值
self.__initArgs__(data)
for iters in range(iteration):
# 2. 遍历样本空间,将每个样本进行归类
for idata in data:
min_dist = float(np.inf)
cluster_name = 0
# 选取一个最近的簇
for icluster in self.cluster.keys():
dist = DistMinkov(self.cluster[icluster]['mean_vec'], idata, p=2)
if min_dist >= dist:
min_dist = dist
cluster_name = icluster
# 将该条数据添加至最近的簇中
self.cluster[cluster_name]['data'] = np.r_[
self.cluster[cluster_name]['data'],
np.array([idata])
]
# 3. 更新簇均值向量
stop = True
for icluster in self.cluster.keys():
# 计算新的簇心均值
new_mean_vec = np.mean(
self.cluster[icluster]['data'], axis=0
)
# 重置数据
self.cluster[icluster]['data'] = np.array([new_mean_vec])
if ~(self.cluster[icluster]['mean_vec'] == new_mean_vec).all():
self.cluster[icluster]['mean_vec'] = new_mean_vec
stop = False
if stop == True:
break
return self.cluster
读取数据
def load_data(filename):
'''
从文件中读取数据
:param self:
:param filename: 文件路径和文件名称
:return: 返回读取的数据
'''
data = pd.read_csv(
filename,
sep='\t',
header=None
)
return data
主函数调用
if __name__ == '__main__':
import numpy as np
import pandas as pd
from Cluster.DistanceClass import DistMinkov
filename = 'data/testSet.txt'
data = load_data(filename)
clf = KMeans(4)
cluster = clf.fit(data.values, 100)
for i in cluster.keys():
print(cluster[i]['mean_vec'])
5. KMeans缺陷以及改进方法
5.1 缺陷
- 容易收敛到局部极大值。
- 分类的数量不易指定。
5.2 改进方法
5.2.1 针对分类的数量问题
5.2.1.1 合并簇
簇的数量越多,说明其分类种类越多,簇内部数据点也越接近其质心。因此,可以通过以下思路提高聚类效果:
预先生成多个簇,然后将相近的簇进行合并。
合并的标准为:
- 各簇簇心之间的距离小于某一正数。
- 合并两个簇,计算其合并后簇内平均距离,若合并后的簇内平均距离相比于合并前的距离增幅最小,则代表两个簇可合并。
5.2.1.2 拆分簇
在簇内平均距离最大的簇使用KMeans算法,指定簇的数量为2,然后再调用合并簇算法。
5.2.2 针对容易收敛到局部极大值的问题
采用二分KMeans算法,具体算法流程如下:
- 输入数据集 D = { x 1 , x 2 , . . . , x N } D=\{x_1, x_2,..., x_N\} D={x1,x2,...,xN}
- 初始化簇集合为 C = { C 1 } C=\{C_1\} C={C1}
- 当集合
C
C
C的数量少于指定数量时:
- 计算簇集合内每个簇的平均距离,记为 a v g ( D i ) avg(D_i) avg(Di)
- 将每一个簇都使用KMeans算法,指定 K = 2 K=2 K=2,记划分后的两个簇为 D i 1 , D i 2 D_{i1}, D_{i2} Di1,Di2;
- 计算
D
i
1
,
D
i
2
D_{i1}, D_{i2}
Di1,Di2平均距离之和
a
v
g
(
D
i
1
)
+
a
v
g
(
D
i
2
)
avg(D_{i1})+avg(D_{i2})
avg(Di1)+avg(Di2)
- 如果:
a
v
g
(
D
i
1
)
+
a
v
g
(
D
i
2
)
<
a
v
g
(
D
i
)
avg(D_{i1})+avg(D_{i2}) < avg(D_i)
avg(Di1)+avg(Di2)<avg(Di):
- 划分成功,将 D i 1 , D i 2 D_{i1}, D_{i2} Di1,Di2对应的簇标签加入簇集合 C C C
- 否则:
- 不划分。
- 如果:
a
v
g
(
D
i
1
)
+
a
v
g
(
D
i
2
)
<
a
v
g
(
D
i
)
avg(D_{i1})+avg(D_{i2}) < avg(D_i)
avg(Di1)+avg(Di2)<avg(Di):
- 如果簇集合
C
C
C的数量达到指定数量:
- 返回划分结果
- 否则:
- 重复上述操作。
6. 参考文献
- 《机器学习实战》
- 《西瓜书》