前言

经过2节对MovieLens数据集的学习,想必读者对MovieLens数据集认识的不错了;同时也顺带回顾了些Spark编程技巧,Python数据分析技巧。

本节将是让人兴奋的一节,它将实现一个基于Spark的推荐系统引擎。

PS1:关于推荐算法的理论知识,请读者先自行学习,本文仅介绍基于ALS矩阵分解算法的Spark推荐引擎实现。

PS2:全文示例将采用Scala语言。

第一步:提取有效特征

1. 首先,启动spark-shell并分配足够内存:

2. 载入用户对影片的评级数据:

  1. // 载入评级数据
  2. val rawData = sc.textFile("/home/kylin/ml-100k/u.data")
  3. // 展示一条记录
  4. rawData.first()

结果为:

       3. 切分记录并返回新的RDD:

  1. // 格式化数据集
  2. val rawRatings = rawData.map(_.split("\t").take(3))
  3. // 展示一条记录
  4. rawRatings.first()

4. 接下来需要将评分矩阵RDD转化为Rating格式的RDD:

  1. // 导入rating类
  2. import org.apache.spark.mllib.recommendation.Rating
  3. // 将评分矩阵RDD中每行记录转换为Rating类型
  4. val ratings = rawRatings.map { case Array(user, movie, rating) => Rating(user.toInt, movie.toInt, rating.toDouble) }

这是因为MLlib的ALS推荐系统算法包只支持Rating格式的数据集。

第二步:训练推荐模型

接下来可以进行ALS推荐系统模型训练了。MLlib中的ALS算法接收三个参数:

- rank:对应的是隐因子的个数,这个值设置越高越准,但是也会产生更多的计算量。一般将这个值设置为10-200;
- iterations:对应迭代次数,一般设置个10就够了;
- lambda:该参数控制正则化过程,其值越高,正则化程度就越深。一般设置为0.01。

1. 首先,执行以下代码,启动ALS训练:

  1. // 导入ALS推荐系统算法包
  2. import org.apache.spark.mllib.recommendation.ALS
  3. // 启动ALS矩阵分解
  4. val model = ALS.train(ratings, 50, 10, 0.01)

这步将会使用ALS矩阵分解算法,对评分矩阵进行分解,且隐特征个数设置为50,迭代10次,正则化参数设为了0.01。

相对其他步骤,训练耗费的时间最多。运行结果如下:

2. 返回类型为MatrixFactorizationModel对象,它将结果分别保存到两个(id,factor)RDD里面,分别名为userFeatures和productFeatures。

也就是评分矩阵分解后的两个子矩阵:

上面展示了id为4的用户的“隐因子向量”。请注意ALS实现的操作都是延迟性的转换操作。

第三步:使用ALS推荐模型

1. 预测用户789对物品123的评分:

2. 为用户789推荐前10个物品:

  1. val userId = 789
  2. val K = 10
  3.  
  4. // 获取推荐列表
  5. val topKRecs = model.recommendProducts(userId, K)
  6. // 打印推荐列表
  7. println(topKRecs.mkString("\n"))

结果为:

3. 初步检验推荐效果

获取到各个用户的推荐列表后,想必大家都想先看看用户评分最高的电影,和给他推荐的电影是不是有相似。

3.1 创建电影id - 电影名字典:

  1. // 导入电影数据集
  2. val movies = sc.textFile("/home/kylin/ml-100k/u.item")
  3. // 建立电影id - 电影名字典
  4. val titles = movies.map(line => line.split("\\|").take(2)).map(array => (array(0).toInt, array(1))).collectAsMap()
  5. // 查看id为123的电影名
  6. titles(123)

结果为:

这样后面就可以根据电影的id找到电影名了。

