用scikit-learn 来演绎随机森林方法
来自: http://datartisan.com/article/detail/85.html
在之前的一篇文章中,我们讨论了如何将随机森林模型转成一个「白箱子」,就像预测变量可以由一组拥有不同特征自变量的来解释。
我对此有不少需求,但不幸的是,大多数随机森林算法包(包括 scikit-learn)并没有给出树的预测路径。因此sklearn的应用需要一个补丁来展现这些路径。幸运的是,cong 0.17 dev,scikit-learn 补充了两个附加的api,使得一些问题更加方便。获得叶子node_id,并将所有中间值存储在决策树中所有节点,不仅叶节点。通过结合这些,我们有可能可以提取每个单独预测的预测路径,以及通过检查路径来分解它们。
废话少说, 代码托管在github,你可以通过 pip install treeinterpreter 来获取。
使用treeinterpreter来分解随机森林
首先我们将使用一个简单的数据集,来训练随机森林模型。在对测试集的进行预测的同时我们将对预测值进行分解。
from treeinterpreter import treeinterpreter as tifrom sklearn.tree import DecisionTreeRegressorfrom sklearn.ensemble import RandomForestRegressorimport numpy as npfrom sklearn.datasets import load_boston boston = load_boston() rf = RandomForestRegressor() rf.fit(boston.data[:300], boston.target[:300])
RandomForestRegressor(bootstrap=True, criterion='mse', max_depth=None, max_features='auto', max_leaf_nodes=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False)
任意选择两个可以产生不同价格模型的数据点。
instances = boston.data[[300, 309]] #任意选择两个可以产生不同价格模型的数据点。 print "Instance 0 prediction:", rf.predict(instances[0])print "Instance 1 prediction:", rf.predict(instances[1])
Instance 0 prediction: [ 30.27] Instance 1 prediction: [ 22.03] /Users/donganlan/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py:386: DeprecationWarning: Passing 1d arrays as data is deprecated in 0.17 and willraise ValueError in 0.19. Reshape your data either using X.reshape(-1, 1) if your data has a single feature or X.reshape(1, -1) if it contains a single sample. DeprecationWarning) /Users/donganlan/anaconda/lib/python2.7/site-packages/sklearn/utils/validation.py:386: DeprecationWarning: Passing 1d arrays as data is deprecated in 0.17 and willraise ValueError in 0.19. Reshape your data either using X.reshape(-1, 1) if your data has a single feature or X.reshape(1, -1) if it contains a single sample. DeprecationWarning)
对于这两个数据点,随机森林给出了差异很大的预测值。为什么呢?我们现在可以将预测值分解成有偏差项(就是训练集的均值)和个体差异,并探究哪些特征导致了差异,并且占了多少。
我们可以简单的使用treeinterpreter中 predict 方法来处理模型和数据。
prediction, bias, contributions = ti.predict(rf, instances)#Printint out the results: for i in range(len(instances)): print "Instance", i print "Bias (trainset mean)", bias[i] print "Feature contributions:" for c, feature in sorted(zip(contributions[i], boston.feature_names), key=lambda x: -abs(x[0])): print feature, round(c, 2) print "-"*20
Instance 0 Bias (trainset mean) 25.8759666667 Feature contributions: RM 4.25 TAX -1.26 LSTAT 0.71 PTRATIO 0.22 DIS 0.15 B -0.14 AGE 0.12 CRIM 0.12 RAD 0.11 ZN 0.1 NOX -0.1 INDUS 0.06 CHAS 0.06 -------------------- Instance 1 Bias (trainset mean) 25.8759666667 Feature contributions: RM -5.81 LSTAT 1.66 CRIM 0.26 NOX -0.21 TAX -0.15 DIS 0.13 B 0.11 PTRATIO 0.07 INDUS 0.07 RAD 0.05 ZN -0.02 AGE -0.01 CHAS 0.0 --------------------
各个特征的贡献度按照绝对值从大到小排序。我们可以从 Instance 0中(预测值较高)可以看到,大多数正效应来自RM.LSTAT和PTRATIO。在Instance 1中(预测值较低),RM实际上对预测值有着很大的负影响,而且这个影响并没有被其他正效应所补偿,因此低于数据集的均值。
但是这个分解真的是对的么?这很容易检查:偏差项和各个特征的贡献值加起来需要等于预测值。
print predictionprint bias + np.sum(contributions, axis=1)
[ 30.27 22.03] [ 30.27 22.03]
对更多的数据集进行对比
当对比两个数据集时,这个方法将会很有用。例如
-
理解导致两个预测值不同的真实原因,究竟是什么导致了房价在两个社区的预测值不同 。
-
调试模型或者数据,理解为什么新数据集的平均预测值与旧数据集所得到的结果不同。
举个例子,我们将剩下的房屋价格数据分成两个部分,分别计算它们的平均估计价格。
ds1 = boston.data[300:400] ds2 = boston.data[400:]print np.mean(rf.predict(ds1))print np.mean(rf.predict(ds2))
22.3327 18.8858490566
我们可以看到两个数据集的预测值是不一样的。现在来看看造成这种差异的原因:哪些特征导致了这种差异,它们分别有多大的影响。
prediction1, bias1, contributions1 = ti.predict(rf, ds1) prediction2, bias2, contributions2 = ti.predict(rf, ds2)#We can now calculate the mean contribution of each feature to the difference. totalc1 = np.mean(contributions1, axis=0) totalc2 = np.mean(contributions2, axis=0)
因为误差项对于两个测试集都是相同的(因为它们来自同一个训练集),那么两者平均预测值的不同主要是因为特征的影响不同。换句话说,特征影响的总和之差应该等于平均预测值之差,这个可以很简单的进行验证。
print np.sum(totalc1 - totalc2)print np.mean(prediction1) - np.mean(prediction2)
3.4468509434 3.4468509434
最后,我们将两个数据集中各个特征的贡献打印出来,这些数的总和正好等于与预测均值的差异。
for c, feature in sorted(zip(totalc1 - totalc2, boston.feature_names), reverse=True): print feature, round(c, 2)
LSTAT 2.23 CRIM 0.56 RM 0.45 NOX 0.28 B 0.1 ZN 0.03 PTRATIO 0.03 RAD 0.03 INDUS -0.0 CHAS -0.0 TAX -0.01 AGE -0.05 DIS -0.18
分类树 和 森林
完全相同的方法可以用于分类树,其中可以得到各个特征对于估计类别的贡献大小。我们可以用iris数据集做一个例子。
from sklearn.ensemble import RandomForestClassifierfrom sklearn.datasets import load_iris iris = load_iris() rf = RandomForestClassifier(max_depth = 4) idx = range(len(iris.target)) np.random.shuffle(idx) rf.fit(iris.data[idx][:100], iris.target[idx][:100])
RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=4, max_features='auto', max_leaf_nodes=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False)
对单个例子进行预测
instance = iris.data[idx][100:101]print rf.predict_proba(instance)
[[ 0. 0. 1.]]
prediction, bias, contributions = ti.predict(rf, instance)print "Prediction", predictionprint "Bias (trainset prior)", biasprint "Feature contributions:"for c, feature in zip(contributions[0], iris.feature_names): print feature, c
Prediction [[ 0. 0. 1.]] Bias (trainset prior) [[ 0.33 0.32 0.35]] Feature contributions: sepal length (cm) [-0.04014815 -0.00237543 0.04252358] sepal width (cm) [ 0. 0. 0.] petal length (cm) [-0.13585185 -0.13180675 0.2676586 ] petal width (cm) [-0.154 -0.18581782 0.33981782]
我们可以看到,对预测值是第二类影响力最大的是花瓣的长度和宽度,它们对更新之前的结果有最大影响。
总结
对随机森林预测值的说明其实是很简单的,与线性模型难度相同。通过使用treeinterpreter (pip install treeinterpreter),简单的几行代码就可以解决问题。
翻译:lan
来源: http://blog.datadive.net/random-forest-interpretation-with-scikit-learn/