K-Means和Fuzzy C-Means聚类算法原理以及python代码实现

K-Means和Fuzzy C-Means聚类算法原理以及python代码实现

1.K-Means聚类

1、原理

K-Means算法原理
      网上有很多关于K-Means算法的原理,当然通过阅读《统计学习方法》也可以知道K-Means聚类的原理,我这个比较懒,在这里就不打出详细的原理了,具体的可以参考上面的链接,写的很详细。
      简单来说就是类似EM迭代的过程:
      (1)对于每个样本,将每个样本分配到最近的簇中;
      (2)对于每个簇,重新求该簇的中心;
      重复迭代步骤(1)和(2),直到损失函数收敛。
      值得一提的是,我在代码实现的环节有采用K-Means算法的改进:K-Means++算法,该算法与K-Means算法最大的不同就是在初始聚类中心矩阵的构造上面,其他完全相同,详细原理见上面的链接。

2、python实现

      在项目中需要用java实现聚类算法,因此不能直接简单的在sklearn中调个包然后就完事了,因此我用python写了K-Means聚类算法,方便阅读并转成java。搜索发现网上大部分的代码都是以《机器学习实战》这本书的代码为框架,但是使用了太多的for循环导致程序运行的效率低下,python的numpy库给矩阵运算提供了很大的帮助,通过合理利用numpy库可以使代码简洁且运行效率高。
      下面的代码主要参考sklearn库下的K-Means框架完成,并进行了一些修改,容易读懂。

import numpy as np

def _EuclideanDistances(sample, centroids):
    """
    计算样本和聚类中心的欧几里得距离,通过公式(a-b)^2 = a^2 + b^2 - 2ab
    
    参数
    ----------
    sample : array, size(n, p)
        用户样本矩阵
    centroids : array, size(k, p)
        聚类中心矩阵(如果输入的是1d array在输入前用reshape转换成二维数组)
        
    返回
    ----------
    ED : array, size(n, k)
        样本与聚类中心的距离矩阵
    """
    if centroids.ndim == 1:
        centroids = centroids.reshape(1, len(centroids))
    vecProd = np.dot(sample, centroids.T) 
    SqA = sample**2
    sumSqA = np.sum(SqA, axis=1, keepdims=True)
    sumSqAEx = np.tile(sumSqA, (1, vecProd.shape[1])) # 将(n, 1)的向量sumSqA拓展成(n, k)的矩阵

    SqB = centroids**2
    sumSqB = np.sum(SqB, axis=1).reshape(1, -1)
    sumSqBEx = np.tile(sumSqB, (vecProd.shape[0], 1)) # 将(1, k)的向量sumSqB扩展成(n, k)的矩阵
    SqED = sumSqBEx + sumSqAEx - 2*vecProd
    SqED[SqED<0]=0.0
    ED = np.sqrt(SqED)
    
    return ED

def _initialize_center(X, k, init_state='K_Means++'):
    """
    初始化聚类中心矩阵
    
    参数
    ----------
    X : array, size(n, p)
        用户样本矩阵
    k : int
        簇个数
    init_state : string ['K_Means++', 'random']
        选择初始化聚类中心矩阵的方式
        
    返回
    ----------
    C : 2d array, size(k, p)
        初始化后的聚类中心矩阵
    label : 1d array, size(n, )
        初始化矩阵下的样本标签
    D: 2d array, size(n, k)
        初始化后的样本点与聚类中心的欧几里得距离矩阵
    """
    n, p = X.shape
    
    if init_state == 'random':
        index = random.sample([i for i in range(n)], k)
        C = X[index]
        D = _EuclideanDistances(X, C)
        label = np.argmin(D, axis=1)
    
    elif init_state == 'K_Means++':
        # 初始化聚类中心矩阵
        C = np.zeros((k, p))
        # index向量
        index_vec = [i for i in range(n)]
        # 随机选一个点作为第一个聚类中心
        index0 = np.random.randint(0, n)
        C[0] = X[index0]
        # 求出欧几里得距离矩阵
        D = _EuclideanDistances(X, C[0]) 
        for i in range(2, k+1):        
            d_min = np.min(D, axis=1)
            prob = d_min / np.sum(d_min)
            index = np.random.choice(index_vec, size=1, p=prob)[0]
            C[i-1] = X[index]
            if i == k:
                D = _EuclideanDistances(X, C)
                break

            # 求出欧几里得距离矩阵
            D = _EuclideanDistances(X, C[:i]) 

        label = np.argmin(D, axis=1)
    
    return C, label, D
        
    

def K_Means(X, k=2, error=0.001, T=100, random_state=None, init_state='K_Means++'):
    """
    模糊C均值聚类算法

    参数
    ----------
    X : 2d array, size (n, p)
        用户样本矩阵.  n是用户样本个数; p是特征维度
    k : int
        簇的个数
    error : float
        停止条件; J - J_old <= error
    T : int
        迭代次数
    random_state : int
        随机观测种子,如果提供了则观测结果可固定,否则随机
    init_state : string ['K_Means++', 'random']
        选择初始化聚类中心矩阵的方式

    返回
    -------
    C : 2d array, size (k, p)
        聚类中心矩阵
    label : 1d array, size(n, )
        样本标签向量
    """
    if random_state:
        np.random.seed(seed=random_state)
        
    n, p = X.shape
    C, label, D = _initialize_center(X, k, init_state)
    J = np.sum(np.min(D**2, axis=1))
    
    for t in range(T):
        J_old = J
        # 重新计算簇中心
        for i in range(k):
            index = np.where(label==i)[0]
            C[i] = X[index].mean(axis=0)
            
        D = _EuclideanDistances(X, C)
        J = np.sum(np.min(D**2, axis=1))
        if np.abs(J - J_old) < error:
            print('迭代次数为:', t+1)
            break
        label = np.argmin(D, axis=1)
    
    label = np.argmin(D, axis=1)
    
    return C, label

      正常来说K-Means聚类是需要对数据预处理的,包括去除异常值和归一化等,由于不同人的数据集不同,因此我把预处理的代码删掉了,留下了K-Means聚类的核心代码。
      本文测试的数据集来自于https://blog.csdn.net/weixin_41857483/article/details/109136568
