Skip to content

Latest commit

 

History

History
683 lines (567 loc) · 26.1 KB

File metadata and controls

683 lines (567 loc) · 26.1 KB
Error in user YAML: (<unknown>): could not find expected ':' while scanning a simple key at line 3 column 1
---
- oeasy Python 0808
- 这是 oeasy 系统化 Python 教程,从基础一步步讲,扎实、完整、不跳步。愿意花时间学,就能真正学会。
本教程同步发布在: 

     个人网站: `https://oeasy.org` 
     蓝桥云课: `https://www.lanqiao.cn/courses/3584` 
     GitHub: `https://github.com/overmind1980/oeasy-python-tutorial` 
     Gitee: `https://gitee.com/overmind1980/oeasypython` 
---

从零开始

回忆

  • 一元
    • 根据 身高 预测 臂展
    • 根据 体重 预测 臂展
  • 二元
    • 根据 身高、体重 预测 臂展

图片描述

  • 除了线性回归之外
    • 还有没有其他类型的回归呢?🤔

决策树回归

from sklearn.tree import DecisionTreeRegressor  # 导入决策树回归器(替换分类器)
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error  # 回归评估指标
import numpy as np
import math

# 原始数据集:提取【身高、体重】作为特征,【臂展】作为回归目标(预测值)
# 数据结构:每一行 = [身高(m), 臂展(m), 体重(kg)] → 特征X=身高+体重,目标y=臂展
raw_data = [
    [1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],
    [1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],
    [1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]
]
# 特征X:仅保留身高、体重(用来预测臂展)
X = np.array([[row[0], row[2]] for row in raw_data])  
# 目标y:臂展(连续值,回归任务的预测目标)
y = np.array([row[1] for row in raw_data])  
feature_names = ['身高(m)', '体重(kg)']  # 特征名适配

# ===================== 1、数据集切分(回归任务调整) =====================
# 回归任务无需stratify(分层抽样仅用于分类标签),其余参数不变
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42  # 移除stratify参数
)

# ===================== 2、构建流水线+决策树回归训练 =====================
# 流水线封装:标准化 → 决策树回归(决策树对标准化不敏感,但保留流水线结构)
pipe_model = Pipeline([
    ("标准化", StandardScaler()),
    ("决策树回归", DecisionTreeRegressor(random_state=42))  # 替换为回归器
])
# 仅用训练集训练回归模型
pipe_model.fit(X_train, y_train)

# ===================== 3、回归模型效果评估(核心调整:替换评估指标) =====================
# 预测值(训练集+测试集)
y_train_pred = pipe_model.predict(X_train)
y_test_pred = pipe_model.predict(X_test)

# 回归任务核心评估指标(越小越好):
# MAE:平均绝对误差 → 预测值与真实值的平均绝对偏差
# RMSE:均方根误差 → 对大误差更敏感,更贴合实际业务
train_mae = mean_absolute_error(y_train, y_train_pred)
test_mae = mean_absolute_error(y_test, y_test_pred)
train_rmse = math.sqrt(mean_squared_error(y_train, y_train_pred))
test_rmse = math.sqrt(mean_squared_error(y_test, y_test_pred))

# 决策树回归的特征重要性(依然可用,代表特征对臂展预测的贡献度)
feat_imp = pipe_model['决策树回归'].feature_importances_

# ===================== 结果汇总输出(适配回归任务) =====================
print("="*70)
print("决策树回归模型-身高+体重预测臂展 训练&验证报告")
print("="*70)
print(f"训练集样本数:{len(X_train)} | 测试集样本数:{len(X_test)}")
print(f"训练集MAE(平均绝对误差):{train_mae:.4f} m")
print(f"测试集MAE(平均绝对误差):{test_mae:.4f} m")
print(f"训练集RMSE(均方根误差):{train_rmse:.4f} m")
print(f"测试集RMSE(均方根误差):{test_rmse:.4f} m")
print(f"特征重要性:身高={feat_imp[0]:.3f} | 体重={feat_imp[1]:.3f}")
print("="*70)

