python----决策树

决策树是一种直观且功能强大的机器学习算法,广泛应用于分类和回归任务。本文将深入探讨构建决策树所需的数学基础,详细讲解决策树的工作原理,并通过 Python 实现一个完整的决策树分类器,帮助你从理论到实践全面掌握这一算法。

决策树算法的核心:

用层层递进的问题把数据不断细分,直到每个子集足够‘纯净’

绘制决策树我们需要考虑的问题:

  1. 哪个节点作为根节点?哪些节点作为中间节点,那些节点作为叶子节点。
  2. 节点如何分裂
  3. 节点分裂标准的依据

决策树的数学基础

决策树分类标准:

  1. ID3算法(最不重要,几乎不用)
  2. C4.5算法(依赖于ID3算法)
  3. CART算法

要理解决策树算法,我们需要掌握几个关键的数学概念,这些概念构成了决策树构建的理论基础。

1.ID3: 

ID3 算法的核心数学基础是信息熵和信息增益。为了让这些概念更易于理解,我们将通过一个具体的例子来详细解释它们的计算过程。

示例数据集

我们将使用一个简单的 "是否适合打网球" 的数据集,包含 4 个特征和 14 个样本:

编号天气温度湿度风速是否打球
1晴朗
2晴朗
3多云
4下雨适中
5下雨凉爽正常
6下雨凉爽正常
7多云凉爽正常
8晴朗适中
9晴朗凉爽正常
10下雨适中正常
11晴朗适中正常
12多云适中
13多云正常
14下雨适中

在这个数据集中,我们的目标是根据天气、温度、湿度和风速这四个特征来预测是否是否适合打网球。

2.信息熵 (Entropy) 的计算

信息熵用于衡量数据集的不确定性或混乱程度。对于整个数据集,我们先计算其信息熵。

在 14 个样本中:

  • 9 个样本为 "是"(适合打球)
  • 5 个样本为 "否"(不适合打球)

信息熵公式:

pi:表示该数据的概率

计算过程:

  • p(是) = 9/14 ≈ 0.643
  • p(否) = 5/14 ≈ 0.357

H(S) = -[p(是) *log2(p(是)) + p(否) *log2(p(否))

把数据带入得出为 0.939

整个数据集的信息熵为 0.939,表明存在一定的不确定性。

 信息增益 (Information Gain) 的计算

信息增益用于衡量使用某个特征划分数据集后,不确定性减少的程度。我们以 "天气" 特征为例计算其信息增益。

"天气" 特征有三个可能的取值:晴朗、多云、下雨

步骤 1:计算每个取值子集的熵

  1. 天气 = 晴朗(共 5 个样本):

    • 2 个 "是",3 个 "否"
    • p(是)=2/5=0.4,p(否)=3/5=0.6
    • H(晴朗) = -[0.4 *log2(0.4) + 0.6 *log2(0.6)]
    • H(晴朗) = -[0.4 * (-1.322) + 0.6 *(-0.737)]
    • H(晴朗) = -[(-0.529) + (-0.442)] = 0.971
  2. 天气 = 多云(共 4 个样本):

    • 4 个 "是",0 个 "否"
    • p(是)=1.0,p(否)=0.0
    • H(多云) = -[1.0 *log2(1.0) + 0.0 *log_2(0.0)] = 0\
  3. 天气 = 下雨(共 5 个样本):

    • 3 个 "是",2 个 "否"
    • p(是)=3/5=0.6,p(否)=2/5=0.4
    • H(下雨) = -[0.6 \log2(0.6) + 0.4 \log2(0.4)] = 0.971\)

步骤 2:计算条件熵

条件熵是所有子集熵的加权平均: H(S|天气) = 5/14H(晴朗) + 4/14H(多云) + 5/14H(下雨)

