数据科学家是怎么了解你的——决策树&随机森林

利润将流向拥有最好的网络、最好的搜索和最好的排序算法的公司。

—— Eric Emerson Schmidt(前Google CEO、前SUN Microsystem CTO)

1. 背景与目标

1.1 业务背景及技术解决方案

  • 业务背景
      从用户的历史行为数据和交易数据中自动提取规律,并基于这些规律对正在成长的用户做出预测或决策,从而达到更好的用户运营目标。比如实现怎样推荐素材、商品给到最适合的用户人群或个人呢?
  • 技术解决方案
      本身的技术解决方案有很多,此次为了方便讲清楚机器学习的入门,就以最简单的决策树和随机森林来解决此类的业务问题,此类问题本身可以转化为分类或者回归的问题。
    • 分类: 如将用户的历史数据划分到合适的类别中。 例如 判断用户是否购买了商品(二分类 ),答案只有是还是否;
    • 回归:主要用于预测实体的数值型数据,如将实体的历史数据划分到合适的数值, 例如预测房价。

  不管是分类,还是回归,其本质是一样的, 都是对输入做出预测,并且都是监督学习,接下来就以决策树&随机森林为例,且看数据科学家是如何了解你或者其他实体的。

1.2 目标

  • 决策树和随机森林是集成学习的基础,想要学习集成学习的其他算法必须先了解决策树和随机森林。

  • 了解什么是用户特征,目标值,模型……

  • 了解什么是回归问题,什么是分类问题,如何做回归的预测和分类的预测。

2. 基本概念

  • 分类问题(classification):将样本数据划分到合适的类别中。 例如 判断用户是否购买了商品(二分类 ),答案只有是还是否,手写数字的自动识别(多分类);

  • 回归问题(regression):主要用于预测数值型数据。 例如: 股票价格波动的预测,房屋价格的预测等。不管是分类,还是回归,其本质是一样的, 都是对输入做出预测,并且都是监督学习。

  • 信息熵:信息熵是用来衡量信息不确定性的指标,不确定性是一个事件出现不同结果的可能性。计算方法如下,其中P(X=i)为随机变量x=i时概率。
    H ( X ) = − ∑ i = 1 n P ( X = i ) log ⁡ 2 P ( X = i ) H(X)= - \sum_{i=1}^nP(X=i)\log_{2}P(X=i) H(X)=i=1nP(X=i)log2P(X=i)

硬币1P
正面0.5
反面0.5

E n t r o p y = − 0.5 ∗ log ⁡ 2 0.5 − 0.5 ∗ log ⁡ 2 0.5 = 1 Entropy = -0.5 * \log_2^{0.5}-0.5 * \log_2^{0.5}=1 Entropy=0.5log20.50.5log20.5=1

硬币2P
正面0.99
反面0.01

E n t r o p y = − 0.99 ∗ log ⁡ 2 0.99 − 0.01 ∗ log ⁡ 2 0.01 = 0.08 Entropy = -0.99 * \log_2^{0.99}-0.01 * \log_2^{0.01}=0.08 Entropy=0.99log20.990.01log20.01=0.08

  • 条件熵:在已知随机变量Y的条件下,随机变量X的不确定性。
    H ( X / Y = v ) = − ∑ i = 1 n P ( X = i / Y = v ) log ⁡ 2 P ( X = i / Y = v ) H(X/Y=v)=-\sum_{i=1}^nP(X=i/Y=v)\log_2P(X=i/Y=v) H(X/Y=v)=i=1nP(X=i/Y=v)log2P(X=i/Y=v)

2.1 衡量分类是否合理的依据

  • 信息增益信息熵-条件熵,代表了在一个条件下,信息不确定性,减少的程度。

I ( X , Y ) = H ( X ) − H ( X / Y ) I(X,Y)=H(X)-H(X/Y) I(X,Y)=H(X)H(X/Y)

  • 基尼指数(基尼不纯度):表示在样本集合中,一个随机选中的样本被分错的概率。这个概率越小,则说明分类的集合纯度越高,反之越不纯,当分类的集合所有样本为一个类别时,基尼指数=0。

G i n i ( P ) = ∑ k = 1 K p k ( 1 − p k ) = 1 − ∑ k = 1 K p k 2 Gini(P)=\sum_{k=1}^Kp_k(1-p_k)=1-\sum_{k=1}^Kp_k^2 Gini(P)=k=1Kpk(1pk)=1k=1Kpk2

2.2 计算信息增益的例子

图2.1 计算信息增益的例子

  1. 父节点熵:
    − ( 14 30 ∗ l o g 2 14 30 ) − ( 16 30 ∗ l o g 2 16 30 ) = 0.996 -({14 \over 30} * log_2{14 \over 30})-({16 \over 30} * log_2{16 \over 30})=0.996 (3014log23014)(3016log23016)=0.996

  2. 子节点加权熵(子节点A的熵+子节点B的熵):

