本文内容和代码是接着上篇文章来写的,推荐先看一下哈~ 
我们上一篇文章是写了电影推荐的实现,但是推荐内容是否合理呢,这就需要我们对模型进行评估 
针对推荐模型,这里根据 均方差 和 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之推荐引擎(二)---推荐模型评估的更多相关文章

  1. SparkML之推荐引擎(一)---电影推荐

    本文将使用 SparkML 来构建推荐引擎. 推荐引擎算法大致分为 基于内容的过滤.协同过滤.矩阵分解,本文将使用基于属于矩阵分解的 最小二乘法 算法来构建推荐引擎. 对于推荐引擎模块这里将分为两篇文 ...

  2. AI时代:推荐引擎正在塑造人类

    We shape our tools and afterwards our tools shape us. ------Marshall McLuhan 麦克卢汉说:"我们塑造了工具,反过来 ...

  3. [转] 基于 Apache Mahout 构建社会化推荐引擎

    来源:http://www.ibm.com/developerworks/cn/java/j-lo-mahout/index.html 推荐引擎简介 推荐引擎利用特殊的信息过滤(IF,Informat ...

  4. 基于 Apache Mahout 构建社会化推荐引擎

    基于 Apache Mahout 构建社会化推荐引擎 http://www.ibm.com/developerworks/cn/views/java/libraryview.jsp 推荐引擎利用特殊的 ...

  5. 基于Spark ALS构建商品推荐引擎

    基于Spark ALS构建商品推荐引擎   一般来讲,推荐引擎试图对用户与某类物品之间的联系建模,其想法是预测人们可能喜好的物品并通过探索物品之间的联系来辅助这个过程,让用户能更快速.更准确的获得所需 ...

  6. 机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源

      机器学习 101 Mahout 简介 建立一个推荐引擎 使用 Mahout 实现集群 使用 Mahout 实现内容分类 结束语 下载资源 相关主题   在信息时代,公司和个人的成功越来越依赖于迅速 ...

  7. 从源代码剖析Mahout推荐引擎

    转载自:http://blog.fens.me/mahout-recommend-engine/ Hadoop家族系列文章,主要介绍Hadoop家族产品,常用的项目包括Hadoop, Hive, Pi ...

  8. 转】从源代码剖析Mahout推荐引擎

    原博文出自于: http://blog.fens.me/mahout-recommend-engine/ 感谢! 从源代码剖析Mahout推荐引擎 Hadoop家族系列文章,主要介绍Hadoop家族产 ...

  9. Spark机器学习3·推荐引擎(spark-shell)

    Spark机器学习 准备环境 jblashttps://gcc.gnu.org/wiki/GFortranBinaries#MacOS org.jblas:jblas:1.2.4-SNAPSHOT g ...

随机推荐

  1. Android Studio创建JAR/AAR库

    [时间:2017-09] [状态:Open] [关键词:Android,Android Studio,gradle,jar,aar,library] 0 引言 最近在工作中遇到了升级Android S ...

  2. 判断当前viewcontroller是push还是present的方式显示的

    网上的姿势,反正我用着不管用 最正确的姿势 NSArray *viewcontrollers = self.navigationController.viewControllers; if (view ...

  3. 《FPGA全程进阶---实战演练》之搞定阻抗匹配

    笔者最近几天在做视频采集板卡时,视频显示端打算采用 USB2.0接口+上位机 显示,其中USB需要做阻抗匹配.通常情况下USB的阻抗值需要做到90Ω±10%.下面就讲解一下关于阻抗匹配的知识,哪里说得 ...

  4. JS截取字符串多余的为...

    如果截取字符串前几位,多余的用...表示该怎样做呢? JS代码 /** * js截取字符串,中英文都能用 * @param str:需要截取的字符串 * @param len: 需要截取的长度 */ ...

  5. 循环神经网络(Recurrent Neural Networks, RNN)介绍

    目录 1 什么是RNNs 2 RNNs能干什么 2.1 语言模型与文本生成Language Modeling and Generating Text 2.2 机器翻译Machine Translati ...

  6. python -- Pythonic

    所谓Pythonic,就是极具Python特色的Python代码(明显区别于其它语言的写法的代码) 总结如下: 两变量的内容交换 Python:a,b = b,a 非Python:t=a;a=b;b= ...

  7. YII2 使用phpexcel(干货)

    参考:http://www.cnblogs.com/xiaocongjiejie/p/5106249.html http://www.cnblogs.com/xiaocongjiejie/p/5106 ...

  8. git rebase 的使用

    rebase 在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase. 在本节中我们将学习什么是“rebase”,怎样使用“rebase”,并将展示该操作的惊艳之处,以及指 ...

  9. jedis中scan的实现

    我的版本说明: redis服务端版本:redis_version:2.8.19 jedis: <dependency> <groupId>redis.clients</g ...

  10. Python学习之解决python下载第三方依赖速度慢的问题

    原因:通过pip命令直接下载,一般下载的资源存放在国外的服务器上,导致下载速度慢.下载过程中报错: 解决方法:通过pip的命令参数,从国内的下载源,国外的替换为国内的镜像,进行目标包的下载: 具体参数 ...