SparkML之推荐引擎(二)---推荐模型评估
本文内容和代码是接着上篇文章来写的,推荐先看一下哈~
我们上一篇文章是写了电影推荐的实现,但是推荐内容是否合理呢,这就需要我们对模型进行评估
针对推荐模型,这里根据 均方差 和 K值平均准确率 来对模型进行评估,MLlib也对这几种评估方法都有提供内置的函数
在真实情况下,是要不断地对推荐模型的三个关键参数 rank、iterations、lambda 分别选取不同的值,然后对不同参数生成的模型进行评估,从而选取出最好的模型。
下面就对两种推荐模型评估的方法进行说明~
1、均方差(MSE) 和 均方根误差(RMSE)
定义:各平方误差的和与总数目的商。其实可以理解为 预测到的评级 与 真实评级的差值 的平方。
均方根误差的使用也很普遍,其计算只需在MSE上取平方根即可~
评估代码为:
//格式:(userID,电影)
val userProducts: RDD[(Int, Int)] = ratings.map(rating => (rating.user, rating.product))
//模型推测出的评分信息,格式为:((userID,电影), 推测评分)
val predictions: RDD[((Int, Int), Double)] = model.predict(userProducts).map(rating => ((rating.user, rating.product),rating.rating))
//格式为:((userID,电影), (真实平评分,推测评分))
val ratingsAndPredictions: RDD[((Int, Int), (Double, Double))] = ratings.map(rating => ((rating.user, rating.product), rating.rating))
.join(predictions)
//均方差
val MSE = ratingsAndPredictions.map(rap => math.pow(rap._2._1 - rap._2._2, )).reduce(_+_) / ratingsAndPredictions.count()
println("MSE:" + MSE)
//均方根误差
val RMSE: Double = math.sqrt(MSE)
println("RMSE:" + RMSE)
上面是我们自己算出来的,也可以用MLlib内置的函数来算:
import org.apache.spark.mllib.evaluation.{RegressionMetrics, RankingMetrics}
val predictedAndTrue: RDD[(Double, Double)] = ratingsAndPredictions.map{ case((userID, product),(actual, predict)) => (actual, predict)}
val regressionMetrics: RegressionMetrics = new RegressionMetrics(predictedAndTrue)
println("MSE:" + regressionMetrics.meanSquaredError)
println("RMSE:" + regressionMetrics.rootMeanSquaredError)
输出为:
MSE:0.08231947642632852
RMSE:0.2869137090247319
2、K值平均准确率(MAPK)
K值平均准确率(MAPK)的意思是整个数据集上的K值平均准确率(APK)的均值。APK是信息检索中常用的一个指标。它用于衡量针对某个查询所返回的“前K个”文档的平均相关性。
如果结果中文档的实际相关性越高且排名也更靠前,那APK分值也就越高。如果在预测结果中得分更高(在推荐列表中排名也更靠前)的物品实际上也与用户更相关,那自然这个模型就更好。
ok,MAPK评估代码如下:
package ml import org.apache.spark.mllib.evaluation.RankingMetrics
import org.apache.spark.mllib.recommendation.{Rating, ALS}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkContext, SparkConf}
import org.jblas.DoubleMatrix
import sql.StreamingExamples
import scala.collection.Map object MAPKTest{
def main(args: Array[String]) {
StreamingExamples.setStreamingLogLevels()
val conf = new SparkConf().setAppName("MAPKTest").setMaster("local[*]")
val sc = new SparkContext(conf)
/*用户 电影 评分*/
val rawData: RDD[String] = sc.textFile("file:///E:/spark/ml-100k/u.data")
//去掉时间的字段,rawRatings:Array
val rawRatings = rawData.map(_.split("\\t").take())
//user moive rating
val ratings = rawRatings.map{case Array(user, movie, rating) =>{
Rating(user.toInt, movie.toInt, rating.toDouble)
}}
/**
* 得到训练的模型
* 注意:50代表我们得到的模型的因子的列的数量,名称叫 因子维数
*/
val model = ALS.train(ratings, , , 0.01) /*获取模型中所有商品的 factor,并转换成矩阵*/
val itemFactors: Array[Array[Double]] = model.productFeatures.map{case (id, factor) => factor}.collect()
val itemMatrix: DoubleMatrix = new DoubleMatrix(itemFactors)
// println(itemMatrix.rows, itemMatrix.columns) /*获得模型中每个用户对应的每个电影的评分*/
val allRecs = model.userFeatures.map{ case(userId, factor) => {
val userVector = new DoubleMatrix(factor)
/**
* socres是一个DoubleMatrix类型,值为1行N列的 Vector
* 为什么可以通过判断这两个矩阵的乘积的大小,从而来判断分数呢?
* 这归根于ALS算法,该算法是将一个 用户-商品 的矩阵 拆分成 用户、商品两个矩阵
* 因此这两个矩阵的乘积就是实际的 分数
*/
val scores = itemMatrix.mmul(userVector)//矩阵和向量的乘积,求出每个用户的分数
//根据评分倒数排序
val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1)
//(score, itemId)
val recommendIds = sortedWithId.map(_._2 + ).toSeq
//返回用户 和 各个商品评分的倒数的值 的 tuple: (userId,(sorce, itemId))
(userId, recommendIds)
}} /*获取实际中的 每个用户对应的有评分过的电影的评分*/
val userMoives: RDD[(Int, Iterable[(Int, Int)])] = ratings.map{ case Rating(user, product, rating) => {
(user, product)
}}.groupBy(_._1) val predictedAndTrueForRanking = allRecs.join(userMoives).map{ case( userId, (predicted, actualWithIds) ) => {
//实际的商品编号
val actual = actualWithIds.map(_._2)
(actual.toArray, predicted.toArray)
}}
val rankingMetrics: RankingMetrics[Int] = new RankingMetrics(predictedAndTrueForRanking)
println("使用内置的计算MAP:" + rankingMetrics.meanAveragePrecision)
}
输出结果为:
使用内置的计算MAP:0.0630466936422453
3、推荐模型完整代码
package ml
import org.apache.spark.mllib.evaluation.{RegressionMetrics, RankingMetrics}
import org.apache.spark.mllib.recommendation.{Rating, ALS}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkContext, SparkConf}
import org.jblas.DoubleMatrix
import sql.StreamingExamples
import scala.collection.Map
/**
* 基于Spark MLlib 的推荐算法
* ALS:最小二乘法
*
* @author lwj
* @date 2018/05/04
*/
object Recommend{
/**
* 用于商品推荐
* 通过传入两个向量,返回这两个向量之间的余弦相似度
*
* @param vec1
* @param vec2
* @return
*/
def cosineSimilarity(vec1: DoubleMatrix, vec2: DoubleMatrix): Double = {
vec1.dot(vec2) / (vec1.norm2() * vec2.norm2())
}
/**
* 模型评估
* K值平均准确率(APK)
*
* @param actual
* @param predicted
* @param k
* @return
*/
def avgPrecisionK(actual: Seq[Int], predicted: Seq[Int], k: Int) : Double = {
val predK: Seq[Int] = predicted.take(k)
var score = 0.0
var numHits = 0.0
for ((p, i) <- predK.zipWithIndex){
if (actual.contains(p)){
numHits += 1.0
score += numHits / (i.toDouble + 1.0) //TODO 为什么除以i.toDouble
}
}
if (actual.isEmpty){
1.0
}else{
score / math.min(actual.size, k).toDouble //TODO 为什么是min
}
}
def main(args: Array[String]) {
StreamingExamples.setStreamingLogLevels()
val conf = new SparkConf().setAppName("recommandTest").setMaster("local[*]")
val sc = new SparkContext(conf)
/*用户 电影 评分*/
val rawData: RDD[String] = sc.textFile("file:///E:/spark/ml-100k/u.data")
//去掉时间的字段,rawRatings:Array
val rawRatings = rawData.map(_.split("\\t").take())
//user moive rating
val ratings = rawRatings.map{case Array(user, movie, rating) =>{
Rating(user.toInt, movie.toInt, rating.toDouble)
}}
//电影
val movies: RDD[String] = sc.textFile("file:///E:/spark/ml-100k/u.item")
//电影ID 电影名
val titles: Map[Int, String] = movies.map(_.split("\\|").take()).map(array => (array().toInt, array())).collectAsMap()
/**
* 得到训练的模型
* 注意:50代表我们得到的模型的因子的列的数量,名称叫 因子维数
*/
val model = ALS.train(ratings, , , 0.01)
/**
* 基于用户进行推荐
*/
//用户因子的数量
// println(mode.userFeatures.count())
//商品因子的数量
// println(mode.productFeatures.count())
//查看某个用户对某个商品的预测评分,ALS模型的初始化是随机的,所以产生的结果可能会不同
// println(mode.predict(789, 123))
//为指定的用户推荐 N 个商品
val userID =
val K =
val topKRecs: Array[Rating] = model.recommendProducts(userID, )
// println(topKRecs.mkString("\n"))
//获取指定用户所评价过的电影
val moviesForUser: Seq[Rating] = ratings.keyBy(_.user).lookup()
//打印出指定用户评价最高的10部电影的名称和评分
println("真实的:")
moviesForUser.sortBy(-_.rating).take().map(rating => {
(titles(rating.product),rating.rating)
}).foreach(println)
//打印出推荐给用户的10部电影的名称和评分,和上面的进行比较
println("推荐的:")
topKRecs.map(rating => {
(titles(rating.product),rating.rating)
}).foreach(println)
println("\n-----------------------\n")
/**
* 基于商品进行推荐
*/
/*通过商品ID获得与该商品相似的商品*/
val itemId =
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)
}}
//打印出前N的商品
val topItem: Array[(Int, Double)] = sims.sortBy(-_._2).take( + )
println("与567商品相似的商品:\n" + topItem.mkString("\n") + "\n")
/*校验商品*/
println("给定的商品名称为: " + titles(itemId))
println("相似的商品名称为:")
topItem.slice(, ).foreach(item => println(titles(item._1)))
println("\n-----------------------\n")
/*模型评估*/
/**
* 均方差评估
* 对model全量数据进行评估
*/
// val actualRating: Rating = moviesForUser.take(1)(0)
// val predictedRating: Double = model.predict(789, actualRating.product)
// println("\n真实分:" + actualRating.rating + " 预测分:" + predictedRating)
//格式:(userID,电影)
val userProducts: RDD[(Int, Int)] = ratings.map(rating => (rating.user, rating.product))
//模型推测出的评分信息,格式为:((userID,电影), 推测评分)
val predictions: RDD[((Int, Int), Double)] = model.predict(userProducts).map(rating => ((rating.user, rating.product),rating.rating))
//格式为:((userID,电影), (真实平评分,推测评分))
val ratingsAndPredictions: RDD[((Int, Int), (Double, Double))] = ratings.map(rating => ((rating.user, rating.product), rating.rating))
.join(predictions)
//均方差
val MSE = ratingsAndPredictions.map(rap => math.pow(rap._2._1 - rap._2._2, )).reduce(_+_) / ratingsAndPredictions.count()
println("均方差MSE为: " + MSE)
//均方根误差
val RMSE: Double = math.sqrt(MSE)
println("均方根误差RMSE为: " + RMSE)
/**
* K值平均准确率评估
* 注意:该评估模型是针对对用户感兴趣和回去接触的物品的预测能力
* 也是就是说:这时针对基于用户推荐的 模型的评估
*/
/*计算 单个 指定用户推荐的APK指标*/
val actualMovies: Seq[Int] = moviesForUser.map(_.product)
val predictedMovies: Array[Int] = topKRecs.map(_.product)
val apk10: Double = avgPrecisionK(actualMovies, predictedMovies, )
println("789的APK值为:" + apk10)
/*获取模型中所有商品的 factor,并转换成矩阵*/
val itemFactors: Array[Array[Double]] = model.productFeatures.map{case (id, factor) => factor}.collect()
val itemMatrix: DoubleMatrix = new DoubleMatrix(itemFactors)
// println(itemMatrix.rows, itemMatrix.columns)
/*获得模型中每个用户对应的每个电影的评分*/
val allRecs = model.userFeatures.map{ case(userId, factor) => {
val userVector = new DoubleMatrix(factor)
/**
* socres是一个DoubleMatrix类型,值为1行N列的 Vector
* 为什么可以通过判断这两个矩阵的乘积的大小,从而来判断分数呢?
* 这归根于ALS算法,该算法是将一个 用户-商品 的矩阵 拆分成 用户、商品两个矩阵
* 因此这两个矩阵的乘积就是实际的 分数
*/
val scores = itemMatrix.mmul(userVector)//矩阵和向量的乘积,求出每个用户的分数
//根据评分倒数排序
val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1)
//(score, itemId)
val recommendIds = sortedWithId.map(_._2 + ).toSeq
//返回用户 和 各个商品评分的倒数的值 的 tuple: (userId,(sorce, itemId))
(userId, recommendIds)
}}
/*获取实际中的 每个用户对应的有评分过的电影的评分*/
val userMoives: RDD[(Int, Iterable[(Int, Int)])] = ratings.map{ case Rating(user, product, rating) => {
(user, product)
}}.groupBy(_._1)
val MAPK = allRecs.join(userMoives).map{ case( userId, (predicted, actualWithIds) ) => {
//实际的商品编号
val actual = actualWithIds.map(_._2).toSeq
avgPrecisionK(actual, predicted, )
}}.reduce(_ + _) / allRecs.count
println("MAPK:" + MAPK)
println("\n-----------------------\n")
/**
* 使用MLlib内置的评估器
*/
/*RMSE 和 MSE*/
val predictedAndTrue: RDD[(Double, Double)] = ratingsAndPredictions.map{ case((userID, product),(actual, predict)) => (actual, predict)}
val regressionMetrics: RegressionMetrics = new RegressionMetrics(predictedAndTrue)
println("使用内置的计算MSE:" + regressionMetrics.meanSquaredError)
println("使用内置的计算RMSE:" + regressionMetrics.rootMeanSquaredError)
/*MAPK*/
val predictedAndTrueForRanking = allRecs.join(userMoives).map{ case( userId, (predicted, actualWithIds) ) => {
//实际的商品编号
val actual = actualWithIds.map(_._2)
(actual.toArray, predicted.toArray)
}}
val rankingMetrics: RankingMetrics[Int] = new RankingMetrics(predictedAndTrueForRanking)
println("使用内置的计算MAP:" + rankingMetrics.meanAveragePrecision)
}
}
SparkML之推荐引擎(二)---推荐模型评估的更多相关文章
- SparkML之推荐引擎(一)---电影推荐
本文将使用 SparkML 来构建推荐引擎. 推荐引擎算法大致分为 基于内容的过滤.协同过滤.矩阵分解,本文将使用基于属于矩阵分解的 最小二乘法 算法来构建推荐引擎. 对于推荐引擎模块这里将分为两篇文 ...
- AI时代:推荐引擎正在塑造人类
We shape our tools and afterwards our tools shape us. ------Marshall McLuhan 麦克卢汉说:"我们塑造了工具,反过来 ...
- [转] 基于 Apache Mahout 构建社会化推荐引擎
来源:http://www.ibm.com/developerworks/cn/java/j-lo-mahout/index.html 推荐引擎简介 推荐引擎利用特殊的信息过滤(IF,Informat ...
- 基于 Apache Mahout 构建社会化推荐引擎
基于 Apache Mahout 构建社会化推荐引擎 http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp 推荐引擎利用特殊的 ...
- 基于Spark ALS构建商品推荐引擎
基于Spark ALS构建商品推荐引擎 一般来讲,推荐引擎试图对用户与某类物品之间的联系建模,其想法是预测人们可能喜好的物品并通过探索物品之间的联系来辅助这个过程,让用户能更快速.更准确的获得所需 ...
- 机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源
机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源 相关主题 在信息时代,公司和个人的成功越来越依赖于迅速 ...
- 从源代码剖析Mahout推荐引擎
转载自:http://blog.fens.me/mahout-recommend-engine/ Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, Pi ...
- 转】从源代码剖析Mahout推荐引擎
原博文出自于: http://blog.fens.me/mahout-recommend-engine/ 感谢! 从源代码剖析Mahout推荐引擎 Hadoop家族系列文章,主要介绍Hadoop家族产 ...
- Spark机器学习3·推荐引擎(spark-shell)
Spark机器学习 准备环境 jblashttps://gcc.gnu.org/wiki/GFortranBinaries#MacOS org.jblas:jblas:1.2.4-SNAPSHOT g ...
随机推荐
- InputStream为什么不能被重复读取?
最近上传阿里云的时候同一个文件上传两个服务地址,第一个文件读取以后第二个再去读取就拿不到了.代码如下: //内网上传OSS获取key值 String ossKey = OSSClientUtil.ge ...
- 修改Egret引擎代码的方法
某些情况下,我们需要修改Egret引擎的源码,我们可以在源码目录(一般如下:xxx\Egret\engine\x.x.x\src\egret)下直接修改ts代码. 在对应的项目下打开CMD命令行,输入 ...
- 一分钟内搭建全web的API接口神器json-server详解
JSON-Server 是一个 Node 模块,运行 Express 服务器,你可以指定一个 json 文件作为 api 的数据源. 安装json-server npm install -g json ...
- Filter需要配置多个url-pattern
最近做项目遇到一个Filter需要配置多个url-pattern,上网查了下资料,经测试,现总结下 filter-mapping元素位于web.xml文件中filter元素之后serlvet元素之前. ...
- Spring事务的5种隔离级别和7种传播性
隔离级别 isolation,5 种: ISOLATION_DEFAULT,ISOLATION_READ_UNCOMMITTED,ISOLATION_READ_COMMITTED,ISOLATION_ ...
- 微信小程序开发填坑指南V1
近期用了一星期的时间,开发了一个小程序.小程序名称是:小特Jarvis,取自钢铁侠的管家. 后台采用C#编写,WebAPI接口.其实开发时间并不多,小程序本身提供的API,相比公众号的API来说,已经 ...
- AOP 技术原理——代理模式全面总结
前言 非常重要的一个设计模式,也很常见,很多框架都有它的影子.定义就不多说了.两点: 1.为其它对象提供一个代理服务,间接控制对这个对象的访问,联想 Spring 事务机制,在合适的方法上加个 tra ...
- My google script which based on Google Sheet and Form
My google script which based on Google Sheet and Form // get sheet data function getSpreadsheetData( ...
- MonoDevelop ctrl + ' 不能定位正确的unity文档
Just Do This I had the same problem in MonoDevalop, but the url in it cannot be changed. So I tried ...
- linux应用以及常用命令
各目录作用"/" :Linux文件系统的入口.也是最高一级的目录."/bin":基本系统所需要的命令,功能和"/usr/bin"类似,这个目 ...