实验四 决策树

目录

一、关于决策树

1、什么是决策树

2、决策树的结点与边

3、决策树的优缺点以及适用数据类型

(1)优点   

(2)缺点   

(3)适用数据类型

4、if-else规则

二、决策树的构造

1、一般流程

2、最优属性划分

(1)信息增益 ID3

(2)增益率 C4.5

(3)基尼指数 CART

三、代码实现

1、数据集展示

2、ID3决策树代码实现

(1)函数分装

(2)决策树可视化

2、C4.5决策树代码实现

(1)构建决策树

(2)决策树可视化

sklearn.tree.DecisionTreeClassifier()函数用于创建一个决策树分类器。

四、实验总结 

五、错误总结


一、关于决策树

1、什么是决策树

决策树是一类常见的机器学习方法,可以帮助我们解决分类与回归两类问题。模型可解释性强,模型符合人类思维方式,是经典的树形结构。分类决策数模型是一种描述对实例进行分类的树形结构。

2、决策树的结点与边

决策树由结点 (node) 和有向边 (directed edge) 组成。结点包含了一个根结点 (root node)、若干个内部结点 (internal node) 和若干个叶结点 (leaf node)。内部结点表示一个特征或属性叶结点表示一个类别

(蓝色的为内部结点,绿色的为叶结点。如下图)

3、决策树的优缺点以及适用数据类型

(1)优点   

        1)易于理解和实现
        2)计算复杂度不高,对中间值的缺失不敏感
        3)可以处理不相关特征数据

(2)缺点   

        1)可能会产生过度匹配问题
        2)对有时间顺序的数据,需要很多预处理的工作。
        3)当类别太多时,错误可能就会增加的比较快。

(3)适用数据类型

        数值型和标称型

4、if-else规则

决策树是一个多层if-else函数,对对象属性进行多层if-else判断,获取目标属性的类别。由于只使用if-else对特征属性进行判断,所以一般特征属性为离散值,即使为连续值也会先进行区间离散化,如可以采用二分法(bi-partition)。

二、决策树的构造

1、一般流程

(1) 收集数据:可以使用任何方法。
(2) 准备数据:树构造算法只是用于标称型数据,因此数值型数据必须离散化。
(3) 分析数据:可以使用任何方法,决策树构造完成后,可以检查决策树图形是否符合预期。
(4) 训练算法:构造一个决策树的数据结构。
(5) 测试算法:使用经验树计算错误率。当错误率达到可接收范围,此决策树就可投放使用。
(6) 使用算法:此步骤可以使用适用于任何监督学习算法,而使用决策树可以更好地理解数据的内在含义。

2、最优属性划分

(1)信息增益 ID3

  • 熵(Entropy -1984年香农提出)的概念:一条信息的信息量大小与它的不确定性有直接关系,而熵就是用来度量这件事不确定性的大小。为什么需要了解“熵”这个概念?因为决策树模型的构建和评估都与信息熵密切相关。而 信息熵(Information Entropy)是用来度量样本纯度的指标。
  • 以下是信息增益计算步骤:

输入:训练集D和属性a (这里每个属性a有V个可能的取值{ a^{1},a^{2},...,a^{v} })
输出:属性a对训练数据集D的信息增益 Gain(D,a)
1. 假定样本集合D中第i类样本所占的比例为p_{i} ( i=1,2,3,...,|n|) ,需要先求信息熵
信息熵公式: 

Ent(D)=-\sum_{k=1}^{|y|}p_{k}*log_{2}p_{k}

2. 求离散特征a对数据集D的条件信息熵
条件信息熵公式:

Ent(D|a)=\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}Ent(D^{v})

3. 计算信息增益(Information Gain)
信息增益公式:

Gain(D,a)=Ent(D)-Ent(D|a)



  • 信息熵Ent(D)的值越小,则样本集合(D)的纯度越高。

(2)增益率 C4.5

  • 因为信息增益对可取值较多的属性有所偏好,为减少这种偏好可能带来的不利影响,不直接使用信息增益,而使用增益率来选择最优划分属性。


1. 根据ID3的信息熵和条件信息熵 求信息增益
信息增益公式:

  • Gain(D,a)=Ent(D)-Ent(D|a)

2. 计算属性a的固有值IV(a)
固有值计算公式:

  • IV(a)=\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}log_{2}\frac{|D^{v}|}{|D|}

3. 根据信息增益和固有值求信息增益率
信息增益率计算公式:

  • GainRatio(D,a)=\frac{Gain(D,a)}{IV(a)}


