决策树-ID3

ID3 算法由 Quinlan 在 1986 年提出,核心是在决策树各个结点上应用信息增益准则选择特征,递归地构建决策树。关于决策树的内容可参考

【具体方法】:

  • 从根结点(root node)出发,对结点计算所有可能的特征的信息增益,选择信息增益最大的特征作为结点的特征,由该特征的不同取值建立子结点;
  • 再对子结点递归地调用以上方法,构建决策树;直到所有特征的信息增益均很小或没有特征可以选择为止。
  • 最后得到一个决策树。

ID3 相当于用极大似然法进行概率模型的选择。

【算法】:ID3 算法

  • 输入:训练数据集 D,特征集 A,阈值 e。
  • 输出:决策树 T。
  • 过程:
  1. 若 D 中所有实例属于同一类 C k C_k Ck,则 T 为单结点树,并将类 C k C_k Ck 作为该结点的类标记,返回 T;
  2. 若 A = ∅ \emptyset ,则 T 为单结点树,并将 D 中实例数最大的类 C k C_k Ck 作为该结点的类标记,返回 T;
  3. 否则,计算 A 中各特征对 D 的信息增益,选择信息增益最大的特征 A g A_g Ag
  4. 如果 A g A_g Ag 的信息增益小于阈值 e,则置 T 为单结点树,并将 D 中实例数最大的类 C k C_k Ck 作为该结点的类标记,返回 T;
  5. 否则,对 A g A_g Ag 的每一可能值 a i a_i ai,依 A g = a i A_g = a_i Ag=ai 将 D 分割为若干非空子集 D i D_i Di,将 D i D_i Di 中实例数最大的类作为标记,构建子结点,由结点及其子结点构成树 T,返回 T;
  6. 对第 i 个子结点,以 D i D_i Di 为训练集,以 A − A g A - {A_g} AAg 为特征集,递归地调用 (1) ~ (5) 步,得到子树 T i T_i Ti,返回 T i T_i Ti

【注意】:ID3 算法只有树的生成,因此生成的树容易产生过拟合。

【信息熵计算公式】
E n t ( D ) = ∑ i = i k − ∣ Y i ∣ ∣ Y ∣ l o g 2 ∣ Y i ∣ ∣ Y ∣ Ent(D) = \sum_{i=i}^{k}-\frac{|Y_i|}{|Y|}log_2\frac{|Y_i|}{|Y|} Ent(D)=i=ikYYilog2YYi
其中,|Y| 表示当前数据集 D 的标签数,k 表示数据集 D 的标签种类数。例如,现在有 5 条样本,每条样本的分类为 1、0、1、2、0,那么 |Y| = 5,k = 3。 ∣ Y i ∣ |Y_i| Yi 表示指定标签种类的标签数,例如 Y 0 Y_0 Y0 对应标签种类 0,那么 ∣ Y 0 ∣ = 2 |Y_0| = 2 Y0=2,同理可得 ∣ Y 1 ∣ = 2 , ∣ Y 2 ∣ = 1 |Y_1| = 2, |Y_2|= 1 Y1=2,Y2=1

【信息增益计算公式】:
G a i n ( D , a ) = ∑ i = 1 ∣ a ∣ ∣ D i ∣ ∣ D ∣ E n t ( D i ) Gain(D, a) = \sum_{i=1}^{|a|}\frac{|D_i|}{|D|}Ent(D_i) Gain(D,a)=i=1aDDiEnt(Di)
其中,a 表示数据集 D 上的一个特征,|a| 表示该特征的所有可能的取值数,例如 a 表示性别,则 a 有两种取值,即“男”和“女”。 D i D_i Di 表示根据指定特征取值划分的数据集, ∣ D i ∣ |D_i| Di 表示该数据集的样本数。

代码实现

ID3 算法只能用来处理离散型数据以及分类任务,因此我们选用 sklearn.datasets 中的 iris 鸢尾花数据集。

import numpy as np
from sklearn.datasets import load_iris


dataset = load_iris()
x = dataset.data
y = dataset.target
features = dataset.feature_names

决策树结点结构代码:

class Node:
    
    def __init__(self, value, type='decision'):
        self.value = value    
        self.type = type
        self.children = {}

calc_shannon_ent()

calc_shannon_ent 信息增益计算函数,该函数接受两个参数 data 和 labels。

  • data:数据集
  • labels:标签集

首先,获取数据集的个数和特征数以及分类的个数。

data_count = float(data.shape[0])
features = data.shape[1]
labels_count = np.array([labels[labels == label].size for label in set(labels)])

注意:labels_count / data_count 的结果即为原始数据集各分类的概率。

接着,计算原始数据集的信息熵以及创建存放划分后各特征信息增益信息的列表 gain_list。

base_ent = -np.sum((labels_count / data_count) * np.log2(labels_count / data_count))
# 存放各种特征值信息增益
gain_list = []

然后,计算每个特征划分后的数据集的信息熵。

# 遍历特征集中的每一个特征
for feature in range(0, features):
    # 获取特征对应的数据
    feature_data = data[:, feature]
    # 获取特征信息,以特征值为 key,特征值的数目为 value
    feature_info = {feature: feature_data[feature_data == feature].size for feature in set(feature_data)}
    feature_shannonEnt = 0
    
    # 获取每个特征值的分类信息并计算条件信息熵
    for feature_value in feature_info:
        # 当前特征值的数目
        feature_count = float(feature_info[feature_value])
        label_data = labels[feature_data == feature_value]
        labels_feature = np.array([label_data[label_data == label].size for label in set(label_data)])
        # 计算每个分类的概率
        p_label = labels_feature / feature_count
        feature_shannonEnt += (feature_count / data_count) * np.sum(-p_label * np.log2(p_label))
    # 将当前特征对应的信息增益添加到 gain_list 列表中
    gain_list.append(base_ent - feature_shannonEnt)

