实践(七)

【Task7】实践

计算每个content的CTR

数据集下载:链接:https://pan.baidu.com/s/1YDvBWp35xKLg5zsysEjDGA 提取码:rpgs

【选做】 使用Spark实现ALS矩阵分解算法

movielen 数据集:http://files.grouplens.org/datasets/movielens/ml-100k.zip

基于ALS矩阵分解算法的Spark推荐引擎实现

使用Spark分析Amazon DataSet(实现 Spark LR、Spark TFIDF)

数据集:http://jmcauley.ucsd.edu/data/amazon/

preprocess

Spark LR

Spark TFIDF

1. 计算每个content的CTR

CTR(Click-Through-Rate)即点击通过率,是互联网广告常用的术语,指网络广告(图片广告/文字广告/关键词广告/排名广告/视频广告等)的点击到达率,即该广告的实际点击次数(严格的来说,可以是到达目标页面的数量)除以广告的展现量(Show content)。

计算公式
计算公式为CTR=实际点击次数/展示量,即 Click / Show content。
CTR:点击通过率,Click-Through-Rate (点击通过比率)

可以先用笔记本打开content_list_id.txt文件看下大致情况,数据由三列组成,在这里。点击率可以用content_id的个数除以content_list的长度得到。
在这里插入图片描述

import pandas as pd
data = pd.read_csv("content_list_id.txt", sep="\t")
data['content_list_len']=data['content_list'].apply(lambda x:len(x.split(',')))
data['content_id_len']=data['content_id'].apply(lambda x:len(x.split(',')))
data['ctr'] = data['content_id_len'] / data['content_list_len']

2. 使用Spark实现ALS矩阵分解算法

大数据基础【Task7】实践

第三篇:一个Spark推荐系统引擎的实现

使用Spark实现ALS矩阵分解算法主要有以下几个步骤:

  1. 提取有效特征
  2. 训练推荐模型
  3. 使用ALS推荐模型
  4. 物品推荐
  5. 推荐效果评估
  6. 小结

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

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

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

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

1. 提取有效特征

  1. 首先,启动spark-shell。
spark-shell
  1. 载入用户对影片的评级数据:

这里的file://代表是本地数据,如果不加这个前缀就会从hdfs上读取数据。

// 载入评级数据
scala> val rawData = sc.textFile("file:///usr/local/hadoop/test/code/u.data")
rawData: org.apache.spark.rdd.RDD[String] = file:///usr/local/hadoop/test/code/u.data MapPartitionsRDD[1] at textFile at <console>:24

// 展示一条记录
scala> rawData.first()

res0: String = 196	242	3	881250949
  1. 切分记录并返回新的RDD:

这里取了前三个字段,即user id、item id、rating。

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

res1: Array[String] = Array(196, 242, 3)
  1. 接下来需要将评分矩阵RDD转化为Rating格式的RDD:
// 导入rating类
scala> import org.apache.spark.mllib.recommendation.Rating
// 将评分矩阵RDD中每行记录转换为Rating类型
scala> val ratings = rawRatings.map { case Array(user, movie, rating) => Rating(user.toInt, movie.toInt, rating.toDouble) }

scala> ratings.first()

res2: org.apache.spark.mllib.recommendation.Rating = Rating(196,242,3.0)

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

2. 训练推荐模型

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

  • rank:对应的是隐因子的个数,这个值设置越高越准,但是也会产生更多的计算量。一般将这个值设置为10-200;
  • iterations:对应迭代次数,一般设置个10就够了;
  • lambda:该参数控制正则化过程,其值越高,正则化程度就越深。一般设置为0.01。
  1. 首先,执行以下代码,启动ALS训练:
// 导入ALS推荐系统算法包
scala> import org.apache.spark.mllib.recommendation.ALS
// 启动ALS矩阵分解
scala> val model = ALS.train(ratings, 50, 10, 0.01)

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

相对其他步骤,训练耗费的时间最多。

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

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

scala> model.userFeatures.first

