欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

如何进行特征选择 实践

程序员文章站 2024-03-08 19:39:28
...

在做特征抽取的时候,我们是尽可能地抽取更多的Feature,但过多的 Feature 会造成 冗余(部分特征的相关度太高了,消耗计算性能),噪声(部分特征是对预测结果有负影响),容易过拟合等问题,因此我们需要进行 特征筛选。特征选择可以加快模型的训练速度,甚至还可以提升效果。

接下来,我们了解下各种特征选择的方式。

如何进行特征选择 实践

 

  • 过滤型(Filter)

评估 单个特征 和 结果值 之间的相关程度,我们可以排序留下Top相关程度较高 的的特征部分。而计算相关程度可以用相关系数、互信息、卡方检验,距离相关度来 计算。这种方法的缺点是:没有考虑到特征之间的关联作用,可能把有用的关联特征误踢掉(数值为0,并不能说明两个变量不相关,只是线性不相关而已)。

  1. 方差选择法

 使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。

from sklearn.datasets import load_iris
from sklearn.feature_selection import VarianceThreshold 
from sklearn.feature_selection import SelectKBest  # 根据 k个最高分选择功能。
Data = load_iris()
X, y = Data.data, Data.target
print(X.shape)
X_new = VarianceThreshold(threshold=3).fit_transform(X, y) #参数threshold为方差的阈值
print(X_new.shape)  

>>>(150, 4)
>>>(150, 1)   

 2.相关系数法

import numpy as np
from scipy.stats import pearsonr

np.random.seed(0)
size = 200
X = np.random.normal(0, 1, size)
print("Lower noise", pearsonr(X, X+np.random.normal(0, 1, size)))
print("higher noise", pearsonr(X, X+np.random.normal(0, 10. size)))

>>>Lower noise (0.7579114556127933, 1.3803800258462316e-38)
>>>higher noise (0.04185684491264431, 0.5562015462329835)   

   3.卡方检验

卡方检验是检验定性自变量对定性因变量的相关性。假设自变量有N种取值,因变量有M种取值,考虑自变量等于i且因变量等于j的样本频数的观察值与期望的差距,构建统计量:

如何进行特征选择 实践

用feature_selection库的SelectKBest类 结合 卡方检验 来选择特征的代码如下:

from sklearn.datasets import load_iris
from sklearn.feature_selection import chi2  #卡方检验
from sklearn.feature_selection import SelectKBest  # 根据 k个最高分选择功能。
Data = load_iris()
X, y = Data.data, Data.target
print(X.shape)
X_new = SelectKBest(chi2, k=2).fit_transform(X, y)
print(X_new.shape)

  >>>(150, 4)
  >>> (150, 2)

  4.互信息法

互信息是用于评价离散特征对离散目标变量的相关性。

互信息用于特征选择有以下缺点:

  1. 它不属于度量方式,无法进行归一化,在不同数据集上的结果无法做比较;
  2. 对于连续变量的计算不是很方便(X和Y都是集合,x,y都是离散的取值),通常变量需要先离散化,而互信息的结果对离散化的方式很敏感。

最大信息系数(Maximal Information Coefficient, MIC)克服了这两个问题。它首先寻找一种最优的离散化方式,然后把互信息取值转换成一种度量方式,取值区间在[0,1]。

MIC度量计算的方法

具有两个属性的数据点的集合分布在两维的空间中,使用m乘以n的网格划分数据空间,使落在第(x,y)格子中的数据点的频率作为P(x,y)的估计即如何进行特征选择 实践,使落在第x行的数据点的频率作为P(x)的估计,同理获得P(y)的估计。然后计算随机变量X、Y的互信息。因为m乘以n的网格划分数据点的方式不止一种,所以我们要获得使互信息最大的网格划分。然后使用归一化因子,将互信息的值转化为(0,1)区间之内。最后,找到能使归一化互信息最大的网格分辨率,作为MIC的度量值。其中网格的分辨率限制为m x n < B,如何进行特征选择 实践

将MIC的计算过程概括为公式为:如何进行特征选择 实践

