# 只需 shift+回车 运行本单元格,就可以让jupyter notebook宽屏显示
from IPython.core.display import display, HTML
display(HTML('<style>.container { width:100% !important; }</style>'))
1 K-Means算法介绍
K K K均值聚类是基于样本集合划分的聚类算法的,通过将样本集合划分为 k k k个子集,构成 k k k个类,再将 n n n个样本分到 k k k个类中,每个样本到其类的中心的距离最小, k k k均值聚类为硬聚类。
1.1 K-Means算法推理
- 样本集合 X = { x 1 , x 2 , … , x n } X=\{x_1,x_2,\dots,x_n\} X={x1,x2,…,xn},每个样本由特征向量表示,其维度为 m m m。
- 目标是将 n n n个样本分到 k k k个不同的类或簇中,这里 k < n k<n k<n。
- k k k个类 G 1 , G 2 , … , G k G_1,G_2,\dots,G_k G1,G2,…,Gk形成对样本集合 X X X的划分,这里 G i ∩ G j = ∅ ; ⋃ i = 1 k G i = X ; i , j ≤ k G_i \cap G_j= \emptyset ; \bigcup \limits_{i=1} ^{k}{G_i}=X ;i,j\leq k Gi∩Gj=∅;i=1⋃kGi=X;i,j≤k。
- 用 C C C表示划分,一个划分对应着一个聚类的结果。 C C C是一个多对一的函数,把每一个样本用整数 i ∈ { 1 , 2 , … , n } i \in \{1,2,\dots,n\} i∈{1,2,…,n}表示,每个类也用一个整数 l ∈ { 1 , 2 , … , k } l \in \{1,2,\dots,k\} l∈{1,2,…,k}表示,划分或者聚类可以用函数 l = C ( i ) l=C(i) l=C(i)表示,即K-Means是一个从样本到类的函数。
样本之间的距离
d
(
x
i
,
x
j
)
d(x_i,x_j)
d(xi,xj)的计算采用欧式距离的平方:
d
(
x
i
,
x
j
)
=
∑
k
=
1
k
(
x
k
i
−
x
k
j
)
2
=
∥
x
i
−
x
j
∥
2
d(x_i,x_j)=\sum \limits_{k=1} ^k{(x_{ki}-x_{kj})^2}= \|x_i-x_j\|^2
d(xi,xj)=k=1∑k(xki−xkj)2=∥xi−xj∥2
损失函数是样本与与其所属的中心之间的距离总和,其表达式为:
W
(
C
)
=
∑
l
=
1
k
∑
C
(
i
)
=
l
∥
x
i
−
x
ˉ
l
∥
2
W(C)=\sum \limits_{l=1} ^k\sum \limits_{C(i)=l} \|x_i-\bar{x}_l\|^2
W(C)=l=1∑kC(i)=l∑∥xi−xˉl∥2
式中
x
ˉ
l
=
(
x
ˉ
1
l
,
x
ˉ
2
l
,
…
,
x
ˉ
m
l
)
T
\bar{x}_l=(\bar{x}_{1l},\bar{x}_{2l},\dots,\bar{x}_{ml})^T
xˉl=(xˉ1l,xˉ2l,…,xˉml)T是
l
l
l个类的均值或中心,
n
l
=
∑
k
=
1
n
I
(
C
(
i
)
=
l
)
n_l=\sum \limits_{k=1}^n{I(C(i)=l)}
nl=k=1∑nI(C(i)=l),
I
(
C
(
i
)
=
l
)
I(C(i)=l)
I(C(i)=l)为指示函数,取值为0或1,函数
W
(
C
)
W(C)
W(C)也称为能量,表示同类样本的相似程度。
K-Means求解最优化问题:
C
∗
=
a
r
g
min
C
W
(
C
)
=
a
r
g
min
C
∑
l
=
1
k
∑
C
(
i
)
=
l
∥
x
i
−
x
ˉ
l
∥
2
C^*=arg \min \limits_C W(C)=arg \min \limits_C \sum \limits_{l=1} ^k\sum \limits_{C(i)=l} \|x_i-\bar{x}_l\|^2
C∗=argCminW(C)=argCminl=1∑kC(i)=l∑∥xi−xˉl∥2
相似样本聚为同一类时,损失数值最小,这个目标函数的最优化能达到聚类的目的。将
n
n
n个样本分到
k
k
k类,所有可能分法的数目是:
S
(
n
,
k
)
=
1
k
!
∑
l
=
1
k
(
−
1
)
k
−
l
(
k
l
)
k
n
S(n,k)=\frac1{k!}\sum \limits_{l=1}^k(-1)^{k-l} \left( \begin{matrix} k\\ l\\ \end{matrix}\right)k^n
S(n,k)=k!1l=1∑k(−1)k−l(kl)kn
可见,显然这个种类很多,实际上则是通过迭代的方法求解。
1.2 K-Means算法流程
① 选择k个类的中心,将样本逐个指派到与其最近的 中心的类中,得到一个聚类的结果;
② 更新每个类的样本的均值,作为类的新的中心;
③ 重复以上步骤,直到收敛为止。
- 具体流程:
① 初始化。令 t = 0 t=0 t=0,随机选择 k k k个样本点作为初始聚类中心 m ( 0 ) = ( m 1 ( 0 ) , … , m l ( 0 ) , … , m k ( 0 ) ) m^{(0)}=\left(m_1^{(0)},\dots,m_l^{(0)},\dots,m_k^{(0)}\right) m(0)=(m1(0),…,ml(0),…,mk(0))。
② 对样本进行聚类。对固定的类中心 m ( t ) = ( m 1 ( t ) , … , m l ( t ) , … , m k ( t ) ) m^{(t)}=\left(m_1^{(t)},\dots,m_l^{(t)},\dots,m_k^{(t)}\right) m(t)=(m1(t),…,ml(t),…,mk(t)),其中 m l ( t ) m_l^{(t)} ml(t)为类 G l G_l Gl的中心,计算每个样本到类中心的距离,然后将每个样本指派到与其最近的中心的类,从而得到聚类结果 C ( t ) C^{(t)} C(t)。
③ 计算新的类中心。对聚类结果 C ( t ) C^{(t)} C(t),计算当前各个样本的均值,作为新的类中心 m ( t + 1 ) = ( m 1 ( t + 1 ) , … , m l ( t + 1 ) , … , m k ( t + 1 ) ) m^{(t+1)}=\left(m_1^{(t+1)},\dots,m_l^{(t+1)},\dots,m_k^{(t+1)} \right) m(t+1)=(m1(t+1),…,ml(t+1),…,mk(t+1))。
④ 如果迭代收敛或者符合停止条件,输出 C ∗ = C ( t ) C^*=C^{(t)} C∗=C(t)。否则,令 t = t + 1 t=t+1 t=t+1,返回第②步。
- K-Means的算法复杂度是 O ( m n k ) O(mnk) O(mnk),其中 m m m是样本维数, n n n是样本个数, k k k是类别的个数。
1.3 K-Means算法特点与K值的确定
1.3.1 K-Means算法特点
① 基于划分的聚类方法;
② 类别k需要事先指定;
③ 以欧式距离平方表示样本之间的距离,以中心或者样本的均值表示类别;
④ 样本和其所属类别的中心之间的距离总和为最优化目标函数;
⑤ 得到的类别是平坦的、非层次化的;
⑥ 算法为迭代算法,不能保证全局最优。
2 K-Means算法Python程序实现
"""
据定义①,并根据欧式距离进行归类
"""
# 导入库
import numpy as np
import pandas as pd
from scipy import linalg
import random
import matplotlib.pyplot as plt
# 创建数据
test = np.random.randint(0,100,(140,3))
df = pd.DataFrame(test,columns=list("XYZ"))
# 用pandas查看数据
# df
def d_HM(p,x_ki,x_kj):
# p为选择的距离算法;x_ki,x_kj均为一行的二维数组
# 曼哈顿距离
if p == 1:
distance = np.fabs(x_ki - x_kj).sum()
# 欧式距离
elif p == 2:
a = x_ki - x_kj
distance = np.dot(a,a.T)**0.5
# 切比雪夫距离
elif p >= 99:
distance = np.fabs(x_ki - x_kj).max()
else:
a = x_ki - x_kj
d = 0
for i in range(len(a)):
c = abs(a[i]) ** p
d = d + c
distance = d ** (1 / p)
return distance.round(3)
def chose_original_center(t,df):
df = df
# t 为返回中心点的索引
a = set(df.index)
if t < len(df):
# 随机选择t个初始聚类中心
b = set(random.sample(list(range(len(df))),t))
center = df.loc[list(b)]
center.reset_index(drop=True,inplace=True)
else:
print("范围超出,请重新选择初始聚类中心的个数")
not_center = df.loc[list(a-b)]
not_center.reset_index(drop=True,inplace=True)
return center,not_center
def Kmeans_step1(t,df):
center,not_center = chose_original_center(t,df)
G = []
for i in not_center.index:
d = []
for j in center.index:
dd = d_HM(2,not_center.loc[i].values,center.loc[j].values)
d.append(dd)
for m,n in enumerate(d):
if d[m] == min(d):
G.append([m,i])
GG = []
for i in range(t):
a = [center.loc[i].tolist()]
for j in G:
if j[0] == i:
a.append(not_center.loc[j[1]].tolist())
GG.append(a)
return GG
def update_center(GG):
center = []
for i in GG:
a = np.zeros(len(df.loc[0]))
for j in i:
a += np.array(j)
center.append((a / len(i)).tolist())
return center
def Kmeans_step2(center,df):
center = np.array(center)
G = []
for i in df.index:
d = []
for j in range(len(center)):
dd = d_HM(2,df.loc[i].values,center[j])
d.append(dd)
for m,n in enumerate(d):
if d[m] == min(d):
G.append([m,i])
GG = []
enter = []
for i in range(len(center)):
a = []
e = []
for j in G:
if j[0] == i:
a.append(df.loc[j[1]].tolist())
e.append(j[1])
GG.append(a)
enter.append(e)
return GG,enter
if __name__ == "__main__":
GG = Kmeans_step1(4,df)
center = update_center(GG)
GG,enter = Kmeans_step2(center,df)
i = 0
# 一般迭代20次都能完成距离了
while i < 30:
center = update_center(GG)
GG,center = Kmeans_step2(center,df)
# print(i,"*"*2,center[0])
i += 1
from mpl_toolkits.mplot3d import Axes3D
plt.figure(figsize=(25,15))
ax = plt.subplot(111, projection='3d') # 创建一个三维的绘图工程
color = ["b","y","r","g"]
j = 0
for i in center:
c = color[j]
ax.scatter(df.loc[i]["X"].values.tolist(),df.loc[i]["Y"].values.tolist(),df.loc[i]["Z"].values.tolist(),s=100,c=c)
j += 1
m = 0
for j in update_center(GG):
c = color[m]
ax.scatter(j[0],j[1],j[2],s=500,c=c)
m += 1
ax.set_zlabel('Z') # 坐标轴
ax.set_ylabel('Y')
ax.set_xlabel('X')
plt.show()
# 二维图
plt.figure(figsize=(14,8))
for i in center:
plt.scatter(df.loc[i]["X"],df.loc[i]["Y"])
for j in update_center(GG):
plt.scatter(j[0],j[1],s=500)
3 Scikit-learn实现K-Means++聚类
from sklearn import cluster
x = df.values
k_means = cluster.KMeans(n_clusters=4)
k_means.fit(x)
KMeans(algorithm='auto', copy_x=True, init='k-means++', max_iter=300,
n_clusters=4, n_init=10, n_jobs=1, precompute_distances='auto',
random_state=None, tol=0.0001, verbose=0)
k_means.cluster_centers_
array([[77.09090909, 53.18181818, 21.78787879],
[37.79310345, 73.34482759, 80.34482759],
[56.79069767, 24.79069767, 74.3255814 ],
[25. , 60.82857143, 21.14285714]])
from mpl_toolkits.mplot3d import Axes3D
plt.figure(figsize=(25,15))
ax = plt.subplot(111, projection='3d') # 创建一个三维的绘图工程
for i,j in enumerate(k_means.labels_.tolist()):
if j == 0:
ax.scatter(df.loc[i]["X"].tolist(),df.loc[i]["Y"].tolist(),df.loc[i]["Z"].tolist(),s=100,c="b")
elif j == 1:
ax.scatter(df.loc[i]["X"].tolist(),df.loc[i]["Y"].tolist(),df.loc[i]["Z"].tolist(),s=100,c="y")
elif j == 2:
ax.scatter(df.loc[i]["X"].tolist(),df.loc[i]["Y"].tolist(),df.loc[i]["Z"].tolist(),s=100,c="r")
else:
ax.scatter(df.loc[i]["X"].tolist(),df.loc[i]["Y"].tolist(),df.loc[i]["Z"].tolist(),s=100,c="g")
color = ["b","y","r","g"]
m = 0
for j in k_means.cluster_centers_:
c = color[m]
ax.scatter(j[0],j[1],j[2],s=500,c=c)
m += 1
ax.set_zlabel('Z') # 坐标轴
ax.set_ylabel('Y')
ax.set_xlabel('X')
plt.show()
4 总结
- 从自己编的程序和Scikit-learn中直接调用得到的结果为什么不同的原因就是在于初始中心不同,这也是K-Means算法存在的缺点。