统计学习方法第五章:决策树法(decision tree)

目录

决策树法的基本知识点:

决策树生成的基本过程

决策树特征选择的准则

信息增益算法

信息增益算法的相关例题

代码分析:

决策树生成算法

ID3算法

ID3算法的相关例题

 ID3算法决策树生成代码分析

C4.5算法

 最小二乘回归树的生成算法

CART生成算法

CART生成算法的相关例题 

​编辑

 决策树的剪枝

CART剪枝算法

​编辑​编辑 

sklearn.tree.DecisionTreeClassifier 代码分析


工大菜凯,希望通过做笔记记录自己学的知识,也希望能帮助到同样在入门的同学 ❥侵权立删~

决策树法的基本知识点:

  • 决策树是基于特征对实例进行分类的过程,分为3个步骤:特征选择、决策树生成、决策树剪枝(考虑全局最优,对已生成的树自下而上进行剪枝,将树变得更加简单,减少过拟合现象,提高树的泛化能力)
  • 决策树生成学习局部的模型,而决策树剪枝学习整体的模型
  • 决策树学习的本质:从训练数据集中归纳出一组分类规则,使其与训练数据集矛盾较小、具有较好的泛化能力
  • 决策树是由结点和有向边组成的,其中结点又分为:内部结点(表示一个特征或属性)和叶结点(表示一个类)
  • 决策树 If-then规则的集合:互斥并完备
  1. 由决策树的根节点-->叶节点的每一条路构建一条规则
  2. 路径上的内部结点的特征对应着规则的条件
  3. 叶结点的类对应着规则的结论

决策树生成的基本过程

  1. 训练数据都放在根结点,选择最优特征(如果特征数很多,在决策树学习开始前,可以只留下对训练数据有足够分类能力的特征)
  2. 根据特征将训练数据集分割成多个子集
  3. 使各个子集都有一个在当前条件下最好的分类
  • 如果这些子集已经基本正确分类,则构建叶结点,将这些子集分到所对应的叶结点中去
  • 如果还有子集不能被基本正确的分类,那么对这些子集选择新的最优特征,继续对其进行分割,构建相应的结点。
  • 递归的进行下去,直到正确分类,就生成了一颗决策树

决策树特征选择的准则

  • 特征选择的准则:信息增益、信息增益比
  1. 信息增益:表示在得知特征X的信息下而使得类Y的信息的不确定性减少的程度
  2. 信息增益比:在分类问题困难时,也就是说在训练数据集的经验熵大的时候,信息增益值会偏大,反之,信息增益会偏小。使用信息增益比可以对这一问题进行校正
  • 熵:表示随机变量不确定性的度量(熵越大,随机变量的不确定性就越大)
熵只与X的分布有关,与X的取值无关、 一般以2 或 e 为底的log

 条件熵

信息增益

  • 信息增益大的特征具有更强的分类能力

g\left ( D,A \right ):表示由于特征A而使得对数据集D的分类不确定性减少的程度

H\left ( D \right ):表示对数据集D进行分类的不确定性

H\left ( D|A \right ):表示在特征A给定的条件下对数据集D进行分类的不确定性

  • 根据特征选择的信息增益准则方法:对训练数据集(或子集)D,计算其每个特征的信息增益,并比较它们的大小,选择信息增益最大的特征,做为该结点处的最优特征

信息增益算法

  • 将训练数据集D分成K类
  • 特征A有n个不同的取值,根据特征A的n个不同的取值将训练数据集D分成n个子集
  • 由特征A不同的取值生成的子集D_{i}中属于类C_{k}的样本集合为D_{ik}

信息增益比的计算

信息增益算法的相关例题

  题目:根据信息增益准则选择最优特征

训练数据表
  •  将整个训练数据表分成两类   C_{k}  其中 k=1,2 (是,否)

代码分析:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

from collections import Counter
import math
from math import log

