如有错误,恳请指出。
以下内容整理自专栏:博主“文火冰糖的硅基工坊”的专栏——机器学习与scikit-learn,对部分的文章的简化与整理。
1. 常见的降维方法简介
1.1 按缺失比率删除特征(Missing Value Ratio)
在数据集中,如果缺失值少,我们可以填补缺失值或直接删除这个变量;如果缺失值在数据集中的占比过高时,一般可以考虑选择直接删除这个变量,因为它包含的信息太少了。也可以设置一个阈值,如果缺失值占比高于阈值,删除它所在的列。 缺失值统计函数是:df.isnull().sum()
1.2 低方差滤波(Low Variance Filter)
如果某一特征,对所有样本,其取值都差不多,这说明,所有的样本,并不是通过该特征来标识差别的,这样的特征,其实对于区分不同样本,意义不大,就可以去除掉这些特征。 这个特性可以在数学上可以用低方差来描述。放到实践中,就是先计算所有特征的方差大小,然后删去其中最小的几个。不过需要注意,方差与数据范围相关的,因此在采用该方法前需要对数据做归一化处理,这样不同特征的方差之间才具备可比性。方差函数是:numeric.var()
1.3 高相关性滤波(High Correlation filter)
如果两个特征之间是高度相关的,这意味着它们具有相似的趋势并且可能携带类似的信息,这类变量的存在会降低某些模型的性能(例如线性和逻辑回归模型)。为了解决这个问题,我们可以计算独立数值变量之间的相关性。如果相关系数超过某个阈值,就删除其中一个特征列。 相关系数计算:df.corr()
1.4 随机森林(Random Forest)
随机森林是一种广泛使用的分类算法,它会自动计算各个特征的重要性,具备天然的特征选择的能力。这里可以先调用sklearn工具包使用随机森林对数据进行处理后,选择出对随机深林决策算法影响Top N的几个特征,去除掉对决策深林做决策无影响或影响较小的特征。这是对算法的另外使用。例子:
features = df.columns
importances = model.feature_importances_
indices = np.argsort(importances[0:9])
plt.title('Feature Importances')
plt.barh(range(len(indices)), importances[indices], color='b', align='center')
plt.yticks(range(len(indices)), [features[i] for i in indices])
plt.xlabel('Relative Importance')
plt.show()
1.5 反向特征消除(Backward Feature Elimination)
反向特征消除的基本思想是:尝试性删除某些特征,然后对模型进行训练,再看删除后该特征后,对模型性能的影响,选择删除对模型影响小的特征。
反向特征消除的主要步骤: 先获取数据集中的全部n个变量,然后用它们训练一个模型。 计算模型的性能。 删除每个变量(n次)后计算模型的性能,即我们每次都去掉一个变量,用剩余的n-1个变量训练模型。 确定对模型性能影响最小的变量,把它删除。 重复此过程,直到不再能删除任何变量。
反向之外,还有正向的特征选择,不过感觉没有反向的好,这里就不介绍了。
1.6 因子分析FA(Factor Analysis)
因子分析是一种常见的统计方法,它能从多个变量(特征)中提取共性因子,并得到最优解。假设我们有两个变量:收入和教育。它们可能是高度相关的,因为总体来看,学历高的人一般收入也更高,反之亦然。所以它们可能存在一个潜在的共性因子,比如“能力”。 在因子分析中,我们将变量按其相关性分组,即特定组内所有变量的相关性较高,组间变量的相关性较低。我们把每个组称为一个因子,它是多个变量的组合。和原始数据集的变量相比,这些因子在数量上更少,但携带的信息基本一致。这种分析方法,就是人类的分类方法。进一步可以采用分层判决的方法。 所以因子分析会把所有的特征按照树形的方式进行组织,具备相同类型的特征归属于同一个分类。这样把偏平的特征构建成了一个分层的、结构化的特征。在进行分类时,可以为不同层次的特征,指定不同的权重,优先在第层次的特征之间进行区分和比较,然后再高一层次的特征上进行进一步的比较。
1.7 主成分分析(PCA)
PCA是通过正交变换将原始的n维数据集变换到一个新的被称做主成分的数据集中,即从现有的大量变量中提取一组新的变量。本质上是一种矩阵运算,提取到一组线性无关的特征向量上实现降维。 主成分分析实际上是一种浓缩数据信息的方法,可将很多个指标浓缩成综合指标(主成分),并保证这些综合指标彼此之间互不相关。 另外的一种理解是,PCA是在一堆的数据中,最大的投影方向(一个向量)的一组就是主成分。而想要降维到D维,就找到前D个主成分即可。
1.8 独立分量分析(ICA)
独立分量分析(ICA)基于信息理论,是最广泛使用的降维技术之一。PCA和ICA之间的主要区别在于,PCA寻找不相关的因素,而ICA寻找独立因素。基本的ICA是指从多个源信号的线性混合信号中分离出源信号的技术。应用在特征的降维上,就是要分离多个不相干的特征信号(数据),并把相干数据合并在一起。
1.9 人工过滤法
这是最基本的一种方法,就是人为的主观分析这个特征是否对判断的结果有帮助。比如考试成绩的高低与性别与学号是没有关系的,再比如泰坦尼克号中能否生存与票号与等仓口也是关系不大的。只要知道特征的含义,就可以人为通过经验进行主观判断。
2. 方差过滤法
方差是在概率论和统计方差衡量随机变量或一组数据时离散程度的度量,统计中的方差(样本方差)是每个样本值与全体样本值的平均数之差的平方值的平均数。方差越小,反应的是不同样本之间该特征值的差别越小,越说明该特征值用于区分不同样本的能力越小。这样的特征数据,对于模型区分不同目标的用处就越小。
一个特征本身的方差很小,就表示样本在这个特征上基本没有差异,可能特征中的大多数值都一样,甚至整个特征的取值都相同,那这个特征对于样本区分没有什么作用。所以无论接下来的特征工程要做什么,都要优先消除方差为0的特征, 然后优先过滤掉方差小的某个门限的特征。
from sklearn.feature_selection import VarianceThreshold
import numpy as np
variance = X.var().values
threshold_min = np.min(variance)
threshold_mid = np.median(variance)
threshold_max = np.max(variance)
print("variance.shape", variance.shape)
print("threshold_min", threshold_min)
print("threshold_mid", threshold_mid)
selector_zero = VarianceThreshold()
selector_min = VarianceThreshold(threshold_min)
selector_mid = VarianceThreshold(threshold_mid)
X_fsvar_zero = selector_zero.fit_transform(X)
X_fsvar_min = selector_min.fit_transform(X)
X_fsvar_mid = selector_mid.fit_transform(X)
print("\n过滤前的特征维度:", X.shape)
print("X_fsvar_zero.shape", X_fsvar_zero.shape)
print("X_fsvar_min.shape", X_fsvar_min.shape)
print("X_fsvar_mid.shape", X_fsvar_mid.shape)
from sklearn.feature_selection import VarianceThreshold
np.random.seed(42)
id = np.random.randint(0,10,5)
nums = [0,1,2,3,4,5,6,7,8,9]
X = []
for i in range(5):
nums_temp = nums.copy()
nums_temp[id[i]] = i
X.append(nums_temp)
selector = VarianceThreshold()
selector.fit_transform(X)
3. 卡方过滤
方差过滤只考虑了不同样本的同一个特征值之间的互异性(即数学上的方差),如果方差小说明该特征对于不同样本没有啥差异,没有差异性的特征对区分不同的样本属于次要矛盾的次要因素,因此可以忽略。但是,对于方差打的特征也不一定与标签一定有关,所以还需要样本特征与样本标签之间有没有相关性,如果样本特征与样本标签的相关性不大,对于这样的特征不管方差多大都可以过滤掉。
在sklearn当中,我们有三种常用的方法来评判特征与标签之间的相关性:卡方,F检验,互信息
-
卡方过滤的基本原理 卡方检验,统计学的方法。卡方检验是英文Chi-Square Test 的谐音。在大数据运营场合,它用于检测某个自变量(即特征)值是不是和因变量有显著关系。Y=F(X1, X2, X3…Xn),对于自变量与因变量关系不大的自变量(即特征),就可以直接过滤掉。这就是卡方过滤的基本思想。 -
卡方过滤的基本步骤 1)选择合适的分类算法:这里选择随机森林,它的运行时间与样本特征的个数无关,不影响执行效率。 2)准确率分数评估:这里选择交叉验证评估方法,进行多次交叉验证,最后选择平均分作为样本标签过滤前后统计分数的工具。 3)卡方过滤前:使用方差过滤后的数据集进行分类算法和交叉评估 4)卡方过滤:方差过滤后的数据集的基础上进行样本特征的二次过滤 。 5)卡方过滤后:使用卡方过滤后的数据集进行分类算法和交叉评估
3.1 selectKbest + chi2实现卡方过滤
- 1. 随机森林 + 方差特征过滤后数据集 + 交叉评估
from sklearn.datasets import load_digits
from sklearn.feature_selection import VarianceThreshold
X, y = load_digits(return_X_y=True)
X.shape, y.shape
selector = VarianceThreshold()
X = selector.fit_transform(X)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf = RandomForestClassifier()
scores = cross_val_score(clf, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
select_kbest = SelectKBest(chi2, k=30)
X = select_kbest.fit_transform(X, y)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf2 = RandomForestClassifier()
scores = cross_val_score(clf2, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
这里可以看见,使用卡方过滤后剩下的30个特征进行分类,效果是降低的,原因是丢失了某些重要的特征信息。一个改进的办法是按照一定的范围依次查看效果,然后再挑选范围内最高的降维数值来实现自动评估。
from sklearn.datasets import load_digits
X, y = load_digits(return_X_y=True)
X.shape, y.shape
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf0 = RandomForestClassifier()
scores = cross_val_score(clf0, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
from sklearn.feature_selection import VarianceThreshold
selector = VarianceThreshold()
X = selector.fit_transform(X)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf1 = RandomForestClassifier()
scores = cross_val_score(clf1, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
select_kbest = SelectKBest(chi2, k=30)
X = select_kbest.fit_transform(X, y)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
clf2 = RandomForestClassifier()
scores = cross_val_score(clf2, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
可以看见,使用适当的降维方法是有助与模型训练的,比如会用方差降维后的效果提升了一点;但是如果降维破坏了特征的信息,就会对模型训练有害,从61维直接降维到了30维,模型的训练效果下降的1%。
3.2 chi2独自过滤
from sklearn.datasets import load_digits
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import chi2
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
X, y = load_digits(return_X_y=True)
selector = VarianceThreshold()
X = selector.fit_transform(X)
chivalue, pvalues_chi = chi2(X, y)
print("chivalue.shape:" , chivalue.shape)
print("pvalues_chi.shape:" , pvalues_chi.shape)
k = chivalue.shape[0] - (pvalues_chi > 0.01).sum()
X = SelectKBest(chi2, k=k).fit_transform(X, y)
clf = RandomForestRegressor(n_estimators=100, random_state=42)
scores = cross_val_score(clf, X, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
ps:这里可以看见选择第一种方法比较好,效果也比较好,所以这一种方法不看也可以。
4. F过滤与互信息过滤
卡方检验能够筛选算特征变量与标签变量之间的相关性,卡方过滤用于过滤掉那些与标签标签无关的特征变量。除了卡方检验,类似的还有F检验和互信息量检验。
F检验要求输入数据服从正态分布,互信息量用于检查特征与标签之间不确定性的大小,即信息量的大小。从代码的角度来看,卡方检验、F检验、互信息量检验的过滤过程是相似的。从目的来看,卡方检验、F检验、互信息量检验也是一致的,过滤掉与标签无关的特征。
4.1 F过滤
from sklearn.datasets import load_digits
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import chi2, f_classif
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
X, y = load_digits(return_X_y=True)
Fvalue, pvalues_f = f_classif(X,y)
print("Fvalue.shape", Fvalue.shape)
print("pvalues_f.shape", pvalues_f.shape)
pvalue_filter = 0.01
k_num = Fvalue.shape[0] - (pvalues_f > pvalue_filter).sum()
print("F过滤后的特征数:", k_num)
X_select = SelectKBest(f_classif, k=k_num).fit_transform(X, y)
clf = RandomForestRegressor(n_estimators=100, random_state=42)
scores = cross_val_score(clf, X_select, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
from sklearn.datasets import load_digits
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import chi2, f_classif
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
X, y = load_digits(return_X_y=True)
Fvalue, pvalues_f = f_classif(X,y)
print("Fvalue.shape", Fvalue.shape)
print("pvalues_f.shape", pvalues_f.shape)
pvalue_filter = 0.
k_num = Fvalue.shape[0] - (pvalues_f > pvalue_filter).sum()
print("F过滤后的特征数:", k_num)
X_select = SelectKBest(f_classif, k=k_num).fit_transform(X, y)
clf = RandomForestRegressor(n_estimators=100, random_state=42)
scores = cross_val_score(clf, X_select, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
4.2 互信息过滤
from sklearn.datasets import load_digits
from sklearn.feature_selection import VarianceThreshold
from sklearn.feature_selection import chi2, f_classif, mutual_info_classif
from sklearn.feature_selection import SelectKBest
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
X, y = load_digits(return_X_y=True)
result = mutual_info_classif(X,y)
print("result.shape", result.shape)
pvalue_filter = 0.
k_num = result.shape[0] - (result > pvalue_filter).sum()
print("F过滤后的特征数:", k_num)
X_select = SelectKBest(mutual_info_classif, k=k_num).fit_transform(X, y)
clf = RandomForestRegressor(n_estimators=100, random_state=42)
scores = cross_val_score(clf, X_select, y, cv=5)
scores_mean = scores.mean()
print("scores=", scores)
print("scores_mean=", scores_mean)
ps:这里过滤后互信息为0的特征只有3个,所以说明只有3个特这是与标签无关的,而这个特点在卡方与F过滤中得到的结果是一致的。这再一次说明,卡方检验、F检验、互信息量检验的过滤过程是相似的,卡方检验、F检验、互信息量检验也是一致的,甚至最后的效果也是类似的。
5. 嵌入法
- 嵌入法的简介:
过滤样本的特征,有两种基本的阶段,一是在算法之前先过滤特征,然后进行算法训练,另一种阶段就是,有些算法提供了一边进行算法训练,一边进行特征选择的方法,这种方法称为嵌入式特征过滤法。其基本思想是在训练过程中检测哪些特征对训练的影响较大(梯度较大),然后忽略梯度较小的特征。
嵌入法是一种让算法自己决定选择或使用哪些特征的方法,即特征选择和算法训练同时进行。在使用嵌入法时,我们先使用某些机器学习的算法和模型进行训练,得到各个特征的权值系数,根据权值系数从大到小选择特征。这些权值系数往往代表了特征对于模型的某种贡献或某种重要性,比如决策树和树的集成模型中的feature_importances_属性,可以列出各个特征对树的建立的贡献,我们就可以基于这种贡献的评估,找出对模型建立最有用(影响权重加大)的特征。因此相比于过滤法,嵌入法的结果会更加精确到模型的效用本身,对于提高模型效力有更好的效果。
- 嵌入法的使用条件:
SelectFromModel是一个元变换器,可以与任何在拟合后具有coef_,feature_importances_属性或参数中可选惩罚项的评估器一起使用(比如随机森林和树模型就具有属feature_importances_,逻辑回归就带有l1和l2惩罚项,线性支持向量机也支持l2惩罚项)。对于有feature_importances_的模型来说,若重要性低于提供的阈值参数,则认为这些特征不重要并被移除。feature_importances_的取值范围是[0,1],如果设置阈值很小,比如0.001,就可以删除那些对标签预测完全没贡献的特征。如果设置得很接近1,可能只有一两个特征能够被留下。
SelectFromModel是根据在算法运算过程中,生成的feature_importances_值来进行特征选择的,如果一个算法无法提供feature_importances_,则不能使用SelectFromModel来选择特征,无法通过该方法对特征进行降维。
- 使用示例:
from sklearn.feature_selection import SelectFromModel
from sklearn.ensemble import RandomForestClassifier as RFC
RFC_ = RFC(n_estimators =10,random_state=0)
print("原始样本的形状X.shape:", X.shape)
RFC_After = RFC_.fit(X,y)
feature_importances = RFC_After.feature_importances_
print("不同特征权重的形状", feature_importances.shape)
print("特征权重最大值:", feature_importances.max())
data = np.flipud(np.sort(feature_importances))
print("前五个特征的权重", data[0:5])
thres_hold = 0.005
RFC_embeded = SelectFromModel(RFC_, threshold=thres_hold)
X_embedded = RFC_embeded.fit_transform(X,y)
print("嵌入式过滤后的样本:", X_embedded.shape)
ps:手工设置的阈值一般具有不确定性,可能过滤掉大部分的特征,所以这时候有的阈值肯定不是最优的
X_embedded = SelectFromModel(RFC_,threshold=0.005).fit_transform(X,y)
print("过滤后的特征", X_embedded.shape)
score=cross_val_score(RFC_,X_embedded,y,cv=5).mean()
print("过滤后的交叉验证分数",score)
ps:当前的效果分数不是很好,那么需要查看一个比较好的阈值范围,以设定更好的阈值
- 3.通过学习曲线确定最优thres_hold的范围
import numpy as np
import matplotlib.pyplot as plt
RFC_.fit(X,y).feature_importances_
threshold = np.linspace(0,(RFC_.fit(X,y).feature_importances_).max(),20)
score = []
for i in threshold:
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score.append(once)
plt.plot(threshold,score)
plt.show()
ps:当确定了一个最高的范围之后,就可以进一步的确定这个门限,在最高范围内再设置一段间隔更小的阈值来查看效果
threshold_up = 0.001
threshold_num = 60
thresholds_list = np.linspace(0,threshold_up, threshold_num)
threshold_best = 0
score_list = []
score_best = 0
for i in thresholds_list:
X_embedded = SelectFromModel(RFC_,threshold=i).fit_transform(X,y)
once = cross_val_score(RFC_,X_embedded,y,cv=5).mean()
score_list.append(once)
if(once > thresholds_best):
thresholds_best = i
score_best = once
plt.figure(figsize=[threshold_num,5])
plt.plot(thresholds_list ,score_list)
plt.xticks(np.linspace(0,threshold_up, threshold_num))
plt.show()
print("过滤后的特征", X_embedded.shape)
print("Best threshold:", thresholds_best)
print("Best core:", score_best)
ps:通过进一步细致的设置特征来挑选最好的阈值
X_embedded = SelectFromModel(RFC_,threshold=0.000564).fit_transform(X,y)
X_embedded.shape
cross_val_score(RFC_,X_embedded,y,cv=5).mean()
ps:一般这时候获得的分数是要比之前的分数高的,因为这是挑选到最好的一个阈值,既筛选了无用的特征,也提高了效果,提高了效率
cross_val_score(RFC(n_estimators=100,random_state=0),X_embedded,y,cv=5).mean()
ps:通过模型调参一般可以再进一步的获取效果的提升,此时还可以使用一些类似nas的方法来确定模型训练的最好参数
参考资料:
- 博主“文火冰糖的硅基工坊”的专栏——机器学习与scikit-learn
|