ID3决策树python程序实现

算法原理

决策树是一类经典的机器学习方法,既可以用于分类任务,也可以用于回归。分类和回归对应的分别是分类树和回归树,本文将以最常见的一类决策树——ID3分类树为例,讲解模型的原理以及程序实现。

模型

已知一组有n个样本的训练集
{ x i , y i } i = 1 n , x i ∈ ∏ k = 1 t D k , y i ∈ { 0 , 1 } \{x_i,y_i\}_ {i=1}^n,x_i\in\prod_{k=1}^tD^k,y_i\in\{0,1\} {xi,yi}i=1n,xik=1tDk,yi{0,1}
其中,每个样本 x i x_i xi都有 t t t个特征(为了简便起见,假设样本的特征都是离散值)。现在的目的是希望通过对训练集进行训练。之后往模型中代入新的样本 x j x_j xj,得到预测的分类类别。

原理

在讲原理之前先通过一个例子引入,假设现在是晚上,你在考虑是否要玩游戏,这时可能要先判断现在是几点,是否已经很晚了,如果很晚则不玩游戏,若是现在还早,那可能还要判断明天需要上交的作业写好了没有,还没写好则要抓紧时间写,如果写好了,就可以去玩游戏了。这个例子的图示可以表示为

决策树依次通过样本的特征自上而下地对样本进行分类,根据某一个特征的不同,可以将样本分成若干组,对于样本类别全都相同的组则停止分类,对于样本类别不全相同的组,则需要根据剩余的特征再进行分类,直至最后得到的每个组里的样本类别全相同。

为了理解决策树的构建过程,需要先弄清楚几个问题

树的组成

决策树包含节点和分支。

图上的方框即代表节点,它是根据特征进行分类得到的数据组。其中,节点包括根节点,中间节点和叶子节点。根节点是指最初始的节点,即还没有进行任何分类的节点。叶子节点是指不用再进行分类的节点。不是根节点和叶子节点的节点便是中间节点。

分支是指由节点引出的若干条细支,每条分支都代表一个分类过程。

哪些特征需要优先选择

对应到决策树中的节点,由于决策树是自上而下构建的,并且每次只通过一个特征进行分类。而样本集中的特征有很多,现在需要考虑的是要优先选择哪一个特征进行分类?按正常情况来讲当然是分类后得到的各组数据的纯度越高越好,这里的纯度体现在每组数据中各类别的分布情况上。当每组数据中只有一种类别,则纯度是最高的,当不止有一种类别,则纯度会较低一些。衡量这种纯度的方法有很多,本文介绍的是ID3算法,它是通过计算信息增益来选择特征的。

信息增益=分类前信息熵-分类后的加权信息熵
信 息 增 益 = E − ∑ i = 1 k ∣ D i ∣ ∣ D ∣ E i 信息增益=E-\sum_{i=1}^k\frac{|D_i|}{|D|}E_i =Ei=1kDDiEi
其中, E E E是分类前的信息熵, ∣ D ∣ |D| D指分类前的样本总数, ∣ D i ∣ |D_i| Di是分类后的第i组数据的样本总数, E i E_i Ei是指分类后的第i组的信息熵。

为了使得纯度更高,我们希望分类后的信息熵更小,因此要选取信息增益最大的那个特征。按照上述方法对类别数超过一的节点即数据集进行分类,直至得到的分组数据集中类别都只有一种。

分类树与回归树的区别

最明显的不同就是分类树输出的是样本的类别,属于离散值,而回归树输出的是实数,是连续值。另外,样本的特征也有离散值和连续值的差别,如果特征是离散值,则在选取特征时可以用普通的ID3、C4.5和Gini方法。如果特征是连续值,则需要将该特征的数值按大小进行排序,然后分别取这些数值作为阈值将数据分成两类,结合ID3、C4.5或Gini方法选出最优的特征。

程序实现

各函数

计算信息熵
def cal_entropy(y):
    """信息熵计算
    
    参数
    -------
    y:类别编号 类型:narray, shape:{n_samples}
    
    返回
    ----
    e:信息熵  类型:float
    """
    count = np.array(pd.Series(y).value_counts())
    p = count/count.sum()
    return -np.sum(np.log2(p)*p)
