特征选择
坊间传言:数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已。因此特征工程显得尤为重要,本文主要介绍特征选择方面工作,后续将会有特征预处理方面的。
peason特征选择
Pearson相关系数(Pearson CorrelationCoefficient)是用来衡量两个数据集合是否在一条线上面,它用来衡量定距变量间的线性关系。同样,同样也可以一定程度上衡量特征与Label间的线性相关性,从而进行特征选择。
缺点:对于分类问题,label是离散变量,而pearson假设条件要求变量是连续的。因此,此种特征选择方法为下下策。
List<Column> cols=new ArrayList<Column>();
for(String featurename:featurenames){
cols.add(functions.corr("label", featurename).alias(featurename));
}
Dataset<Row> featruecorr = feature.
agg(functions.first("label"), (scala.collection.Seq)scala.collection.JavaConversions.asScalaBuffer(cols));
Row[] tempvalueRows = (Row[]) featruecorr.collect();
String tempfeaturenames[] = featruecorr.columns();
ArrayList<String> tempArrayList = new ArrayList<String>();
ArrayList<String> tempcorrList = new ArrayList<String>();
for(int i = 1; i < tempvalueRows[0].length(); i++){
if (tempvalueRows[0].get(i) instanceof Double){
if(Math.abs((Double)tempvalueRows[0].get(i)) > threshhold){
tempArrayList.add(tempfeaturenames[i]);
tempcorrList.add(String.valueOf(tempvalueRows[0].get(i)));
}
}
}
String[] newfeaturenames = new String[tempArrayList.size()];
newfeaturenames = (String[])tempArrayList.toArray(newfeaturenames);
System.out.println("newfeaturenames: ");
for(int i =0;i < newfeaturenames.length;i++){
System.out.println(String.valueOf(newfeaturenames[i]) + " " + tempcorrList.get(i));
}
System.out.println("rawfeaturenames num is " + featurenames.length);
System.out.println("newfeaturenames num is " + newfeaturenames.length);
卡方特征选择
卡方检验最基本的思想就是通过观察实际值与理论值的偏差来确定理论的正确与否。具体做的时候常常先假设两个变量确实是独立的(行话就叫做“原假设”),然后观察实际值(也可以叫做观察值)与理论值(这个理论值是指“如果两者确实独立”的情况下应该有的值)的偏差程度,如果偏差足够小,我们就认为误差是很自然的样本误差,是测量手段不够精确导致或者偶然发生的,两者确确实实是独立的,此时就接受原假设;如果偏差大到一定程度,使得这样的误差不太可能是偶然产生或者测量不精确所致,我们就认为两者实际上是相关的,即否定原假设,而接受备择假设。
卡方特征选择来源于统计学中的卡方检验,用来衡量两个变量是否一致。将其用于特征选择中,可根据特征与Label分布相似性来选择特征。
public static String[] feautureSelectionByChiSq(Dataset<Row> feature, Double threshholdfpr,Integer featurenum, String[] featurenames){
VectorAssembler va = new VectorAssembler()
.setInputCols(featurenames).setOutputCol("features");
feature = va.transform(feature);
Dataset<Row> newfeature = feature.select("userid", "features", "label");
feature.unpersist();
// scaler
StandardScaler scaler = new StandardScaler()
.setInputCol("features")
.setOutputCol("scaledFeatures")
.setWithStd(true)
.setWithMean(false);
Dataset<Row> scalerfeature = scaler.fit(newfeature).transform(newfeature);
//
ChiSqSelector selector = null;
if(featurenum < 0){
selector = new ChiSqSelector()
.setFpr(threshholdfpr)
// .setPercentile(threshholdfpr)
.setFeaturesCol("scaledFeatures")
.setLabelCol("label")
.setOutputCol("selectedFeatures");
}else{
selector = new ChiSqSelector()
.setNumTopFeatures(featurenum)
.setFeaturesCol("scaledFeatures")
.setLabelCol("label")
.setOutputCol("selectedFeatures");
}
ChiSqSelectorModel selectorModel = selector.fit(scalerfeature);
ArrayList<String> tempList = new ArrayList<String>();
System.out.println(selectorModel.selectedFeatures());
for(int i = 0; i < selectorModel.selectedFeatures().length; i++ ){
tempList.add(featurenames[i]);
}
System.out.println(selectorModel.getFpr());
String[] newfeaturenames = new String[tempList.size()];
newfeaturenames = (String[])tempList.toArray(newfeaturenames);
return newfeaturenames;
}
随机森林特征选择
随机森林特征选择是一种基于模型的方法,对特征重要性进行排名,从而选择对label影响较大的特征。
1)对每一颗决策树,选择相应的袋外数据(out of bag,OOB)计算袋外数据误差,记为errOOB1.
所谓袋外数据是指,每次建立决策树时,通过重复抽样得到一个数据用于训练决策树,这时还有大约1/3的数据没有被利用,没有参与决策树的建立。这部分数据可以用于对决策树的性能进行评估,计算模型的预测错误率,称为袋外数据误差。
这已经经过证明是无偏估计的,所以在随机森林算法中不需要再进行交叉验证或者单独的测试集来获取测试集误差的无偏估计。
2)随机对袋外数据OOB所有样本的特征X加入噪声干扰(可以随机改变样本在特征X处的值),再次计算袋外数据误差,记为errOOB2。
3)假设森林中有N棵树,则特征X的重要性=∑(errOOB2-errOOB1)/N。这个数值之所以能够说明特征的重要性是因为,如果加入随机噪声后,袋外数据准确率大幅度下降(即errOOB2上升),说明这个特征对于样本的预测结果有很大影响,进而说明重要程度比较高。
VectorAssembler va = new VectorAssembler()
.setInputCols(featurenames).setOutputCol("features");
feature = va.transform(feature);
Dataset<Row> newfeature = feature.select("userid", "features", "label");
feature.unpersist();
// scaler
StandardScaler scaler = new StandardScaler()
.setInputCol("features")
.setOutputCol("scaledFeatures")
.setWithStd(true)
.setWithMean(false);
Dataset<Row> scalerfeature = scaler.fit(newfeature).transform(newfeature);
//
RandomForestClassifier rf = new RandomForestClassifier()
.setLabelCol("label")
.setFeaturesCol("scaledFeatures");
RandomForestClassificationModel model = rf.fit(scalerfeature);
HashMap<String, Double> featurenameMap = new HashMap<String, Double>();
ArrayList<String> tempList = new ArrayList<String>();
for(int i =0; i < featurenames.length; i++){
featurenameMap.put(featurenames[i], model.featureImportances().toArray()[i]);
if(model.featureImportances().toArray()[i] > threshhold){
tempList.add(featurenames[i]);
}
}
String[] newfeaturenames = new String[tempList.size()];
newfeaturenames = (String[])tempList.toArray(newfeaturenames);
以上三种方法是笔者在spark里常用的特征选择方法,当然还有更多的方法可以尝试,如互信息法,递归特征消除法(耗时较长,不建议)等。