( − ( 13 17 ∗ l o g 2 13 17 ) − ( 4 17 ∗ l o g 2 4 17 ) ) ∗ 17 30 + ( − ( 1 13 ∗ l o g 2 1 13 ) − ( 12 13 ∗ l o g 2 12 13 ) ) ∗ 13 30 = 0.615 (-({13 \over 17} * log _2 {13 \over 17})-({4 \over 17} * log _2 {4 \over 17}))*{17 \over 30}+(-({1 \over 13} * log _2 {1 \over 13})-({12 \over 13} * log _2 {12 \over 13}))*{13 \over 30}=0.615 ((1713log21713)(174log2174))3017+((131log2131)(1312log21312))3013=0.615

  1. 信息增益:0.996-0.615=0.381

3. 决策树

3.1 分类树

  每次开完大会后,聪哥都会发起一次打篮球的组织,但是由于天气等原因,并不是每次都能组织起来,下表是不同天气下是否能组织起来打篮球的历史记录,我们无法直接单纯的通过Yes 和No的历史频率来判断下次是否能组织起来,因此我们需要借助天气的信息减少不确定性,从而帮助球馆老板预测下能否赚到下次聪哥的场地费。

天气温度湿度是否有风是否打球
RainyHotHighFalseNo
RainyHotHighTureNo
OvercastHotHighFalseYes
SunnyMildHighFalseYes
SunnyCoolNormalFalseYes
SunnyCoolNormalTureNo
OvercastCoolNormalTureYes
RainyMildHighFalseNo
RainyCoolNormalFalseYes
SunnyMildNormalFalseYes
RainyMildNormalTureYes
OvercastMildHighTureYes
OvercastHotNormalFalseYes
SunnyMildHighTureNo
  • 计算根节点的熵
是否打篮球次数概率
No50.360.531
Yes90.640.410
合计1410.940

信息熵:

− 0.36 ∗ l o g 2 0.36 − 0.64 ∗ l o g 2 0.64 = 0.531 + 0.410 = 0.940 -0.36*log_2{0.36}-0.64*log_2{0.64}=0.531+0.410=0.940 0.36log20.360.64log20.64=0.531+0.410=0.940
  如果要算基尼指数,则是:
G i n i = 1 − ∑ i = 1 3 p i 2 = 1 − ( 5 14 ) 2 − ( 9 14 ) 2 = 0.459 Gini=1-\sum_{i=1}^3p_i^2=1-({5 \over 14})^2-({9 \over 14})^2=0.459 Gini=1i=13pi2=1(145)2(149)2=0.459

  • 在根节点的基础上,计算其他条件的信息增益,以天气为例
天气YesNo次数概率
Rainy2350.36
Overcast4040.29
Sunny3250.36
天气YesNo
Rainy2/53/50.971
Overcast4/40/40.000
Sunny3/52/50.971

  使用天气的条件熵
0.36 ∗ 0.971 + 0.29 ∗ 0 + 0.36 ∗ 0.971 = 0.69 0.36 * 0.971+0.29*0+0.36*0.971=0.69 0.360.971+0.290+0.360.971=0.69
  信息增益:0.940-0.69=0.25

  同理得到:

  使用温度的信息增益=0.02

  使用湿度得到的信息增益=0.15

  使用是否有风的信息增益=0.05

  使用天气的加权基尼指数来算:
5 14 ∗ ( 1 − ( 2 5 ) 2 − ( 3 5 ) 2 ) + 4 14 ∗ ( 1 − ( 4 4 ) 2 − ( 0 4 ) 2 ) + 5 14 ∗ ( 1 − ( 3 5 ) 2 − ( 2 5 ) 2 ) = 0.342 {5 \over14 } * (1-({2 \over 5})^2-({3 \over 5})^2)+{4 \over14 } * (1-({4 \over 4})^2-({0 \over 4})^2)+{5 \over14 } * (1-({3 \over 5})^2-({2 \over 5})^2)=0.342 145(1(52)2(53)2)+144(1(44)2(40)2)+145(1(53)2(52)2)=0.342
  天气的Gini增益:0.459-0.342=0.117

  同理得到温度的Gini增益:0.0185

  湿度的基尼增益:0.0916

  是否有风的基尼增益:0.0304

  因此第一层分类树的分割特征的最佳选择是天气,得到

图3.1 通过信息增益拆分第一层决策树

  抽象一下这棵树得到:

图3.2 通过信息增益拆分第一层决策树抽象版本

  以此类推,通过信息增益得到的最终分类决策树如下:

图3.3 通过信息增益得到的最终分类决策树

  进行预测,假设下次的条件如下,带入分类决策树进行预测看是否能组成局打球

天气温度湿度是否有风是否打球
SunnyMildHighFalse

  带入分类决策树,由于天气=Sunny是否有风=False得到是否打球=Yes,因此问题得解。

3.2 回归树

  同样的例子,只是将最后的目标值是否打球替换成为打了多少小时这样的连续值,而非单纯的Yes or No的分类问题,那么又该怎么解?

