决策树、随机森林的决策路径与预测概率
最近笔者在学习机器学习内容的过程中,发现自己越来越想搞清楚每个算法的运行机制,决策树和随机森林在机器学习中虽然已经是经典到不能再经典的算法了,但是笔者仍然发现自己对这两个算法存在一些模棱两可的理解,这种模棱两可主要在两方面:决策路径的生成和预测概率的计算,本文通过sklearn实现这两个算法,并对两个算法的决策路径和预测概率的计算进行了辨析和理解。
机器学习笔记------随机森林分类的sklearn实现 - 掘金 (juejin.cn)
机器学习笔记------分类决策树的Sklearn实现 - 掘金 (juejin.cn)
首先导入需要用的包,并生成半月型数据集。
python
from sklearn.ensemble import RandomForestClassifier as rfc
import pandas as pd
#import graphviz#用于绘制决策树的包
from sklearn.tree import DecisionTreeClassifier as dtc
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
import numpy as np
from sklearn import tree
python
#构建二维数据集
moon=make_moons(200,noise=0.2,random_state=1)
moon1=pd.DataFrame({"f1":moon[0][:,0],"f2":moon[0][:,1],"label":moon[1]})
moon1['f1']=moon1['f1'].apply(lambda x:round(x,2))
moon1['f2']=moon1['f2'].apply(lambda x:round(x,2))
plt.scatter(moon1['f1'],moon1['f2'],c=moon1['label'])
plt.show()
查看部分数据:
python
moon1.head()
| | f1 | f2 | label |
| 0 | -0.31 | 0.90 | 0 |
| 1 | 0.39 | 1.30 | 0 |
| 2 | 0.49 | 0.98 | 0 |
| 3 | -0.13 | 0.28 | 1 |
4 | 1.73 | 0.17 | 1 |
---|
1.决策树
1.1决策路径
首先训练一个最深深度为3(方便观察)的决策树,并绘制决策树的节点图,节点图上有各个节点的编号
python
#moon2=pd.concat([moon1,moon1])
dtc1=dtc(max_features='auto', max_depth=3,
random_state=0).fit(moon1[['f1','f2']],moon1['label'])
plt.figure(figsize=(8,8))
tree.plot_tree(dtc1,
feature_names=['f1','f2'],
filled=True,#填充颜色
class_names=["0","1"],
rounded=False,#边缘圆润与否
node_ids=True#写出每个节点的序号
)
plt.show()
查看决策路径,主要是查看生成的决策树,**在对某个样本进行决策的时候经过哪些节点,并最终到达哪个叶子结点。**以样本[[1.73,0.17]]
为例,结合上面的图片,该样本在本决策树最终的决策路径应该是:
这里主要用到的函数是决策树中的decision_path()
方法:
参数:
X: 需要进行预测、计算的样本,是二维格式,具体shape=[样本数量,特征数量]
返回值:
indicator: 形状为(n_samples, n_nodes)的稀疏矩阵, 返回一个节点指示矩阵,非零元素的位置表示样本在预测过程中经过了那些节点
python
print(dtc1.decision_path([[1.73,0.17]]))
#这里只测试一个样本
scss
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
以上是样本[[1.73,0.17]]
的决策路径,表示样本在预测过程中,经过了0、1、3、5四个节点,其中5为最终的叶子结点。与前面绘制的决策树节点图进行对照,结果一致。
概率
我们使用决策树的predict_proba
,决策树预测出来的概率与其他的类似逻辑回归的计算方法不一样,决策树判断某个样本所属某一类别的概率,主要是根据它被分到的叶子结点中的每个类别的数量来计算,即每个类别在叶子结点中的占比。
python
dtc1.predict_proba([[1.73,0.17]])
lua
array([[0.34782609, 0.65217391]])
测试一下,我们知道最终样本被分到的叶子结点#5,样本的数量为[8,15],则预测为1的概率为以下:
python
print(15/(15+8))
0.6521739130434783
与前面用predict_proba
计算得到的概率一致。
2随机森林
2.1决策路径
首先训练一个决策树数量为4,最大深度为3的随机森林,随机森林不能直接绘制出节点图,但是可以绘制出构成随机森林的每个决策树的节点图像。
python
rfc1=rfc(n_estimators=4,bootstrap=True,min_samples_leaf=10,max_depth=3).fit(moon1[['f1','f2']],moon1['label'])
python
est=rfc1.estimators_
plt.figure(figsize=(16,16))
plt.subplot(221)
tree.plot_tree(est[0],
feature_names=['f1','f2'],
filled=True,#填充颜色
class_names=["0","1"],
rounded=False,#边缘圆润与否
node_ids=True
)
plt.subplot(222)
tree.plot_tree(est[1],
feature_names=['f1','f2'],
filled=True,#填充颜色
class_names=["0","1"],
rounded=False,#边缘圆润与否
node_ids=True
)
plt.subplot(223)
tree.plot_tree(est[2],
feature_names=['f1','f2'],
filled=True,#填充颜色
class_names=["0","1"],
rounded=False,#边缘圆润与否
node_ids=True
)
plt.subplot(224)
tree.plot_tree(est[3],
feature_names=['f1','f2'],
filled=True,#填充颜色
class_names=["0","1"],
rounded=False,#边缘圆润与否
node_ids=True
)
plt.show()
我们已经知道,随机森林的预测值是由构成森林的决策树预测并进行取均值/投票得到的,因此其决策路径也是由所有的决策树的决策路径合并组成。
在随机森林中decision_path
方法的返回值与决策树中的该函数有所不同
参数:
X: 需要进行预测、计算的样本,是二维格式,具体shape=[样本数量,特征数量]
返回值:
indicator: 形状为(n_samples, n_nodes)的稀疏矩阵
返回一个节点指示矩阵,非零元素的位置表示样本在预测过程中经过了那些节点
n_nodes_ptr:形状为(n_estimators + 1,)的数组
The columns from indicator[n_nodes_ptr[i]:n_nodes_ptr[i+1]] ,gives the indicator value for the i-th estimator.
indicator[n nodes ptr[i]:n nodes ptr[i+1]]中的列给出了第i个分类器的indicator(决策路径)。
以下是某个样本[[-0.13,0.28]]
在训练好的的随机森林rfc1中的决策路径indicator,以及n_nodes_ptr(个人认为它的主要作用就是切分随机森林中不同的决策树的决策路径)。
python
print(rfc1.decision_path([[-0.13,0.28]])[0])
print(rfc1.decision_path([[-0.13,0.28]])[1])
scss
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
(0, 9) 1
(0, 10) 1
(0, 12) 1
(0, 14) 1
(0, 18) 1
(0, 22) 1
(0, 26) 1
(0, 27) 1
(0, 29) 1
(0, 30) 1
(0, 32) 1
(0, 33) 1
[ 0 9 18 29 38]
接下来以[[1.73,0.17]]
为例观察随机森林的决策路径,首先为了方便观察和计算,将稀疏矩阵转换为稠密矩阵:
python
drfc1=rfc1.decision_path([[1.73,0.17]])[0].toarray()#方便观察转变为稠密矩阵
print(drfc1)
lua
[[1 1 0 1 0 1 0 0 0 1 1 0 1 0 1 0 0 0 1 0 0 0 1 1 0 1 0 0 0 1 1 0 1 0 1 0
0 0]]
并提取n_nodes_ptr,用于切分稠密矩阵drfc1,来获取构成rfc1的每个决策树的决策路径
python
mrfc1=rfc1.decision_path([[1.73,0.17]])[1]
print(mrfc1)
css
[ 0 9 18 29 38]
同样的为了方便观察,我们将切分好的决策路径,用稀疏矩阵的形式表现出来
python
from scipy.sparse import csr_matrix#导入将稠密矩阵转换为稀疏矩阵的包
python
list1=[]
#提取样本在每个决策树的决策路径,并转换为稀疏矩阵
for i in range(len(mrfc1)-1):
list1.append(csr_matrix(drfc1[0][mrfc1[i]:mrfc1[i+1]]))
python
for i in range(len(list1)):
print("样本第{}个决策树的决策路径".format(i+1))
print(list1[i])
scss
样本第1个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
样本第2个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
样本第3个决策树的决策路径
(0, 0) 1
(0, 4) 1
(0, 5) 1
(0, 7) 1
样本第4个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
同样的,我们也可以直接用构成随机森林的每个决策树来生成该样本的决策路径,如下所示,和上面的结果一致:
python
for i in range(4):
print("样本第{}个决策树的决策路径".format(i+1))
print(rfc1[i].decision_path([[1.73,0.17]]))
scss
样本第1个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
样本第2个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
样本第3个决策树的决策路径
(0, 0) 1
(0, 4) 1
(0, 5) 1
(0, 7) 1
样本第4个决策树的决策路径
(0, 0) 1
(0, 1) 1
(0, 3) 1
(0, 5) 1
2.2概率
随机森林的预测概率即构成每个决策树的预测概率的均值,具体可以观察下面的例子,这里不多加赘述
python
rfc1.predict_proba([[1.73,0.17]])
lua
array([[0.20480082, 0.79519918]])
python
ll=[]
for i in range(4):
ll.append(rfc1[i].predict_proba([[1.73,0.17]])[0].tolist())
print(np.array(ll))
lua
[[0.38983051 0.61016949]
[0.2972973 0.7027027 ]
[0.13207547 0.86792453]
[0. 1. ]]
python
np.array(ll).mean(axis=0)
scss
array([0.20480082, 0.79519918])
3.一些其他的发现
在绘制决策树的节点图的时候,我发现,用全部的样本去训练一棵决策树,其#0节点的samples=sum(value)=真实的样本总数, 其中samples表示该节点的样本数量,value表示该节点中每个类别的数量。
但是在训练完成的随机森林中,每个决策树的#0节点value加和虽然与真实的样本总数相等,但是并不等于samples ,笔者认为这与随机森林在训练每个决策树的过程中使用boostrap
有放回抽样有关,但是没有找到具体的源代码支撑。