(3)基尼指数 CART

  • CART决策树(Classification and Regression Tree)独立于另外两种决策树,一方面它使用基尼指数(Gini Index)作为划分依据,另一方面它既可以做分类,也可以做回归。Python中的sklearn决策树模型就是采用的CART来选择分支的。

步骤:

1. 计算基尼指数

  • Gini(D)=-\sum_{i=1}^{|n|}\sum_{i^{'}\neq i}^{}p_{i}p_{i^{'}}=1-\sum_{i=1}^{|n|}p_{i}^{2}


2. 对于属性a计算条件基尼指数

  • GiniIndex(D,a)=-\sum_{v=1}^{V}\frac{|D^{v}|}{|D|}Gini(D^{v})
     


三、代码实现

1、数据集展示

2、ID3决策树代码实现

(1)函数分装

  • 信息熵的计算
def cal_information_entropy(data):
    data_label = data.iloc[:,-1]
    label_class =data_label.value_counts() #总共有多少类
    Ent = 0
    for k in label_class.keys():
        p_k = label_class[k]/len(data_label)
        Ent += -p_k*np.log2(p_k)
    return Ent
  • 数据集中属性值为a的信息增益
def cal_information_gain(data, a):
    Ent = cal_information_entropy(data)
    feature_class = data[a].value_counts() #特征有多少种可能
    gain = 0
    for v in feature_class.keys():
        weight = feature_class[v]/data.shape[0]
        Ent_v = cal_information_entropy(data.loc[data[a] == v])
        gain += weight*Ent_v
    return Ent - gain
  • 获取标签多的类别

def get_most_label(data):
    data_label = data.iloc[:,-1]
    label_sort = data_label.value_counts(sort=True)
    return label_sort.keys()[0]
  • 挑选最优特征,即信息增益最大的特征
def get_best_feature(data):
    features = data.columns[:-1]
    res = {}
    for a in features:
        temp = cal_information_gain(data, a)
        res[a] = temp
    res = sorted(res.items(),key=lambda x:x[1],reverse=True)
    return res[0][0]
  • 将数据转化为(属性值:数据)的元组形式返回,并删除之前的特征列

def drop_exist_feature(data, best_feature):
    attr = pd.unique(data[best_feature])
    new_data = [(nd, data[data[best_feature] == nd]) for nd in attr]
    new_data = [(n[0], n[1].drop([best_feature], axis=1)) for n in new_data]
    return new_data
  • 创建决策树

def create_tree(data):
    data_label = data.iloc[:,-1]
    if len(data_label.value_counts()) == 1: #只有一类
        return data_label.values[0]
    if all(len(data[i].value_counts()) == 1 for i in data.iloc[:,:-1].columns): #所有数据的特征值一样,选样本最多的类作为分类结果
        return get_most_label(data)
    best_feature = get_best_feature(data) #根据信息增益得到的最优划分特征
    Tree = {best_feature:{}} #用字典形式存储决策树
    exist_vals = pd.unique(data[best_feature]) #当前数据下最佳特征的取值
    if len(exist_vals) != len(column_count[best_feature]): #如果特征的取值相比于原来的少了
        no_exist_attr = set(column_count[best_feature]) - set(exist_vals) #少的那些特征
        for no_feat in no_exist_attr:
            Tree[best_feature][no_feat] = get_most_label(data) #缺失的特征分类为当前类别最多的

    for item in drop_exist_feature(data,best_feature): #根据特征值的不同递归创建决策树
        Tree[best_feature][item[0]] = create_tree(item[1])
    return Tree
  • ds
def predict(Tree , test_data):
    first_feature = list(Tree.keys())[0]
    second_dict = Tree[first_feature]
    input_first = test_data.get(first_feature)
    input_value = second_dict[input_first]
    if isinstance(input_value , dict): #判断分支还是不是字典
        class_label = predict(input_value, test_data)
    else:
        class_label = input_value
    return class_label
  • 主函数