minepy提供了MIC功能,代码如下:

from minepy import MINE
import numpy as np
m = MINE()
x = np.random.uniform(-1, 1, 10000)
m.compute_score(x, x ** 2)
print(m.mic())

>>>1.0000000000000009

MIC的不足:零假设不成立时,MIC的统计就会受到影响。在有的数据集上不存在这个问题,但有的数据集上就存在这个问题。

  • 包裹型(Wrapper)

包裹型是指把特征选择看做一个特征子集搜索问题,筛选各种特征子集,用模型评估效果。典型的包裹型算法为 “递归特征删除算法”REF(recursive feature elimination algorithm)

比如用逻辑回归,怎么做这个事情呢?

1、用全量特征跑一个模型

2、根据线性模型的系数(体现相关性),删掉5-10%的弱特征,观察 准确率/auc的变化

3、逐步进行,直至准确率/auc出现大的下滑停止

from sklearn.feature_selection import RFE # 递归特征删除算法,通过递归地删除越来越小来弱特征
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_boston
data = load_boston()
X = data["data"]
y = data["target"]
names = data["feature_names"]

LR = LinearRegression()
rfe = RFE(LR, n_features_to_select=1) #对所有特征排序,持续消除直到最后一个
rfe.fit(X, y)
print("通过排序后得到特征分类")

# 在 Python 3.x 中为了减少内存,zip() 返回的是一个对象。如需展示列表,需手动 list() 转换。
print(sorted(zip(map(lambda x: round(x, 3), rfe.ranking_), names), reverse=True))# 返回最接近于参数x的数,其值保留到小数点后面3位

>>>[(13, 'AGE'), (12, 'B'), (11, 'TAX'), (10, 'ZN'), (9, 'INDUS'), (8, 'CRIM'), (7, 'RAD'), (6, 'LSTAT'), (5, 'DIS'), (4, 'PTRATIO'), (3, 'CHAS'), (2, 'RM'), (1, 'NOX')]

  • 嵌入型(Embedded)