天气温度湿度是否有风打了多久(一个月小时)
OvercastCoolNormalTure43
OvercastHotHighFALSE46
OvercastHotNormalFALSE44
OvercastMildHighTure52
RainyCoolNormalFALSE38
RainyHotHighTure30
RainyHotHighFALSE25
RainyMildHighFALSE35
RainyMildNormalTure48
SunnyCoolNormalTure23
SunnyCoolNormalFALSE52
SunnyMildHighTure30
SunnyMildHighFALSE45
SunnyMildNormalFALSE46

  回归树就是用树模型做回归问题,每片叶子都输出一个预测值。预测值一般是叶子节点所含训练集元素输出的均值。回归树的分支标准:标准方差(Standard Deviation)。回归树使用某特征将原集合分为多个子集,用标准方差衡量子集中的元素是否相近,越小表示越相近。首先计算根节点的标准方差:

  • 数量:n=14
  • 均值:x=39.8
  • 标准差:SD=9.32
  • 变化系数:CV=23%

  使用标准差来确定分支,以计算天气分支后的标准差为例:

-StDev数量概率
Overcast3.4944/14
Rainy7.7855/14
Sunny10.8755/14

  天气的加权标准差:
( 4 14 ) ∗ 3.49 + ( 5 14 ) ∗ 7.78 + ( 5 14 ) ∗ 10.87 = 7.66 ({4 \over 14})*3.49+({5 \over 14})*7.78+({5 \over 14})*10.87=7.66 (144)3.49+(145)7.78+(145)10.87=7.66
  得到天气的标准差的减小值=9.32-7.66=1.66

  同理得到其他条件的标准差减小值:

  1. 温度标准差减小值:0.17
  2. 湿度标准差减小值:0.28
  3. 是否有风的标准差减小值:0.29

  重复这个过程,使用标注差降低最多的特征进行分支,直到满足某个停止条件,从而得到最终的回归决策树。

  1. 当某个分支的变化系数小于某个自定义的阈值,如10%;
  2. 当前节点包含的元素个数小于某个自定义的阈值,如3;

图3.4 通过信息增益得到的最终回归决策树

  进行预测,假设下次的条件如下,带入回归决策树进行预测看看新的一个月能打多少个小时?

天气温度湿度是否有风打了多久(一个月小时)
SunnyMildHighFalse

  带入回归决策树,由于天气=Sunny是否有风=False得到打了多久(一个月小时)=47.7小时,因此问题得解。

4. 集成学习(Bagging)——随机森林

  集成学习是通过构建并组合多个学习器来完成学习任务的算法,常用的有两大类:

  • Bagging:基学习器之间无强烈依赖关系,可以同时生成并行化方法;

  • Boosting:基学习器之间存在强烈依赖的关系,必须串行生成基分类器或者基学习器,此类下篇文章再阐述。

  但是不论Bagging还是Boosting,都是可以构建模型解决分类和回归的问题的,并利用模型结合已知特征对用户进行分类或者回归的预测。

4.1 原理和伪代码

图4.1 bagging算法的原理和伪代码

4.2 随机森林(Bagging+决策树)

  同时训练多个决策树,预测时综合考虑多个结果进行预测,例如取多个节点的均值(回归预测),或者是众数,中位数(分类预测)。

  优点:

  1. 消除了决策树容易过拟合的缺点;
  2. 减少预测的方差,预测值不会因为训练数据的小变化而导致结果摇摆不定。

  随机森林是怎么随机的?

  1. 从原来历史记录的训练数据集当中,随机取一个子集作为森林中某一个决策树的训练数据集;
  2. 每一次选择分叉的特征时,限定为在随机选择的特征子集中寻找一个特征。

5. 模型好坏的评估标准

5.1 分类问题——技术指标

图5.1 分类问题的真实类别和预测类别

  这里有两类概念,类别和预测对错。类别有正负,即Positive和Negative,预测包括True和False,True表示预测正确,False表示预测错误。 真实类别和预测类别之间的关系有如下四种:

  • TP(True Positive): 当真实类别是True,预测为Positive,预测正确;
  • FN(False Negative): 当真实类别是True,预测为Negative,预测错误;
  • FP(Fasle Positive): 当真实类别是False,预测为Positive,预测错误;
  • TN(True Negative): 当真实类别是False,预测为Negative,预测正确。

  几个常用的指标:

  • 准确率/正确率(accuracy) = 所有预测正确的样本数量 /总样本数量,即 (TP+TN) / (TP+TN+FP+FN)。

  • 精确率(precision) = 预测正确的正类样本数量 / 预测为正类的样本数量 TP/(TP+FP),即预测为正的样本中,真正的正类占比。

  • 召回率(recall) = 预测正确的正类样本数量 / 实际为正类的样本数量 TP/(TP+FN),即所有正类中,正确预测的占比。

  • F1值 = 精确率 * 召回率 * 2 / (精确率 + 召回率) (F 值即为精确率和召回率的调和均值)

  • 真阳性率(TPR) = TP/(TP+FN),即所有正类样本中被正确分为正类的比例,计算方式和召回率相同。

  • 假阳性率(FPR) = FP/(FP+TN),即所有负类样本中被错误分为正类的比例。

  • AUC=随着预测为正类的阈值变化,TPR和FPR相应地变化,因此可以得到以TPR为纵坐标和FPR为横坐标的曲线,即ROC曲线,,因此可以得到AUC。

  为什么分类问题一般用AUC作为评估标准呢,是因为在评估模型时,若使用准确率、精确率、召回率、TPR、FPR等单个指标,有一些明显的缺点,容易使模型虽然在评估指标上表现很好,但实际效果不佳。各指标特点如下。

  1. 单独使用准确率作为评估指标,样本分布对模型影响大。例如,当样本分布极度不均衡时,如负样本占99%,正样本占1%,若模型将全部样本预测为负样本,准确率可以达到99%,但此时模型对正负样本没有区分能力,对正样本的预测能力几乎为0。一种缓解方法是,根据正负样本分布,对正负样本的准确率进行加权求和。

  2. 单独使用精确率作为评估指标,模型将提高预测为正类的门槛,使得预测为正类的置信度非常高,同时导致把实际为正类的样本预测为负类的数量增多,也就是FN增多,因此模型只能识别非常明显的正类,此时模型对弱正类和负类的区分能力很差。

  3. 单独使用召回率或者真阳性率作为评估指标,容易导致模型将样本都预测为正类。当模型对所有样本均预测为正类时,FN=0,召回率/TPR为1,即所有正样本都被找到了,但代价是被预测为正类的样本中有大量真实负类(FP),此时模型对负样本的预测能力几乎为0。

  4. 单独使用假阳性率作为评估指标,效果和单独使用真阳性率类似,当模型将样本全部预测为正类,此时TN=0,FPR=1,模型对负样本的预测能力几乎为0。

  5. 精确率和召回率之间存在一定的“此消彼长”。当精确率越高,通常预测为正类的门槛越高,则越多的正类容易被预测为负类,导致召回率低。当召回率高,通常预测为正类的门槛低,导致预测为正类的样本里真实为负类的数量增多,使得精确率低。当一个模型能同时做到精确率和召回率都高的时候,说明模型的分类能力比较好。

  6. TPR和FPR之间存在正相关性。从混淆矩阵和TPR以及FPR的定义可以看出,当TPR越高,说明FN越少,说明预测为正类的越多,则TP和FP越多,而样本总量不变,因此FPR越多。极端情况下,当没有样本被预测为正类,则TPR和FPR均为0,当所有样本均被预测为正类,TPR和FPR均为1。

  那么AUC有什么优势呢?

  单个指标对模型评估存在多种问题,从而无法真实地评估模型的性能,因此通常综合考虑多个指标。比如F值,综合考虑精确率和召回率,比如AUC,综合考虑TPR和FPR。

  AUC作为指标衡量模型时,不依赖于分类阈值的选取,而准确率、精确率、召回率、F1值对阈值的选取依赖大,不同的阈值会带来不同的结果,而从AUC的定义(ROC的生成)知道,AUC是根据所有分类阈值得到的,因此比单独依赖一个分类阈值的指标更有优势。AUC体现的是对样本的排序能力,与具体的分值无关,和推荐系统中的大多数业务场景更贴合,因为大多数业务场景关心item之间的相对序而不关心item的预测分。

  AUC对正负样本比例不敏感,也是它在业界被广泛使用的原因,下图是ROC曲线和AUC的关系和解释。

图5.2 ROC和AUC曲线的关系

  怎么看懂这些指标?

指标全称方向理想值判断口诀
Accuracy准确率越大越好1“预测正确比,高则模型强”
Precision精确率越大越好1“阳性预测准,高少假警报”
Recall召回率越大越好1“漏检要避免,高才抓得全”
F1-ScoreF1分数越大越好1“精确召回均,平衡求最高”
AUCROC曲线下面积越大越好1“排序能力强,1是天花板”

