Advertisement

数据挖掘--决策树

阅读量:

1. 算法原理

决策树是通过一系列规则对数据进行分类的过程。它提供一种在什么条件下会得到什么值的类似规则的方法。决策树分为分类树和回归树两种,分类树对离散变量做决策树,回归树对连续变量做决策树 。近来的调查表明决策树也是最经常使用的数据挖掘算法,它的概念非常简单。决策树算法之所以如此流行,一个很重要的原因就是使用者基本上不用了解机器学习算法,也不用深究它是如何工作的。直观看上去,决策树分类器就像判断模块和终止块组成的流程图,终止块表示分类结果(也就是树的叶子)。判断模块表示对一个特征取值的判断(该特征有几个值,判断模块就有几个分支)。

如果不考虑效率等,那么样本所有特征的判断级联起来终会将某一个样本分到一个类终止块上。实际上,样本所有特征中有一些特征在分类时起到决定性作用,决策树的构造过程就是找到这些具有决定性作用的特征,根据其决定性程度来构造一个倒立的树–-决定性作用最大的那个特征作为根节点,然后递归找到各分支下子数据集中次大的决定性特征,直至子数据集中所有数据都属于同一类。所以,构造决策树的过程本质上就是根据数据特征将数据集分类的递归过程,我们需要解决的第一个问题就是,当前数据集上哪个特征在划分数据分类时起决定性作用。

2. 优缺点分析

决策树适用于数值型和标称型(离散型数据,变量的结果只在有限目标集中取值),能够读取数据集合,提取一些列数据中蕴含的规则。在分类问题中使用决策树模型有很多的优点,决策树计算复杂度不高、便于使用、而且高效,决策树可处理具有不相关特征的数据、可很容易地构造出易于理解的规则,而规则通常易于解释和理解。

决策树模型也有一些缺点,比如处理缺失数据时的困难、过度拟合以及忽略数据集中属性之间的相关性等。

3. Adult数据集

  • 数据集使用 UCI 数据集中的 Adult 数据集。数据集下载链接:http://archive.ics.uci.edu/ml/datasets/Adult ,我们提供了已经下载好的数据集:【Data Mining】Adult
  • 源数据集共有 14 个属性,分类有两种 (<=50k,>50K)。这 14 个属性中,有 6 个是连续类型的,有 8 个是离散类型的。

4. 代码展示

4.1 导入依赖

由于AI Studio平台没有提供sklearn_pandas库,因此我们手动安装一下,只要运行下方代码块即可:

In [1]

复制代码
 # 升级pip

    
 !pip install --upgrade pip
    
  
    
 # 安装sklearn_pandas
    
 !pip install sklearn_pandas
复制代码

In [2]

复制代码
 import pandas as pd

    
 import numpy as np 
    
 from sklearn_pandas import DataFrameMapper #sklearn-pandas模块提供了Scikit-Learn的机器学习方法和pandas风格的数据框架之间的桥梁。
    
 from sklearn.preprocessing import LabelEncoder
    
 from sklearn.tree import DecisionTreeClassifier #sklearn提供的决策树分类器
    
 from sklearn.tree import export_graphviz # 决策树可视化
    
 import graphviz # 用于绘制DOT语言脚本描述的图形
    
  
    
 from matplotlib import pyplot as plt
    
 from pylab import *
    
  
    
 from collections import defaultdict,Counter 
    
 from tqdm import tqdm # 进度条

4.2 加载数据集

In [3]

复制代码
 # 14个属性+类别

    
 columns=['age','workclass','fnlwgt','education','education_num','marital_status','occupation','relationship',
    
              'race','sex','capital_gain','capital_loss','hours_per_week','native_country','annual_salary']
    
  
    
 # 加载训练集
    
 adult_train_path = 'data/data87314/adult.data'
    
 adult_train = pd.read_csv(adult_train_path,header=None,names=columns)
    
  
    
 # 加载测试集
    
 adult_test_path = 'data/data87314/adult.test'
    
 adult_test = pd.read_csv(adult_test_path,header=None,names=columns)

数据展示:

In [4]

复制代码
    adult_train.head()
