本文内容和代码是接着上篇文章来写的,推荐先看一下哈~ 
我们上一篇文章是写了电影推荐的实现,但是推荐内容是否合理呢,这就需要我们对模型进行评估 
针对推荐模型,这里根据 均方差 和 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. how-can-i-see-the-size-of-files-and-directories-in-linux

    https://stackoverflow.com/questions/11720079/how-can-i-see-the-size-of-files-and-directories-in-linu ...

  2. 使用import scope解决maven继承(单)问题<转>

    测试环境 maven 3.3.9 想必大家在做SpringBoot应用的时候,都会有如下代码: <parent> <groupId>org.springframework.bo ...

  3. Win7 SP1 64位 旗舰版 IE8 快速稳定 纯净优化 无人值守 自动激活 20180604

    一.系统特色 1.采用微软原版旗舰版定制而成. 2.优化系统服务,关闭一些平时很少使用的服务. 3.精简掉一些无用的东西. 4.系统全程离线制作,不包含任何恶意插件,放心使用. 5.右下角时间加上星期 ...

  4. 把去世的亲友做成聊天机器人,就能让生者慰藉、死者安息吗? - 看了 寻梦历险记,我的回答是 :是的,他/她永远活在我们心里 www.iremember.com.cn

    AppU 如今的我们,之所以离不开手机和互联网,是因为它们确实可以带来信息和方便,让我们轻松记录并分享自己的生活体验,留下了充满回忆的各种文字.语音,各种图片.视频…… 但你有没有想过,当我们逝世时, ...

  5. SAP FICO 凭证导入接口 数据xml格式

    接口传入参数说明 <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xm ...

  6. 第四百一十五节,python常用排序算法学习

    第四百一十五节,python常用排序算法学习 常用排序 名称 复杂度 说明 备注 冒泡排序Bubble Sort O(N*N) 将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮 ...

  7. Netty:option和childOption参数设置说明

    Channel配置参数 (1).通用参数 CONNECT_TIMEOUT_MILLIS :   Netty参数,连接超时毫秒数,默认值30000毫秒即30秒. MAX_MESSAGES_PER_REA ...

  8. 为何GET只发一次TCP连接,POST发两次TCP连接

    GET和POST是HTTP请求的两种基本方法,要说他们的区别,接触过WEB开发的人都能说出一二. 最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数. 你可能自己 ...

  9. samba4.4security配置

    security=share在新版中已经被废弃了把security = share改为 security = user map to guest = Bad User 就可以了 [global] wo ...

  10. 【转载】C++中替代sprintf的std::ostringstream输出流详解

    一.简单介绍 ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中.ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓 ...