8.特征选择


特征过多会带来维度诅咒的问题,也需要更多的样本来训练模型,通过特征选择来减少特征数量是很常见的方法。

1.删除方差非常小的特征

特征选择最简单的方法是删除方差非常小的特征。如果特征的方差很小,它的值都很接近,对于模型来说就没有价值(通过这个特征没办法区分不同的样本)。
最好的办法就是去掉它们,降低模型复杂度。可以用sklearn的VarianceThreshold类实现这个功能。

from sklearn.feature_selection import VarianceThreshold
data = ...
var_thresh = VarianceThreshold(threshold=0.1)
transformed_data = var_thresh.fit_transform(data)

2.删除相关性较高的特征

我们还可以删除相关性较高的特征,可以用df.corr()计算特征间的皮尔逊相关性。
通过下面的操作,MedInc_Sqrt和MedInc的相关性很高,可以删掉其中一个。

import pandas as pd 
from sklearn.datasets import fetch_california_housing
data = fetch_california_housing()
X = data["data"]
col_names = data["feature_names"]
y = data["target"]
df = pd.DataFrame(X, columns=col_names)
# 根据已有的MedInc字段构建新字段MedInc_Sqrt
df.loc[:, "MedInc_Sqrt"] = df.MedInc.apply(np.sqrt)
df.corr()

3.单变量特征选择

接下来转向单变量特征选择的方法(Univariate feature selection),原理就是针对给定目标,对每个特征进行评分。
互信息、方差分析F检验、卡方检验(chi2)是最常用的单变量特征选择方法。

在sklearn中,有两种方式可以使用这些方法:
一是SelectKBest:保留得分最高的k个特征
二是SelectPercentile:保留用户指定百分比内的顶级特征

必须注意的是,只有非负数据才能使用chi2。在自然语言处理中,当我们有一些单词或基于tf-idf的特征时,这是一种特别有用的特征选择技术。
下面特地为单变量特征选择创建一个包装器,几乎可以用于任何新问题。

from sklearn.feature_selection import chi2
from sklearn.feature_selection import f_classif
from sklearn.feature_selection import f_regression
from sklearn.feature_selection import mutual_info_classif
from sklearn.feature_selection import mutual_info_regression
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import SelectPercentile

class UnivariateFeatureSelection:
    def __init__(self, n_features, problem_type, scoring):
        """
        n_features: 为int时使用SelectKBest方法,为float时SelectPercentile
        problem_type: 问题类型为回归或者分类
        scoring: 对特征的评分方法
        """
        if problem_type == 'classification':
            valid_scoring = {
                "f_classif": f_classif,
                "chi2": chi2,
                "mutual_info_classif": mutual_info_classif
            }
        else:
            valid_scoring ={
                "f_regression": f_regression,
                "mutual_info_regression": mutual_info_regression
            }
        if scoring not in valid_scoring:
            raise Exception("Invalid scoring function")
            
        if isinstance(n_features, int):
            self.selection = SelectKBest(
                valid_scoring[scoring],
                k=n_features
            )
        elif isinstance(n_features, float):
            self.selection = SelectPercentile(
                valid_scoring[scoring],
                percentile=int(n_features * 100)
            )
        else:
            raise Exception("Invalid type of feature")
            
    def fit(self, X, y):
        return self.selection.fit(X, y)
    def transform(self, X):
        return self.selection.transform(X)
    def fit_transform(self, X, y):
        return self.selection.fit_transform(X, y)

调用这个类相当简单。

ufs = UnivariateFeatureSelection(
    n_features=0.1,
    problem_type="regression",
    scoring="f_regression"
)
ufs.fit(X, y)
X_transformed = ufs.transform(X)

上面的实现可以满足大部分单变量特征选择的需求,创建较少而重要的特征通常比创建数以百计的特征要好。
但单变量特征选择不一定总表现良好,大多数情况下,人们更喜欢使用机器学习模型进行特征选择。

4.基于模型的特征选择

4.1贪婪特征选择

使用模型进行特征选择最简单的形式被称为贪婪特征选择。在贪婪特征选择中,
第一步是选择一个模型,
第二步是选择损失/评分指标,
第三步也是最后一步是每次新增一个特征,如果能提高评分,就加入“好”特征列表中。
这种做法在每次新增特征时,都会重新拟合模型,所以计算量比较大,计算成本高。

import pandas

from sklearn import linear_model
from sklearn import metrics
from sklearn.datasets import make_classification

