Spark ML自定义选择最优模型算法深入剖析-Spark商业ML实战

版权声明:本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。QQ邮箱地址:[email protected],如有任何商业交流,可随时联系。 https://blog.csdn.net/shenshouniu/article/details/84202899

本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。QQ邮箱地址:[email protected],如有任何商业交流,可随时联系。

1 自定义选择最优模型

什么叫做自定义模型?其实就是不借助Spark官方支持的交叉验证和训练验证拆分,而是根据实际场景进行自定义的RMSE等指标进行综合分析。奉上试验美图:

2 协同过滤(Collaborative Filtering)

  • 显式的用户反馈:这类是用户在网站上自然浏览或者使用网站以外,显式地提供反馈信息,例如用户对物品的评分或者对物品的评论。

  • 隐式的用户反馈:这类是用户在使用网站是产生的数据,隐式地反映了用户对物品的喜好,例如用户购买了某物品,用户查看了某物品的信息,等等

  • 显式的用户反馈能准确地反映用户对物品的真实喜好,但需要用户付出额外的代价;

  • 而隐式的用户行为,通过一些分析和处理,也能反映用户的喜好,只是数据不是很精确,有些行为的分析存在较大的噪音。但只要选择正确的行为特征,隐式的用户反馈也能得到很好的效果,只是行为特征的选择可能在不同的应用中有很大的不同,

  • Spark ML目前支持基于协同过滤的模型,在这个模型里,用户和产品被一组可以用来预测缺失项目的潜在因子来描述。ML 实现了交替最小二乘(ALS)算法来学习这些潜在的因子,在 ML 中的实现有如下参数:

      numBlocks 是用于并行化计算的用户和商品的分块个数 (默认为10)。
      rank 是模型中隐语义因子的个数(默认为10)。
      maxIter 是迭代的次数(默认为10)。
      regParam 是ALS的正则化参数(默认为1.0)。
      implicitPrefs 决定了是用显性反馈ALS的版本还是用适用隐性反馈数据集的版本(默认是false,即用显性反馈)。
      alpha 是一个针对于隐性反馈 ALS 版本的参数,这个参数决定了偏好行为强度的基准(默认为1.0)。
      nonnegative 决定是否对最小二乘法使用非负的限制(默认为false)。
    

3 经典协同过滤数据集

  • 在MovieLens提供的电影评分数据分为三个表:评分、用户信息和电影信息,在该系列提供的附属数据提供大概6000位读者和100万个评分数据。

  • 评分数据说明(ratings.data)

      该评分数据总共四个字段,格式为UserID::MovieID::Rating::Timestamp,
      分为为用户编号::电影编号::评分::评分时间戳,
      其中各个字段说明如下:
          用户编号范围1~6040
          电影编号1~3952
          电影评分为五星评分,范围0~5
          评分时间戳单位秒
          每个用户至少有20个电影评分
    
          1::1193::5::978300760
          1::661::3::978302109
          1::914::3::978301968
    
  • 2.用户信息(users.dat)

      用户信息五个字段,格式为UserID::Gender::Age::Occupation::Zip-code,
      分为为用户编号::性别::年龄::职业::邮编。
      其中各个字段说明如下:
      
      用户编号范围1~6040
      性别,其中M为男性,F为女性
      不同的数字代表不同的年龄范围,如:25代表25~34岁范围
      职业信息,在测试数据中提供了21中职业分类
      地区邮编
      
      1::F::1::10::48067
      2::M::56::16::70072
      3::M::25::15::55117
    
  • 3 电影信息(movies.dat)

      电影数据分为三个字段,格式为MovieID::Title::Genres,分为
      电影编号::电影名::电影类别
      其中各个字段说明如下:
      
      电影编号1~3952
      由IMDB提供电影名称,其中包括电影上映年份
      电影分类,这里使用实际分类名非编号,如:Action、Crime等
      
      1::Toy Story (1995)::Animation|Children's|Comedy
      2::Jumanji (1995)::Adventure|Children's|Fantasy
      3::Grumpier Old Men (1995)::Comedy|Romance
    

4 自定义模型