(H(S|天气) = 0.347 + 0 + 0.347 = 0.694

步骤 3:计算信息增益

信息增益 = 原始熵 - 条件熵: IG(S, 天气) = H(S) - H(S|天气)

IG(S, 天气) = 0.939 - 0.694 = 0.245

3. 比较不同特征的信息增益

为了选择最佳分裂特征,我们需要计算所有特征的信息增益。

温度特征的信息增益

温度有三个取值:热、适中、凉爽

  • 热:4 个样本(2 是,2 否),H=1.0
  • 适中:6 个样本(4 是,2 否),H=0.918
  • 凉爽:4 个样本(3 是,1 否),H=0.811

条件熵: (H(S|温度) = 4\14*  1.0 + 6\14*0.918 + 4\14* 0.811 = 0.892

信息增益: IG(S, 温度) = 0.939 - 0.892 = 0.047

湿度特征的信息增益

湿度有两个取值:高、正常

  • 高:7 个样本(3 是,4 否),H=0.985
  • 正常:7 个样本(6 是,1 否),H=0.592

条件熵: H(S|湿度) = 7\14* 0.985 + 7\14* 0.592 = 0.788

信息增益:IG(S, 湿度) = 0.939 - 0.788 = 0.151

风速特征的信息增益

风速有两个取值:弱、强

  • 弱:8 个样本(6 是,2 否),H=0.811
  • 强:6 个样本(3 是,3 否),H=1.0

条件熵:

H(S|风速) = 8\14*0.811 6\14*1.0 = 0.892

信息增益: IG(S, 风速) = 0.939 - 0.892 = 0.047

4. 结果比较与特征选择

各特征的信息增益:

  • 天气:0.245
  • 湿度:0.151
  • 温度:0.047
  • 风速:0.047

根据 ID3 算法,我们选择信息增益最大的特征作为根节点的分裂特征。在这个例子中,"天气" 特征的信息增益最大(0.245),因此被选为第一个分裂特征。

这意味着使用 "天气" 特征划分数据能最大程度地降低不确定性,是当前最有效的分类依据。

5. 递归计算过程

在选择 "天气" 作为根节点后,我们需要对每个子节点递归地进行相同的计算:

  1. 天气 = 晴朗的子集中:

    • 我们需要计算剩余特征(温度、湿度、风速)的信息增益
    • 选择信息增益最大的特征作为该节点的分裂特征
  2. 天气 = 多云的子集中:

    • 所有样本都属于 "是" 类别,熵为 0
    • 这是一个叶节点,不需要进一步分裂
  3. 天气 = 下雨的子集中:

    • 同样计算剩余特征的信息增益
    • 选择最佳特征继续分裂

通过这种递归计算,我们可以构建出完整的决策树。

 二、C4.5算法

C4.5 算法是 Ross Quinlan 在 ID3 算法基础上提出的改进版本,解决了 ID3 算法的诸多局限性。

C4.5 算法的核心改进 :

  1. 使用信息增益率替代信息增益:解决了 ID3 偏向于选择取值较多的特征的问题
  2. 支持连续值处理:能直接处理连续型特征,无需手动离散化
  3. 规则提取:可以将决策树转换为易于理解的规则集

 C4.5计算公式为:

 IV(A):特征A的固有值

计算方式如下(在ID3的基础上实例):

前面计算出了各特征信息增益:

  • 数据集总熵 H(S) = 0.939
  • 天气的信息增益 IG(S, 天气) = 0.245
  • 湿度的信息增益 IG(S, 湿度) = 0.151
  • 温度的信息增益 IG(S, 温度) = 0.047
  • 风速的信息增益 IG(S, 风速) = 0.047

 计算各特征的固有值(IV):

天气特征(3 个取值:晴朗、多云、下雨):

  1. 晴朗:5/14,多云:4/14,下雨:5/14
  2. IV(天气) = -[5/14 *log2(5/14) + 4/14 *log2(4/14) + 5/14 *log2(5/14)]
  3. IV(天气) = -[5/14 *(-1.485) + 4/14 *(-1.807) + 5/14 * (-1.485)]\)
  4. IV(天气) = -[(-0.530) + (-0.516) + (-0.530)] = 1.576

天气:GR=0.245/1.576≈0.155

湿度特征(2 个取值:高、正常):

  1. 高:7/14,正常:7/14
  2. IV(湿度) = -[7/14 *log2(7/14) + 7/14 *log2(7/14)]
  3. IV(湿度) = -[0.5 *(-1) + 0.5 *(-1)] = 1.0

 湿度:\(GR = 0.151 / 1.0 ≈ 0.151

温度特征(3 个取值:热、适中、凉爽):

  1. 热:4/14,适中:6/14,凉爽:4/14
  2. IV(温度) = -[4/14 *log2(4/14) + 6/14 *log_2(6/14) + 4/14 *log_2(4/14)]
  3. IV(温度) = 1.556

 温度:\(GR = 0.047 / 1.556 ≈ 0.030\)

风速特征(2 个取值:弱、强):

  1. 弱:8/14,强:6/14 
  2. IV(风速) = -[8/14 *log2(8/14) + 6/14 *log2(6/14)]
  3. IV(风速) = 0.985

 风速:GR = 0.047 / 0.985 ≈ 0.048

信息增益率排序:温度(1.556)>天气(1.55)>适度(0.151)>风速(0.048)

 三、CART决策树

CART(Classification and Regression Trees,分类与回归树)是一种功能强大的决策树算法,由 Breiman 等人在 1984 年提出。与 ID3 和 C4.5 不同,CART 不仅可以处理分类问题,还能有效解决回归任务,是应用最广泛的决策树算法之一。

1. 基尼不纯度(Gini Impurity)

CART 在分类问题中使用基尼不纯度作为分裂标准,衡量数据集的纯度:

S:数据集

c:类别总数

pi:第i类样本在S中所占的比例

2. 基尼增益(Gini Gain)

CART 使用基尼增益来选择最佳分裂特征:

 

GG(S, A): 是用特征A划分数据集S获得的基尼增益

G(S): 是原始数据集的基尼不纯度

Sv: 是S中特征A取值为v的样本子集

用具体例子计算基尼不纯度

步骤 1:计算整个数据集的基尼不纯度

在 14 个样本中:

9 个 "是",5 个 "否"

p(是) = 9/14 ≈ 0.643,p(否) = 5/14 ≈ 0.357

G(S) = 1 - (p(是)^2 + p(否)^2)

(G(S) = 1 - (0.643^2 + 0.357^2)

(G(S) = 1 - (0.413 + 0.127) = 1 - 0.540 = 0.460\)

步骤 2:计算 "天气" 特征的基尼增益

  1. 天气 = 晴朗(5 个样本:2 是,3 否):

    • G(晴朗) = 1 - (0.4^2 + 0.6^2) = 1 - (0.16 + 0.36) = 0.48
  2. 天气 = 多云(4 个样本:4 是,0 否):

    • G(多云) = 1 - (1.0^2 + 0.0^2) = 0
  3. 天气 = 下雨(5 个样本:3 是,2 否):

    • G(下雨) = 1 - (0.6^2 + 0.4^2) = 1 - (0.36 + 0.16) = 0.48
  4. 计算基尼增益

      GG(S, 天气) = G(S) - [5/14* G(晴朗) + 4/14 *G(多云) + 5/14 * G(下雨)]

      GG(S, 天气) = 0.460 - [5/14 *0.48 + 4/14 *0 + 5/14*0.48]

      GG(S, 天气) = 0.460 - 0.343 = 0.117

步骤 3:比较不同特征的基尼增益

通过类似计算,我们可以得到所有特征的基尼增益:

  • 天气:0.117
  • 湿度:0.096
  • 温度:0.016
  • 风速:0.018

CART 会选择基尼增益最大的 "天气" 特征作为根节点的分裂特征。

决策树剪枝: 

 决策树算法在构建过程中容易出现过拟合现象像 —— 模型在训练数据上表现优异,但在新数据上泛化能力差。剪枝(Pruning)是解决这一问题的核心技术,通过移除决策树中不必要的分支,简化模型结构,提高泛化能力。为了防止过拟合

剪枝的数学定义

对于一个决策树T,其成本复杂度定义为:

其中:

  • C(T) 是树T在训练数据上的误差(分类为错分率,回归为平方误差)
  • |T| 是树T的叶节点数量(衡量树的复杂度)
  • alpha 是正则化参数,控制复杂度惩罚的强度

 

 构建决策树:

项目背景与目标

电信行业客户流失率高一直是困扰运营商的难题。据统计,获取新客户的成本是保留老客户的 5-10 倍。构建准确的客户流失预测模型,能够帮助企业:

  • 提前识别高流失风险客户
  • 制定针对性的挽留策略
  • 降低客户获取成本,提高企业收益

本项目将使用决策树算法构建客户流失预测模型,决策树的优势在于模型解释性强,能够清晰展示影响客户流失的关键因素,便于业务部门理解和应用。

完整代码实现与解析

1. 环境准备与库导入

首先,我们需要导入项目所需的 Python 库:

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.model_selection import train_test_split, cross_val_score
from imblearn.over_sampling import SMOTE
from sklearn import metrics
import seaborn as sns

# 设置中文显示
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

 这些库涵盖了数据处理(pandas)、数值计算(numpy)、可视化(matplotlib、seaborn)、机器学习模型(sklearn)以及不平衡数据处理(imblearn)等功能。

2. 数据加载与初步探索

数据是建模的基础,首先我们需要加载数据并进行初步探索:

# 读取数据
data = pd.read_excel("电信客户流失数据.xlsx")

# 查看数据基本信息
print(f"数据集形状: {data.shape}")
print("\n数据集前5行:")
print(data.head())

# 查看流失状态分布
churn_distribution = data['流失状态'].value_counts()
print("\n流失状态分布:")
print(churn_distribution)
print(f"流失率: {churn_distribution[1]/len(data):.2%}")

# 绘制流失状态分布图
plt.figure(figsize=(8, 6))
sns.countplot(x='流失状态', data=data)
plt.title('客户流失状态分布')
plt.xlabel('流失状态 (0:未流失, 1:已流失)')
plt.ylabel('客户数量')
plt.show()

这一步的主要目的是:

  • 确认数据加载正确
  • 了解数据的基本结构和特征
  • 分析目标变量(流失状态)的分布情况,判断是否存在类别不平衡问题

在电信客户流失数据中,通常流失客户占比会低于未流失客户,形成不平衡数据集,这一点我们后续需要专门处理。

3. 数据预处理与划分

数据预处理是建模过程中至关重要的一步,直接影响模型性能:

# 划分特征与目标变量
X = data.drop('流失状态', axis=1)
y = data['流失状态']

# 区分类别特征和数值特征
categorical_features = X.select_dtypes(include=['object', 'category']).columns
numerical_features = X.select_dtypes(include=['int64', 'float64']).columns

print(f"类别特征: {list(categorical_features)}")
print(f"数值特征: {list(numerical_features)}")

# 对类别特征进行独热编码
if not categorical_features.empty:
    X = pd.get_dummies(X, columns=categorical_features, drop_first=True)
    print(f"独热编码后特征数量: {X.shape[1]}")

# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)

print(f"训练集大小: {x_train.shape[0]}, 测试集大小: {x_test.shape[0]}")

# 处理类别不平衡
print("\n处理类别不平衡...")
sm = SMOTE(random_state=42)
x_train_res, y_train_res = sm.fit_resample(x_train, y_train)

print(f"重采样后训练集类别分布: {pd.Series(y_train_res).value_counts()}")

 

数据预处理主要包括:

  1. 特征与目标变量分离:将 "流失状态" 作为目标变量,其余作为特征变量
  2. 特征类型区分:区分类别特征和数值特征,因为决策树虽然可以处理两种类型特征,但类别特征通常需要编码
  3. 独热编码:将类别特征转换为数值形式,使模型能够处理
  4. 数据集划分:将数据分为训练集(70%)和测试集(30%),使用stratify=y确保分层抽样,保持与原数据相同的类别比例
  5. 类别不平衡处理:使用 SMOTE 算法对训练集进行过采样,解决流失客户样本少的问题

4. 决策树参数调优

决策树模型的性能很大程度上依赖于超参数的选择,我们使用网格搜索进行参数优化:

# 定义参数搜索范围
max_depth_choose = [3, 4, 5, 6, 7, 8, 9, 10]
min_samples_split_choose = [2, 3, 4, 5, 6, 7, 8, 9, 10]
min_samples_leaf_choose = [1, 2, 3, 4, 5]

# 存储参数和对应的分数
scores = []
params_list = []

# 网格搜索
print("开始参数网格搜索(可能需要几分钟)...")
for depth in max_depth_choose:
    for split in min_samples_split_choose:
        for leaf in min_samples_leaf_choose:
            # 创建决策树模型
            dtr = DecisionTreeClassifier(
                max_depth=depth,
                min_samples_split=split,
                min_samples_leaf=leaf,
                random_state=42
            )
            
            # 5折交叉验证,使用recall作为评分指标
            cv_scores = cross_val_score(
                dtr, x_train_res, y_train_res, 
                cv=5, scoring='recall'
            )
            
            # 存储结果
            scores.append(np.mean(cv_scores))
            params_list.append((depth, split, leaf))

# 找到最佳参数
best_idx = np.argmax(scores)
best_params = params_list[best_idx]
best_score = scores[best_idx]

print(f"最佳参数: 最大深度={best_params[0]}, 最小分裂样本数={best_params[1]}, 最小叶节点样本数={best_params[2]}")
print(f"最佳交叉验证召回率: {best_score:.4f}")

这里我们选择了三个关键参数进行优化:

  • max_depth:树的最大深度,控制树的复杂度,防止过拟合
  • min_samples_split:节点分裂所需的最小样本数
  • min_samples_leaf:叶节点所需的最小样本数

在客户流失预测中,我们更关注召回率(recall),即尽可能多地识别出真实的流失客户,因此使用召回率作为评分指标。通过 5 折交叉验证评估不同参数组合的性能,选择表现最佳的参数。

5. 训练最佳模型

使用找到的最佳参数训练最终模型:

dtr_best = DecisionTreeClassifier(
    max_depth=best_params[0],
    min_samples_split=best_params[1],
    min_samples_leaf=best_params[2],
    random_state=42
)

# 拟合模型
dtr_best.fit(x_train_res, y_train_res)

6. 模型评估

模型训练完成后,需要全面评估其性能:

# 在训练集上的预测
y_train_pred = dtr_best.predict(x_train_res)
# 在测试集上的预测
y_test_pred = dtr_best.predict(x_test)

# 训练集评估报告
print("\n训练集分类报告:")
print(metrics.classification_report(y_train_res, y_train_pred))

# 测试集评估报告
print("\n测试集分类报告:")
print(metrics.classification_report(y_test, y_test_pred))

# 计算并绘制混淆矩阵
def plot_confusion_matrix(y_true, y_pred, title):
    cm = metrics.confusion_matrix(y_true, y_pred)
    plt.figure(figsize=(8, 6))
    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
                xticklabels=['未流失', '已流失'],
                yticklabels=['未流失', '已流失'])
    plt.title(title)
    plt.xlabel('预测结果')
    plt.ylabel('实际结果')
    plt.show()

plot_confusion_matrix(y_train_res, y_train_pred, '训练集混淆矩阵')
plot_confusion_matrix(y_test, y_test_pred, '测试集混淆矩阵')

 

模型评估主要关注以下指标:

  • 精确率(Precision):预测为流失的客户中,实际流失的比例
  • 召回率(Recall):实际流失的客户中,被正确预测的比例
  • F1 分数:精确率和召回率的调和平均
  • 准确率(Accuracy):总体预测正确的比例

通过对比训练集和测试集的表现,可以判断模型是否过拟合。混淆矩阵则直观展示了四类预测结果:真正例(TP)、假正例(FP)、真负例(TN)、假负例(FN)。

7. 特征重要性分析

决策树的一大优势是可以解释特征的重要性:

# 获取特征重要性
feature_importance = pd.DataFrame({
    '特征': X.columns,
    '重要性': dtr_best.feature_importances_
})

# 按重要性排序
feature_importance = feature_importance.sort_values('重要性', ascending=False)
print("前10个最重要的特征:")
print(feature_importance.head(10))

# 绘制特征重要性条形图
plt.figure(figsize=(12, 8))
sns.barplot(x='重要性', y='特征', data=feature_importance.head(10))
plt.title('特征重要性 Top 10')
plt.tight_layout()
plt.show()

 

特征重要性分析能够揭示影响客户流失的关键因素,例如:

  • 月消费金额可能是重要因素,高消费客户可能更容易流失
  • 合约期限长短可能影响流失率,短期合约客户更易流失
  • 客户服务投诉次数可能与流失正相关

这些 insights 可以直接指导业务部门制定针对性的客户挽留策略。

8. 决策树可视化

为了更直观地理解模型决策过程,我们可以可视化决策树:

# 文本形式展示决策树(前5层)
tree_rules = export_text(dtr_best, feature_names=list(X.columns), max_depth=5)
print("决策树规则(前5层):")
print(tree_rules)

# 图形形式展示决策树
plt.figure(figsize=(30, 20))
plot_tree(
    dtr_best,
    feature_names=list(X.columns),
    class_names=['未流失', '已流失'],
    filled=True,
    rounded=True,
    fontsize=10
)
plt.title('电信客户流失预测决策树')
plt.tight_layout()
plt.savefig('telecom_churn_tree.png', dpi=300, bbox_inches='tight')
print("决策树已保存为 'telecom_churn_tree.png' 文件")
plt.show()

 

决策树可视化有两种形式:

  • 文本形式:展示决策规则,便于程序化理解
  • 图形形式:直观展示决策路径和节点分裂条件

通过决策树,我们可以清晰看到模型如何根据客户特征一步步判断其流失风险。

9. 参数影响分析

最后,我们分析关键参数对模型性能的影响:

# 分析最大深度对模型性能的影响
depth_scores = {}
for depth in max_depth_choose:
    dtr = DecisionTreeClassifier(
        max_depth=depth,
        min_samples_split=best_params[1],
        min_samples_leaf=best_params[2],
        random_state=42
    )
    scores = cross_val_score(dtr, x_train_res, y_train_res, cv=5, scoring='recall')
    depth_scores[depth] = np.mean(scores)

plt.figure(figsize=(10, 6))
plt.plot(list(depth_scores.keys()), list(depth_scores.values()), marker='o')
plt.title('最大深度对模型召回率的影响')
plt.xlabel('最大深度')
plt.ylabel('交叉验证召回率')
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

这个分析帮助我们理解:

  • 随着树深度增加,模型性能如何变化
  • 是否存在一个最佳平衡点,过深的树是否会导致过拟合
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值