K-Means算法原理与Python程序实现

# 只需 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 GiGj=;i=1kGi=X;i,jk
  • 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=1k(xkixkj)2=xixj2

  损失函数是样本与与其所属的中心之间的距离总和,其表达式为:

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=1kC(i)=lxixˉl2

  式中 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=1nI(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=1kC(i)=lxixˉl2

  相似样本聚为同一类时,损失数值最小,这个目标函数的最优化能达到聚类的目的。将 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=1k(1)kl(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算法存在的缺点。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

而又何羡乎

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值