import org.apache.spark.sql.Row
import org.apache.spark.ml.recommendation.{ALS,ALSModel}
import org.apache.spark.ml.evaluation.RegressionEvaluator
import org.apache.spark.sql.Dataset
import org.apache.spark.sql.DataFrame
import org.apache.spark.ml.evaluation.RegressionEvaluator
import spark.implicits._

case class Rating(userId: Int, movieId: Int, rating: Float, timestamp: Long)

1 根据数据结构定义数据规范
def parseRating(str: String): Rating = {     
val fields = str.split("::")   
assert(fields.size == 4)  
Rating(fields(0).toInt, fields(1).toInt, fields(2).toFloat, fields(3).toLong) }
 
val ratings = spark.sparkContext.textFile("/data/mllib/als/sample_movielens_ratings.txt").map(parseRating).toDF()
ratings.show()

+------+-------+------+----------+
|userId|movieId|rating| timestamp|
+------+-------+------+----------+
|     0|      2|   3.0|1424380312|
|     0|      3|   1.0|1424380312|
|     0|      5|   2.0|1424380312|
|     0|      9|   4.0|1424380312|

2 分割数据集
val splits = ratings.randomSplit(Array(0.6, 0.2, 0.2),12)
val training =splits(0).cache()
val validation=splits(1).toDF.cache()
val test =splits(2).toDF.cache()


3 训练不同参数下的模型,并集中验证,获取最佳参数下的模型
val numValidation =validation.count
val numTraining =training.count
val numTest =test.count

val ranks = List(8, 12,13)
val lambdas = List(0.1, 10.0, 12.0)
val numIters = List(10, 20, 20)
var bestModel: Option[ALSModel] = None
var bestValidationRmse = Double.MaxValue
var bestRank = 0
var bestLambda = -1.0
var bestNumIter = -1

4  校验集预测数据和实际数据之间的均方根误差算子(参看上一篇指标定义)

   模型预测结果
    +------+-------+------+----------+-----------+                                  
    |userId|movieId|rating| timestamp| prediction|
    +------+-------+------+----------+-----------+
    |    13|     31|   1.0|1424380312|    2.35308|
    |     0|     31|   1.0|1424380312|  2.5408225|
    |    18|     31|   1.0|1424380312|  1.3848196|
    |     4|     85|   1.0|1424380312|  2.4104187|
    |     8|     85|   5.0|1424380312|  3.9386258|
    |    23|     85|   1.0|1424380312| -0.7795656|
    |    29|     85|   1.0|1424380312|0.118287265|
    |    28|     65|   1.0|1424380312|  4.6700068|

  def computeRmse(model: ALSModel, data: DataFrame, n: Long): Double = {
    val predictions = model.transform(data)
    
    //计算过程:((userId,movieId),(rating,prediction)) ====> (rating,prediction)
    val predictionsAndRatings = predictions.rdd.map{x => ((x(0), x(1)),x(2))}
      .join(predictions.rdd.map(x => ((x(0), x(1)), x(4))))
      .values
    math.sqrt(predictionsAndRatings.map(x => (x._1.toString.toDouble - x._2.toString.toDouble) * (x._1.toString.toDouble - x._2.toString.toDouble)).reduce(_ + _) / n)
  }
  
 5  计算最优模型的遍历测试
  
  for (rank <- ranks; lambda <- lambdas; numIter <- numIters) {
  
  val als = new ALS().setMaxIter(numIter).setRegParam(lambda).setRank(rank).setUserCol("userId"). setItemCol("movieId").setRatingCol("rating")
  val model = als.fit(training)
  
  
  模型作用于验证集进行验证
  val validationRmse = computeRmse(model, validation, numValidation)
  println("RMSE (模型评估) = " + validationRmse + " 参数 rank = "
    + rank + ", lambda = " + lambda + ", and numIter = " + numIter + ".")
    
  if (validationRmse < bestValidationRmse) {
    bestModel = Some(model)
    bestValidationRmse = validationRmse
    bestRank = rank
    bestLambda = lambda
    bestNumIter = numIter
  }
}