res4: (Int, Array[Double]) = (1,Array(0.49355196952819824, -0.22464631497859955, 0.26314327120780945, -0.5041911005973816, 
-0.17332540452480316, -0.09947088360786438, -0.2455645054578781, -0.21306976675987244, 0.07296579331159592, -0.315679132938385, 
0.26139432191848755, 0.5458331108093262, 0.38469621539115906, 0.11748316884040833, -0.15589895844459534, 0.3157919943332672, 
-0.2610902488231659, -0.356866717338562, -0.3456942141056061, 0.5182305574417114, 0.008591878227889538, -0.07697590440511703, 
0.06339816749095917, -0.13890786468982697, 0.2576437294483185, -0.01784001663327217, 0.3721158802509308, -0.08425401151180267, 
-0.7575527429580688, -0.2158384621143341, 0.4707823097705841, 0.2881649434566498, -0.16873227059841156, -0.34343791007995605, 
0.1242368221282959, -0.19271430373191833, ...

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

3. 使用ALS推荐模型

  1. 预测用户789对物品123的评分:
scala> val predictedRating = model.predict(789,123)

predictedRating: Double = 3.3851580857036034
  1. 为用户789推荐前10个物品:
scala> val userId = 789
userId: Int = 789

scala> val K = 10
K: Int = 10

// 获取推荐列表
scala> val topKRecs = model.recommendProducts(userId, K)

topKRecs: Array[org.apache.spark.mllib.recommendation.Rating] = Array(Rating(789,56,5.721679562476089), 
Rating(789,134,5.678340203670223), Rating(789,340,5.503186535270071), Rating(789,663,5.402873018755409), 
Rating(789,346,5.268079473217043), Rating(789,246,5.258068771694328), Rating(789,298,5.174696541949913), 
Rating(789,347,5.099838202988733), Rating(789,654,5.030737723453546), Rating(789,211,5.019128792654539))

// 打印推荐列表
scala> println(topKRecs.mkString("\n"))

Rating(789,56,5.721679562476089)
Rating(789,134,5.678340203670223)
Rating(789,340,5.503186535270071)
Rating(789,663,5.402873018755409)
Rating(789,346,5.268079473217043)
Rating(789,246,5.258068771694328)
Rating(789,298,5.174696541949913)
Rating(789,347,5.099838202988733)
Rating(789,654,5.030737723453546)
Rating(789,211,5.019128792654539)
  1. 初步检验推荐效果

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

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

// 导入电影数据集
scala> val movies = sc.textFile("File:///usr/local/lily/task4/u.item")
// 建立电影id - 电影名字典
scala> val titles = movies.map(line => line.split("\\|").take(2)).map(array => (array(0).toInt, array(1))).collectAsMap()

titles: scala.collection.Map[Int,String] = Map(137 -> Big Night (1996), 891 -> Bent (1997), 
550 -> Die Hard: With a Vengeance (1995), 1205 -> Secret Agent, The (1996), 146 -> Unhook the Stars (1996), 
864 -> My Fellow Americans (1996), 559 -> Interview with the Vampire (1994), 218 -> Cape Fear (1991), 568 -> Speed (1994), 
227 -> Star Trek VI: The Undiscovered Country (1991), 765 -> Boomerang (1992), 1115 -> Twelfth Night (1996), 
774 -> Prophecy, The (1995), 433 -> Heathers (1989), 92 -> True Romance (1993), 1528 -> Nowhere (1997), 
846 -> To Gillian on Her 37th Birthday (1996), 1187 -> Switchblade Sisters (1975), 
1501 -> Prisoner of the Mountains (Kavkazsky Plennik) (1996), 442 -> Amityville Curse, The (1990), 
1160 -> Love! Valour! Compassion! (1997), 101 -> Heavy Metal (1981), 1196 -> Sa...

// 查看id为123的电影名
scala> titles(123)

res6: String = Frighteners, The (1996)

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

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

// 建立用户名-其他RDD,并仅获取用户789的记录
scala> val moviesForUser = ratings.keyBy(_.user).lookup(789)

moviesForUser: Seq[org.apache.spark.mllib.recommendation.Rating] = WrappedArray(Rating(789,1012,4.0), Rating(789,127,5.0), 
Rating(789,475,5.0), Rating(789,93,4.0), Rating(789,1161,3.0), Rating(789,286,1.0), Rating(789,293,4.0), Rating(789,9,5.0), 
Rating(789,50,5.0), Rating(789,294,3.0), Rating(789,181,4.0), Rating(789,1,3.0), Rating(789,1008,4.0), Rating(789,508,4.0), 
Rating(789,284,3.0), Rating(789,1017,3.0), Rating(789,137,2.0), Rating(789,111,3.0), Rating(789,742,3.0), Rating(789,248,3.0), 
Rating(789,249,3.0), Rating(789,1007,4.0), Rating(789,591,3.0), Rating(789,150,5.0), Rating(789,276,5.0), Rating(789,151,2.0), 
Rating(789,129,5.0), Rating(789,100,5.0), Rating(789,741,5.0), Rating(789,288,3.0), Rating(789,762,3.0), Rating(789,628,3.0), 
Rating(789,124,4.0))

// 获取用户评分最高的10部电影,并打印电影名和评分值
scala> moviesForUser.sortBy(-_.rating).take(10).map(rating => (titles(rating.product), rating.rating)).foreach(println)

(Godfather, The (1972),5.0)
(Trainspotting (1996),5.0)
(Dead Man Walking (1995),5.0)
(Star Wars (1977),5.0)
(Swingers (1996),5.0)
(Leaving Las Vegas (1995),5.0)
(Bound (1996),5.0)
(Fargo (1996),5.0)
(Last Supper, The (1995),5.0)
(Private Parts (1997),4.0)

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

scala> val topKRecs = model.recommendProducts(789, 10)
scala> topKRecs.map(rating => (titles(rating.product),rating.rating)).foreach(println)

(Manhattan (1979),5.832914304933656)
(Duck Soup (1933),5.570379610662419)
(Pulp Fiction (1994),5.504039044943547)
(Big Sleep, The (1946),5.483157516029839)
(Citizen Kane (1941),5.343982611556479)
(Maltese Falcon, The (1941),5.334708918288964)
(L.A. Confidential (1997),5.286353962843096)
(Harold and Maude (1971),5.244546353446239)
(Paths of Glory (1957),5.235629127626481)
(Touch of Evil (1958),5.180871609876424)

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

4. 物品推荐

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

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

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

由于jblas库没有,先要下载该库,放到spark/jars目录下,下载地址:

http://pan.baidu.com/s/1o8w6Wem

参考:https://blog.csdn.net/chun19920827/article/details/74332178

// 导入jblas库中的矩阵类
import org.jblas.DoubleMatrix
// 定义相似度函数
def cosineSimilarity(vec1: DoubleMatrix, vec2: DoubleMatrix): Double = {
    vec1.dot(vec2) / (vec1.norm2() * vec2.norm2())
}
  1. 接下来获取物品(本例以物品567为例)的因子特征向量,并将它转换为jblas的矩阵格式:
// 选定id为567的电影
scala> val itemId = 567
// 获取该物品的隐因子向量
scala> val itemFactor = model.productFeatures.lookup(itemId).head
// 将该向量转换为jblas矩阵类型
scala> val itemVector = new DoubleMatrix(itemFactor)
  1. 计算物品567和所有其他物品的相似度:
// 计算电影567与其他电影的相似度
scala> val sims = model.productFeatures.map{ case (id, factor) => 
    val factorVector = new DoubleMatrix(factor)
    val sim = cosineSimilarity(factorVector, itemVector)
    (id, sim)
}
// 获取与电影567最相似的10部电影
scala> val sortedSims = sims.top(10)(Ordering.by[(Int, Double), Double] { case (id, similarity) => similarity })
// 打印结果
scala> println(sortedSims.mkString("\n"))

(567,1.0)
(219,0.7708029243146022)
(1376,0.7245712144554307)
(195,0.7213879610509834)
(413,0.7161206343271771)
(184,0.7119537102065846)
(181,0.7074297269599691)
(250,0.7070691619306613)
(825,0.7050879435249158)
(670,0.7036005054982796)

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

  1. 查看推荐结果:
// 打印电影567的影片名
scala> println(titles(567))
Wes Craven's New Nightmare (1994)

// 获取和电影567最相似的11部电影(含567自己)
scala> val K=10
K: Int = 10

scala> val sortedSims2 = sims.top(K + 1)(Ordering.by[(Int, Double), Double] { case (id, similarity) => similarity })
sortedSims2: Array[(Int, Double)] = Array((567,1.0), (219,0.7708029243146022), (1376,0.7245712144554307), (195,0.7213879610509834), 
(413,0.7161206343271771), (184,0.7119537102065846), (181,0.7074297269599691), (250,0.7070691619306613), (825,0.7050879435249158), 
(670,0.7036005054982796), (76,0.7033096049883402))

// 再打印和电影567最相似的10部电影
scala> sortedSims2.slice(1, 11).map{ case (id, sim) => (titles(id), sim) }.mkString("\n")
res2: String =
(Nightmare on Elm Street, A (1984),0.7708029243146022)
(Meet Wally Sparks (1997),0.7245712144554307)
(Terminator, The (1984),0.7213879610509834)
(Tales from the Crypt Presents: Bordello of Blood (1996),0.7161206343271771)
(Army of Darkness (1993),0.7119537102065846)
(Return of the Jedi (1983),0.7074297269599691)
(Fifth Element, The (1997),0.7070691619306613)
(Arrival, The (1996),0.7050879435249158)
(Body Snatchers (1993),0.7036005054982796)
(Carlito's Way (1993),0.7033096049883402)

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

5. 推荐效果评估

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

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

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

PS:记得先要导入jblas库。

  1. 首先计算MSE和RMSE:
// 创建用户id-影片id RDD
val usersProducts = ratings.map{ case Rating(user, product, rating)  => (user, product)}
// 创建(用户id,影片id) - 预测评分RDD
val predictions = model.predict(usersProducts).map{
    case Rating(user, product, rating) => ((user, product), rating)
}
// 创建用户-影片实际评分RDD,并将其与上面创建的预测评分RDD join起来
val ratingsAndPredictions = ratings.map{
    case Rating(user, product, rating) => ((user, product), rating)
}.join(predictions)
 
// 导入RegressionMetrics类
import org.apache.spark.mllib.evaluation.RegressionMetrics
// 创建预测评分-实际评分RDD
val predictedAndTrue = ratingsAndPredictions.map { case ((user, product), (actual, predicted)) => (actual, predicted) }
// 创建RegressionMetrics对象
val regressionMetrics = new RegressionMetrics(predictedAndTrue)
 
// 打印MSE和RMSE
scala> println("Mean Squared Error = " + regressionMetrics.meanSquaredError)
Mean Squared Error = 0.08528673495619758

scala> println("Root Mean Squared Error = " + regressionMetrics.rootMeanSquaredError)
Root Mean Squared Error = 0.29203892712478857

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

  1. 计算MAPK:
// 创建电影隐因子RDD,并将它广播出去
val itemFactors = model.productFeatures.map { case (id, factor) => factor }.collect()
scala> val itemFactors = model.productFeatures.map { case (id, factor) => factor }.collect()
itemFactors: Array[Array[Double]] = Array(Array(0.23932668566703796, -0.2555418312549591, 0.5615943074226379, 0.608315646648407, 
-0.16954585909843445, -0.43999677896499634, 0.03769494965672493, -1.8908671140670776...
scala> val itemMatrix = new DoubleMatrix(itemFactors)
itemMatrix: org.jblas.DoubleMatrix = [0.239327, -0.255542, 0.561594, 0.608316, -0.169546, -0.439997, 0.037695, -1.890867...

 
// 创建用户id - 推荐列表RDD
val allRecs = model.userFeatures.map{ case (userId, array) => 
  val userVector = new DoubleMatrix(array)
  val scores = imBroadcast.value.mmul(userVector)
  val sortedWithId = scores.data.zipWithIndex.sortBy(-_._1)
  val recommendedIds = sortedWithId.map(_._2 + 1).toSeq
  (userId, recommendedIds)
}
 
// 创建用户 - 电影评分ID集RDD
val userMovies = ratings.map{ case Rating(user, product, rating) => (user, product) }.groupBy(_._1)
 
// 导入RankingMetrics类
import org.apache.spark.mllib.evaluation.RankingMetrics
// 创建实际评分ID集-预测评分ID集 RDD
val predictedAndTrueForRanking = allRecs.join(userMovies).map{ case (userId, (predicted, actualWithIds)) => 
  val actual = actualWithIds.map(_._2)
  (predicted.toArray, actual.toArray)
}
// 创建RankingMetrics对象
val rankingMetrics = new RankingMetrics(predictedAndTrueForRanking)
// 打印MAPK
scala> println("Mean Average Precision = " + rankingMetrics.meanAveragePrecision)
Mean Average Precision = 0.19938697526328025

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

小结

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

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

3. 使用Spark分析Amazon DataSet(实现 Spark LR、Spark TFIDF)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值