嵌入型特征选择是指根据模型来分析 特征的重要性(有别于上面的方式, 是从产生的模型权重等.  最常见的方式为用正则化方式来做特征选择。

正则化一般分为两种:L1正则化 和 L2正则化。

一般回归分析中 回归w 表示 特征的系数,从下式可以看到正则化项是对 系数做了处理(限制)

L1正则化和L2正则化的说明如下:

  •  L1正则化是指权值向量w中 各个元素的绝对值之和,通常表示为

如何进行特征选择 实践

  •  L2正则化是指权值向量w中 各个元素的平方和(可以看到Ridge回归的L2正则化项有平方符号),通常表示为

如何进行特征选择 实践

一般都会在正则化项之前添加一个系数,Python中用 α 表示,一些文章也用 λ 表示,主要用来 权衡正则项与C0项的比重。另外L2正则化项须要再乘以1/2,要是为了后面求导的结果方便,后面那一项求导会产生一个2, 与1/2相乘刚好凑整。

L2正则化项是怎么避免overfitting的呢?

我们推导一下看看,先求导:

如何进行特征选择 实践

能够发现 L2正则化项 对 b 的更新没有影响,可是对于 w 的更新有影响:

如何进行特征选择 实践

在不使用L2正则化时,求导结果中 w 前系数为 1,经变化后w前面系数为 1−ηλ/n ,由于η、λ、n都是正的。所以 1−ηλ/n小于1,它的效果是减小w,这也就是权重衰减(weight decay)的由来。

更小的权值w,从某种意义上说,表示网络的复杂度更低,对数据的拟合刚刚好(这个法则也叫做奥卡姆剃刀)

那添加L1和L2正则化有什么用?

  • L1正则化可以产生 稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择
  • L2正则化可以防止模型 过拟合(overfitting)。当然,一定程度上,L1也可以防止过拟合。

稀疏模型与特征选择


上面提到 L1正则化b 有助于生成一个稀疏权值矩阵,进而可以用于特征选择。为什么要生成一个稀疏矩阵?

稀疏矩阵指的是很多元素为0,只有少数元素是非零值的矩阵,即得到的线性回归模型的大部分系数都是0。通常机器学习中特征数量很多,例如文本处理时,如果将一个词组(term)作为一个特征,那么特征数量会达到上万个(bigram)。
在预测或分类时,那么多特征显然难以选择,而且模型复杂。但是,如果 代入这些特征 得到的模型是一个 稀疏模型,表示只有少数特征对这个模型有贡献,绝大部分特征是没有贡献的,或者贡献微小(因为它们前面的系数是0或者是很小的值,即使去掉对模型也没有什么影响),此时我们就可以只关注系数是非零值的特征,大大减少模型复杂度,这就是稀疏模型与特征选择的关系。

举个例子,最早在电商用LR做CTR预估,在3-5亿维的系数 特征上用L1正则化的LR模型。剩余2-3千万的feature,意味着其他的feature重要度不够。

在什么情况下使用L1,什么情况下使用L2?

L1和L2的差别,为什么一个让绝对值最小,一个让平方最小,会有那么大的差别呢?我看到的有两种几何上直观的解析:

(1)下降速度:

我们知道,L1和L2都是规则化的方式,我们将 权值参数 以 L1 或者 L2 的方式放到代价函数里面去。然后模型就会尝试去最小化这些权值参数。而这个最小化就像一个下坡的过程,L1和L2的差别就在于这个“坡”不同,如下图:L1就是按绝对值函数的“坡”下降的,而L2是按二次函数的“坡”下降。所以实际上在0附近,L1的下降速度比L2的下降速度要快,所以会非常快得降到0。不过我觉得这里解释的不太中肯,当然了也不知道是不是自己理解的问题.

L1称Lasso,L2称Ridge

如何进行特征选择 实践

L1会趋向于产生少量的特征,而其他的特征都是0,而L2会选择更多的特征,这些特征都会接近于0。

L1(Lasso)在 特征选择 时候非常有用,而L2(Ridge)就只是一种规则化而已.

通过训练带L1或L2惩罚项的Linear Model模型来筛选特征

from sklearn.svm import LinearSVC
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
data = load_iris()
X, y = data.data, data.target
LSVC_L1 = LinearSVC(C=0.01, penalty="l1", dual=False).fit(X, y)
LSVC_L2 = LinearSVC(C=0.01, penalty="l2", dual=False).fit(X, y)
#如果prefit=为True,则必须直接调用``transform``, 否则使用``fit``然后``transform``来训练模型
model_L1 = SelectFromModel(LSVC_L1, prefit=True)
model_L2 = SelectFromModel(LSVC_L2, prefit=True)
# model.fit(X, y)
X_L1 = model_L1.transform(X)
X_L2 = model_L2.transform(X)
print(X.shape)
print(X_L1.shape)
print(X_L2.shape)

>>>(150, 4)

>>>(150, 2)

基于随机森林的特征选择法

随机森林由多个决策树构成。决策树中的每一个节点都是基于某个特征的将数据集按照不同的label一分为二。利用随机森林提供的不纯度可以进行特征选择,对于分类问题,通常采用 基尼指数Gini 或者 信息增益IG对于回归问题,通常采用的是 方差 或者最小二乘拟合。当训练随机森林时,可算出每个特征平均减少了多少不纯度,并把它平均减少的不纯度作为特征选择的依据。基于随机森林的特征选择代码如下:

from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rf = RandomForestRegressor()
rf.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), reverse=True))

>>>Features sorted by their score:
>>>[(0.4761, 'LSTAT'), (0.3321, 'RM'), (0.0649, 'DIS'), (0.0361, 'CRIM'), (0.0245, 'NOX'), (0.0178, 'TAX'), (0.0144, 'AGE'), (0.0119, 'PTRATIO'), (0.0097, 'B'), (0.0059, 'INDUS'), (0.0048, 'RAD'), (0.0014, 'CHAS'), (0.0004, 'ZN')]
 

总结

如何进行特征选择 实践

相关标签: 特征选择