if __name__ == '__main__':
    #读取数据
    data = pd.read_csv('C://Users//小羔//Desktop//机器学习//watermelon_data.csv')

    #统计每个特征的取值情况作为全局变量
    column_count = dict([(ds, list(pd.unique(data[ds]))) for ds in data.iloc[:, :-1].columns])

    #创建决策树
    dicision_Tree = create_tree(data)
    print(dicision_Tree)

    #测试数据
    test_data_1 = {'色泽':'青绿','根蒂':'蜷缩','敲声':'浊响','纹理':'稍糊','脐部':'凹陷','触感':'硬滑'}
    test_data_2 = {'色泽': '乌黑', '根蒂': '稍蜷', '敲声': '浊响', '纹理': '清晰', '脐部': '凹陷', '触感': '硬滑'}
    result = predict(dicision_Tree,test_data_1)
    print('青绿,蜷缩,浊响,稍糊,凹陷,硬滑的西瓜为'+'好瓜'if result == 1 else '青绿,蜷缩,浊响,稍糊,凹陷,硬滑的西瓜为坏瓜')
    result2 = predict(dicision_Tree,test_data_2)
    print('乌黑,稍蜷,浊响,清晰,凹陷,硬滑的西瓜为'+'好瓜'if result2 == 1 else '乌黑, 稍蜷, 浊响,清晰,凹陷,硬滑的西瓜为坏瓜')

结果截图:

(2)决策树可视化

  • 设置文本框和箭头格式
decisionNode = dict(boxstyle = "sawtooth", fc = "0.8")
leafNode = dict(boxstyle = "round4", fc = "0.8")
arrow_args = dict(arrowstyle = "<-")
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['font.family'] = 'sans-serif'
  • 画出结点
def plotNode(nodeTxt, centerPt, parentPt, nodeType):
    createPlot.ax1.annotate(nodeTxt, xy = parentPt,\
    xycoords = "axes fraction", xytext = centerPt, textcoords = 'axes fraction',\
    va = "center", ha = "center", bbox = nodeType, arrowprops = arrow_args)
    
  • 获取决策树的叶子结点树
def getNumLeafs(myTree):
    leafNumber = 0
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        if(type(secondDict[key]).__name__ == 'dict'):
            leafNumber = leafNumber + getNumLeafs(secondDict[key])
        else:
            leafNumber += 1
    return leafNumber
  • 采用递归法获取决策树的高度
def getTreeDepth(myTree):
    maxDepth = 0
    firstStr = list(myTree.keys())[0]
    secondDict = myTree[firstStr]
    for key in secondDict.keys():
        #测试以查看节点是否是字典,如果不是,则它们是叶节点
        if type(secondDict[key]).__name__=='dict':
            thisDepth = 1 + getTreeDepth(secondDict[key])
        else:   thisDepth = 1
        if thisDepth > maxDepth: maxDepth = thisDepth
    return maxDepth
  •  在父子结点前添加相关信息
def plotMidText(cntrPt, parentPt, txtString):
    xMid = (parentPt[0]-cntrPt[0])/2.0 + cntrPt[0]
    yMid = (parentPt[1]-cntrPt[1])/2.0 + cntrPt[1]
    createPlot.ax1.text(xMid, yMid, txtString, va="center", ha="center", rotation=30)
  • 画出决策树
def plotTree(myTree, parentPt, nodeTxt):#如果第一个键告诉你在什么特征处
    numLeafs = getNumLeafs(myTree)      #被拆分了,这决定了这棵树的 x 宽度
    depth = getTreeDepth(myTree)
    firstStr = list(myTree.keys())[0]     #此结点的标签应为以下内容
    cntrPt = (plotTree.xOff + (1.0 + float(numLeafs))/2.0/plotTree.totalW, plotTree.yOff)
    plotMidText(cntrPt, parentPt, nodeTxt)
    plotNode(firstStr, cntrPt, parentPt, decisionNode)
    secondDict = myTree[firstStr]
    plotTree.yOff = plotTree.yOff - 1.0/plotTree.totalD
    for key in secondDict.keys():
        if type(secondDict[key]).__name__=='dict':
            plotTree(secondDict[key],cntrPt,str(key))        #递归
        else:   #这是叶子结点,输出
            plotTree.xOff = plotTree.xOff + 1.0/plotTree.totalW
            plotNode(secondDict[key], (plotTree.xOff, plotTree.yOff), cntrPt, leafNode)
            plotMidText((plotTree.xOff, plotTree.yOff), cntrPt, str(key))
    plotTree.yOff = plotTree.yOff + 1.0/plotTree.totalD
  • 初始化画布 
def createPlot(inTree):
    fig = plt.figure(1, facecolor='white')
    fig.clf()
    axprops = dict(xticks=[], yticks=[])
    createPlot.ax1 = plt.subplot(111, frameon=False, **axprops) 
    plotTree.totalW = float(getNumLeafs(inTree))
    plotTree.totalD = float(getTreeDepth(inTree))
    plotTree.xOff = -0.5/plotTree.totalW; plotTree.yOff = 1.0;
    plotTree(inTree, (0.5,1.0), '')
    plt.show()

