机器学习之特征工程

1.嵌套法

很多同学在做特征工程的时候,面对特征的筛选,常常感到头疼,今天,我们介绍一种嵌套前进法专门用来筛选特征变量。所谓的嵌套前进法就是组合了嵌套法与前进法。

接下来,我们选择sklearn的Boston房价的数据作为测试。
首先导入需要的库

from sklearn.datasets import load_boston
import matplotlib.pyplot as plt 
from xgboost import XGBRegressor
import numpy as np 
import pandas as pd 
from itertools import combinations
from sklearn.linear_model import LogisticRegression,Lasso,LinearRegression
from sklearn.model_selection import cross_val_score,KFold,train_test_split
import warnings
warnings.filterwarnings("ignore") 

这里定义一个train_test_1000函数,代表将数据集随机的划分1000次。1000次是为降低随机带来的波动性,降低方差。

#划分训练集测试集1000次,看看全数据的中测试集的表现结果
def train_test_1000(model,X,y):
    scores = []
    FIS = []
    for i in range(1000):
        X_train,X_test,Y_train,Y_test = train_test_split(X,y,test_size=0.3)
        model.fit(X_train,Y_train)
        scores.append(model.score(X_test,Y_test))
        FI  = pd.DataFrame(list(zip(X.columns.tolist(),model.feature_importances_ )))
        
        FIS.append(FI)
    return np.mean(scores),FIS

我们测试一下,没有做特征筛选的时,建模的效果。选取的Xgboost回归建模。

data = load_boston() 
X = pd.DataFrame(data.data)
X.columns = data.feature_names
y = pd.DataFrame(data.target)
#这里为了加快计算速度,只选取了50颗数
model = XGBRegressor(n_estimators=50,objective="reg:squarederror")
score_mean ,FIS = train_test_1000(model,X,y) 
print("1000次划分训练集测试集后,R2的平均值",score_mean)

在这里插入图片描述

xgboost中的属性feature_importances_ 可以知道特征的相对重要性

df = pd.concat((FIS[i] for i in range(len(FIS))),axis=1)
df_col = df.iloc[:,0]
df_v = df.drop([0],axis=1 )
df_v.columns = range(1,1001)
df_ = pd.concat((df_col,df_v.sum(axis=1)),axis=1)
df_.columns = ["features","score"]
画出各个特征的重要性的柱状图
plt.figure(figsize=(8,4),facecolor="W") #建立画布
plt.ylabel("feature_importances_",fontsize=15)
plt.vlines(x=df_.features, ymin=0, ymax=df_.score, color='firebrick', alpha=0.7, linewidth=20);
df_ = df_.sort_values(by="score",ascending=False)

在这里插入图片描述
既然是要选特征那么我们就选选取前10个得分最高的特征,拿出来建模。这里的10也是人为选取的,反正之前那的13个特征少,达到了筛选特征的目的。接下来,就是看看这个10个特征所建的模型效果是不是较之前有所提升。
在这里插入图片描述

X_cho = X.loc[:,choice_features]
xgbr1 = XGBRegressor(n_estimators=50,objective="reg:squarederror")
score_mean_cho,FIS_cho = train_test_1000(xgbr1,X_cho,y)
print("使用被选出来的10个特征建模的R2为",score_mean_cho )

在这里插入图片描述
结果正如我们希望的那样,模型的效果提升了。

2.前进法

	所谓的前进法就是逐个的选取的特征,如果这个特征对提升模型的作用,那么就将它留下来,反之就将它丢弃。
	但是这个方法有个致命的缺陷就是特征的个数到达一定的数量,就会出现巨大的计算量,
	比如特征为100个,那需要的迭代的次数就是:**100^10^ -1** 次 这是一个巨大的数字,普通的计算机根本无法计算出结果。
	更何况100个特征的数据还是很常见的。
	所以,如果单独使用前进法是根本行不通的。我们可以配合着嵌套法来一起使用,这就是我们所说的嵌套前进法。
	那我们就在刚才使用嵌套法的基础上选取的10特征做前进法。

def com(X):
    """
    return 前进法特征组合
    """
    list_com = []
    for i in range(1,X.shape[1]+1):
        list_com += list(combinations(list(np.arange(0,X.shape[1])),i))
    print("特征组合有:{}个".format(len(list_com)))
    return list_com

