当前位置: 首页 > news >正文

第8篇:决策树与随机森林:从零实现到调参实战

摘要
本文系统讲解决策树的构建原理(信息增益、基尼不纯度)、递归分裂过程,并用Python从零实现ID3算法。随后深入探讨随机森林的集成思想、Bagging机制,并使用Scikit-learn进行调参与特征重要性分析。结合鸢尾花和波士顿房价数据集,帮助学习者掌握“白盒”模型的可解释性优势与强大性能。


一、为什么选择决策树?

决策树是机器学习中最直观、最易解释的模型之一,其结构类似于“流程图”,模拟人类决策过程。

1.1 决策树的优势

  • 可解释性强:能清晰展示决策路径
  • 无需数据标准化:对数值缩放不敏感
  • 能处理非线性关系:通过分层分裂
  • 自动特征选择:重要特征出现在树的上层

1.2 典型应用场景

  • 信贷审批(是否放贷)
  • 医疗诊断(是否患病)
  • 用户分群(高/低价值客户)

二、决策树原理:如何“聪明地”分裂?

2.1 核心思想

通过递归地选择最佳特征和分割点,将数据集划分为更“纯净”的子集。

目标:每次分裂后,子集的“混乱度”降低。

2.2 纯度度量:信息增益(ID3/C4.5)与基尼不纯度(CART)

(1)信息熵(Entropy)

衡量数据集的混乱程度:

Entropy(S) = - Σ p_i log₂(p_i)
  • p_i:类别i在集合S中的比例
  • 熵越小,数据越纯净(如全为同一类,熵=0)
import numpy as npdef entropy(y):_, counts = np.unique(y, return_counts=True)probs = counts / len(y)return -np.sum(probs * np.log2(probs + 1e-9))  # 防止log(0)# 示例
print(f"纯类别: {entropy([0,0,0]):.3f}")     # 0.000
print(f"混合类别: {entropy([0,1]):.3f}")     # 1.000
(2)信息增益(Information Gain)

选择使信息增益最大的特征进行分裂:

IG(S, A) = Entropy(S) - Σ [ |S_v|/|S| × Entropy(S_v) ]
  • A:特征
  • S_v:根据特征A的值v划分的子集
(3)基尼不纯度(Gini Impurity)

CART算法使用,计算更高效:

Gini(S) = 1 - Σ (p_i)²
def gini(y):_, counts = np.unique(y, return_counts=True)probs = counts / len(y)return 1 - np.sum(probs ** 2)

📌 Scikit-learn的DecisionTreeClassifier默认使用Gini


三、从零实现决策树(ID3算法)

class Node:def __init__(self, feature=None, threshold=None, left=None, right=None, value=None):self.feature = feature      # 分裂特征索引self.threshold = threshold  # 分裂阈值(连续值)或值(离散值)self.left = left            # 左子树self.right = right          # 右子树self.value = value          # 叶子节点的预测值class DecisionTree:def __init__(self, min_samples_split=2, max_depth=100, n_features=None):self.min_samples_split = min_samples_splitself.max_depth = max_depthself.n_features = n_featuresself.root = Nonedef fit(self, X, y):self.n_features = X.shape[1] if self.n_features is None else min(self.n_features, X.shape[1])self.root = self._grow_tree(X, y)def _grow_tree(self, X, y, depth=0):n_samples, n_feats = X.shapen_labels = len(np.unique(y))# 停止条件if (depth >= self.max_depth or n_labels == 1 or n_samples < self.min_samples_split):leaf_value = self._most_common_label(y)return Node(value=leaf_value)# 随机选择特征子集(用于随机森林)feat_idxs = np.random.choice(n_feats, self.n_features, replace=False)# 找到最佳分裂best_feat, best_thresh = self._best_criteria(X, y, feat_idxs)# 分裂数据left_idxs, right_idxs = self._split(X[:, best_feat], best_thresh)# 递归构建子树left = self._grow_tree(X[left_idxs, :], y[left_idxs], depth + 1)right = self._grow_tree(X[right_idxs, :], y[right_idxs], depth + 1)return Node(best_feat, best_thresh, left, right)def _best_criteria(self, X, y, feat_idxs):best_gain = -1split_idx, split_thresh = None, Nonefor feat_idx in feat_idxs:X_column = X[:, feat_idx]thresholds = np.unique(X_column)for threshold in thresholds:gain = self._information_gain(y, X_column, threshold)if gain > best_gain:best_gain = gainsplit_idx = feat_idxsplit_thresh = thresholdreturn split_idx, split_threshdef _information_gain(self, y, X_column, threshold):# 父节点熵parent_entropy = entropy(y)# 子集left_idxs = X_column <= thresholdy_left, y_right = y[left_idxs], y[~left_idxs]if len(y_left) == 0 or len(y_right) == 0:return 0# 加权子节点熵n = len(y)n_left, n_right = len(y_left), len(y_right)child_entropy = (n_left / n) * entropy(y_left) + (n_right / n) * entropy(y_right)return parent_entropy - child_entropydef _split(self, X_column, split_thresh):left_idxs = X_column <= split_threshreturn left_idxs, ~left_idxsdef _most_common_label(self, y):counter = np.bincount(y)return counter.argmax()def predict(self, X):return np.array([self._traverse_tree(x, self.root) for x in X])def _traverse_tree(self, x, node):if node.value is not None:return node.valueif x[node.feature] <= node.threshold:return self._traverse_tree(x, node.left)return self._traverse_tree(x, node.right)

