决策树DecisionTree

参考李航《统计学习方法》

 1.信息论

信息论的基本想法是一个不太可能的事件居然发生了,要比一个非常可能的事 件发生,能提供更多的信息。消息说:‘‘今天早上太阳升起’’ 信息量是如此之少以至 于没有必要发送,但一条消息说:‘‘今天早上有日食’’ 信息量就很丰富。

我们想要通过这种基本想法来量化信息。特别地,

• 非常可能发生的事件信息量要比较少,并且极端情况下,确保能够发生的事件 应该没有信息量

• 较不可能发生的事件具有更高的信息量。

• 独立事件应具有增量的信息。

例如,投掷的硬币两次正面朝上传递的信息量, 应该是投掷一次硬币正面朝上的信息量的两倍。 为了满足上述三个性质,我们定义一个事件 x = x 的 自信息(self-information) 为

I(x) = − log P(x)

自信息只处理单个的输出。我们可以用 香农熵(Shannon entropy)来对整个概 率分布中的不确定性总量进行量化:

H(x) = Ex∼p [I(x)] = −Ex∼p [log P(x)]

也记作 H(P)。 

如果我们对于同一个随机变量 x 有两个单独的概率分布 P(x) Q(x),我们可 以使用 KL 散度(Kullback-Leibler (KL) divergence)来衡量这两个分布的差异:

DKL(P||Q) = Ex∼p [ log P(x) Q(x) ] = Ex∼p [log P(x) − log Q(x)]. 

一个和 KL 散度密切联系的量是 交叉熵(cross-entropy)H(P, Q) = H(P) + DKL(P||Q),它和 KL 散度很像但是缺少左边一项:

H(P, Q) = −Ex∼p log Q(x) 

互信息(Mutual Information)是信息论里一种有用的信息度量,它可以看成是一个随机变量中包含的关于另一个随机变量的信息量,或者说是一个随机变量由于已知另一个随机变量而减少的不确定性 ,记为I(P, Q)

 I(P, Q)=H(P) H(P|Q)

或 I(P, Q)=H(P)+H(Q) H(PQ) 

也记作g(P,Q)。

2.特征选择准则

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

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

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

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

4.决策树的生成

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

5.决策树的剪枝

由于生成的决策树存在过拟合问题,需要对它进行剪枝,以简化学到的决策树。决策树的剪枝,往往从已生成的树上剪掉一些叶结点或叶结点以上的子树,并将其父结点或根结点作为新的叶结点,从而简化生成的决策树。

 6.示例

(1)简单地,使用sklearn实现,样本选自鸢尾花

from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz

# 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'
    ]
    data = np.array(df.iloc[:100, [0, 1, -1]])
    # print(data)
    return data[:, :2], data[:, -1]


if __name__ == '__main__':
    #get data
    X, y = create_data()
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
    
    #classify
    clf = DecisionTreeClassifier()
    clf.fit(X_train, y_train,)
    clf.score(X_test, y_test)

(2)具体地,GITHUB链接lihang-code/第05章 决策树 at master · yaoyansum/lihang-code (github.com)

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):
         # 决策树初始化,带有可选的epsilon参数
        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(DataFrame格式),特征集A,阈值eta
        output:决策树T
        """
        _, 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)

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

一级piaopiao虎

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

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

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

打赏作者

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

抵扣说明:

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

余额充值