5.2 回归问题——技术指标

  在回归任务中,AUC(Area Under the ROC Curve)并不适用,因为AUC是专门用于评估二分类模型的指标,回归模型的评估需要使用其他针对连续型预测值的指标。为什么回归模型不能用AUC?

  • AUC的本质: AUC基于分类模型的真阳性率(TPR)假阳性率(FPR),依赖预测概率的排序能力(如判断正类概率是否高于负类)。

  • 回归问题预测的是连续值(如房价、温度),没有“类别”概念,因此无法计算TPR/FPR。

  • 例外情况: 如果强行将回归问题转化为二分类问题(例如预测房价是否高于某个阈值),此时可用AUC,但这已不属于纯回归任务。

   回归模型的常用评估指标,以下是回归任务中最常用的评估指标及其适用场景。

  • 均方误差(MSE, Mean Squared Error)
    M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE =\frac{1}{n}\sum _{i=1}^n(y_i- \widehat y_i)^2 MSE=n1i=1n(yiy i)2

    • 特点:放大较大误差(平方项),对异常值敏感。
    • 用途:适用于重视大误差的场景(如金融风险预测)。
  • 均方根误差(RMSE, Root MSE)
    M E S \sqrt {MES} MES

    • 特点:与原始数据量纲一致,更易解释。
    • 用途:最常用的回归指标(如房价预测)。
  • 平均绝对误差(MAE, Mean Absolute Error)
    M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) MSE =\frac{1}{n}\sum _{i=1}^n(y_i- \widehat y_i) MSE=n1i=1n(yiy i)

    • 特点:对异常值鲁棒,但无法体现误差方向。
    • 用途:需要稳健评估时(如医疗数据)。
  • R²(决定系数)
    R 2 = 1 − ∑ i = 1 n ( y i − y ^ i ) 2 ∑ i = 1 n ( y i − y ˉ ) 2 R^2 = 1 - \frac{\sum_{i=1}^n (y_i - \hat{y}_i)^2}{\sum_{i=1}^n (y_i - \bar{y})^2} R2=1i=1n(yiyˉ)2i=1n(yiy^i)2

    • 特点:表示模型解释的方差比例,范围在 ( − ∞ , 1 ] (- \infty, 1] (,1]
      • 完美拟合:
        R 2 = 1 R^2 = 1 R2=1
      • 等于用均值预测:
        R 2 = 0 R^2 = 0 R2=0
      • 模型比均值预测还差:
        R 2 < 0 R^2 < 0 R2<0
    • 用途:对比不同模型对数据的解释能力。
  • 平均绝对百分比误差(MAPE)
    MAPE = 100 % n ∑ i = 1 n ∣ y i − y ^ i y i ∣ \text{MAPE} = \frac{100\%}{n} \sum_{i=1}^n \left| \frac{y_i - \hat{y}_i}{y_i} \right| MAPE=n100%i=1n yiyiy^i

    • 特点:相对误差,适合比例敏感的领域(如销量预测)。
    • 缺点:真实值 ( y i = 0 ) ( y_i = 0 ) (yi=0) 时无法计算。

  如何选择这些评估指标呢?

场景推荐指标原因
异常值多,需稳健评估MAE对离群点不敏感
强调大误差的惩罚MSE/RMSE平方放大误差
需要解释模型方差贡献直观反映拟合优度
业务需求为百分比误差MAPE结果易沟通(如误差5%)
多模型对比RMSE + R²兼顾绝对误差和解释力

  如何看懂这些评估指标呢?

指标全称方向理想值判断口诀
MSE均方误差越小越好0“误差平方和,越小越精准”
RMSE均方根误差越小越好0“误差开方值,小即模型优”
MAE平均绝对误差越小越好0“绝对误差均,趋零是目标”
MAPE平均绝对百分比误差越小越好0%“百分比误差,低才显实力”
决定系数越大越好1“解释方差比,接近1最佳”

5.2 业务指标

  • CTR (Click-Through Rate - 点击率),越大越好。

    • 是什么: 看到你东西的人里,有多少人点进去看了。
    • 简单说: 吸引眼球的能力。比如:你发了个朋友圈广告,100个人看到了,有5个人点开看了详情,CTR就是5%。网页上有个按钮,1000个人看到了这个按钮,50个人点了它,CTR就是5%。
  • CVR (Conversion Rate - 转化率),越大越好。

    • 是什么: 点进去看了的人里,有多少人真的做了你希望的事(比如买东西、注册、下载)。
    • 简单说: 让人行动的能力。比如:100个人点进了你的商品页面(点击),其中10个人下单买了(转化),CVR就是10%。50个人点了你的“注册试用”按钮(点击),其中5个人完成了注册(转化),CVR就是10%。
  • 停留时长:每次点击进入在商品详情页停留的时间,越大越好。

  • 每次Session内浏览的推荐商品数:每次用户来到平台后,点击查看了几个商品,越大越好。

  • 人气率:点进去看了的人里,有多少人真的做收藏。越大越好

  • ……

  如何找到指标的基准(Benchmark)来衡量某一次的决策树或者随机森林模型是否优秀呢?

  • 第一期:一般只要比默认的业务规则,或者通用业务规则的点击率,转化率等优秀,就算是成功的。
  • 第二期以及后续:比上一期的点击率,转化率等优秀,就算是成功的。
  • 不论决策树、随机森林还是后续的其他算法,都会有个瓶颈,当指标优化达到瓶颈时,维持团队认可的正常波动即可。

6. 代码实战

6.1 以决策树为例的代码片段

分类任务

from sklearn.tree import DecisionTreeClassifier

回归任务

from sklearn.tree import DecisionTreeRegressor

分类决策树常用参数

clf = DecisionTreeClassifier(
    criterion='gini',  # 或 'entropy'
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    random_state=42
)

回归决策树常用参数

