机器学习——随机森林算法分类问题案例解析(sklearn)

1. 集成学习:三个臭皮匠,如何赛过诸葛亮?

我们之前学习的线性回归、决策树等算法,就像是团队里的某一位"专家"。这位专家可能在某个领域很擅长,但单凭他一人,要解决复杂多变的问题,总会遇到瓶颈。就好比,一位再厉害的"诸葛亮",也难免有失算的时候。

那怎么办呢?古人云:"三个臭皮匠,赛过诸葛亮"。**集成学习(Ensemble Learning)**的核心思想正是如此:它不依赖于某一个"超级天才"(强学习器),而是将一群"普通人"(弱学习器)的智慧汇集起来,形成一个决策能力超强的"专家团"(强学习器)。

一个弱学习器,通常指那些性能仅比随机猜测好一点点的模型。而一个成功的"专家团"需要满足两个关键条件:

  1. 个体优秀 (Good):团队里的每个成员(弱学习器)都得有两把刷子,至少得具备一定的判断能力(比如,分类准确率要大于50%)。
  2. 彼此差异 (Different):如果团队成员的想法和知识背景一模一样,那和一个人决策没区别。成员之间必须有差异性、能互补短长,这样集成的效果才会最好。

为了组建这样的"专家团",我们主要有两种策略,可以从两个维度来理解:

维度一:团队成员的构成方式 (同质 vs. 异质)

  • 异质集成 (Heterogeneous Ensemble):这就像组建一个"跨学科团队"。团队里有搞数学的(线性回归)、搞分类的(决策树)、搞空间分析的(K近邻)等等。我们让每个不同类型的模型都对问题进行预测,然后给那些历史表现更好的"专家"更高的发言权(权重),最后综合所有人的加权意见,得出最终结论。
  • 同质集成 (Homogeneous Ensemble) :这就像组建一个"专科攻坚小组",所有成员都是同一类型,比如全是决策树专家。为了避免大家想法一致,我们会给每个专家分发略有不同的"资料包"(搅动数据)。通过这种方式,即使模型类型相同,他们学到的侧重点也各不相同,从而保证了差异性。

维度二:团队的工作模式 (并行 vs. 串行)

  • 并行集成 (Parallel Ensemble) :大家"同时开工,独立思考"。就像一场开卷考试,我们给每个学生(弱学习器)发一份略有不同的复习资料(通过自助采样得到的数据子集),让他们独立完成整套试卷。最后,我们统计所有学生的答案,通过"民主投票"来决定最终答案。这种方式互不干扰,非常适合大规模并行计算。

    • 代表算法装袋法 (Bagging)随机森林 (Random Forest)
  • 串行集成 (Serial Ensemble):大家"接力工作,查漏补缺"。第一位同学(弱学习器)先做一遍题,然后把做错的题目标记出来。第二位同学拿到后,就重点攻克上一位同学搞错的难题。以此类推,每一位新成员都致力于解决前面所有成员留下的"历史遗留问题"。这样层层递进,模型的能力越来越强。

    • 代表算法提升法 (Boosting),如 AdaBoost, GBDT, XGBoost 等。
分类维度 类型 核心思想 工作模式 代表算法 比喻
按构成 异质集成 不同类型的模型组合 各自预测,加权表决 Stacking 跨学科专家会诊
同质集成 同一类型的模型组合 通过数据扰动创造差异 Bagging, Boosting 决策树专家小组
按生成 并行集成 独立、互不依赖 同时训练,投票/平均 Bagging, 随机森林 学生独立完成考试
串行集成 依赖、层层递进 迭代训练,弥补前序错误 Boosting (AdaBoost, GBDT) 师徒接力解决难题

2. 随机森林分类任务:民主投票的决策专家团

随机森林是同质并行集成的杰出代表。顾名思义,"森林"由大量的"树"(决策树)组成。下面我们来看看这个"专家团"是如何工作的。

工作流程:

  1. 建造森林:有控制的"随机"

    • 样本随机 (Bootstrap Sampling) :假设我们有1000个原始样本。为了训练第一棵树,我们从这1000个样本中有放回地随机抽取1000次,形成一个训练集。因为是有放回的,所以这个新的训练集中,有些样本可能出现多次,有些则一次都未出现。这个过程就像是为每个专家准备一份独特的"复习资料"。
    • 特征随机 (Feature Sampling) :在决策树的每个节点进行分裂时,我们不是 从全部 P 个特征中寻找最优分裂点,而是随机抽取 m 个特征(m < P),再从这 m 个中选出最好的。这确保了每棵树都不会过分依赖某几个强特征,增加了树之间的"视角"差异。
      • 经验法则 :对于分类任务,通常取 m ≈ √p;对于回归任务,取 m ≈ p/3
      • 与装袋法(Bagging)的关系 :如果设置 m = p,即每次都考虑所有特征,那随机森林就退化成了装袋法。所以说,随机森林是装袋法的一种"进阶版",它通过增加特征随机性来进一步提升模型的多样性。
  2. 独立训练:并行工作的专家

    每棵决策树都使用自己独特的样本集和特征子集进行独立训练,互不干扰。

  3. 集中决策:少数服从多数

    当一个新的样本需要预测时,森林里的每一棵树都独立地给出一个自己的分类判断。最后,随机森林采用"多数投票 (Majority Voting)"原则,得票最多的那个类别就是最终的预测结果。

