推荐系统模型是基于协同过滤思想实现
概念:无论是基于用户还是基于物品的推荐,其本质思想是计算用户和用户之间的相似度,或者计算物品和物品之间相似度,所以我们可以用常见的一些距离来进行衡量,比如欧氏距离,马氏距离,曼哈顿距离等,也可以使用夹角余弦相似度来衡量。目前,主流做法是通过夹角余弦相似度来实现。
但是生产环境中偏好矩阵往往是稀疏的
所以需要对空缺值进行处理,不能简单设置为0或者某个特定值,因为会导致最后计算的相似度不够准确,使得最终的推荐系统模型失去价值。所以需要某种算法将空缺值预测出来。
原理:多次迭代,求解一系列最小二乘法回归问题。每一次迭代时,固定(随机生成因子值)某一个因子矩阵,更新另外一个因子矩阵,然后计算误差平方和,如此迭代,直到模型收敛。
ALS只计算已知打分的重构误差
这是ALS算法类的源码
case class Rating(user: Int, product: Int, rating: Double)
其中Rating是固定的ALS输入格式,它要求是一个元组类型的数据,其中的数值分别为[Int,Int,Double],因此在数据集建立时,用户名和物品名分别用数值代替,而最后的评分没有变化。
在这个类里,ALS.tran方法是最为重要的方法。这个方法有几个重要参数:
数据集:
在这里插入代码片
import org.apache.spark.mllib.recommendation.{ALS, Rating}
import org.apache.spark.{SparkConf, SparkContext}
object Driver01 {
def main(args: Array[String]): Unit = {
//196 242 3 881250949
val conf = new SparkConf().setMaster("local").setAppName("movies")
val sc = new SparkContext(conf)
val data = sc.textFile("D://data/u.data", 4)
//电影信息文件
val moviedata = sc.textFile("D://data/u.item", 4)
//ALS模型需要RDD[Ratings]类型参数
val res = data.map(line=>{
val info = line.split("\t")
val userId = info(0).toInt
val itemId = info(1).toInt
val rating = info(2).toDouble
Rating(userId,itemId, rating)
})
val mv_res = moviedata.map(line=>{
var info = line.split("\\|")
val m_id= info(0).toInt
val m_name = info(1)
(m_id, m_name)
}).collectAsMap() //转成map方便根据itenid查询name
res.foreach(println(_))
val model = ALS.train(res, 20, 10, 0.01)
val resMovies = model.recommendProducts(789, 10).map(rat=>{
val userID = rat.user
val product = rat.product
val score = rat.rating
val m_name = mv_res.apply(product)
(userID, m_name, score)
})
// resMovies.foreach(println(_))
//检验推荐系统的准确性
//获取789用户看过的所有电影
//keyby 和 lookup一般都是成对使用的
var u789movie = res.keyBy(rat=>rat.user).lookup(789)
val u789top10 = u789movie.sortBy(rat=> -rat.rating).take(10)
val RDD_Top10 = sc.makeRDD(u789top10).map(rat=>{
val userID = rat.user
val product = rat.product
val score = rat.rating
val m_name = mv_res.apply(product)
(userID, m_name, score)
})
// RDD_Top10.foreach(println(_))
//模型的存储
model.save(sc, "hdfs://hadoop01:9000/ALSmovie")
}
}
python实现基于商品的相似度完成推荐算法(待完善)
Spark没有提供基于商品的API实现, 因此需要成需要手动实现
val movieFactors = model.productFeatures
val i123_score = movieFactors.lookup(123).head //head后获取的数据类型是Array[Double]
val movies123Cos = movieFactors.map{case (id, faxtor)=>{
val res = faxtor zip i123_score
val fenzi = res.map(x=>(x._1*x._2)).sum //分子
val fenmu = Math.sqrt(res.map(x=>x._1*x._1).sum) * Math.sqrt(res.map(x=>x._2*x._2).sum) //分母
(id,fenzi/fenmu)
}}.sortBy(x=> -x._2)
完整代码如下
import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel
object itemDriver {
def main(args: Array[String]): Unit = {
//基于物品推荐的核心->计算物品与物品之间的相似度
//需要先计算所有电影和123号电影的相似度
val conf = new SparkConf().setMaster("local").setAppName("movies")
val sc = new SparkContext(conf)
//推荐系统模型的加载
val model = MatrixFactorizationModel.load(sc,"hdfs://hadoop01:9000/ALSmovie")
//为123用户推荐
val u123Res = model.recommendProducts(123,10)
//第一步获取物品因子矩阵RDD[(itemid, 物品的因子数组)]
val movieFactors = model.productFeatures
// movieFactors.foreach(x=>{println(x._1, x._2.mkString(","))})
//第二部:获取123号电影的因子数
val i123_score = movieFactors.lookup(123).head
i123_score.foreach(println(_))
//第三步:计算其他电影与123号电影的相似度,使用向量之间的夹角余弦
val movies123Cos = movieFactors.map{case (id, faxtor)=>{
val res = faxtor zip i123_score
val fenzi = res.map(x=>(x._1*x._2)).sum //分子
val fenmu = Math.sqrt(res.map(x=>x._1*x._1).sum) * Math.sqrt(res.map(x=>x._2*x._2).sum) //分母
(id,fenzi/fenmu)
}}.sortBy(x=> -x._2)
movies123Cos.foreach(println(_))
}
}
保存
model.save(sc, "hdfs://hadoop01:9000/res")
加载
org.apache.spark.mllib.recommendation.MatrixFactorizationModel.load(sc, "hdfs://hadoop01:9000/res")