import pprint
def create_data():
    datasets = [['青年', '否', '否', '一般', '否'],
               ['青年', '否', '否', '好', '否'],
               ['青年', '是', '否', '好', '是'],
               ['青年', '是', '是', '一般', '是'],
               ['青年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '好', '否'],
               ['中年', '是', '是', '好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '好', '是'],
               ['老年', '是', '否', '好', '是'],
               ['老年', '是', '否', '非常好', '是'],
               ['老年', '否', '否', '一般', '否'],
               ]
    labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']
    # 前缀"u"代表 它们是Unicode字符串、"u"代表Unicode字符串的前缀
    # 返回数据集和每个维度的名称
    return datasets, labels
  • 训练数据框的显示
datasets, labels = create_data()
train_data = pd.DataFrame(datasets, columns=labels)
训练数据框的构成
# 经验熵H(D)函数定义
def calc_ent(datasets):
    # 训练数据样本个数 data_length =15
    data_length = len(datasets)
    label_count = {}
    for i in range(data_length):
        # datasets[i][-1] 代表每一个样本的类别信息
        label = datasets[i][-1]
     # 这段代码是在判断一个标签是否在label_count字典中,如果不在则将其添加,并将对应的值设为0
        if label not in label_count:
            # label_count[label] = 0 将label作为键,将其对应的值设置为0
            label_count[label] = 0
        # 计算相同类别样本的数目
        label_count[label] += 1
    # 经验熵H(D)的计算
    ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
    return ent
# 经验条件熵 H(D|A) 函数
def cond_ent(datasets, axis=0):
    data_length = len(datasets)
    # 创建一个空字典,用于存储每个特征值对应的样本子集
    feature_sets = {}
    for i in range(data_length):
        # axis 是对特征选择
        feature = datasets[i][axis]

        # 判断特征值是否已经存在于字典中,如果不存在,则将其添加为键,对应的值初始化为空列表
        if feature not in feature_sets:
            feature_sets[feature] = []
        # 将当前样本添加到对应特征值的样本子集中,形成对应特征的样本子集,便于样本子集经验熵的计算
        feature_sets[feature].append(datasets[i])
    # 其中p是子集列表
    cond_ent = sum([(len(p)/data_length)*calc_ent(p) for p in feature_sets.values()])
    return cond_ent
# 信息增益g(D,A)=H(D)-H(D|A) 函数
def info_gain(ent, cond_ent):
    return ent - cond_ent
def info_gain_train(datasets):
    # 题中count = 4 表示训练数据每个样本的特征数量 因为最后一个特征是样本的目标变量 所以要-1
    count = len(datasets[0]) - 1
    # 计算训练数据集的经验熵
    ent = calc_ent(datasets)
    # 创建一个的空列表 存储特征与对应特征的信息增益
    best_feature = []
    # 其中c代表特征的选择 
    for c in range(count):
        c_info_gain = info_gain(ent, cond_ent(datasets, axis=c))
        # 将每一个特征与其对应的信息增益,形成一个数据对存放在best_feature列表中
        best_feature.append((c, c_info_gain))
        print('特征({}) - info_gain - {:.3f}'.format(labels[c], c_info_gain))

    # 比较各个特征下的信息增益大小
    best_ = max(best_feature, key=lambda x: x[-1])

    return '特征({})的信息增益最大,选择为根节点特征'.format(labels[best_[0]])
信息增益算法的运行结果

决策树生成算法

ID3算法

  • ID3算法只有树的生成,容易产生过拟合现象

具体方法

  • 从根结点开始,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立结点,再对子节点递归调用以上方法,构建决策树(知道3所有特征的信息增益均很小或没有特征可以选择为止)

ID3算法的相关例题
训练数据表

 题目:对训练数据表,利用ID3建立决策树

  • 由信息增益算法的相关例题可知,特征A_{3}(有自己的房子)的信息增益值最大,所以选择特征A_{3}作为根结点的特征
  • A_{3}特征将训练数据集D划分为两个子集D_{1}A_{3}取值为:“是”)和D_{2}A_{3}取值为:“否”)。由于通过训练数据表可知,子集D_{1}A_{3}取值为:“是”)只有同一类样本点(类别为:“是”),所以它成为一个叶结点,结点的类标记为:“是”
  • D_{2}A_{3}取值为:“否”),则需从特征A_{1}(年龄)、A_{2}(有工作)和A_{4}(信贷情况)中选择新的特征,计算各个特征的信息增益

    选择信息增益最大的特征A_{2}(有工作)作为结点的特征,由于 A_{2}(有工作)有两个可能的取值,从这一结点引出两个子结点:一个对应“是”(有工作)子结点(即 没有房 但有工作),包含3个羊样本,它们都属于同一类(类别:“是”),所以这是一个叶结点,类标记为:“是”;另一个对应“否”(无工作)的子结点(即 没有房 也没有工作),包含6个样本,它们也属于同一类,所以这也是一个叶结点,类标记为“否”

 ID3算法决策树生成代码分析
# 定义节点类 二叉树
class Node:
    #__init__方法用于初始化节点对象 参数包括 root(布尔值,表示是否为根节点)label(节点标签)
    # feature_name(特征名称)和 feature(特征值)
    def __init__(self, root=True, label=None, feature_name=None, feature=None):
        self.root = root
        self.label = label
        self.feature_name = feature_name
        self.feature = feature
        # 创建一个空字典
        self.tree = {}
        # 创建一个结果字典result
        self.result = {'label:': self.label, 'feature': self.feature, 'tree': self.tree}

    # __repr__ 是 Python 中的一个特殊方法,用于返回节点对象的字符串表示 作用:如图 1所示
    def __repr__(self):
        return '{}'.format(self.result)

    # add_node 用于向结点的tree字典中添加子结点 有两个参数,val表示特征值 node 表示对应的子结点
    # 在决策树模型中,每个结点都可以有多个子结点,每个子结点对应一个特征值
