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

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

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

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

VectorSlicer

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

  • 如果是下标都可以使用setIndices方法
  • 如果是列名可以使用setNames方法。使用这个方法的时候,vector字段需要通过AttributeGroup设置每个向量元素的列名。

注意1:可以同时使用setInices和setName

object VectorSlicer {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("VectorSlicer-Test").setMaster("local[2]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    var sqlContext = new SQLContext(sc)

    val data = Array(Row(Vectors.dense(-2.0, 2.3, 0.0,1.0,2.0)))

    val defaultAttr = NumericAttribute.defaultAttr
    val attrs = Array("f1", "f2", "f3","f4","f5").map(defaultAttr.withName)
    val attrGroup = new AttributeGroup("userFeatures", attrs.asInstanceOf[Array[Attribute]])

    val dataRDD = sc.parallelize(data)
    val dataset = sqlContext.createDataFrame(dataRDD, StructType(Array(attrGroup.toStructField())))

    val slicer = new VectorSlicer().setInputCol("userFeatures").setOutputCol("features")

    slicer.setIndices(Array(0)).setNames(Array("f2"))
    val output = slicer.transform(dataset)
    println(output.select("userFeatures", "features").first())
  }
}

注意2:如果下标和索引重复,会报重复的错:

比如:

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

那么会遇到报错

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]
    at scala.Predef$.require(Predef.scala:233)
    at org.apache.spark.ml.feature.VectorSlicer.getSelectedFeatureIndices(VectorSlicer.scala:137)
    at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:108)
    at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
    at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

注意3:如果下标不存在

slicer.setIndices(Array(6))

如果数组越界也会报错

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 6
    at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3$$anonfun$apply$2.apply(VectorSlicer.scala:110)
    at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3$$anonfun$apply$2.apply(VectorSlicer.scala:110)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
    at scala.collection.mutable.ArrayOps$ofInt.foreach(ArrayOps.scala:156)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
    at scala.collection.mutable.ArrayOps$ofInt.map(ArrayOps.scala:156)
    at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3.apply(VectorSlicer.scala:110)
    at org.apache.spark.ml.feature.VectorSlicer$$anonfun$3.apply(VectorSlicer.scala:109)
    at scala.Option.map(Option.scala:145)
    at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:109)
    at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
    at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

注意4:如果名称不存在也会报错

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).
    at scala.Predef$.require(Predef.scala:233)
    at org.apache.spark.ml.util.MetadataUtils$$anonfun$getFeatureIndicesFromNames$2.apply(MetadataUtils.scala:89)
    at org.apache.spark.ml.util.MetadataUtils$$anonfun$getFeatureIndicesFromNames$2.apply(MetadataUtils.scala:88)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.TraversableLike$$anonfun$map$1.apply(TraversableLike.scala:244)
    at scala.collection.IndexedSeqOptimized$class.foreach(IndexedSeqOptimized.scala:33)
    at scala.collection.mutable.ArrayOps$ofRef.foreach(ArrayOps.scala:108)
    at scala.collection.TraversableLike$class.map(TraversableLike.scala:244)
    at scala.collection.mutable.ArrayOps$ofRef.map(ArrayOps.scala:108)
    at org.apache.spark.ml.util.MetadataUtils$.getFeatureIndicesFromNames(MetadataUtils.scala:88)
    at org.apache.spark.ml.feature.VectorSlicer.getSelectedFeatureIndices(VectorSlicer.scala:129)
    at org.apache.spark.ml.feature.VectorSlicer.transform(VectorSlicer.scala:108)
    at xingoo.mllib.VectorSlicer$.main(VectorSlicer.scala:35)
    at xingoo.mllib.VectorSlicer.main(VectorSlicer.scala)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

注意5:经过特征选择后,特征的顺序与索引和名称的顺序相同

RFormula

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

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

StringIndex...OneHotEncoder...

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

id coutry hour clicked
7 US 18 1.0
8 CA 12 0.0
9 NZ 15 0.0

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

比如下面的代码:

object RFormulaTest {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("RFormula-Test").setMaster("local[2]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    var sqlContext = new SQLContext(sc)

    val dataset = sqlContext.createDataFrame(Seq(
      (7, "US", 18, 1.0,"a"),
      (8, "CA", 12, 0.0,"b"),
      (9, "NZ", 15, 0.0,"a")
    )).toDF("id", "country", "hour", "clicked","my_test")
    val formula = new RFormula()
      .setFormula("clicked ~ country + hour + my_test")
      .setFeaturesCol("features")
      .setLabelCol("label")
    val output = formula.fit(dataset).transform(dataset)
    output.show()
    output.select("features", "label").show()
  }
}

