目录
1. 概述
1.1 协同过滤
1.2 相似度的计算
1.3 ALS算法
2. 代码实践
2.1 案列1:综合
2.2 案列2:基于用户的推荐
2.3 模型存储与加载
2.3.1 存储
2.3.2 加载
2.4 案列3:基于物品推荐
3. 推荐系统的冷启动问题
① 用户冷启动
② 系统冷启动
③ 物品冷启动
买了一个手机,再次刷新会出现类似的产品
推荐系统模型是基于协同过滤思想实现
协同过滤思想:它利用大量已有的用户偏好来估计用户对其未接触过的物品的喜好程度。其内在思想是相似度的定义。
基于用户的推荐(“志趣相投”):计算用户与用户之间的相似度,完成推荐
基于物品的推荐(物以类聚”):对于不熟悉的用户,在缺少特定用户信息的情况下,根据用户已有的偏好数据去推荐一个未知物品是合理的。
无论是基于用户还是基于物品的推荐,其本质思想是计算用户和用户之间的相似度,或者计算物品和物品之间相似度,所以我们可以用常见的一些距离来进行衡量,比如欧氏距离,马氏距离,曼哈顿距离等,也可以使用夹角余弦相似度来衡量。目前,主流做法是通过夹角余弦相似度来实现。
如果是基于距离方法来判断的话,值越小(距离越近),相似度越大。
用余弦度量的相似度计算中,可以用夹角的大小来反映目标之间的相似性。得到的值,越大,相似度越大。
在生产环境下,用户的偏好矩阵往往是稀疏的,但不能把空的置为0,会出现很大的偏差。
预测空缺值是多少。
对这个稀疏的矩阵建模。一般可以采用矩阵分解(或矩阵补全)的方式。
具体就是找出两个低维度的矩阵,使得它们的乘积是原始的矩阵。
用户因子矩阵和物品因子矩阵最重要作用:计算相似度
原始评级矩阵通常很稀疏,但因子矩阵却是稠密的(满秩的)
因子矩阵的值是自己瞎编的,打个样板(通过算法可以得到系数值)
因子分解类模型的好处在于,一旦建立了模型,对推荐的求解便相对容易。所以这类模型的表现通常都很出色。但弊端可能在于因子数量的选择有一定困难,往往要结合具体业务和数据量来决定。一般来说,因子的取值范围在10~200之间。
K隐藏因子:如导演因素,演员因素,题材因素
ALS是交替最小二乘(alternating least squares)的简称
ALS算法的解决方案很简单:只计算已知打分的重构误差。
ALS的实现原理是迭代式求解一系列最小二乘回归问题。在每一次迭代时,固定用户因子矩阵或是物品因子矩阵中的一个,然后用固定的这个矩阵以及评级数据来更新另一个矩阵。之后,被更新的矩阵被固定住,再更新另外一个矩阵。如此迭代,直到模型收敛(或是迭代了预设好的次数)
固定:随机化为一个因子矩阵生成初始数据
为用户推荐商品,为商品推荐用户,预测用户下次打分情况
用户ID 物品id 打分
1 11 2
1 12 3
1 13 1
1 14 0
1 15 1
2 11 1
2 12 2
2 13 2
2 14 1
2 15 4
3 11 2
3 12 3
3 14 0
3 15 1
4 11 1
4 12 2
4 14 1
4 15 4
5 11 1
5 12 2
5 13 2
5 14 1
5 15 4
package com.lj.als
import org.apache.spark.mllib.recommendation.{ALS, Rating}
import org.apache.spark.{SparkConf, SparkContext}
object Driver {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("als")
val sc = new SparkContext(conf)
val data = sc.textFile("e://data/als.txt")
//--为了满足Spark建模要求:
//--RDD[String]->RDD[Rating(userId[Int],itemId[Int],score[Double])]
val r1 = data.map { line =>
val info = line.split(" ")
val userId = info(0).toInt
val itemId = info(1).toInt
val score = info(2).toDouble
Rating(userId, itemId, score)
}
// r1.foreach(println)
//--①参:数据集 ②参:隐藏因子数 K,根据实际情况来定 ③参:最大的迭代次数 ④参:λ 正则化参数,防止模型过拟合
val model = ALS.train(r1, 3, 10, 0.01)
val u5Result = model.recommendProducts(5, 2) //--下面表示为5号用户,推荐两个商品
val item12Result = model.recommendUsers(12, 1) //--下面表示为12号商品,推荐1个用户
val u3Predict = model.predict(3, 14) //--下面表示预测3号用户对14号商品的评分
u5Result.foreach {println}
item12Result.foreach {println}
println(u3Predict)
}
}
u.data
196 242 3 881250949
186 302 3 891717742
22 377 1 878887116
244 51 2 880606923
166 346 1 886397596
298 474 4 884182806
u.item:
1|Toy Story (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Toy%20Story%20(1995)|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
2|GoldenEye (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?GoldenEye%20(1995)|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0
package com.lj.als
import org.apache.spark.SparkConf
import org.apache.spark.SparkContext
import org.apache.spark.mllib.recommendation.Rating
import org.apache.spark.mllib.recommendation.ALS
object Driver {
def main(args: Array[String]): Unit = {
//--处理u.data文件,建立推荐系统模型,为编号789号用户推荐10部电影
//--提示:分隔符是制表符。
val conf=new SparkConf().setMaster("local").setAppName("alsmovie")
val sc=new SparkContext(conf)
val data=sc.textFile("d://data/ml/u.data",5)
//--步骤1:RDD[String]->RDD[Rating]
val ratings=data.map { line =>
val info=line.split("\t")
val userId=info(0).toInt
val movieId=info(1).toInt
val score=info(2).toDouble
Rating(userId,movieId,score)
}
//--步骤2:建模
val model=ALS.train(ratings,50,10,0.01)
val u789Result=model.recommendProducts(789, 10)
//--步骤3:读取u.item文件(包含了电影信息)
//--RDD[String]->RDD[(movieId,movieName)]->collectAsMap
//--将RDD转为一个Map
//--所以可以通过Map获取电影名
val movieData=sc.textFile("d://data/ml/u.item", 3)
val movieMap=movieData.map { line =>
val info=line.split("\\|")
val movieId=info(0).toInt
val movieName=info(1)
(movieId,movieName)
}.collectAsMap
//--步骤4:将推荐结果中的电影id变为电影名
val u789=u789Result.map { x =>
//--获取用户id
val userId=x.user
//--获取物品id
val movidId=x.product
//--获取电影名
val movieName=movieMap(movidId)
//--获取评分
val score=x.rating
(userId,movieName,score)
}
//--步骤5:模型检验
//--本例中使用直观检验法,思想:
//--1.先获取789号看过的所有电影
//--2.根据789号用户对电影的打分,获取他喜欢的前10部电影
//--3.用推荐的结果和用户喜好的结果比较,是否有类似的电影
//--keyBy根据指定的匿名函数规则为key属性
//--lookup和keyBy配合使用,指定具体的key
val u789data=ratings.keyBy { x => x.user }.lookup(789)
val u789top10=u789data.sortBy { x => -x.rating }.take(10)
.map { x =>(x.user,movieMap(x.product),x.rating) }
u789top10.foreach{println}
println("--------------")
u789.foreach{println}
}
}
报错
原因:单击模式迭代次数过多
解决办法:减少迭代次数,
注意:本次的测试环境,在生产环境,迭代次数不宜过少,否则会还没有到收敛条件就停止
1.为789号用户推荐10部电影
2.获取了789号用户看过的所有电影:
3.获取789号用户喜爱的前10部电影
1.对于隐藏因子k的选择,在生产环境,用户数和物品数都很多,k的选取范围:50~200。k的取值不宜过多,避免带来过大的计算代价
2.迭代次数,根据服务器的配置情况,尽量多一些,保证算法最后收敛
3.检测模型的准确性,可以用直观检测法,用模型推荐的结果和用户本身的喜好相对比,看是否准备
本例中的实现思路:
①先获取789号用户看过的所有电影
②找出789号用户最喜欢的前10部电影
③用推荐的10部和他喜爱的10部做比对,看是否有类似题材的电影
4.keyBy作用:在操作数据集,指定以什么属性为key,下面表示以用户id属性为key
keyBy后会跟lookup,作用:具体找哪个key
package com.lj.alsmovie
import org.apache.spark.mllib.recommendation.{ALS, Rating}
import org.apache.spark.{SparkConf, SparkContext}
object SaveDriver {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("alsmovie1")
val sc = new SparkContext(conf)
val data = sc.textFile("e://data/u.data", 4)
val ratings = data.map { line =>
val info = line.split("\t")
val userId = info(0).toInt
val movieId = info(1).toInt
val score = info(2).toDouble
Rating(userId, movieId, score)
}
val model = ALS.train(ratings, 50, 10, 0.01)
//--模型存储,目录路径可以是本地文件系统,也可以是HDFS
model.save(sc, "e://data/recResult2")
}
}
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel
import org.apache.spark.{SparkConf, SparkContext}
object LoadDrievr {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("loadDriver")
val sc = new SparkContext(conf)
//--加载推荐系统模型
val model = MatrixFactorizationModel.load(sc, "e://data/recResult2")
val u789 = model.recommendProducts(789, 10)
u789.foreach(println)
}
}
Spark提供的ALS算法建立的推荐系统模型存在一个缺点:
只有基于用户推荐的方法,没有基于物品推荐的方法
基于物品推荐的概念:基于物品来推荐物品
所以用户自己来实现
基于物品推荐的实现思路:
①先定好一个物品,比如物品编号123号,比如推荐10个商品
②要计算出其他物品和123号物品的相似度(通过夹角余弦来计算)
③根据相似度做降序排序,取出前10个商品
package com.lj.alsmovie
import org.apache.spark.mllib.recommendation.MatrixFactorizationModel
import org.apache.spark.{SparkConf, SparkContext}
object LoadDrievr {
def main(args: Array[String]): Unit = {
val conf = new SparkConf().setMaster("local").setAppName("loadDriver")
val sc = new SparkContext(conf)
//--加载推荐系统模型
val model = MatrixFactorizationModel.load(sc, "e://data/recResult2")
val u789 = model.recommendProducts(789, 10)
//--获取的是用户因子矩阵
val userFactors = model.userFeatures
//--获取的是物品因子矩阵,可以用于计算物品和物品之间的相似度
val itemFactors = model.productFeatures
//--找到123号电影的因子值
val item123Factor = itemFactors.keyBy { x => x._1 }.lookup(123).head._2
// item123Factor.foreach(println)
//--此方法用于计算向量之间的夹角余弦
def cosArray(a1: Array[Double], a2: Array[Double]) = {
val a1a2 = a1 zip a2
val a1a2Fenzi = a1a2.map { x => x._1 * x._2 }.sum
val a1Fenmu = Math.sqrt(a1.map { x => x * x }.sum)
val a2Fenmu = Math.sqrt(a2.map { x => x * x }.sum)
val a1a2Cos = a1a2Fenzi / (a1Fenmu * a2Fenmu)
a1a2Cos
}
val cos123 = itemFactors.map { case (id, factor) =>
//--计算当前电影和123号电影的夹角余弦,-本质上就是计算Array[Double] 和Array[Double]之间的夹角余弦
val cos = cosArray(item123Factor, factor)
//--返回当前电影的id和123号的夹角余弦
(id, cos)
}
// cos123.foreach(println)
val item123Top10 = cos123.sortBy { case (id, cos) => -cos }.take(10)
item123Top10.foreach {println}
}
}
如何在没有大量用户数据的情况(新用户)下设计个性化推荐系统并且让用户对推荐结果满意从而愿意使用推荐系统,就是冷启动的问题。
冷启动问题(cold start)主要分3类:
用户冷启动主要解决如何给新用户做个性化推荐的问题。
解决方案:
1.共性推荐:让用户选择感兴趣的内容
2.用户注册时提供的年龄、性别等数据做粗粒度的个性化推荐。
3.提供非个性化的推荐,非个性化推荐的最简单例子就是热门排行榜,
系统冷启动主要解决如何在一个新开发的网站上(还没有用户,也没有用户行为,只有一些物品的信息)设计个性化推荐系统
解决方案:
物品冷启动主要解决如何将新的物品推荐给可能对它感兴趣的用户这一问题。
解决方案:
比如新用户第一次访问推荐系统时,不立即给用户展示推荐结果,而是给用户提供一些物品,让用户反馈他们对这些物品的兴趣,然后根据用户反馈给提供个性化推荐。很多推荐系统采取了这种方式来解决冷启动问题。
如果您看到这了,请点个赞为自己的努力加油