决策树,一种强大的机器学习算法

一、决策树的定义和理解

决策树是一种基于树状结构的机器学习算法,用于解决分类和回归问题。它是一种直观且易于理解的模型,常被用于数据挖掘和预测分析。

决策树通过将数据集逐步划分为不同的子集来进行决策。树的每个内部节点代表一个特征或属性,叶节点代表一个决策结果或类别。从根节点开始,根据样本特征的取值,沿着树的分支向下遍历,最终到达叶节点,得到预测结果。

为了更加好理解我们列举一个实例,

假设我们有一个数据集,包含了一些汽车的属性(如品牌、车型、价格、颜色等),我们想要使用决策树算法来预测这些汽车是否适合买家。

首先,我们需要将数据集划分为训练集和测试集。然后,我们可以使用决策树算法来构建模型。在构建模型时,算法会选择最优的特征来进行划分,并递归地生成一棵决策树。

例如,算法可能会选择“品牌”作为第一个划分特征。如果品牌为“奥迪”,则继续判断车型,如果为“Q5”,则进一步判断价格是否大于10万;如果品牌为“宝马”,则继续判断车型,如果为“X5”,则进一步判断颜色是否为黑色。

通过这样的递归过程,算法可以得出一个针对汽车属性的决策树,用于预测每个汽车是否适合买家。

在预测新的汽车时,我们可以沿着决策树进行遍历,直到到达叶子节点上的类别,即可得到预测结果。最后会得到一个类似于二叉树的决策树(它是基于贪心算法):

二、决策树的构造(原理)

2.1决策树的一般流程

收集数据:收集用于构建决策树的数据集。数据集应该包含特征和对应的类别或目标变量。

特征选择:通过一定的准则选择最佳的特征来划分数据集。常用的特征选择准则包括基尼不纯度、熵或信息增益等。

构建树:根据选择的特征,将数据集逐步划分为不同的子集,构建决策树的过程。通常采用递归的方式从根节点开始构建树。

剪枝:为了防止过拟合,可以对已构建的决策树进行剪枝操作。剪枝可以通过预剪枝或后剪枝来实现。

预测:使用构建好的决策树对新的样本进行预测。从根节点开始,根据样本的特征值沿着树的分支进行遍历,最终到达叶节点,得到预测结果。

评估模型:对构建好的决策树进行评估,评估指标可以包括准确率、召回率、F1值等。

可视化:可以将构建好的决策树可视化,以便更好地理解和解释模型。

 

2.2信息熵和信息增益

 信息熵(Entropy)和信息增益(Information Gain)是决策树算法中常用的特征选择准则,用于衡量分裂数据集的效果。

(1)定义:信息熵是指在给定样本集合的条件下,随机变量不确定性的度量,通常用公式表示为:

H\left ( x \right )=-\sum_{i=1}^{n}p_{i}log_{2}p_{i}

其中,pi表示第i个类别在样本中出现的概率,n表示类别数目。信息熵的值越大,表示随机变量的不确定性越高。

(2)定义:在决策树算法中,我们希望通过选择最佳的特征来最大化信息增益。信息增益表示利用某一特征对样本进行划分所得到的信息增益,通常用公式表示为:

信息增益的值越大,表示使用特征进行划分后所获得的信息增益越大。

下面通过一个例子来更好地理解:

我们通过上数据来确定:

2.3基尼值和基尼指数

 基尼值和基尼指数是衡量样本集合纯度的指标,常用于决策树算法中的特征选择准则。

(1)定义:基尼值表示从样本集合中随机抽取两个样本,其类别标签不一致的概率。对于一个具有nn个类别的样本集合,基尼值可以通过以下公式计算:

(2)定义:基尼指数是使用某一特征对样本进行划分后,衡量划分结果纯度的指标。基尼指数可以通过以下公式计算:

下图很好展示基尼系数随着概率的变化:

 下面通过一个案例来展示:

  有工作                      有房子                       信誉                                 贷款结果

2.4 剪枝处理

决策树的剪枝是为了防止过度拟合,提高模型的泛化能力。剪枝可以通过两种方式实现:预剪枝和后剪枝。

预剪枝:在构建决策树时,在每个节点处对当前节点进行判断,如果当前节点不满足一定的条件,则停止分裂并将该节点标记为叶子节点。常用的预剪枝条件包括设定一个最小样本数、限制树的最大深度等。

