预排序输入上的Spark特征向量转换

时间:2016-01-26 18:13:54

标签: apache-spark apache-spark-mllib naivebayes

我在HDFS上以制表符分隔的文件中有一些数据,如下所示:

label | user_id | feature
------------------------------
  pos | 111     | www.abc.com
  pos | 111     | www.xyz.com
  pos | 111     | Firefox
  pos | 222     | www.example.com
  pos | 222     | www.xyz.com
  pos | 222     | IE
  neg | 333     | www.jkl.com
  neg | 333     | www.xyz.com
  neg | 333     | Chrome

我需要对其进行转换,为每个user_id创建一个特征向量,以训练org.apache.spark.ml.classification.NaiveBayes模型。

我目前的做法主要是以下几点:

  1. 将原始数据加载到DataFrame
  2. 使用StringIndexer索引功能
  3. 通过user_id向下转到RDD和Group,并将要素索引映射到稀疏Vector。
  4. 踢球者就是这个...... 数据已经按user_id预先排序。什么是利用它的最佳方式?我很难想到可能会发生多少不必要的工作。

    如果有一些代码有助于理解我当前的方法,这就是地图的本质:

    val featurization = (vals: (String,Iterable[Row])) => {
      // create a Seq of all the feature indices
      // Note: the indexing was done in a previous step not shown
      val seq = vals._2.map(x => (x.getDouble(1).toInt,1.0D)).toSeq
    
      // create the sparse vector
      val featureVector = Vectors.sparse(maxIndex, seq)
    
      // convert the string label into a Double
      val label = if (vals._2.head.getString(2) == "pos") 1.0 else 0.0
    
      (label, vals._1, featureVector)
    }
    
    d.rdd
      .groupBy(_.getString(1))
      .map(featurization)
      .toDF("label","user_id","features")
    

1 个答案:

答案 0 :(得分:1)

让我们从your other question

开始
  

如果我的磁盘上的数据保证按照用于组聚合或缩减的密钥进行预排序,那么Spark有什么方法可以利用它?

这取决于。如果您应用的操作可以从映射端聚合中受益,那么您可以通过预分类数据获得相当多的收益而无需在代码中进一步干预。共享相同密钥的数据应位于相同的分区上,并且可以在随机播放之前在本地聚合。

不幸的是,在这种特殊情况下它并没有多大帮助。即使你启用了地图边聚合(groupBy(Key)没有使用,所以你需要自定义实现)或聚合特征向量(你会在我对{{的答案中找到一些例子) 3}})没有太大的收获。你可以在这里和那里保存一些工作,但你仍然需要在节点之间传输所有索引。

如果你想获得更多,你将不得不做更多的工作。我可以看到两种可以利用现有订单的基本方法:

  1. 使用自定义Hadoop输入格式仅生成完整记录(标签,ID,所有功能),而不是逐行读取数据。如果您的数据每个ID都有固定的行数,您甚至可以尝试使用NLineInputFormat并应用mapPartitions来汇总记录。

    这绝对是更详细的解决方案,但Spark中不需要额外的改组。

  2. 照常读取数据,但使用groupBy的自定义分区程序。据我所知,使用rangePartitioner应该可以正常工作但确保您可以尝试以下程序:

    • 使用mapPartitionsWithIndex查找每个分区的最小/最大ID。
    • 创建保持最小值的分区器< = ids<当前( i-th )分区的最大值并将最大值推送到分区 i + 1
    • 将此分区程序用于groupBy(Key)

    这可能是更友好的解决方案,但至少需要一些改组。如果要移动的预期记录数量很少(<<#records-per-partition),您甚至可以使用mapPartitionsbroadcast *来处理此问题,尽管分区可能更有用且更便宜实践。

  3. *您可以使用与此类似的方法:How to define a custom aggregation function to sum a column of Vectors?