基于三支决策思想的Kmeans优化

文章介绍了k-means聚类算法的基本原理和优化方法,特别是引入了三支决策的概念,用于处理样本与簇中心点距离相近的情况。通过实例展示了如何在紧急医学服务中的分类问题中应用三支决策,并提供了算法的Python实现。文章还比较了优化后的k-means算法与传统算法在Heart数据集上的表现,优化后的算法在准确性上有提升。
摘要由CSDN通过智能技术生成

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在学习三支决策思想的时候,给了一些启发。这种思想是可以用于多种算法中运用的,在此文章中用于kmeans算法,并使用实例进行验证。


一、kmeans是什么?

        k-means 是一种常用的聚类算法,用于将数据集中的数据划分为 K 个不同的簇(cluster)。它的目标是最小化簇内的样本之间的平均平方距离。

以下是 k-means 算法的详细步骤:

输入:数据集 D = {x₁, x₂, ..., xₙ},簇数量 K

1. 初始化:从数据集 D 中随机选择 K 个样本作为初始的 K 个簇的中心点(centroid)。
2. 循环迭代直到收敛:
3. 对数据集 D 中的每个样本 xᵢ:
4. 计算样本 xᵢ 到每个簇中心点的距离,选择距离最近的簇。
5. 将样本 xᵢ 分配给距离最近的簇。
6. 对于每个簇:
7. 计算该簇中所有样本的平均值,作为新的簇中心点。
8. 判断是否达到收敛条件,如簇中心点不再变化或达到最大迭代次数。
9. 输出:得到 K 个簇及其对应的簇中心点。

        在算法的执行过程中,样本通过计算到簇中心的欧氏距离或其他距离度量来确定所属的簇。通常使用平方欧氏距离来表示样本与簇中心点之间的距离。

        这个算法的关键步骤是不断迭代更新簇中心点和重新分配样本,直到满足收敛条件。最终得到的结果是一组聚类簇,其中每个簇由其对应的簇中心点和分配给该簇的样本组成。

        需要注意的是,在执行 k-means 算法时,初始的簇中心点选择可能会影响最终的聚类结果。因此,为了得到更好的聚类效果,可以采用多次运行算法并选取最优的聚类结果。

二、三支决策是什么?

2.1 三支决策实例-患者三支分类

在紧急医学服务体系中,我们通常采用Triage的分类体系。Triage的概念可能出现在拿破仑战争期间,是一种对病人进行分级、用以判断哪个病人优先诊治的分类技术。

第一次世界大战期间,在医生、治疗材料不足的前提下,法国医师采用Triage方法来决定哪些患者的治疗。具体说来,Triage是把病人(伤员)分成三组。

(1)不管是否得到救助,他一定会活下来。

(2)不管是否得到救助,他一定会死去。

(3)伤者得到及时救助,可能对结果产生积极影响。

战地医生对于新出现的伤者,首先基于其伤势确定:第一种伤者不管有没有进行过处理,他都一定会活下去,如普通的重大创伤:第二种伤者不管进行了什么处理方式,他都一定会死去,如一般伤及人体关键部位;第三种伤者一旦马上进行了处理方法,则对其未来威胁很大。医疗机构首先根据患者伤势的严重程度决定了他所采用的处理方式,在这种基础上再对伤者进行了分级。显然,一般医务人员都是优先考虑第三类伤员的。在医院条件不够时,这些分级方法也起到了重要作用。目前,根据这些方法的伤员分级方法开始在急救医学服务体系中普遍运用。

2.2 三支决策通俗解释

        二支决策就是非黑即白。当人们做一项选择时,采用的要么是接受,要么是拒绝,会及时快速的给出我们的答案。而三支则在这二者的基础上添加了待定的选项,对于一项决策来说,我还需要进行多方面的考察,深度思考之后,结合多方的因素,审慎的对待,从而降低决策的风险和失误,帮我们做出正确的选择,提高准确率。简而言之,三支决策就是对一项的决策采取三种态度:接受,拒绝,不确定域。

