共计 2792 个字符,预计需要花费 7 分钟才能阅读完成。
一、为什么选择决策树?
决策树是机器学习中最直观的算法之一,它的最大优势在于模型可解释性强——最终的决策逻辑可以像流程图一样呈现。CART(Classification and Regression Trees)作为经典实现,相比 ID3/C4.5 有以下特点:

- 支持连续值和离散值混合处理
- 使用基尼系数 (Gini Index) 代替信息增益
- 二叉树结构更节省计算资源
- 天然支持回归任务
二、准备西瓜数据集
UCI 西瓜数据集是经典的入门练习数据,包含 17 个样本和以下特征:
- 离散特征:
- 色泽(青绿 / 乌黑 / 浅白)
- 根蒂(蜷缩 / 稍蜷 / 硬挺)
-
敲声(浊响 / 沉闷 / 清脆)
-
连续特征:
- 密度(0- 1 之间的浮点数)
-
含糖率(百分比)
-
标签:好瓜(是 / 否)
数据预处理步骤
import pandas as pd
from sklearn.preprocessing import LabelEncoder
# 手工构建数据集
watermelon_data = [['青绿', '蜷缩', '浊响', 0.697, 0.460, '是'],
# ... 其他 16 条数据
]
# 转换为 DataFrame
columns = ['色泽', '根蒂', '敲声', '密度', '含糖率', '好瓜']
df = pd.DataFrame(watermelon_data, columns=columns)
# 离散特征编码
le = LabelEncoder()
for col in ['色泽', '根蒂', '敲声', '好瓜']:
df[col] = le.fit_transform(df[col])
三、核心算法实现
1. 基尼系数计算
基尼系数反映数据集的「不纯度」,计算公式:
$$Gini(D) = 1 – \sum_{k=1}^{K}(\frac{|C_k|}{|D|})^2$$
Python 实现:
def gini_index(y):
"""计算基尼系数"""
_, counts = np.unique(y, return_counts=True)
probabilities = counts / len(y)
return 1 - np.sum(probabilities ** 2)
2. 特征选择策略
对每个特征计算分割后的加权基尼系数:
- 离散特征:遍历所有可能二分组合
- 连续特征:先排序后取相邻值中点作为候选分割点
def find_best_split(X, y):
best_gini = float('inf')
best_feature = None
best_value = None
for feature in range(X.shape[1]):
# 处理离散特征
if isinstance(X[0, feature], str):
# ... 遍历所有可能分割组合
# 处理连续特征
else:
sorted_values = np.sort(np.unique(X[:, feature]))
split_points = (sorted_values[:-1] + sorted_values[1:]) / 2
# ... 评估每个分割点
return best_feature, best_value
3. 递归建树
终止条件:
– 当前节点样本全部属于同一类别
– 没有剩余特征可供分割
– 达到预设的最大深度
class TreeNode:
def __init__(self, feature=None, value=None, left=None, right=None):
self.feature = feature # 分裂特征
self.value = value # 分裂值
self.left = left # 左子树
self.right = right # 右子树
def build_tree(X, y, max_depth=5):
# 终止条件检查
if len(np.unique(y)) == 1 or max_depth == 0:
return TreeNode(value=np.argmax(np.bincount(y)))
# 寻找最佳分割
feature, value = find_best_split(X, y)
# 递归构建子树
left_idx = X[:, feature] <= value
left = build_tree(X[left_idx], y[left_idx], max_depth-1)
right = build_tree(X[~left_idx], y[~left_idx], max_depth-1)
return TreeNode(feature=feature, value=value, left=left, right=right)
四、可视化与评估
决策树可视化
安装 graphviz 后:
from sklearn.tree import export_graphviz
import graphviz
dot_data = export_graphviz(
tree_model,
out_file=None,
feature_names=features,
class_names=['坏瓜', '好瓜'],
filled=True
)
graph = graphviz.Source(dot_data)
graph.render('watermelon_tree')
模型评估
from sklearn.metrics import accuracy_score, confusion_matrix
# 预测函数
def predict(node, x):
if node.value is not None:
return node.value
if x[node.feature] <= node.value:
return predict(node.left, x)
else:
return predict(node.right, x)
# 评估
preds = [predict(tree, x) for x in X_test]
print("准确率:", accuracy_score(y_test, preds))
print("混淆矩阵:\n", confusion_matrix(y_test, preds))
五、避坑指南
1. 解决过拟合
- 预剪枝:限制最大深度 / 最小样本数
- 后剪枝:通过验证集评估剪枝收益
2. 类别不平衡
- 在基尼计算中引入类别权重
- 使用过采样 / 欠采样技术
3. 连续值处理
- 排序后取中点作为候选分割点
- 避免选择方差过小的分割点
六、扩展思考
- 与其他算法对比:
- ID3 只能处理离散特征
-
C4.5 通过信息增益比改进特征选择
-
sklearn 快速实现:
from sklearn.tree import DecisionTreeClassifier clf = DecisionTreeClassifier(criterion='gini', max_depth=3) clf.fit(X_train, y_train)
写在最后
通过这个完整的实现过程,你会发现决策树的魅力在于其直观性——每个判断节点都可以对应到业务逻辑。建议尝试用不同参数训练模型,观察树结构的变化,这对理解算法行为非常有帮助。当熟悉基本原理后,可以进一步探索随机森林等集成方法如何提升单棵决策树的性能。
正文完