四、使用Scikit-learn实战决策树

from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt# 加载数据
iris = load_iris()
X, y = iris.data, iris.target# 训练
tree = DecisionTreeClassifier(criterion='gini', max_depth=3, random_state=42)
tree.fit(X, y)# 可视化树结构
plt.figure(figsize=(15, 10))
plot_tree(tree, feature_names=iris.feature_names, class_names=iris.target_names, filled=True)
plt.title("决策树可视化")
plt.show()

✅ 图中显示了每个节点的特征、阈值、基尼不纯度、样本数和类别分布。


五、随机森林:集成学习的王者

5.1 为什么需要随机森林?

单棵决策树容易过拟合。随机森林通过集成多棵树,显著提升泛化能力。

5.2 核心思想:Bagging + 随机特征

  • Bagging(Bootstrap Aggregating):
    • 从训练集中有放回地采样生成多个子集
    • 每个子集训练一棵决策树
  • 随机特征
    • 每次分裂时,随机选择部分特征寻找最佳分裂
  • 预测
    • 分类:多棵树投票
    • 回归:多棵树取平均

5.3 随机森林的优势

  • 高准确率:通常优于单棵树
  • 抗过拟合:集成降低方差
  • 可估计特征重要性
  • 能处理高维数据

六、Scikit-learn实现随机森林

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score# 划分数据
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 训练随机森林
rf = RandomForestClassifier(n_estimators=100,      # 树的数量max_depth=5,           # 控制每棵树复杂度max_features='sqrt',   # 每次分裂考虑的特征数random_state=42
)
rf.fit(X_train, y_train)# 预测
y_pred = rf.predict(X_test)
print(f"测试集准确率: {accuracy_score(y_test, y_pred):.3f}")

七、特征重要性分析

随机森林能输出每个特征对模型的贡献度。

# 获取特征重要性
importances = rf.feature_importances_
feature_names = iris.feature_names# 可视化
indices = np.argsort(importances)[::-1]
plt.figure(figsize=(10, 6))
plt.title("特征重要性")
plt.bar(range(len(importances)), importances[indices])
plt.xticks(range(len(importances)), [feature_names[i] for i in indices])
plt.ylabel("重要性")
plt.show()# 输出
for i in indices:print(f"{feature_names[i]}: {importances[i]:.3f}")

✅ 可用于特征选择,去除不重要特征。


八、调参实战:网格搜索优化随机森林

from sklearn.model_selection import GridSearchCV# 参数网格
param_grid = {'n_estimators': [50, 100, 200],'max_depth': [3, 5, 7, None],'min_samples_split': [2, 5, 10]
}# 网格搜索
grid_search = GridSearchCV(RandomForestClassifier(random_state=42),param_grid, cv=5, scoring='accuracy', n_jobs=-1
)grid_search.fit(X_train, y_train)print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳CV得分: {grid_search.best_score_:.3f}")# 最终模型
best_rf = grid_search.best_estimator_
test_acc = best_rf.score(X_test, y_test)
print(f"测试集准确率: {test_acc:.3f}")

九、回归任务:波士顿房价预测

from sklearn.ensemble import RandomForestRegressor
from sklearn.datasets import fetch_california_housing
from sklearn.metrics import mean_squared_error# 加载数据
housing = fetch_california_housing()
X, y = housing.data, housing.targetX_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)# 训练随机森林回归
rf_reg = RandomForestRegressor(n_estimators=100, random_state=42)
rf_reg.fit(X_train, y_train)# 预测
y_pred = rf_reg.predict(X_test)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
print(f"RMSE: {rmse:.3f}")# 特征重要性
importances_reg = rf_reg.feature_importances_
indices_reg = np.argsort(importances_reg)[::-1]
plt.figure(figsize=(10, 6))
plt.title("回归任务特征重要性")
plt.bar(range(len(importances_reg)), importances_reg[indices_reg])
plt.xticks(range(len(importances_reg)), [housing.feature_names[i] for i in indices_reg], rotation=45)
plt.show()