感谢大佬无私提供数据集

def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet

test = np.array(load_data_set('testSet.txt'))
C, label = K_Means(test, k=4, error=0.001, T=100, random_state=2020)

在这里插入图片描述

import matplotlib.pyplot as plt

label_map = {
    
    0: 'r', 1:'b', 2:'g', 3:'y'}
label_color = list(map(lambda x: label_map[x], label))
plt.scatter(x=test[:, 0], y=test[:, 1], c=label_color)

在这里插入图片描述

2.Fuzzy C-Means聚类

1、原理

Fuzzy C-Means聚类原理
Fuzzy C-Means聚类原理
      Fuzzy C-Means聚类原理主要参考这两篇博客,写的很详细了,补充一点就是,里面的m是模糊化因子,是一个超参数,基本取2,同样的,Fuzzy C-Means聚类也是一个迭代算法。

2、python实现

import numpy as np

def _initialize_U0(numbers, clusters):
    """
    初始化隶属度矩阵U0
    
    参数
    ----------
    numbers : int
        用户样本的个数
    clusters : int
        簇的个数
        
    返回
    ----------
    U0 : array, size(numbers, clusters)
        初始化后的隶属矩阵
    """
    U = np.random.rand(numbers, clusters)
    U0 = U / np.sum(U, axis=1, keepdims=True)
    
    return U0


def _EuclideanDistances(sample, centroids):
    """
    计算样本和聚类中心的欧几里得距离,通过公式(a-b)^2 = a^2 + b^2 - 2ab
    
    参数
    ----------
    sample : 2d array, size(n, p)
        用户样本矩阵
    centroids : 2d array, size(k, p)
        聚类中心矩阵(如果输入的是1d array在输入前用reshape转换成二维数组)
        
    返回
    ----------
    ED : array, size(n, k)
        样本与聚类中心的距离矩阵
    """
    if centroids.ndim == 1:
        centroids = centroids.reshape(1, len(centroids))
    vecProd = np.dot(sample, centroids.T) 
    SqA = sample**2
    sumSqA = np.sum(SqA, axis=1, keepdims=True)
    sumSqAEx = np.tile(sumSqA, (1, vecProd.shape[1])) # 将(n, 1)的向量sumSqA拓展成(n, k)的矩阵

    SqB = centroids**2
    sumSqB = np.sum(SqB, axis=1).reshape(1, -1)
    sumSqBEx = np.tile(sumSqB, (vecProd.shape[0], 1)) # 将(1, k)的向量sumSqB扩展成(n, k)的矩阵
    SqED = sumSqBEx + sumSqAEx - 2*vecProd
    SqED[SqED<0]=0.0
    ED = np.sqrt(SqED)
    
    return ED
    
def C_Means(X, k=5, m=2, error=0.001, T=1000, random_state=False):
    """
    模糊C均值聚类算法

    参数
    ----------
    X : 2d array, size (n, p)
        用户样本矩阵.  n是用户样本个数; p是特征维度
    k : int
        簇的个数
    m : float
        模糊化因子,是一个超参数
    error : float
        停止条件; np.max(np.abs(U - U_old)) < error
    T : int
        迭代次数
    random_state : int
        随机观测种子,如果提供了则观测结果可固定,否则随机

    返回
    -------
    C : 2d array, size (k, p)
        聚类中心矩阵
    U : 2d array, (n, k)
        隶属度矩阵
    """
    if random_state:
        np.random.seed(seed=random_state)
    n = X.shape[0] # 用户个数
    p = X.shape[1] # 特征维度
    U = _initialize_U0(n, k)
    
    for t in range(T):
        # 拷贝U用于停止迭代的判断
        U_old = U.copy()
        # 计算聚类中心矩阵
        C =  np.dot((U**m).T, X) / np.sum(U**2, axis=0, keepdims=True).T
        # 更新隶属度矩阵U
        d = _EuclideanDistances(X, C) # 计算X与C的欧几里得距离矩阵
        d = d ** (2/(m-1))
        U = (d * (1/d).sum(axis=1).reshape(n, 1))
        U = 1 / U
        if np.max(np.abs(U - U_old)) <= error:
            print('迭代次数为: ',t+1)
            break
    
    return C, U 

由于输出的U是一个隶属度矩阵,只说明样本归属于第i个簇的概率有多大,因此验证时将概率最大的作为输出标签。

def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet

test = np.array(load_data_set('testSet.txt'))
C, U = C_Means(test, k=4, error=0.001, T=100, random_state=2020)
label = np.argmax(U, axis=1)

在这里插入图片描述

import matplotlib.pyplot as plt

label_map = {
    
    0: 'r', 1:'b', 2:'g', 3:'y'}
label_color = list(map(lambda x: label_map[x], label))
plt.scatter(x=test[:, 0], y=test[:, 1], c=label_color)

在这里插入图片描述
如果有什么错误欢迎各位大佬指出,也欢迎大家在评论区讨论。

猜你喜欢

转载自blog.csdn.net/weixin_44424296/article/details/109706279
今日推荐