# 额外:打印测试集真实值vs预测值,更直观
print("\n【测试集真实臂展 vs 预测臂展】")
for true_val, pred_val in zip(y_test, y_test_pred):
    print(f"真实值:{true_val:.2f} m | 预测值:{pred_val:.2f} m | 误差:{abs(true_val-pred_val):.4f} m")

效果

  • 流水线中第二个流程是
DecisionTreeRegressor(random_state=42)
  • 通过 这棵决策树
    • 确实可以得到预测值
    • 预测值 是 有意义的物理模拟量
    • 所以是线性回归
======================================================================
决策树回归模型-身高+体重预测臂展 训练&验证报告
======================================================================
训练集样本数:8 | 测试集样本数:2
训练集MAE(平均绝对误差):0.0000 m
测试集MAE(平均绝对误差):0.0650 m
训练集RMSE(均方根误差):0.0000 m
测试集RMSE(均方根误差):0.0667 m
特征重要性:身高=0.885 | 体重=0.115
======================================================================

【测试集真实臂展 vs 预测臂展】
真实值:2.13 m | 预测值:2.21 m | 误差:0.0800 m
真实值:1.68 m | 预测值:1.73 m | 误差:0.0500 m
  • 可以看看这棵树长什么样吗?

树的形态

from sklearn.tree import plot_tree, export_text
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_squared_error
import numpy as np
import math

# 原始数据集
raw_data = [
    [1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],
    [1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],
    [1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]
]
X = np.array([[row[0], row[2]] for row in raw_data])  
y = np.array([row[1] for row in raw_data])  

# ========== 修复1:改用英文特征名,彻底解决中文字体警告 ==========
feature_names = ['Height(m)', 'Weight(kg)']  # 英文特征名,无字体问题

# 数据集切分
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# 构建流水线+训练
pipe_model = Pipeline([
    ("标准化", StandardScaler()),
    ("决策树回归", DecisionTreeRegressor(random_state=42))
])
pipe_model.fit(X_train, y_train)

# 模型评估(保留原有逻辑)
y_train_pred = pipe_model.predict(X_train)
y_test_pred = pipe_model.predict(X_test)
train_mae = mean_absolute_error(y_train, y_train_pred)
test_mae = mean_absolute_error(y_test, y_test_pred)
train_rmse = math.sqrt(mean_squared_error(y_train, y_train_pred))
test_rmse = math.sqrt(mean_squared_error(y_test, y_test_pred))
feat_imp = pipe_model['决策树回归'].feature_importances_

# 结果输出(保留原有逻辑,仅调整特征名显示)
print("="*70)
print("Decision Tree Regression - Predict Arm Span by Height&Weight")
print("="*70)
print(f"训练集样本数:{len(X_train)} | 测试集样本数:{len(X_test)}")
print(f"训练集MAE(平均绝对误差):{train_mae:.4f} m")
print(f"测试集MAE(平均绝对误差):{test_mae:.4f} m")
print(f"训练集RMSE(均方根误差):{train_rmse:.4f} m")
print(f"测试集RMSE(均方根误差):{test_rmse:.4f} m")
print(f"特征重要性:Height={feat_imp[0]:.3f} | Weight={feat_imp[1]:.3f}")
print("="*70)
print("\n【测试集真实臂展 vs 预测臂展】")
for true_val, pred_val in zip(y_test, y_test_pred):
    print(f"真实值:{true_val:.2f} m | 预测值:{pred_val:.2f} m | 误差:{abs(true_val-pred_val):.4f} m")

