python实现
分步
- 划分数据子集(注意区分离散特征值和连续特征值)
#获取数据子集,分类与回归的做法相同
#将数据集根据划分特征切分为两类
def split_dataset(data_x,data_y,fea_axis,fea_value):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
fea_axis(int):进行划分的特征编号(列数)
fea_value(int):进行划分的特征对应特征值
output:data_x[equal_Idx],data_y[equal_Idx](ndarry):特征值等于(大于等于)目标特征值的样本与标签
data_x[nequal_Idx],data_y[nequal_Idx](ndarry):特征值不等于(小于)目标特征的样本与标签
'''
if isinstance(fea_value,float):
#如果特征值为浮点数(连续特征值),那么进行连续值离散化
equal_Idx = np.where(data_x[:,fea_axis]>=fea_value) #找出特征值大于等于fea_alue的样本序号
nequal_Idx = np.where(data_x[:,fea_axis]<fea_value) #找出特征值小于fea_alue的样本序号
else:
equal_Idx = np.where(data_x[:,fea_axis]==fea_value) #找出特征值等于fea_alue的样本序号
nequal_Idx = np.where(data_x[:,fea_axis]!=fea_value) #找出特征值不等于fea_alue的样本序号
return data_x[equal_Idx],data_y[equal_Idx],data_x[nequal_Idx],data_y[nequal_Idx]
- 求解基尼系数(在该数据集相下,随机抽取两个样本,标签不同的概率)
#使用决策树进行分类需要用到
#求解基尼系数,即两个样本不属于同一标签的概率(二分类问题)
def cal_gini(data_y):
'''
input:data_y(ndarry):标签值
output:gini(float):基尼指数
'''
m = len(data_y) #全部样本的标签数量
labels = np.unique(data_y) #获得不同种类的标签(去重)
gini = 1.0 #最后返回的基尼指数
for label in labels:
ans = data_y[np.where(data_y[:]==label)].size/m #该标签的出现概率
gini -= ans*ans #累减计算基尼指数(两两不同的总概率)
return gini
- 最优特征选取以及特征值划分(遍历计算每个特征下每个特征值的基尼系数,取最小值(纯度最高)进行划分标准)
#分类方法实现最优特征的选取以及特征值划分
def classify_get_best_fea(data_x,data_y):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
output:best_fea(int):最优特征
best_fea_val(int):最优特征值
'''
m,n = np.shape(data_x) #m,n分别为样本数以及特征属性数
#初始化
best_fea = -1
best_fea_val = -1
min_fea_gini = np.inf
for i in range(n): #遍历所有特征(列)
feas = np.unique(data_x[:,i]) #获得该特征下所有特征值
#分别以每个特征值为中心进行划分求基尼系数,找到使基尼系数最小的划分
for j in feas:
equal_data_x,equal_data_y,nequal_data_x,nequal_data_y = split_dataset(data_x,data_y,i,j)
fea_gini = 0.0
#计算该划分下的基尼系数
fea_gini = len(equal_data_y)/m*cal_gini(equal_data_y)+len(nequal_data_y)/m*cal_gini(nequal_data_y)
#如果该划分方式的基尼系数更小(纯度更高),那么直接进行更新
if fea_gini<min_fea_gini:
min_fea_gini = fea_gini
best_fea = i
best_fea_val = j
return best_fea,best_fea_val
- 创建分类决策树(使用递归的方法构建决策树字典,注意处理特殊情况:只有一类标签的情况,无特征的情况,当基尼系数过低时也可以中断划分)
#创建分类方法的决策树
def classify_create_tree(data_x,data_y,fea_label):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
fea_label(list):特征属性列表
output:my_tree(dict):生成的CART分类树
'''
labels = np.unique(data_y)
#只有一个标签的情况
if len(labels)==1:
return data_y[0]
#特征集为0的情况,采用多数投票的方法
if data_x.shape[1]==0:
best_fea,best_fea_num = 0,0
for label in labels:
num = data_y[np.where(data_y==label)].size
if num>best_fea_num:
best_fea = label
best_fea_num = num
return best_fea
best_fea,best_fea_val = classify_get_best_fea(data_x,data_y)
best_fea_label = fea_label[best_fea]
print(u"此时最优索引为:"+str(best_fea_label))
my_tree = {best_fea_label:{}}
#获得划分结果
equal_data_x,equal_data_y,nequal_data_x,nequal_data_y = split_dataset(data_x,data_y,best_fea,best_fea_val)
#删除最优特征
equal_data_x = np.delete(equal_data_x,best_fea,1)
nequal_data_x = np.delete(nequal_data_x,best_fea,1)
fea_label = np.delete(fea_label,best_fea,0)
#递归生成CART分类树
my_tree[best_fea_label]["{}_{}".format(1,best_fea_val)] = classify_create_tree(equal_data_x,equal_data_y,fea_label)
my_tree[best_fea_label]["{}_{}".format(0,best_fea_val)] = classify_create_tree(nequal_data_x,nequal_data_y,fea_label)
return my_tree
决策树字典格式为{“划分属性”:{“含于该划分值中”,划分结果/继续分类},{“不含于该划分值中”,划分结果/继续分类}},注意在判断是否含于该划分值中,构建如“1_划分值”, “0 _划分值”这样的字符串,1表示包含,0表示不包含。如下如所示:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pgWzKlis-1637412940662)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120181205717.png)]](https://img-blog.csdnimg.cn/668114cacefb440f9da6cd66daeb2437.png)
测试函数
#测试操作 import re #预测一条测试数据结果 def classify(inputTree,xlabel,testdata): ''' input:inputTree(dict):CART分类决策树 xlabel(list):特征属性列表 testdata(darry):一条测试数据特征值 output:classLabel(int):测试数据预测结果 ''' firstStr = list(inputTree.keys())[0] secondDict = inputTree[firstStr] featIndex = xlabel.index(firstStr)#根据key值得到索引 classLabel = '0'#定义变量classLabel,默认值为0 ans = re.findall(r'\d+\.\d+',list(secondDict.keys())[0]) if isinstance(testdata[featIndex],float): if float(testdata[featIndex]) >= float(ans[0]): if type(secondDict['1_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式 classLabel = classify(secondDict['1_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归 else: classLabel = secondDict['1_'+ans[0]] else: if type(secondDict['0_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式 classLabel = classify(secondDict['0_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归 else: classLabel = secondDict['0_'+ans[0]] return int(classLabel) else: if float(testdata[featIndex]) == float(ans[0]): if type(secondDict['1_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式 classLabel = classify(secondDict['1_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归 else: classLabel = secondDict['1_'+ans[0]] else: if type(secondDict['0_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式 classLabel = classify(secondDict['0_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归 else: classLabel = secondDict['0_'+ans[0]] return int(classLabel) #预测所有测试数据结果 def classifytest(inputTree, xlabel, testDataSet): ''' input:inputTree(dict):训练好的决策树 xlabel(list):特征值标签列表 testDataSet(ndarray):测试数据集 output:classLabelAll(list):测试集预测结果列表 ''' classLabelAll = []#创建空列表 for testVec in testDataSet:#遍历每条数据 classLabelAll.append(classify(inputTree, xlabel, testVec))#将每条数据得到的特征标签添加到列表 return np.array(classLabelAll)
源代码(全部)
import numpy as np
import re
#获取数据子集,分类与回归的做法相同
#将数据集根据划分特征切分为两类
def split_dataset(data_x,data_y,fea_axis,fea_value):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
fea_axis(int):进行划分的特征编号(列数)
fea_value(int):进行划分的特征对应特征值
output:data_x[equal_Idx],data_y[equal_Idx](ndarry):特征值等于(大于等于)目标特征值的样本与标签
data_x[nequal_Idx],data_y[nequal_Idx](ndarry):特征值不等于(小于)目标特征的样本与标签
'''
if isinstance(fea_value,float):
#如果特征值为浮点数(连续特征值),那么进行连续值离散化
equal_Idx = np.where(data_x[:,fea_axis]>=fea_value) #找出特征值大于等于fea_alue的样本序号
nequal_Idx = np.where(data_x[:,fea_axis]<fea_value) #找出特征值小于fea_alue的样本序号
else:
equal_Idx = np.where(data_x[:,fea_axis]==fea_value) #找出特征值等于fea_alue的样本序号
nequal_Idx = np.where(data_x[:,fea_axis]!=fea_value) #找出特征值不等于fea_alue的样本序号
return data_x[equal_Idx],data_y[equal_Idx],data_x[nequal_Idx],data_y[nequal_Idx]
#使用决策树进行分类需要用到
#求解基尼指数,即两个样本不属于同一标签的概率(二分类问题)
def cal_gini(data_y):
'''
input:data_y(ndarry):标签值
output:gini(float):基尼指数
'''
m = len(data_y) #全部样本的标签数量
labels = np.unique(data_y) #获得不同种类的标签(去重)
gini = 1.0 #最后返回的基尼指数
for label in labels:
ans = data_y[np.where(data_y[:]==label)].size/m #该标签的出现概率
gini -= ans*ans #累减计算基尼指数(两两不同的总概率)
return gini
#分类方法实现最优特征的选取以及特征值划分
def classify_get_best_fea(data_x,data_y):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
output:best_fea(int):最优特征
best_fea_val(int):最优特征值
'''
m,n = np.shape(data_x) #m,n分别为样本数以及特征属性数
#初始化
best_fea = -1
best_fea_val = -1
min_fea_gini = np.inf
for i in range(n): #遍历所有特征(列)
feas = np.unique(data_x[:,i]) #获得该特征下所有特征值
#分别以每个特征值为中心进行划分求基尼系数,找到使基尼系数最小的划分
for j in feas:
equal_data_x,equal_data_y,nequal_data_x,nequal_data_y = split_dataset(data_x,data_y,i,j)
fea_gini = 0.0
fea_gini = len(equal_data_y)/m*cal_gini(equal_data_y)+len(nequal_data_y)/m*cal_gini(nequal_data_y)
#如果该划分方式的基尼系数更小(纯度更高),那么直接进行更新
if fea_gini<min_fea_gini:
min_fea_gini = fea_gini
best_fea = i
best_fea_val = j
return best_fea,best_fea_val
#创建分类方法的决策树
def classify_create_tree(data_x,data_y,fea_label):
'''
input:data_x(ndarry):特征值
data_y(ndarry):标签值
fea_label(list):特征属性列表
output:my_tree(dict):生成的CART分类树
'''
labels = np.unique(data_y)
#只有一个标签的情况
if len(labels)==1:
return data_y[0]
#特征集为0的情况,采用多数投票的方法
if data_x.shape[1]==0:
best_fea,best_fea_num = 0,0
for label in labels:
num = data_y[np.where(data_y==label)].size
if num>best_fea_num:
best_fea = label
best_fea_num = num
return best_fea
best_fea,best_fea_val = classify_get_best_fea(data_x,data_y)
best_fea_label = fea_label[best_fea]
print(u"此时最优索引为:"+str(best_fea_label))
my_tree = {best_fea_label:{}}
#获得划分结果
equal_data_x,equal_data_y,nequal_data_x,nequal_data_y = split_dataset(data_x,data_y,best_fea,best_fea_val)
#删除最优特征
equal_data_x = np.delete(equal_data_x,best_fea,1)
nequal_data_x = np.delete(nequal_data_x,best_fea,1)
fea_label = np.delete(fea_label,best_fea,0)
#递归生成CART分类树
my_tree[best_fea_label]["{}_{}".format(1,best_fea_val)] = classify_create_tree(equal_data_x,equal_data_y,fea_label)
my_tree[best_fea_label]["{}_{}".format(0,best_fea_val)] = classify_create_tree(nequal_data_x,nequal_data_y,fea_label)
return my_tree
#预测一条测试数据结果
def classify(inputTree,xlabel,testdata):
'''
input:inputTree(dict):CART分类决策树
xlabel(list):特征属性列表
testdata(darry):一条测试数据特征值
output:classLabel(int):测试数据预测结果
'''
firstStr = list(inputTree.keys())[0]
secondDict = inputTree[firstStr]
featIndex = xlabel.index(firstStr)#根据key值得到索引
classLabel = '0'#定义变量classLabel,默认值为0
ans = re.findall(r'\d+\.\d+',list(secondDict.keys())[0])
if isinstance(testdata[featIndex],float):
if float(testdata[featIndex]) >= float(ans[0]):
if type(secondDict['1_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式
classLabel = classify(secondDict['1_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归
else:
classLabel = secondDict['1_'+ans[0]]
else:
if type(secondDict['0_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式
classLabel = classify(secondDict['0_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归
else:
classLabel = secondDict['0_'+ans[0]]
return int(classLabel)
else:
if float(testdata[featIndex]) == float(ans[0]):
if type(secondDict['1_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式
classLabel = classify(secondDict['1_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归
else:
classLabel = secondDict['1_'+ans[0]]
else:
if type(secondDict['0_'+ans[0]]).__name__ == 'dict':#判断secondDict[key]是否是字典格式
classLabel = classify(secondDict['0_'+ans[0]], xlabel, testdata)#如果是字典格式,进行递归
else:
classLabel = secondDict['0_'+ans[0]]
return int(classLabel)
#预测所有测试数据结果
def classifytest(inputTree, xlabel, testDataSet):
'''
input:inputTree(dict):训练好的决策树
xlabel(list):特征值标签列表
testDataSet(ndarray):测试数据集
output:classLabelAll(list):测试集预测结果列表
'''
classLabelAll = []#创建空列表
for testVec in testDataSet:#遍历每条数据
classLabelAll.append(classify(inputTree, xlabel, testVec))#将每条数据得到的特征标签添加到列表
return np.array(classLabelAll)
测试集1(鸢尾花集)
全部属性以及部分标签如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vUOmkfyz-1637412940665)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120182558070.png)]](https://img-blog.csdnimg.cn/a0602ad5a75d43d4a28b18a1a2277dbd.png)
导入以及训练CART分类树过程如下:
#测试数据集一(鸢尾花集)
import pandas
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
#导入数据集iris
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
names = ['sepal-length', 'sepal-width', 'petal-length', 'petal-width', 'class']
dataset = pandas.read_csv(url, names=names) #读取csv数据
dataset.head()
X = dataset.iloc[:,:-1].values
y = dataset.iloc[:,-1].values
#将标签分别改为0,1,2
for i in range(len(y)):
if y[i]=='Iris-setosa':
y[i]=0
elif y[i]=='Iris-versicolor':
y[i]=1
elif y[i]=='Iris-virginica':
y[i]=2
#获得特征属性名称列表
fea_label = names[:-1]
#划分训练集与测试集,参数test_size设为0.3,random_state设为666
x_train,x_test,y_train,y_test = train_test_split(X,y,test_size = 0.3,random_state = 666)
#生成CART分类树
cartTree = classify_create_tree(x_train,y_train,fea_label)
print(cartTree)
生成结果如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4yr9rFn1-1637412940667)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120182033087.png)]](https://img-blog.csdnimg.cn/ae5658af559842078d15d53c37537510.png)
选取测试数据进行测试,并计算相应的分类正确率:
classlist=classifytest(cartTree,names,x_test)
print("预测结果",classlist)
print("真实结果",y_test)
print("准确率为:%.4f"%np.mean(classlist==y_test))
结果如下:

测试集2(红酒品类数据集)
13个属性如下图所示:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AERzqg3m-1637412940671)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120182924343.png)]](https://img-blog.csdnimg.cn/2410aa9a7ab94a38950dca85203c0d2e.png)
导入以及训练CART分类树过程如下:
#测试数据集二
from sklearn.datasets import load_wine
wine = load_wine()
data = wine.data
target = wine.target
fea_names = list(wine.feature_names)
# #划分训练集与测试集,参数test_size设为0.3,random_state设为666
x_train,x_test,y_train,y_test = train_test_split(data,target,test_size = 0.35,random_state = 666)
# #生成CART分类树
cartTree = classify_create_tree(x_train,y_train,fea_names)
print(cartTree)
生成结果如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kv3GvQ3E-1637412940673)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120204946213.png)]](https://img-blog.csdnimg.cn/70fb9e64fc404ce7b3122def93560539.png)
选取测试数据进行测试,并计算相应的分类正确率:
classlist=classifytest(cartTree,fea_names,x_test)
print("预测结果",classlist)
print("真实结果",y_test)
print("准确率为:%.4f"%np.mean(classlist==y_test))
结果如下:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8w4Y8MC-1637412940674)(C:\Users\CQU CJ\AppData\Roaming\Typora\typora-user-images\image-20211120205100255.png)]](https://img-blog.csdnimg.cn/5cbd8e25bc9f4b5bae8a62182911af85.png)
总结
(1)主要面对特征值离散或者连续的问题,离散值只需要判断是否相等就可以进行二分类,而连续值需要不断的选择不同的特征值进行划分,然后计算求得基尼系数,选取基尼系数最小的情况作为划分结果。
(2)因为可能存在连续的属性,也可能存在离散的属性,所以在划分子集的时候,对这些情况就需要考虑全面。不同的值对应不同的划分方法。同样对于测试集的预测,我们也需要更具不同的属性选择不同的预测方式。
版权声明:本文为weixin_46308081原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。