三、kmeans的优化

        在传统K-means聚类算法中,其最主要的计算步骤:把每个对象分配给距离它最近的聚类中心。但试想,若出现该对象与某k聚类中心距离同时过于接近,直接分配最小距离会使得考虑不当,故引入三支决策思想。不妨定义容忍度p(x_i,x_j)=\frac{|d_i-d_j|}{d_i+d_j},其中表示对象与第聚类中心的距离。定义阈值(\beta ,\alpha ),然后待聚类样本用优化后的K-means算法进行聚类。该算法的详细描述如下。

四、算法实现

4.1 引入库

import os
import time
import joblib
import datetime
import warnings
import pandas as pd 
import random 
import numpy as np 
from sklearn import svm
from sklearn import metrics
warnings.filterwarnings('ignore')
from sklearn.metrics import f1_score
from sklearn.metrics import recall_score
from sklearn.metrics import precision_score
from itertools import combinations  # 全排列使用
from sklearn.model_selection import GridSearchCV #网格搜索
from sklearn.metrics import accuracy_score # 准确率
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn import tree
from scipy.spatial.distance import pdist, squareform
from sklearn.pipeline import make_pipeline
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import BaggingClassifier
from sklearn.svm import SVC, LinearSVC 
from sklearn.ensemble import GradientBoostingClassifier
from sklearn import naive_bayes
import copy
from itertools import combinations  # 全排列使用
from sklearn.linear_model import LinearRegression, LogisticRegression, Ridge, Lasso,SGDRegressor,ARDRegression,BayesianRidge
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import mean_absolute_error
from sklearn.metrics import mean_squared_error
from sklearn.metrics import r2_score 
from sklearn.cluster import KMeans 
from numpy import mat 
from math import sqrt

4.2 各类度量实现

class select_mode_calculation_distance():
    def __init__(self,values1,values2,distance_mode="Euclidean"): # 默认为欧式距离
        self.distance_mode = distance_mode
        self.values1 = values1
        self.values2 = values2
        
        self.get_Calculatec_distance()  
        
    def Euclidean(self):# 欧式距离
        vector1 = mat(self.values1)
        vector2 = mat(self.values2)
        return sqrt((vector1-vector2)*((vector1-vector2).T)) # 返回欧式距离
        # return np.sqrt(sum((np.array(self.values1) - np.array(self.values1)) ** 2))
        
    def Manhattan(self): # 曼哈顿距离
#         vector1 = mat(self.values1)
#         vector2 = mat(self.values2) 
#         return sum(abs(vector1-vector2))
        return sum(map(lambda i,j:abs(i-j),self.values1,self.values2))
    
    
    def Chebyshev(self):# 切比雪夫距离
        vector1 = mat(self.values1)
        vector2 = mat(self.values2)
        return abs(vector1-vector2).max() 
    
    
    def min_s(self): 
        
        return distance 
    
    def Hamming(self):# 汉明距离 
        self.values1=np.asarray(self.values1,int)
        self.values2=np.asarray(self.values2,int)
        return np.mean(self.values1!=self.values2)  
         
    def cosine_similarity(self):# 余弦相似度
        return  1-np.dot(self.values1, self.values2)/(np.linalg.norm(self.values1)*np.linalg.norm(self.values2)) 
    
    def Jaccard_similary(self): # 杰卡德相似系数
        X = np.vstack([self.values1, self.values2])
        return pdist(X, 'jaccard')[0]  
         
    def Mahalanobis(self):     # 马氏距离
        XT = np.vstack([self.values1, self.values2]).T 
        return sum(list(pdist(XT, 'mahalanobis'))) 
    
    def get_Calculatec_distance(self):  
        
        if self.distance_mode == "Euclidean"        :return self.Euclidean()          # 欧氏距离
        if self.distance_mode == "Manhattan"        :return self.Manhattan()          # 曼哈顿距离
        if self.distance_mode == "Chebyshev"        :return self.Chebyshev()          # 切比雪夫距离
        #if self.distance_mode == "min's"            :return self.min_s()              # 闵氏距离
        if self.distance_mode == "cosine_similarity":return self.cosine_similarity() # 余弦相似度
        if self.distance_mode == "Hamming"          :return self.Hamming()           # 汉明距离 
        #if self.distance_mode == "Mahalanobis"      :return self.Mahalanobis()       # 马氏距离
        if self.distance_mode == "Jaccard_similary" :return self.Jaccard_similary()  # 杰卡德相似系数
        
       

