本套技术专栏是作者(秦凯新)平时工作的总结和升华,通过从真实商业环境抽取案例进行总结和分享,并给出商业应用的调优建议和集群环境容量规划等内容,请持续关注本套博客。版权声明:禁止转载,欢迎学习。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