选取最优的特征
def choose_features_ID3(X, y):
    """选择特征(单个)
    
    参数
    ------
    X:特征,类型:ndarray,shape:{n_samples, n_features}
    
    y: 类别编号 类型:narray, shape:{n_samples}
    
    返回
    -----
    min_fea_index:选出的特征,类型:integer
    
    entropy:信息增益,类型:float
    """
    n_samples, n_features = X.shape
    
    fea_index = 0
    max_entropy = 0
    pre_y_entropy = cal_entropy(y)
    for i in range(n_features):
        entropy_sum = 0
        row_value = X[:,i]
        for value in set(row_value):
            bools = row_value==value
            entropy_sum += np.sum(bools)/n_samples * cal_entropy(y[bools])
        entropy = pre_y_entropy-entropy_sum
        if entropy>max_entropy:
            max_entropy = entropy
            fea_index = i
    return fea_index,entropy
构建ID3决策树
def tree_ID3(X, y, X_name):
    """构建决策树,采用ID3,无剪枝操作
    
    参数
    ------
    X:特征,类型:ndarray,shape:{n_samples, n_features}
    
    y: 类别编号,类型:ndarray,shape:{n_samples}
    
    X_name: 特征名,类型:ndarray,shape:{n_samples}
    """
    if not len(X):return 
    if cal_entropy(y)==0:return y[0]
    
    n_samples, n_features = X.shape
    index = choose_features_ID3(X, y)[0]
    dic = {X_name[index]:{}}
    remove_fea = X[:, index]
    for fea in set(remove_fea):
        row_bool = remove_fea==fea  # 留下的行索引
        col_bool = np.arange(n_features)!=index   # 留下的列索引
        dic[X_name[index]][fea] = tree_ID3(X[row_bool][:,col_bool], y[row_bool], X_name[col_bool])
    return dic

实例化演示

以西瓜数据集为例

