作者:Chelsea🐑
伯克利运筹学硕士,机器学习/数据科学死忠粉
编者按:在数据科学领域,有大量随手可得的算法包可以直接使用。当我们了解了各种模型的数学原理、并清楚自己想要调用的模型后就可以直接调用相关的算法包来实现整个过程、分析结果,但是这些已经搭建好的算法包背后的数学原理却很少有人深究,所以在数据处理和分析过程中可能会得出很多谬论或者非理想的结果。随机森林模型现在为业界广泛使用的模型之一,所以本文将对Scikit-learn包对随机森林模型特征重要性分析存在的问题进行一些讨论,希望能对今后调用随机森林模型相关包的同学起到一些帮助。

Sklearn包存在的问题分析
当我们运行完建立的模型以后,可能会对模型进行解释找到最有用的预测因子来获取有用信息、商业价值。比如在预测糖尿病时,哪些人体指标是最有影响力的;在预测超市大卖场的营业额的时候,是降低运输成本更重要、货品的货架摆放位置更重要还是促销更重要呢?或者在预测结果不理想需要再次做特征优化时,我们都可能会需要对特征的重要性进行分析。

在使用很多模型的时候,都可以直接调用Python已经搭建好的包来得到所有特征的重要性系数。比如在使用随机森林模型时候,我们可以直接将Scikit-learn(Sklearn)相关包就可以得到所有特征的重要性给分。
但是在我们使用这些随手可得的包的时候有没有想过背后的数学逻辑是什么呢?在线性回归模型中,SKlearn包在做特征排分的时候是基于模型内在的参数得到的,比如线性回归模型的某个因子系数越大时,其相应的重要性排分也会越高,通过这样的方式来得到的特征重要性很多时候都是不符合逻辑的。
那么具体可能在什么场景下通过这种简单的方式来做特征重要性打分会出现问题呢?
比如在用线性回归的时候,如果没有对数据做标准化处理,那么不同预测因子的数据大小可能会相差巨大,模型可能会给数据大的因子训练出更大的系数,此时得到的系数与重要性并没有太大联系,也就会得出不正确的打分结果。再比如当我们用Lasso来避免过度拟合的时候,某些预测因子可能被舍弃,这时候得到的排分也就不准确了,或者是当系数之间高度相关的时候,也会存在类似的问题。
实例分析--纽约房价预测
预测纽约的房屋出租价格是曾经在Kaggle上很火热的一个赛题。这里我们选取其中的卧室数量,房子的精度,纬度,房间的租金五个特征,并且加入一列随机生成的数作为第六个特征。然后用随机森林模型来做训练,并调取SKlearn包来看它是怎么对这些因子的重要性进行排序的。
数据下载:rent.csv 
(https://github.com/parrt/random-forest-importances/blob/master/notebooks/data/rent.csv)
(Kaggle网站详细介绍:
https://www.kaggle.com/c/two-sigma-connect-rental-listing-inquiries) 
代码:
features = ['bathrooms', 'bedrooms', 'longitude', 'latitude', 'price'] #选取5个特征dfr = df[features] X_train, y_train = dfr.drop('price',axis=1), dfr['price'] X_train['random'] = np.random.random(size=len(X_train)) rf = RandomForestRegressor( n_estimators=100, min_samples_leaf=1, n_jobs=-1, oob_score=True) rf.fit(X_train, y_train)
最后我们得到的结果是:
从上面的图可以看到随机生成数这一个特征的重要性居然比卧室和厕所的数量更重要,这是完全违背了常识的,所以可以很显而易见的是这个Sklearn内在包对特征的重要性分析是存在问题的,并非任何情况下都可以直接调用的。 
但是Sklearn包里对随机森林重要性排分和线性回归是不同的。在随机森林模型里,重要性排分是基于混沌程度(Impurity/Gini系数)来计算的,也就是说,衡量一个特征有多重要的标准是看这个特征在通过决策树搭建随机森林的过程中降低了多少混沌程度,综合所有的树以后,平均降低越大的则被判定为越重要的特征。 
如果大家熟悉决策树就会知道决策树的搭建过程就是判断混沌程度(Cross Entropy)来实现的。比如当数据集中所有的真实值结果都是1或者都是0的时候,就没有任何混沌,并不需要对这些信息做预测或者分类,这个时候的Cross Entropy就是0,当0和1的分布越不均匀的时候Cross Entropy就会越靠近1,就越需要模型来学习其中的信息。通过一层一层搭建不同的特征,我们会不断地从这些特征中获取到判断是0还是1的信息(Information Gain),那么Cross Entropy就会一层一层从母节点向子树降低,降低的量就是信息获取的量。降低得越多就证明这个特征越有用,因此也越靠近顶层(Root node),然后不断重复此过程来获得整个决策树。 

打乱特征测量法
随机森林的建立者Breiman和Cutler指出通这个方式计算出来的预测因子重要性排序通常和打乱特征测量法(permutation importance measure)得到的结果一致。 

但是问题在于当特征是连续的时候或者是类别很多的分类因子时(High-cardinality categorical variables),前文提到的特征重要性分析的方法会将这些特征的重要性增加。(针对此点,如果对特征做优化也可以解决该问题,比如将多分类的因子重新分段成分类少的因子,这是业界常常会使用的方法) 
那么我们就来介绍一下能解决以上这个问题的打乱特征测量法的机制和原理吧! 
  • 首先我们需要将测试数据或者随机森林中的OOB(Out-of-Bag)数据最原始的准确率(如R squared score)作为一个准确率基线。 
  • 然后我们对其中的某个需要测量的特征做重新组合(permute)处理,也就是打乱数据重新排列 
  • 然后用测试数据(相同数据集)再跑一次模型,计算新的准确率 
  • 计算出新的准确率和基线准确率的差值 
最后差值越大代表该特征越重要。在这个过程中,数据都不需要进行标准化,最后得到的重要性排分总和并不是1,而是一个相对的排名。
那么这个方法的优缺点是什么呢? 
优点:
  • 不需要对模型进行再次地训练
  • 随机森林自带OOB数据,不需要再自己去选择用来测试的数据集 
缺点:
  • 计算量比混沌法的计算量更大,相比于直接调用Sklearn包更复杂,业界使用较少
代码实现过程
def permutation_importances(rf, X_train, y_train, metric):baseline = metric(rf, X_train, y_train)imp = [] for col in X_train.columns: #每个特征都做再组合处理save = X_train[col].copy() X_train[col] = np.random.permutation(X_train[col])m = metric(rf, X_train, y_train) X_train[col] = save imp.append(baseline - m) #得到所有的特征处理后的准确率差值return np.array(imp)
选择OOB的代码实现过程 
rf = RandomForestClassifier(...)imp= permutation_importances(rf, X_train, y_train, oob_classifier_accuracy)
美国小企业投资实例
数据下载:
https://catalog.data.gov/dataset/state-small-business-credit-initiative-ssbci-transactions-dataset
美国国营小企业会通过风险投资、政府投资、社区投资等方式来融资,所以这里选取美国国有小企业的融资数据来对未来某天可能获得的融资金额做时间序列预测。该模型选用了21个特征,包括了该企业的利润、成立时间、全职雇员总数、融资的项目类型、是否位于大城市、董事长是否为女性、董事长是否为少数族群、交易类型、社区融资的类型,其中融资的项目类型、是否位于大城市、董事长是否为女性、董事长是否为少数群、交易类型、社区融资的类型均为分类变量,所以这里把它转换成了虚拟变量(get_dummies把缺失的分类变量被定义为新的特征),总特征向量数增加到了21个。
用随机森林建立模型以后,用Sklearn的包可视化出模型的特征重要性分析
importances = rf.feature_importances_#调用Sklearn包得到重要性排序indices = np.argsort(importances)[::-1]# 输出排序print("Feature ranking:")featurerank=[]for f in range(train_features.shape[1]): featurerank.append(feature_list[indices[f]])print("%d. feature %s (%f)" % (f + 1, feature_list[indices[f]], importances[indices[f]]))#特征重要性可视化plt.figure(figsize=(15,12))feature_imp = pd.Series(importances,index=feature_list).sort_values(ascending=False)sns.barplot(x= feature_imp,y=feature_imp.index)plt.xlabel('Feature Importance Score')plt.ylabel('Features')plt.title("Visualizing Important Features")plt.legend()plt.show()
依次打乱每个特征的数据,然后再对特征的重要性排序
from collections import defaultdictfrom sklearn.model_selection import ShuffleSplitX = train_features.as_matrix() # 特征Y=np.array(train_labels) # 预测数据scores = defaultdict(list) # 重要性变化的大小features_=train_features.columns.valuesfor train_idx, test_idx in ShuffleSplit(n_splits=5, random_state=0, test_size=0.25, train_size=None).split(X): X_train, X_test = X[train_idx], X[test_idx] Y_train, Y_test = Y[train_idx], Y[test_idx] reg = rf.fit(X_train, Y_train) acc = r2_score(Y_test, reg.predict(X_test))# 打乱特征for i in range(X.shape[1]): X_t = X_test.copy() np.random.shuffle(X_t[:, i]) #shuffle用来打乱特征 shuff_acc = r2_score(Y_test, reg.predict(X_t)) scores[features_[i]].append((acc-shuff_acc)/acc)#特征重要性可视化rcParams['figure.figsize'] = 18, 12mda_features = [f for f in scores.keys()]mda_importance = [(np.mean(score)) for score in scores.values()]mda_indices = np.argsort(mda_importance)plt.title('Feature Importances')plt.barh(range(len(mda_indices)), [mda_importance[i] for i in mda_indices], color='#8f63f4', align='center')plt.yticks(range(len(mda_indices)), [mda_features[i] for i in mda_indices])plt.xlabel('Mean decrease accuracy')plt.show()
对比两个特征重要性的排序图,可以看到两种方法对虚拟变量的重要性打分完全不同。在Sklearn包给出的重要性排名中,很多项缺失数据特征都被给了很高的重要性排分,但是打乱特征的方法都把这些特征定义为了极其不重要的特征,所以可以看到在这里直接调用内在包就会出现很严重的问题。
如果我们不把分类变量转换为虚拟变量还会发现当分类变量类别越多时,Sklearn包给出的重要排分通常也会越高,这明显是不符合逻辑的,所以当我们需要探究哪一个变量比较重要,对模型起了很大的作用的时候,就需要对数据进行分析,来看是否能直接调用包还是需要做打乱特征的处理。
小提示:如果将分类变量转为虚拟变量后想要探究该分类变量的重要性,不能直接将不同虚拟变量的重要性简单求和,根据Gini系数的计算公式,需要用对虚拟变量加权的方式来计算其重要性。
参考处理方式:
https://stats.stackexchange.com/questions/92419/relative-importance-of-a-set-of-predictors-in-a-random-forests-classification-in
删除特征判断重要性
打乱特征(permutation importance measure)机制的最大一个优点之一就是不需要对模型做再次训练,可以避免数据庞大时的大量运行时间的浪费。 但是如果想要更加精确的对特征重要性的测量,最好的方式是直接将某个特征给删除掉(Drop-column Importance Measure)机制,然后和打乱特征方法同样的过程来得到准确率的变化。
但是当某个特征彻底被删除的时候,就需要不断地去再次训练模型,即便有强有力的计算能力,也会耗费掉很大量的时间,所以该方法的应用很有限。
希望大家今后在使用Sklearn包时能够机智巧妙地躲过这个坑。

参考文献:

https://explained.ai/rf-importance/ 
https://stats.stackexchange.com/questions/92419/relative-importance-of-a-set-of-predictors-in-a-random-forests-classification-in
—— 完 ——
文章申明
Sept . 2019
文章作者:Chelsea🐑
责任编辑:周岩
微信编辑:葡萄
文章由『运筹OR帷幄』原创发布,如需转载请与『运筹OR帷幄』公众号联系
继续阅读
阅读原文