# ========== 核心:决策树可视化(仅保存、无show、无字体警告) ==========
# 无需设置中文字体(已改用英文),彻底消除字体警告
plt.figure(figsize=(12, 8), dpi=100)  # 提升图片清晰度
plot_tree(
    pipe_model['决策树回归'],
    feature_names=feature_names,  # 英文特征名,无字体问题
    filled=True,
    rounded=True,
    precision=4,  # plot_tree支持precision,保留
    fontsize=10,
    label='all',
)
# 改用英文标题,无字体警告
plt.title('Decision Tree Regression - Predict Arm Span by Height&Weight', fontsize=14)

# 保存图片(仅save、无show)
plt.savefig(
    'decision_tree_arm_span.png',  # 英文文件名,避免中文路径问题
    bbox_inches='tight',
    facecolor='white',
    dpi=100
)
plt.close()  # 关闭画布,释放内存

# ========== 修复2:export_text移除precision参数(低版本sklearn兼容) ==========
print("\n【决策树文本版决策路径】")
# 移除precision参数,仅保留feature_names(低版本支持)
tree_text = export_text(
    pipe_model['决策树回归'],
    feature_names=feature_names  # 仅保留兼容参数
)
print(tree_text)
print(f"\n✅ 决策树图片已保存为:decision_tree_arm_span.png")

效果

  • 第一刀 在高度上
|--- Height(m) <= 0.28
|   |--- Weight(kg) <= -0.70
|   |   |--- Weight(kg) <= -1.36
|   |   |   |--- value: [1.73]
|   |   |--- Weight(kg) >  -1.36
|   |   |   |--- value: [1.78]
|   |--- Weight(kg) >  -0.70
|   |   |--- Height(m) <= -0.36
|   |   |   |--- value: [1.85]
|   |   |--- Height(m) >  -0.36
|   |   |   |--- Weight(kg) <= 0.06
|   |   |   |   |--- value: [1.93]
|   |   |   |--- Weight(kg) >  0.06
|   |   |   |   |--- value: [1.96]
|--- Height(m) >  0.28
|   |--- Height(m) <= 0.71
|   |   |--- value: [2.11]
|   |--- Height(m) >  0.71
|   |   |--- Height(m) <= 1.35
|   |   |   |--- value: [2.21]
|   |   |--- Height(m) >  1.35
|   |   |   |--- value: [2.27]
  • 后面不断细分

图片描述

  • 误差不大
【测试集真实臂展 vs 预测臂展】
真实值:2.13 m | 预测值:2.21 m | 误差:0.0800 m
真实值:1.68 m | 预测值:1.73 m | 误差:0.0500 m
  • 可以交叉验证吗?

Cross-Valdate

  • 交叉验证
# 导入所有必要库
from sklearn.tree import plot_tree, export_text, _tree
from sklearn.tree import DecisionTreeRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_absolute_error, mean_squared_error
import matplotlib.pyplot as plt
import numpy as np
import math

# ===================== 1. 原始数据集 =====================
raw_data = [
    [1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],
    [1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],
    [1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]
]
# 全量特征(10个样本:身高、体重)
X = np.array([[row[0], row[2]] for row in raw_data])  
# 全量标签(10个样本:臂展)
y = np.array([row[1] for row in raw_data])  
# 特征名(英文避免字体问题,图片标题用中文但提前设置字体)
feature_names = ['Height(m)', 'Weight(kg)']

# ===================== 2. 构建模型流水线 =====================
pipe_model = Pipeline([
    ("标准化", StandardScaler()),  # 先标准化特征
    ("决策树回归", DecisionTreeRegressor(random_state=42))  # 决策树回归器
])

# ===================== 3. 网格搜索+5折CV选最优参数 =====================
# 定义要调优的超参数范围
param_grid = {
    '决策树回归__max_depth': [2, 3, 4],        # 树的最大深度
    '决策树回归__min_samples_split': [2, 3]     # 分裂节点的最小样本数
}