复制代码
    age          workclass  fnlwgt   education  education_num  \

    
 0   39          State-gov   77516   Bachelors             13   
    
 1   50   Self-emp-not-inc   83311   Bachelors             13   
    
 2   38            Private  215646     HS-grad              9   
    
 3   53            Private  234721        11th              7   
    
 4   28            Private  338409   Bachelors             13   
    
  
    
     marital_status          occupation    relationship    race      sex  \
    
 0        Never-married        Adm-clerical   Not-in-family   White     Male   
    
 1   Married-civ-spouse     Exec-managerial         Husband   White     Male   
    
 2             Divorced   Handlers-cleaners   Not-in-family   White     Male   
    
 3   Married-civ-spouse   Handlers-cleaners         Husband   Black     Male   
    
 4   Married-civ-spouse      Prof-specialty            Wife   Black   Female   
    
  
    
    capital_gain  capital_loss  hours_per_week  native_country annual_salary  
    
 0          2174             0              40   United-States         <=50K  
    
 1             0             0              13   United-States         <=50K  
    
 2             0             0              40   United-States         <=50K  
    
 3             0             0              40   United-States         <=50K  
    
 4             0             0              40            Cuba         <=50K

4.3 数据向量化

使用LabelEncoderDataFrameMapper将非数值列的数据转化为数值,也即向量化。

处理训练集:

In [5]

复制代码
 # 获取非数值列的列名

    
 train_dtype=adult_train.dtypes
    
 #print(train_dtype)
    
 train_list=[train_dtype.index[i] for i in range(len(train_dtype)) if train_dtype[i]=='object']
    
  
    
  
    
 # 使用LabelEncoder、DataFrameMapper将非数值列的数据转化为数值,也即向量化。 
    
 # 列的顺序会发生变化
    
 mapper=DataFrameMapper([(i, LabelEncoder()) for i in train_list], df_out=True, default=None)
    
 adult_train = mapper.fit_transform(adult_train.copy()).astype(dtype='int64')

处理测试集:

In [6]

复制代码
 test_dtype=adult_test.dtypes

    
 test_list=[test_dtype.index[i] for i in range(len(test_dtype)) if test_dtype[i]=='object']
    
  
    
  
    
 mapper=DataFrameMapper([(i, LabelEncoder()) for i in test_list], df_out=True, default=None)
    
 adult_test = mapper.fit_transform(adult_test.copy()).astype(dtype='int64')

向量化后的数据展示:

In [7]

复制代码
    adult_train.head()
复制代码
    workclass  education  marital_status  occupation  relationship  race  sex  \

    
 0          7          9               4           1             1     4    1   
    
 1          6          9               2           4             0     4    1   
    
 2          4         11               0           6             1     4    1   
    
 3          4          1               2           6             0     2    1   
    
 4          4          9               2          10             5     2    0   
    
  
    
    native_country  annual_salary  age  fnlwgt  education_num  capital_gain  \
    
 0              39              0   39   77516             13          2174   
    
 1              39              0   50   83311             13             0   
    
 2              39              0   38  215646              9             0   
    
 3              39              0   53  234721              7             0   
    
 4               5              0   28  338409             13             0   
    
  
    
    capital_loss  hours_per_week  
    
 0             0              40  
    
 1             0              13  
    
 2             0              40  
    
 3             0              40  
    
 4             0              40

4.4 属性与标签划分

  • fnlwgt属性,由于数据过于分散,在生成决策树的过程中会很耗时,在数据处理过程中删除该属性。
  • 最后得到的训练集大小为 (32561, 13) (32561,);测试集大小为(16281, 13) (16281,)。

In [8]

复制代码
 col=list(adult_train.columns)

    
 label='annual_salary'
    
 col.remove(label)
    
 col.remove('fnlwgt')
    
  
    
 x_train,y_train=adult_train[col].values,adult_train[label].values
    
 x_test,y_test=adult_test[col].values,adult_test[label].values
    
  
    
 print("训练集shape: ",x_train.shape,y_train.shape,"\n测试集shape: ",x_test.shape,y_test.shape)
复制代码

4.5 手动实现决策树分类算法并可视化

4.5.1 定义决策树类:

In [9]

复制代码
 class Branch:

    
     no=0 # 决策树节点的编号
    
     column=0 # 该节点的属性
    
     entropy=0 # 交叉熵
    
     samples=0 # 该节点下的数据数目
    
     value=[] # 记录由该节点划分的不同类别的数据
    
     split=0 # 分类临界值
    
     clss=-1 # 该节点的分类
    
     
    
     branch_positive=None # 左分支
    
     branch_negative=None # 右分支

4.5.2 构造决策树分类器

主要进行的操作有:

  • 定义计算熵的函数
  • 定义根据指定属性进行分类的函数
  • 定义在指定数据范围内查找最佳分类属性的函数
  • 定义递归的构造决策树分类器的函数

定义计算熵的函数:

In [10]

复制代码
 def entroy(y):

    
     counter=Counter(y)
    
     res=0.0
    
     for num in counter.values():
    
     p=num/len(y) #每个类别的占比
    
     res+=-p*np.log2(p)
    
     return res

定义数据划分的函数:

In [11]

