【推荐系统】电影推荐系统(二)


前言

本文主要阐述如何将电影评价矩阵通过ALS算法计算出电影特征,请大家参考。


一、ALS算法简介

      ALS是交替最小二乘法的简称,是2008年以来,用的比较多的协同过滤算法。它已经集成到Spark的Mllib库中,使用起来比较方便。
      这里可以想象一下,每个人的性格爱好可以认为是一个抽象的模型,每个人的模型都有自己的一个特点。因此,每个人对于商品的评价都有自己的一套规律,ALS算法就是可以通过这些已有的评价数据中,尽可能的计算出一个误差较小的模型,来预测对未评价商品的评分。
      ALS算法的核心就是假设:打分矩阵是近似低跌的,通过用户u成行,商品v成列的方式,形成一个u * v的打分矩阵A。由于用户和商品的数量非常多,所以形成的矩阵A也非常庞大,这样在处理起来比较困难,所以矩阵A可以通过两个小矩阵的乘积来表示,如下图所示:
在这里插入图片描述
      实际情况中k远小于u和v的值, 一般在50-200之,具体取值是什么不重要,因为这个只是为了最后计算出误差最小的模型。这样一来得到一个用户矩阵和商品矩阵。通过两个矩阵的乘积得到矩阵A。
      ALS的交替二字用的很是精辟,就是指需要先随机生成一个矩阵U0,然后求另一个矩阵V0。然后固定V0,求U1。这样交替下去,误差会逐渐变小。另外ALS不能保证会收敛到全局最优解。但是在实际情况中,ALS最后是不是最优解影响并不是很大。因为,本来就没有最好的推荐,只有更好的推荐。
交替最小二乘法百科

二、ALS代码示例

首先需要一批用户电影评价的数据,数据格式如下:

#用户id,#电影id,#评分,#时间戳
1,31,2.5,1260759144
1,1029,3.0,1260759179

数据用逗号隔开。

2.1、模型训练

import breeze.numerics.sqrt
import org.apache.spark.SparkConf
import org.apache.spark.mllib.recommendation.{
    
    ALS, MatrixFactorizationModel, Rating}
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession

 

object ALSTrainerTest {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[*]"
    )
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("OfflineRecommender")
    // 创建一个SparkSession
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()
    import spark.implicits._
	//加载数据
    val ratingRDD = spark.read.textFile("E:\\ratings.csv").map(_.split(","))
   .rdd
   .map( rating => Rating( rating(0).toInt,rating(1).toInt,rating(2).toDouble ) )
   .cache()
    // 随机切分数据集,生成训练集和测试集
    val splits = ratingRDD.randomSplit(Array(0.8, 0.2))
    val trainingRDD = splits(0)
    val testRDD = splits(1)
    // 模型参数选择,输出最优参数
    adjustALSParam(trainingRDD, testRDD)
    spark.close()
  }

  def adjustALSParam(trainData: RDD[Rating], testData: RDD[Rating]): Unit ={
    
    
    //  50, 100, 200, 300
    //循环计算
    val result = for( rank <- Array(50); lambda <- Array( 0.01, 0.1, 1 ))
      yield {
    
    
        val model = ALS.train(trainData, rank, 5, lambda)
        // 计算当前参数对应模型的rmse,返回Double
        val rmse = getRMSE( model, testData )
        ( rank, lambda, rmse )
      }
    // 控制台打印输出最优参数
    println(">>>"+result.minBy(_._3))
  }

  def getRMSE(model: MatrixFactorizationModel, data: RDD[Rating]): Double = {
    
    
    // 计算预测评分
    val userProducts = data.map(item => (item.user, item.product))
    val predictRating = model.predict(userProducts)

    // 以uid,mid作为外键,inner join实际观测值和预测值
    val observed = data.map( item => ( (item.user, item.product), item.rating ) )
    val predict = predictRating.map( item => ( (item.user, item.product), item.rating ) )
    // 内连接得到(uid, mid),(actual, predict)
    sqrt(
      observed.join(predict).map{
    
    
        case ( (uid, mid), (actual, pre) ) =>
          val err = actual - pre
          err * err
      }.mean()
    )
  }
}
  • 从入口main开始,先加载评分数据,这里是加载的全部,实际情况中要看情况,如果新闻类的推荐,可能只需要加载最近一段时间的数据即可。
  • 将数据拆分为预测集和测试集。ALS算法本来就是一个预测的算法,如果全部用来预测计算的话,将无法知道预测的结果误差有多少,拆分两个集合这个由于上学考试时的AB卷,通过A卷平时练习,B卷检测真实的成绩。
  • adjustALSParam方法进行模型的计算,rank是特征维度,本地计算时只取了50,生成的时候应该多取一些值分别测试。lambda是为了防止过拟合
  • ALS.train方法中迭代次数,只填写了5次,这个数值肯定是越大越精确,当然也会越耗费时间。
  • 最后通过getRMSE方法计算误差。打印出误差最小的一组数据,即可在线上使用。