3.2 获取某用户的所有观影记录并打印:

  1. // 建立用户名-其他RDD,并仅获取用户789的记录
  2. val moviesForUser = ratings.keyBy(_.user).lookup(789)
  3. // 获取用户评分最高的10部电影,并打印电影名和评分值
  4. moviesForUser.sortBy(-_.rating).take(10).map(rating => (titles(rating.product), rating.rating)).foreach(println)

结果为:

3.3 获取某用户推荐列表并打印:

读者可以自行对比这两组列表是否有相似性。

第四步:物品推荐

很多时候还有另一种需求:就是给定一个物品,找到它的所有相似物品。

遗憾的是MLlib里面竟然没有包含内置的函数,需要自己用jblas库来实现 = =#。

1. 导入jblas库矩阵类,并创建一个余弦相似度计量函数:

  1. // 导入jblas库中的矩阵类
  2. import org.jblas.DoubleMatrix
  3. // 定义相似度函数
  4. def cosineSimilarity(vec1: DoubleMatrix, vec2: DoubleMatrix): Double = {
  5. vec1.dot(vec2) / (vec1.norm2() * vec2.norm2())
  6. }

2. 接下来获取物品(本例以物品567为例)的因子特征向量,并将它转换为jblas的矩阵格式:

  1. // 选定id为567的电影
  2. val itemId = 567
  3. // 获取该物品的隐因子向量
  4. val itemFactor = model.productFeatures.lookup(itemId).head
  5. // 将该向量转换为jblas矩阵类型
  6. val itemVector = new DoubleMatrix(itemFactor)

3. 计算物品567和所有其他物品的相似度:

  1. // 计算电影567与其他电影的相似度
  2. val sims = model.productFeatures.map{ case (id, factor) =>
  3. val factorVector = new DoubleMatrix(factor)
  4. val sim = cosineSimilarity(factorVector, itemVector)
  5. (id, sim)
  6. }
  7. // 获取与电影567最相似的10部电影
  8. val sortedSims = sims.top(K)(Ordering.by[(Int, Double), Double] { case (id, similarity) => similarity })
  9. // 打印结果
  10. println(sortedSims.mkString("\n"))

结果为:

其中0.999999当然就是自己跟自己的相似度了。

4. 查看推荐结果:

  1. // 打印电影567的影片名
  2. println(titles(567))
  3. // 获取和电影567最相似的11部电影(含567自己)
  4. val sortedSims2 = sims.top(K + 1)(Ordering.by[(Int, Double), Double] { case (id, similarity) => similarity })
  5. // 再打印和电影567最相似的10部电影
  6. sortedSims2.slice(1, 11).map{ case (id, sim) => (titles(id), sim) }.mkString("\n")

结果为:

看看,这些电影是不是和567相似?

第五步:推荐效果评估

在Spark的ALS推荐系统中,最常用到的两个推荐指标分别为MSE和MAPK。其中MSE就是均方误差,是基于评分矩阵的推荐系统的必用指标。那么MAPK又是什么呢?

它称为K值平均准确率,最多用于TopN推荐中,它表示数据集范围内K个推荐物品与实际用户购买物品的吻合度。具体公式请读者自行参考有关文档。

本文推荐系统就是一个[基于用户-物品评分矩阵的TopN推荐系统],下面步骤分别用来获取本文推荐系统中的这两个指标。

PS:记得先要导入jblas库。

1. 首先计算MSE和RMSE:

  1. // 创建用户id-影片id RDD
  2. val usersProducts = ratings.map{ case Rating(user, product, rating) => (user, product)}
  3. // 创建(用户id,影片id) - 预测评分RDD
  4. val predictions = model.predict(usersProducts).map{
  5. case Rating(user, product, rating) => ((user, product), rating)
  6. }
  7. // 创建用户-影片实际评分RDD,并将其与上面创建的预测评分RDD join起来
  8. val ratingsAndPredictions = ratings.map{
  9. case Rating(user, product, rating) => ((user, product), rating)
  10. }.join(predictions)
  11.  
  12. // 导入RegressionMetrics类
  13. import org.apache.spark.mllib.evaluation.RegressionMetrics
  14. // 创建预测评分-实际评分RDD
  15. val predictedAndTrue = ratingsAndPredictions.map { case ((user, product), (actual, predicted)) => (actual, predicted) }
  16. // 创建RegressionMetrics对象
  17. val regressionMetrics = new RegressionMetrics(predictedAndTrue)
  18.  
  19. // 打印MSE和RMSE
  20. println("Mean Squared Error = " + regressionMetrics.meanSquaredError)
  21. println("Root Mean Squared Error = " + regressionMetrics.rootMeanSquaredError)