得到的结果

+---+-------+----+-------+-------+------------------+-----+
| id|country|hour|clicked|my_test|          features|label|
+---+-------+----+-------+-------+------------------+-----+
|  7|     US|  18|    1.0|      a|[0.0,0.0,18.0,1.0]|  1.0|
|  8|     CA|  12|    0.0|      b|[1.0,0.0,12.0,0.0]|  0.0|
|  9|     NZ|  15|    0.0|      a|[0.0,1.0,15.0,1.0]|  0.0|
+---+-------+----+-------+-------+------------------+-----+

+------------------+-----+
|          features|label|
+------------------+-----+
|[0.0,0.0,18.0,1.0]|  1.0|
|[1.0,0.0,12.0,0.0]|  0.0|
|[0.0,1.0,15.0,1.0]|  0.0|
+------------------+-----+

ChiSqSelector

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

代码也很简单

object ChiSqSelectorTest {
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("ChiSqSelector-Test").setMaster("local[2]")
    val sc = new SparkContext(conf)
    sc.setLogLevel("WARN")
    var sqlContext = new SQLContext(sc)

    val data = Seq(
      (7, Vectors.dense(0.0, 0.0, 18.0, 1.0), 1.0),
      (8, Vectors.dense(0.0, 1.0, 12.0, 0.0), 0.0),
      (9, Vectors.dense(1.0, 0.0, 15.0, 0.1), 0.0)
    )

    val beanRDD = sc.parallelize(data).map(t3 => Bean(t3._1,t3._2,t3._3))
    val df = sqlContext.createDataFrame(beanRDD)

    val selector = new ChiSqSelector()
      .setNumTopFeatures(2)
      .setFeaturesCol("features")
      .setLabelCol("clicked")
      .setOutputCol("selectedFeatures")

    val result = selector.fit(df).transform(df)
    result.show()
  }

  case class Bean(id:Double,features:org.apache.spark.mllib.linalg.Vector,clicked:Double){}
}

这样得到的结果:

+---+------------------+-------+----------------+
| id|          features|clicked|selectedFeatures|
+---+------------------+-------+----------------+
|7.0|[0.0,0.0,18.0,1.0]|    1.0|      [18.0,1.0]|
|8.0|[0.0,1.0,12.0,0.0]|    0.0|      [12.0,0.0]|
|9.0|[1.0,0.0,15.0,0.1]|    0.0|      [15.0,0.1]|
+---+------------------+-------+----------------+

总结

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

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

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

参考

Spark特征处理

Spark官方文档

如何优化逻辑回归

数据挖掘中的VI和WOE

Spark卡方选择器

卡方分布

皮尔逊卡方检验

卡方检验原理

本文转自博客园xingoo的博客,原文链接:推荐系统那点事 —— 基于Spark MLlib的特征选择,如需转载请自行联系原博主。

时间: 2024-10-25 20:53:16

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

Spark MLlib中的协同过滤

本文主要通过Spark官方的例子理解ALS协同过滤算法的原理和编码过程,然后通过对电影进行推荐来熟悉一个完整的推荐过程. 协同过滤 协同过滤常被应用于推荐系统,旨在补充用户-商品关联矩阵中所缺失的部分.MLlib当前支持基于模型的协同过滤,其中用户和商品通过一小组隐语义因子进行表达,并且这些因子也用于预测缺失的元素.Spark MLlib实现了交替最小二乘法(ALS) 来学习这些隐性语义因子. 在 MLlib 中的实现类为org.apache.spark.mllib.recommendation

协同过滤算法 R/mapreduce/spark mllib多语言实现

用户电影评分数据集下载 http://grouplens.org/datasets/movielens/ 1) Item-Based,非个性化的,每个人看到的都一样 2) User-Based,个性化的,每个人看到的不一样 对用户的行为分析得到用户的喜好后,可以根据用户的喜好计算相似用户和物品,然后可以基于相似用户或物品进行推荐.这就是协同过滤中的两个分支了,基于用户的和基于物品的协同过滤. 在计算用户之间的相似度时,是将一个用户对所有物品的偏好作为一个向量,而在计算物品之间的相似度时,是将所有

