[python 那些事]kmeans and fcm

kmeans and fcm algorithm

1、kmeans

  • 步骤一:初始化聚类中心C1,i=1,2…cn 。经典做法是从所有数据点中任取C个点。
  • 步骤二:确定隶属矩阵U
  • 步骤三:计算价值函数,如果它小于某个确定的阈值,或它相对上次价值函数的该变量小于某个阈值,则算法停止。
  • 步骤四:修正聚类中心.

优点:对于大型数据集也是简单高效、时间复杂度、空间复杂度低。
缺点:最重要是数据集大时结果容易局部最优;需要预先设定K值,对最先的K个点选取很敏感;对噪声和离群值非常敏感;只用于numerical类型数据;不能解决非凸(non-convex)数据。1

import copy
import numpy as np
import math
import random

'''
k-means
步骤一:初始化聚类中心C1,i=1,2....cn 。经典做法是从所有数据点中任取C个点。
步骤二:确定隶属矩阵U
步骤三:计算价值函数,如果它小于某个确定的阈值,或它相对上次价值函数的该变量小于某个阈值,则算法停止。
步骤四:修正聚类中心.
'''


class k_Means(object):
    def __init__(self, k=2):
        '''
        :param k:分组数
        '''
        self.k = k

    def distEclud(self, vectorA, vectorB):
        '''欧式距离
        :param vectorA:
        :param vectorB:
        :return:distance of vectorA and vectorB
        '''
        a = (vectorA[0, 0] - vectorB[0, 0]) ** 2
        b = (vectorA[0, 1] - vectorB[0, 1]) ** 2
        return math.sqrt(a + b)

    def testDistEclud(self, vectorA, vectorB):
        '''
        测试欧氏距离
        :return:
        '''
        vectorA = np.array([vectorA])
        vectorB = np.array([vectorB])
        return self.distEclud(vectorA, vectorB)

    def getData(self):
        '''
        :return:二维数组,模拟一些随机点,进行kmeans分类
        '''
        data = np.array([[1, 1], [1, 2], [1, 3], [2, 1], [2, 2], [10, 10], [11, 10]])
        return data

    # 初始化质心
    def initCentroids(self, dataset):
        '''
        :param dataset:被无监督分类的数据集
        :param k:用户设定的分为几类
        :return:返回质心
        '''
        k = self.k
        numSamples, dim = dataset.shape
        # print(numSamples, dim)
        # 产生k行 dim列的零矩阵
        centroids = np.zeros((k, dim))
        # 定义重复列表
        repeat_list = []
        # 找出每个k的随机质点
        '''
        1、处理了重复的情况,通过一个repeate_list去解决重复问题导致两个质心是相同的 
        2、问题出现在array对比是元素对比,必须any或者all进行统一判断是否一样
        3、如果完全相同就跳过重新生成随机数。
        '''

        for i in range(k):
            while True:
                # flag每次都要初始化,如果有一次生成了两个相同的质心,flag变为0 要重新来一次,只有变回1  再去判断第二次生成的是否一样才可以跳出。
                flag = 1
                index = int(random.uniform(0, numSamples))  # 服从均匀分布0-数据量的随机数
                for repeatElement in repeat_list:
                    # print(repeatElement)
                    # print(dataset[index, :])
                    # print(all(repeatElement == dataset[index, :]))
                    if all(repeatElement == dataset[index, :]):
                        flag = 0
                        break
                if flag:
                    # print(dataset[index, :])
                    centroids[i, :] = dataset[index, :]
                    # print(centroids[i, :])
                    repeat_list.append(dataset[index, :])
                    break
                else:
                    continue
        return centroids

    def kMeans(self, dataset, centroids):
        '''
        :param dataset:初始数据集
        :param centroids:初始化的中心
        :return:列表,其中包含k簇(array)
        '''
        step = 0
        dataset_copy = copy.deepcopy(dataset)
        # print(dataset_copy, "数据集", type(dataset_copy))
        # print(centroids, "质心", type(centroids))

        # ##在深copy的数组中去掉两个质心。(弃用)
        # removeIndex = []
        # for i in range(len(centroids)):
        #     xList = np.where(dataset == centroids[i])[0].tolist()
        #     for i in range(len(xList)):
        #         if xList.count(xList[i]) > 1:
        #             removeIndex.append(xList[i])
        # removeIndex = list(set(removeIndex))
        # # print(removeIndex)
        # dataset_copy = np.delete(dataset_copy, removeIndex, axis=0)
        # # print(dataset_copy, "deepcopy")
        # 删除质心后的array距离
        # print(centroids[0])
        # print(dataset_copy[0])
        print("----------迭代开始-----------")
        # print(centroids, "一开始的质心")
        while True:
            km = [[] for i in range(self.k)]
            for i in range(len(dataset_copy)):
                # 计算到所有质心的距离
                _see = [self.testDistEclud(dataset_copy[i], centroids[j]) for j in range(self.k)]
                # 找出最近的簇
                min_index = _see.index(min(_see))
                # 把最近的并入对应簇
                km[min_index].append(i)
            # print(km)
            # print(dataset_copy[km[0][0]])
            # 更换质心
            step += 1
            k_new = []
            for i in range(self.k):
                # print("---------------")
                a = [dataset_copy[km[i][j]] for j in range(len(km[i]))]
                # 绝对不能把质心加入到列表
                # a.append(centroids[i])

                _centroids = sum(a) / len(a)
                # print(_centroids)
                k_new.append(_centroids)
            k_new = np.array(k_new)
            # print(k_new)
            # 更新质心
            print("newcentroids", k_new)
            # print(centroids, "centroids")
            if ~(k_new == centroids).all():
                centroids = k_new
                print("---------------->>>>>>>>>更换质心")
                # print(centroids)
            else:
                print("------------------------>>>>>>>迭代结束")
                print("迭代次数", step)
                # print(km)
                result_km = []
                for i in range(self.k):
                    a = [dataset_copy[km[i][j]] for j in range(len(km[i]))]
                    result_km.append(np.array(a))
                # [dataset_copy[km[i][j]] for i in range(self.k) for j in range(len(km[i]))] 有空试试二维列表生成
                return result_km