基本原理是将实际评分-预测评分扔到RegressionMetrics类里,该类提供了mse和rmse成员,可直接输出获取。

结果为:

2. 计算MAPK:

  1. // 创建电影隐因子RDD,并将它广播出去
  2. val itemFactors = model.productFeatures.map { case (id, factor) => factor }.collect()
  3. val itemMatrix = new DoubleMatrix(itemFactors)
  4. val imBroadcast = sc.broadcast(itemMatrix)
  5.  
  6. // 创建用户id - 推荐列表RDD
  7. val allRecs = model.userFeatures.map{ case (userId, array) =>
  8. val userVector = new DoubleMatrix(array)
  9. val scores = imBroadcast.value.mmul(userVector)
  10. val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1)
  11. val recommendedIds = sortedWithId.map(_._2 + 1).toSeq
  12. (userId, recommendedIds)
  13. }
  14.  
  15. // 创建用户 - 电影评分ID集RDD
  16. val userMovies = ratings.map{ case Rating(user, product, rating) => (user, product) }.groupBy(_._1)
  17.  
  18. // 导入RankingMetrics类
  19. import org.apache.spark.mllib.evaluation.RankingMetrics
  20. // 创建实际评分ID集-预测评分ID集 RDD
  21. val predictedAndTrueForRanking = allRecs.join(userMovies).map{ case (userId, (predicted, actualWithIds)) =>
  22. val actual = actualWithIds.map(_._2)
  23. (predicted.toArray, actual.toArray)
  24. }
  25. // 创建RankingMetrics对象
  26. val rankingMetrics = new RankingMetrics(predictedAndTrueForRanking)
  27. // 打印MAPK
  28. println("Mean Average Precision = " + rankingMetrics.meanAveragePrecision)

结果为:

比较坑的是不能设置K,也就是说,计算的实际是MAP...... 正如属性名:meanAveragePrecision。

小结

感觉MLlib的推荐系统真的很一般,一方面支持的类型少 - 只支持ALS;另一方面支持的推荐系统算子也少,连输出个RMSE指标都要写好几行代码,太不方便了。

唯一的好处是因为接近底层,所以可以让使用者看到些实现的细节,对原理更加清晰。

