提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
一、kmeans是什么?
二、三支决策是什么?
2.1 三支决策实例-患者三支分类
2.2 三支决策通俗解释
三、kmeans的优化
四、算法实现
4.1 引入库
4.2 各类度量实现
4.3 算法实现
五、实例验证
5.1 Heart数据集验证
5.1.1 数据集简单预处理
5.1.2 算法对比
5.1.3 结果展示
总结
前言
在学习三支决策思想的时候,给了一些启发。这种思想是可以用于多种算法中运用的,在此文章中用于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聚类中心距离同时过于接近,直接分配最小距离会使得考虑不当,故引入三支决策思想。不妨定义容忍度,其中
表示对象与第
聚类中心的距离。定义阈值
,然后待聚类样本用优化后的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%。可见,该算法是具有有效性的。读者有兴趣可以去实验其他数据集。
总结
三支决策是一种思想,可以尝试于其他算法之中。若读者有其他想法可联系共同讨论。