当前位置:   article > 正文

决策树(Decision Tree,DT)_rules are derived from decision trees decision-t

rules are derived from decision trees decision-tree (dt) method (决策树

决策树(decision tree)是一种基本的分类与回归方法。

  • 分类问题中,基于特征对实例进行分类的过程。
  • 优点:模型具有可读性,分类速度快。
  • 学习:利用训练数据,根据损失函数最小化的原则建立决策树模型。
  • 预测:对新的数据,利用决策树模型进行分类。

决策树学习通常包括3个步骤:特征选择决策树生成决策树修剪

Quinlan在1986年提出的ID3算法、1993年提出的C4.5算法
Breiman等人在1984年提出的CART算法

1. 决策树模型与学习

决策树由结点(node)和有向边(directed edge)组成。

  • 内部结点(internal node)和叶结点(leaf node)。
  • 内部结点表示一个特征或属性,结点表示一个
  • 用决策树分类,从根结点开始,对实例的某一特征进行测试,根据测试结果,将实例分配到其子结点;这时,每一个子结点对应着该特征的一个取值。
  • 递归地对实例进行测试并分配,直至达到叶结点。最后将实例分到叶结点的类中。
    在这里插入图片描述

决策树学习本质:从训练数据集中归纳出一组分类规则。

  • 需要一个与训练数据矛盾较小的决策树,同时具有很好的泛化能力。
  • 决策树学习的损失函数:通常是正则化的极大似然函数。
  • 决策树学习的策略:损失函数为目标函数的最小化。

2. 特征选择

决策树训练时高度太高,对训练数据准确率高,但泛化能力差,需要剪枝。

常用的准则

(1)样本集合 D D D对特征 A A A信息增益(ID3)

g ( D , A ) = H ( D ) − H ( D ∣ A ) g(D, A)=H(D)-H(D|A) g(D,A)=H(D)H(DA)

H ( D ) = − ∑ k = 1 K ∣ C k ∣ ∣ D ∣ log ⁡ 2 ∣ C k ∣ ∣ D ∣ H(D)=-\sum_{k=1}^{K} \frac{\left|C_{k}\right|}{|D|} \log _{2} \frac{\left|C_{k}\right|}{|D|} H(D)=k=1KDCklog2DCk

H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) H(D | A)=\sum_{i=1}^{n} \frac{\left|D_{i}\right|}{|D|} H\left(D_{i}\right) H(DA)=i=1nDDiH(Di)

其中, H ( D ) H(D) H(D)是数据集 D D D的熵, H ( D i ) H(D_i) H(Di)是数据集 D i D_i Di的熵, H ( D ∣ A ) H(D|A) H(DA)是数据集 D D D对特征 A A A的条件熵。 D i D_i Di D D D中特征 A A A取第 i i i个值的样本子集, C k C_k Ck D D D中属于第 k k k类的样本子集。 n n n是特征 A A A取值的个数, K K K是类的个数。

  • 熵越大,随机变量的不确定性就越大
  • 信息增益(information gain)表示得知特征X的信息而使得类Y的信息的不确定性减少的程度。
  • 选择信息增益 大的

(2)样本集合 D D D对特征 A A A信息增益比(C4.5)

g R ( D , A ) = g ( D , A ) H A ( D ) g_{R}(D, A)=\frac{g(D, A)}{H_A(D)} gR(D,A)=HA(D)g(D,A)

其中, g ( D , A ) g(D,A) g(D,A)是信息增益, H A ( D ) H_A(D) HA(D)是数据集 D D D关于特征 A A A的熵。

(3)样本集合 D D D 基尼指数(CART)

Gini ⁡ ( D ) = 1 − ∑ k = 1 K ( ∣ C k ∣ ∣ D ∣ ) 2 \operatorname{Gini}(D)=1-\sum_{k=1}^{K}\left(\frac{\left|C_{k}\right|}{|D|}\right)^{2} Gini(D)=1k=1K(DCk)2

特征 A A A条件下集合 D D D的基尼指数:

Gini ⁡ ( D , A ) = ∣ D 1 ∣ ∣ D ∣ Gini ⁡ ( D 1 ) + ∣ D 2 ∣ ∣ D ∣ Gini ⁡ ( D 2 ) \operatorname{Gini}(D, A)=\frac{\left|D_{1}\right|}{|D|} \operatorname{Gini}\left(D_{1}\right)+\frac{\left|D_{2}\right|}{|D|} \operatorname{Gini}\left(D_{2}\right) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)

  • 基尼指数表示集合D的不确定性,基尼指数 G i n i ( D , A ) Gini(D,A) Gini(D,A) 表示经 A = a A=a A=a分割后集合 D D D的不确定性。
  • 基尼指数值越大,样本集合的不确定性也就越大,这一点与熵相似。
  • 选择 基尼指数 小的

2.1 特征选择Python代码

def get_data():
    datasets = [['青年', '否', '否', '一般', '否'],
                ['青年', '否', '否', '好', '否'],
                ['青年', '是', '否', '好', '是'],
                ['青年', '是', '是', '一般', '是'],
                ['青年', '否', '否', '一般', '否'],
                ['中年', '否', '否', '一般', '否'],
                ['中年', '否', '否', '好', '否'],
                ['中年', '是', '是', '好', '是'],
                ['中年', '否', '是', '非常好', '是'],
                ['中年', '否', '是', '非常好', '是'],
                ['老年', '否', '是', '非常好', '是'],
                ['老年', '否', '是', '好', '是'],
                ['老年', '是', '否', '好', '是'],
                ['老年', '是', '否', '非常好', '是'],
                ['老年', '否', '否', '一般', '否'],
                ]
    labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'分类']
    # 字符串前加 u, 后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止乱码
    return datasets, labels;
# ---------书上贷款例子-----------------
datasets, labels = get_data()

def cal_entropy(datasets):  # 经验熵H(D)
    data_len = len(datasets)
    label_count = {}
    for i in range(data_len):
        label = datasets[i][-1]
        if label not in label_count:
            label_count[label] = 0
        label_count[label] += 1
    entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
    return entropy


def cond_entropy(datasets, axis=0):  # 经验条件熵H(D|A)
    data_len = len(datasets)
    feature_set = {}
    for i in range(data_len):
        feature = datasets[i][axis]
        if feature not in feature_set:
            feature_set[feature] = []
        feature_set[feature].append(datasets[i])
    cond_ent = sum([(len(p) / data_len) * cal_entropy(p) for p in feature_set.values()])
    return cond_ent


def info_gain(entropy, cond_ent):  # 信息增益
    return entropy - cond_ent


def info_gain_train(datasets):	# 基于特征信息增益的特征选择
    count = len(datasets[0]) - 1
    entropy = cal_entropy(datasets)
    best_feature = []
    for i in range(count):
        info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
        best_feature.append((i, info_gain_i))
        print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
    best_feature_i = max(best_feature, key=lambda x: x[-1])
    print("特征({})的信息增益最大,选为根节点的特征".format(labels[best_feature_i[0]]))

info_gain_train(np.array(datasets))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
特征(年龄)- info_gain - 0.083
特征(有工作)- info_gain - 0.324
特征(有自己的房子)- info_gain - 0.420
特征(信贷情况)- info_gain - 0.363
特征(有自己的房子)的信息增益最大,选为根节点的特征
  • 1
  • 2
  • 3
  • 4
  • 5

3. 决策树的生成

通常使用信息增益最大、信息增益比最大或基尼指数最小作为特征选择的准则。

决策树的生成往往通过计算信息增益或其他指标,从根结点开始,递归地产生决策树。
这相当于用信息增益或其他准则不断地选取局部最优的特征,或将训练集分割为能够基本正确分类的子集。

  • ID3算法只有树的生成,所以该算法生成的树容易产生过拟合
  • C4.5算法与ID3算法相似,进行了改进。C4.5在生成的过程中,用信息增益比来选择特征。