# 网格搜索:结合5折CV自动选最优参数,并训练最终模型
grid_search = GridSearchCV(
    estimator=pipe_model,
    param_grid=param_grid,
    cv=5,  # 5折交叉验证
    scoring='neg_mean_absolute_error',  # 用MAE作为评分标准(越小越好)
    n_jobs=-1,  # 多线程加速
    refit=True  # 自动用最优参数+全量数据训练最终模型
)
# 执行网格搜索(核心:用5折CV评估所有参数组合,选最优后训全量数据)
grid_search.fit(X, y)

# ===================== 4. 提取核心结果 =====================
# 最优超参数
best_params = grid_search.best_params_
# 最优参数对应的5折CV平均MAE(转正,越小越好)
cv_best_mae = -grid_search.best_score_
# 最终模型(最优参数+全量10个样本训练)
final_model = grid_search.best_estimator_

# ===================== 5. 评估最终模型 =====================
# 用最终模型预测全量数据
y_pred_full = final_model.predict(X)
# 计算全量数据的MAE和RMSE
full_mae = mean_absolute_error(y, y_pred_full)
full_rmse = math.sqrt(mean_squared_error(y, y_pred_full))
# 特征重要性
feat_imp = final_model['决策树回归'].feature_importances_

# ===================== 6. 查看决策树深度(核心新增) =====================
# 树的设置深度上限(最优max_depth)
tree_max_depth = final_model['决策树回归'].max_depth
# 树的实际深度(由数据分裂情况决定,≤设置的上限)
tree_actual_depth = final_model['决策树回归'].get_depth()

# ===================== 7. 结果输出 =====================
print("="*80)
print("决策树回归最优版本 - 全量数据训练+5折CV选最优参数")
print("="*80)

# 7.1 交叉验证+调参结果
print("\n【1. 5折CV选最优参数结果】")
print(f"最优超参数:{best_params}")
print(f"最优参数对应的5折CV平均MAE:{cv_best_mae:.4f} m(越小越好)")

# 7.2 最终模型评估结果
print("\n【2. 最终模型(全量10个样本训练)评估】")
print(f"全量数据预测MAE:{full_mae:.4f} m")
print(f"全量数据预测RMSE:{full_rmse:.4f} m")
print(f"特征重要性:Height={feat_imp[0]:.3f} | Weight={feat_imp[1]:.3f}")

# 7.3 决策树深度信息(核心新增)
print("\n【3. 最终决策树深度信息】")
print(f"设置的深度上限(最优max_depth):{tree_max_depth}")
print(f"树的实际深度:{tree_actual_depth}")

# 7.4 全量数据真实值vs预测值
print("\n【4. 全量数据真实臂展 vs 预测臂展】")
for true_val, pred_val in zip(y, y_pred_full):
    error = abs(true_val - pred_val)
    print(f"真实值:{true_val:.2f} m | 预测值:{pred_val:.2f} m | 误差:{error:.4f} m")

# ===================== 8. 解决中文字体问题+可视化决策树 =====================
# 设置matplotlib字体(文泉驿正黑,解决中文显示警告)
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 绘制决策树
plt.figure(figsize=(12, 8), dpi=100)
plot_tree(
    final_model['决策树回归'],
    feature_names=feature_names,
    filled=True,  # 填充颜色
    rounded=True, # 圆角框
    precision=4,  # 数值精度
    fontsize=10,
    label='all'
)
# 图片标题(中文,已设置字体不会报错)
plt.title('最终决策树(最优参数+全量10样本)- 预测臂展', fontsize=14)
# 保存图片(解决中文显示问题)
plt.savefig(
    'final_decision_tree_arm_span.png',
    bbox_inches='tight',  # 自适应边界
    facecolor='white',    # 背景白色
    dpi=100
)
plt.close()  # 关闭画布,释放内存

# ===================== 9. 文本版决策树 =====================
print("\n【5. 最终决策树文本版决策路径】")
tree_text = export_text(final_model['决策树回归'], feature_names=feature_names)
print(tree_text)

