电影推荐系统-整体总结(四)离线推荐
一、Scala代码实现
1.自定义数据类--Model.scala
package offlineRecommender /** * @Author : ASUS and xinrong * @Version : 2020/9/4 * 数据格式转换类 * ---------------电影表------------------------ * 1 * Toy Story (1995) * * 81 minutes * March 20, 2001 * 1995 * English * Adventure|Animation|Children|Comedy|Fantasy * Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn|John Ratzenberger|Annie Potts|John Morris|Erik von Detten|Laurie Metcalf|R. Lee Ermey|Sarah Freeman|Penn Jillette|Tom Hanks|Tim Allen|Don Rickles|Jim Varney|Wallace Shawn * John Lasseter */ case class Movie(val mid:Int,val name:String,val descri:String, val timelong:String,val cal_issue:String,val shoot:String, val language:String,val genres :String,val actors:String,val directors:String) /** * -----用户对电影的评分数据集-------- * 1,31,2.5,1260759144 */ case class MovieRating(val uid:Int,val mid:Int,val score:Double,val timestamp:Int) /** * --------用户对电影的标签数据集-------- * 15,339,sandra 'boring' bullock,1138537770 */ case class Tag(val uid:Int,val mid:Int,val tag:String,val timestamp:Int) /** * * MongoDB配置对象 * @param uri * @param db */ case class MongoConfig(val uri:String,val db:String) /** * ES配置对象 * @param httpHosts * @param transportHosts:保存的是所有ES节点的信息 * @param clusterName */ case class EsConfig(val httpHosts:String,val transportHosts:String,val index:String,val clusterName:String) /** * recs的二次封装数据类 * @param mid * @param res */ case class Recommendation(mid:Int,res:Double) /** * Key-Value封装数据类 * @param genres * @param recs */ case class GenresRecommendation(genres:String,recs:Seq[Recommendation]) //注:Seq-Sequence是一个特质,可以理解成一个列表;Recommendation是一个实现类 /** * 基于用户的推荐-用户相似度 * @param uid * @param recs */ case class UserRecs(uid:Int,recs:Seq[Recommendation]) /** * 定义电影相似度 * @param mid * @param recs * 注:Seq-Sequence是一个特质,可以理解成一个列表;Recommendation是一个自定义实现类 */ case class MovieRecs(mid:Int,recs:Seq[Recommendation])
2.离线推荐算法实现类--OfflineRecommender
package offlineRecommender import org.apache.spark.SparkConf import org.apache.spark.mllib.recommendation.{ALS, Rating} import org.apache.spark.sql.SparkSession import org.jblas.DoubleMatrix /** * @Author : ASUS and xinrong * @Version : 2020/9/20 * 离线推荐算法 */ object OfflineRecommender { //声明要用的表 val MONGODB_RATING_COLLECTION="Rating" val MONGODB_MOVIE_COLLECTION="Movie" //将要取前几位的数量定义成常量,便于修改 val USER_MAX_RECOMMENDATION=10 //指定表用于存储基于用户的推荐 val MONGODB_USER_RECE="UserRecs" //指定表用于存储两个电影的相似度相关数据 val MONGODB_MOVIE_RECS="MovieRecs" def main(args: Array[String]): Unit = { val conf=Map( "spark.core"->"local[2]", "mongodb.uri"->"mongodb://192.168.212.21:27017/recom", "mongodb.db"->"recom" ) //1.创建Spark运行的环境(第二种创建环境的表现形式) //因为涉及到计算,所以这里设置一下executor的内存的大小和driver的内存大小 val sparkConf=new SparkConf().setAppName("OfflineRecommender").setMaster(conf("spark.core")) .set("spark.executor.memory","6G") .set("spark.driver.memory","2G") val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate() //2.获取MongoDB中的数据----------------------------------------------------------------- //1)获取MongoDB的连接信息 val mongoConfig = new MongoConfig(conf("mongodb.uri"),conf("mongodb.db")) //2)获取数据 import sparkSession.implicits._ //将DataFrame数据转换成RDD数据,需要先.as[MovieRating]再.rdd() //(1)获取Rating的数据 val ratingRDD=sparkSession .read .option("uri",mongoConfig.uri) .option("collection",MONGODB_RATING_COLLECTION) .format("com.mongodb.spark.sql") .load() .as[MovieRating] //Dataset[MovieRating] .rdd //RDD[MovieRating] .map(x=>(x.uid,x.mid,x.score)) //RDD[(Int,Int,Double)] .cache() //(2)获取Movie的数据 val movieRDD=sparkSession .read .option("uri",mongoConfig.uri) .option("collection",MONGODB_MOVIE_COLLECTION) .format("com.mongodb.spark.sql") .load() .as[Movie] .rdd .map(_.mid) .cache() //3.训练ALS(Alternatingleast squares)-交替最小二乘法模型------------------------------------- /** * ALS算法属于User-Item CF,也叫做混合CF。它同时考虑了User和Item两个方面。 * ALS模型需要4个参数 * 1)trainData-训练数据(训练集):Rating(Spark MLlib提供的)对象的RDD--包含用户ID、物品ID,偏好值 * 2)Rank-特征维度(特征值,它越大越好):50 * 3)Iterations-迭代次数(也是有越多越好):5次(根据自己的算力和用户需求来设置) * 4)Lambda-防过拟合参数(跨度):0.01 */ //1)构建训练数据集、得到训练模型---------------------------------------------------------------------------------- //下面这行中的Rating是Spark Mllib里面提供的-包含三种数据如下: //userId-用户ID、product-物品ID、rating-偏好值(他们三个统一在一起就成为Rating) val trainData=ratingRDD.map(x=>Rating(x._1,x._2,x._3)) // val rank=50 //定义维度 // val iterations=5 //定义迭代次数 // val lambda=0.01 //设置跨度 //如果有多个数据,建议用元组的方式将他们钉在一起 //这样更方便 val (rank,iteration,lambda)=(50,5,0.01) val model=ALS.train(trainData,rank,iteration,lambda) //2)计算用户推荐矩阵--(基于用户的推荐)----------------------------------------------------------------------------- val userRDD=ratingRDD.map(_._1).distinct().cache() println("*********** userRDD **********") userRDD.toDF().show() //(1)将 含有所有用户名的数据集合 和 含有所有电影名的数据集合 做笛卡儿积(RDD之间的笛卡儿积运算)--RDD[(Int,Int)] val userWithMovie=userRDD.cartesian(movieRDD.distinct()) println("************ userMovies ***********") userWithMovie.toDF().show() //(2)连带模型和做好笛卡儿积之后的表得出预测数据 //predict()方法可以用来对新的数据点或数据点组成的RDD应用该模型进行预测 val predictData = model.predict(userWithMovie) //4.将数据写入MongoDB------------------------------------------------------------- //1)Filter--将相关性强的数据写入,这里就是将偏好值大于0的数据写入 val userRecs=predictData .filter(_.rating>0) //Spark Mllib 提供的Rating是RDD类型的,这里将其转换成Double类型的 //用户ID-user作为Key,(物品ID-product、偏好值-rating)作为value //rating123就是preRatings里面的Rating(注:preRatings的类型为:RDD[Rating]) .map{ rating123=>(rating123.user,(rating123.product,rating123.rating)) //2)groupBy }.groupByKey() //3)将数据转换成MongoDB能够接收的形式--需要自定义数据类 .map{ case(uid,predict)=> //在Model里面新建自定义数据类 UserRecs(uid,predict.toList.sortWith(_._2>_._2).take(USER_MAX_RECOMMENDATION).map(x=>Recommendation(x._1,x._2))) } //转成DataFrame再写入MongoDB .toDF() //4)写入MongoDB userRecs .write .option("uri",mongoConfig.uri) .option("collection",MONGODB_USER_RECE) .mode("overwrite") .format("com.mongodb.spark.sql") .save() //5.计算电影相似度矩阵-(基于物品的推荐)----------------------------------------------------------------------------------------- //1)获取电影的50维向量的特征矩阵(productFeatures)-S(q属于S) //并将它的features转换成DoubleMatrix便于之后基于org.jblas计算余弦相似度 //DoubleMatrix就是来自org.jblas包, //movieFeatures是RDD[(Int-mid,DoubleMatrix-feature)],这两列数据 val movieFeatures =model.productFeatures.map{ //(电影ID,产品特征) , 将特征-feature矩阵化 case(mid,feature)=> (mid,new DoubleMatrix(feature)) } //2)自己和自己做笛卡儿积--求出每一个电影和所有电影的相似性(可能会是稀疏的) val movieMatrix = movieFeatures.cartesian(movieFeatures) //过滤前--movieRecs:RDD[(Int,DoubleMatrix),(Int,DoubleMatrix)] //过滤掉自己和自己的相似度数据 val movieRecs=movieMatrix.filter{ case(a,b)=> a._1!=b._1 } //3)求余弦相似度、格式变换 .map{ case(a,b)=> //电影相似性评分--余弦相似性,自定义方法 //(电影1,(电影2,电影1和电影2的相似度)) //(Int,(Int,Double)) (a._1,(b._1,this.consinSim(a._2,b._2))) //这个this写不写均可 }// 4)取出相似度大于0.6的数据(余弦值越接近1,就越相似) .filter(_._2._2>0.6) //5)分组 (Int,Iterable[(Int,Double)]) .groupByKey() //6)转换成MongoDB可以存储的形式-通过自定义数据类 .map{ case(mid,recs)=> //自定义数据类 //recs在.toList之前数据类型是:Iterable[(Int,Double)] //recs在.toList之后数据类型是:List[(Int,Double)] MovieRecs(mid,recs.toList.map(x=>Recommendation(x._1,x._2))) } //7)转成DataFrame .toDF() // 8)写入MongoDB movieRecs .write .option("uri", mongoConfig.uri) .option("collection", MONGODB_MOVIE_RECS) .mode("overwrite") .format("com.mongodb.spark.sql") .save() //6.关闭Spark--------------------------------------------------------------------- sparkSession.close() } /** * 计算两个电影间的余弦相似度 * * @param movie1 在求余弦相似度的时候,将其看成一个向量 * @param movie2 在求余弦相似度的时候,将其看成一个向量 * @return */ def consinSim(movie1: DoubleMatrix, movie2: DoubleMatrix)= { //点乘:dot()、模:norm2() movie1.dot(movie2)/(movie1.norm2()*movie2.norm2()) } }
1)离线推荐算法-ALS实现过程图:


2)基于用户、基于物品的推荐功能实现的主要方法---解释过程图



其中model是训练好的ALS(交替最小二乘法模型):

3.ALS优化Scala代码实现--ALSTrainer
package offlineRecommender import breeze.numerics.sqrt import org.apache.spark.SparkConf import org.apache.spark.mllib.recommendation.{ALS, MatrixFactorizationModel, Rating} import org.apache.spark.rdd.RDD import org.apache.spark.sql.SparkSession /** * @Author : ASUS and xinrong * @Version : 2020/9/21 * 优化ALS * 原理:遍历所有业务范围内的取值情况,找到最优的模型 * val(rank,iteration,lambda)=(50,5,0.01) * 模型的评价:预测值和实际的误差值最小 */ object ALSTrainer { def main(args: Array[String]): Unit = { val conf=Map( "spark.core"->"local[2]", "mongodb.uri"->"mongodb://192.168.212.21:27017/recom", "mongodb.db"->"recom" ) //一、创建Spark环境 val sparkConf=new SparkConf().setAppName("ALSTrainer").setMaster(conf("spark.core")) val sparkSession = SparkSession.builder().config(sparkConf).getOrCreate() //二、加载评分数据 import sparkSession.implicits._ val mongoConfig = new MongoConfig(conf("mongodb.uri"),conf("mongodb.db")) val ratingRDD= sparkSession .read .option("uri", mongoConfig.uri) .option("collection", OfflineRecommender.MONGODB_RATING_COLLECTION) .format("com.mongodb.spark.sql") //格式化 .load() .as[MovieRating] //Dataset .rdd .map(x => Rating(x.uid, x.mid, x.score)) .cache() //数据切分[2/8分](不加也可) val arraySplit = ratingRDD.randomSplit(Array(0.8,0.2)) val trainRDD=arraySplit(0) // 8/10 的数据作为训练数据 val testRDD=arraySplit(1) // 2/10 的数据作为测试数据 //三、模型训练--->输出最优参数 adjectALSParams(trainRDD,testRDD) //四、关闭spark sparkSession.close() } /** * 自定义函数--输出最优参数 * * @param traningRDD * @param testRDD */ def adjectALSParams(traningRDD: RDD[Rating], testRDD: RDD[Rating]): Unit = { //迭代的次数不用太在意,因为他们和自身机器的资源有关 //rank:特征维度 //lambda:跨度 val result=for(rank<-Array(30,40,50,60,70);lambda<-Array(1,0.1,0.01)) yield { //训练模型 val model = ALS.train(traningRDD,rank,5,lambda) //获取模型均方根误差 val rmse=getRmase(model,testRDD) (rank,lambda,rmse) } //打印出误差最小的: //用第三个Double进行排序,然后得出第一个--最优参数 println(result.sortBy(_._3).head) } /** * 自定义函数--获取模型均方根误差函数 * * @param model * @param testRDD */ def getRmase(model: MatrixFactorizationModel, testRDD: RDD[Rating]) = { //需要构造userProductsRDD--预测一下用户打的分数 //1)先通过从MongoDB里面取出的testRDD得到第一行(商品)和第一列(用户)的数据 val userWithProduct=testRDD.map(x=>(x.user,x.product)) //2)再通过model、测试数据算出预期的predictRatingRDD val predictRatingRDD=model.predict(userWithProduct) //3)真实写的分数-从测试的数据里面取的 val realRating = testRDD.map(x=>((x.user,x.product),x.rating)) //4)预测的分数-预测的数据-predictRating val predictRating=predictRatingRDD.map(x=>((x.user,x.product),x.rating)) //5)计算误差 //最外面的sqrt--开平方 sqrt { //join()--合并实际表和预测表(是为了将实际偏好值和预测偏好值放在一起好做差) //得到的是:(userId-int,produceId-int),(真实值-double,预测值-double) realRating.join(predictRating) .map { //模式匹配 case((uid,mid),(real,pre))=> val err =real - pre //计算误差 err * err //误差的平方 }.mean() //求平均 } } }
1)ALS优化实现过程图



2)ALS优化参数过程图:

浙公网安备 33010602011771号