十、总结与学习建议

本文我们:

  • 掌握了信息熵、信息增益、基尼不纯度的计算;
  • 用Python从零实现了决策树
  • 理解了随机森林的Bagging与随机特征机制;
  • 使用Scikit-learn完成了分类与回归实战;
  • 进行了特征重要性分析网格搜索调参

📌 学习建议

  1. 理解“集成”思想:单一模型弱,组合起来强。
  2. 善用特征重要性:提升模型可解释性。
  3. 注意过拟合:即使随机森林,max_depth等参数仍需调优。

十一、下一篇文章预告

第9篇:支持向量机(SVM):从线性可分到核技巧
我们将深入探讨:

  • SVM的最大间隔分类思想
  • 支持向量的几何意义
  • 软间隔正则化
  • 核技巧(Kernel Trick)如何处理非线性问题
  • 使用Scikit-learn实现SVM分类与回归

揭开“最大间隔”背后的数学之美!


参考文献

  1. 周志华. 《机器学习》(“西瓜书”). 第4章(决策树)、第8章(集成学习)。
  2. Breiman, L. (2001). Random Forests. Machine Learning.
  3. Scikit-learn决策树文档: 1.10. Decision Trees — scikit-learn 1.7.1 documentation
  4. 《The Elements of Statistical Learning》 by Hastie et al.


文章转载自:

http://ak7Hsmbi.cbpkr.cn
http://DjM0Eifh.cbpkr.cn
http://ThbRiiO1.cbpkr.cn
http://04k4Wjv2.cbpkr.cn
http://z0FXeIKe.cbpkr.cn
http://705hL2ut.cbpkr.cn
http://8c6gFT2s.cbpkr.cn
http://PfGjujm6.cbpkr.cn
http://2TAzxonn.cbpkr.cn
http://713zE2LZ.cbpkr.cn
http://6XJBvvhk.cbpkr.cn
http://7m7FJUGA.cbpkr.cn
http://bGJ6jWNL.cbpkr.cn
http://WDvF8qhm.cbpkr.cn
http://gkFoXpxk.cbpkr.cn
http://6rbKI1Cu.cbpkr.cn
http://79Gri95r.cbpkr.cn
http://aZOERKzo.cbpkr.cn
http://VK3XXB56.cbpkr.cn
http://R2ADxVTw.cbpkr.cn
http://DU0vy1ZY.cbpkr.cn
http://fPiIWDiA.cbpkr.cn
http://DB4z8ECZ.cbpkr.cn
http://6brnfig9.cbpkr.cn
http://PlcO7otb.cbpkr.cn
http://j59bBZLf.cbpkr.cn
http://FxQbUSUS.cbpkr.cn
http://21IsdFtz.cbpkr.cn
http://XppqwgtQ.cbpkr.cn
http://aTPSMDg9.cbpkr.cn
http://www.dtcms.com/a/368952.html

相关文章:

  • 迁移学习-ResNet
  • CentOS安装或升级protoc
  • 【QT 5.12.12 下载 Windows 版本】
  • 多语言Qt Linguist
  • 2025年- H118-Lc86. 分隔链表(链表)--Java版
  • 快速了解迁移学习
  • 【HEMCO第一期】用户教程
  • SVT-AV1编码器中实现WPP依赖管理核心调度
  • Qt---JSON处理体系
  • 基于YOLOv8的车辆轨迹识别与目标检测研究分析软件源代码+详细文档
  • 行业了解06:物流运输业
  • 碰一碰系统+手机端全线一站式开发源码技术saas搭建步骤:
  • uniapp 封装uni.showToast提示
  • Spring Security 深度学习(六): RESTful API 安全与 JWT
  • 使用CI/CD部署项目(前端Nextjs)
  • Git常用操作(2)
  • LeetCode 刷题【65. 有效数字】
  • Android,jetpack Compose模仿QQ侧边栏
  • 让语言模型自我进化:探索 Self-Refine 的迭代反馈机制
  • Kubernetes(k8s) po 配置持久化挂载(nfs)
  • 支持二次开发的代练App源码:订单管理、代练监控、安全护航功能齐全,一站式解决代练护航平台源码(PHP+ Uni-app)
  • proble1111
  • Ubuntu 24.04.2安装k8s 1.33.4 配置cilium
  • nextcyber——暴力破解
  • Process Explorer 学习笔记(第三章3.2.3):工具栏与参考功能
  • C++两个字符串的结合
  • c51串口通信原理及实操
  • Java垃圾回收算法详解:从原理到实践的完整指南
  • MongoDB 6.0 新特性解读:时间序列集合与加密查询
  • IAR借助在瑞萨RH850/U2A MCU MCAL支持,加速汽车软件开发