Bootstrap

从零逐行实现决策树算法

决策树先验知识

  • 熵表示随机变量的不确定性,熵越大,随机变量的不确定性越大,数学表达式为:

H ( p ) = − ∑ i = 1 n p i l o g ( p i ) H(p) = -\sum^n_{i=1}p_i log (p_i) H(p)=i=1npilog(pi)

  • 假设 n = 2 n=2 n=2,则 H ( p ) H(p) H(p)变为:

H ( p ) = − p l o g ( p ) − ( 1 − p ) l o g ( 1 − p ) H(p) = -plog(p) - (1-p)log(1-p) H(p)=plog(p)(1p)log(1p)

  • 在机器学习中,为了表达的统一性,将熵的数学公式变为:

H ( D ) = − ∑ k = 1 k ∣ C k ∣ ∣ D ∣ l o g 2 ∣ C k ∣ ∣ D ∣ H(D) = -\sum_{k=1}^{k}\frac{|C_k|}{|D|}log_2\frac{|C_k|}{|D|} H(D)=k=1kDCklog2DCk

其中, C k C_k Ck表示列 C C C(标签列)的第 k k k个取值, ∣ C k ∣ |C_k| Ck表示列 C C C的第 k k k个取值的样本数,|D|则表示全部样本数量。

  • 假设要计算下面这个数据集的熵:
年龄有工作有房子信贷情况类别
青年一般
青年
青年
青年一般
青年一般
中年一般
中年
中年
中年非常好
中年非常好
老年非常好
老年
老年
老年非常好
老年一般
  • 可以看到,类别列就是标签列,一共有2种取值,假设 C 1 = 是 C_1 = \text{是} C1= C 2 = 否 C_2 = \text{否} C2=,则 H ( D ) H(D) H(D)为:

H ( D ) = − 9 15 l o g 2 ( 9 15 ) − 6 15 l o g 2 ( 6 15 ) H(D) = -\frac{9}{15}log_2(\frac{9}{15}) - \frac{6}{15}log_2(\frac{6}{15}) H(D)=159log2(159)156log2(156)

条件熵

  • 条件熵数学表达式:

H ( Y ∣ X ) = ∑ i = 1 n p i H ( Y ∣ X = x i ) H(Y|X) = \sum_{i=1}^np_iH(Y|X=x_i) H(YX)=i=1npiH(YX=xi)

其中:
p i = P ( X = x i ) p_i = P(X=x_i) pi=P(X=xi)

  • 变为机器学习中的通用表达式:

H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 k ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ H(D|A) = \sum^n_{i=1}\frac{|D_i|}{|D|}H(D_i) = -\sum_{i=1}^{n}\frac{|D_i|}{|D|}\sum_{k=1}^k\frac{|D_{ik}|}{|D_i|}log_2\frac{|D_{ik}|}{|D_i|} H(DA)=i=1nDDiH(Di)=i=1nDDik=1kDiDiklog2DiDik

其中, n n n表示特征 A A A可能取值的个数, ∣ D i ∣ |D_i| Di则表示特征A取第 i i i个值时的样本个数, ∣ D ∣ |D| D表示全部数据的样本数。 k k k表示标签列的可能取值的个数, ∣ D i k ∣ |D_{ik}| Dik表示特征A取第i个值时,标签列取第k个值时的样本个数。

  • 还是以上面的数据集为例
年龄有工作有房子信贷情况类别
青年一般
青年
青年
青年一般
青年一般
中年一般
中年
中年
中年非常好
中年非常好
老年非常好
老年
老年
老年非常好
老年一般
  • 假设现在特征 A A A代表年龄, i = 1 i=1 i=1表示青年, i = 2 i=2 i=2表示中年, i = 3 i=3 i=3表示老年;对于标签列, k = 1 k=1 k=1表示是, k = 2 k=2 k=2表示否,则 H ( D ∣ A ) H(D|A) H(DA)为:

H ( D ∣ A ) = − [ 5 15 × ( 2 5 l o g 2 2 5 + 3 5 l o g 2 3 5 ) + 5 15 × ( 3 5 l o g 2 3 5 + 2 5 l o g 2 2 5 ) + 5 15 × ( 4 5 l o g 2 4 5 + 1 5 l o g 2 1 5 ) ] H(D|A) = -[\frac{5}{15} \times (\frac{2}{5}log_2\frac{2}{5} + \frac{3}{5}log_2\frac{3}{5}) + \frac{5}{15} \times (\frac{3}{5}log_2\frac{3}{5} + \frac{2}{5}log_2\frac{2}{5}) + \frac{5}{15} \times (\frac{4}{5}log_2\frac{4}{5} + \frac{1}{5}log_2\frac{1}{5})] H(DA)=[155×(52log252+53log253)+155×(53log253+52log252)+155×(54log254+51log251)]

