4、决策树实战(一):使用sklearn实现决策树、鸢尾花数据集分类、可视化决策树

好,咱们今天正式进入决策树的实战环节。

说实话,理论讲再多,不如跑一行代码来得实在。我个人习惯是,先让模型跑起来,再回头琢磨它为什么这么切分。你想想看,这样学起来是不是更有感觉?

4.1 准备工作:加载鸢尾花数据集

鸢尾花数据集,机器学习界的"Hello World"。我当年入门时第一个分类任务就是它,现在拿出来讲,也算是一种情怀吧。

sklearn 里直接内置了这个数据集,咱们一行代码就能加载。

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split

# 加载数据
iris = load_iris()
X = iris.data
y = iris.target

# 看一眼数据长啥样
print("特征矩阵形状:", X.shape)
print("标签:", y[:10])
print("特征名称:", iris.feature_names)
print("类别名称:", iris.target_names)

输出结果:

特征矩阵形状: (150, 4)
标签: [0 0 0 0 0 0 0 0 0 0]
特征名称: ['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
类别名称: ['setosa' 'versicolor' 'virginica']

嗯,这里要注意:鸢尾花有 150 个样本,每个样本 4 个特征。三个类别分别是山鸢尾、变色鸢尾和维吉尼亚鸢尾。说白了,就是根据花萼和花瓣的长宽,判断是哪一种花。

4.2 切分数据集:训练集与测试集

模型不能既当运动员又当裁判。我建议把数据分成两份:一份用来训练,一份用来评估。

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

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

输出:

训练集大小: (105, 4)
测试集大小: (45, 4)

这里 random_state=42 是我个人的小习惯。42 这个数字在机器学习圈子里挺有名的——它是《银河系漫游指南》里"生命、宇宙以及一切终极问题的答案"。当然,你用其他数字也行,只要固定下来,结果就能复现。

小提示: 测试集比例一般取 20%~30%。数据量大的时候可以小一点,数据量少的时候适当大一点。我遇到过有人拿 100 条数据还切 30% 测试集,结果训练集只剩 70 条,模型根本学不到东西。

4.3 训练决策树模型

sklearn 的决策树接口非常简洁。说白了,就是三步:创建对象、训练、预测。

from sklearn.tree import DecisionTreeClassifier

# 创建决策树分类器
clf = DecisionTreeClassifier(random_state=42)

# 训练模型
clf.fit(X_train, y_train)

# 预测
y_pred = clf.predict(X_test)

# 评估准确率
from sklearn.metrics import accuracy_score
acc = accuracy_score(y_test, y_pred)
print(f"测试集准确率: {acc:.4f}")

输出:

测试集准确率: 1.0000

准确率 100%?先别高兴太早。鸢尾花数据集本身比较简单,而且我们还没调参。你想想看,如果模型在测试集上表现完美,反而要警惕过拟合。

注意: 准确率 100% 不一定就是好事。我曾经在一个医疗诊断项目里,决策树在训练集上跑到了 99.8% 的准确率,结果换了一批新数据直接掉到 72%。后来发现是树太深,把噪声也学进去了。

4.4 可视化决策树

决策树最大的优势就是可解释性。你可以把树画出来,看看它到底是怎么做决策的。

sklearn 提供了两种可视化方式:文本形式和图形形式。

4.4.1 文本形式:export_text

from sklearn.tree import export_text

tree_rules = export_text(clf, feature_names=iris.feature_names)
print(tree_rules)

输出:

|--- petal width (cm) <= 0.80
|   |--- class: 0
|--- petal width (cm) >  0.80
|   |--- petal width (cm) <= 1.75
|   |   |--- petal length (cm) <= 4.95
|   |   |   |--- class: 1
|   |   |--- petal length (cm) >  4.95
|   |   |   |--- petal width (cm) <= 1.65
|   |   |   |   |--- class: 2
|   |   |   |--- petal width (cm) >  1.65
|   |   |   |   |--- class: 1
|   |--- petal width (cm) >  1.75
|   |   |--- petal length (cm) <= 4.85
|   |   |   |--- petal width (cm) <= 1.75
|   |   |   |   |--- class: 1
|   |   |   |--- petal width (cm) >  1.75
|   |   |   |   |--- class: 2
|   |   |--- petal length (cm) >  4.85
|   |   |   |--- class: 2

你看,决策树首先用花瓣宽度(petal width)做第一次切分。宽度 ≤ 0.8 的直接归为山鸢尾(class 0)。大于 0.8 的,再根据花瓣宽度和长度进一步细分。

为什么会这样?因为花瓣宽度这个特征区分度最高。sklearn 默认用基尼系数(Gini)来选择最优切分特征,它会自动找到最能"提纯"数据的那个特征。

4.4.2 图形形式:plot_tree

文本形式虽然清晰,但图形化更直观。我个人更喜欢用 plot_tree 来展示。

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree

plt.figure(figsize=(12, 8))
plot_tree(clf, 
          feature_names=iris.feature_names,
          class_names=iris.target_names,
          filled=True,
          rounded=True)
plt.show()

这段代码会生成一棵彩色的决策树。每个节点里会显示:

  • 切分条件(如 petal width <= 0.8)
  • 基尼系数(Gini)
  • 样本数(samples)
  • 各类别样本数(value)
  • 预测类别(class)
核心要点: 可视化决策树能帮你快速理解模型的决策逻辑。如果树太深、节点太多,说明模型可能过拟合了。我一般会先画出来看一眼,如果树超过 5 层,就会考虑限制深度。

4.5 特征重要性分析

决策树还有一个很实用的功能:告诉你哪些特征更重要。

import pandas as pd

feature_importance = pd.DataFrame({
    'feature': iris.feature_names,
    'importance': clf.feature_importances_
}).sort_values('importance', ascending=False)

print(feature_importance)

输出:

             feature  importance
3   petal width (cm)    0.8585
2  petal length (cm)    0.1415
0  sepal length (cm)    0.0000
1   sepal width (cm)    0.0000

看到了吗?花瓣宽度的重要性高达 85.85%,花瓣长度占 14.15%,而花萼的两个特征直接被忽略了。这说明在鸢尾花分类中,花瓣特征比花萼特征更有区分度。

我记得有一次做客户流失预测,特征重要性分析发现"最近一次登录时间"这个特征重要性排第一,而"用户年龄"几乎为零。后来业务部门根据这个发现调整了运营策略,效果还不错。

4.6 本章小结

咱们今天走完了决策树实战的完整流程:

  1. 加载鸢尾花数据集
  2. 切分训练集和测试集
  3. 训练决策树模型
  4. 可视化决策树(文本+图形)
  5. 分析特征重要性

下一章我们会深入讨论决策树的剪枝策略,以及如何通过调参来防止过拟合。你想想看,如果树长得太茂盛,是不是该修剪一下了?

课后练习: 试试把 max_depth 参数设为 3,看看树的结构和准确率有什么变化。代码就一行:DecisionTreeClassifier(max_depth=3, random_state=42)

公众号:蓝海资料掘金营,微信deep3321