特征选择是特征工程中的重要问题(另一个重要的问题是特征提取),坊间常说:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程尤其是特征选择在机器学习中占有相当重要的地位。
根据特征选择的形式又可以将特征选择方法分为3种:
- Filter(过滤式)
- Wrapper(包裹式)
- embeded(嵌入式)
1 Filter(过滤式)
根据某种度量规则,从众多特征中挑选出对结果预测最有用的特征。这里的度量规则可以是各种距离度量,比如 卡方检验、欧拉距离 和 Pearson相关系数;也可以是信息熵(如何直观理解交叉熵及其优势?),比如 信息增益 和 信息增益率。
1.1 方差选择法
使用方差选择法,先要计算各个特征的方差,然后根据阈值,选择方差大于阈值的特征。使用feature_selection库的VarianceThreshold类来选择特征的代码如下:
from sklearn.feature_selection import VarianceThreshold #方差选择法,返回值为特征选择后的数据 #参数threshold为方差的阈值 VarianceThreshold(threshold=3).fit_transform(iris.data)
1.2 相关系数法
使用相关系数法,先要计算各个特征对目标值的相关系数以及相关系数的P值。用
feature_selection库的SelectKBest类结合相关系数来选择特征的代码如下: from sklearn.feature_selection import SelectKBest from scipy.stats import pearsonr #选择K个最好的特征,返回选择特征后的数据 #第一个参数为计算评估特征是否好的函数,该函数输入特征矩阵和目标向量, #输出二元组(评分,P值)的数组,数组第i项为第i个特征的评分和P值。在此定义为计算相关系数 #参数k为选择的特征个数 SelectKBest(lambda X, Y: array(map(lambda x:pearsonr(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)
1.3 卡方检验
经典的卡方检验是检验定性自变量对定性因变量的相关性。假设自变量有N种取值,因变量有M种取值,考虑自变量等于i且因变量等于j的样本频数的观察值与期望的差距,构建统计量:
不难发现,这个统计量的含义简而言之就是自变量对因变量的相关性。用feature_selection库的SelectKBest类结合卡方检验来选择特征的代码如下:
from sklearn.feature_selection import SelectKBest from sklearn.feature_selection import chi2 #选择K个最好的特征,返回选择特征后的数据 SelectKBest(chi2, k=2).fit_transform(iris.data, iris.target)
1.4 互信息法
经典的互信息也是评价定性自变量对定性因变量的相关性的,互信息计算公式如下:
为了处理定量数据,最大信息系数法被提出,使用feature_selection库的SelectKBest类结合最大信息系数法来选择特征的代码如下:
from sklearn.feature_selection import SelectKBest from minepy import MINE #由于MINE的设计不是函数式的,定义mic方法将其为函数式的, #返回一个二元组,二元组的第2项设置成固定的P值0.5 def mic(x, y): m = MINE() m.compute_score(x, y) return (m.mic(), 0.5) #选择K个最好的特征,返回特征选择后的数据 SelectKBest(lambda X, Y: array(map(lambda x:mic(x, Y), X.T)).T, k=2).fit_transform(iris.data, iris.target)
2 Wrapper(包裹式)
包裹法 把特征选择看做是一个针对特征子集的搜索问题,依赖具体的模型效果来评估好坏。一般采用线性模型评估效果,先用全量特征训练,陆续批量删掉权重较低的特征,观察指标,直到指标出现重大下滑停止。所用的方法叫 递归特征消除算法。
2.1 递归特征消除法
递归消除特征法使用一个基模型来进行多轮训练,每轮训练后,消除若干权值系数的特征,再基于新的特征集进行下一轮训练。使用feature_selection库的RFE类来选择特征的代码如下:
from sklearn.feature_selection import RFE from sklearn.linear_model import LogisticRegression #递归特征消除法,返回特征选择后的数据 #参数estimator为基模型 #参数n_features_to_select为选择的特征个数 RFE(estimator=LogisticRegression(), n_features_to_select=2).fit_transform(iris.data, iris.target)
3 embeded(嵌入式)
确定模型的过程中,挑选出那些对模型的训练有意义的特征。(特征选择和模型训练融为一体)
3.1 基于惩罚项的特征选择法
使用带惩罚项的基模型,除了筛选出特征外,同时也进行了降维。使用feature_selection库的SelectFromModel类结合带L1惩罚项的逻辑回归模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel from sklearn.linear_model import LogisticRegression #带L1惩罚项的逻辑回归作为基模型的特征选择 SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(iris.data, iris.target)
实际上,L1惩罚项降维的原理在于保留多个对目标值具有同等相关性的特征中的一个,所以没选到的特征不代表不重要。故,可结合L2惩罚项来优化。具体操作为:若一个特征在L1中的权值为1,选择在L2中权值差别不大且在L1中权值为0的特征构成同类集合,将这一集合中的特征平分L1中的权值,故需要构建一个新的逻辑回归模型:
1 from sklearn.linear_model import LogisticRegression 2 3 class LR(LogisticRegression): 4 def __init__(self, threshold=0.01, dual=False, tol=1e-4, C=1.0, 5 fit_intercept=True, intercept_scaling=1, class_weight=None, 6 random_state=None, solver='liblinear', max_iter=100, 7 multi_class='ovr', verbose=0, warm_start=False, n_jobs=1): 8 9 #权值相近的阈值 10 self.threshold = threshold 11 LogisticRegression.__init__(self, penalty='l1', dual=dual, tol=tol, C=C, 12 fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight=class_weight, 13 random_state=random_state, solver=solver, max_iter=max_iter, 14 multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs) 15 #使用同样的参数创建L2逻辑回归 16 self.l2 = LogisticRegression(penalty='l2', dual=dual, tol=tol, C=C, fit_intercept=fit_intercept, intercept_scaling=intercept_scaling, class_weight = class_weight, random_state=random_state, solver=solver, max_iter=max_iter, multi_class=multi_class, verbose=verbose, warm_start=warm_start, n_jobs=n_jobs) 17 18 def fit(self, X, y, sample_weight=None): 19 #训练L1逻辑回归 20 super(LR, self).fit(X, y, sample_weight=sample_weight) 21 self.coef_old_ = self.coef_.copy() 22 #训练L2逻辑回归 23 self.l2.fit(X, y, sample_weight=sample_weight) 24 25 cntOfRow, cntOfCol = self.coef_.shape 26 #权值系数矩阵的行数对应目标值的种类数目 27 for i in range(cntOfRow): 28 for j in range(cntOfCol): 29 coef = self.coef_[i][j] 30 #L1逻辑回归的权值系数不为0 31 if coef != 0: 32 idx = [j] 33 #对应在L2逻辑回归中的权值系数 34 coef1 = self.l2.coef_[i][j] 35 for k in range(cntOfCol): 36 coef2 = self.l2.coef_[i][k] 37 #在L2逻辑回归中,权值系数之差小于设定的阈值,且在L1中对应的权值为0 38 if abs(coef1-coef2) < self.threshold and j != k and self.coef_[i][k] == 0: 39 idx.append(k) 40 #计算这一类特征的权值系数均值 41 mean = coef / len(idx) 42 self.coef_[i][idx] = mean 43 return self
使用feature_selection库的SelectFromModel类结合带L1以及L2惩罚项的逻辑回归模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel
#带L1和L2惩罚项的逻辑回归作为基模型的特征选择 #参数threshold为权值系数之差的阈值
SelectFromModel(LR(threshold=0.5, C=0.1)).fit_transform(iris.data, iris.target)
3.2 基于树模型的特征选择法
树模型中GBDT也可用来作为基模型进行特征选择,使用feature_selection库的SelectFromModel类结合GBDT模型,来选择特征的代码如下:
from sklearn.feature_selection import SelectFromModel from sklearn.ensemble import GradientBoostingClassifier #GBDT作为基模型的特征选择 SelectFromModel(GradientBoostingClassifier()).fit_transform(iris.data, iris.target