当前位置:   article > 正文

推荐系统那点事 —— 基于Spark MLlib的特征选择

ml lib的特征选择方法

推荐系统那点事 —— 基于Spark MLlib的特征选择

在机器学习中,一般都会按照下面几个步骤:特征提取、数据预处理、特征选择、模型训练、检验优化。那么特征的选择就很关键了,一般模型最后效果的好坏往往都是跟特征的选择有关系的,因为模型本身的参数并没有太多优化的点,反而特征这边有时候多加一个或者少加一个,最终的结果都会差别很大。

在SparkMLlib中为我们提供了几种特征选择的方法,分别是VectorSlicerRFormulaChiSqSelector

下面就介绍下这三个方法的使用,强烈推荐有时间的把参考的文献都阅读下,会有所收获!

VectorSlicer

这个转换器可以支持用户自定义选择列,可以基于下标索引,也可以基于列名。

  • 如果是下标都可以使用setIndices方法
  • 如果是列名可以使用setNames方法。使用这个方法的时候,vector字段需要通过AttributeGroup设置每个向量元素的列名。
注意1:可以同时使用setInices和setName
  1. object VectorSlicer {
  2. def main(args: Array[String]) {
  3. val conf = new SparkConf().setAppName("VectorSlicer-Test").setMaster("local[2]")
  4. val sc = new SparkContext(conf)
  5. sc.setLogLevel("WARN")
  6. var sqlContext = new SQLContext(sc)
  7. val data = Array(Row(Vectors.dense(-2.0, 2.3, 0.0,1.0,2.0)))
  8. val defaultAttr = NumericAttribute.defaultAttr
  9. val attrs = Array("f1", "f2", "f3","f4","f5").map(defaultAttr.withName)
  10. val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])
  11. val dataRDD = sc.parallelize(data)
  12. val dataset = sqlContext.createDataFrame(dataRDD, StructType(Array(attrGroup.toStructField())))
  13. val slicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features")
  14. slicer.setIndices(Array(0)).setNames(Array("f2"))
  15. val output = slicer.transform(dataset)
  16. println(output.select("userFeatures", "features").first())
  17. }
  18. }
注意2:如果下标和索引重复,会报重复的错:

比如:

slicer.setIndices(Array(1)).setNames(Array("f2"))

那么会遇到报错

  1. Exception in thread "main" java.lang.IllegalArgumentException: requirement failed: VectorSlicer requires indices and names to be disjoint sets of features, but they overlap. indices: [1]. names: [1:f2]
  2. at scala.Predef$.require(Predef.scala:233)
  3. at org.apache.spark.ml.feature.VectorSlicer.getSelectedFeatureIndices(VectorSlicer.scala:137)
  4. at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:108)
  5. at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
  6. at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
  7. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  8. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  9. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  10. at java.lang.reflect.Method.invoke(Method.java:497)
  11. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
注意3:如果下标不存在
slicer.setIndices(Array(6))

如果数组越界也会报错

  1. Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6
  2. at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3$$anonfun$apply$2.apply(VectorSlicer.scala:110)
  3. at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3$$anonfun$apply$2.apply(VectorSlicer.scala:110)
  4. at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
  5. at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
  6. at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  7. at scala.collection.mutable.ArrayOps$ofInt.foreach(ArrayOps.scala:156)
  8. at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
  9. at scala.collection.mutable.ArrayOps$ofInt.map(ArrayOps.scala:156)
  10. at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3.apply(VectorSlicer.scala:110)
  11. at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3.apply(VectorSlicer.scala:109)
  12. at scala.Option.map(Option.scala:145)
  13. at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:109)
  14. at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
  15. at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
  16. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  17. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  18. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  19. at java.lang.reflect.Method.invoke(Method.java:497)
  20. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
注意4:如果名称不存在也会报错
  1. Exception in thread "main" java.lang.IllegalArgumentException: requirement failed: getFeatureIndicesFromNames found no feature with name f8 in column StructField(userFeatures,org.apache.spark.mllib.linalg.VectorUDT@f71b0bce,false).
  2. at scala.Predef$.require(Predef.scala:233)
  3. at org.apache.spark.ml.util.MetadataUtils$$anonfun$getFeatureIndicesFromNames$2.apply(MetadataUtils.scala:89)
  4. at org.apache.spark.ml.util.MetadataUtils$$anonfun$getFeatureIndicesFromNames$2.apply(MetadataUtils.scala:88)
  5. at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
  6. at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
  7. at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
  8. at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:108)
  9. at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
  10. at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:108)
  11. at org.apache.spark.ml.util.MetadataUtils$.getFeatureIndicesFromNames(MetadataUtils.scala:88)
  12. at org.apache.spark.ml.feature.VectorSlicer.getSelectedFeatureIndices(VectorSlicer.scala:129)
  13. at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:108)
  14. at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
  15. at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
  16. at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  17. at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  18. at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  19. at java.lang.reflect.Method.invoke(Method.java:497)
  20. at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
注意5:经过特征选择后,特征的顺序与索引和名称的顺序相同

RFormula

这个转换器可以帮助基于R模型,自动生成feature和label。比如说最常用的线性回归,在先用回归中,我们需要把一些离散化的变量变成哑变量,即转变成onehot编码,使之数值化,这个我之前的文章也介绍过,这里就不多说了。

如果不是用这个RFormula,我们可能需要经过几个步骤:

StringIndex...OneHotEncoder...

而且每个特征都要经过这样的变换,非常繁琐。有了RFormula,几乎可以一键把所有的特征问题解决。