reg = DecisionTreeRegressor(
    criterion='squared_error',  # 或 'friedman_mse', 'absolute_error', 'poisson'
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    random_state=42
)

分类评估

from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score

y_pred = clf.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))
print("Precision:", precision_score(y_test, y_pred))

回归评估

from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score

y_pred = reg.predict(X_test)
print("MSE:", mean_squared_error(y_test, y_pred))
print("R2 Score:", r2_score(y_test, y_pred))

分类样例

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 加载数据
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.2, random_state=42)

# 创建分类决策树
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(X_train, y_train)

# 预测和评估
y_pred = clf.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))

回归样例

from sklearn.datasets import load_diabetes
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# 加载数据
data = load_diabetes()
X, y = data.data, data.target

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

# 创建回归决策树
reg = DecisionTreeRegressor(max_depth=3, random_state=42)
reg.fit(X_train, y_train)

# 预测和评估
y_pred = reg.predict(X_test)
print("MSE:", mean_squared_error(y_test, y_pred))

6.2 以决策树/随机森林为例的分类/回归

  根据员工的行为,预测员工是否会离职,数据集样例如下,完整文件可以参考:HR_Analysis

satisfaction_level,last_evaluation,number_project,average_montly_hours,time_spend_company,Work_accident,left,promotion_last_5years,department,salary
0.38,0.53,2,157,3,0,1,0,sales,low
0.8,0.86,5,262,6,0,1,0,sales,medium
0.11,0.88,7,272,4,0,1,0,sales,medium
0.72,0.87,5,223,5,0,1,0,sales,low
0.37,0.52,2,159,3,0,1,0,sales,low
0.41,0.5,2,153,3,0,1,0,sales,low
0.1,0.77,6,247,4,0,1,0,sales,low
0.92,0.85,5,259,5,0,1,0,sales,low
……

  代码项目结构目录参考如下:
图6.1 代码项目结构目录参考

  具体代码实现如下:

# 先执行pip install scikit-learn 安装sklearn
from sklearn.metrics import roc_auc_score
from sklearn.metrics import classification_report
from sklearn.ensemble import RandomForestClassifier
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import export_graphviz   # 需要先在MacOS内执行brew install graphviz 安装graphviz
from six import StringIO
from IPython.display import Image  # pip install ipython
import pydotplus  # pip install pydotplus
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import joblib  # 用于模型保存,使用joblib(推荐,适合大模型)
import pickle  #

from sklearn.metrics import roc_curve