最后,返回其中信息增益最大特征的下标。

gain_list = np.array(gain_list)
return np.argmax(gain_list)

【完整代码】:

def calc_shannon_ent(data, labels):
    data_count = float(data.shape[0])
    features = data.shape[1]
    # 统计每个分类的个数
    labels_count = np.array([labels[labels == label].size for label in set(labels)])
    # 计算数据集的信息熵
    base_ent = -np.sum((labels_count / data_count) * np.log2(labels_count / data_count))
    # 存放各种特征值信息增益
    gain_list = []
    
    # 计算每个特征划分后的数据集的信息熵
    for feature in range(0, features):
        # 获取特征对应的数据
        feature_data = data[:, feature]
        # 获取特征信息,以特征值为 key,特征值的数目为 value
        feature_info = {feature: feature_data[feature_data == feature].size for feature in set(feature_data)}
        feature_shannonEnt = 0
        # 获取每个特征值的分类信息并计算条件信息熵
        for feature_value in feature_info:
            # 当前特征值的数目
            feature_count = float(feature_info[feature_value])
            label_data = labels[feature_data == feature_value]
            labels_feature = np.array([label_data[label_data == label].size for label in set(label_data)])
            # 计算每个分类的概率
            p_label = labels_feature / feature_count
            feature_shannonEnt += (feature_count / data_count) * np.sum(-p_label * np.log2(p_label))
        gain_list.append(base_ent - feature_shannonEnt)
    gain_list = np.array(gain_list)
    return np.argmax(gain_list)

split_dataset()

split_dataset() 方法根据特征和特征值划分数据集,该参数接受四个参数:

  • data:数据集
  • labels:标签列表
  • feature:特征
  • value:特征值

split_dataset() 返回一个包含划分后数据集和标签集的元组。

【完整代码】:

def split_dataset(data, labels, feature, value):
    """
    :param data:    数据集 ndarray
    :param labels:  标签列表 ndarray
    :param feature: 特征 
    :param value:   特征值
    :return: 
    """
    feature_data = data[:, feature]
    select_rows = feature_data == value
    return (np.delete(data[select_rows], feature, axis=1), labels[select_rows])

voting_label()

voting_label() 投票函数,用以确定叶结点的分类结果。该方法仅接受一个参数 labels,标签集。

【完整代码】:

def voting_label(labels):
    return sorted([(label, len(labels[label == label])) for label in set(labels)])[-1][1]

create_tree()

create_tree() 递归创建决策树函数,该方法接受三个参数:

  • data:数据集
  • labels:标签集
  • features:特征集

首先,判断特征集是否存在,若不存在,则当前结点作为叶结点。

if len(features) == 0:
    return Node(voting_label(labels))

然后,判断标签集,若仅有一种标签,则当前结点作为叶结点。

if len(set(labels)) == 1:
    return Node(labels[0])

接着,调用 calc_shannon_ent() 方法获取最优特征的下标,并根据下标得到最优特征。

best_feature_index = calc_shannon_ent(data, labels)
best_feature = features[best_feature_index]

第四步,创建结点,并将已划分的特征从特征集中移除。

node = Node(best_feature)
features = np.delete(features, best_feature_index)

第五步,根据最优特征划分数据集。

best_feature_data = data[:, best_feature_index]
best_feature_info = {feature: best_feature_data[best_feature_data == feature].size for feature in set(best_feature_data)}

注意:best_feature_info 是一个包含各特征值所对应数据集的字典。

第六步,根据各特征值对应的数据集,递归调用 create_tree() 方法来创建子结点。

for feature_value in best_feature_info:
    split_data, split_labels = split_dataset(data, labels, best_feature_index, feature_value)
    node.children[feature_value] = create_tree(split_data, split_labels, features)

最后,返回根结点。

return node

【完整代码】:

def create_tree(data, labels, features):
    # 判断:特征集是否存在,如果不存在,则当前结点作为叶结点
    if len(features) == 0:
        return Node(voting_label(labels))
    # 判断:标签集,若标签只有一种,则当前结点作为叶结点
    if len(set(labels)) == 1:
        return Node(labels[0])
    # 获取最优特征的下标
    best_feature_index = calc_shannon_ent(data, labels)
    best_feature = features[best_feature_index]
    # 创建结点
    node = Node(best_feature)
    # 将已划分的特征从特征集中移除
    features = np.delete(features, best_feature_index)
    # 根据最优特征划分数据集
    best_feature_data = data[:, best_feature_index]
    best_feature_info = {feature: best_feature_data[best_feature_data == feature].size for feature in set(best_feature_data)}
    for feature_value in best_feature_info:
        split_data, split_labels = split_dataset(data, labels, best_feature_index, feature_value)
        node.children[feature_value] = create_tree(split_data, split_labels, features)
    return node      

【调用 create_tree() 函数】:

root = create_tree(x, y, list(range(x.shape[1])))
print(root.value) # 输出 2

以信息增益作为特征选择依据,根节点的值为特征集下标为 2 的特征,在鸢尾花数据集中,下标为 2 的特征的名称为:

print(features[root.value])
# 输出:'petal length (cm)'

完整代码以及调用过程可从 传送门 中获取。

参考

  • 《统计学习方法》李航
  • 《机器学习》周志华
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值