AI 文章摘要

决策树是一种机器学习模型,用于分类和回归,模拟人类决策过程,具有易于理解、可处理多输出、数据预处理简单等特点。构建过程包括特征选择、数据集分割、停止条件和剪枝。实战中,使用iris数据集构建决策树,基于Gini指标或信息增益划分属性,评估准确率,并与scikit-learn的DecisionTreeClassifier比较,结果显示准确率相同。阅读此文章大概需要5-10分钟。
摘要更新时间:2026-06-27 04:16

决策树简介

决策树是一种常见的机器学习模型,用于解决分类和回归问题。它模拟人类在做决策时的思维过程,通过一系列的决策节点和分支来表示数据集的决策过程。决策树的每个内部节点表示对某个属性的测试,每个分支代表一个测试结果,每个叶子节点代表一个类别标签或一个数值。

决策树的特点包括:

  1. 易于理解和解释: 决策树模型类似于人类的决策过程,因此非专业人士也能够理解和解释模型的工作原理。
  2. 可处理多输出问题: 决策树可以处理多类别输出问题,也可以处理连续型输出问题。
  3. 数据预处理简单: 决策树模型对数据的处理能力较强,不需要对数据进行过多的预处理,如归一化、标准化等。
  4. 能够处理缺失值: 决策树模型能够处理缺失值,不需要对缺失值进行填充。
  5. 适用于大规模数据集: 决策树模型适用于大规模数据集,并且在数据集包含大量属性时也能表现良好。

决策树的构建过程涉及选择最佳的特征来构建树,通常使用信息增益、基尼系数等指标来评估特征的重要性。通过递归地选择最佳特征并分裂数据集,最终构建出一棵能够对新数据进行准确预测的决策树模型。

决策树在实际应用中具有广泛的用途,包括医疗诊断、金融风险评估、客户分类等领域。它不仅能够提供准确的预测结果,还能够帮助用户理解数据背后的决策逻辑,具有很高的实用性和可解释性。

决策树的构建过程

决策树的构建是一个逐步选择特征并分裂数据集的过程,以最终构建出能够对新数据进行准确预测的决策树模型。其构建步骤如下:

  1. 特征选择 在构建决策树时,需要选择最佳的特征来作为节点进行数据集的分割。常用的特征选择指标包括信息增益(ID3算法)、增益率(C4.5算法)、基尼指数(CART算法)等。这些指标帮助决策树算法确定哪个特征最适合作为节点来划分数据集。
  2. 数据集分割 一旦选择了最佳的特征,数据集将根据该特征的取值进行分割。这个过程会持续进行,直到数据集中的所有样本都属于同一类别(对于分类问题)或直到满足某个停止条件。
  3. 停止条件 在构建决策树时,需要设定停止条件,以防止树的过度生长(过拟合)。停止条件可以是节点中样本数量的阈值、树的深度等。
  4. 剪枝 构建完成后的决策树可能存在过拟合问题,因此通常需要进行剪枝操作,以简化树的结构并提高泛化能力。剪枝可以是预剪枝(在构建树的过程中进行剪枝)或后剪枝(在树构建完成后进行剪枝)。
  5. 生成决策树模型 经过上述步骤,就可以生成一个用于分类或回归的决策树模型。该模型可以对新样本进行预测,并且具有一定的可解释性,使人们能够理解模型的决策过程。

实战演练(iris.data.txt)

内容

1)读取数据集“iris.data.txt”

2)对数据集进行训练集与测试集的随机或者不随机分割

3)构建决策树分类模型,采用二路划分,实现基于度量(包括Gini指标和信息增益)的属性划分,并迭代构建决策树

4)评估决策树分类模型,利用上一步构建的决策树,对测试集中的样本进行分类,并计算分类准确率

5)利用scikit-learn库中已有的决策树类DecisionTreeClassifier对上述的训练集进行拟合,在测试集上计算分类准确率,比较两个模型准确率

读取数据集:

data = pd.read_csv("iris.data.txt",header = 0,names=["sepal_length", "sepal_width", "petal_length", "petal_width", "class"])
print("前十条数据为:n",data.head(10))

对数据集进行分割:

X = data.drop("class",axis=1)
y = data['class']
# 分割数据集,70%训练集,30%测试集,随机分
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

#不随机分
 # 计算用于训练的样本数量
train_size = int(0.7 * len(data))
# 按照索引划分数据集
X_train = data.iloc[:train_size, :-1]  # 选择前70%的样本作为训练集的特征
y_train = data.iloc[:train_size, -1]   # 选择前70%的样本作为训练集的目标变量
X_test = data.iloc[train_size:, :-1]   # 选择后30%的样本作为测试集的特征
y_test = data.iloc[train_size:, -1]    # 选择后30%的样本作为测试集的目标变量

计算gini指标、计算信息熵:

# 定义基尼不纯度计算函数
def gini_impurity(labels):
    total_samples = len(labels)
    classes, counts = np.unique(labels, return_counts=True)
    impurity = 1 - sum((counts[i] / total_samples) ** 2 for i in range(len(classes)))
    return impurity

#定义信息熵计算函数
def entropy(labels):
    total_samples=len(labels)
    classes,counts=np.unique(labels,return_counts=True)
    entropy_val=0
    entropy_val-=sum((counts[i]/total_samples)*math.log(counts[i]/total_samples,2) for i in range(len(classes)))
    return entropy_val

构建决策树:

# 递归构建决策树,最大深度停止
def build_decision_tree(data, depth=0, max_depth=None):
    if len(data['class'].unique()) == 1 or (max_depth is not None and depth == max_depth):
        return data['class'].mode().iloc[0]   #返回频率最高

    if len(data.columns) == 1:
        return data['class'].mode().iloc[0]

    best_feature, best_threshold = find_best_split(data)

    if best_feature is None:
        return data['class'].mode().iloc[0]

    left_data, right_data = split_dataset(data, best_feature, best_threshold)

    left_su***ree = build_decision_tree(left_data, depth + 1, max_depth)
    right_su***ree = build_decision_tree(right_data, depth + 1, max_depth)

    decision_tree = {
        'feature': best_feature,
        'threshold': best_threshold,
        'left_su***ree': left_su***ree,   #左子树
        'right_su***ree': right_su***ree  #右子树
    }

    return decision_tree

样本分类:

# 分类样本
def classify_sample(sample, decision_tree):
    if isinstance(decision_tree, str):
        return decision_tree
    feature = decision_tree['feature']
    threshold = decision_tree['threshold']
    if sample[feature] <= threshold:
        return classify_sample(sample, decision_tree['left_su***ree'])
    else:
        return classify_sample(sample, decision_tree['right_su***ree'])

基本重要过程就在上述,其目标就是自己学会构建一棵决策树并训练好之后对测试数据集进行一个类别评判以得到一个准确率并与scikit-learn库中自带的决策树得到的准确率进行比较,通过上述程序得到的运行图如下:

决策树
决策树

根据运行结果图可知:自己构建的决策树准确率为百分之一百,与scikit-learn库的决策树得出的准确率相比是一样的,由此可知,自己所构建的决策树大概率是比较好的。

全部代码在下:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
import math