第三篇:一个Spark推荐系统引擎的实现的更多相关文章

  1. 第三篇:Spark SQL Catalyst源码分析之Analyzer

    /** Spark SQL源码分析系列文章*/ 前面几篇文章讲解了Spark SQL的核心执行流程和Spark SQL的Catalyst框架的Sql Parser是怎样接受用户输入sql,经过解析生成 ...

  2. Hadoop环境搭建|第三篇:spark环境搭建

    一.环境搭建 1.1.上传spark安装包 创建文件夹用于存放spark安装文件命令:mkdir spark 1.2.解压spark安装包 命令:tar -zxvf spark-2.1.0-bin-h ...

  3. jquery jtemplates.js模板渲染引擎的详细用法第三篇

    jquery jtemplates.js模板渲染引擎的详细用法第三篇 <span style="font-family:Microsoft YaHei;font-size:14px;& ...

  4. 分析RAC下一个SPFILE整合的三篇文章的文件更改

    大约RAC下一个spfile分析_整理在_2014.4.17 说明:文章来源于网络 第一篇:RAC下SPFILE文件改动 在RAC下spfile位置的改动与单节点环境不全然一致,有些地方须要特别注意, ...

  5. itemKNN发展史----推荐系统的三篇重要的论文解读

    itemKNN发展史----推荐系统的三篇重要的论文解读 本文用到的符号标识 1.Item-based CF 基本过程: 计算相似度矩阵 Cosine相似度 皮尔逊相似系数 参数聚合进行推荐 根据用户 ...

  6. 【Spark深入学习 -13】Spark计算引擎剖析

    ----本节内容------- 1.遗留问题解答 2.Spark核心概念 2.1 RDD及RDD操作 2.2 Transformation和Action 2.3 Spark程序架构 2.4 Spark ...

  7. 大数据篇:Spark

    大数据篇:Spark Spark是什么 Spark是一个快速(基于内存),通用,可扩展的计算引擎,采用Scala语言编写.2009年诞生于UC Berkeley(加州大学伯克利分校,CAL的AMP实验 ...

  8. 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)

    从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn) 第一篇http://www.cnblogs.com/lyhabc/p/4678330.html第二篇http://w ...

  9. (转) 从0开始搭建SQL Server AlwaysOn 第三篇(配置AlwaysOn)

    原文地址: http://www.cnblogs.com/lyhabc/p/4682986.html 这一篇是从0开始搭建SQL Server AlwaysOn 的第三篇,这一篇才真正开始搭建Alwa ...

随机推荐

  1. 反反爬虫 IP代理

    0x01 前言 一般而言,抓取稍微正规一点的网站,都会有反爬虫的制约.反爬虫主要有以下几种方式: 通过UA判断.这是最低级的判断,一般反爬虫不会用这个做唯一判断,因为反反爬虫非常容易,直接随机UA即可 ...

  2. asp.net Global.asax 不运行解决

    asp.net application的站点发布后 Global.asax 未运行,搞了好久终于解决, 解决方法如下: publish设置 该设置经测试在win server 2003 和2008 都 ...

  3. CEF小白人系列1-认识CEF

    手头上有个项目需要做浏览器的相关功能,评估了几个嵌入式方案最后选定CEF作为开发基础. 在入坑新技术的时候第一选择是去官网学习,这是一个非常好的习惯. CEF官网(请科学上网) https://bit ...

  4. yaf框架加载全局公共函数

    在Boostrap里面建一个方法(按规则命名的函数都会被自动执行) public function _initCommonFunctions(){ Yaf_Loader::import(Yaf_App ...

  5. 关于C/S框架网单表绑定,查询

    这种绑定暂时支持单表,并且不支持主键自增长!保存,删除,查看,修改用框架现成的. 1.先生成tb.bll.dal三个类.框架有生成工具,在debug文件里面有个叫CSFramework.Tools.C ...

  6. Java线程的六种状态

    java线程有很多种状态,最主要的有六种,被创建.运行.睡眠.等待.阻塞以及消亡六种,也有很多归结为5种,把睡眠以及等待归结为冻结: 被创建:就是线程被创建,就是new thread()之后就是创建一 ...

  7. react——一个todolist的demo

    代码如下: function ToDoListHeader(props) { return <h1 className={props.className}>ToDoList</h1& ...

  8. docker mysql 主从复制

    当然首先 docker pull mysql mkdir /usr/local/mysqlData/master/cnf mkdir /usr/local/mysqlData/master/data ...

  9. C语言老司机学Python (二)

    标准数据类型: 共6种:Number(数字),String(字符串),List(列表),Tuple(元组),Sets(集合),Dictionary(字典) 本次学习主要是和数据类型混个脸熟,知道每样东 ...

  10. Redis笔记3-redis事务

    Redis的事务机制允许同时执行多条指令,它是原子性操作,事务中的命令要么全部执行,要么全部不执行,另外,事务中的所有指令都会被序列化,而且其开始执行过程中,不回被即时过来的指令所打断,其需要经历三个 ...