# 通过调用add_node方法,可以将子结点添加到结点tree字典中,以便构建决策树的结结构 作用:如图2所示
    def add_node(self, val, node):
        self.tree[val] = node

    # 用于预测给定特征的样本所述的类别,该方法有一个参数features 表示输入样本的特征
    # 根据输入样本的特征值features[self.feature]在当前结点的tree字典中找到对应的子结点
    # 并递归调用子结点的predict方法来进行预测,实现在决策树上沿着特征值路径向下预测的过程
    def predict(self, features):
        if self.root is True:
            return self.label
        return self.tree[features[self.feature]].predict(features)
class DTree:
    # 初始化决策树对象,其中epsilon是一个阈值参数
    def __init__(self, epsilon=0.1):
        self.epsilon = epsilon
        # _tree 用于存储建立的决策树结构
        self._tree = {}
   # 熵
    @staticmethod
    def calc_ent(datasets):
        data_length = len(datasets)
        label_count = {}
        for i in range(data_length):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        ent = -sum([(p/data_length)*log(p/data_length, 2) for p in label_count.values()])
        return ent

    # 经验条件熵
    def cond_ent(self, datasets, axis=0):
        data_length = len(datasets)
        feature_sets = {}
        for i in range(data_length):
            feature = datasets[i][axis]
            if feature not in feature_sets:
                feature_sets[feature] = []
            feature_sets[feature].append(datasets[i])
        cond_ent = sum([(len(p)/data_length)*self.calc_ent(p) for p in feature_sets.values()])
        return cond_ent

    # 信息增益
    # @staticmethod 是 Python 中的一个装饰器,用于将一个方法声明为静态方法。提高代码的可读性
    # 静态方法不需要依赖类的实例,可以直接通过类名调用,并且在方法内部无法访问类的实例属性和方法
   # 调用方法 DTree.calc_ent(datasets)  通过类名DTree直接调用静态方法calc_ent来计算数据集的熵
    @staticmethod
    def info_gain(ent, cond_ent):
        return ent - cond_ent

    def info_gain_train(self, datasets):
        count = len(datasets[0]) - 1
        ent = self.calc_ent(datasets)
        best_feature = []
        for c in range(count):
            c_info_gain = self.info_gain(ent, self.cond_ent(datasets, axis=c))
            best_feature.append((c, c_info_gain))
        # 比较大小
        best_ = max(best_feature, key=lambda x: x[-1])
        return best_
    def train(self, train_data):

        """
        input:数据集D(DataFrame格式),特征集A,阈值eta
        output:决策树T
        """
        # _ 代表训练数据集的所有样本及特征(除了最后一列样本目标标签)
        # y_train 代表训练样本目标标签,选择训练数据集的所有行(:),并且选择最后一列目标标签
        # features 代表训练数据集的所有列名(特征名),形成包含训练特征名的列表
        _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
        
        # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
        # .value_counts()是一个pandas Series对象的方法,用于计算每个唯一值的频率
        # len()函数用于获取这个频率计数结果的长度,也就是唯一值的数量
        # len(y_train.value_counts()) == 1,表示训练数据集 y_train 中只有一个唯一的类别
        if len(y_train.value_counts()) == 1:
            return Node(root=True,
                        label=y_train.iloc[0])

        # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
        # 创建并返回一个Node对象,该对象表示一个决策树的结点
        # y_train.value_counts()计算了每个唯一值的频率,并返回一个包含唯一值和对应频率的pandas 
        # Series对象。
        # .sort_values(ascending=False)将频率按降序排列,以便找到最常见的类别
        # 最后,.index[0]提取出排名第一的类别的索引值
        # 整个返回语句的意思是:创建一个根节点,其标签为训练数据集中最常见的类别
        if len(features) == 0:
            return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])

        # 3,计算最大信息增益 同5.1,Ag为信息增益最大的特征
        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
        # max_feature表示的是最大信息增益特征的位置 不是最大信息增益的名称
      # 使用max_feature作为索引,从features列表中获取相应的特征名称,并赋值给max_feature_name
        max_feature_name = features[max_feature]

        # 4,Ag的信息增益小于阈值epsilon,则置T为单节点树,并将D中是实例数最大的类Ck作为该节点的类标记,返回T
        if max_info_gain < self.epsilon:
            return Node(root=True, label=y_train.value_counts().sort_values(ascending=False).index[0])


   对𝐴𝑔的每一个可能值𝑎𝑖,依 𝐴𝑔=𝑎𝑖将 𝐷分割为若干非空子集 𝐷𝑖,将 𝐷𝑖中实例数对大的类作为标记,构建子结点,由结点及其子结点构成树 𝑇,返回 𝑇


        # 5,构建Ag子集

        node_tree = Node(root=False, feature_name=max_feature_name, feature=max_feature)

        # 看图3解释
        feature_list = train_data[max_feature_name].value_counts().index
        # 看图4 图5 解释  axis=1 表示对列进行操作
        # sub_train_df为构建子树的训练数据集
        for f in feature_list:
            sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)

            # 6, 对 node_tree 递归生成树 
            # 看图6 解释
            sub_tree = self.train(sub_train_df)
            # f 父结点 sub_tree 子结点 递归生成决策树
            node_tree.add_node(f, sub_tree)
        #最后输出决策树
        return node_tree
    # 看图7 解释 用于训练决策树模型并返回生成的决策树 _tree 并可以使用该模型进行预测或其他操作
    def fit(self, train_data):
        self._tree = self.train(train_data)
        return self._tree
    # 看图8解释 predict是一个递归函数(自己中包含自己)
    # 所以可以将测试样本沿着决策树从根节点开始依次判断
    def predict(self, X_test):
        return self._tree.predict(X_test)