if __name__ == '__main__':
    # 定义分类数
    init_k = 2
    # 初始化对象
    k1 = k_Means()
    # 初始化分类数
    k1.k = init_k
    # 获取样例数据
    data = k1.getData()
    # 初始化质心
    centroids = k1.initCentroids(data)
    # print("初始化的多个质心为", centroids)
    # print(centroids.shape)

    # # 观察每个类别初始化的质心
    # for i in range(centroids.shape[0]):
    #     print("第" + str(i + 1) + "个类别的初始质心:", centroids[i])
    # print(k1.testDistEclud(centroids[0], centroids[1]))
    result = k1.kMeans(data, centroids)
    for item in range(len(result)):
        print("第" + str(item + 1) + "簇")
        print(result[item])

2、FCM

FCM算法融合了模糊理论的精髓,相较于k-means的硬聚类,模糊c提供了更加灵活的聚类结果。因为大部分情况下,数据集中的对象不能划分成为明显分离的类,指派一个对象到一个特定的类有些生硬,也可能会出错。因此FCM对每个对象和每个类赋予一个权值即隶属度,指明对象属于该类的程度。当然,基于概率的方法也可以给出这样的权值,但是有时候我们很难确定一个合适的统计模型,因此使用具有自然地、非概率特性的FCM算法就是一个比较好的选择。该方法由Dunn在1973年提出,并由Bezdek在1981年改进,在模式识别被频繁使用。2 3

  • J. C. Dunn (1973): “A Fuzzy Relative of the ISODATA Process and Its Use in Detecting Compact Well-Separated Clusters”, Journal of
    Cybernetics 3: 32-57 4

  • J. C. Bezdek (1981): “Pattern Recognition with Fuzzy Objective Function Algoritms”, Plenum Press, New York5

  • 初始化模糊矩阵
  • 更新类中心点
  • 更新隶属度
  • 得到聚类结果
from pylab import *
from numpy import *
import numpy as np
import operator
import math
import random
from sklearn.datasets import load_iris
import pandas as pd
import os
from collections import defaultdict

def readData(filename):
    df_full = pd.read_csv(filename)
    # 列出所有列
    columns = list(df_full.columns)
    # 特征值
    features = columns[:len(columns) - 1]
    df = df_full[features]
    return df