复制代码
 def split(x,y,d,value):

    
     # x 数据集属性
    
     # y 数据集标签
    
     # d 划分的维度
    
     # value 划分的参考值
    
     left=(x[:,d]<=value)
    
     
    
     right=(x[:,d]>value)
    
     return x[left],x[right],y[left],y[right]

定义选取最好分类特征的函数,在当前的数据下(x,y)选取最合适的分类特征,并返回分类后的左右分支数据:

In [12]

复制代码
 def find_best_fearture(x,y):

    
     best_entroy=entroy(y) # 熵初始化
    
     best_v=None # 分类临界值
    
     best_d='' # 分类属性
    
     x_r=None
    
     x_l=None
    
     y_r=None
    
     y_l=None
    
     # 逐个属性进行比较
    
     for d in range(x.shape[1]):
    
     # 每个属性中,寻找最好的切分点。
    
     # 因为有的属性本身是数值类型的,需要进行更细致的查找,确定最好的切分点。
    
     sorted_index=np.argsort(x[:,d])# 根据d维度进行排序
    
     for i in range(1,len(x)):#遍历每个样本
    
         if x[sorted_index[i-1],d]!=x[sorted_index[i],d]:
    
             v=(x[sorted_index[i-1],d]+x[sorted_index[i],d])/2.0
    
             # 调用split函数进行划分
    
             xl,xr,yl,yr=split(x,y,d,v)
    
             n1=len(yl)
    
             n2=len(yr)
    
             n=n1+n2
    
             
    
             # 计算基尼系数
    
             e=n1/n*entroy(yl)+n2/n*entroy(yr)
    
             
    
             if e<best_entroy:
    
                 best_entroy,best_d,best_v,x_l,x_r,y_l,y_r=e,d,v,xl,xr,yl,yr
    
     
    
     # 返回值:最好的分类属性、分类临界值、基尼系数、左分支属性、右分支属性、左分支标签、右分支标签
    
     return best_d,best_v,entroy(y),x_l,x_r,y_l,y_r

定义决策树的构造函数,通过该函数递归生成一颗决策树:

In [13]

复制代码
 number=0

    
  
    
 def decison_tree_in(x,y,depth,max_depth=3):
    
     global number
    
     branch=Branch()
    
     branch.no=number
    
     number+=1
    
     ddepth=depth # 记录分支的深度
    
     
    
     branch.samples=len(y) # 记录该结点所包含数据的数量
    
     n_positive=y[y==1].shape[0]
    
     branch.value=[branch.samples-n_positive,n_positive] # 该结点下,0与1类别数目列表
    
     if branch.value[0]>branch.value[1]:
    
     branch.clss=0
    
     else :
    
     branch.clss=1
    
     branch.entropy=entroy(y) # 计算该节点下的信息熵
    
     best_feature=find_best_fearture(x,y)
    
     branch.column=best_feature[0]
    
     branch.split=best_feature[1]
    
     
    
     if ddepth==max_depth or branch.column=='':
    
     return branch
    
     else:
    
     x_l,y_l=best_feature[3],best_feature[5]
    
     branch.branch_positive=decison_tree_in(x_l,y_l,ddepth+1,max_depth)
    
     x_r,y_r=best_feature[4],best_feature[6]
    
     branch.branch_negative=decison_tree_in(x_r,y_r,ddepth+1,max_depth)
    
     
    
     return branch

In [14]

复制代码
    tree=decison_tree_in(x_train,y_train,0,max_depth=4)

4.5.3 可视化构造好的决策树分类器模型

使用graphviz(使用DOT语言脚本绘制图形)可视化决策树。

In [15]

复制代码
 def get_dot_data_innner(branch:Branch, dot_data):

    
    
    
     if branch.branch_positive:
    
     dot_data=dot_data+'{} [label=<{}≤{}<br/>entropy = {:.3f}<br/>samples = {}<br/>value = {}<br/>class = {}> , fillcolor="#FFFFFFFF"] ;\r\n'.format(
    
         branch.no, col[branch.column],branch.split, branch.entropy, branch.samples, branch.value,branch.clss)
    
     else:
    
     dot_data=dot_data+'{} [label=<{} <br/>entropy = {:.3f}<br/>samples = {}<br/>value = {}<br/>class = {}> , fillcolor="#FFFFFFFF"] ;\r\n'.format(
    
         branch.no,branch.column, branch.entropy, branch.samples, branch.value,branch.clss)
    
         
    
     if branch.branch_positive:
    
     dot_data=dot_data+'{} -> {} [labeldistance=2.5, labelangle=45, headlabel="True"]; \r\n'.format(branch.no, branch.branch_positive.no)
    
     dot_data=get_dot_data_innner(branch.branch_positive, dot_data)
    
     
    
     if branch.branch_negative:
    
     dot_data=dot_data+'{} -> {} [labeldistance=2.5, labelangle=45, headlabel="False"]; \r\n'.format(branch.no, branch.branch_negative.no)
    
     dot_data=get_dot_data_innner(branch.branch_negative, dot_data)
    
     
    
     return dot_data