结果截图:

2、C4.5决策树代码实现

(1)构建决策树

  • 数据导入
data = pd.read_csv('C://Users//小羔//Desktop//机器学习//watermelon_data.csv')

x = data[["色泽","根蒂","敲声","纹理","脐部","触感"]].copy()
y = data['好瓜'].copy()
print(data)
  • 将特征值数值化
for i in ["色泽","根蒂","敲声","纹理","脐部","触感"]:
    for j in range(len(x)):
        if(x[i][j] == "青绿" or x[i][j] == "蜷缩" or data[i][j] == "浊响" \
           or x[i][j] == "清晰" or x[i][j] == "凹陷" or x[i][j] == "硬滑"):
            x[i][j] = 1
        elif(x[i][j] == "乌黑" or x[i][j] == "稍蜷" or data[i][j] == "沉闷" \
           or x[i][j] == "稍糊" or x[i][j] == "稍凹" or x[i][j] == "软粘"):
            x[i][j] = 2
        else:
            x[i][j] = 3
            
y = y.copy()
for i in range(len(y)):
    if(y[i] == 0):
        y[i] = int(1)
    else:
        y[i] = int(-1) 
  • 转化数据x,y格式,数据框dataframe

x = pd.DataFrame(x).astype(int)
y = pd.DataFrame(y).astype(int)
print(x)
print(y)

结果截图:

  • 将80%数据用于训练,20%数据用于测试
x_train, x_test, y_train, y_test = train_test_split(x,y,test_size=0.2)
print(x_train)

结果截图:

(2)决策树可视化

sklearn.tree.DecisionTreeClassifier()函数用于创建一个决策树分类器。

具体可参考:DecisionTreeClassifier()函数解析

export_graphviz此函数生成决策树的 graphviz 表示

具体可参考:

clf = tree.DecisionTreeClassifier(criterion='gini', splitter='best', max_depth=None, min_samples_split=2,
											min_samples_leaf=1, min_weight_fraction_leaf=0.0, max_features=None, 
											random_state=None, max_leaf_nodes=None, min_impurity_decrease=0.0, 
											 class_weight=None)


feature_name = ["色泽","根蒂","敲声","纹理","脐部","触感"]
clf.fit(x_train,y_train) 
dot_data = tree.export_graphviz(clf,feature_names= feature_name,class_names=["好瓜","坏瓜"]                                
                                ,filled=True                                
                                ,rounded=True
                                ,out_file =None  
                                ) 
graph = graphviz.Source(dot_data)
graph.save('C://Users//小羔//Desktop//机器学习//watermelon_tree.dot') #保存

结果截图:

​ 

四、实验总结 

1、决策树作为经典分类算法,具有计算复杂度低、结果直观、分类效率高等优点。

2、学习通过用sk-learn库对西瓜数据集,进行ID3算法代码实现,通过sklearn库和graphviz库C4.5的算法代码实现。

3、采用C4.5算法的决策树可视化使用的是graphviz库,在可视化时采用时,由于没有安装graphviz所以保存下来的dot文件只能打开为文本文件。

五、错误总结

  • 使用决策树分类器函数时报错,内部参数使用错误。

解决过程:通过网上查阅资料得知,“_init_() got an unexpected keyword argument'min_impurity_split”是一个经常遇到的错误信息,它通常出现在使用Scikit-earn库建立决策树、随机森林等模型时。它的产生是因为Scikit-learn库在版本更新后取消了min_impurity_split这个参数。该参数是用于设置节点停止分裂的阈值,如果纯度(impurity)小于该值,则终止分裂。在Salkit-earn库0.23及以后的版本中,min_impurity_Split被替换为min_impurity_decrease,该参数的作用与min_impurity_split大致相同,但它计算方式略有不同。

  • 在进行测试数据之前没有对模型进行实例化,我的理解就是在测试数据之前,我们只是定义了决策树模型,而没有对决策树模型输入数据,进行训练,所以模型不知道怎么进行预测。

​​

解决:测试之前加入:clf.fit(x_train,y_train) 进行实例化,问题解决。解决后正常输出结果。

 

  • 19
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

萍萍无奇a

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

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

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

打赏作者

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

抵扣说明:

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

余额充值