决策树原理及numpy实现版

2021-12-15 08:40:29 浏览数 (1)

文章目录
  • 决策树原理
    • 学习过程
      • 特征选择
      • 信息增益
      • 信息增益比
    • ID3决策树的生成
      • ID3伪代码
    • C4.5的生成算法
  • numpy实现

决策树原理

决策树是一种基本的分类与回归方法.决策树模型呈树形结构.分类决策树模型是一种描述对实例进行分类的树形结构. 决策树由结点(node)与有向边(directed edge)组成.结点分为年内不结点与叶结点组成. 内部结点表示一个特征或属性,叶表示一个类. 决策树学习本质上是从训练数据集中归纳出一组分类规则。 决策树学习的损失函数通常是正则 化的极大似然函数。决策树学习的策略是以损失函数为目标函数的最小化。

学习过程

特征选择/决策树生成/决策树剪枝

特征选择

依据信息增益或信息增益比 熵是表示随机变量不确定性的度量 随机变量熵的定义为:

熵越大,随机变量的不确定性越大. 条件熵:定义为X给定条件下Y的条件概率分布的熵的数学期望

信息增益

信息增益:特征A对训练数据集D的信息增益g(D,A),定义为集合D的经验熵H(D)与特征A给定条件下D的经验条件熵H(D|A)之差

熵H(D)与H(D|A)之差为互信息,决策树的信息增益等价于训练集数据集中类与特征的互信息. 信息增益,就表示由于特征A而使得对数 据集D的分类的不确定性减少的程度。信息增益越大具备更强的分类能力.

信息增益比

由于信息增益的大小容易受到数据集大小的影响,也没有绝对的意义.因此在分类的时候,通常以信息增益比作为分类标准.

ID3决策树的生成

ID3算法的核心是决策树各个结点上信息增益规则选择特征,递归地构建决策树.具体方法是:从根结点(root node)开始,对结点计算所有可能的特征的信息增益, 选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;再对子结点 递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。

ID3伪代码

输入:训练数据集D,特征集A,阈值 ξ;

输出:决策树

(1)若D中所有实例属于同一类 C k 则T为单结点树,并将类 C k ​作为该结点的类标 记,返回T; (2)若A=Ø,则T为单结点树,并将D中实例数最大的类 C k 作为该结点的类标记, 返回T; (3)否则,计算A中各特征对D的信息增益,选择信息增益最大的特征 Ag ; (4)如果Ag 的信息增益小于阈值ξ ,则置T为单结点树,并将D中实例数最大的类C k ​作为该结点的类标记,返回T; (5)否则,对Ag 的每一可能值 a i ​,依Ag = a i i​将D分割为若干非空子集D i ​,将D i 中实例 数最大的类作为标记,构建子结点,由结点及其子结点构成树T,返回T; (6)对第i个子结点,以 D i​为训练集,以A-{Ag }为特征集,递归地调用步(1)~步(5),得到子树T i 返回 T i

C4.5的生成算法

C4.5算法与ID3算法相似,C4.5算法对ID3算法进行改进.采用信息增益比俩选择特征. 决策树的剪枝往往通过极小化决策树整体的损失函数或代价函数来实现。

numpy实现

代码语言:javascript复制
# -*- coding:utf-8 -*-
# /usr/bin/python

import numpy as np
import pandas as pd
from math import log

def create_data():
    datasets = [['青年', '否', '否', '一般', '否'],
               ['青年', '否', '否', '好', '否'],
               ['青年', '是', '否', '好', '是'],
               ['青年', '是', '是', '一般', '是'],
               ['青年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '一般', '否'],
               ['中年', '否', '否', '好', '否'],
               ['中年', '是', '是', '好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['中年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '非常好', '是'],
               ['老年', '否', '是', '好', '是'],
               ['老年', '是', '否', '好', '是'],
               ['老年', '是', '否', '非常好', '是'],
               ['老年', '否', '否', '一般', '否'],
               ]
    labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'类别']
    # 返回数据集和每个维度的名称
    return datasets, labels

datasets, labels = create_data()
train_data = pd.DataFrame(datasets, columns=labels)
print(train_data)

class Node():

    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 = {}
        self.result = {'label:': self.label, 'feature': self.feature, 'tree': self.tree}

    def __repr__(self):
        return '{}'.format(self.result)

    def add_node(self, val, node):
        self.tree[val] = node

    def predict(self, features):
        if self.root is True:
            return self.label
        return self.tree[features[self.feature]].predict(features)

class DTree:
    def __init__(self,epsilon=0.1):
        self.epsilon = epsilon
        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
    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,特征集A,阈值eta
        output: 决策树T
        :return:
        '''
        _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]
        # 1,若D中实例属于同一类Ck,则T为单节点树,并将类Ck作为结点的类标记,返回T
        if len(y_train.value_counts()) == 1:
            return Node(root=True,label=y_train.iloc[0])

        # 2, 若A为空,则T为单节点树,将D中实例树最大的类Ck作为该节点的类标记,返回T
        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_name = features[max_feature]

        # 4,Ag的信息增益小于阈值eta,则置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)

        feature_list = train_data[max_feature_name].value_counts().index
        for f in feature_list:
            sub_train_df = train_data.loc[train_data[max_feature_name] == f].drop([max_feature_name], axis=1)

            # 6, 递归生成树
            sub_tree = self.train(sub_train_df)
            node_tree.add_node(f, sub_tree)

        # pprint.pprint(node_tree.tree)
        return node_tree

    def fit(self, train_data):
        self._tree = self.train(train_data)
        return self._tree

    def predict(self,X_test):
        return self._tree.predict(X_test)

datasets, labels = create_data()
data_df = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(data_df)
result = dt.predict(['老年', '否', '否', '一般'])
print(result)

0 人点赞