# ===================== 10. 最终提示 =====================
print(f"\n✅ 所有结果已输出!")
print(f"✅ 决策树图片已保存为:final_decision_tree_arm_span.png")

验证结果

  • 5折 交叉验证
    • 5次训练 生成5棵树
    • 得到最优参数max_depth为3

【1. 5折CV选最优参数结果】
最优超参数:{'决策树回归__max_depth': 3, '决策树回归__min_samples_split': 3}
最优参数对应的5折CV平均MAE:0.0775 m(越小越好)

【2. 最终模型(全量10个样本训练)评估】
全量数据预测MAE:0.0160 m
全量数据预测RMSE:0.0221 m
特征重要性:Height=0.830 | Weight=0.170

【3. 最终决策树深度信息】
设置的深度上限(最优max_depth):3
树的实际深度:3
  • 然后五棵树都扔掉
    • 使用指定参数
    • 对于所有特征
    • 进行训练

图片描述

  • 可以来 随机森林线性回归吗?

随机森林

# 导入核心库
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV, cross_validate
from sklearn.metrics import mean_absolute_error, mean_squared_error
import matplotlib.pyplot as plt
import numpy as np
import math

# ===================== 1. 原始数据集 =====================
raw_data = [
    [1.75,1.78,70],[1.65,1.68,55],[1.83,1.85,85],
    [1.70,1.73,60],[1.91,1.96,95],[1.88,1.93,82],
    [1.98,2.11,90],[2.03,2.21,102],[2.08,2.13,111],[2.16,2.27,116]
]
X = np.array([[row[0], row[2]] for row in raw_data])  # 身高、体重
y = np.array([row[1] for row in raw_data])            # 臂展
feature_names = ['Height(m)', 'Weight(kg)']

# ===================== 2. 全局设置(解决中文显示) =====================
plt.rcParams['font.sans-serif'] = ['WenQuanYi Zen Hei']
plt.rcParams['axes.unicode_minus'] = False

# ===================== 3. 随机森林回归(4个核心超参数调优) =====================
print("="*80)
print("随机森林回归 - 4个超参数调优 + 森林可视化")
print("="*80)

# 3.1 构建随机森林流水线
rf_pipe = Pipeline([
    ("标准化", StandardScaler()),
    ("随机森林回归", RandomForestRegressor(random_state=42))
])

# 3.2 定义4个核心超参数的搜索网格(随机森林最关键的4个参数)
param_grid = {
    '随机森林回归__n_estimators': [10, 20, 30],        # 超参数1:森林中树的数量
    '随机森林回归__max_depth': [2, 3, 4],              # 超参数2:单棵树的最大深度
    '随机森林回归__min_samples_split': [2, 3],         # 超参数3:节点分裂的最小样本数
    '随机森林回归__min_samples_leaf': [1, 2]           # 超参数4:叶子节点的最小样本数
}

# 3.3 网格搜索+5折CV选最优参数
grid_search = GridSearchCV(
    estimator=rf_pipe,
    param_grid=param_grid,
    cv=5,                      # 5折交叉验证
    scoring='neg_mean_absolute_error',  # 用MAE评分
    n_jobs=-1,                 # 多线程加速
    refit=True                 # 用最优参数+全量数据训练最终模型
)
grid_search.fit(X, y)

# 3.4 提取核心结果
best_params = grid_search.best_params_          # 最优超参数
cv_best_mae = -grid_search.best_score_          # 5折CV平均MAE(转正)
final_rf_model = grid_search.best_estimator_    # 最终森林模型(最优参数)
y_pred = final_rf_model.predict(X)              # 全量数据预测结果
full_mae = mean_absolute_error(y, y_pred)       # 全量数据MAE
full_rmse = math.sqrt(mean_squared_error(y, y_pred))  # 全量数据RMSE
feat_imp = final_rf_model['随机森林回归'].feature_importances_  # 特征重要性