后剪枝:在构建完整棵决策树之后,从下往上逐层考虑是否需要剪枝,判断是否剪枝的依据是对验证集的准确率是否有所提升。如果剪枝后准确率有所提升,则进行剪枝操作;否则保留原来的节点。

具体的后剪枝算法可以采用C4.5算法中的REP方法,该方法的步骤如下:

1.把原始数据集分成训练集和验证集。

2.构建完整的决策树。

3.自下而上地对各内部节点进行考虑,如果剪枝后验证集的错误率不降反升,则不进行剪枝操作;否则进行剪枝操作。

4.重复步骤3,直到无法提高验证集的准确率为止。

三、决策树的生成(实现)

3.1计算给定数据集的基尼指数

def calc_gini_index(data_set):
    """
    计算给定数据集的基尼指数
    :param data_set: 数据集
    :return: 基尼指数
    """
    num_entries = len(data_set)
    label_counts = {}
    for feat_vec in data_set:
        current_label = feat_vec[-1]
        label_counts[current_label] = label_counts.get(current_label, 0) + 1
    gini_index = 1.0
    for key in label_counts:
        prob = float(label_counts[key]) / num_entries
        gini_index -= prob ** 2
    return gini_index

3.2划分数据集

 

def split_data_set(data_set, axis, value):
    """
    划分数据集
    :param data_set: 待划分的数据集
    :param axis: 划分数据集的特征的索引
    :param value: 特征的值
    :return: 划分后的子集
    """
    ret_data_set = []
    for feat_vec in data_set:
        if feat_vec[axis] == value:
            reduced_feat_vec = np.concatenate((feat_vec[:axis], feat_vec[axis+1:]))
            ret_data_set.append(reduced_feat_vec)
    return ret_data_set

3.3根据基尼指数选择划分方式

def choose_best_feature_to_split(data_set):
    """
    选择最好的数据集划分方式
    :param data_set: 数据集
    :return best_feature: 最优特征的索引
            best_gini_index: 最小基尼指数
    """
    num_features = len(data_set[0]) - 1
    best_gini_index = float('inf')
    best_feature = -1
    for i in range(num_features):
        feat_list = [example[i] for example in data_set]
        unique_vals = set(feat_list)
        new_gini_index = 0.0
        for value in unique_vals:
            sub_data_set = split_data_set(data_set, i, value)
            prob = len(sub_data_set) / float(len(data_set))
            new_gini_index += prob * calc_gini_index(sub_data_set)
        if new_gini_index < best_gini_index:
            best_gini_index = new_gini_index
            best_feature = i
    return best_feature, best_gini_index

 

3.4统计类别出现的次数

 

def majority_cnt(class_list):
    """
    统计类别出现的次数
    :param class_list: 类别列表
    :return sorted_class_count[0][0]: 出现次数最多的类别
    """
    class_count = {}
    for vote in class_list:
        class_count[vote] = class_count.get(vote, 0) + 1
    sorted_class_count = sorted(class_count.items(), key=lambda x: x[1], reverse=True)
    return sorted_class_count[0][0]

3.5创建决策树并使用决策树分类

 

def create_tree(data_set, labels):
    """
    创建决策树
    :param data_set: 数据集
    :param labels: 特征标签
    :return my_tree: 决策树
    """
    class_list = [example[-1] for example in data_set]
    # 如果类别完全相同则停止继续划分
    if class_list.count(class_list[0]) == len(class_list):
        return class_list[0]
    # 遍历完所有特征时返回出现次数最多的类别
    if len(data_set[0]) == 1:
        return majority_cnt(class_list)
    best_feat, _ = choose_best_feature_to_split(data_set)
    best_feat_label = labels[best_feat]
    my_tree = {best_feat_label: {}}
    del(labels[best_feat])
    feat_values = [example[best_feat] for example in data_set]
    unique_vals = set(feat_values)
    for value in unique_vals:
        sub_labels = labels[:]
        my_tree[best_feat_label][value] = create_tree(split_data_set(data_set, best_feat, value), sub_labels)
    return my_tree

def classify(input_tree, feat_labels, test_vec):
    """
    使用决策树分类
    :param input_tree: 已经生成的决策树
    :param feat_labels: 特征标签
    :param test_vec: 测试数据列表,顺序对应特征标签
    :return class_label: 分类结果
    """
    first_str = list(input_tree.keys())[0]
    second_dict = input_tree[first_str]
    feat_index = feat_labels.index(first_str)
    key = test_vec[feat_index]
    value_of_feat = second_dict.get(key, None)
    if isinstance(value_of_feat, dict):
        class_label = classify(value_of_feat, feat_labels, test_vec)
    else:
        class_label = value_of_feat
    return class_label

