文章目录
利润将流向拥有最好的
网络
、最好的搜索
和最好的排序
算法的公司。—— 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=1∑nP(X=i)log2P(X=i)
硬币1 | P |
---|---|
正面 | 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.5∗log20.5−0.5∗log20.5=1
硬币2 | P |
---|---|
正面 | 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.99∗log20.99−0.01∗log20.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=1∑nP(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=1∑Kpk(1−pk)=1−k=1∑Kpk2
2.2 计算信息增益的例子
-
父节点熵:
− ( 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 −(3014∗log23014)−(3016∗log23016)=0.996 -
子节点加权熵(子节点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 (−(1713∗log21713)−(174∗log2174))∗3017+(−(131∗log2131)−(1312∗log21312))∗3013=0.615
- 信息增益:0.996-0.615=0.381
3. 决策树
3.1 分类树
每次开完大会后,聪哥都会发起一次打篮球的组织,但是由于天气等原因,并不是每次都能组织起来,下表是不同天气下是否能组织起来打篮球的历史记录,我们无法直接单纯的通过Yes 和No的历史频率来判断下次是否能组织起来,因此我们需要借助天气的信息减少不确定性,从而帮助球馆老板预测下能否赚到下次聪哥的场地费。
天气 | 温度 | 湿度 | 是否有风 | 是否打球 |
---|---|---|---|---|
Rainy | Hot | High | False | No |
Rainy | Hot | High | Ture | No |
Overcast | Hot | High | False | Yes |
Sunny | Mild | High | False | Yes |
Sunny | Cool | Normal | False | Yes |
Sunny | Cool | Normal | Ture | No |
Overcast | Cool | Normal | Ture | Yes |
Rainy | Mild | High | False | No |
Rainy | Cool | Normal | False | Yes |
Sunny | Mild | Normal | False | Yes |
Rainy | Mild | Normal | Ture | Yes |
Overcast | Mild | High | Ture | Yes |
Overcast | Hot | Normal | False | Yes |
Sunny | Mild | High | Ture | No |
- 计算根节点的熵
是否打篮球 | 次数 | 概率 | 熵 |
---|---|---|---|
No | 5 | 0.36 | 0.531 |
Yes | 9 | 0.64 | 0.410 |
合计 | 14 | 1 | 0.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.36∗log20.36−0.64∗log20.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=1−i=1∑3pi2=1−(145)2−(149)2=0.459
- 在根节点的基础上,计算其他条件的信息增益,以天气为例
天气 | Yes | No | 次数 | 概率 |
---|---|---|---|---|
Rainy | 2 | 3 | 5 | 0.36 |
Overcast | 4 | 0 | 4 | 0.29 |
Sunny | 3 | 2 | 5 | 0.36 |
天气 | Yes | No | 熵 |
---|---|---|---|
Rainy | 2/5 | 3/5 | 0.971 |
Overcast | 4/4 | 0/4 | 0.000 |
Sunny | 3/5 | 2/5 | 0.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.36∗0.971+0.29∗0+0.36∗0.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
因此第一层分类树的分割特征的最佳选择是天气,得到
抽象一下这棵树得到:
以此类推,通过信息增益得到的最终分类
决策树如下:
进行预测,假设下次的条件如下,带入分类
决策树进行预测看是否能组成局打球
天气 | 温度 | 湿度 | 是否有风 | 是否打球 |
---|---|---|---|---|
Sunny | Mild | High | False | ? |
带入分类
决策树,由于天气=Sunny
,是否有风=False
,得到是否打球=Yes,因此问题得解。
3.2 回归树
同样的例子,只是将最后的目标值是否打球
替换成为打了多少小时
这样的连续值,而非单纯的Yes or No的分类问题,那么又该怎么解?
天气 | 温度 | 湿度 | 是否有风 | 打了多久(一个月小时) |
---|---|---|---|---|
Overcast | Cool | Normal | Ture | 43 |
Overcast | Hot | High | FALSE | 46 |
Overcast | Hot | Normal | FALSE | 44 |
Overcast | Mild | High | Ture | 52 |
Rainy | Cool | Normal | FALSE | 38 |
Rainy | Hot | High | Ture | 30 |
Rainy | Hot | High | FALSE | 25 |
Rainy | Mild | High | FALSE | 35 |
Rainy | Mild | Normal | Ture | 48 |
Sunny | Cool | Normal | Ture | 23 |
Sunny | Cool | Normal | FALSE | 52 |
Sunny | Mild | High | Ture | 30 |
Sunny | Mild | High | FALSE | 45 |
Sunny | Mild | Normal | FALSE | 46 |
回归树就是用树模型做回归问题,每片叶子都输出一个预测值。预测值一般是叶子节点所含训练集元素输出的均值。回归树的分支标准:标准方差(Standard Deviation)。回归树使用某特征将原集合分为多个子集,用标准方差衡量子集中的元素是否相近,越小表示越相近。首先计算根节点的标准方差:
- 数量:n=14
- 均值:x=39.8
- 标准差:SD=9.32
- 变化系数:CV=23%
使用标准差来确定分支,以计算天气分支后的标准差为例:
- | StDev | 数量 | 概率 |
---|---|---|---|
Overcast | 3.49 | 4 | 4/14 |
Rainy | 7.78 | 5 | 5/14 |
Sunny | 10.87 | 5 | 5/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
同理得到其他条件的标准差减小值:
- 温度标准差减小值:0.17
- 湿度标准差减小值:0.28
- 是否有风的标准差减小值:0.29
重复这个过程,使用标注差降低最多的特征进行分支,直到满足某个停止条件,从而得到最终的回归
决策树。
- 当某个分支的变化系数小于某个自定义的阈值,如10%;
- 当前节点包含的元素个数小于某个自定义的阈值,如3;
进行预测,假设下次的条件如下,带入回归
决策树进行预测看看新的一个月能打多少个小时?
天气 | 温度 | 湿度 | 是否有风 | 打了多久(一个月小时) |
---|---|---|---|---|
Sunny | Mild | High | False | ? |
带入回归
决策树,由于天气=Sunny
,是否有风=False
,得到打了多久(一个月小时)=47.7小时,因此问题得解。
4. 集成学习(Bagging)——随机森林
集成学习是通过构建并组合多个学习器来完成学习任务的算法,常用的有两大类:
-
Bagging
:基学习器之间无强烈依赖关系,可以同时生成并行化方法; -
Boosting
:基学习器之间存在强烈依赖的关系,必须串行生成基分类器或者基学习器,此类下篇文章再阐述。
但是不论Bagging还是Boosting,都是可以构建模型解决分类和回归的问题的,并利用模型结合已知特征对用户进行分类或者回归的预测。
4.1 原理和伪代码
4.2 随机森林(Bagging+决策树)
同时训练多个决策树,预测时综合考虑多个结果进行预测,例如取多个节点的均值(回归预测),或者是众数,中位数(分类预测)。
优点:
- 消除了决策树容易过拟合的缺点;
- 减少预测的方差,预测值不会因为训练数据的小变化而导致结果摇摆不定。
随机森林是怎么随机的?
- 从原来历史记录的训练数据集当中,随机取一个子集作为森林中某一个决策树的训练数据集;
- 每一次选择分叉的特征时,限定为在随机选择的特征子集中寻找一个特征。
5. 模型好坏的评估标准
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等单个指标,有一些明显的缺点,容易使模型虽然在评估指标上表现很好,但实际效果不佳。各指标特点如下。
-
单独使用准确率作为评估指标,样本分布对模型影响大。例如,当样本分布极度不均衡时,如负样本占99%,正样本占1%,若模型将全部样本预测为负样本,准确率可以达到99%,但此时模型对正负样本没有区分能力,对正样本的预测能力几乎为0。一种缓解方法是,根据正负样本分布,对正负样本的准确率进行加权求和。
-
单独使用精确率作为评估指标,模型将提高预测为正类的门槛,使得预测为正类的置信度非常高,同时导致把实际为正类的样本预测为负类的数量增多,也就是FN增多,因此模型只能识别非常明显的正类,此时模型对弱正类和负类的区分能力很差。
-
单独使用召回率或者真阳性率作为评估指标,容易导致模型将样本都预测为正类。当模型对所有样本均预测为正类时,FN=0,召回率/TPR为1,即所有正样本都被找到了,但代价是被预测为正类的样本中有大量真实负类(FP),此时模型对负样本的预测能力几乎为0。
-
单独使用假阳性率作为评估指标,效果和单独使用真阳性率类似,当模型将样本全部预测为正类,此时TN=0,FPR=1,模型对负样本的预测能力几乎为0。
-
精确率和召回率之间存在一定的“此消彼长”。当精确率越高,通常预测为正类的门槛越高,则越多的正类容易被预测为负类,导致召回率低。当召回率高,通常预测为正类的门槛低,导致预测为正类的样本里真实为负类的数量增多,使得精确率低。当一个模型能同时做到精确率和召回率都高的时候,说明模型的分类能力比较好。
-
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的关系和解释。
怎么看懂这些指标?
指标 | 全称 | 方向 | 理想值 | 判断口诀 |
---|---|---|---|---|
Accuracy | 准确率 | 越大越好 | 1 | “预测正确比,高则模型强” |
Precision | 精确率 | 越大越好 | 1 | “阳性预测准,高少假警报” |
Recall | 召回率 | 越大越好 | 1 | “漏检要避免,高才抓得全” |
F1-Score | F1分数 | 越大越好 | 1 | “精确召回均,平衡求最高” |
AUC | ROC曲线下面积 | 越大越好 | 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=1∑n(yi−y 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=1∑n(yi−y 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=1−∑i=1n(yi−yˉ)2∑i=1n(yi−y^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
- 完美拟合:
- 用途:对比不同模型对数据的解释能力。
- 特点:表示模型解释的方差比例,范围在
(
−
∞
,
1
]
(- \infty, 1]
(−∞,1]
-
平均绝对百分比误差(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=1∑n yiyi−y^i - 特点:相对误差,适合比例敏感的领域(如销量预测)。
- 缺点:真实值 ( y i = 0 ) ( y_i = 0 ) (yi=0) 时无法计算。
如何选择这些评估指标呢?
场景 | 推荐指标 | 原因 |
---|---|---|
异常值多,需稳健评估 | MAE | 对离群点不敏感 |
强调大误差的惩罚 | MSE/RMSE | 平方放大误差 |
需要解释模型方差贡献 | R² | 直观反映拟合优度 |
业务需求为百分比误差 | MAPE | 结果易沟通(如误差5%) |
多模型对比 | RMSE + R² | 兼顾绝对误差和解释力 |
如何看懂这些评估指标呢?
指标 | 全称 | 方向 | 理想值 | 判断口诀 |
---|---|---|---|---|
MSE | 均方误差 | 越小越好 | 0 | “误差平方和,越小越精准” |
RMSE | 均方根误差 | 越小越好 | 0 | “误差开方值,小即模型优” |
MAE | 平均绝对误差 | 越小越好 | 0 | “绝对误差均,趋零是目标” |
MAPE | 平均绝对百分比误差 | 越小越好 | 0% | “百分比误差,低才显实力” |
R² | 决定系数 | 越大越好 | 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
……
代码项目结构目录参考如下:
具体代码实现如下:
# 先执行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)
接下来是回归的代码实现,但是子曰:“不愤不启,不悱不发。举一隅不以三隅反,则不复也。” ,好吧,此处就留给观众朋友们三隅反了,遇到的问题可以评论区或者找博主私信讨论。