# 3.5 输出核心结果
print(f"\n【1. 最优超参数(4个)】")
for param, value in best_params.items():
    print(f"  {param.replace('随机森林回归__', '')}: {value}")
print(f"\n【2. 模型性能】")
print(f"  5折CV平均MAE:{cv_best_mae:.4f} m(越小越好)")
print(f"  全量数据MAE:{full_mae:.4f} m | RMSE:{full_rmse:.4f} m")
print(f"\n【3. 特征重要性】")
print(f"  Height(m): {feat_imp[0]:.3f} | Weight(kg): {feat_imp[1]:.3f}")

# ===================== 4. 森林可视化(4个维度体现“森林”特性) =====================
# 4.1 可视化1:特征重要性(森林对特征的整体判断)
plt.figure(figsize=(8, 5), dpi=100)
plt.bar(feature_names, feat_imp, color=['#1f77b4', '#ff7f0e'])
plt.title('随机森林 - 特征重要性(森林整体判断)', fontsize=14)
plt.ylabel('重要性得分')
plt.grid(axis='y', alpha=0.3)
plt.savefig('rf_feature_importance.png', bbox_inches='tight', facecolor='white', dpi=100)
plt.close()
print("\n✅ 可视化1:特征重要性图已保存为 rf_feature_importance.png")

# 4.2 可视化2:森林中多棵树的结构示例(选前3棵树,体现“多棵树组成森林”)
# 获取森林中的所有树
all_trees = final_rf_model['随机森林回归'].estimators_
# 绘制前3棵树(子图布局:1行3列)
fig, axes = plt.subplots(1, 3, figsize=(18, 6), dpi=100)
fig.suptitle('随机森林 - 前3棵决策树结构示例(森林的组成单元)', fontsize=16)

from sklearn.tree import plot_tree
for i in range(3):
    plot_tree(
        all_trees[i],
        ax=axes[i],
        feature_names=feature_names,
        filled=True,
        rounded=True,
        precision=4,
        fontsize=8
    )
    axes[i].set_title(f'第{i+1}棵决策树', fontsize=12)

plt.tight_layout()
plt.savefig('rf_trees_sample.png', bbox_inches='tight', facecolor='white', dpi=100)
plt.close()
print("✅ 可视化2:前3棵树结构示例已保存为 rf_trees_sample.png")

# 4.3 可视化3:真实值vs预测值对比(森林的整体预测效果)
plt.figure(figsize=(8, 6), dpi=100)
# 绘制真实值和预测值的散点
plt.scatter(range(len(y)), y, label='真实臂展', color='#1f77b4', s=80, alpha=0.8)
plt.scatter(range(len(y)), y_pred, label='随机森林预测臂展', color='#ff7f0e', s=80, alpha=0.8)
# 绘制误差线
for i in range(len(y)):
    plt.plot([i, i], [y[i], y_pred[i]], color='gray', linestyle='--', alpha=0.5)
plt.title('随机森林 - 真实臂展 vs 预测臂展', fontsize=14)
plt.xlabel('样本序号')
plt.ylabel('臂展 (m)')
plt.legend()
plt.grid(alpha=0.3)
plt.savefig('rf_true_vs_pred.png', bbox_inches='tight', facecolor='white', dpi=100)
plt.close()
print("✅ 可视化3:真实值vs预测值对比图已保存为 rf_true_vs_pred.png")

# 4.4 可视化4:森林中每棵树的预测结果分布(体现集成优势)
# 计算每棵树对每个样本的预测值
tree_preds = np.array([tree.predict(X) for tree in all_trees])
# 绘制前5个样本的每棵树预测值分布
plt.figure(figsize=(10, 6), dpi=100)
sample_ids = [0, 1, 2, 3, 4]  # 选前5个样本展示
for idx in sample_ids:
    # 每棵树对该样本的预测值
    tree_pred_for_sample = tree_preds[:, idx]
    # 绘制小提琴图/散点图
    plt.scatter([idx]*len(tree_pred_for_sample), tree_pred_for_sample, 
                alpha=0.5, color='#2ca02c', s=20, label='单棵树预测值' if idx==0 else "")
    # 森林的最终预测值(所有树的均值)
    plt.scatter(idx, y_pred[idx], color='red', s=100, marker='*', label='森林最终预测值' if idx==0 else "")
    # 真实值
    plt.scatter(idx, y[idx], color='black', s=100, marker='o', label='真实值' if idx==0 else "")