if __name__ == '__main__':
    pd.set_option('display.max_rows', 500)
    pd.set_option('display.max_columns', 100)
    pd.set_option('display.width', 1000)

    df = pd.read_csv('data/HR_comma_sep.csv', index_col=None)
    df.isnull().any()
    print(df.head())

    ## rename
    df = df.rename(columns={'satisfaction_level': 'satisfaction',
                            'last_evaluation': 'evaluation',
                            'number_project': 'projectCount',
                            'average_montly_hours': 'averageMonthlyHours',
                            'time_spend_company': 'yearsAtCompany',
                            'Work_accident': 'workAccident',
                            'promotion_last_5years': 'promotion',
                            'department': 'department',
                            'left': 'turnover'
                            })


    front = df["turnover"]

    # 将string类型转化为整数类型
    df["department"] = df["department"].astype('category').cat.codes
    df["salary"] = df["salary"].astype('category').cat.codes

    print(df.head())

    # 产生我们的特征向量矩阵X, 对应的目标值变量y
    target_name = 'turnover'
    X = df.drop('turnover', axis=1)
    y = df[target_name]

    # 将数据分为训练和测试数据集

    # 特征向量矩阵X, 对应的目标值变量y
    # test_size=0.15表示测试集比率占15%
    # random_state=123 设置随机数生成器的种子,保证每次运行代码时划分结果相同(可重复性),如果不设置,每次划分结果会不同
    # stratify=y 按标签y的类别比例进行分层抽样,确保训练集和测试集中各类别比例与原始数据集一致,特别适用于分类问题中类别不均衡的情况,比如y=0的占40%,y=1的占60%,则每次的测试集和训练集的y值分配应该遵循这个比例
    # X_train:训练集特征(85 % 的原始数据)
    # X_test:测试集特征(15 % 的原始数据)
    # y_train:训练集标签(与X_train对应)
    # y_test:测试集标签(与X_test对应)

    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=123, stratify=y)

    # 实例化决策树及相关配置
    dtree = tree.DecisionTreeClassifier(
        criterion ='entropy',  ## 以信息增益来划分,亦可以填值=gini,用基尼不纯度来
        ## max_depth=3, # 定义树的深度,可以用来防止过拟合,一般是3-10层
        min_weight_fraction_leaf=0.01 # 定义叶子节点最少需要包含多少个样本(使用百分比表达),防止过拟合
    )

    # 训练
    dtree = dtree.fit(X_train, y_train)

    # 指标计算
    dt_roc_auc = roc_auc_score(y_test, dtree.predict(X_test))

    ## 打印出AUC
    print("决策树 AUC=%2.2f"%dt_roc_auc)

    ## 打印出决策数的准确率,召回率,f1-score,支撑样本数等数据
    print(classification_report(y_test, dtree.predict(X_test)))

    # 决策树可视化

    # 因为第0列是目标变量,所以排除第0列,从第一列开始拿到特征矩阵
    feature_names=df.columns[1:]

    # 创建一个内存中的字符串缓冲区(StringIO),用于临时存储决策树的DOT格式数据。也可以写成out_file='tree.dot'
    dot_data = StringIO()

    # 导出决策树为DOT格式
    export_graphviz(
        dtree,
        out_file=dot_data,
        filled=True,      # 填充颜色表示类别
        rounded=True,     # 圆角节点
        special_characters=True,  # 支持特殊字符
        class_names=['0','1'],  # 类别标签名称
        feature_names=feature_names  # 特征名称
    )

    # 生成图形对象,将DOT格式数据转换为pydotplus图形对象,需要先安装需pydotplus和Graphviz软件,
    graph = pydotplus.graph_from_dot_data(dot_data.getvalue())

    # 将决策树保存到文件路径 “image/decision_tree.png”,确保该路径真实存在,否则会报错文件路径异常
    graph.write_png('image/decision_tree.png')

    # 在Jupyter中显示图形
    Image(graph.create_png())

    # 获取特征重要性,从训练好的决策树模型 dtree 中提取特征重要性数组。
    # 一维数组,长度等于特征数量,每个元素表示对应特征的重要性(值范围 0~1,总和为 1)。
    # 例如:若模型有 3 个特征,可能返回 [0.6, 0.3, 0.1],表示第一个特征最重要。
    importances = dtree.feature_importances_

    #print(importances)

    # 获取特征名称
    feat_names = df.drop('turnover', axis=1).columns

    # 排序
    # np.argsort(importances):返回特征重要性的升序排列索引(从小到大)。例如,若 importances = [0.1, 0.6, 0.3],则返回 [0, 2, 1]。
    # [::-1]:将索引数组反转,变为降序排列(从大到小)。接上例,反转后为[1, 2, 0]。
    indices = np.argsort(importances)[::-1]
    # print(indices)

    # 绘图
    plt.figure(figsize=(8, 15))  # 设置画布大小
    plt.title("Feature importances by Decision Tree")  # 设置标题

    # 绘制条形图(特征重要性)
    plt.bar(
        range(len(indices)),  # X轴位置,返回indices个数的横坐标
        importances[indices], # Y轴位置,各特征的重要性
        color='lightblue',    # 条形颜色
        align="center"        # 条形居中对齐刻度
    )

    # 绘制折线图, 累积重要性曲线
    plt.step(
        range(len(indices)), # X轴位置,返回indices个数的横坐标
        np.cumsum(importances[indices]),   # Y轴位置,累积重要性值
        where='mid',  # 阶梯图中点对齐
        label='Cumulative' # 图列标签
    )

    # 设置X轴刻度(特征名称)
    plt.xticks(
        range(len(indices)), # X轴位置,返回indices个数的横坐标
        feat_names[indices], # Y轴位置,各特征的重要性从大到小的排序的特征名称
        rotation='vertical',  # 垂直旋转标签(避免重叠)
        fontsize=8   # 字体大小
    )

    # 调整X轴范围,将X轴范围略微扩展(-1 到 N),避免条形紧贴边界。
    plt.xlim([-1, len(indices)])

    # plt.show()


    ## 随机森林
    ## 实例化随机森林的配置
    # 实例化随机森林
    rf = RandomForestClassifier(
        criterion='entropy',   ## 以信息增益来划分,亦可以填值=gini,用基尼不纯度来
        n_estimators=3,
        max_depth=None,  # 定义树的深度, 可以用来防止过拟合
        min_samples_split=10,  # 定义至少多少个样本的情况下才继续分叉
        # min_weight_fraction_leaf=0.02 # 定义叶子节点最少需要包含多少个样本(使用百分比表达), 防止过拟合
    )
    # 模型训练
    rf.fit(X_train, y_train)
    # 计算指标参数
    rf_roc_auc = roc_auc_score(y_test, rf.predict(X_test))
    print("随机森林 AUC = %2.2f" % rf_roc_auc)
    print(classification_report(y_test, rf.predict(X_test)))

    # 特征的重要程度
    importances = rf.feature_importances_
    # 特征名称
    feat_names = df.drop(['turnover'], axis=1).columns
    # 排序
    indices = np.argsort(importances)[::-1]


    # 绘图
    plt.figure(figsize=(8, 15))  # 设置画布大小
    plt.title("Feature importances by Decision Tree")  # 设置标题

    # 绘制条形图(特征重要性)
    plt.bar(
        range(len(indices)),  # X轴位置,返回indices个数的横坐标
        importances[indices],  # Y轴位置,各特征的重要性
        color='lightblue',  # 条形颜色
        align="center"  # 条形居中对齐刻度
    )

    # 绘制折线图, 累积重要性曲线
    plt.step(
        range(len(indices)),  # X轴位置,返回indices个数的横坐标
        np.cumsum(importances[indices]),  # Y轴位置,累积重要性值
        where='mid',  # 阶梯图中点对齐
        label='Cumulative'  # 图列标签
    )

    # 设置X轴刻度(特征名称)
    plt.xticks(
        range(len(indices)),  # X轴位置,返回indices个数的横坐标
        feat_names[indices],  # Y轴位置,各特征的重要性从大到小的排序的特征名称
        rotation='vertical',  # 垂直旋转标签(避免重叠)
        fontsize=8  # 字体大小
    )

    # 调整X轴范围,将X轴范围略微扩展(-1 到 N),避免条形紧贴边界。
    plt.xlim([-1, len(indices)])

    # plt.show()

    # ROC 图

    # 计算ROC曲线
    rf_fpr, rf_tpr, rf_thresholds = roc_curve(y_test, rf.predict_proba(X_test)[:, 1])
    dt_fpr, dt_tpr, dt_thresholds = roc_curve(y_test, dtree.predict_proba(X_test)[:, 1])

    print("rf_thresholds值:\n",rf_thresholds)

    print("rf_thresholds值:\n", dt_thresholds)
    plt.figure()

    # 随机森林 ROC
    plt.plot(rf_fpr, rf_tpr, label='Random Forest (area = %0.2f)' % rf_roc_auc)

    # 决策树 ROC
    plt.plot(dt_fpr, dt_tpr, label='Decision Tree (area = %0.2f)' % dt_roc_auc)
    # 绘图
    plt.xlim([0.0, 1.0])
    plt.ylim([0.0, 1.05])
    plt.xlabel('False Positive Rate')
    plt.ylabel('True Positive Rate')
    plt.title('ROC Graph')
    plt.legend(loc="lower right")
    plt.show()




    # 保存模型
    joblib.dump(dtree, 'model/decision_tree.joblib')
    joblib.dump(rf, 'model/random_forest_model.joblib')

    # 保存特征名称(预测时需对齐顺序)
    feature_names=list(X.columns)
    with open('model/feature_names.pkl', 'wb') as f:
        pickle.dump(feature_names, f)

    # 保存特征名称(预测时需要对其顺序)
    dt_model = joblib.load('model/decision_tree.joblib')
    rf_model = joblib.load('model/decision_tree.joblib')

    with open('model/feature_names.pkl', 'rb') as f:
        feature_names = pickle.load(f)

    # 'feature2': [0.8, 0.86, 5, 262, 6, 0, 0, 7, 2],
    new_data = pd.DataFrame(
        {
            'satisfaction': [ 0.38,0.8],
            'evaluation': [0.53,0.86],
            'projectCount':[2,5],
            'averageMonthlyHours':[157,262],
            'yearsAtCompany':[3,6],
            'workAccident':[0,0],
            'promotion':[0,0],
            'department':[7,7],
            'salary':[1,2]
        }
    )[feature_names]

    # 3. 进行预测
    dt_preds = dt_model.predict(new_data)
    rf_preds = rf_model.predict(new_data)

    dt_probs = dt_model.predict_proba(new_data)
    rf_proba = rf_model.predict_proba(new_data)  # 获取概率

    print("决策树预测结果:", dt_preds)
    print("随机森林预测结果:", rf_preds)

    print("决策树预测概率:",dt_probs)

    print("随机森林预测概率:", rf_proba)

    # 4. 批量预测示例
    batch_data = pd.read_csv('data/new_data.csv')[feature_names]
    batch_rf_preds = rf_model.predict(batch_data)
    batch_dt_preds = dt_model.predict(batch_data)

    batch_dt_probs = dt_model.predict_proba(new_data)
    batch_rf_proba = rf_model.predict_proba(new_data)  # 获取概率

    batch_data['rf_prediction'] = batch_rf_preds  # 添加预测列
    # batch_data['batch_dt_probs'] = batch_dt_probs

    batch_data['dt_prediction'] = batch_dt_preds  # 添加预测列
    # batch_data['batch_rf_proba'] = batch_rf_proba

    batch_data.to_csv('output/predicted_results.csv', index=False)

    batch_dt_probs=pd.DataFrame(batch_rf_proba)
    batch_rf_proba=pd.DataFrame(batch_rf_proba)


    batch_dt_probs.to_csv('output/batch_dt_probs.csv', index=False)
    batch_rf_proba.to_csv('output/batch_rf_proba.csv', index=False)



   接下来是回归的代码实现,但是子曰:“不愤不启,不悱不发。举一隅不以三隅反,则不复也。” ,好吧,此处就留给观众朋友们三隅反了,遇到的问题可以评论区或者找博主私信讨论。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╭⌒若隐_RowYet——大数据

谢谢小哥哥,小姐姐的巨款

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值