在进行Spark ALS算法进行试验的时候发现模型对所有用户其推荐结果是一样的,即针对所有用户建模得到的模型对其推荐的项目是一样的,只是评分有比较小的差异。下面就分3个部分来进行分析,分别是实验过程及结果描述,ALS算法原理,问题分析及解决。
// 设置日志级别 sc.setLogLevel(“WARN”) // 导入必要的包 import org.apache.spark.mllib.recommendation._ // 加载movies 数据到map val movies = sc.textFile("/user/root/als/movies.dat").map{line => val fields = line.split("::") ; (fields(0).toInt,fields(1))}.collect.toMap // 加载评分数据 val ratings = sc.textFile("/user/root/als/ratings.dat").map{line => val fields = line.split("::");val rating = Rating(fields(0).toInt,fields(1).toInt,fields(2).toDouble);val timestamp = fields(3).toLong %10; (timestamp,rating)} // 输出统计信息 println(ratings.count) println(ratings.map(_._2.user).distinct.count) println(ratings.map(_._2.product).distinct.count)
4. 分割数据到训练集和测试集,其代码如下所示:
// 分训练集 val training = ratings.filter(x=>x._1<6).values.cache() // 分测试集 val test = ratings.filter(x=>x._1>=6).values.cache() training.count test.count
5. 设置参数,进行建模,其代码如下所示:
val rank = 10 val lambda = 10.0 val iter = 20 val model = ALS.train(training,rank,iter,lambda)
6. 编写均方根误差函数,并对测试数据集求其均方误差根,其代码如下所示:
// 建立均方根误差函数 import org.apache.spark.rdd.RDD def computeRMSE(model:MatrixFactorizationModel, data:RDD[Rating]): Double = { val usersProducts = data.map(x=>(x.user,x.product)) val ratingsAndPredictions = data.map{case Rating(user,product,rating)=>((user,product),rating)}.join(model.predict(usersProducts).map{case Rating(user,product,rating)=>((user,product),rating)}).values ; math.sqrt(ratingsAndPredictions.map(x=>(x._1-x._2)*(x._1-x._2)).mean())} // 使用测试集,计算均方根误差 val testRMSE = computeRMSE(model,test)得到的均方根误差为:
7. 预测某用户的推荐电影,并格式化输出,其代码如下所示:
val userid = 1 // 求得userid对应用户评分过的所有电影 val user1RatedMovieIds = ratings.filter(_._2.user==userid).map(_._2.product).collect.toSeq // 过滤得到用户userid对应的潜在推荐电影 val cands = sc.parallelize(movies.keys.filter(!user1RatedMovieIds.contains(_)).toSeq) // 使用模型来对潜在推荐电影进行评分,并按照预测评分进行排序,取前10 val recommendations = model.predict(cands.map((userid,_))).collect.sortBy(- _.rating).take(10) var i =1 println(“用户”+userid+”的推荐结果为:”) recommendations.foreach{rec => println("%2d".format(i)+": "+movies(rec.product)+", predictRating: "+rec.rating);i+=1}
8.经过如上的代码调用,其推荐结果为:
9. 修改第7步的userid为2,3,看到其推荐结果是一样的,只是预测评分是不一样的,这也就是如题所述的问题了。
最后,Spark als 中的两处引用错误:
另外,Spark ALS对新用户推荐思路如下:
在分析上面的问题前,先了解下ALS算法的原理,这里只是大概分析下,如果想深入了解这个算法,请自行查找相关资料,进行了解。
注意:电影评分最高5分,最低1分。
如上表所示,是一个数据集,数据集中一共有5个电影、4个用户,并且这4个用户对5个电影中的某些电影看过,并给过评分,比如用户Tom对《釜山行》评分为5分,对《潜伏3》评分为1分,但是没有看过《招魂2》,所以没有显示评分,为“?”。
现在,假如说我们给电影定义两个标签,比如“动作”、“恐怖”,并且已经有人(比如电影影评人等)帮我们给每个电影根据这两个标签打过分,那么现在就会有这样的一个数据,如下表所示。
根据上面的评分,如果我们可以构造这样的一个列向量Θ,满足下面的公式:
那么,我们就可以预测上面的空格部分的数值了(注意4,5,4和1中间有个空格)。当然,上面是针对Tom用户来说的,如果我们针对Kate、John、Fansy都可以得到这样的一个列向量,那么针对所有用户就可以得到下面的一个二维矩阵了。
其中,、代表用户Tom的特征值,其他用户以此类推。所以,其实这里我们也可以理解为使用了、就完全可以代表用户Tom了,、就可以完全代表用户Fansy了。
注意:这里需要说明一点是,如果根据不完整的用户电影评分列表以及电影的标签评分,我们是可以在一定的误差范围内推算出Θ矩阵的。
好了,那么其实我们就已经完成了这个算法了。但是,好像有什么地方不对?影评人对这些电影的评价是否完全一致?是否影评人可以看完所有电影,并给这些电影画上标签,并给出评分?可以想象,这是一个很难完成的任务(当然,如果有时间也是可以完成的)。那怎么办呢?
一种很自然的想法是可以随机电影标签评分(可以理解为电影特征矩阵),然后求得Θ矩阵(可以理解为用户特征矩阵)。当然,这时就会有很大的误差,所以就需要根据我们推导得到的Θ矩阵,再反推回电影特征矩阵。以此反复循环,就可以保证我们预测的电影评分和实际电影评分(用户已经评价过的电影评分)的全局均方根误差在一定阈值内。这个时候我们就可以说算法建模完成,并且得到了算法模型的参数:电影特征矩阵和用户特征矩阵(本例中是2维的,这个也是这个算法的参数之一,rank)。接着,就可以根据这两个矩阵针对用户还没有评分过的电影进行评分预测,进而得到可以推荐给用户的电影(根据预测评分大小取出评分top10即可)。
这个问题可以从下面两个方面来看:
当然,这个问题也可以从另外的角度来分析。如果预测评分基本都是接近0的,那么用户特征向量,项目特征向量是否也是接近0?那怎么看呢?
在得到模型后,可以通过下面的方法查看某个用户或某个电影的特征向量,代码如下:
看到的结果也可以说,确实,不管是用户特征向量或者项目特征向量,其值都是非常接近0的,这肯定是有问题的,一般是介于-1~1之间的某个小数,但是肯定不是接近0的一个数。所以,如果你使用Spark ALS算法,怎么去评价这个算法的优劣就可以从这个几个方面来分析(当然,也可以使用准确率以及召回率来评判)。
问题的解决很简单,一行代码即可搞定:
val model = ALS.train(trainging,10,20,0.01);
即修改lambda的值为 0.01即可。修改完成后,再来看看上面分析的两个问题。
1. 测试数据集均方根误差:
2. 模型的用户特征向量以及项目特征向量及最后的推荐结果
针对用户1,2,3,其推荐结果为:
从上面的结果可以看出,不仅各个用户推荐的电影不一样 了,而且其预测评分也不是接近0;另外,其测试数据集的误差仅为0.9,和之前的3.8相差好几倍。
那是怎么想到要这样设置的呢?可以那就要在了解算法的基础上来设置此参数;
分享,成长,快乐
脚踏实地,专注
转载请注明blog地址:http://blog.csdn.net/fansy1990