文章系列:
特征工程系列:数据清洗
特征工程系列:特征筛选的原理与实现
特征工程系列:特征预处理
特征工程系列:特征构造
特征工程系列:时间特征构造以及时间序列特征构造
数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。由此可见,特征工程在机器学习中占有相当重要的地位。在实际应用当中,可以说特征工程是机器学习成功的关键。
那特征工程是什么?
特征工程是利用数据领域的相关知识来创建能够使机器学习算法达到最佳性能的特征的过程。
特征工程又包含了Feature Selection(特征选择)、Feature Extraction(特征提取)和Feature construction(特征构造)等子问题,本章内容主要讨论特征选择相关的方法及实现。
在实际项目中,我们可能会有大量的特征可使用,有的特征携带的信息丰富,有的特征携带的信息有重叠,有的特征则属于无关特征,如果所有特征不经筛选地全部作为训练特征,经常会出现维度灾难问题,甚至会降低模型的准确性。因此,我们需要进行特征筛选,排除无效/冗余的特征,把有用的特征挑选出来作为模型的训练数据。
1.特征按重要性分类
相关特征:对于学习任务(例如分类问题)有帮助,可以提升学习算法的效果;
无关特征:对于我们的算法没有任何帮助,不会给算法的效果带来任何提升;
冗余特征:不会对我们的算法带来新的信息,或者这种特征的信息可以由其他的特征推断出;
2.特征选择的目的
对于一个特定的学习算法来说,哪一个特征是有效的是未知的。因此,需要从所有特征中选择出对于学习算法有益的相关特征。而且在实际应用中,经常会出现维度灾难问题。如果只选择所有特征中的部分特征构建模型,那么可以大大减少学习算法的运行时间,也可以增加模型的可解释性。
3.特征选择的原则
获取尽可能小的特征子集,不显著降低分类精度、不影响分类分布以及特征子集应具有稳定、适应性强等特点。
1.Filter方法(过滤式)
先进行特征选择,然后去训练学习器,所以特征选择的过程与学习器无关。相当于先对特征进行过滤操作,然后用特征子集来训练分类器。
主要思想:对每一维特征“打分”,即给每一维的特征赋予权重,这样的权重就代表着该特征的重要性,然后依据权重排序。
主要方法:
Chi-squared test(卡方检验)
参考文章1
参考文章2
Information gain(信息增益)
Correlation coefficient scores(相关系数)
优点 | 缺点 |
---|---|
运行速度快,是一种非常流行的特征选择方法 | 无法提供反馈,特征选择的标准/规范的制定是在特征搜索算法中完成,学习算法无法向特征搜索算法传递对特征的需求。另外,可能处理某个特征时由于任意原因表示该特征不重要,但是该特征与其他特征结合起来则可能变得很重要。 |
2.Wrapper方法(封装式)
直接把最后要使用的分类器作为特征选择的评价函数,对于特定的分类器选择最优的特征子集。
主要思想:将子集的选择看作是一个搜索寻优问题,生成不同的组合,对组合进行评价,再与其他的组合进行比较。这样就将子集的选择看作是一个优化问题,这里有很多的优化算法可以解决,尤其是一些启发式的优化算法,如GA、PSO(如:优化算法-粒子群算法)、DE、ABC(如:优化算法-人工蜂群算法)等。
主要方法:递归特征消除算法。
优点 | 缺点 |
---|---|
对特征进行搜索时围绕学习算法展开的,对特征选择的标准/规范是在学习算法的需求中展开的,能够考虑学习算法所属的任意学习偏差,从而确定最佳子特征,真正关注的是学习问题本身。由于每次尝试针对特定子集时必须运行学习算法,所以能够关注到学习算法的学习偏差/归纳偏差,因此封装能够发挥巨大的作用。 | 运行速度远慢于过滤算法,实际应用用封装方法没有过滤方法流行。 |
3.Embedded方法(嵌入式)
将特征选择嵌入到模型训练当中,其训练可能是相同的模型,但是特征选择完成后,还能给予特征选择完成的特征和模型训练出的超参数,再次训练优化。
主要思想:在模型既定的情况下学习出对提高模型准确性最好的特征。也就是在确定模型的过程中,挑选出那些对模型的训练有重要意义的特征。
主要方法:用带有L1正则化的项完成特征选择(也可以结合L2惩罚项来优化)、随机森林平均不纯度减少法/平均精确度减少法。
优点 | 缺点 |
---|---|
对特征进行搜索时围绕学习算法展开的,能够考虑学习算法所属的任意学习偏差。训练模型的次数小于Wrapper方法,比较节省时间。 | 运行速度慢。 |
该方法一般用在特征选择前作为一个预处理的工作,即先去掉取值变化小的特征,然后再使用其他特征选择方法选择特征。
考察某个特征下,样本的方差,可以认为给定一个阈值,抛弃哪些小于某个阈值的特征。
1.实现原理
离散型变量:
假设某特征的特征值只有0和1,并且在所有输入样本中,95%的实例的该特征取值都是1,那就可以认为这个特征作用不大。
如果100%都是1,那这个特征就没意义了。
连续型变量:
需要将连续变量离散化之后才能用。
而且实际当中,一般不太会有95%以上都取某个值的特征存在,所以这种方法虽然简单但是不太好用。可以把它作为特征选择的预处理,先去掉那些取值变化小的特征,然后再从接下来提到的的特征选择方法中选择合适的进行进一步的特征选择。
from sklearn.feature_selection import VarianceThreshold
X = [[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 1, 1], [0, 1, 0], [0, 1, 1]]
sel = VarianceThreshold(threshold=(.8 * (1 - .8)))
sel.fit_transform(X)
# 输出结果 从结果可以看出,因为第一列只有1个不为0,所以去掉第一列
array([[0, 1],
[1, 0],
[0, 0],
[1, 1],
[1, 0],
[1, 1]])
这种情况在实际竞赛中基本不会出现,可以忽略。
单变量特征选择方法独立的衡量每个特征与响应变量之间的关系,单变量特征选择能够对每一个特征进行测试,衡量该特征和响应变量之间的关系,根据得分扔掉不好的特征。该方法简单,易于运行,易于理解,通常对于理解数据有较好的效果(但对特征优化、提高泛化能力来说不一定有效);这种方法有许多改进的版本、变种。
1.Pearson相关系数(Pearson Correlation)
皮尔森相关系数是一种最简单的,能帮助理解特征和响应变量之间关系的方法,该方法衡量的是变量之间的线性相关性。
1)原理介绍
r i j = ∑ k = 1 m ( x k i − x ˉ i ) ( x k j − x ˉ j ) [ ∑ k = 1 m ( x k i − x ˉ i ) 2 ∑ k = 1 m ( x k j − x ˉ j ) 2 ] 1 2 r_{i j}=\frac{\sum_{k=1}^{m}\left(x_{k i}-\bar{x}_{i}\right)\left(x_{k j}-\bar{x}_{j}\right)}{\left[\sum_{k=1}^{m}\left(x_{k i}-\bar{x}_{i}\right)^{2} \sum_{k=1}^{m}\left(x_{k j}-\bar{x}_{j}\right)^{2}\right]^{\frac{1}{2}}} rij=[∑k=1m(xki−xˉi)2∑k=1m(xkj−xˉj)2]21∑k=1m(xki−xˉi)(xkj−xˉj)
x ˉ i = 1 m ∑ k = 1 m x k i , x ˉ j = 1 m ∑ k = 1 m x k j \bar{x}_{i}=\frac{1}{m} \sum_{k=1}^{m} x_{k i}, \bar{x}_{j}=\frac{1}{m} \sum_{k=1}^{m} x_{k j} xˉi=m1k=1∑mxki,xˉj=m1k=1∑mxkj
就是用 x i x_i xi、 x j x_j xj的协方差除以 x i x_i xi的标准差和 x j x_j xj的标准差,可以看成一种剔除了两个变量量纲影响、标准化后的特殊协方差。
协方差是度量各个维度偏离其均值的程度,协方差的值为正值时说明两者是正相关,否则是负相关的。
结果的取值区间为[-1,1],-1表示完全的负相关,+1表示完全的正相关,0表示没有线性相关,绝对值表示相关性的强度。
标准差也称均方差,是方差的算术平方根,能反映一个数据集的离散程度。
主要用于连续型特征的筛选,不适用于离散型特征的筛选。
优点:相关系数计算速度快、易于计算,经常在拿到数据(经过清洗和特征提取之后的)之后第一时间就执行。Pearson相关系数能够表征丰富的关系,符合表示关系的正负,绝对值能够表示强度。
缺点:相关系数作为特征排序机制,它只对线性关系敏感,如果关系是非线性的,即便两个变量具有一一对应的关系,相关系数系数也可能会接近0。
import numpy as np
from scipy.stats import pearsonr
np.random.seed(2019)
size=1000
x = np.random.normal(0, 1, size)
# 计算两变量间的相关系数
print("Lower noise {}".format(pearsonr(x, x + np.random.normal(0, 1, size))))
print("Higher noise {}".format(pearsonr(x, x + np.random.normal(0, 10, size))))
#输出结果
Lower noise (0.7261901361835682, 1.365052942629593e-164)
Higher noise (0.09357375287756171, 0.003057851441699255)
2.互信息和最大信息系数(Mutual information and maximal information coefficient)
如果变量不是独立的,那么我们可以通过考察联合概率分布与边缘概率分布乘积之间的 Kullback-Leibler 散度来判断它们是否“接近”于相互独立。
1)互信息方法
熵H(Y)与条件熵H(Y|X)之间的差称为互信息,互信息与条件熵之间的关系:
I ( x , y ) = H ( x ) − H ( x ∣ y ) = H ( y ) − H ( y ∣ x ) \mathrm{I}(\mathrm{x}, \mathrm{y})=\mathrm{H}(\mathrm{x})-\mathrm{H}(\mathrm{x} | \mathrm{y})=\mathrm{H}(\mathrm{y})-\mathrm{H}(\mathrm{y} | \mathrm{x}) I(x,y)=H(x)−H(x∣y)=H(y)−H(y∣x)
其实,这就是ID3决策树的特征选择规则。
互信息法也是评价定性自变量对定性因变量的相关性的,但是并不方便直接用于特征选择:
它不属于度量方式,也没有办法进行归一化,在不同的数据上的结果无法做比较。
只能用于离散型特征的选择,连续型特征需要先进行离散化才能用互信息进行特征选择,而互信息的结果对离散化的方式很敏感。
2)最大信息系数方法
由于互信息法并不方便直接用于特征选择,因此引入了最大信息系数。最大信息数据首先寻找一种最优的离散方式,然后把互信息取值转换成一种度量方式,取值区间为[0,1]。
#最大信息系数方法代码实现
x = np.random.normal(0,10,300)
z = x *x
pearsonr(x,z)
# 输出-0.1
from minepy import MINE
m = MINE()
m.compute_score(x, z)
print(m.mic())
# 输出1.0
3.距离相关系数(Distance correlation)
距离相关系数是为了克服Pearson相关系数的弱点而生的。
1)原理介绍
d ^ corr ( u , v ) = d ^ cov ( u , v ) d ^ cov ( u , u ) d ^ cov ( v , v ) d ^ cov 2 ( u , v ) = S ^ 1 + S ^ 2 − 2 S ^ 3 \begin{aligned} \hat{d} \operatorname{corr}(u, v) &=\frac{\hat{d} \operatorname{cov}(u, v)}{\sqrt{\hat{d} \operatorname{cov}(u, u) \hat{d} \operatorname{cov}(v, v)}} \\ \hat{d} \operatorname{cov}^{2}(u, v) &=\widehat{S}_{1}+\widehat{S}_{2}-2 \widehat{S}_{3} \end{aligned} d^corr(u,v)d^cov2(u,v)=d^cov(u,u)d^cov(v,v)d^cov(u,v)=S 1+S 2−2S 3
S 1 ^ = 1 n 2 ∑ i = 1 n ∑ j = 1 n ∥ u i − u j ∥ d u ∥ v i − v j ∥ d v + S 2 ^ = 1 n 2 ∑ i = 1 n ∑ j = 1 n ∥ u i − u j ∥ d u 1 n 2 ∑ i = 1 n ∑ j = 1 n ∥ v i − v j ∥ d v S 3 ^ = 1 n 3 ∑ i = 1 n ∑ j = 1 n ∑ l = 1 n ∥ u i − u l ∥ d u ∥ v j − v l ∥ d v + \begin{array}{l}{\widehat{S_{1}}=\frac{1}{n^{2}} \sum_{i=1}^{n} \sum_{j=1}^{n}\left\|u_{i}-u_{j}\right\|_{d_{u}}\left\|v_{i}-v_{j}\right\|_{d_{v}}^{+}} \\ {\widehat{S_{2}}=\frac{1}{n^{2}} \sum_{i=1}^{n} \sum_{j=1}^{n}\left\|u_{i}-u_{j}\right\|_{d_{u}} \frac{1}{n^{2}} \sum_{i=1}^{n} \sum_{j=1}^{n}\left\|v_{i}-v_{j}\right\|_{d_{v}}} \\ {\widehat{S_{3}}=\frac{1}{n^{3}} \sum_{i=1}^{n} \sum_{j=1}^{n} \sum_{l=1}^{n}\left\|u_{i}-u_{l}\right\|_{d_{u}}\left\|v_{j}-v_{l}\right\|_{d_{v}}^{+}}\end{array} S1 =n21∑i=1n∑j=1n∥ui−uj∥du∥vi−vj∥dv+S2 =n21∑i=1n∑j=1n∥ui−uj∥dun21∑i=1n∑j=1n∥vi−vj∥dvS3 =n31∑i=1n∑j=1n∑l=1n∥ui−ul∥du∥vj−vl∥dv+
同理计算 d ^ cov ( u , u ) \hat{d} \operatorname{cov}(u, u) d^cov(u,u)和 d ^ cov ( v , v ) \hat{d} \operatorname{cov}(v, v) d^cov(v,v)。
Pearson相关系数是0,我们也不能断定这两个变量是独立的(有可能是非线性相关)。
例如 x x x和 x 2 x^2 x2之间的 P e a r s o n Pearson Pearson相关系数是0,但是两个变量并不是独立的。
from scipy.spatial.distance import pdist, squareform
import numpy as np
from numbapro import jit, float32
def distcorr(X, Y):
""" Compute the distance correlation function
>>> a = [1,2,3,4,5]
>>> b = np.array([1,2,9,4,4])
>>> distcorr(a, b)
0.762676242417
"""
X = np.atleast_1d(X)
Y = np.atleast_1d(Y)
if np.prod(X.shape) == len(X):
X = X[:, None]
if np.prod(Y.shape) == len(Y):
Y = Y[:, None]
X = np.atleast_2d(X)
Y = np.atleast_2d(Y)
n = X.shape[0]
if Y.shape[0] != X.shape[0]:
raise ValueError('Number of samples must match')
a = squareform(pdist(X))
b = squareform(pdist(Y))
A = a - a.mean(axis=0)[None, :] - a.mean(axis=1)[:, None] + a.mean()
B = b - b.mean(axis=0)[None, :] - b.mean(axis=1)[:, None] + b.mean()
dcov2_xy = (A * B).sum()/float(n * n)
dcov2_xx = (A * A).sum()/float(n * n)
dcov2_yy = (B * B).sum()/float(n * n)
dcor = np.sqrt(dcov2_xy)/np.sqrt(np.sqrt(dcov2_xx) * np.sqrt(dcov2_yy))
return dcor
4.基于学习模型的特征排序(Model based ranking)
这种方法的思路是直接使用你要用的机器学习算法,针对每个单独的特征和响应变量建立预测模型。如果特征与响应变量之间的关系是非线性的,则有许多替代方案,例如基于树的方法(决策树,随机森林)、或者扩展的线性模型等。基于树的方法是最简单的方法之一,因为他们可以很好地模拟非线性关系,不需要太多的调整。但是要避免的主要是过度拟合,因此树的深度应该相对较小,并且应该应用交叉验证。
from sklearn.model_selection import cross_val_score, ShuffleSplit
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rf = RandomForestRegressor(n_estimators=20, max_depth=4)
scores = []
# 使用每个特征单独训练模型,并获取每个模型的评分来作为特征选择的依据。
for i in range(X.shape[1]):
score = cross_val_score(rf, X[:, i:i+1], Y, scoring="r2",cv=ShuffleSplit(len(X), 3, .3))
scores.append((round(np.mean(score), 3), names[i]))
print(sorted(scores, reverse=True))
#输出结果
[(-2.904, 'LSTAT'), (-3.258, 'RAD'), (-3.354, 'CHAS'), (-4.226, 'ZN'), (-4.503, 'RM'), (-5.477, 'INDUS'), (-5.877, 'PTRATIO'), (-6.142, 'TAX'), (-6.384, 'AGE'), (-7.149, 'NOX'), (-9.375, 'B'), (-10.484, 'CRIM'), (-11.399, 'DIS')]
5.卡方检验
卡方检验是一种用途很广的计数资料的假设检验方法,由卡尔•皮尔逊提出。卡方值描述两个事件的独立性或者描述实际观察值与期望值的偏离程度。卡方值越大,表名实际观察值与期望值偏离越大,也说明两个事件的相互独立性越弱。
1)原理介绍
C H I ( x , y ) = χ 2 ( x , y ) = ∑ ( A − T ) 2 T \mathrm{CHI}(\mathrm{x}, \mathrm{y})=\chi^{2}(x, y)=\sum \frac{(A-T)^{2}}{T^{\mathrm{}}} CHI(x,y)=χ2(x,y)=∑T(A−T)2
C H I CHI CHI值(卡方值)用于衡量实际值与理论值的差异程度,除以T是为了避免不同观察值与不同期望之间产生的偏差因T的不同而差别太大,所以除以E以消除这种弊端。
实际值与理论值偏差的绝对大小(由于平方的存在,差异被放大)
差异值与理论值的相对大小
2)实现流程
CHI值越大,说明两个变量越不可能是独立无关的,也就是说CHI值越大,两个变量的相关程度也越高。
a. 对于特征变量x1,x2,…,xn,以及分类变量y。只需要计算CHI(x1,y)、CHI(x2,y)、…、CHI(xn,y),并按照CHI的值从大到小将特征排序。
b. 选择合适的阈值,大于阈值的特征留下,小于阈值的特征删除。这样筛选出一组特征子集就是输入模型训练的特征。
3)只适用于分类问题中离散型特征筛选,不能用于分类问题中连续型特征的筛选,也不能用于回归问题的特征筛选。
4)代码实现
现实方法:
sklearn.feature_selection.SelectKBest:
返回k个最佳特征
sklearn.feature_selection.SelectPercentile:
返回表现最佳的前r%个特征
#导入sklearn库中的SelectKBest和chi2
from sklearn.feature_selection import SelectKBest ,chi2
#选择相关性最高的前5个特征
X_chi2 = SelectKBest(chi2, k=5).fit_transform(X, y)
X_chi2.shape
输出:(27, 5)
1.主要思想
当所有特征在相同尺度上时,最重要的特征应该在模型中具有最高系数,而与输出变量不相关的特征应该具有接近零的系数值。即使使用简单的线性回归模型,当数据不是很嘈杂(或者有大量数据与特征数量相比)并且特征(相对)独立时,这种方法也能很好地工作。
2.正则化模型
正则化就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。损失函数由原来的 E ( X , Y ) E(X,Y) E(X,Y)变为 E ( X , Y ) + a l p h a ∣ ∣ w ∣ ∣ E(X,Y)+alpha||w|| E(X,Y)+alpha∣∣w∣∣, w w w是模型系数组成的向量(有些地方也叫参数parameter,coefficients), ∣ ∣ ⋅ ∣ ∣ ||·|| ∣∣⋅∣∣一般是 L 1 L1 L1或者 L 2 L2 L2范数, a l p h a alpha alpha是一个可调的参数,控制着正则化的强度。当用在线性模型上时, L 1 L1 L1正则化和 L 2 L2 L2正则化也称为 L a s s o Lasso Lasso和 R i d g e Ridge Ridge。
1)L1正则化/Lasso regression
L1正则化将系数 w w w的 L 1 L1 L1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0。因此 L 1 L1 L1正则化往往会使学到的模型很稀疏(系数 w w w经常为0),这个特性使得 L 1 L1 L1正则化成为一种很好的特征选择方法。
Lasso能够挑出一些优质特征,同时让其他特征的系数趋于0。当如需要减少特征数的时候它很有用,但是对于数据理解来说不是很好用。
2)L2正则化/Ridge regression
L2正则化将系数向量的 L 2 L2 L2范数添加到了损失函数中。
由于 L 2 L2 L2惩罚项中系数是二次方的,这使得 L 2 L2 L2和 L 1 L1 L1有着诸多差异,最明显的一点就是,L2正则化会让系数的取值变得平均。
对于关联特征,这意味着他们能够获得更相近的对应系数。
Ridge将回归系数均匀的分摊到各个关联变量上。
L2正则化对于特征选择来说一种稳定的模型,不像L1正则化那样,系数会因为细微的数据变化而波动。所以L2正则化和L1正则化提供的价值是不同的,L2正则化对于特征理解来说更加有用:表示能力强的特征对应的系数是非零。
3.原理介绍
多元线性回归,具有n个特征值,预测公式如下。
y ^ ( i ) = θ 0 + θ 1 x 1 ( i ) + θ 2 x 2 ( i ) + ⋯ + + θ n x n ( i ) \hat{y}^{(i)}=\theta_{0}+\theta_{1} x_{1}^{(i)}+\theta_{2} x_{2}^{(i)}+\cdots++\theta_{n} x_{n}^{(i)} y^(i)=θ0+θ1x1(i)+θ2x2(i)+⋯++θnxn(i)
多元线性回归方程演变成求 θ θ θ。
每个特征都有对应的权重系数 c o e f coef coef,特征的权重系数的正负值代表特征与目标值是正相关还是负相关,特征的权重系数的绝对值代表重要性。
sklearn中 中LinearRegression的fit()方法就是通过训练集求出 θ θ θ,LinearRegression的两个属性intercept和coef分别对应 θ 0 θ_{0} θ0和 θ 1 θ_{1} θ1~ θ n θ_{n} θn。
4.代码实现
1)普通线性模型
from sklearn import datasets
from sklearn.linear_model import LinearRegression
#获取boston数据
boston=datasets.load_boston()
x=boston.data
y=boston.target
#过滤掉异常值
x=x[y<50]
y=y[y<50]
reg=LinearRegression()
reg.fit(x,y)
#求排序后的coef
coefSort=reg.coef_.argsort()
#featureNameSort: 按对标记值的影响,从小到大的各特征值名称
#featureCoefSore:按对标记值的影响,从小到大的coef_
featureNameSort=boston.feature_names[coefSort]
featureCoefSore=reg.coef_[coefSort]
print("featureNameSort:", featureNameSort)
print("featureCoefSore:", featureCoefSore)
# 输出结果:
featureNameSort: ['NOX' 'DIS' 'PTRATIO' 'LSTAT' 'CRIM' 'INDUS' 'AGE' 'TAX' 'B' 'ZN' 'RAD'
'CHAS' 'RM']
featureCoefSore: [-1.23981083e+01 -1.21096549e+00 -8.38180086e-01 -3.50107918e-01
-1.06715912e-01 -4.38830943e-02 -2.36790549e-02 -1.37774382e-02
7.85316354e-03 3.53133180e-02 2.51301879e-01 4.52209315e-01
3.75945346e+00]
结果分析:
正相关影响系数最大的特征值是”RM”:房间的平均数量,系数值为3.75。
负相关影响系数最大的特征值是”NOX”:一氧化氮浓度,系数值为-1.23。
2)L1正则化线性模型
from sklearn import datasets
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
#A helper method for pretty-printing linear models
def pretty_print_linear(coefs, names = None, sort = False):
names = ["X%s" % x for x in range(len(coefs))]
lst = zip(coefs, names)
if sort:
lst = sorted(lst, key = lambda x:-np.abs(x[0]))
return " + ".join("%s * %s" % (round(coef, 3), name) for coef, name in lst)
boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]
lasso = Lasso(alpha=.3)
lasso.fit(X, Y)
print("Lasso model: {}".format(pretty_print_linear(lasso.coef_, names, sort = True)))
# 输出:
Lasso model: -3.707 * LSTAT + 2.992 * RM + -1.757 * PTRATIO
+ -1.081 * DIS + -0.7 * NOX + 0.631 * B + 0.54 * CHAS + -0.236 * CRIM
+ 0.081 * ZN + -0.0 * INDUS + -0.0 * AGE + 0.0 * RAD + -0.0 * TAX
许多特征具有系数0。 L 1 L1 L1正则化回归的稳定性与非正则化线性模型类似,这意味着当数据中存在相关特征时,系数(以及特征等级)即使在小数据变化时也会发生显着变化。
3)L2正则化线性模型
from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
size = 100
#We run the method 10 times with different random seeds
for i in range(10):
print("Random seed {}".format(i))
np.random.seed(seed=i)
X_seed = np.random.normal(0, 1, size)
X1 = X_seed + np.random.normal(0, .1, size)
X2 = X_seed + np.random.normal(0, .1, size)
X3 = X_seed + np.random.normal(0, .1, size)
Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
X = np.array([X1, X2, X3]).T
lr = LinearRegression()
lr.fit(X,Y)
print("Linear model: {}".format(pretty_print_linear(lr.coef_)))
ridge = Ridge(alpha=10)
ridge.fit(X,Y)
print("Ridge model: {}".format(pretty_print_linear(ridge.coef_)))
#输出结果:
Random seed 0
Linear model: 0.728 * X0 + 2.309 * X1 + -0.082 * X2
Ridge model: 0.938 * X0 + 1.059 * X1 + 0.877 * X2
Random seed 1
Linear model: 1.152 * X0 + 2.366 * X1 + -0.599 * X2
Ridge model: 0.984 * X0 + 1.068 * X1 + 0.759 * X2
Random seed 2
Linear model: 0.697 * X0 + 0.322 * X1 + 2.086 * X2
Ridge model: 0.972 * X0 + 0.943 * X1 + 1.085 * X2
Random seed 3
Linear model: 0.287 * X0 + 1.254 * X1 + 1.491 * X2
Ridge model: 0.919 * X0 + 1.005 * X1 + 1.033 * X2
Random seed 4
Linear model: 0.187 * X0 + 0.772 * X1 + 2.189 * X2
Ridge model: 0.964 * X0 + 0.982 * X1 + 1.098 * X2
Random seed 5
Linear model: -1.291 * X0 + 1.591 * X1 + 2.747 * X2
Ridge model: 0.758 * X0 + 1.011 * X1 + 1.139 * X2
Random seed 6
Linear model: 1.199 * X0 + -0.031 * X1 + 1.915 * X2
Ridge model: 1.016 * X0 + 0.89 * X1 + 1.091 * X2
Random seed 7
Linear model: 1.474 * X0 + 1.762 * X1 + -0.151 * X2
Ridge model: 1.018 * X0 + 1.039 * X1 + 0.901 * X2
Random seed 8
Linear model: 0.084 * X0 + 1.88 * X1 + 1.107 * X2
Ridge model: 0.907 * X0 + 1.071 * X1 + 1.008 * X2
Random seed 9
Linear model: 0.714 * X0 + 0.776 * X1 + 1.364 * X2
Ridge model: 0.896 * X0 + 0.903 * X1 + 0.98 * X2
从示例中可以看出,线性回归的系数变化很大,具体取决于生成的数据。然而,对于L2正则化模型,系数非常稳定并且密切反映数据的生成方式(所有系数接近1)。
随机森林具有准确率高、鲁棒性好、易于使用等优点,这使得它成为了目前最流行的机器学习算法之一。随机森林提供了两种特征选择的方法:mean decrease impurity和mean decrease accuracy。
1.平均不纯度减少(mean decrease impurity)
1)原理介绍
随机森林由多颗CART决策树构成,决策树中的每一个节点都是关于某个特征的条件,为的是将数据集按照不同的响应变量一分为二。
CART利用不纯度可以确定节点(最优条件),对于分类问题,通常采用基尼不纯度,对于回归问题,通常采用的是方差或者最小二乘拟合。
当训练决策树的时候,可以计算出每个特征减少了多少树的不纯度。对于一个决策树森林来说,可以算出每个特征平均减少了多少不纯度,并把它平均减少的不纯度作为特征选择的标准。
随机森林基于不纯度的排序结果非常鲜明,在得分最高的几个特征之后的特征,得分急剧的下降。
2)代码实现
from sklearn.datasets import load_boston
from sklearn.ensemble import RandomForestRegressor
import numpy as np
#Load boston housing dataset as an example
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
# 训练随机森林模型,并通过feature_importances_属性获取每个特征的重要性分数。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.4383, 'RM'), (0.4121, 'LSTAT'), (0.0802, 'DIS'), (0.0273, 'CRIM'), (0.0179, 'NOX'), (0.0131, 'PTRATIO'), (0.0033, 'AGE'), (0.0021, 'TAX'), (0.0017, 'RAD'), (0.0014, 'CHAS'), (0.0012, 'INDUS'), (0.0012, 'B'), (0.0002, 'ZN')]
2.平均精确度减少(mean decrease accuracy)
1)原理介绍
通过直接度量每个特征对模型精确率的影响来进行特征选择。
主要思路是打乱每个特征的特征值顺序,并且度量顺序变动对模型的精确率的影响。
2)代码实现
from sklearn.model_selection import ShuffleSplit
from sklearn.metrics import r2_score
from collections import defaultdict
import warnings
warnings.filterwarnings('ignore')
X = boston["data"]
Y = boston["target"]
rf = RandomForestRegressor()
scores = defaultdict(list)
#crossvalidate the scores on a number of different random splits of the data
rs = ShuffleSplit(n_splits=5, test_size=.25, random_state=0)
rs.get_n_splits(X)
ShuffleSplit(n_splits=5, random_state=0, test_size=0.25, train_size=None)
for train_idx, test_idx in rs.split(X):
X_train, X_test = X[train_idx], X[test_idx]
Y_train, Y_test = Y[train_idx], Y[test_idx]
# 使用修改前的原始特征训练模型,其acc作为后续混洗特征值后的对比标准。
r = rf.fit(X_train, Y_train)
acc = r2_score(Y_test, rf.predict(X_test))
# 遍历每一列特征
for i in range(X.shape[1]):
X_t = X_test.copy()
# 对这一列特征进行混洗,交互了一列特征内部的值的顺序
np.random.shuffle(X_t[:, i])
shuff_acc = r2_score(Y_test, rf.predict(X_t))
# 混洗某个特征值后,计算平均精确度减少程度。
scores[names[i]].append((acc-shuff_acc)/acc)
print("Features sorted by their score:")
print(sorted([(round(np.mean(score), 4), feat) for feat, score in scores.items()], reverse=True))
#输出结果:
Features sorted by their score:
[(0.7234, 'LSTAT'), (0.6134, 'RM'), (0.1071, 'DIS'), (0.0465, 'CRIM'), (0.0274, 'PTRATIO'), (0.0265, 'NOX'), (0.0195, 'TAX'), (0.0121, 'AGE'), (0.0063, 'B'), (0.004, 'RAD'), (0.0039, 'INDUS'), (0.0002, 'ZN'), (-0.0003, 'CHAS')]
顶层特征选择发建立在基于模型的特征选择方法基础之上的,例如线性回归和SVM等,在不同的子集上建立模型,然后汇总最终确定特征得分。
1.稳定性选择(Stability selection)
稳定性选择常常是一种既能够有助于理解数据又能够挑出优质特征的这种选择。
1)原理介绍
稳定性选择是一种基于二次抽样和选择算法相结合较新的方法,选择算法可以是回归、SVM或其他类似的方法。
它的主要思想是在不同的数据子集和特征子集上运行特征选择算法,不断的重复,最终汇总特征选择结果。比如可以统计某个特征被认为是重要特征的频率(被选为重要特征的次数除以它所在的子集被测试的次数)。
理想情况下,重要特征的得分会接近100%。稍微弱一点的特征得分会是非0的数,而最无用的特征得分将会接近于0。
2)代码实现
from sklearn.linear_model import RandomizedLasso
from sklearn.datasets import load_boston
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
rlasso = RandomizedLasso(alpha=0.025)
rlasso.fit(X, Y)
print("Features sorted by their score:")
print(sorted(zip(map(lambda x: round(x, 4), rlasso.scores_), names),reverse=True))
递归特征消除(Recursive feature elimination,RFE)
1)原理介绍
递归特征消除的主要思想是反复的构建模型(如SVM或者回归模型)然后选出最好的(或者最差的)的特征(可以根据系数来选),把选出来的特征放到一遍,然后在剩余的特征上重复这个过程,直到所有特征都遍历了。
这个过程中特征被消除的次序就是特征的排序。因此,这是一种寻找最优特征子集的贪心算法。
RFE的稳定性很大程度上取决于在迭代的时候底层用哪种模型。
假如RFE采用的普通的回归,没有经过正则化的回归是不稳定的,那么RFE就是不稳定的。
假如RFE采用的是Ridge,而用Ridge正则化的回归是稳定的,那么RFE就是稳定的。
2)代码实现
from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_boston
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
lr = LinearRegression()
rfe = RFE(lr, n_features_to_select=1)
rfe.fit(X,Y)
print("Features sorted by their rank:")
print(sorted(zip(map(lambda x: round(x, 4), rfe.ranking_), names)))
#输出结果:
Features sorted by their rank:
[(1, 'NOX'), (2, 'RM'), (3, 'CHAS'), (4, 'PTRATIO'), (5, 'DIS'), (6, 'LSTAT'), (7, 'RAD'), (8, 'CRIM'), (9, 'INDUS'), (10, 'ZN'), (11, 'TAX'), (12, 'B'), (13, 'AGE')]
去掉取值变化小的特征方法一般用在特征选择前作为一个预处理的工作,即先去掉取值变化小的特征,然后再使用其他特征选择方法选择特征。如果机器资源充足,并且希望尽量保留所有信息,可以把阈值设置得比较高,或者只过滤离散型特征只有一个取值的特征。
单变量特征选择可以用于理解数据、数据的结构、特点,也可以用于排除不相关特征,但是它不能发现冗余特征。
去掉取值变化小的特征方法和单变量特征选择方法都属于过滤式类特征筛选方法,但是学习算法无法向特征搜索算法传递对特征的需求。为了真正关注的是学习问题本身,我们将在下一章中继续介绍Wrapper方法和Embedded方法的原理与实现。
单变量特征选择可以用于理解数据、数据的结构、特点,也可以用于排除不相关特征,但是它不能发现冗余特征。
正则化的线性模型可用于特征理解和特征选择。相比起L1正则化,L2正则化的表现更加稳定,L2正则化对于数据的理解来说很合适。由于响应变量和特征之间往往是非线性关系,可以采用basis expansion的方式将特征转换到一个更加合适的空间当中,在此基础上再考虑运用简单的线性模型。
随机森林是一种非常流行的特征选择方法,它易于使用。但它有两个主要问题:
重要的特征有可能得分很低(关联特征问题)
这种方法对特征变量类别多的特征越有利(偏向问题)
特征选择在很多机器学习和数据挖掘场景中都是非常有用的。在使用的时候要弄清楚自己的目标是什么,然后找到哪种方法适用于自己的任务。
当选择最优特征以提升模型性能的时候,可以采用交叉验证的方法来验证某种方法是否比其他方法要好。
当用特征选择的方法来理解数据的时候要留心,特征选择模型的稳定性非常重要,稳定性差的模型很容易就会导致错误的结论。
对数据进行二次采样然后在子集上运行特征选择算法能够有所帮助,如果在各个子集上的结果是一致的,那就可以说在这个数据集上得出来的结论是可信的,可以用这种特征选择模型的结果来理解数据。
关于训练模型的特征筛选,个人建议的实施流程 :
数据预处理后,先排除取值变化很小的特征。如果机器资源充足,并且希望尽量保留所有信息,可以把阈值设置得比较高,或者只过滤离散型特征只有一个取值的特征。
如果数据量过大,计算资源不足(内存不足以使用所有数据进行训练、计算速度过慢),可以使用单特征选择法排除部分特征。这些被排除的特征并不一定完全被排除不再使用,在后续的特征构造时也可以作为原始特征使用。
如果此时特征量依然非常大,或者是如果特征比较稀疏时,可以使用PCA(主成分分析)和LDA(线性判别)等方法进行特征降维。
经过样本采样和特征预筛选后,训练样本可以用于训练模型。但是可能由于特征数量比较大而导致训练速度慢,或者想进一步筛选有效特征或排除无效特征(或噪音),我们可以使用正则化线性模型选择法、随机森林选择法或者顶层特征选择法进一步进行特征筛选。
最后,特征筛选是为了理解数据或更好地训练模型,我们应该根据自己的目标来选择适合的方法。为了更好/更容易地训练模型而进行的特征筛选,如果计算资源充足,应尽量避免过度筛选特征,因为特征筛选很容易丢失有用的信息。如果只是为了减少无效特征的影响,为了避免过拟合,可以选择随机森林和XGBoost等集成模型来避免对特征过拟合。
[1] Feature selection – Part I: univariate selection
[2] Selecting good features – Part II: linear models and regularization
[3] Feature selection
[4] Github