dataSet = np.array([
        # 1
        ['青绿', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
        # 2
        ['乌黑', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
        # 3
        ['乌黑', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
        # 4
        ['青绿', '蜷缩', '沉闷', '清晰', '凹陷', '硬滑', '好瓜'],
        # 5
        ['浅白', '蜷缩', '浊响', '清晰', '凹陷', '硬滑', '好瓜'],
        # 6
        ['青绿', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '好瓜'],
        # 7
        ['乌黑', '稍蜷', '浊响', '稍糊', '稍凹', '软粘', '好瓜'],
        # 8
        ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '硬滑', '好瓜'],

        # ----------------------------------------------------
        # 9
        ['乌黑', '稍蜷', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜'],
        # 10
        ['青绿', '硬挺', '清脆', '清晰', '平坦', '软粘', '坏瓜'],
        # 11
        ['浅白', '硬挺', '清脆', '模糊', '平坦', '硬滑', '坏瓜'],
        # 12
        ['浅白', '蜷缩', '浊响', '模糊', '平坦', '软粘', '坏瓜'],
        # 13
        ['青绿', '稍蜷', '浊响', '稍糊', '凹陷', '硬滑', '坏瓜'],
        # 14
        ['浅白', '稍蜷', '沉闷', '稍糊', '凹陷', '硬滑', '坏瓜'],
        # 15
        ['乌黑', '稍蜷', '浊响', '清晰', '稍凹', '软粘', '坏瓜'],
        # 16
        ['浅白', '蜷缩', '浊响', '模糊', '平坦', '硬滑', '坏瓜'],
        # 17
        ['青绿', '蜷缩', '沉闷', '稍糊', '稍凹', '硬滑', '坏瓜']
    ])
X = dataSet[:,:-1]
y = dataSet[:,-1]
X_name = np.array(['色泽','根蒂','敲声','纹理','脐部','触感'])
tree_ID3(X,y,X_name)

out

{'纹理': {'稍糊': {'触感': {'硬滑': '坏瓜', '软粘': '好瓜'}},
  '清晰': {'根蒂': {'稍蜷': {'色泽': {'青绿': '好瓜',
      '乌黑': {'触感': {'硬滑': '好瓜', '软粘': '坏瓜'}}}},
    '硬挺': '坏瓜',
    '蜷缩': '好瓜'}},
  '模糊': '坏瓜'}}
封装成一个类

增加训练函数fit和预测函数predict、check,其中,check函数是对一个样本进行预测,predict函数整合了多个样本。

class Tree_ID3:
    def __init__(self):
        pass
        
    def cal_entropy(self, y):
        """信息熵计算

        参数
        -------
        y:类别, 类型:narray, shape:{n_samples}

        返回
        ----
        e:信息熵, 类型:float
        """
        count = np.array(pd.Series(y).value_counts())
        # 每个类别的概率
        p = count/count.sum()
        # 信息熵
        return -np.sum(np.log2(p)*p)

    def choose_features_ID3(self, X, y):
        """选择特征(单个)

        参数
        ------
        X:特征, 类型:ndarray, shape:{n_samples, n_features}

        y:类别, 类型:ndarray, shape:{n_samples}

        返回
        -----
        fea_index:选出的特征, 类型:integer

        entropy:信息增益, 类型:float
        """
        n_samples, n_features = X.shape

        # 最优特征的索引
        fea_index = 0
        # 最大的信息增益
        max_entropy = 0
        # 分类前标签y的信息熵
        pre_y_entropy = self.cal_entropy(y)
        
        for i in range(n_features):
            # 初始化分类后的加权信息熵
            entropy_sum = 0
            row_value = X[:,i]
            for value in set(row_value):
                # 选中的样本索引
                bools = row_value==value
                entropy_sum += np.sum(bools)/n_samples * self.cal_entropy(y[bools])
            # 当前信息增益
            entropy = pre_y_entropy-entropy_sum
            if entropy>max_entropy:
                max_entropy = entropy
                fea_index = i
        return fea_index,entropy

    def tree_ID3(self, X, y, X_name):
        """构建决策树

        参数
        ------
        X:特征, 类型:ndarray, shape:{n_samples, n_features}

        y:类别编号, 类型:ndarray, shape:{n_samples}

        X_name:特征名, 类型:ndarray, shape:{n_samples}
        
        返回
        -----
        dic:决策树, 类型:dict
        """
        if not len(X):return 
        # 只剩一类,返回
        if self.cal_entropy(y)==0:return y[0]

        n_samples, n_features = X.shape
        index = self.choose_features_ID3(X, y)[0]
        # 决策树构建
        dic = {X_name[index]:{}}
        remove_fea = X[:, index]
        for fea in set(remove_fea):
            # 剩下的行索引
            row_bool = remove_fea==fea  
            # 剩下的列索引
            col_bool = np.arange(n_features)!=index   
            # 递归
            dic[X_name[index]][fea] = self.tree_ID3(X[row_bool][:,col_bool], y[row_bool], X_name[col_bool])
        return dic 
    
    def check(self, tree, X, X_name):
        """预测
        """
        if not len(tree) or not len(X):return
        cur_fea_name = list(tree.keys())[0]
        cur_fea_index = np.where(X_name==cur_fea_name)[0][0]
        if X[cur_fea_index] not in tree[cur_fea_name].keys():return
        if tree[cur_fea_name][X[cur_fea_index]] in self.y_name:
            return tree[cur_fea_name][X[cur_fea_index]]
        else:
            bools = np.arange(len(X))!=cur_fea_index
            return self.check(tree[cur_fea_name][X[cur_fea_index]], X[bools], X_name[bools])
    
    def fit(self, X, y, X_name):
        self.X_name = X_name
        self.y_name = list(set(y))
        self.tree = self.tree_ID3(X, y, X_name)
        
    def predict(self, X):
        res = []
        for i in range(len(X)):
            res.append(self.check(self.tree, X[i], self.X_name))
        return np.array(res)
演示
clf = Tree_ID3()
clf.fit(X, y, X_name)
predict_y = clf.predict(X)
sum(predict_y==y)==len(y)

以上便是ID3分类树的代码实现,如有不对不妥之处,欢迎交流批评改正。

----end----

参考资料:
周志华《机器学习》

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值