1. 导入隐式转换
import spark.implicits._
2. 读取 / 存储 mongodb 数据并转换为对象 df (不 as 转换也是 DataFrame 对象,但一般会习惯转换一下在进行操作)
case class Rating(val uid: Int, val mid: Int, val score: Double, val timestamp: Int) val ratingsDF = spark.read .option("uri", mongoConfig.uri) .option("collection", MONGO_RATINGS_COLLECTION) .format("com.mongodb.spark.sql") .load() .as[Rating] .toDF() count.write .option("uri", mongoConfig.uri) .option("collection", MONGO_MOVIE_SCORE_COUNT) .format("com.mongodb.spark.sql") .mode("overwrite") .save()
3. 将 DataFrame 转换为 sql 表进行操作, 如果例如有时间格式化等功能需要加入 sql 语句中,需要 注册一个 UDF 函数 来操作
//统计以月为单位每个电影的评分数 val simpleDateFormat = new SimpleDateFormat("yyyyMM") //注册一个UDF函数, 用于将timestamp转换成年月形式 spark.udf.register("changeDate", (x:Int) => simpleDateFormat.format(new Date(x*1000L)).toInt) val mouthCountDF = spark.sql("select mid, count(mid) count, changeDate(timestamp) as yearmouth from ratings group by yearmouth,mid order by count desc")
4. 将 2 个 RDD 通过某个字段进行 join
//统计每种电影类别中评分最高的10个电影 val movieWithScore = movieDF.join(avgMoviesDF, Seq("mid", "mid"))
5. 将 list 转化为 RDD
val genres = List("Action", "Adventure", "Animation", "Comedy", "Ccrime", "Documentary", "Drama", "Family", "Fantasy", "Foreign", "History", "Horror", "Music", "Mystery", "Romance", "Science", "Tv", "Thriller", "War", "Western") val genresRDD = spark.sparkContext.makeRDD(genres)
6. 过滤 某个 RDD 中包含另一个 RDD 的数据
genresRDD.cartesian(movieWithScore.rdd) .filter{case (genres, row) => row.getAs[String]("genres").contains(genres) }
7. 如果一个RDD 格式为 RDD[(String, Iterable[(Integer, Double)])], 这样的格式,需要对 rdd 中的迭代器中的某个字段进行排序, 例如 Double 这个字段, 并且只取前 10 条记录
.map{case (genres, item) => (GenresRecommendation(genres, item.toList.sortWith(_._2 > _._2).take(10).map(x => Recommendation(x._1, x._2)))) }
8. 推荐算法 用户相似度推荐算法 (根据用户对各个电影的评分,计算出用户除了评分电影外,还有可能对那些电影感兴趣,包含计算的字段就是 用户,电影,评分)
//1.首先需要做一个 ALS训练模型 model,那么久需要创建一个训练集, 训练集 为 Rating(org.apache.spark.mllib.recommendation.Rating) 对象, 字段为 3个,如下,: x._1, x._2, x._3
x._1 为 uid 用户id, x._2 为 mid 电影 id, x._3 为评分 这样的三个字段, 所以需要先将 ratingsRDD 转化成这3个字段的rdd,
val trainData = ratingsRDD.map(x => Rating(x._1, x._2, x._3))
//2.设置训练参数
val (rank, iterations, lambda) = (50, 10, 0.01)
//3.创建训练 ALS模型, 也就是后面要进行相似度计算的时候要以这个model为原模型进行匹配计算
val model = ALS.train(trainData, rank, iterations, lambda)
//4.要使用这个 model 进行计算,还需要有一个格式为 RDD[(Int, Int)] 这样一个待计算的 Rdd,因为 model 模型创建的时候是 uid, mid, score, 所以这个RDD 第一个 Int 应该是uid, 第二个RDD 是 mid
所以要先获得这样一个 RDD, 首先获得一个 RDD[Int] 格式的 userRDD, 然后获得一个 RDD[Int] 格式的 movieRDD, 再做 笛卡尔积 就可以获得 RDD[(Int, Int)]
val userMovies = userRDD.cartesian(movieRDD)
//5.使用 predict 方法计算相似度
val preRatings = model.predict(userMovies)
//转换成我们希望的推荐对象矩阵 (计算出的 preRatings 是一个Rating对象, 通过如下字段就能拿到我们想要的数据, 格式为 RDD[(Int, (Int, Double))],也就是 RDD[(uid, (mid, score))]),然后再通过
需求转换成我们想要的格式活对象
val userRecommender = preRatings.map(rating => (rating.user, (rating.product, rating.rating)))
.groupByKey()
.map {
case (uid, recs) => UserRecommender(uid, recs.toList.sortWith(_._2 > _._2).take(20).map(x => Recommenderation(x._1, x._2)))
}.toDF()
9. 电影相似度推荐算法 (电影相似度是 电影矩阵中 uid, mid, score, 用户对很多电影进行过评分,根据用户对电影的喜好计算出那些电影比较相似, 所以还是使用上面的 model 来进行计算)
//1. model.productFeatures 将会获得一个 RDD[(Int, Array[Double])] 的 RDD, 需要将 Array[Double] 转化为 DoubleMatrix
val movieFeatures = model.productFeatures.map { case (mid, freatures) => (mid, new DoubleMatrix(freatures)) }
//电影相识度矩阵计算
val movieRecommender = movieFeatures.cartesian(movieFeatures)
.filter { case (a, b) => a._1 != b._1 }
.map {
case (a, b) =>
val simSocore = this.consinSim(a._2, b._2)
(a._1, (b._1, simSocore))
}.filter(_._2._2 > 0.6)
.groupByKey()
.map {
case (mid, recs) => (MovieRecommender(mid, recs.toList.sortWith(_._2 > _._2).map(x => Recommenderation(x._1, x._2))))
}.toDF()
def consinSim(movie1: DoubleMatrix, movie2: DoubleMatrix): Double = {
movie1.dot(movie2) / (movie1.norm2() * movie2.norm2())
}