图 1
图2
图3
图4
图5
图6
图7
图8
# 根据训练数据集create_data 进行决策树模型训练
datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
# 决策树模型训练 生成的模型赋值给tree
tree = dt.fit(data_df)
决策树模型和测试结果

C4.5算法

  • C4.5算法与ID3算法相似,C4.5算法对ID3算法进行了改进,C4.5算法在生成的过程中,用信息增益比来选择特征

 最小二乘回归树的生成算法

  • 用平方误差\sum_{x\varepsilon R_{m}}^{}\left (( y_{i}-f\left ( x_{i} \right ) \right )^{2}表示回归树对于训练数据的预测误差

 遍历所有输入变量,找到最优的切分变量 j ,构成一对(j,s)依此将输入空间划分成两个区域,生成一颗回归树

分类树的生成

CART生成算法

  • 给定输入随机变量X条件下输出变量Y的条件概率分布的学习方法

CART生成算法的相关例题 
训练数据表

 决策树的剪枝

  • 决策树剪枝:通过极小化决策树整体的损失函数或代价函数来实现,为了减小模型复杂度(过拟合)
  • 过拟合的原因:学习时过多地考虑如何提高训练数据的正确分类,从而构建出过于复杂的决策树

\alpha\geqslant0 控制模型与训练数据的拟合程度和模型复杂度两者之间的影响

  • 较大的\alpha促使选择较简单的模型
  • 较小的\alpha促使选择较复杂的模型
  • \alpha=0只考虑模型与训练数据的拟合程度,不考虑模型的复杂程度

 当\alpha确定时

  • 子树越大,与训练数据的拟合越好,模型复杂度越高
  • 子树越小,与训练数据的拟合不好,模型复杂度越低

CART剪枝算法

 

sklearn.tree.DecisionTreeClassifier 代码分析

  • criterion(准则): string (字符串), optional (default=”gini”) (默认为:基尼)
  • The function to measure the quality of a split. Supported criteria are “gini” for the Gini impurity and “entropy” for the information gain.(用于衡量分割质量的函数。支持的标准有:"基尼 "表示基尼不纯度,"熵 "表示信息增益)
# data
def create_data():
    iris = load_iris()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['label'] = iris.target
    df.columns = ['sepal length', 'sepal width', 'petal length', 'petal width', 'label']
    # 前100行的(第一列(特征)、第二列(特征) 和倒数第一列(目标标签列)
    data = np.array(df.iloc[:100, [0, 1, -1]])
    # print(data)
    return data[:,:2], data[:,-1]
# X代表各个样本的特征数据 y代表各个样本的目标标签
X, y = create_data()

# 将数据集分成70%为训练数据 30%为测试数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
data 部分(有100行)
X 样本特征数据(部分)(有100行)
y样本的目标标签
from sklearn.tree import DecisionTreeClassifier

from sklearn.tree import export_graphviz
import graphviz

# 调用sklearn 中的决策树算法,并利用训练数据进行决策树的训练
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train,)

tree_pic = export_graphviz(clf, out_file="mytree.pdf")
with open('mytree.pdf') as f:
    dot_graph = f.read()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

工大凯K

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

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

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

打赏作者

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

抵扣说明:

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

余额充值