while True:
    # 读取数据集
    data = pd.read_csv('iris.data.txt', header=0, names=["sepal_length", "sepal_width", "petal_length", "petal_width", "class"])
    print("前十条数据为:n",data.head(10))

    # 特征和目标变量,预测花的类别,X是特征,y是目标
    X = data.drop('class', axis=1)
    y = data['class']
    
    print("1、随机分 2、不随机分 请选择:")
    a = int(input())
    if a == 1: 
        # 分割数据集,70%训练集,30%测试集,随机分
        X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
    elif a == 2:
        # 计算用于训练的样本数量
        train_size = int(0.7 * len(data))

        # 按照索引划分数据集
        X_train = data.iloc[:train_size, :-1]  # 选择前70%的样本作为训练集的特征
        y_train = data.iloc[:train_size, -1]   # 选择前70%的样本作为训练集的目标变量
        X_test = data.iloc[train_size:, :-1]   # 选择后30%的样本作为测试集的特征
        y_test = data.iloc[train_size:, -1]    # 选择后30%的样本作为测试集的目标变量

    print("训练集大小:", X_train.shape)
    print("测试集大小:", X_test.shape)
    print("测试集为:n",X_test)

    # 定义数据集划分函数
    def split_dataset(data, feature, threshold):
        left_indices = data[feature] <= threshold   #左边小于等于阈值
        right_indices = data[feature] > threshold   #右边大于阈值
        left_data = data[left_indices]
        right_data = data[right_indices]
        return left_data, right_data

    print("1、Gini系数划分 2、信息增益划分 请选择:")
    b = int(input())
    if b == 1:
        # 定义基尼不纯度计算函数
        def gini_impurity(labels):
            total_samples = len(labels)
            classes, counts = np.unique(labels, return_counts=True)
            impurity = 1 - sum((counts[i] / total_samples) ** 2 for i in range(len(classes)))
            return impurity

        # 寻找最佳分裂特征和阈值(gini)
        def find_best_split(data):
            best_gini = float('inf')
            best_feature = None    #最好的特性
            best_threshold = None  #最好的阈值

            for feature in data.columns[:-1]:   #排除最后一个属性"类别"
                thresholds = data[feature].unique()  #取唯一值
                for threshold in thresholds:
                    left_data, right_data = split_dataset(data, feature, threshold)
                    if len(left_data) == 0 or len(right_data) == 0:
                        continue
                    gini = (len(left_data) / len(data)) * gini_impurity(left_data['class']) + 
                        (len(right_data) / len(data)) * gini_impurity(right_data['class'])  #gini加权平均
                    if gini < best_gini:
                        best_gini = gini
                        best_feature = feature
                        best_threshold = threshold
            print(f"按 {best_feature} 特征划分,划分阈值为: {best_threshold}")
            return best_feature, best_threshold
    elif b == 2:
        #定义信息熵计算函数
        def entropy(labels):
            total_samples=len(labels)
            classes,counts=np.unique(labels,return_counts=True)
            entropy_val=0
            entropy_val-=sum((counts[i]/total_samples)*math.log(counts[i]/total_samples,2) for i in range(len(classes)))
            return entropy_val

        # 寻找最佳分裂特征和阈值(基于信息增益)
        def find_best_split(data):
            best_info_gain = float('-inf')
            best_feature = None
            best_threshold = None

            for feature in data.columns[:-1]:
                thresholds = data[feature].unique()
                for threshold in thresholds:
                    left_data, right_data = split_dataset(data, feature, threshold)
                    if len(left_data) == 0 or len(right_data) == 0:
                        continue
                    info_gain = entropy(data['class']) - ((len(left_data) / len(data)) * entropy(left_data['class']) +
                                                        (len(right_data) / len(data)) * entropy(right_data['class']))
                    if info_gain > best_info_gain:
                        best_info_gain = info_gain
                        best_feature = feature
                        best_threshold = threshold

            print(f"按 {best_feature} 特征划分,划分阈值为: {best_threshold}")
            return best_feature, best_threshold

    # 递归构建决策树,最大深度停止
    def build_decision_tree(data, depth=0, max_depth=None):
        if len(data['class'].unique()) == 1 or (max_depth is not None and depth == max_depth):
            return data['class'].mode().iloc[0]   #返回频率最高

        if len(data.columns) == 1:
            return data['class'].mode().iloc[0]

        best_feature, best_threshold = find_best_split(data)

        if best_feature is None:
            return data['class'].mode().iloc[0]

        left_data, right_data = split_dataset(data, best_feature, best_threshold)

        left_su***ree = build_decision_tree(left_data, depth + 1, max_depth)
        right_su***ree = build_decision_tree(right_data, depth + 1, max_depth)

        decision_tree = {
            'feature': best_feature,
            'threshold': best_threshold,
            'left_su***ree': left_su***ree,   #左子树
            'right_su***ree': right_su***ree  #右子树
        }

        return decision_tree

    # 分类样本
    def classify_sample(sample, decision_tree):
        if isinstance(decision_tree, str):
            return decision_tree
        feature = decision_tree['feature']
        threshold = decision_tree['threshold']
        if sample[feature] <= threshold:
            return classify_sample(sample, decision_tree['left_su***ree'])
        else:
            return classify_sample(sample, decision_tree['right_su***ree'])

    # 评估模型性能
    def evaluate_model(test_data, decision_tree):
        correct_predictions = 0
        total_samples = len(test_data)
        for _, sample in test_data.iterrows():
            predicted_class = classify_sample(sample, decision_tree)
            if predicted_class == sample['class']:
                correct_predictions += 1
        accuracy = correct_predictions / total_samples
        return accuracy

    # 保存决策树为.dot文件
    def save_tree_as_dot(decision_tree, filename='decision_tree.dot'):
        def traverse(node, file):
            if isinstance(node, dict):
                file.write(f"{node['feature']} <= {node['threshold']};n")
                file.write(f"{node['feature']} > {node['threshold']};n")
                traverse(node['left_su***ree'], file)
                traverse(node['right_su***ree'], file)
            else:
                file.write(f"{node};n")

        with open(filename, 'w') as f:
            f.write('digraph decision_tree {n')
            traverse(decision_tree, f)
            f.write('}')

    # 使用训练集构建决策树
    decision_tree_custom = build_decision_tree(pd.concat([X_train, y_train], axis=1))
    print("从头开始构建的决策树模型:", decision_tree_custom)

    # 保存决策树为.dot文件
    save_tree_as_dot(decision_tree_custom, filename='decision_tree_custom.dot')

    # 评估从头开始构建的决策树模型
    accuracy_custom_tree = evaluate_model(X_test.join(y_test), decision_tree_custom)
    print("从头开始构建的决策树模型准确率:", accuracy_custom_tree)

    # 使用scikit-learn的DecisionTreeClassifier构建模型
    sklearn_tree = DecisionTreeClassifier()
    sklearn_tree.fit(X_train, y_train)

    # 使用scikit-learn的决策树模型评估性能
    accuracy_sklearn_tree = sklearn_tree.score(X_test, y_test)
    print("使用scikit-learn的决策树模型准确率:", accuracy_sklearn_tree)

    print("是否继续:(y/n)")
    c = input()
    if c.lower() == 'n':
        break