RMSE (validation) = 1.0747445332055616 for the model trained with rank = 8, lambda = 0.1, and numIter = 10.
RMSE (validation) = 1.045271064998892 for the model trained with rank = 8, lambda = 0.1, and numIter = 20.
RMSE (validation) = 2.041241452319315 for the model trained with rank = 8, lambda = 10.0, and numIter = 10.
RMSE (validation) = 2.041241452319315 for the model trained with rank = 8, lambda = 10.0, and numIter = 20.
RMSE (validation) = 1.0213510038051121 for the model trained with rank = 12, lambda = 0.1, and numIter = 10.
RMSE (validation) = 1.005770421453116 for the model trained with rank = 12, lambda = 0.1, and numIter = 20.
RMSE (validation) = 2.041241452319315 for the model trained with rank = 12, lambda = 10.0, and numIter = 10.
RMSE (validation) = 2.041241452319315 for the model trained with rank = 12, lambda = 10.0, and numIter = 20.



6  最优模型作用于测试集

val testRmse = computeRmse(bestModel.get, test, numTest)
println("最优模型参数: rank = " + bestRank + " and lambda = " + bestLambda  + ", and numIter = " + bestNumIter + ", 最优模型均方根误差为 " + testRmse + ".")
 
最优模型参数: rank = 12 and lambda = 0.1, and numIter = 20, 最优模型均方根误差为 0.9519301678208573.

5 饭后甜点(基于RegressionEvaluator评估器)

使用ALS来建立推荐模型,这里我们构建了两个模型,一个是显性反馈,一个是隐性反馈

1 构建模型
val Array(training, test) = ratings.randomSplit(Array(0.8, 0.2))

显性反馈 
val alsExplicit = new ALS().setMaxIter(5).setRegParam(0.01).setUserCol("userId"). setItemCol("movieId").setRatingCol("rating")

隐性反馈
val alsImplicit = new ALS().setMaxIter(5).setRegParam(0.01).setImplicitPrefs(true). setUserCol("userId").setItemCol("movieId").setRatingCol("rating")

val modelExplicit = alsExplicit.fit(training)
val modelImplicit = alsImplicit.fit(training)

2 模型预测
val predictionsExplicit = modelExplicit.transform(test)
predictionsExplicit.show()

------+-------+------+----------+----------+                                   
|userId|movieId|rating| timestamp|prediction|
+------+-------+------+----------+----------+
|    29|     31|   1.0|1424380312| 1.6074163|
|     0|     31|   1.0|1424380312| 1.7223389|
|    28|     85|   1.0|1424380312|  4.469816|
|    26|     85|   1.0|1424380312| 1.0373509|
|    15|     85|   1.0|1424380312|  5.004366|
|    23|     85|   1.0|1424380312|-1.0499454|
|     5|     65|   2.0|1424380312| 0.6295809|


val predictionsImplicit = modelImplicit.transform(test)
predictionsImplicit.show()

+------+-------+------+----------+------------+                                 
|userId|movieId|rating| timestamp|  prediction|
+------+-------+------+----------+------------+
|    29|     31|   1.0|1424380312|  0.24691844|
|     0|     31|   1.0|1424380312|  0.18451405|
|    28|     85|   1.0|1424380312|   0.6521389|
|    26|     85|   1.0|1424380312|   0.9124242|
|    15|     85|   1.0|1424380312|   0.6542819|
|    23|     85|   1.0|1424380312|  0.34113467|

3 模型评估
scala>val evaluator = new RegressionEvaluator().setMetricName("rmse").setLabelCol("rating"). setPredictionCol("prediction")
evaluator: org.apache.spark.ml.evaluation.RegressionEvaluator = regEval_bfdf233c6bad

scala>val rmseExplicit = evaluator.evaluate(predictionsExplicit)
rmseExplicit: Double = 1.6652229504120535                                       

scala>val rmseImplicit = evaluator.evaluate(predictionsImplicit)
rmseImplicit: Double = 1.7925024428799021 

结语

花了大量时间实现了自定义模型评估算法,不管对你有没有用,我觉得通过实例和现场试验,我终于强化了我的知识体系。辛苦成文,各自珍惜,谢谢!奉上美图。

秦凯新 于深圳 201811181817

猜你喜欢

转载自blog.csdn.net/shenshouniu/article/details/84202899