# 前进法代码
def forward(model,X,y):

    best_cols = []
    accuracys = []
    best_accuracy = 0

    for j in range(len(list_com)):
        X_set = X.iloc[:,list(np.array(list_com[j]))]
        accuracy = (cross_val_score(model,X_set,y,cv=cv,n_jobs=-1)).mean()
        accuracys.append(accuracy)
        if best_accuracy < accuracy:
            best_accuracy = accuracy
            best_cols.append(X_set.columns)
    print("最佳特征:",best_cols[-1])
    print("最高得分为:",max(accuracys),"\n")
    return best_cols[-1] 
%%time
best_cols = []
for i in range(10):
    x_tr,x_te,y_tr,y_te = train_test_split(X_cho,y)
    model = XGBRegressor(n_estimators=50,objective="reg:squarederror")
    cho_col = forward(model,x_tr,y_tr)
    cho_col = cho_col.tolist() 
    best_cols.append(cho_col)

10次前前进法运行的结果:
在这里插入图片描述

看到这个你也许会很惊呀!为什么每次选出的特征会不一样呢?当然如果你了解集成模型的话,就应该知道集成模型为保持单个评估器的特异性,都会有随机性,所以造成每次运行的结果不一样,当然,差距应该不大。

那我们就选择得分最高的第二组特征建模吧,第二组选择了[‘LSTAT’, ‘RM’, ‘NOX’, ‘DIS’, ‘PTRATIO’, ‘CRIM’, ‘B’]这七个特征。

col_ = ['LSTAT', 'RM', 'NOX', 'DIS', 'PTRATIO', 'CRIM', 'B'] 
X1 = X.loc[:,col_] 
model = XGBRegressor(n_estimators=50,objective="reg:squarederror")
score_mean1 ,FIS1 = train_test_1000(model,X1,y) 
print("这7个特征建模的结果为:",score_mean1 )