2.2、电影相似计算


import org.apache.spark.SparkConf
import org.apache.spark.mllib.recommendation.{
    
    ALS, MatrixFactorizationModel, Rating}
import org.apache.spark.sql.SparkSession
import org.jblas.DoubleMatrix


object ALSTrainerTest2 {
    
    
  def main(args: Array[String]): Unit = {
    
    
    val config = Map(
      "spark.cores" -> "local[*]"
    )
    val sparkConf = new SparkConf().setMaster(config("spark.cores")).setAppName("OfflineRecommender")
    // 创建一个SparkSession
    val spark = SparkSession.builder().config(sparkConf).getOrCreate()
    import spark.implicits._

    val ratingRDD = spark.read.textFile("E:\\webapps\\github\\MovieRecommendSystem\\recommender\\DataLoader\\src\\main\\resources\\ratings.csv").map(_.split(","))
   .rdd
   .map( rating => Rating( rating(0).toInt,rating(1).toInt,rating(2).toDouble ) )
   .cache()
    val model = ALS.train(ratingRDD, 50, 5, 0.1)
    val movieFeatures = model.productFeatures.map{
    
    
      case (mid, features) => (mid, new DoubleMatrix(features))
    }
    // 对所有电影两两计算它们的相似度,先做笛卡尔积
    val movieRecs = movieFeatures.cartesian(movieFeatures)
      .filter{
    
    
        // 把自己跟自己的配对过滤掉
        case (a, b) => a._1 != b._1
      }
      .map{
    
    
        case (a, b) => {
    
    
          val simScore = this.consinSim(a._2, b._2)
          ( a._1, ( b._1, simScore ) )
        }
      }
      .filter(_._2._2 > 0.6)    // 过滤出相似度大于0.6的
      .groupByKey()
      .map{
    
    
        case (mid, items) => MovieRecs( mid, items.toList.sortWith(_._2 > _._2).map(x => Recommendation(x._1, x._2)) )
      }
        .toDF()
    print(">>>"+movieRecs.show(10,false))
    spark.close()
  }

  // 求向量余弦相似度
  def consinSim(movie1: DoubleMatrix, movie2: DoubleMatrix):Double ={
    
    
    movie1.dot(movie2) / ( movie1.norm2() * movie2.norm2() )
  }
}
  • 在模型计算时,计算出特征值50和lambda为0.1时误差最小,这里就直接使用。
  • 之前有说als算法会将一个大的矩阵分解为用户特征矩阵和物品特征矩阵。所以,这里可以通过model.productFeatures直接获取物品特征矩阵。最后通过余弦相似度计算出两两电影的相似度。
  • 生成环境有时物品的数量太多,比如电商中的商品。由于数量太多,在特征计算时如果选用全部的商品计算是一种不明智的选择。因此可以通过先聚类再计算相似度。比如我们单纯的需要做手机推荐时,那么这个时候计算一个手机和一件衣服的相似度是没有意义的。

最后会得到一批这样的数据。每一行第一个是电影id,后面跟上一个数组,数组中每一项的第一个是电影id,第二个是相似度。
电影id|[[电影id,相似度]]

1216 |[[268,0.9020642201372278], [100553,0.8698425508833494]]
1792 |[[108188,0.900184780661061], [100487,0.8869996818771411]
2688 |[[27808,0.8402992760950369], [122,0.8381670349448246]]

最后这些数据可以在每一个电影的详细资料之后附加一个为你推荐的栏位,将这个电影最相似的几个电影查询出来。例如腾讯视频。在这里插入图片描述


猜你喜欢

转载自blog.csdn.net/qq_30285985/article/details/113420685