Spark MLlib - Decision Tree源码分析

以决策树作为开始,因为简单,而且也比较容易用到,当前的boosting或random forest也是常以其为基础的 决策树算法本身参考之前的blog,其实就是贪婪算法,每次切分使得数据变得最为有序   那么如何来定义有序或无序? 无序,node impurity  对于分类问题,我们可以用熵entropy或Gini来表示信息的无序程度  对于回归问题,我们用方差Variance来表示无序程度,方差越大,说明数据间差异越大 information gain 用于表示,由父节点划分后得到子节点,所

如何基于Spark Streaming构建实时计算平台

1.前言 随着互联网技术的迅速发展,用户对于数据处理的时效性.准确性与稳定性要求越来越高,如何构建一个稳定易用并提供齐备的监控与预警功能的实时计算平台也成了很多公司一个很大的挑战. 自2015年携程实时计算平台搭建以来,经过两年多不断的技术演进,目前实时集群规模已达上百台,平台涵盖各个SBU与公共部门数百个实时应用,全年JStorm集群稳定性达到100%.目前实时平台主要基于JStorm与Spark Streaming构建而成,相信关注携程实时平台的朋友在去年已经看到一篇关于携程实时平台的分享:

《Spark MLlib 机器学习实战》1——读后总结

1 概念 2 安装 3 RDD RDD包含两种基本的类型:Transformation和Action.RDD的执行是延迟执行,只有Action算子才会触发任务的执行. 宽依赖和窄依赖用于切分任务,如果都是窄依赖,那么就可以最大化的利用并行. 常用操作: cache 缓存 cartesian 笛卡尔积 coalesce 重分区 countByValue 分组统计 distinct 去除重复 filter 过滤 flatMap map groupBy 分组 keyBy 增加key reduce 拼接

Spark MLlib中的OneHot哑变量实践

在机器学习中,线性回归和逻辑回归算是最基础入门的算法,很多书籍都把他们作为第一个入门算法进行介绍.除了本身的公式之外,逻辑回归和线性回归还有一些必须要了解的内容.一个很常用的知识点就是虚拟变量(也叫做哑变量)-- 用于表示一些无法直接应用到线性公式中的变量(特征). 举个例子: 通过身高来预测体重,可以简单的通过一个线性公式来表示,y=ax+b.其中x为身高,y为体重. 现在想要多加一些特征(参数),比如性别. 那么问题来了:如何在一个公式中表示性别呢? 这就是哑变量的作用,它可以通过扩展特征值

推荐系统那点事 —— 什么是用户画像?

用户画像在大数据分析中是一种很有用的系统,它可以各种不同的系统中,起到很关键的作用.比如搜索引擎.推荐系统.内容系统等等,可以帮助应用实现千人千面.个性化.精准等的效果. 下面将从几个方面来说一下,什么是用户画像,主要的内容来自<用户网络行为画像分析与内容推荐应用>这本书. 应用场景 数据来源 特性 建模 群体画像 画像的存储 画像的查询 画像的更新 图片来自京东--想要购买可以点这里跳转 应用场景 下面举几个很典型的场景: 搜索引擎 在搜索的时候考虑用户的画像标签,返回用户感兴趣的内容.比如

Apache Spark源码走读(十一)浅谈mllib中线性回归的算法实现&amp;Spark MLLib中拟牛顿法L-BFGS的源码实现

<一>浅谈mllib中线性回归的算法实现 概要 本文简要描述线性回归算法在Spark MLLib中的具体实现,涉及线性回归算法本身及线性回归并行处理的理论基础,然后对代码实现部分进行走读. 线性回归模型 机器学习算法是的主要目的是找到最能够对数据做出合理解释的模型,这个模型是假设函数,一步步的推导基本遵循这样的思路 假设函数 为了找到最好的假设函数,需要找到合理的评估标准,一般来说使用损失函数来做为评估标准 根据损失函数推出目标函数 现在问题转换成为如何找到目标函数的最优解,也就是目标函数的最

基于Spark on Yarn 的淘宝数据挖掘平台

基于Spark on Yarn 的淘宝数据挖掘平台 淘宝技术部--数据挖掘与计算 为什么选择Spark On Yarn Spark On Yarn的原理和框架 淘宝在Spark On Yarn上做的工作 基于Spark on Yarn 的淘宝数据挖掘平台