class GreedyFeatureSelection:
    """
    一个简单的关于贪婪特征选择的自定义类
    实际运用可能需要根据你自身数据集的情况做一些修改
    """
    def evaluate_score(self, X, y):
        """
        X: 训练数据特征
        y: 训练数据标签
        return: auc分数
        """
        # 定义逻辑回归模型
        model = linear_model.LogisticRegression()
        # 用训练数据X, y拟合模型
        model.fit(X, y)
        # 用模型对训练样本进行预测,取预测为1的概率
        predictions = model.predict_proba(X)[:, 1]
        # 计算auc分数
        auc = metrics.roc_auc_score(y, predictions)
        return auc
    
    def _feature_selection(self, X, y):
        # 初始化好特征列表和最优分数列表,以保持对两者的追踪
        good_features = []
        best_scores = []
        
        # 计算特征数量
        num_features = X.shape[1]
        
        # 无限循环
        while True:
            # 初始化这个循环的最佳特征和分数
            this_feature = None
            best_score = 0
            
            # 每一次都从所有特征中循环,找到那个使auc最大的特征,加入到good_features列表中
            for feature in range(num_features):
                # 如果该特征已经在good_features中,跳过
                if feature in good_features:
                    continue
                # 已有的特征新增本次循环的特征
                selected_features = good_features + [feature]
                # 获取新增特征后的数据
                xtrain = X[:, selected_features]
                # 计算auc分数
                score = self.evaluate_score(xtrain, y)
                
                # 如果该score大于best_score,那么改变本次新增特征为feature
                # 并改变best_score
                if score > best_score:
                    this_feature = feature
                    best_score = score
                    
            # 如果上面的for循环选好了本轮新增的特征
            # 将其添加到good_features列表并更新best_scores列表
            if this_feature != None:
                good_features.append(this_feature)
                best_scores.append(best_score)
                
            # 如果score没有提升,则退出while循环
            if len(best_scores) > 2:
                if best_scores[-1] < best_scores[-2]:
                    break
            
        # 最后新增的特征没有带来分数提升,弃用
        return best_scores[:-1], good_features[:-1]
    
    def __call__(self, X, y):
        """
        call函数会调用类,当传入参数的时候
        """
        # 获取特征索引列表和分数列表
        scores, features = self._feature_selection(X, y)
        # 获取特征选择后的样本
        return X[:, features], scores
    
if __name__ == "__main__":
    # 产生二分类数据
    X, y = make_classification(n_samples=1000, n_features=100)
    # 通过贪婪特征选择转换数据(相当于调用__call__函数)
    # 因为定义了__call__,这里的实例变成可调用对象
    X_transformed, scores = GreedyFeatureSelection()(X, y)

这种贪婪特征选择方法总体上是从零个特征开始,每次递归地增加一个新特征,
直到增加特征没有办法使得模型有更好的表现(分数)时,停止新增特征的动作。

4.2递归特征消除法(RFE)

另一种贪婪方法被称为递归特征消除法(RFE)。跟前一种方法相反,在RFE中,我们从所有特征开始,在每次迭代去除一个对给定模型价值最小的特征。

如何知道哪个特征价值最小?
如果使用SVM或者LR,我们会得为每个特征得到一个系数,该系数决定了特征的重要性。
如果是任何基于树的模型,我们得到的是特征重要性,而不是系数。

在每次迭代中,我们都可以剔除最不重要的特征或者系数接近0的特征,直到达到所需的特征数量为止。我们可以决定保留多少个特征。
下面的实例用了sklearn提供的RFE。

import pandas as pd

from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import fetch_california_housing

# 获取一个回归数据集
data = fetch_california_housing()
X = data["data"]
col_names = data["features_names"]
y = data["target"]

# 初始化模型
model = LinearRegression()
# 初始化RFE
rfe = RFE(
    estimator=model,
    n_features_to_select=3
)
# 拟合RFE
rfe.fit(X, y)

# 获取经过特征选择的转换后的数据
X_transformed = rfe.transform(X)

4.3基于特征重要性的特征选择方法

4.3.1特征重要性的获取和可视化

上面看了两种从模型中选择特征的贪婪方法。你也可以用模型拟合数据,然后根据特征系数或者特征重要性从模型中选择特征。
如果你用系数的方法选择特征,你可以设定一个阈值,如果特征的系数高于该阈值,则保留该特征,否则剔除。

下面看看如何用随机森林获取特征重要性。

import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.ensemble import RandomForestRegressor

# 获取糖尿病的回归数据,基于一些特征预测一年后糖尿病的发展
data = load_diabetes()
X = data["data"]
col_names = data["feature_names"]
y = data["target"]

# 初始化模型
model = RandomForestRegressor()

# 拟合模型
model.fit(X, y)

特征重要性的可视化。

import numpy as np
import matplotlib.pyplot as plt
importances = model.feature_importances_

# 特征重要性的排名,对importances升序排列,返回的是对应的索引值
idxs = np.argsort(importances)
plt.title('Feature Importances')
plt.barh(range(len(idxs)), importances[idxs], align='center')
# 将原始刻度修改为特征名字
plt.yticks(range(len(idxs)), [col_names[i] for i in idxs])
plt.xlabel('Random Forest Feature Importance')
plt.show()

下图是特征重要性的可视化结果。
在这里插入图片描述

4.3.2用SelectFromModel类做特征选择

你可以用一个模型来选择特征,然后用另一个模型进行训练,这不是什么新鲜事。

sklearn还提供了SelectFromModel类,可以帮助你直接从给定的模型中选择特征。
你还可以根据需要指定系数或特征重要性的阈值,以及要选择的特征的最大数量(这在下面的例子中没有体现)。
以下是用SelectFromModel类进行特征选择的例子。

import pandas as pd
from sklearn.datasets import load_diabetes
from sklearn.ensemble import RandomForestRegressor
from sklearn.feature_selection import SelectFromModel

# 读取数据
data = load_diabetes()
X = data["data"]
col_names = data["feature_names"]
y = data["target"]

# 特征选择
model = RandomForestRegressor()
sfm = SelectFromModel(estimator=model)

# 特征选择后的X
X_transformed = sfm.fit_transform(X, y)

# 获取特征是否选择的支持状态列表
support = sfm.get_support()
# 如果支持状态为True,则打印该特征名字
print([x for x, y in zip(col_names, support) if y == True])

上述代码打印结果为[‘bmi’, ‘s5’]。我们再看上图,可以发现这是最重要的两个特征。
所有树模型都提供特征重要性,所以本章所有基于树模型的片段都可用XGBoost、LightGBM或CatBoost等代替。
特征重要性函数的名称可能不同,产生的结果格式也可能不同,但用法是一样的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值