变量选择是模型构建的一个重要方面,每个建模人员都必须学习。通过变量选择,可以排除相关变量以及数据噪音等,有助于提高模型的泛化性。

许多新手认为,保留所有(或更多)的变量就能产生最佳的模型,因为没有丢失任何信息。可悲的是,他们错了!从模型中删除一个变量,变量个数减少了却增加了模型的精度,这种事情你遇到过多少次?至少,我已经碰到过很多次。这样的变量往往被发现是相关的,而且会妨碍实现更高的模型精度。

Boruta原本是一个R包,但是github上面有对应的python版本,点击链接1链接2

Boruta算法

Boruta是一种特征选择算法。精确地说,它是随机森林周围的一种包装算法。这个包的名字来源是斯拉夫神话中一个居住在松林的恶魔。我们知道,特征选择是预测模型中很关键的一步。当构建一个数据集包含多个变量的模型时,这个步骤尤为重要。

当你有兴趣了解变量相关性的价值,而不是只局限于建立一个具有良好的预测精度黑盒的预测模型时候,用Boruta算法来处理这些数据集无疑是最佳选择。

Boruta函数通过循环的方式评价各变量的重要性,在每一轮迭代中,对原始变量和影子变量进行重要性比较。如果原始变量的重要性显著高于影子变量的重要性,则认为该原始变量是重要的;如果原始变量的重要性明显低于影子变量的重要性,则认为该原始变量是不重要的。

    1. 原始变量:就是我们输入的要进行特征选择的变量。
    1. 影子变量:就是根据原始变量生成的变量,生成规则是:
    • 2.1 先向原始变量中加入随机干扰项,这样得到的是扩展后的变量
    • 2.2 从扩展后的变量中进行抽样,得到影子变量

使用python来实现影子特征,类似于:

1
2
3
4
5
6
# Get feature from training dataset
z = train_df[f].values
# Shuffle data
np.random.shuffle(z)
# Put shuffled data back into dataset
train_df[f + "shadow"] = z

下面是Boruta算法运行的步骤:

    1. 首先,它通过创建混合数据的所有特征(即影子特征)为给定的数据集增加了随机性。
    1. 然后,它训练一个随机森林分类的扩展数据集,并采用一个特征重要性措施(默认设定为平均减少精度),以评估的每个特征的重要性,越高则意味着越重要。
    1. 在每次迭代中,它检查一个真实特征是否比最好的影子特征具有更高的重要性(即该特征是否比最大的影子特征得分更高)并且不断删除它视为非常不重要的特征。
    1. 最后,当所有特征得到确认或拒绝,或算法达到随机森林运行的一个规定的限制时,算法停止。

有何不同?

Boruta遵循所有相关的特征选择方法,它可以捕获结果变量有关的所有的特征。相比之下,大多数传统的特征选择算法都遵循一个最小的优化方法,它们依赖于特征的一个小的子集,会在选择分类上产生最小错误。

在对数据集进行随机森林模型的拟合时,你可以递归地处理每个迭代过程中表现不佳的特征。该方法最大限度地减少了随机森林模型的误差,这将最终形成一个最小化最优特征子集。这通过选择一个输入数据集的过度精简版本发生,反过来,会丢失一些相关的特征。

另一方面,Boruta找到所有的特征,无论其与决策变量的相关性强弱与否。这使得它非常适合被应用于生物医学领域,一部分人会感兴趣了解哪些人类的基因(特征)与某种程度上的特定的医疗条件(目标变量)相关。

案例实现

以kaggle的Porto Seguro’s Safe Driver Prediction比赛数据为例进行操作。

如果你没有安装Boruta模块的话,直接运行下面命令进行安装模块包

1
pip install Boruta

接下来主要加载所需要的模块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from __future__ import print_function
import pandas as pd
import numpy as np
from datetime import datetime
from sklearn.ensemble import RandomForestClassifier # 随机森林算法
from boruta import BorutaPy #Boruta模块
pd.set_option('display.max_rows', 1000)
pd.set_option('display.max_columns', 1000)
# 时间计算
def timer(start_time=None):
if not start_time:
start_time = datetime.now()
return start_time
elif start_time:
thour, temp_sec = divmod((datetime.now() - start_time).total_seconds(), 3600)
tmin, tsec = divmod(temp_sec, 60)
print('\n Time taken: %i hours %i minutes and %s seconds.' % (thour, tmin, round(tsec, 2)))

加载数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
train = pd.read_csv('train.csv', dtype={'target': np.int8, 'id': np.int32})
X = train.drop(['id','target'], axis=1).values
y = train['target'].values
tr_ids = train['id'].values
n_train = len(X)
test = pd.read_csv('test.csv', dtype={'id': np.int32})
X_test = test.drop(['id'], axis=1).values
te_ids = test['id'].values

# 使用随机森林模型
rfc = RandomForestClassifier(n_estimators=200, n_jobs=4, class_weight='balanced', max_depth=6)
boruta_selector = BorutaPy(rfc, n_estimators='auto', verbose=2)
start_time = timer(None)
boruta_selector.fit(X, y)
timer(start_time)

接下来对结果进行分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 原始特征
print ('\n Initial features: ', train.drop(['id','target'], axis=1).columns.tolist() )

# boruta选择的特征个数
print ('\n Number of selected features:')
print (boruta_selector.n_features_)

# top特征(也就是被选择的特征)
feature_df = pd.DataFrame(train.drop(['id','target'], axis=1).columns.tolist(), columns=['features'])
feature_df['rank']=boruta_selector.ranking_
feature_df = feature_df.sort_values('rank', ascending=True).reset_index(drop=True)
print ('\n Top %d features:' % boruta_selector.n_features_)
print (feature_df.head(boruta_selector.n_features_))
feature_df.to_csv('boruta-feature-ranking.csv', index=False)

# 原始特征的排序
print ('\n Feature ranking:')
print (boruta_selector.ranking_)

# 选择的特征
# print ('\n Selected features:')
# print (boruta_selector.support_)

#
# print ('\n Support for weak features:')
#print (boruta_selector.support_weak_)

selected = train.drop(['id','target'], axis=1).columns[boruta_selector.support_]
train = train[selected]
train['id'] = tr_ids
train['target'] = y
train = train.set_index('id')
train.to_csv('train_boruta_filtered.csv', index_label='id')
test = test[selected]
test['id'] = te_ids
test = test.set_index('id')
test.to_csv('test_boruta_filtered.csv', index_label='id')

另外Boruta支持其他模型,比如xgboost或lightgbm,下面以lightGBM为例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
trn_df = pd.read_csv("train.csv", index_col=0)
target = trn_df.target
del trn_df["target"]
clf = LGBMClassifier(boosting_type="rf",
num_leaves=1024,
max_depth=6,
n_estimators=500, # 1000
subsample=.623, # .623
colsample_bytree=.5) # .5
# define Boruta feature selection method
feat_selector = BorutaPy(clf, n_estimators=500, verbose=2, random_state=None)
# find all relevant features
feat_selector.fit(trn_df.values, target.values)
# check selected features
print(feat_selector.support_)
print(trn_df.columns[feat_selector.support_])