idcoutryhourclicked
7US181.0
8CA120.0
9NZ150.0

然后我们只要写一个类似这样的公式clicked ~ country + hour + my_test,就代表clickedlabelcoutry、hour、my_test是三个特征

比如下面的代码:

  1. object RFormulaTest {
  2. def main(args: Array[String]) {
  3. val conf = new SparkConf().setAppName("RFormula-Test").setMaster("local[2]")
  4. val sc = new SparkContext(conf)
  5. sc.setLogLevel("WARN")
  6. var sqlContext = new SQLContext(sc)
  7. val dataset = sqlContext.createDataFrame(Seq(
  8. (7, "US", 18, 1.0,"a"),
  9. (8, "CA", 12, 0.0,"b"),
  10. (9, "NZ", 15, 0.0,"a")
  11. )).toDF("id", "country", "hour", "clicked","my_test")
  12. val formula = new RFormula()
  13. .setFormula("clicked ~ country + hour + my_test")
  14. .setFeaturesCol("features")
  15. .setLabelCol("label")
  16. val output = formula.fit(dataset).transform(dataset)
  17. output.show()
  18. output.select("features", "label").show()
  19. }
  20. }

得到的结果

  1. +---+-------+----+-------+-------+------------------+-----+
  2. | id|country|hour|clicked|my_test| features|label|
  3. +---+-------+----+-------+-------+------------------+-----+
  4. | 7| US| 18| 1.0| a|[0.0,0.0,18.0,1.0]| 1.0|
  5. | 8| CA| 12| 0.0| b|[1.0,0.0,12.0,0.0]| 0.0|
  6. | 9| NZ| 15| 0.0| a|[0.0,1.0,15.0,1.0]| 0.0|
  7. +---+-------+----+-------+-------+------------------+-----+
  8. +------------------+-----+
  9. | features|label|
  10. +------------------+-----+
  11. |[0.0,0.0,18.0,1.0]| 1.0|
  12. |[1.0,0.0,12.0,0.0]| 0.0|
  13. |[0.0,1.0,15.0,1.0]| 0.0|
  14. +------------------+-----+

ChiSqSelector

这个选择器支持基于卡方检验的特征选择,卡方检验是一种计算变量独立性的检验手段。具体的可以参考维基百科,最终的结论就是卡方的值越大,就是我们越想要的特征。因此这个选择器就可以理解为,再计算卡方的值,最后按照这个值排序,选择我们想要的个数的特征。

代码也很简单

  1. object ChiSqSelectorTest {
  2. def main(args: Array[String]) {
  3. val conf = new SparkConf().setAppName("ChiSqSelector-Test").setMaster("local[2]")
  4. val sc = new SparkContext(conf)
  5. sc.setLogLevel("WARN")
  6. var sqlContext = new SQLContext(sc)
  7. val data = Seq(
  8. (7, Vectors.dense(0.0, 0.0, 18.0, 1.0), 1.0),
  9. (8, Vectors.dense(0.0, 1.0, 12.0, 0.0), 0.0),
  10. (9, Vectors.dense(1.0, 0.0, 15.0, 0.1), 0.0)
  11. )
  12. val beanRDD = sc.parallelize(data).map(t3 => Bean(t3._1,t3._2,t3._3))
  13. val df = sqlContext.createDataFrame(beanRDD)
  14. val selector = new ChiSqSelector()
  15. .setNumTopFeatures(2)
  16. .setFeaturesCol("features")
  17. .setLabelCol("clicked")
  18. .setOutputCol("selectedFeatures")
  19. val result = selector.fit(df).transform(df)
  20. result.show()
  21. }
  22. case class Bean(id:Double,features:org.apache.spark.mllib.linalg.Vector,clicked:Double){}
  23. }

这样得到的结果:

  1. +---+------------------+-------+----------------+
  2. | id| features|clicked|selectedFeatures|
  3. +---+------------------+-------+----------------+
  4. |7.0|[0.0,0.0,18.0,1.0]| 1.0| [18.0,1.0]|
  5. |8.0|[0.0,1.0,12.0,0.0]| 0.0| [12.0,0.0]|
  6. |9.0|[1.0,0.0,15.0,0.1]| 0.0| [15.0,0.1]|
  7. +---+------------------+-------+----------------+

总结

下面总结一下三种特征选择的使用场景:

  • VectorSilcer,这个选择器适合那种有很多特征,并且明确知道自己想要哪个特征的情况。比如你有一个很全的用户画像系统,每个人有成百上千个特征,但是你指向抽取用户对电影感兴趣相关的特征,因此只要手动选择一下就可以了。
  • RFormula,这个选择器适合在需要做OneHotEncoder的时候,可以一个简单的代码把所有的离散特征转化成数值化表示。
  • ChiSqSelector,卡方检验选择器适合在你有比较多的特征,但是不知道这些特征哪个有用,哪个没用,想要通过某种方式帮助你快速筛选特征,那么这个方法很适合。

以上的总结纯属个人看法,不代表官方做法,如果有其他的见解可以留言~ 多交流!

参考

1 Spark特征处理

2 Spark官方文档

3 如何优化逻辑回归

4 数据挖掘中的VI和WOE

5 Spark卡方选择器

6 卡方分布

7 皮尔逊卡方检验

8 卡方检验原理

posted @ 2017-07-11 22:24 xingoo 阅读( ...) 评论( ...) 编辑 收藏
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/寸_铁/article/detail/997182
推荐阅读
相关标签
  

闽ICP备14008679号