class myFcm(object):
    def __init__(self, k=3, MAX_ITER=100, n=2, m=2.00, num_attr=4):
        # 分类数
        self.k = k
        # 最大迭代数
        self.MAX_ITER = MAX_ITER
        # 样本数
        self.n = n
        # 模糊参数(加权指数)
        self.m = m

    # 初始化模糊矩阵U
    def initializeMembershipMatrix(self):
        '''
        步骤 1:用值在 0,1 间的随机数初始化隶属矩阵 U
        理解:每个数都有属于三类的概率
        '''
        membership_mat = list()
        for i in range(self.n):
            random_num_list = [random.random() for i in range(self.k)]
            summation = sum(random_num_list)
            temp_list = [x / summation for x in random_num_list]  # 首先归一化
            membership_mat.append(temp_list)
        # print(membership_mat)
        return membership_mat

    # 计算类中心点
    def calculateClusterCenter(self, membership_mat):
        '''
        步骤 2:用式(6.12)计算 c 个聚类中心 c
        return:返回三个维度的中心
        '''
        cluster_mem_val = zip(*membership_mat)
        cluster_centers = list()
        # 将n个数的三个聚类隶属度zip为三个列表
        cluster_mem_val_list = list(cluster_mem_val)
        # print(cluster_mem_val_list)
        # 分别对三个类别 的隶属度操作
        for j in range(k):
            # 分别拿出
            x = cluster_mem_val_list[j]
            # 做uij的m次方操作
            xraised = [e ** m for e in x]
            # 分母求和
            denominator = sum(xraised)
            temp_num = list()
            # 取出每一行的数据
            for i in range(n):
                # 去除某一行的数据
                data_point = list(df.iloc[i])
                # 6.12 分子——相乘
                prod = [xraised[i] * val for val in data_point]
                temp_num.append(prod)
            # 分子并不是一个数 而是一个向量,向量应该每个维度都进行求和再去和分母做商
            numerator = map(sum, zip(*temp_num))
            center = [z / denominator for z in numerator]  # 每一维都要计算。
            cluster_centers.append(center)
        # print(cluster_centers)
        return cluster_centers

    # 更新隶属度
    def updateMembershipValue(self, membership_mat, cluster_centers):
        #    p = float(2/(m-1))
        data = []
        for i in range(n):
            # 取出文件中的每一行数据
            x = list(df.iloc[i])
            data.append(x)
            # operator.sub 减法
            # 求每行数据与三个中心点的距离  ord默认是平方和开跟
            distances = [np.linalg.norm(list(map(operator.sub, x, cluster_centers[j]))) for j in range(self.k)]
            for j in range(self.k):
                # 6.13   求最新隶属度
                den = sum([math.pow(float(distances[j] / distances[c]), 2 / (self.m - 1)) for c in range(self.k)])
                membership_mat[i][j] = float(1 / den)
        return membership_mat, data

    # 得到聚类结果
    def getClusters(self, membership_mat):
        cluster_labels = list()
        for i in range(self.n):
            max_val, idx = max((val, idx) for (idx, val) in enumerate(membership_mat[i]))
            cluster_labels.append(idx)
        return cluster_labels

    def fuzzyCMeansClustering(self):
        # 主程序
        # 初始化模糊矩阵
        membership_mat = self.initializeMembershipMatrix()
        # 迭代次数记录
        curr = 0
        while curr <= MAX_ITER:  # 最大迭代次数
            # 更新类中心点
            cluster_centers = self.calculateClusterCenter(membership_mat)
            # 更新隶属度
            membership_mat, data = self.updateMembershipValue(membership_mat, cluster_centers)
            # 得到聚类结果
            cluster_labels = self.getClusters(membership_mat)
            curr += 1
        return cluster_labels, cluster_centers, data, membership_mat, curr


if __name__ == '__main__':
    # 读取数据
    filename = "iris.csv"
    df = readData(filename)
    # 初始化一些参数
    # 分类数
    k = 3
    # 最大迭代数
    MAX_ITER = 100
    # 样本数
    n = len(df)  # the number of row
    # 模糊参数
    m = 2.00
    # 维度
    num_attr = len(df.columns) - 1
    myfcm = myFcm(k=k, MAX_ITER=MAX_ITER, n=n, m=m, num_attr=num_attr)
    labels, centers, data, membership, curr = myfcm.fuzzyCMeansClustering()
    value_cnt = {}
    for lable in labels:
        # get(value, num)函数的作用是获取字典中value对应的键值, num=0指示初始值大小。
        value_cnt[lable] = value_cnt.get(lable, 0) + 1
    print(value_cnt)
    print(curr, "次迭代")

    mainlist = defaultdict(list)
    for k, va in [(v, i) for i, v in enumerate(labels)]:
        mainlist[k].append(va)

    for i in mainlist:
        print("第"+str(i)+"类:"+str(mainlist[i]))

3、代码

GitHub:https://github.com/littlepaike/algorithmLearning.git


  1. 聚类算法优缺点 ↩︎

  2. FCM介绍 ↩︎

  3. FCM公式推导 ↩︎

  4. Dunn - 1973 - A Fuzzy Relative of the ISODATA Process and Its Us.pdf ↩︎

  5. Pattern Recognition With Fuzzy Objective Function Algorithms ↩︎

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小范今天学Java了嘛?

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

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

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

打赏作者

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

抵扣说明:

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

余额充值