3.1 Python代码

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):  # 类似str方法,更侧重程序员调试
        print('{}'.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):  # 信息增益阈值, < epsilon 时,结束决策树展开
        self.epsilon = epsilon
        self._tree = {}

    @staticmethod
    def cal_entropy(datasets):  # 经验熵H(D)
        data_len = len(datasets)
        label_count = {}
        for i in range(data_len):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
        return entropy

    def cond_entropy(self, datasets, axis=0):  # 经验条件熵H(D|A)
        data_len = len(datasets)
        feature_set = {}
        for i in range(data_len):
            feature = datasets[i][axis]
            if feature not in feature_set:
                feature_set[feature] = []
            feature_set[feature].append(datasets[i])
        cond_ent = sum([(len(p) / data_len) * self.cal_entropy(p) for p in feature_set.values()])
        return cond_ent

    @staticmethod
    def info_gain(entropy, cond_ent):  # 信息增益
        return entropy - cond_ent

    def info_gain_train(self, datasets):  # 基于特征信息增益的特征选择
        count = len(datasets[0]) - 1
        entropy = self.cal_entropy(datasets)
        best_feature = []
        for i in range(count):
            info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
            best_feature.append((i, info_gain_i))
            print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
        best_feature_i = max(best_feature, key=lambda x: x[-1])
        return best_feature_i

    def train(self, train_data):
        '''
        :input: 数据集D(DataFrame格式),特征集A,阈值eta
        :return: 决策树DT
        '''
        _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]

        # 1. 若所有D实例都属于同一分类,不用分了,直接返回那个类
        if len(y_train.value_counts()) == 1:
            return Node(root=True, label=y_train.iloc[0])
        # 2. 若没有特征A,返回D中数量最多的分类
        if len(features) == 0:
            return Node(root=True, label=y_train.value_counts().sort_values(
                ascending=False).index[0])
        # 3. 计算最大信息增益,取为特征
        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
        max_feature_name = features[max_feature]

        # 4. 如果信息增益小于阈值epsilon,置为单节点,将实例数最大的类作为节点标记
        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)
        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)


train_data = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(train_data)
print(dt.predict(['老年', '否', '否', '一般']))
print(dt.predict(['青年', '否', '是', '一般']))
print(dt.predict(['中年', '是', '否', '好']))
print(dt.predict(['老年', '否', '是', '一般']))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117

4. 决策树的剪枝

学习时,过多考虑准确性,树复杂,过拟合,泛化能力差,需要剪枝。

方法:极小化决策树整体损失函数

5. CART 算法

分类与回归树(classification and regression tree,CART)模型

  • 二叉树
  • 左分支,是;右分支,否
  • (1)决策树生成:基于训练数据集生成决策树,生成的决策树要尽量大;
    (2)决策树剪枝:用验证数据集对已生成的树进行剪枝并选择最优子树,这时用损失函数最小作为剪枝的标准。

6. sklearn 例子

sklearn.tree.DecisionTreeClassifier

class sklearn.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, min_impurity_split=None,
  class_weight=None, presort='deprecated', ccp_alpha=0.0)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 特征选择标准:
criterion{“gini”, “entropy”}, default=”gini”
  • 1
  • 择优划分、树的最大深度、最小划分几类、叶子节点个数等参数

6.1 书上贷款例子

# ---------书上贷款例子-----------------
datasets, labels = get_data()
train_data = np.array(pd.DataFrame(datasets, columns=labels))
X_train, y_train = train_data[:, :-1], train_data[:, -1:]
encoder = preprocessing.OrdinalEncoder()    # 将字符转成浮点
encoder.fit(X_train)    # 先拟合
X_train = encoder.transform(X_train)    # 转换成数字
A = encoder.transform([['青年', '否', '是', '一般']])
B = encoder.transform([['中年', '是', '否', '好']])
C = encoder.transform([['老年', '否', '是', '一般']])

encoder = preprocessing.OrdinalEncoder()
encoder.fit(y_train)
y_train = encoder.transform(y_train)
# sklearn 决策树
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
print(encoder.inverse_transform([clf.predict(A)]))
print(clf.predict_proba(B))
print(clf.predict_proba(C))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
[['是']]
[[0. 1.]]
[[0. 1.]]
  • 1
  • 2
  • 3

6.2 鸢尾花 及 决策树可视化

# ------------鸢尾花---------------
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']
data = np.array(df.iloc[:100, [0, 1, -1]])
X = data[:, :2]
y = data[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
clf = DecisionTreeClassifier()
print(clf)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))

# --------------决策树可视化-------------
# 需要安装graphviz,添加path,可视化决策树
with open('mytree.dot', 'w', encoding='utf-8') as f:
    dot_data = export_graphviz(clf, out_file=None, feature_names=df.columns[:2],
                               filled=True, rounded=True, special_characters=True, 
                               class_names=iris.target_names[0:2])
