老码农和你一起学AI系列:机器学习实战-树与森林
本文系统介绍了决策树与集成学习模型。决策树通过信息增益或基尼不纯度选择最优分裂点,采用递归方式构建,为防止过拟合可使用预剪枝策略。Scikit-learn提供了高效的决策树实现,支持多种优化参数。集成学习分为Bagging(如随机森林)和Boosting(如GBDT)两大流派,前者通过并行投票提升性能,后者通过串行纠错优化结果。以泰坦尼克数据集为例,GBDT在生还预测任务中表现略优于随机森林。实践
在机器学习的世界里,有一类模型如同自然界的树木般生长 —— 它们从一个根节点出发,通过不断分叉形成复杂的结构,这就是决策树。而当多棵树汇聚成 “森林”,便形成了性能更强大的集成学习模型。本文将从决策树的核心原理出发,解析其构建逻辑与优化方法,深入探讨集成学习的思想,并通过泰坦尼克生还预测实战,展示决策树与集成模型的应用价值,让不同背景的读者都能理解这一重要机器学习分支。
一、决策树的生长逻辑
决策树是一种直观易懂的非线性模型,它模拟人类决策的过程:通过一系列 “是 / 否” 的判断,最终得出结论。比如在相亲场景中,我们可能会先判断 “对方是否有稳定工作”,再根据结果判断 “是否有房”,逐步缩小范围 —— 这就是决策树的工作方式。
1.1 如何找到最佳分裂点?
决策树的核心是每次选择最优特征进行分裂,而衡量 “最优” 的标准主要有两个:信息增益(基于熵)和基尼不纯度。
信息增益与熵(Entropy)
熵是信息论中衡量不确定性的指标,熵值越高,数据的混乱程度越高。对于一个包含多个类别的数据集,其熵的计算公式为:
其中, 是第 k类样本在数据集 D 中的占比。当所有样本属于同一类别时,熵为 0(完全确定);当两类样本各占 50% 时,熵为 1(最不确定)。
信息增益则是指分裂后熵的减少量,即:
其中,A 是待选择的特征,是特征 A 取值为 v 的子数据集。信息增益越大,说明使用特征 A 分裂后,数据的不确定性降低得越多,因此应优先选择信息增益最大的特征。
基尼不纯度(Gini Impurity)
基尼不纯度衡量的是从数据集中随机抽取两个样本,其类别不同的概率,计算公式为:
与熵类似,基尼不纯度越小,数据的纯度越高。基于基尼不纯度的特征选择标准是最小化分裂后的基尼不纯度,即选择使子数据集基尼不纯度加权和最小的特征。
实例:用代码计算熵和基尼系数
以一个简单的二分类数据集为例(假设正类占比 60%,负类占比 40%):
import numpy as np
def entropy(p):
"""计算熵(p为正类占比)"""
if p == 0 or p == 1:
return 0
return -p * np.log2(p) - (1 - p) * np.log2(1 - p)
def gini(p):
"""计算基尼不纯度"""
return 1 - p**2 - (1 - p)** 2
# 正类占比60%时
p = 0.6
print(f"熵:{entropy(p):.3f}") # 输出:0.971
print(f"基尼不纯度:{gini(p):.3f}") # 输出:0.480
通过对比不同特征分裂后的信息增益或基尼不纯度,就能找到每次分裂的最优特征。
1.2 递归构建
决策树的构建是一个递归过程,主要步骤如下:
- 初始化:将所有样本作为根节点的数据集。
- 选择最优特征:计算当前数据集上所有特征的信息增益(或基尼不纯度),选择最优特征。
- 分裂节点:根据最优特征的取值,将数据集划分为多个子数据集,每个子数据集对应一个子节点。
- 递归终止条件:当子数据集所有样本属于同一类别(纯度为 100%),或没有更多特征可分裂时,停止递归,将该节点标记为叶节点,类别为子数据集中多数类。
1.3 防止决策树 “过生长”
决策树如果不加以限制,会不断分裂直到每个叶节点只包含一个样本,这会导致过拟合(对训练数据拟合极好,但泛化能力差)。剪枝是解决这一问题的关键,预剪枝是最常用的方法之一。
预剪枝通过在决策树生长过程中设置限制条件,提前终止分裂,常见策略包括:
- 最大深度:限制树的最大层数(如max_depth=5)。
- 最小样本数:当节点样本数少于阈值时,停止分裂(如min_samples_split=10)。
- 最小信息增益:若分裂带来的信息增益小于阈值,停止分裂。
例如,当设置max_depth=3时,决策树最多只能分裂 3 次,避免了过度复杂的结构。
二、Scikit-learn 核心
Scikit-learn 提供了高效的决策树与集成学习实现,不仅简化了模型训练流程,还支持多种优化策略。
2.1 决策树 Classifier
Scikit-learn 的DecisionTreeClassifier与我们手动实现的决策树核心逻辑一致,但在细节上更优化:
- 支持多种分裂标准(criterion='gini'或'entropy')。
- 内置丰富的预剪枝参数(max_depth、min_samples_split等)。
- 优化了高维数据的分裂效率,支持并行计算。
手写版与内置版的对比:
- 手写版更适合理解原理,但未考虑缺失值处理、连续特征离散化等细节。
- DecisionTreeClassifier能自动处理连续特征(通过寻找最佳阈值分裂)和缺失值,且计算效率更高。
使用示例:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
# 加载数据
data = load_iris()
X, y = data.data, data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 初始化决策树(预剪枝:最大深度3,最小分裂样本数5)
dt = DecisionTreeClassifier(criterion='gini', max_depth=3, min_samples_split=5, random_state=42)
dt.fit(X_train, y_train)
# 评估性能
print(f"测试集准确率:{dt.score(X_test, y_test):.3f}") # 输出约0.978
2.2 集成学习:Bagging 与 Boosting
集成学习通过组合多个弱模型(性能略优于随机猜测),形成强模型。其核心逻辑是 “三个臭皮匠顶个诸葛亮”,主要分为 Bagging 和 Boosting 两大流派。
Bagging:并行的 “投票机制”
Bagging(Bootstrap Aggregating)的思想是:通过自助采样(从训练集中随机有放回地抽取样本)生成多个不同的训练集,在每个训练集上训练一个弱模型,最终通过投票(分类)或平均(回归)得到结果。
随机森林(Random Forest) 是 Bagging 的典型代表,它在 Bagging 基础上增加了 “随机特征选择”:每次分裂时,只从随机选择的部分特征中寻找最优分裂点。这进一步降低了模型间的相关性,提升了集成效果。
随机森林的核心参数:
- n_estimators:树的数量(越多通常性能越好,但计算成本增加)。
- max_depth:单棵树的最大深度(控制过拟合)。
- max_features:每次分裂时考虑的最大特征数(默认sqrt(n_features))。
Boosting:串行的 “纠错机制”
Boosting 的思想是:串行训练多个弱模型,每个模型专注于纠正前一个模型的错误。通过给错误分类的样本增加权重,让后续模型更关注难分样本,最终加权组合所有模型的结果。
梯度提升树(GBDT,Gradient Boosting Decision Tree) 是 Boosting 的代表,它通过拟合前一轮模型的残差(预测误差)来不断优化。
GBDT 的核心参数:
- n_estimators:树的数量(需配合学习率调整)。
- learning_rate:每个树的贡献权重(较小的学习率通常需要更多树,如learning_rate=0.1)。
- max_depth:单棵树的深度(通常较小,如 3-5,避免过拟合)。
2.3 随机森林与 GBDT 的核心参数调优
参数调优遵循 “先粗调后细调” 的原则,以随机森林为例:
- 先固定max_depth=10,调整n_estimators(如从 10 到 200),找到性能稳定的最小值。
- 固定n_estimators,调整max_depth(如 3-15)和min_samples_split,优化过拟合。
- 最后微调max_features,进一步提升性能。
GBDT 调优则需注意learning_rate与n_estimators的平衡:降低学习率时,需增加树的数量以保证性能。
三、实战项目
泰坦尼克号数据集是经典的分类任务数据集,包含乘客的年龄、性别、票价等特征,目标是预测乘客是否生还。我们将分别使用随机森林和 GBDT 进行建模,并对比两者的性能。
3.1 数据预处理
首先加载数据并处理缺失值、转换特征:
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
# 加载数据
data = pd.read_csv('titanic.csv')
# 选择特征与目标
X = data[['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']]
y = data['Survived']
# 处理缺失值
X['Age'].fillna(X['Age'].median(), inplace=True) # 年龄用中位数填充
X['Embarked'].fillna(X['Embarked'].mode()[0], inplace=True) # 登船港口用众数填充
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 特征预处理:类别特征独热编码,数值特征保持不变
categorical_features = ['Pclass', 'Sex', 'Embarked']
numeric_features = ['Age', 'SibSp', 'Parch', 'Fare']
preprocessor = ColumnTransformer(
transformers=[
('cat', OneHotEncoder(handle_unknown='ignore'), categorical_features),
('num', 'passthrough', numeric_features)
])
3.2 模型训练与评估
分别训练随机森林和 GBDT,并使用准确率、精确率、召回率和 F1-score 评估性能:
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
# 随机森林 pipeline
rf_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', RandomForestClassifier(n_estimators=100, max_depth=5, random_state=42))
])
rf_pipeline.fit(X_train, y_train)
rf_pred = rf_pipeline.predict(X_test)
# GBDT pipeline
gbdt_pipeline = Pipeline([
('preprocessor', preprocessor),
('classifier', GradientBoostingClassifier(n_estimators=100, learning_rate=0.1, max_depth=3, random_state=42))
])
gbdt_pipeline.fit(X_train, y_train)
gbdt_pred = gbdt_pipeline.predict(X_test)
# 评估函数
def evaluate(y_true, y_pred, model_name):
acc = accuracy_score(y_true, y_pred)
prec = precision_score(y_true, y_pred)
rec = recall_score(y_true, y_pred)
f1 = f1_score(y_true, y_pred)
print(f"{model_name} 评估结果:")
print(f"准确率:{acc:.3f},精确率:{prec:.3f},召回率:{rec:.3f},F1-score:{f1:.3f}\n")
evaluate(y_test, rf_pred, "随机森林")
evaluate(y_test, gbdt_pred, "GBDT")
典型输出:
随机森林 评估结果:
准确率:0.817,精确率:0.792,召回率:0.726,F1-score:0.758
GBDT 评估结果:
准确率:0.825,精确率:0.803,召回率:0.742,F1-score:0.771
可见在该任务中,GBDT 性能略优于随机森林,这是因为 Boosting 的纠错机制更适合处理这种存在明显模式但有噪声的数据。
3.3 特征重要性分析
随机森林和 GBDT 都能输出特征重要性,帮助理解模型决策:
import matplotlib.pyplot as plt
# 获取随机森林特征重要性
rf = rf_pipeline.named_steps['classifier']
feature_names = (preprocessor.named_transformers_['cat'].get_feature_names_out(categorical_features).tolist() +
numeric_features)
rf_importance = pd.Series(rf.feature_importances_, index=feature_names).sort_values(ascending=False)
# 可视化
plt.figure(figsize=(10, 6))
rf_importance[:10].plot(kind='barh')
plt.title('随机森林特征重要性')
plt.show()
分析发现,“性别”(女性生还率更高)、“票价”(高票价乘客更可能生还)和 “年龄” 是影响生还的关键特征,这与历史事实一致。
四、最后小结
决策树以其直观性和可解释性成为机器学习的重要工具,而集成学习通过组合多棵树,解决了单棵树易过拟合、稳定性差的问题。随机森林(Bagging)适合处理高维数据和大规模数据集,计算并行高效;GBDT(Boosting)则在中小型数据集上表现更优,但训练成本较高。
在实际应用中,选择模型需结合数据规模、特征类型和业务需求:
- 若追求解释性和训练速度,单棵决策树是首选。
- 若需平衡性能与效率,随机森林是可靠选择。
- 若追求最高性能且能接受较高计算成本,GBDT 或其改进版(如 XGBoost、LightGBM)更合适。
从单棵树到一片森林,集成学习的思想不仅是机器学习的突破,也给我们带来启示:集体的智慧往往超越个体,而如何有效组合个体优势,是提升性能的关键。无论是数据科学还是日常生活,这一思想都同样适用。未完待续.............
更多推荐
所有评论(0)