【机器学习】决策树

本文详细阐述了如何使用Python实现简单的二叉决策树,包括信息增益、信息增益率和基尼指数的计算,以及如何处理数据、划分训练集和测试集,并在最大深度10下评估模型精度。通过实例学习,强调了扎实理论和实践的重要性。
摘要由CSDN通过智能技术生成

这次实验将实现一个简单的二叉决策树,本来想要在不熟悉理论的前提下囫囵吞枣地完成作业,结果遇到了瓶颈…于是乎开始从头认真的整理思路。看来,走的捷径最终都会加倍奉还。
知识还是应该老老实实地积累,遇到困难就退缩可不是学习的模样。

实验内容:
1.实现信息增益、信息增益率、基尼指数三种划分标准
2.使用给定的训练集完成三种决策树的训练过程
3.计算三种决策树在最大深度为10时在训练集和测试集上的精度,查准率,查全率,F1值

一、数据处理

1. 读取数据

只使用grade, term, home_ownership, emp_length这四列作为特征,safe_loans作为标记

# 导入数据
loans = pd.read_csv('data/lendingclub/lending-club-data.csv', low_memory=False)

features = ['grade',              # grade of the loan
            'term',               # the term of the loan
            'home_ownership',     # home_ownership status: own, mortgage or rent
            'emp_length',         # number of years of employment
           ]
target = 'safe_loans'
loans = loans[features + [target]]

2.划分训练集和测试集

from sklearn.utils import shuffle
loans = shuffle(loans, random_state = 34)
#选取前60%的列作为训练集
split_line = int(len(loans) * 0.6)
train_data = loans.iloc[: split_line]
test_data = loans.iloc[split_line:]

train_data如下图所示:在这里插入图片描述

3. 特征预处理

one-hot处理

可以看到所有的特征都是离散类型的特征,需要对数据进行预处理,使用one-hot编码对其进行处理。
pandas中使用get_dummies生成one-hot向量

def one_hot_encoding(data, features_categorical):
    '''
    Parameter
    ----------
    data: pd.DataFrame
    
    features_categorical: list(str)
    '''
    
    # 对所有的离散特征遍历
    for cat in features_categorical:
        
        # 对这列进行one-hot编码,前缀为这个变量名
        one_encoding = pd.get_dummies(data[cat], prefix = cat)
        
        # 将生成的one-hot编码与之前的dataframe拼接起来
        data = pd.concat([data, one_encoding],axis=1)
        
        # 删除掉原始的这列离散特征
        del data[cat]
    
    return data
    
 

首先对训练集生成one-hot向量,然后对测试集生成one-hot向量,这里需要注意的是,如果训练集中,特征 𝐴 的取值为
{𝑎,𝑏,𝑐} ,这样我们生成的特征就有三列,分别为 𝐴_𝑎 , 𝐴_𝑏 , 𝐴_𝑐
,然后我们使用这个训练集训练模型,模型就就只会考虑这三个特征,在测试集中如果有一个样本的特征 𝐴 的值为 𝑑 ,那它的
𝐴_𝑎 , 𝐴_𝑏 , 𝐴_𝑐 就都为0,我们不去考虑 𝐴_𝑑 ,因为这个特征在训练模型的时候是不存在的,也就是会报错。

#训练集做onehot编码处理
train_data = one_hot_encoding(train_data, features)
one_hot_features = train_data.columns.tolist()
one_hot_features.remove(target)

提示:
data.index返回一个index类型的行索引列表,data.index.values返回的是行索引组成的ndarray类型。
data.columns返回一个index类型的列索引列表,data.columns.values返回的是列索引组成的ndarray类型。

接下来是对测试集进行one_hot编码,但只要保留出现在one_hot_features中的特征即可·(仔细思考为什么!因为one-hot编码后不在范围内的会出错)

test_data_tmp = one_hot_encoding(test_data, features)
# 创建一个空的DataFrame
test_data = pd.DataFrame(columns = train_data.columns)
for feature in train_data.columns:
    # 如果训练集中当前特征在test_data_tmp中出现了,将其复制到test_data中
    if feature in test_data_tmp.columns:
        test_data[feature] = test_data_tmp[feature].copy()
    else:
        # 否则就用全为0的列去替代
        test_data[feature] = np.zeros(test_data_tmp.shape[0], dtype = 'uint8')
  

二、特征划分

决策树中有很多常用的特征划分方法,比如信息增益、信息增益率、基尼指数
我们需要实现一个函数,它的作用是,给定决策树的某个结点内的所有样本的标记,让它计算出对应划分指标的值是多少

  • 这里我们约定,将所有特征取值为0的样本,划分到左子树,特征取值为1的样本,划分到右子树