4.3 算法实现

封装成类,方便调用,并且给出详细注释

class Kmeans_TWDecisions():                                    # [beta,alpha]
    def __init__(self,N_clustering,distance_mode="Euclidean",beta=0.01,alpha=0.02,random_state=2022,iter_times=100,
                      Take_origi_alg=True,Take_TW_Decisions=False,Take_iter_toUpdate_center_point=False,
                 Convergence_gap=0.01,select_distance_mode=["Euclidean","Manhattan","Chebyshev","Hamming","cosine_similarity",
                                                            "Jaccard_similary"]):   
        self.N = N_clustering                                   # 需要聚类的个数
        self.Haven_Cluster_from_train  = []                     # 储存训练时候对每个样本聚类的情况
        self.distance_mode = distance_mode                      # 选择计算距离公式,默认欧氏距离
        self.every_sample_sitution = []                         # 每个样本每次迭代的聚类情形,当两者距离过小,需要重新考虑分类
        self.beta = beta                                        # 三支决策的阈值    [beta,alpha]
        self.alpha = alpha                                      # 三支决策的阈值    [beta,alpha]
        self.iter_times = iter_times                            # 迭代次数
        self.Take_origi_alg = Take_origi_alg                    # 原始kmeans算法:全部样本聚类后才来更新一次伪中心点
        self.Take_TW_Decisions = Take_TW_Decisions              # 采取三支决策理论
        self.Convergence_gap = Convergence_gap                  # 用于确定是否收敛允许的百分比误差
        self.Take_iter_toUpdate_center_point = Take_iter_toUpdate_center_point # 每一次样本聚类后都重新更新一次伪中心点
        self.select_distance_mode = select_distance_mode        # 选择计算距离公式
        random.seed(random_state)                               # 设定随机种子,确保能够复现
        
        #self.distance_mode_
        
    def Previous_parameter_setting(self,x_train):                #(1)前期的参数设置  
        self.x_train  = x_train 
        self.x_train_row = x_train.shape[0]                      # 训练样本的个数 
        self.columns = self.x_train.columns                      # 数据集的列名称
        self.get_Pseudo_center_point()                           # 随机初始化伪中心点
        self.fit_cluster_xtrain = [ []  for i in range(self.N)] # 将每行划分到对应的类别进去,每个列表储存相同的聚类数据集
    
    
    def get_Pseudo_center_point(self):                           # (2)随机初始化伪中心点
        len_columns = len(self.columns)                          # 获取数据集列的长度 
        self.center_point = [ [ [] for j in range(len_columns)]  for i in range(self.N)]    
                                              # 根据聚类个数,创建对应伪随机点个数,一个列表储存一个类的伪随机点,点的维度为数据集列的长度
    
        for i in range(self.N): 
            for j in range(len_columns):                         # 从列的最小最大值之间选一个值作为伪中心点
                self.center_point[i][j] = random.uniform( self.x_train[self.columns[j]].min(), 
                                                          self.x_train[self.columns[j]].max()+1)
    
    # --------------------------------------------------------------------------------------------------------------------                                                                # 从数据集对应列的最大最小值取随机值
    
    
    def Three_way_decisions_Reject(self,rethink_clustering_index_beta):# 拒绝域处理方式
        
        for distance_mode in self.select_distance_mode:          # 由于两者距离过小,故所有距离计算公式都计算一遍
                
            new_calculation_distances = {}                        # index(class) :distance
            for index in rethink_clustering_index_beta:          # 重新计算一遍该行样本与最小距离相近的类(索引)的距离(所有计算公式)
                new_calculation_distances[index]=select_mode_calculation_distance(self.Every_row_values,self.center_point[index],
                                                                                 distance_mode=distance_mode).get_Calculatec_distance()
                                                                  # 计算该行样本与每类的伪中心点的距离
            #print(new_calculation_distances)
            #min_indexs = min(new_calculation_distances, key=new_calculation_distances.get)
            self.every_sample_sitution.append(min(new_calculation_distances, key=new_calculation_distances.get))
                                                                  # 返回距离最小对应的类(索引)
                
        return max(set(self.every_sample_sitution),key=self.every_sample_sitution.count)  # 返回在各个距离公式下聚类的众数
    
    
    
    def Check_if_Uncertain_area(self,calculation_distances):
        #min_index = calculation_distances.index(min(calculation_distances))# 计算距离最小值对应的索引---也就是类别 
        min_distance = min(calculation_distances)                           # 获取最小距离
        #self.every_sample_sitution.append(min_index)                       # 每个样本每次迭代的聚类情形,保留每次迭代的聚类
        #print(calculation_distances) 
            
        rethink_clustering_index_beta  = []         # 保留两者距离过小,对应的是哪些类(索引), 拒绝域(拒绝第一次分类效果)
        rethink_clustering_index_alpha = []         # 两者距离明确分离,接受域(接受第一次分类效果)
        rethink_clustering_index_between = []       # 不确定域
        for distance in calculation_distances:
            Proportion = abs(distance-min_distance)/((distance+min_distance)/2+1e-10)
        
            
            if   Proportion<= self.beta:        # 两者距离过小,需要重新考虑分类情况 拒绝域           
                rethink_clustering_index_beta.append(calculation_distances.index(distance))# 保留与最小距离相近的类(索引)   
           
            elif Proportion>= self.alpha:   # 两者距离明确分离,接受域(接受第一次分类效果)
                rethink_clustering_index_alpha.append(calculation_distances.index(distance))
            
            else:rethink_clustering_index_between.append(calculation_distances.index(distance))
                                                                            # 保留与最小距离不确定的类(索引)  不确定域
                
             
            
        if len(rethink_clustering_index_alpha)==self.N-1:return calculation_distances.index(min_distance)
                                                         # 此时为接受域,直接返回第一次聚类的结果
        
        if len(rethink_clustering_index_beta)> 1        :return self.Three_way_decisions_Reject(rethink_clustering_index_beta)
                                                        # 此时为拒绝域,表明存在着至少两类距离极其接近,采取所有计算距离公式
        
        #if len(rethink_clustering_index_between)> 1     :return self.Three_way_decisions_between(rethink_clustering_index_between) 
                                                        # 此时为不确定域
        return calculation_distances.index(min_distance)
                                                        # 先将不确定域的结果范围归纳为接受域,后续再来添加代码
        
        
    # --------------------------------------------------------------------------------------------------------------------  
    
    def Final_classification(self):
        calculation_distances = [] # 保留每次计算的距离
        
        for i in range(self.N):
            calculation_distances.append(select_mode_calculation_distance(self.Every_row_values,self.center_point[i],
                                                                         distance_mode=self.distance_mode).get_Calculatec_distance())
                                                                           # 计算该行样本与每类的伪中心点的距离
                
        if self.Take_TW_Decisions:     # 检验是否要开启三支决策思路
            return self.Check_if_Uncertain_area(calculation_distances)    
                                                                            # 依据三支决策原理检查是否存在不确定区域,返回最终聚类的类别    
        else:return calculation_distances.index(min(calculation_distances))# 不采取三支决策思路,直接返回最小距离对应的类别
                
                
    def to_update_center_point2(self):                                     # 原始kmeans算法:全部样本聚类后才来更新一次伪中心点
        for i in range(self.N): 
            for j in range(len(self.columns)):                             # 从已聚类的各个列表中,取均值作为伪随机点
                values = 0  
                if len(self.fit_cluster_xtrain[i])==0:pass                   # 此时全部分为同一类
                else:
                    for value in self.fit_cluster_xtrain[i]:values +=value[j]    
                    self.center_point[i][j] = values/len(self.fit_cluster_xtrain[i])
                
            
            
    def to_update_center_point(self,update_class_center_point): 
        len_columns = len(self.columns)                          # 获取数据集列的长度 
        for j in range(len_columns):                            # 针对需要更新的类进行求新的伪中心点:
            Last_center_point = self.center_point[update_class_center_point][j] # 获取未更新前的伪中心点
            n = len(self.fit_cluster_xtrain[update_class_center_point])
            self.center_point[update_class_center_point][j] = ((n-1)*Last_center_point + self.Every_row_values[j])/n  
                                                                # 更新该类每列下伪中心点  
        
    def check_if_convergence(self): # self.Before_iter_center_points迭代之前的伪中心点,self.center_point迭代之后的伪中心点
        for i in range(self.N):
            for j in range(len(self.columns)):
                if abs(self.Before_iter_center_points[i][j]-self.center_point[i][j])/self.center_point[i][j]>self.Convergence_gap:
                    return False  # 说明仍未收敛
                                    # 只有当每个类别的新旧伪中心点的各个列的差占比(变动占比)小于self.Convergence_gap,才说明收敛                                
        return True                # 说明收敛
        
        
    def fit(self,x_train):
        self.Previous_parameter_setting(x_train)                                       # 前期参数设置 ,仅仅调用一次
        
        convergence  = False     # 表示未收敛
        iter_times   = 0          # 表明迭代次数
        
        while iter_times<=self.iter_times and not convergence:                       # 迭代次数超过设定次数或者已经收敛,则跳出循环
            self.Before_iter_center_points = copy.deepcopy(self.center_point)          # 深复制迭代之前的伪中心点(保留迭代之前的)
            self.Haven_Cluster_from_train  = []                     # 储存训练时候对每个样本聚类的情况,因为再次迭代,故重新化为列表
            self.fit_cluster_xtrain = [ []  for i in range(self.N)] # 将每行划分到对应的类别进去,每个列表储存相同的聚类数据集
                                                                     #                             因为再次迭代,故重新化为列表
                
            for row in range(self.x_train_row):                                       # 根据索引取每一行的值
                self.Every_row_values = list(x_train[row:row+1].values[0])             # 将值列表化
                self.Haven_Cluster_from_train.append(self.Final_classification())      # 将该行的值放进去计算与每一类的距离是多少,返回最终预测类
                #print(self.Haven_Cluster_from_train[-1])
                self.fit_cluster_xtrain[self.Haven_Cluster_from_train[-1]].append(self.Every_row_values) 
                                                                                  # 将每行划分到对应的类别进去,每个列表储存相同的聚类数据集
                
                if self.Take_iter_toUpdate_center_point:                         # 每一行样本成功最终聚类之后,开始更新该类的伪中心点
                    self.to_update_center_point(self.Haven_Cluster_from_train[-1])#  并且结合三支决策原理来更新
                                                                                
                self.every_sample_sitution = []                 # 更新为空列表,每个样本每次迭代的聚类情形,当两者距离过小,需要重新考虑分类
            
            if self.Take_origi_alg:                             # 采取原始算法
                self.to_update_center_point2()                  # 原始kmeans算法:全部样本聚类后来更新一次伪中心点,再来重新聚类
            
            iter_times+=1 # 标记已迭代次数
            if iter_times%200==0:
                print("已迭代{}次".format(iter_times)) 
            convergence = self.check_if_convergence() # 检查是否已经收敛,返回True,Flase值
                       
                  
    def predict(self,x_test):
        pass 