dot = graphviz.Source(dot_data)
dot.view()
# 写入png , pdf
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_png('tree.png')
# cmd: dot -Tpdf tree.dot -o output.pdf,dot -Tpng tree.dot -o output.png
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

决策树可视化
在这里插入图片描述

附. 本文完整代码

# -*- coding:utf-8 -*-
# @Python Version: 3.7
# @Time: 2020/3/13 19:36
# @Author: Michael Ming
# @Website: https://michael.blog.csdn.net/
# @File: 5.decisionTree.py
# @Reference: https://github.com/fengdu78/lihang-code

import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.model_selection import train_test_split
from collections import Counter
import math
from math import log
import pprint
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz
import graphviz
import pydotplus


def get_data():
    datasets = [['青年', '否', '否', '一般', '否'],
                ['青年', '否', '否', '好', '否'],
                ['青年', '是', '否', '好', '是'],
                ['青年', '是', '是', '一般', '是'],
                ['青年', '否', '否', '一般', '否'],
                ['中年', '否', '否', '一般', '否'],
                ['中年', '否', '否', '好', '否'],
                ['中年', '是', '是', '好', '是'],
                ['中年', '否', '是', '非常好', '是'],
                ['中年', '否', '是', '非常好', '是'],
                ['老年', '否', '是', '非常好', '是'],
                ['老年', '否', '是', '好', '是'],
                ['老年', '是', '否', '好', '是'],
                ['老年', '是', '否', '非常好', '是'],
                ['老年', '否', '否', '一般', '否'],
                ]
    labels = [u'年龄', u'有工作', u'有自己的房子', u'信贷情况', u'分类']
    # 字符串前加 u, 后面字符串以 Unicode 格式 进行编码,一般用在中文字符串前面,防止乱码
    return datasets, labels;


# ---------书上贷款例子-----------------
datasets, labels = get_data()
train_data = np.array(pd.DataFrame(datasets, columns=labels))
X_train, y_train = train_data[:, :-1], train_data[:, -1:]
encoder = preprocessing.OrdinalEncoder()  # 将字符转成浮点
encoder.fit(X_train)  # 先拟合
X_train = encoder.transform(X_train)  # 转换成数字
A = encoder.transform([['青年', '否', '是', '一般']])
B = encoder.transform([['中年', '是', '否', '好']])
C = encoder.transform([['老年', '否', '是', '一般']])

encoder = preprocessing.OrdinalEncoder()
encoder.fit(y_train)
y_train = encoder.transform(y_train)
# sklearn 决策树
clf = DecisionTreeClassifier()
clf.fit(X_train, y_train)
print(encoder.inverse_transform([clf.predict(A)]))
print(clf.predict_proba(B))
print(clf.predict_proba(C))

# --------------决策树可视化-------------
# 需要安装graphviz,添加path,可视化决策树
with open('mytree.dot', 'w', encoding='utf-8') as f:
    dot_data = export_graphviz(clf, out_file=None, feature_names=clf.feature_importances_,
                               filled=True, rounded=True, special_characters=True)
dot = graphviz.Source(dot_data)
# dot.view()
# 写入png , pdf
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_png('tree.png')


# cmd: dot -Tpdf tree.dot -o output.pdf,dot -Tpng tree.dot -o output.png

# -----------自编程,抄一遍---------------
# ----特征选择,基于信息增益----
def cal_entropy(datasets):  # 经验熵H(D)
    data_len = len(datasets)
    label_count = {}
    for i in range(data_len):
        label = datasets[i][-1]
        if label not in label_count:
            label_count[label] = 0
        label_count[label] += 1
    entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
    return entropy


def cond_entropy(datasets, axis=0):  # 经验条件熵H(D|A)
    data_len = len(datasets)
    feature_set = {}
    for i in range(data_len):
        feature = datasets[i][axis]
        if feature not in feature_set:
            feature_set[feature] = []
        feature_set[feature].append(datasets[i])
    cond_ent = sum([(len(p) / data_len) * cal_entropy(p) for p in feature_set.values()])
    return cond_ent


def info_gain(entropy, cond_ent):  # 信息增益
    return entropy - cond_ent