信息增益

  • 信息增益(互信息)定义为:

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

其中 D D D是数据集, A A A是数据集中的某个特征

  • 根据信息增益准则,特征选择方法是:
    • 对训练数据集D,计算其每个特征的信息增益
    • 选择信息增益最大的特征
  • 信息增益算法一般步骤:
    • 计算数据集 D D D的经验熵 H ( D ) H(D) H(D)

H ( D ) = − ∑ k = 1 k ∣ C k ∣ ∣ D ∣ l o g 2 C k D H(D) = -\sum_{k=1}^k\frac{|C_k|}{|D|}log_2\frac{C_k}{D} H(D)=k=1kDCklog2DCk

  • 计算特征 A A A对数据集 D D D的经验条件熵 H ( D ∣ A ) H(D|A) H(DA)

H ( D ∣ A ) = ∑ i = 1 n ∣ D i ∣ ∣ D ∣ H ( D i ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ ∑ k = 1 k ∣ D i k ∣ ∣ D i ∣ l o g 2 ∣ D i k ∣ ∣ D i ∣ H(D|A) = \sum^n_{i=1}\frac{|D_i|}{|D|}H(D_i) = -\sum_{i=1}^{n}\frac{|D_i|}{|D|}\sum_{k=1}^k\frac{|D_{ik}|}{|D_i|}log_2\frac{|D_{ik}|}{|D_i|} H(DA)=i=1nDDiH(Di)=i=1nDDik=1kDiDiklog2DiDik

  • 计算信息增益

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

ID3算法

  • 在决策树递归构建过程中,使用信息增益的方式进行特征选择
  • 决策树生成过程:
    • 从根节点开始计算所有特征的信息增益,选择信息增益最大的特征作为特征作为节点特征。
    • 对子节点递归调用上述方法,构建决策树
    • 特征信息增益很小或没有特征可以选择时递归结束得到决策树

C4.5算法

  • C4.5算法是对ID3算法的改进,包括
    • 处理连续值属性:ID3算法主要用于处理离散属性,而C4.5能够直接处理连续值属性,通过寻找最佳切分点来将连续值属性转换为二元特征。
    • 缺失值处理:ID3算法无法处理包含缺失值的数据集。C4.5算法提供了一种方法来估算缺失值,并且可以忽略那些特定实例中缺失值的属性。
    • 剪枝技术:ID3算法没有提供对过拟合问题的有效解决方案。C4.5引入了预剪枝和后剪枝技术以减少过拟合的风险,从而提高模型的泛化能力。
    • 多输出类别:C4.5可以处理具有多个类别的分类任务,而不仅仅局限于两个类别。
    • 信息增益比:ID3使用信息增益作为选择属性的标准,但在某些情况下,信息增益可能偏向于选择具有许多不同值的属性。C4.5使用信息增益比(Gain Ratio)作为分裂标准,它考虑了属性纯度改善与该属性值数量之间的关系,以避免偏好具有大量唯一值的属性。
  • C4.5算法在树生成过程中,使用信息增益比来选择特征
  • C4.5信息增益比计算公式:

g r ( D , A ) = g ( D , A ) H A ( D ) H A ( D ) = − ∑ i = 1 n ∣ D i ∣ ∣ D ∣ l o g 2 D i D g_r(D,A) = \frac{g(D,A)}{H_A(D)}\\ H_A(D) = -\sum^n_{i=1}\frac{|D_i|}{|D|}log_2\frac{D_i}{D} gr(D,A)=HA(D)g(D,A)HA(D)=i=1nDDilog2DDi

其中 n n n是特征 A A A可能取值的个数

CART算法

  • CART算法相较于C4.5算法有一些不同:
    • 连续值处理:CART算法也能够处理连续值属性,它通过寻找最优分割点来将连续值属性转化为二元分割。
    • 二叉树结构:CART算法生成的是二叉树,每个非叶子节点都有两个子节点。这与C4.5不同,后者允许非二叉树结构(即一个节点可以有多个子节点)。
    • 分裂标准:对于分类任务,CART使用基尼不纯度(Gini Impurity)作为分裂标准,而不是C4.5所使用的增益比。对于回归任务,CART使用平方误差作为分裂标准。
    • 剪枝策略:CART算法同样支持剪枝以减少过拟合,但它通常采用成本复杂度剪枝(Cost Complexity Pruning),这是一种系统性的后剪枝方法。
    • 可扩展性和效率:CART算法在处理大型数据集时表现良好,并且在计算效率上有所优化。
  • CART算法在树生成过程中,使用基尼指数选择最优特征。生成的是二叉树。
  • 设样本点属于第 k k k类的概率为 p k p_k pk,则:

G i n i ( p ) = ∑ k = 1 k p k ( 1 − p k ) = 1 − ∑ k = 1 k p k 2 Gini(p) = \sum^k_{k=1}p_k(1-p_k) = 1- \sum_{k=1}^kp_k^2 Gini(p)=k=1kpk(1pk)=1k=1kpk2

  • 给定样本集合 D D D,其基尼指数为:

G i n i ( D ) = 1 − ∑ k = 1 k ( ∣ C k ∣ D ) 2 Gini(D) = 1-\sum_{k=1}^k\left(\frac{|C_k|}{D}\right)^2 Gini(D)=1k=1k(DCk)2

  • 在特征A的条件下,集合D的基尼指数为:

G i n i ( D , A ) = ∣ D 1 ∣ ∣ D ∣ G i n i ( D 1 ) + ∣ D 2 ∣ ∣ D ∣ G i n i ( D 2 ) Gini(D,A) = \frac{|D_1|}{|D|}Gini(D_1) + \frac{|D_2|}{|D|}Gini(D_2) Gini(D,A)=DD1Gini(D1)+DD2Gini(D2)

  • 基尼指数越大,样本的不确定性越大,这点与熵相似

ID3算法python代码实现

  • 导入必要库
import numpy as np
import pandas as pd
import statsmodels.api as sm
from tqdm.notebook import tqdm
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing, make_regression
  • 计算 H ( D ) H(D) H(D),即整个数据集的熵
# 计算整个数据集的熵
def entropy(data, label):
    row_num, col_num = data.shape
    target = np.sort(data[label].unique())
    h = 0
    for i in target:
        p = data[data[label] == i].shape[0]/row_num
        h = h + p * np.log2(p)
    return -1 * h
  • 根据特征以及特征值分割数据集
def split_data_set(data, feature, value):
    data = data.copy(deep=True)
    return data[data[feature] == value].drop(feature,axis=1)
  • 选择最优划分特征
def choose_feature(data, label):
    base_entropy = entropy(data, label)
    info_gain = []
    for col in data.columns:
        if col == label:
            continue
        else:
            # 取出当前特征的唯一值
            col_unique = np.sort(data[col].unique())
            temp_entropy = 0
            for u in col_unique:
                sd = split_data_set(data, col, u)
                p = sd.shape[0]/data.shape[0]
                temp_entropy = temp_entropy + p * entropy(sd, label)
            info_gain.append({col:base_entropy - temp_entropy})
    info_gain = sorted(info_gain, key=lambda d: list(d.values())[0], reverse=True)
    return list(info_gain[0].keys())[0]
  • 投票结果
def class_vote(data_list):
    data = pd.DataFrame(data_list, columns=['vote'])
    temp = data['vote'].value_counts()
    return temp.index[0]
  • 训练决策树
def train_tree(data, label):
    # 数据样本数为1
    if data.shape[0] ==1:
        return class_vote(data[label].tolist())
    # lable列只剩下1个类别
    if data[label].unique().shape[0] == 1:
        return data[label].unique()[0]
    feat = choose_feature(data, label)
    tree = {feat:{}}
    for value in data[feat].unique():
        # 持续划分特征递归构建树
        tree[feat][value] = train_tree(split_data_set(data, feat, value), label)
    return tree
  • 决策树预测
def predict(decision_tree, data):
    predictions = []
    for index, row in data.iterrows():
        node = decision_tree
        while isinstance(node, dict):
            feature, children = next(iter(node.items()))
            value = row[feature]
            if value in children:
                node = children[value]
        predictions.append(node)
    return predictions
  • 函数调用
data = pd.read_csv('/kaggle/input/studyml/loan_data.csv')
tree = train_tree(data, '类别')
print(tree)
# 打印输出:{'有房子': {'否': {'有工作': {'否': '否', '是': '是'}}, '是': '是'}}
  • 预测结果
res = predict(tree, data)
print(res)
# 打印输出:['否', '否', '是', '是', '否', '否', '否', '是', '是', '是', '是', '是', '是', '是', '否']
;