五、实例验证

5.1 Heart数据集验证

数据集来源:https://archive.ics.uci.edu/ml/datasets/Heart+Disease  

5.1.1 数据集简单预处理

data = pd.read_csv(r"heart.csv")
for column in data.columns: 
    data[column].replace("N",np.nan,inplace=True)  # 将错误的值换成缺失值格式np.Nan
print(data.shape)
data = data.dropna() # 删除nan值
print(data.shape)
data["sex"].replace("male",0,inplace=True) 
data["sex"].replace("female",1,inplace=True) 
data["thal"].replace("fixed defect",0,inplace=True) 
data["thal"].replace("reversable defect",1,inplace=True)  
data["thal"].replace("normal",2,inplace=True)
Y = data['target'] 
X = data.drop("target",axis=1) 

5.1.2 算法对比

将上述复现的优化kmeans与sklearn中的kmeans算法进行对比

# sklern自带的kmeans算法
kmeans=KMeans(n_clusters=2, random_state=2022)
predict = kmeans.fit_predict(X)


# 优化kmeans
model = Kmeans_TWDecisions(2,iter_times=50,Take_origi_alg=True,Take_TW_Decisions=True,Take_iter_toUpdate_center_point=True,Convergence_gap=1e-100,beta=0.4,alpha=0.8)                                                             
model.fit(X)    
self_predict = model.Haven_Cluster_from_train  