In [16]

复制代码
 def get_dot_data(branch:Branch):

    
     dot_data="""
    
 digraph Tree {
    
 node [shape=box, style="filled, rounded", color="black", fontname=helvetica] ;
    
 edge [fontname=helvetica] ;
    
 """
    
     dot_data=get_dot_data_innner(branch,  dot_data)
    
     dot_data=dot_data+'\r\n}'
    
     return dot_data

In [17]

复制代码
    dot_data=get_dot_data(tree)

In [18]

复制代码
 graph = graphviz.Source(dot_data)

    
 graph.render('./data/my_dt', format='png')
    
 graph

4.5.4 用测试集进行验证,计算模型分类准确性得分

In [20]

复制代码
 def cl(branch:Branch, x):

    
     
    
     # 纯的数据集,不需要继续划分
    
     if branch.split==None:
    
     return branch.clss
    
     
    
     # 继续划分,直至最大深度
    
     if x[branch.column]<=branch.split:
    
     if branch.branch_positive is not None:
    
         return cl(branch.branch_positive,x)
    
     else:
    
         return branch.clss
    
     
    
     if x[branch.column]>branch.split:
    
     if branch.branch_negative is not None:
    
         return cl(branch.branch_negative,x)
    
     else:
    
         return branch.clss

In [21]

复制代码
 def compute_score(branch:Branch,x,y):

    
     re=[]
    
     for i in range(len(x)):
    
     re.append(cl(branch,x[i]))
    
     
    
     if len(re)!=len(y):
    
     print("预测结果与实际结果数量不同,请检查程序。")
    
     exit(0)
    
     
    
     a= re==y
    
     score=a[a==1].shape[0]/a.shape[0]
    
     
    
     return score

In [22]

复制代码
 score=compute_score(tree,x_test,y_test)

    
 print('自己搭建的决策树分类准确度得分:', score)
复制代码

4.6 使用sklearn提供的决策树分类器进行实验

4.6.1 实例化决策树分类器

In [23]

复制代码
    treeClassifier = DecisionTreeClassifier(max_depth=4,criterion='entropy')

4.6.2 决策树分类器训练

In [24]

复制代码
    treeClassifier.fit(x_train, y_train)
复制代码

4.6.3 可视化训练好的决策树分类器模型

In [25]

复制代码
 export_graphviz(treeClassifier, out_file="dt_clf.pdf",feature_names=col)

    
 with open('dt_clf.pdf','r') as f:
    
     dot_graph = f.read()
    
 graphviz.Source(dot_graph)

4.6.4 用测试集进行验证,计算模型分类准确性得分

In [26]

复制代码
 score = treeClassifier.score(x_test, y_test)

    
 print('使用sklearn提供的决策树分类准确度得分:', score)
复制代码

5. 总结

  • 本项目通过自行搭建决策树和使用sklearn提供的决策树进行实验,在Adult数据集的测试集上取得了一致的结果。
  • 我们通过graphviz可视化了自行搭建的决策树,使用sklearn.tree.export_graphviz可视化了sklearn提供的决策树。

在线教程

有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓


请添加图片描述

人工智能书籍

第一阶段:零基础入门(3-6个月)

新手应首先通过少而精的学习,看到全景图,建立大局观。 通过完成小实验,建立信心,才能避免“从入门到放弃”的尴尬。因此,第一阶段只推荐4本最必要的书(而且这些书到了第二、三阶段也能继续用),入门以后,在后续学习中再“哪里不会补哪里”即可。

第二阶段:基础进阶(3-6个月)

熟读《机器学习算法的数学解析与Python实现》并动手实践后,你已经对机器学习有了基本的了解,不再是小白了。这时可以开始触类旁通,学习热门技术,加强实践水平。在深入学习的同时,也可以探索自己感兴趣的方向,为求职面试打好基础。

第三阶段:工作应用

这一阶段你已经不再需要引导,只需要一些推荐书目。如果你从入门时就确认了未来的工作方向,可以在第二阶段就提前阅读相关入门书籍(对应“商业落地五大方向”中的前两本),然后再“哪里不会补哪里”。

有需要的小伙伴,可以点击下方链接免费领取或者V扫描下方二维码免费领取🆓



在这里插入图片描述

全部评论 (0)

还没有任何评论哟~