plt.title('随机森林 - 单棵树预测值 vs 森林最终预测值(前5个样本)', fontsize=14)
plt.xlabel('样本序号')
plt.ylabel('臂展 (m)')
plt.legend(loc='upper right')
plt.grid(alpha=0.3)
plt.savefig('rf_tree_pred_dist.png', bbox_inches='tight', facecolor='white', dpi=100)
plt.close()
print("✅ 可视化4:单棵树vs森林预测值分布已保存为 rf_tree_pred_dist.png")

# 4.5 输出全量数据的真实值vs预测值
print("\n【4. 全量数据真实值 vs 随机森林预测值】")
for i, (true_val, pred_val) in enumerate(zip(y, y_pred)):
    error = abs(true_val - pred_val)
    print(f"样本{i+1}:真实值={true_val:.2f} m | 预测值={pred_val:.2f} m | 误差={error:.4f} m")

print("\n🎉 所有可视化文件已保存,随机森林分析完成!")

结果

===================================================

【1. 最优超参数(4个)】
  max_depth: 2
  min_samples_leaf: 1
  min_samples_split: 2
  n_estimators: 10

【2. 模型性能】
  5折CV平均MAE:0.0719 m(越小越好)
  全量数据MAE:0.0399 m | RMSE:0.0462 m

【3. 特征重要性】
  Height(m): 0.603 | Weight(kg): 0.397
===============

4个超参数的选择(随机森林最核心的4个)

超参数名 含义
n_estimators 森林中决策树的数量(数量越多越稳定,但计算量越大)
max_depth 单棵决策树的最大深度(限制树的复杂度,避免过拟合)
min_samples_split 节点分裂所需的最小样本数(样本数不足则不分裂,减少过拟合)
min_samples_leaf 叶子节点必须包含的最小样本数(避免生成过细的叶子,提升泛化能力)

4个可视化的意义(体现“森林”而非单棵树)

  • 可视化1(特征重要性):反映整个森林对“身高/体重”的权重判断(而非单棵树)

图片描述

  • 可视化2(前3棵树结构):展示森林的“组成单元”,能看到不同树的分裂规则略有差异(随机森林的随机性)

图片描述

  • 可视化3(真实值vs预测值):展示整个森林的最终预测效果,误差线能直观看到每个样本的预测偏差

图片描述

  • 可视化4(单棵树vs森林预测值):体现随机森林的核心优势——单棵树预测有波动,森林通过“投票/平均”让预测更稳定。

图片描述

总结

  • 以前 决策树 和 随机森林 用于分类

    • 预测的是人为的标签
  • 这次是用了 决策树 和 随机森林 回归

    • 预测的是 真实的物理量
对比维度 回归问题 分类问题
预测目标 猜一个连续的数 猜一个确定的类别
输出长啥样 比如 1.78m、60kg、25℃ 比如 是/否、猫/狗、及格/不及格
咋判断模型好不好 看预测值和真实值差多少(差越小越好) 看预测对的比例有多高(比例越高越好)
你的实战例子 用身高体重预测臂展 用身高体重预测“是否是运动员”
常用算法 线性回归、决策树回归、随机森林回归 逻辑回归、决策树分类、随机森林分类

图片描述

  • 还能有什么样别的回归呢??🤔
  • 我们下次再说👋

  • 本文来自 oeasy Python 系统教程。
  • 想完整、扎实学 Python,
  • 搜索 oeasy 即可。