优点与缺点:

  • 优点
    • 高准确率与抗过拟合:通过平均/投票多棵树的结果,个别树的错误会被其他树纠正,使得整体模型非常稳健,不易过拟含。
    • 处理高维数据:即使有数千个特征,随机森林也能表现良好,并且能帮我们评估哪些特征更重要。
    • 易于并行化:各棵树的训练可以分散到多个CPU核心或机器上,效率很高。
  • 缺点
    • 可解释性较差 :相对于单棵决策树,由成百上千棵树组成的"黑箱"模型,我们很难直观地理解其内部的决策逻辑。(这催生了下面要讲的解释性工具!
    • 计算成本:对于大数据集,训练大量的树会消耗较多的时间和内存。

3. 谁是MVP?解密随机森林的特征重要性

随机森林是一个"黑箱",但我们依然有办法窥探其内部,比如,搞清楚"在所有决策中,哪个特征的贡献最大?" 这就像评选一支球队的"最有价值球员 (MVP)"。

核心思想 :一个特征越重要,意味着它在分裂决策树节点时,起到的"让数据变纯粹"的作用越大。

计算方法

  1. 单棵树的贡献 :在一棵决策树中,每当一个特征被用来分裂节点时,我们会计算这次分裂带来的"不纯度下降量"(例如,基尼指数下降或信息增益提升)。把这个特征在这棵树里所有分裂点的贡献加起来,就是它在这棵树里的重要性得分。
  2. 整个森林的总评 :将该特征在森林中所有树上的重要性得分进行平均,就得到了它在整个随机森林中的最终重要性排名。

一句话总结:一个特征的重要性,就是它在所有树中、所有分裂决策里,平均贡献度的总和。贡献越大,排名越靠前,它就是我们模型眼中的"MVP"。


4. 不止看"谁"重要,更要看"如何"重要:模型的可解释性利器

特征重要性告诉我们"哪些 "特征是MVP,但没有告诉我们这个MVP"如何 "影响比赛结果的(例如,是得分能力强还是防守能力强?)。部分依赖图 (PDP)个体条件期望图 (ICE) 就是为了回答"如何影响"这个问题的强大工具。

1. 部分依赖图 (Partial Dependence Plot, PDP):看清"平均趋势"

  • 解决的问题 :它展示了当其他所有特征保持不变时 ,某一个特征值的变化,对模型平均预测结果的影响。
  • 比喻 :就像研究"咖啡因摄入量"对"所有学生平均专注度"的影响。我们想知道,是咖啡因越多越好,还是适量最好?
  • 工作原理
    1. 选择一个你感兴趣的特征,比如"年龄"。
    2. 固定住数据集中其他所有特征,然后强制让所有样本的"年龄"都等于20岁,计算出所有样本的平均预测概率。
    3. 接着,强制让所有样本的"年龄"都等于21岁,再算一个平均预测概率。
    4. ...以此类推,将不同年龄值对应的平均预测结果连接起来,就形成了一条曲线。
  • 解读 :这条曲线展示了"年龄"这个特征对模型预测结果的边际效应。我们可以直观地看到,随着年龄的增长,预测结果是线性上升、下降,还是呈现更复杂的关系。

2. 个体条件期望图 (Individual Conditional Expectation, ICE Plot):洞察"个体故事"

  • 解决的问题 :PDP展示的是平均效果,但这种"平均"可能会掩盖个体之间的巨大差异。ICE图则为每一个样本 都画出一条线,展示特定特征的变化对该样本预测结果的影响。
  • 比喻 :PDP是看咖啡因对"所有学生"的平均影响,而ICE图是看咖啡因对"张三 "、"李四"每个人专注度的具体影响。可能张三喝了咖啡生龙活虎,李四喝了却心慌意乱。
  • 工作原理 :它和PDP的原理几乎一样,唯一的区别是------它不对所有样本的预测结果求平均。它为每个样本单独画出一条"特征-预测"关系曲线。
  • 解读
    • ICE图是PDP的"拆解版"。所有ICE曲线的平均线,就是PDP曲线
    • 通过观察ICE图,我们可以发现是否存在一些行为特异的子群体。如果所有ICE曲线都大致平行,说明该特征对所有样本的影响是同质的。如果曲线杂乱无章,说明存在复杂的交互效应。

一句话总结

  • 特征重要性:告诉你哪个特征是MVP。
  • PDP:告诉你这位MVP对**整个团队(所有样本)**的平均影响是怎样的。
  • ICE:告诉你这位MVP对**每一位队员(单个样本)**的具体影响是怎样的。

数据13.1中的数据为例进行讲解。针对"数据13.1",我们以credit(是否发生违约)为响应变量,以age(年龄)、education(受教育程度)、workyears(工作年限)、resideyears(居住年限)、income(年收入水平)、debtratio(债务收入比)、creditdebt(信用卡负债)、otherdebt(其他负债)为特征变量,使用分类随机森林算法进行。

5.随机森林算法分类问题案例解析

1 变量设置及数据处理

复制代码
from sklearn.ensemble import RandomForestClassifierfrom sklearn.ensemble import RandomForestRegressorfrom sklearn.metrics import confusion_matrixfrom sklearn.metrics import classification_reportfrom sklearn.metrics import cohen_kappa_scorefrom sklearn.inspection import PartialDependenceDisplayfrom mlxtend.plotting import plot_decision_regions#分类问题随机森林算法示例#1  变量设置及数据处理data=pd.read_csv('数据13.1.csv')X = data.iloc[:,1:]#设置特征变量y = data.iloc[:,0]#设置响应变量X_train, X_test, y_train, y_test =  train_test_split(X,y,test_size=0.3, stratify=y, random_state=10)

2 二元Logistic回归、单颗分类决策树算法对比观察

复制代码
#2  二元Logistic回归、单颗分类决策树算法观察model =  LogisticRegression(C=1e10, max_iter=1000,fit_intercept=True)model.fit(X_train, y_train)model.score(X_test, y_test)#单颗分类决策树算法model = DecisionTreeClassifier()path = model.cost_complexity_pruning_path(X_train, y_train)param_grid = {'ccp_alpha': path.ccp_alphas}kfold = StratifiedKFold(n_splits=10, shuffle=True, random_state=10)model = GridSearchCV(DecisionTreeClassifier(random_state=10), param_grid, cv=kfold)model.fit(X_train, y_train)print("最优alpha值:", model.best_params_)     model = model.best_estimator_print("最优预测准确率:", model.score(X_test, y_test))

最优alpha值:{'ccp_alpha': 0.004534462760155709}

最优预测准确率:0.861904761904762

3 装袋法分类算法

复制代码
# 3  装袋法分类算法model=BaggingClassifier(estimator=DecisionTreeClassifier(random_state=10),n_estimators=300,max_samples=0.8,random_state=0)model.fit(X_train, y_train)model.score(X_test, y_test)

0.8666666666666667

4 随机森林分类算法

复制代码
# 4  随机森林分类算法model = RandomForestClassifier(n_estimators=300, max_features='sqrt', random_state=10)model.fit(X_train, y_train)model.score(X_test, y_test)

5 寻求max_features最优参数

复制代码
#5  寻求max_features最优参数scores = []for max_features in range(1, X.shape[1] + 1):    model = RandomForestClassifier(max_features=max_features,                                  n_estimators=300, random_state=10)    model.fit(X_train, y_train)    score = model.score(X_test, y_test)    scores.append(score)index = np.argmax(scores)range(1, X.shape[1] + 1)[index]plt.rcParams['font.sans-serif']=['SimHei'] #正常显示中文plt.plot(range(1, X.shape[1] + 1), scores, 'o-')plt.axvline(range(1, X.shape[1] + 1)[index], linestyle='--', color='k', linewidth=1)plt.xlabel('最大特征变量数')plt.ylabel('最优预测准确率')plt.title('预测准确率随选取的最大特征变量数变化情况')plt.show()plt.savefig('预测准确率随选取的最大特征变量数变化情况.png')print(scores)

6 寻求n_estimators最优参数

复制代码
#6  寻求n_estimators最优参数ScoreAll = []for i in range(100,300,10):    model= RandomForestClassifier(max_features=2,n_estimators = i,random_state = 10)    model.fit(X_train, y_train)    score = model.score(X_test, y_test)    ScoreAll.append([i,score])ScoreAll = np.array(ScoreAll)print(ScoreAll)max_score = np.where(ScoreAll==np.max(ScoreAll[:,1]))[0][0] #找出最高得分对应的索引print("最优参数以及最高得分:",ScoreAll[max_score])  plt.rcParams['font.sans-serif']=['SimHei'] #正常显示中文plt.figure(figsize=[20,5])plt.xlabel('n_estimators')plt.ylabel('预测准确率')plt.title('预测准确率随n_estimators变化情况')plt.plot(ScoreAll[:,0],ScoreAll[:,1])plt.show()plt.savefig('预测准确率随n_estimators变化情况.png')

进一步寻求n_estimators最优参数

复制代码
#进一步寻求n_estimators最优参数ScoreAll = []for i in range(190,210):    model= RandomForestClassifier(max_features=2,n_estimators = i,random_state = 10)    model.fit(X_train, y_train)    score = model.score(X_test, y_test)    ScoreAll.append([i,score])ScoreAll = np.array(ScoreAll)print(ScoreAll)max_score = np.where(ScoreAll==np.max(ScoreAll[:,1]))[0][0] #找出最高得分对应的索引print("最优参数以及最高得分:",ScoreAll[max_score])  plt.figure(figsize=[20,5])plt.xlabel('n_estimators')plt.ylabel('预测准确率')plt.title('预测准确率随n_estimators变化情况')plt.plot(ScoreAll[:,0],ScoreAll[:,1])plt.show()plt.savefig('预测准确率随n_estimators变化情况.png')

7 随机森林特征变量重要性水平分析​​​​

复制代码
# 7  随机森林特征变量重要性水平分析sorted_index = model.feature_importances_.argsort()plt.rcParams['font.sans-serif'] = ['SimHei']#解决图表中中文显示问题plt.barh(range(X_train.shape[1]), model.feature_importances_[sorted_index])plt.yticks(np.arange(X_train.shape[1]), X_train.columns[sorted_index])plt.xlabel('特征变量重要性水平')plt.ylabel('特征变量')plt.title('随机森林特征变量重要性水平分析')plt.tight_layout()plt.show()plt.savefig('随机森林特征变量重要性水平分析.png')

8 绘制部分依赖图与个体条件期望图​​​​​​​

复制代码
#8  绘制部分依赖图与个体条件期望图PartialDependenceDisplay.from_estimator(model, X_train, ['workyears','debtratio'], kind='average')#绘制部分依赖图简称PDP图PartialDependenceDisplay.from_estimator(model, X_train, ['workyears','debtratio'],kind='individual')#绘制个体条件期望图(ICE Plot)PartialDependenceDisplay.from_estimator(model, X_train, ['workyears','debtratio'],kind='both')#绘制个体条件期望图(ICE Plot)plt.show()plt.savefig('部分依赖图与个体条件期望图.png')

9 模型性能评价

复制代码
#9  模型性能评价prob = model.predict_proba(X_test)prob[:5]pred = model.predict(X_test)pred[:5]print(classification_report(y_test,pred))cohen_kappa_score(y_test, pred)#计算kappa得分

热力图

复制代码
#热力图import seaborn as snssns.heatmap(confusion_matrix(y_test, pred), annot=True, fmt='d', cmap='Blues')plt.show()plt.savefig('混淆矩阵热力图.png')

10 绘制ROC曲线

​​​​​​​

复制代码
#10  绘制ROC曲线plt.rcParams['font.sans-serif'] = ['SimHei']#解决图表中中文显示问题from sklearn.metrics import roc_curve,roc_auc_score
# 假设 y_true 和 y_score 是你的真实标签和模型预测的概率得分predict_target_prob=model.predict_proba(X_test)fpr, tpr, thresholds = roc_curve(y_test, predict_target_prob[:,1])# 计算AUC值auc = roc_auc_score(y_test, predict_target_prob[:,1])print("AUC值:", auc)# 绘制 ROC 曲线plt.plot(fpr, tpr, label='ROC Curve area=%.3f'%auc)plt.plot(fpr, fpr, 'k--', linewidth=1)plt.xlabel('False Positive Rate')plt.ylabel('True Positive Rate')plt.title('随机森林分类树算法ROC曲线')plt.legend()plt.show()plt.savefig('随机森林分类树算法ROC曲线.pdf')

11 运用两个特征变量绘制随机森林算法决策边界图

复制代码
#11  运用两个特征变量绘制随机森林算法决策边界图X2 = X.iloc[:, [2,5]]#仅选取workyears、debtratio作为特征变量model = RandomForestClassifier(n_estimators=300, max_features=1, random_state=1)model.fit(X2,y)model.score(X2,y)plot_decision_regions(np.array(X2), np.array(y), model)plt.xlabel('debtratio')#将x轴设置为'debtratio'plt.ylabel('workyears')#将y轴设置为'workyears'plt.title('随机森林算法决策边界')#将标题设置为'随机森林算法决策边界'plt.show()plt.savefig('随机森林算法决策边界.png')