![在这里插入图片描述](https://img-blog.csdnimg.cn/20191211184255290.png
当然这个结果比我们所选取的10个特征建模的效果要稍微差一点点,但是却少了3个特征,精度也只稍微降低了一丢丢,但是之前全部特征(13个)建模的效果要好。这也说明这个方法还是有点效果的。

总结一下:(R2得分取了4位有效数值)

特征个数R2得分
130.8537
100.8559
70.8549

3.前进法升级版

之前介绍的前进法必须依赖嵌套法,而且只能用在有feature_importances_的树模型中,并且对迭代的特征个数有限制,但是一般的数据的特征个数远远多于10个。那么也没有更好的方法来解决这个问题呢?
答案是,有的!

我们可以牺牲掉一部分(应该是很大一部分特征组合)。
具体的优化的方案是:假设我们现在数据有n个特征,分别用X1,X2,X3……Xn表示

  1. 第一步:让每一个特征单独去建模,看哪一个特征的建模效果最佳,并记录最佳得分best_score,假设最佳特征为特征X1,这一轮就会迭代n
  2. 第二步:剩余的特征(X2~ Xn)逐个的与X1 组合成新的第二轮迭代的特征,得到(X1,X2),(X1,X3),(X1,X4)……(X1,Xn)再建模,记录每次的得分,并且与best_score作比较,如果得分高于best_score,就将其替换成best_score。假设最佳组合为:(X1,X2).这样又会迭代n-1次。如果没有一个组合的得分高于best_score,那么就可以跳出循环了。
  3. 第三步:在第二步的基础上遍历第三个特征,如果没有一个组合的得分高于best_score,那么就可以跳出循环了。
  4. ……
  5. ……
  6. ……
  7. 如果遍历到最后一个特征,还没有跳出循环,那直接break

这样你会发现最坏的情况程序只会执行:n*(n+1)/2 次,所以时间复杂度会骤降。
原理介绍结束,那下面直接上代码。

class Forward:
    def __init__(self, model,X, X_train, X_test, Y_train, Y_test):

        # 初始化
        self.model = model
        self.X = X
        self.X_train = X_train
        self.X_test = X_test
        self.Y_train = Y_train
        self.Y_test = Y_test
        # 初始化最佳得分best_score,和每个特征的得分col_scores
        self.best_score = 0
        self.axes = [] 
        self.col_scores = []

    def best_split(self):
        """
        functions:找到第一个最佳的分割特征

        :return: axis:最佳分割特征,
                col_scores:每个特征的得分,
                best_score:最高得分

        """
        for i in range(self.X_train.shape[1]):
            XTrainSet = pd.DataFrame(self.X_train.iloc[:, i])
            XTestSet = pd.DataFrame(self.X_test.iloc[:, i])

            self.model.fit(XTrainSet, self.Y_train)
            score = self.model.score(XTestSet, Y_test)

            self.col_score = ("".join(XTrainSet.columns.tolist()), score)
            self.col_scores.append(self.col_score)

            if score > self.best_score:
                self.best_score = score
                self.axis = i
        return self.axis, self.col_scores, self.best_score
    
    
    def best_features(self):
        """
        return : 返回最佳的特征组合
        """
        self.axes.append(self.axis)
        col_axis = range(len(self.X.columns.tolist())) 
        range_axis = list(set(col_axis).difference(set(self.axes)))
        
        while True:
            axis2 = -1 
            for i in range_axis:
                X_train_set = self.X_train.iloc[:,self.axes+[i]]
                X_test_set = self.X_test.iloc[:,self.axes+[i]]
                self.model.fit(X_train_set,Y_train)
                score1 = self.model.score(X_test_set,Y_test)
        #         print(i,score1)
                if score1 > self.best_score:
                    self.best_score = score1           
                    axis2 = i 
            if axis2 != -1 :
                self.axes.append(axis2)

             # 如果剩余的    
            if range_axis == list(set(range_axis).difference(set(self.axes))):
                break
            else:
                range_axis = list(set(range_axis).difference(set(self.axes)))

            if range_axis == [] :
                break 

        return self.axes 
   
data = load_boston()
X = pd.DataFrame(data.data)
y = pd.DataFrame(data.target)
X.columns = data.feature_names
SCORES = []
COL_C = [] 
for i in range(10):
    X_train,X_test,Y_train,Y_test = train_test_split(X,y,test_size=0.3)
    model = XGBRegressor(n_estimators=50,objective="reg:squarederror")
    Fw = Forward( model, X,X_train, X_test, Y_train, Y_test) 
    axis, col_scores, best_score = Fw.best_split() 
    col_c = Fw.best_features() 
    COL_C.append(col_c)
    X1 = X.iloc[:,col_c]
    X_train1,X_test1,Y_train1,Y_test1 = train_test_split(X1,y,test_size=0.3)
    model1 = XGBRegressor(n_estimators=50,objective="reg:squarederror")
    model1.fit(X_train1,Y_train1)
    SCORES.append(model1.score(X_test1,Y_test1))

这里做个10次循环建模。
COL_C :10次模型筛选出来的特征的axis

在这里插入图片描述
这里只选出出现频率最高前7个特征去建模。(注意:我是认为选取7个特征的,你也可以只选6个,5个……,只要少于全部的特征(13个)达到筛选特征的目的就好)

from functools import reduce
from collections import Counter
#将量程列表平铺
COL_C_  = reduce(list.__add__,COL_C  )
#查看各个特征的个数
dict_cols  = Counter(COL_C_)
#计算字典中元素的个数
result_sort = sorted(dict_cols.items(),key = lambda x : x[1],reverse=True) 
df = pd.DataFrame(result_sort ) 
col = df[:7][0].values.tolist() 
X3 = X.iloc[:,col]
X_train3,X_test3,Y_train3,Y_test3 = train_test_split(X3,y,test_size=0.3)
score,FIS = train_test_1000(model,X3,y)

print("选出前7个出现次数最多的特征",score)

在这里插入图片描述
Amazing!!!
竟然之前任何一次得分都要高!!!
请注意,这都是循环1000次取平均值的结果,可信度相对较高,说明这个方法筛选出来的特征确实都是很重要的特征。
那么我们都来看看到底选取了哪些特征:
在这里插入图片描述
再次总结一下:

方法得分
嵌套前进法0.8537(13个特征)
嵌套前进法0.8559(10个特征)
嵌套前进法0.8549(7个特征)
前进法升级版0.8582(7个特征)

注意: 嵌套前进法 选的那7个特征 与 前进法升级版 选的7个特征不一致的。

嵌套前进法前进法升级版
[‘LSTAT’, ‘RM’, ‘NOX’, ‘DIS’, ‘PTRATIO’, ‘CRIM’, ‘B’][‘LSTAT’, ‘NOX’, ‘TAX’, ‘RM’, ‘INDUS’, ‘DIS’, ‘PTRATIO’]

4.总结:

前进法升级版既吸收了前进法的优点又能够避开前进法的在时间复杂度方面的缺点。
本文是本人原创,转载请说明出处,谢谢。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值