本文将使用 SparkML 来构建推荐引擎.
推荐引擎算法大致分为 基于内容的过滤, 协同过滤, 矩阵分解, 本文将使用基于属于矩阵分解的 最小二乘法 算法来构建推荐引擎.
对于推荐引擎模块这里将分为两篇文章, 第一篇文章主要是以实现推荐功能为主, 第二篇文章主要是对模型进行评估
文章将按照以下章节来进行书写: 需求分析, 获取数据, 提取特征, 训练模型, 使用模型 (推荐)
一, 需求分析
假设我们是 MovieStream 团队, 专门为用户提供在线电影和电视节目的内容服务.
现在我们有个需求:: 给用户推荐电影!
就这么简单, 哈哈~
二, 获取数据
可从 http://files.grouplens.org/datasets/movielens/ml-100k.zip 下载模拟的数据集.
对于推荐模型, 主要用到了里面的三个文件:
- u.user(用户属性文件)
- u.item(电影元数据)
- u.data(用户对电影的评级)
数据文件说明:
1,u.user(用户属性文件)
字段及格式说明: user id | age | gender | occupation(职业) | zip code
样例:
- |24|M|technician|85711
- |53|F|other|94043
- |23|M|writer|32067
- |24|M|technician|43537
- |33|F|other|15213
- 2,u.item(电影信息数据)
字段及格式说明:
movie id | movie title | release date | video release date | IMDb URL | unknown | Action | Adventure | Animation | Children's | Comedy | Crime | Documentary | Drama | Fantasy | Film-Noir | Horror | Musical | Mystery | Romance | Sci-Fi | Thriller | War | Western |
样例:
- |Toy Story (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Toy Story (1995)|0|0|0|1|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0
- |GoldenEye (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?GoldenEye (1995)|0|1|1|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0
- |Four Rooms (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Four Rooms (1995)|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0|1|0|0
- |Get Shorty (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Get Shorty (1995)|0|1|0|0|0|1|0|0|1|0|0|0|0|0|0|0|0|0|0
- |Copycat (1995)|01-Jan-1995||http://us.imdb.com/M/title-exact?Copycat (1995)|0|0|0|0|0|0|1|0|1|0|0|0|0|0|0|0|1|0|0
- 3,u.data(用户对电影的评分)
字段及格式说明: user_id item_id rating timestamp(注意: 分隔符为 "\t")
样例:
- 196 242 3 881250949
- 186 302 3 891717742
- 22 377 1 878887116
- 244 51 2 880606923
- 166 346 1 886397596
三, 提取特征
- /* 生成用户评分数据的 RDD, 格式为: 用户 电影 评分 时间戳 */
- val rawData: RDD[String] = sc.textFile("file:///E:/spark/ml-100k/u.data")
- /* 去掉时间戳的字段, 格式变为: 用户 电影 评分; rawRating 类型为 Array */
- val rawRatings = rawData.map(_.split("\\t").take(3))
- /* 格式变为: Rating(用户 电影 评分), 作为后续训练模型的参数 */
- val ratings = rawRatings.map{case Array(user, movie, rating) =>{
- // 封装成 Rating
- Rating(user.toInt, movie.toInt, rating.toDouble)
- }}
四, 训练模型
最小二乘法的模型需要以下三个参数:
1,rank
对应 ALS 模型中的因子个数, 也就是在低阶近似矩阵中的隐含特征个数. 因子个数一般越多越好. 但它也会接影响模型训练和保存时所需的内存开销, 尤其是在用户和物品很多的时候. 因此实践中该参数常作为训练效果与系统开销之间的调节参数. 通常, 其合理取值为 10 到 200.
可以简单理解为: 模型因子的列的数量
2,iterations
对应运行时的迭代次数. ALS 能确保每次迭代都能降低评级矩阵的重建误差, 但一般经少数次迭代后 ALS 模型便已能收敛为一个比较合理的好模型. 这样, 大部分情况下都没必要迭代太多次 (10 次左右一般就挺好).
3,lambda
该参数控制模型的正则化过程, 从而控制模型的过拟合情况. 其值越高, 正则化越严厉. 该参数的赋值与实际数据的大小, 特征和稀疏程度有关. 和其他的机器学习模型一样, 正则参数应该通过用非样本的测试数据进行交叉验证来调整.
这里将使用的 rank,iterations 和 lambda 参数的值分别为 50,10 和 0.01
代码如下:
- import org.apache.spark.mllib.recommendation.{
- Rating, ALS
- }
- // 这就得到了推荐的模型
- val model = ALS.train(ratings, 50, 10, 0.01)
五, 使用模型 (推荐)
1, 用户推荐
为 id 为 789 的用户推荐 10 个电影
- // 为指定的用户推荐 N 个商品
- val userID = 789
- val K = 10
- val topKRecs: Array[Rating] = model.recommendProducts(userID, K)
- println(topKRecs.mkString("\n"))
输出为:
- Rating(789,715,5.931851273771102)
- Rating(789,12,5.582301095666215)
- Rating(789,959,5.516272981542168)
- Rating(789,42,5.458065302395629)
- Rating(789,584,5.449949837103569)
- Rating(789,750,5.348768847643657)
- Rating(789,663,5.30832117499004)
- Rating(789,134,5.278933936827717)
- Rating(789,156,5.250959077906759)
- Rating(789,432,5.169863417126231)
2, 物品推荐 (作为了解)
物品推荐可以理解为: 给定一个物品, 推荐 K 个与该物品相似的物品
我们上面得到的推荐模型中没有提供物品推荐的方法, 但是谋问题, 我们自己可以根据余弦相似度来实现.
科普: 余弦相似度是两个两个向量在 n 维空间里两者夹角的度数. 它的值是两个向量的点积与各向量范数 (或长度) 的乘积的商. 该值的取值范围是 -1 到 1 之间, 1 表示完全相似, 0 表示不相关,-1 表示两者不仅不相关而且还完全不同.
ok, 我们来写一个计算余弦相似度的函数, 在写之前需要引入 jblas 线性代数库, 该库中有一个 DoubleMatrix 类对象, 向量和矩阵都用该对象来表示
- import org.jblas.DoubleMatrix
- /**
- * 用于商品推荐
- * 通过传入两个向量, 返回这两个向量之间的余弦相似度
- * @param vec1
- * @param vec2
- * @return
- */
- def cosineSimilarity(vec1: DoubleMatrix, vec2: DoubleMatrix): Double = {
- vec1.dot(vec2) / (vec1.norm2() * vec2.norm2())
- }
开始根据物品推荐:
- /**
- * 基于商品进行推荐
- */
- /* 通过商品 ID 获得与该商品相似的商品 */
- val itemId = 567
- val itemFactor: Array[Double] = model.productFeatures.lookup(itemId).head
- val itemVector: DoubleMatrix = new DoubleMatrix(itemFactor)
- // 获得每个商品与给出的商品的余弦相似度
- val sims = model.productFeatures.map{case (id, factor) => {
- val factorVector = new DoubleMatrix(factor)
- val sim = cosineSimilarity(factorVector, itemVector)
- (id, sim)
- }}
- // 打印出前 10 的商品
- val topItem: Array[(Int, Double)] = sims.sortBy(-_._2).take(10)
- println("与 567 商品相似的商品:\n" + topItem.mkString("\n") + "\n")
输出为:
与 567 商品相似的商品:
- (567,1.0)
- (1471,0.6932331537649621)
- (670,0.6898690594544726)
- (201,0.6897964975027041)
- (343,0.6891221044611473)
- (563,0.6864214133620066)
- (294,0.6812075443259535)
- (413,0.6754663844488256)
- (184,0.6702643811753909)
- (109,0.6594872765176396)
很正常, 排名第一的最相似物品就是我们给定的物品. 但是注意, 因为模型的初始化是随机的, 所以后面的商品可能跟你的不一样, 这很正常哈~
来源: http://www.bubuko.com/infodetail-3004136.html