print('模型的评估报告:\n',metrics.classification_report(Y, self_predict),"\n\n")  
print('模型的评估报告:\n',metrics.classification_report(Y, predict)) 

5.1.3 结果展示

使用优化kmeans算法准确度在评估报告中显示为62%。使用传统的kmeans算法准确度是58%。可见,该算法是具有有效性的。读者有兴趣可以去实验其他数据集。

总结

        三支决策是一种思想,可以尝试于其他算法之中。若读者有其他想法可联系共同讨论。

  • 15
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
三支决策(Three-way Decision)是一种基于符合人类认知的决策模式,它认为:人们在实际决策过程中,对于具有充分把握接受或拒绝的事物能够立即作出快速的判断;对于哪些不能立即作出决策的事物,人们往往会推迟对事件的判断,即:延迟决策。造成延迟决策的原因很多,比如:所掌握的信息不够充分、对风险的评估不够全面、对事件的认知不够彻底等。当人们对信息、风险、认知的掌握程度达到一定的水平,会作出接受或拒绝的最终判断,从这个角度说,三支决策是最终实现二支决策的一个中间步骤。此外,三支决策有着十分广泛的应用背景。例如:在论文的审稿过程中,对于一篇稿件,如果十分优秀则直接接收,如果质量太差则直接拒稿。但是在大多情况下,稿件可能具有一定的创新性,但技术、语言等方面都需要进一步提高,主编往往选择修改和重审。在医学治疗中,讲究听闻望切,对于一些小病而言,医生能够快速准确地作出有病或无病的诊断;而对于一些疑难杂症,需要通过进行一些检查才能进一步的确诊。三支决策思想已在医学、工程、管理、信息领域得到了成功的应用。近几年来,对于三支决策和粒计算的研究引起了国内外学者的广泛关注,在2009-2012年连续四届国际粗糙集与知识技术学术会议(RSKT)以及2011-2012年连续两届中国粗糙集与软计算学术会议(CRSSC)上都举办了三支决策与粒计算的研讨会,李华雄等编著的《决策粗糙集理论及其研究进展》以及贾修一等编著的《三支决策理论与应用》推动了三支决策与粒计算的发展,国际著名SCI期刊《International Journal of Approximate Reasoning》和《Fundamenta Informaticae》等也先后出版专刊推动该主题的发展。粒计算(Granular Computing)是当前计算智能研究领域中模拟人类思维和解决复杂问题的新方法。它覆盖了所有有关粒度的理论、方法和技术,是研究复杂问题求解、海量数据挖掘和模糊信息处理等问题的有力工具。粒计算从提出到现在已有30多年,近年来受到了众多研究者的广泛关注,已经成为日益受到学术界重视的一个新的研究领域。随着粒计算研究工作的不断深入,人们从不同的角度研究得到不同的粒计算理论模型,主要有模糊集(词计算)理论模型、粗糙集理论模型、商空间理论模型和云模型等。我国学者开展了以国际、国内学术研讨会议和暑期研讨会等多种形式的粒计算学术交流与合作。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值