1. 信息增益

概念

熵:表示随机变量的不确定性。

条件熵:在一个条件下,随机变量的不确定性。

信息增益:熵 - 条件熵。表示在一个条件下,信息不确定性减少的程度。

通俗地讲,X(明天下雨)是一个随机变量,X的熵可以算出来, Y(明天阴天)也是随机变量,在阴天情况下下雨的信息熵我们如果也知道的话(此处需要知道其联合概率分布或是通过数据估计)即是条件熵。X的熵减去Y条件下X的熵,就是信息增益。
具体解释:原本明天下雨的信息熵是2,条件熵是0.01(因为如果知道明天是阴天,那么下雨的概率很大,信息量少),这样相减后为1.99。在获得阴天这个信息后,下雨信息不确定性减少了1.99,不确定减少了很多,所以信息增益大。也就是说,阴天这个信息对明天下午这一推断来说非常重要。

所以在特征选择的时候常常用信息增益,如果选择一个特征后,信息增益最大(信息不确定性减少的程度最大),那么我们就选取这个特征。

公式

信息熵:
E n t ( D ) = − ∑ k = 1 ∣ Y ∣ p k l o g 2 p k \mathrm{Ent}(D) = - \sum^{\vert \mathcal{Y} \vert}_{k = 1} p_k \mathrm{log}_2 p_k Ent(D)=k=1Ypklog2pk
信息增益:
G a i n ( D , a ) = E n t ( D ) − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ E n t ( D v ) \mathrm{Gain}(D, a) = \mathrm{Ent}(D) - \sum^{V}_{v=1} \frac{\vert D^v \vert}{\vert D \vert} \mathrm{Ent}(D^v) Gain(D,a)=Ent(D)v=1VDDvEnt(Dv)
计算信息熵时约定:若 p = 0 p = 0 p=0,则 p log ⁡ 2 p = 0 p \log_2p = 0 plog2p=0

代码

 '''
    求当前结点的信息熵
    
    Parameter
    ----------
    labels_in_node: np.ndarray, 如[-1, 1, -1, 1, 1]
    
    Returns
    ----------
    float: information entropy
    '''
    # 统计样本总个数
def information_entropy(labels_in_node):
   
    num_of_samples = labels_in_node.shape[0]
    
    if num_of_samples == 0:
        return 0
    
    # 统计出标记为1的个数
    num_of_positive = len(labels_in_node[labels_in_node == 1])
    
    # 统计出标记为-1的个数
    num_of_negative = len(labels_in_node[labels_in_node == -1])                                                               # YOUR CODE HERE
    
    # 统计正例的概率
    prob_positive = num_of_positive / num_of_samples
    
    # 统计负例的概率
    prob_negative = num_of_negative  / num_of_samples                                                          # YOUR CODE HERE
    
    if prob_positive == 0:
        positive_part = 0
    else:
        positive_part = prob_positive * np.log2(prob_positive)
    
    if prob_negative == 0:
        negative_part = 0
    else:
        negative_part = prob_negative * np.log2(prob_negative)
    
    return - ( positive_part + negative_part )
 '''
    计算所有特征的信息增益
    
    Parameter
    ----------
        data: pd.DataFrame,传入的样本,带有特征和标记的dataframe
        
        features: list(str),特征名组成的list
        
        target: str, 标记(label)的名字
        
        annotate, boolean,是否打印所有特征的信息增益值,默认为False
        
    Returns
    ----------
        information_gains: dict, key: str, 特征名
                                 value: float,信息增益
    '''
def compute_information_gains(data, features, target, annotate = False):
   
    
    # 我们将每个特征划分的信息增益值存储在一个dict中
    # 键是特征名,值是信息增益值
    information_gains = dict()
    
    # 对所有的特征进行遍历,使用信息增益对每个特征进行计算
    for feature in features:
        
        # 左子树保证所有的样本的这个特征取值为0
        left_split_target = data[data[feature] == 0][target]
        
        # 右子树保证所有的样本的这个特征取值为1
        right_split_target =  data[data[feature] == 1][target]
            
        # 计算左子树的信息熵
        left_entropy = information_entropy(left_split_target)
        
        # 计算左子树的权重
        left_weight = len(left_split_target) / (len(left_split_target) + len(right_split_target))

        # 计算右子树的信息熵
        right_entropy = information_entropy(right_split_target)                                                           # YOUR CODE HERE
        
        # 计算右子树的权重
        right_weight = len(right_split_target) / (len(right_split_target) + len(left_split_target))                                                                  # YOUR CODE HERE
        
        # 计算当前结点的信息熵
        current_entropy = information_entropy(data[target])
            
        # 计算使用当前特征划分的信息增益
        gain =   gains(data,feature,target)                                                                      # YOUR CODE HERE
        
        # 将特征名与增益值以键值对的形式存储在information_gains中
        information_gains[feature] = gain
        
        if annotate:
            print(" ", feature, gain)
            
    return information_gains

总结:对于以上信息增益,还可以这样理解
信息增益 = entroy(前) - entroy(后) =
熵可以表示样本集合的不确定性,熵越大,样本的不确定性就越大。因此可以使用划分前后集合熵的差值 来衡量使用当前特征对于样本集合D划分效果的好坏

2. 信息增益率

== 信息增益比 = 惩罚参数 * 信息增益==

信息增益率:
G a i n _ r a t i o ( D , a ) = G a i n ( D , a ) I V ( a ) \mathrm{Gain\_ratio}(D, a) = \frac{\mathrm{Gain}(D, a)}{\mathrm{IV}(a)} Gain_ratio(D,a)=IV(a)Gain(D,a)
其中
I V ( a ) = − ∑ v = 1 V ∣ D v ∣ ∣ D ∣ log ⁡ 2 ∣ D v ∣ ∣ D ∣ \mathrm{IV}(a) = - \sum^V_{v=1} \frac{\vert D^v \vert}{\vert D \vert} \log_2 \frac{\vert D^v \vert}{\vert D \vert} IV(a)=v=1VDDvlog2DDv

信息增益比本质: 是在信息增益的基础之上乘上一个惩罚参数。特征个数较多时,惩罚参数较小;特征个数较少时,惩罚参数较大。

缺点:信息增益比偏向取值较少的特征

原因: 当特征取值较少时HA(D)的值较小,因此其倒数较大,因而信息增益比较大。因而偏向取值较少的特征。

使用信息增益比:基于以上缺点,并不是直接选择信息增益率最大的特征,而是现在候选特征中找出信息增益高于平均水平的特征,然后在这些特征中再选择信息增益率最高的特征。

  '''
    计算所有特征的信息增益率并保存起来
    
    Parameter
    ----------
    data: pd.DataFrame, 带有特征和标记的数据
    
    features: list(str),特征名组成的list
    
    target: str, 特征的名字
    
    annotate: boolean, default False,是否打印注释
    
    Returns
    ----------
    gain_ratios: dict, key: str, 特征名
                       value: float,信息增益率
    '''

def compute_information_gain_ratios(data, features, target, annotate = False):
  
    gain_ratios={}
    
    # 对所有的特征进行遍历,使用当前的划分方法对每个特征进行计算
    for feature in features:
        # 左子树保证所有的样本的这个特征取值为0
        left_split_target = data[data[feature] == 0][target]
        
        # 右子树保证所有的样本的这个特征取值为1
        right_split_target =  data[data[feature] == 1][target]
            
        # 计算左子树的信息熵
        left_entropy = information_entropy(left_split_target)
        
        # 计算左子树的权重
        left_weight = len(left_split_target) / (len(left_split_target) + len(right_split_target))

        # 计算右子树的信息熵
        right_entropy = information_entropy(right_split_target)                                                   # YOUR CODE HERE
        
        # 计算右子树的权重
        right_weight =  len(right_split_target) / (len(left_split_target) + len(right_split_target))                                                               # YOUR CODE HERE
        
        # 计算当前结点的信息熵
        current_entropy = information_entropy(data[target])
        
        # 计算当前结点的信息增益
        
        gain = gains(data,feature,target)                                                                             # YOUR CODE HERE
        
        # 计算IV公式中,当前特征为0的值
        if left_weight == 0:
            left_IV =0
        else:
            left_IV =  np.log2(left_weight  )*left_weight                            # YOUR CODE HERE
        
        # 计算IV公式中,当前特征为1的值
        if right_weight == 0:
            right_IV = 0
        else:
            right_IV = right_weight*np.log2(right_weight)  # YOUR CODE HERE
        
        # IV 等于所有子树IV之和的相反数
        IV = - (left_IV + right_IV)
            
        # 计算使用当前特征划分的信息增益率
        # 这里为了防止IV是0,导致除法得到np.inf(无穷),在分母加了一个很小的小数
        gain_ratio = gain / (IV + np.finfo(np.longdouble).eps)
        
        # 信息增益率的存储
        gain_ratios[feature] = gain_ratio
        
        if annotate:
            print(" ", feature, gain_ratio)
            
    return gain_ratios
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值