3.6递归添加决策树的节点和边

 

def add_tree_nodes(dot_data, tree, labels, parent_node=None, edge_label=None):
    """
    递归添加决策树节点和边
    :param dot_data: GraphViz对象
    :param tree: 决策树
    :param labels: 特征标签
    :param parent_node: 父节点名称
    :param edge_label: 边标签
    """
    first_str = list(tree.keys())[0]
    if parent_node is not None:
        dot_data.node(parent_node, first_str)
    for key in tree[first_str]:
        if isinstance(tree[first_str][key], dict):
            if edge_label is not None:
                dot_data.edge(parent_node, key, label=edge_label)
                edge_label = None
            else:
                dot_data.edge(parent_node, key)
            add_tree_nodes(dot_data, tree[first_str][key], labels, parent_node=key)
        else:
            if edge_label is not None:
                dot_data.node(key, tree[first_str][key])
                dot_data.edge(parent_node, key, label=edge_label)
            else:
                dot_data.node(key, tree[first_str][key])
                dot_data.edge(parent_node, key)

3.7用图像将决策树输出

def export_tree_as_graph(tree, labels):
    """
    将决策树输出为图像
    :param tree: 决策树
    :param labels: 特征标签
    """
    dot_data = graphviz.Digraph()
    add_tree_nodes(dot_data, tree, labels)
    dot_data.format = 'png'
    dot_data.render('decision_tree')

 

3.8引用数据集输出结果

# 加载鸢尾花数据集
iris = load_iris()
X, y = iris.data, iris.target


# 构建决策树模型
clf = DecisionTreeClassifier()
clf.fit(X, y)

# 绘制决策树
plt.figure(figsize=(10, 6))
plot_tree(clf, feature_names=iris.feature_names, class_names=iris.target_names.tolist(), filled=True)
plt.show()

3.9实验输出结果 

四、决策树算法的优劣

 

决策树算法具有以下优点:

直观易懂:决策树的模型可视化简单明了,易于解释和理解。

适用于各种类型的数据:决策树可以处理离散型和连续型特征,也能够处理多分类和回归问题。

处理特征交互和缺失值:决策树可以捕捉特征之间的交互关系,并且能够处理缺失值的情况。

然而,决策树算法也存在一些局限性:

容易过拟合:决策树倾向于过度匹配训练数据,导致在新数据上表现不佳。剪枝技术可以一定程度上缓解这个问题。

对输入数据的变化敏感:即使输入数据的微小变化,也可能导致完全不同的决策树结构。

难以处理类别数较多的特征:当特征具有大量类别时,决策树可能会产生过于复杂的模型。

决策树算法在实际应用中被广泛使用,特别适用于具有可解释性要求的场景,例如金融风控、医学诊断、推荐系统等。

五、小结和实现过程中遇到问题的解决方案

决策树算法是一种常用的机器学习算法,具有易于理解和解释、能够处理离散和连续特征、能够处理多分类问题等优点。在实验中,我总结了一些关于决策树实现的要点和经验。

1.数据预处理:在进行决策树实验之前,需要对原始数据进行预处理。包括数据清洗、缺失值处理、特征选择等步骤。这些步骤可以提高决策树模型的性能和稳定性。

2.特征选择:决策树算法对特征选择非常敏感。选择合适的特征可以提高模型的准确性和泛化能力。常用的特征选择方法包括信息增益、基尼指数和方差等。需要根据具体问题选择合适的特征选择方法。

3.剪枝处理:决策树容易过拟合,剪枝处理是防止过拟合的重要手段。可以使用预剪枝或后剪枝来提高决策树模型的泛化能力。预剪枝通过限制分裂条件来控制决策树的复杂度,后剪枝通过剪枝操作来简化已经生成的决策树结构。

4.参数调优:决策树模型有一些参数需要进行调优,例如树的深度、节点分裂的最小样本数等。通过交叉验证等方法,调整这些参数可以提高模型的性能。

5.可视化展示:决策树算法生成的模型具有可解释性,可以将生成的决策树可视化展示。这有助于了解决策树的结构和规则,并对模型的结果进行解释。

在实验中报错:

后面查资料得知在调用plot_tree函数时,class_names参数应该是一个列表或者为None。而代码中传入的是一个数组。

解决这个问题,可以将class_names参数改为一个列表形式,即class_names=iris.target_names.tolist()。

后面解决这个问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值