随机森林的直观理解
由ypyu创建,最终由ypyu 被浏览 384 用户
导语
对于那些认为随机森林是黑匣子算法的人来说,这篇帖子会提供一个不同的观点。接下来,我将从4个方面去理解随机森林模型。
特征有多重要
在sklearn随机森林中使用model.feature_importance来研究其重要特征是很常见的。重要特征是指与因变量密切相关的特征,并且对因变量的变化影响较大。我们通常将尽可能多的特征提供给随机森林模型,并让算法反馈对预测最有用的特征列表。但仔细选择正确的特征可以使我们的目标预测更加准确。
计算feature_importances的想法很简单,但却很有效。
步骤
- 训练随机森林模型(假定有正确的超参数)
- 找到模型的预测分数(称之为基准分数)
- 多次(p次,p为特征个数)计算预测分数,每次打乱某个特征的顺序,可见下图
- 将每次预测分数与基准分数进行比较。如果随机调整特征顺序后预测分数小于基准分数,这意味着我们的模型如果没有这个特征会变得很糟糕。
- 删除那些不会降低基准分数的特征,并用减少后的特征子集重新训练模型。
注:将F4列打乱重新进行预测来判断特征F4的重要性
计算特征重要性的代码
下面的代码将为所有特征提供一个结构为{特征,重要性}的字典。
# defining rmse as scoring criteria (any other criteria can be used in a similar manner)
def score(x1,x2):
return metrics.mean_squared_error(x1,x2)
# defining feature importance function based on above logic
def feat_imp(m, x, y, small_good = True):
"""
m: random forest model
x: matrix of independent variables
y: output variable
small__good: True if smaller prediction score is better
"""
score_list = {}
score_list[‘original’] = score(m.predict(x.values), y)
imp = {}
for i in range(len(x.columns)):
rand_idx = np.random.permutation(len(x)) # randomization
new_coli = x.values[rand_idx, i]
new_x = x.copy()
new_x[x.columns[i]] = new_coli
score_list[x.columns[i]] = score(m.predict(new_x.values), y)
imp[x.columns[i]] = score_list[‘original’] — score_list[x.columns[i]] # comparison with benchmark
if small_good:
return sorted(imp.items(), key=lambda x: x[1])
else: return sorted(imp.items(), key=lambda x: x[1], reverse=True)
随机森林中的重要特征
输出:
importance = feat_imp(ens, X_train[cols], y_train); importance
[('YearMade', -0.21947050888595573),
('Coupler_System', -0.21318328275792894),
('ProductSize', -0.18353291714217482),
('saleYear', -0.045706193607739254),
('Enclosure', -0.041566508577359523),
('MachineID', -0.01399141076436905),
('MachineHoursCurrentMeter', -1.9246700722952426e-05)]
在上面的输出中,可以看出,YearMade将最大程度增加RMSE预测。所以它一定是最重要的特征。
(上面的结果所对应的数据是从Kaggle competition获取的,这是链接——https://www.kaggle.com/c/bluebook-for-bulldozers)
对预测有多大信心?
一般来说,当企业想要有所预测时,他们的最终目的不是降低成本就是提高利润。在做出重大商业决策之前,企业十分热衷于去评估做出这个决定的风险的大小。但是,当预测结果并没有被展现在置信区间时,我们可能会无意中将企业至于更多的风险中,而不是降低风险。
当我们使用线性模型(基于分布假设的一般模型)时,比较容易找到我们预测的置信水平。但是当谈到随机森林的置信区间时,找起来并不是那么容易。 我想,任何上过线性回归课程的人都肯定看过这张图3。为了找到一个最佳线性模型,我们要去寻找偏差—方差最为折衷的模型。这张图片很好地说明了预测中偏差和方差的定义。(我们理解为这4张图分别是由四个不同的人掷飞镖所得)。
如果我们有高偏差和低方差值时(第三个人),我们投掷的飞镖会固定的远离红心。相反,如果我们有高的方差和低的偏差(第二个人),那么他投掷飞镖的结果就很不一样。如果有人去猜测他下一个飞镖击中的位置,那么它既有可能打到靶心也有可能远离靶心。现在我们来假设在现实生活中识别一起信用欺诈等同于上面例子击中靶心。如果信用公司拥有的的预测模型与上面第二人的掷飞镖行为很相似,那么该公司在大多数时候都不会抓住这个诈骗犯,尽管模型预测的是正确的。
因此,不仅仅是意味着预测的准确程度,我们还应该检查我们的预测的置信水平。
如何做到这一点?
随机森林是由许多决策树组成。每棵树分别预测新的数据,随机森林从这些树中提取出平均预测值。预测置信水平的想法只是为了去看来自不同树木的预测有多少因为新的观测而产生变化,然后进一步分析。
基于方差树预测置信度的源代码
#Pseudo code:
def pred_ci(model, x_val, percentile = 95, n_pnt):
"""
x_val = validation input
percentile = required confidence level
model = random forest model
"""
allTree_preds = np.stack([t.predict(x_val) for t in model.estimators_], axis = 0)
err_down = np.percentile(allTree_preds, (100 - percentile) / 2.0 ,axis=0)
err_up = np.percentile(allTree_preds, 100- (100 - percentile) / 2.0 ,axis=0)
ci = err_up - err_down
yhat = model.predict(x_val)
y = y_val
df = pd.DataFrame()
df['down'] = err_down
df['up'] = err_up
df['y'] = y
df['yhat'] = yhat
df['deviation'] = (df['up'] - df['down'])/df['yhat']
df.reset_index(inplace=True)
df_sorted = df.iloc[np.argsort(df['deviation'])[::-1]]
return df_sorted
注:偏差 = (up-down)/Yhat
代码输出
从这个输出数据可以读出,我们可以说我们对于验证集上索引为14的观测的预测最没有信心。
什么是预测路径?
如果我们想要分析哪些特征对于整体随机森林模型是重要的,则 特征重要性(如在第一部分中)是有用的。但是如果我们对某个特定的观察感兴趣,那么 Tree interpreter的角色就会发挥作用。
举个例子,现在有一个RF模型,这种模型会预测—一位来医院的患者X是否具有很高的概率再入院?,为了简单起见,我们考虑只有3个特征—患者的血压值,患者的年龄以及患者的性别。现在,如果我们的模型认为患者A有80%的可能会再次入院,我们怎么能知道这个被模型预测为他(她)将重新入院的患者A有什么特殊之处?在这种情况下,Tree interpreter会指示预测路径紧随那个特殊的患者。就像是,因为患者A是65岁的男性,这就是为什么我们的模型会预测他将再次入院。另一个被模型预测将再次入院的患者B ,可能时因为他有高血压(而不是因为年龄或性别)。
基本上,Tree interpreter给出了偏差的排序列表(在起始节点的平均值)以及单个节点对给定预测的贡献。 <div align="center">
图5的这棵决策树(深度:3层)基于波士顿房价数据集。根据中间节点的预测值以及导致数值发生变化的特征,它显示了决策路径的分解。单节点的贡献是该节点的值与前一个节点值的差值。 图6 给出了对于患者A使用Tree interpreter的输出示例。图片显示年龄为65岁是模型预测再入院概率高于均值的最高贡献者。 图6同样也可以使用瀑布图7来表示。我从“ 瀑布图包 ”中选材做的这个快速简单的瀑布图。
上面的瀑布图可视化代码:
from waterfallcharts import quick_charts as qc
a = ['Bias', 'Age', 'Sex', 'Blood Pressure']
b = [0.3, 0.6, -0.1, -0.2]
plot = qc.waterfall(a,b, Title= 'Patient A', y_lab= 'Predicted probability', x_lab= 'Contributing features (path)',
net_label = 'Final Prediction')
plot.show()
相关变量的阐释: • 值(图片B)是指通过节点预测目标值。(就是在该节点中落下的观测目标的平均值)。 • 贡献是当前节点的值减去上一节点的值(这是为一个路径提供的贡献特征)。 • 路径是为了到达叶节点而通过某些观察所获得的所有特征分割的组合。 tree interpreter包直接用来计算每个节点的贡献,链接:treeinterpreter
目标变量相关重要特征
找到最重要的特征后,下一步我们可能会感兴趣的是研究目标变量与兴趣特征之间的直接关系。从线性回归中得到的与其相类似的是模型系数。对于线性回归,系数以这种方式被计算,即我们可以通过说:“在$X_j$中有1个单位变化,保持所有其他$X_i$不变,$Y$会发生什么变化?”这样的方式来解释。
虽然我们有来自随机森林的特征重要性,但是它们只是给出$Y$的变化是由于$X_i$的改变之间的相关性。我们不能直接地解释他们就像保持所有其他特征不变,$Y$该变量取决于$X_j$中的单位的变化。
幸运的是,我们有看一被看作线性模型系数图表的局部依赖图,但同样也可被扩展为看起来像黑箱模型。这个想法是将预测中所做的改变孤立于一个特定的功能。它不同于$X$对$Y$的散点图,因为散点图不能隔离$X$对$Y$的直接关系,并且可能受$X$和$Y$所依赖的其他变量的间接关系所影响。
PDP分析步骤如下:
- 训练一个随机森林模型(假设F1 ... F4是我们的特征,Y是目标变量,假设F1是最重要的特征)。
- 我们有兴趣探索Y和F1的直接关系。
- 用F1(A)代替F1列,并为所有的观察找到新的预测值。采取预测的平均值。(称之为基准值)
- 对F1(B)... F1(E)重复步骤3,即针对特征F1的所有不同值。
- PDP的X轴具有不同的F1值,而Y轴是虽该基准值F1值的平均预测而变化。
图 9 是partial dependence plot的一个例子。数据来自 kaggle bulldozer competition data,它显示了生产年份(YearMade)和(销售价格)SalesPrice的关系
而图10是SalePrice与YearMade的线状图。我们可以看到,散点图/折线图可能无法像PDP那样捕获YearMade对SalesPrice的直接影响。
在大多数情况下,随机森林在预测中可以击败线性模型预测。针对随机森林经常提出的反对意见是:对它的理解没有线性模型那样直观,但是本文的讨论希望帮助你回答这样的反对意见。
\