使用ML的ALS的推荐系统

时间:2016-02-29 03:28:46

标签: scala apache-spark dataframe apache-spark-sql

我做了一些研究,据我所知,有两种方法可以使用Apache Spark创建推荐系统,一种方法是使用随附的MLLib一个非常好的example,我尝试过并且很容易,另一方面,你可以使用ML中的ALS。我觉得与RDD合作非常舒服,但我正在尝试更频繁地使用DataFrames以获得更多经验。

为了练习,我开始使用一些标准化ratings的疯狂数据,并且我有超过4000条记录,只有5种可能的产品(如下所示)。所以我的第一个挑战是如何将此DataFrame转换为所需的结构;几个小时后我读到源代码时猜到的结构。

val df = sqlContext.createDataFrame(sc.parallelize(List(Row(0.0, 0.12, 0.1, 0.0, 0.16),
                                                        Row(0.1, 0.0, 0.3, 0.52, 0.67))),
                                    StructType(StructField("product1", DoubleType, true) ::
                                               StructField("product2", DoubleType, true) ::
                                               StructField("product3", DoubleType, true) ::
                                               StructField("product4", DoubleType, true) ::
                                               StructField("product5", DoubleType, true) :: Nil))

df.show

+--------+--------+--------+--------+--------+
|product1|product2|product3|product4|product5|
+--------+--------+--------+--------+--------+
|     0.0|    0.12|     0.1|     0.0|    0.16|
|     0.1|     0.0|     0.3|    0.52|    0.67|
+--------+--------+--------+--------+--------+

我做了几次,不知何故复杂的转换,我想看看是否有更好的方法来获得所需的结构。

val rdd = df.rdd.zipWithIndex.map {
    case (row, index) => row.toSeq.zipWithIndex.map(x => Row(index.toInt, x._2.toInt, x._1)) 
}.flatMap{x => x}

val (train, testing) = rdd.partitionBy(_.get(2) != 0.0)
val rdds = List(train, testing)

然后我将这些RDD转换为DataFrame s。

val dfs = rdds.map(sqlContext.createDataFrame(_, StructType(StructField("user", IntegerType, true) ::
                                                            StructField("product", IntegerType, true) ::
                                                            StructField("rating", DoubleType, true) :: Nil)))

在完成所有这些步骤后,我终于可以使用ALS算法,当事情如此冗长时,可能是因为你做错了。

val rec = (new ALS().setUserCol("user")
                    .setItemCol("product")
                    .setRatingCol("rating")
                    .setPredictionCol("value")
                    .setSeed(17)
                    .setMaxIter(20))

val model = rec.fit(dfs(0))

model.transform(dfs(1)).collect
Array([0,0,0.0,0.022231804], [1,1,0.0,0.102589644], [0,3,0.0,0.11560536])

1 个答案:

答案 0 :(得分:1)

一些评论:

  • userratinguserColratingCol的默认参数。如果您将product重命名为item,也可以省略此项。
  • 您可以将Rank替换为Rating并稍后省略架构:

    case (row, u) => 
       row.toSeq.zipWithIndex.map{ case (r: Double, i: Int) => Rating(u, i, r) }
    
    ...
    .toDF
    
  • 由于id似乎无关紧要,您可以使用zipWithUniqueId
  • 如果uniqueId可以接受,您可以将monotonically_increasing_idDataFrame
  • 一起使用
  • 可以通过将数据包装为一个爆炸:

    来避免将数据传递给RDD
    val exprs = explode(array(df.columns.map(c => 
      struct(lit(c).alias("item"), col(c).alias("rating"))): _*
    ))
    
    df
      .withColumn("user", monotonically_increasing_id)
      .withColumn("tmp", exprs)
      .select($"user", $"tmp.item", $"tmp.rating")
    

    并用ids替换名称。

尽管如此,我相信在这里使用DataFrames并没有多大好处。将这种或另一种数据传递回MLlib模型,该模型需要RDD[Rating]