def info_gain_train(datasets):  # 基于特征信息增益的特征选择
    count = len(datasets[0]) - 1
    entropy = cal_entropy(datasets)
    best_feature = []
    for i in range(count):
        info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
        best_feature.append((i, info_gain_i))
        print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
    best_feature_i = max(best_feature, key=lambda x: x[-1])
    print("特征({})的信息增益最大,选为根节点的特征".format(labels[best_feature_i[0]]))


info_gain_train(np.array(datasets))


# -------ID3算法生成决策树---------

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):  # 类似str方法,更侧重程序员调试
        print('{}'.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):  # 信息增益阈值, < epsilon 时,结束决策树展开
        self.epsilon = epsilon
        self._tree = {}

    @staticmethod
    def cal_entropy(datasets):  # 经验熵H(D)
        data_len = len(datasets)
        label_count = {}
        for i in range(data_len):
            label = datasets[i][-1]
            if label not in label_count:
                label_count[label] = 0
            label_count[label] += 1
        entropy = -sum([(p / data_len) * log(p / data_len, 2) for p in label_count.values()])
        return entropy

    def cond_entropy(self, datasets, axis=0):  # 经验条件熵H(D|A)
        data_len = len(datasets)
        feature_set = {}
        for i in range(data_len):
            feature = datasets[i][axis]
            if feature not in feature_set:
                feature_set[feature] = []
            feature_set[feature].append(datasets[i])
        cond_ent = sum([(len(p) / data_len) * self.cal_entropy(p) for p in feature_set.values()])
        return cond_ent

    @staticmethod
    def info_gain(entropy, cond_ent):  # 信息增益
        return entropy - cond_ent

    def info_gain_train(self, datasets):  # 基于特征信息增益的特征选择
        count = len(datasets[0]) - 1
        entropy = self.cal_entropy(datasets)
        best_feature = []
        for i in range(count):
            info_gain_i = info_gain(entropy, cond_entropy(datasets, axis=i))
            best_feature.append((i, info_gain_i))
            print("特征({})- info_gain - {:.3f}".format(labels[i], info_gain_i))
        best_feature_i = max(best_feature, key=lambda x: x[-1])
        return best_feature_i

    def train(self, train_data):
        '''
        :input: 数据集D(DataFrame格式),特征集A,阈值eta
        :return: 决策树DT
        '''
        _, y_train, features = train_data.iloc[:, :-1], train_data.iloc[:, -1], train_data.columns[:-1]

        # 1. 若所有D实例都属于同一分类,不用分了,直接返回那个类
        if len(y_train.value_counts()) == 1:
            return Node(root=True, label=y_train.iloc[0])
        # 2. 若没有特征A,返回D中数量最多的分类
        if len(features) == 0:
            return Node(root=True, label=y_train.value_counts().sort_values(
                ascending=False).index[0])
        # 3. 计算最大信息增益,取为特征
        max_feature, max_info_gain = self.info_gain_train(np.array(train_data))
        max_feature_name = features[max_feature]

        # 4. 如果信息增益小于阈值epsilon,置为单节点,将实例数最大的类作为节点标记
        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)
        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)


train_data = pd.DataFrame(datasets, columns=labels)
dt = DTree()
tree = dt.fit(train_data)
print(dt.predict(['老年', '否', '否', '一般']))
print(dt.predict(['青年', '否', '是', '一般']))
print(dt.predict(['中年', '是', '否', '好']))
print(dt.predict(['老年', '否', '是', '一般']))

# ------------鸢尾花---------------
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']
data = np.array(df.iloc[:100, [0, 1, -1]])
X = data[:, :2]
y = data[:, -1]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
clf = DecisionTreeClassifier()
print(clf)
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))
# --------------决策树可视化-------------
# 需要安装graphviz,添加path,可视化决策树
with open('mytree.dot', 'w', encoding='utf-8') as f:
    dot_data = export_graphviz(clf, out_file=None, feature_names=df.columns[:2],
                               filled=True, rounded=True, special_characters=True, 
                               class_names=iris.target_names[0:2])
dot = graphviz.Source(dot_data)
dot.view()
# 写入png , pdf
graph = pydotplus.graph_from_dot_data(dot_data)
graph.write_png('tree.png')
# cmd: dot -Tpdf tree.dot -o output.pdf,dot -Tpng tree.dot -o output.png
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/847492
推荐阅读
相关标签
  

闽ICP备14008679号