大数据进阶必修课!Spark实战协同过滤推荐算法

10.SparkMLlib协同过滤推荐算法

10.1 协同过滤推荐算法

协同过滤算法是一种经典的推荐算法,推荐算法的基础是计算两个对象之间的相关度,其有两种实现形式:基于用户的推荐和基于物品的推荐。
基于用户的推荐思想是基于用户对某项物品的喜好找到具有相同喜好的相邻用户,然后将相邻用户喜欢的物品推荐给该用户。在这里,将一个用户对所有物品的喜好作为一个向量,计算不同用户之间的相似度,计算出若干位邻居用户后,根据这些邻居用户的相似度权重和其喜好的物品,从该用户没有喜好的物品中挑选出物品进行排序作为推荐。基本原理如下:

用户/物品物品1物品2物品3物品4
用户1喜好喜好推荐
用户2喜好
用户3喜好喜好喜好
计算相似度,用户3为1的相邻用户推荐物品4给用户1

基于物品的推荐思想与基于用户的相似,计算相似度时是计算物品之间的,基于用户喜好的物品找到相似的物品,做为推荐物品推荐给用户。在这里,就是将所有用户对某个物品的喜好作为一个向量,计算物品之间的相似度,得到物品的相似物品后,就可以根绝用户以前喜好的所有物品预测出用户还没有喜好的物品,同样也可以得到一个排序后的推荐列表。基本原理如下:

用户/物品物品1物品2物品3
用户1喜好喜好
用户2喜好喜好喜好
用户3喜好推荐
分析物品1、3,预测用户3有可能喜欢物品3

通过以上的简单描述,我们明确需要计算的三个地方:用户对物品的喜好,用户与用户之间相似度的计算,物品与物品之间相似度的计算。接下来将对这三点做一个简单的介绍。
推荐算法中决定推荐效果的最重要的因素局势用户的喜好信息,比如我们浏览商品时的点击,转发,停留时间等等。这些用户行为决定了用户对某项物品的喜好程度,通常推荐算法中对众多的用户行为有两种处理方式:

(1)将不同用户行为分组;

(2)对不同用户行为进行加权,降噪和归一化处理后根据用户行为的重要性设置不同权重。

相似度计算是推荐算法中的关键一环。无论是基于用户还是基于物品的计算,其计算方法都是一样的。常用计算方式有基于欧氏距离,基于余弦角度,皮尔逊相关系数以及由余弦相似度扩展而来的Tanimoto系数。

10.2 算法源码分析

在SparkMLlib中并没有我们上述的协同过滤推荐算法。在这里就简单介绍如何基于前述的理论知识,实现基于物品的协同过滤推荐算法。
算法的实现过程主要包括以下几个过程:
(1)相似度的计算,用new ItemSimilarity建立物品相似度计算,设置模型参数,再用Similarity方法计算相似度,最后返回的结果是物品之间的相似度,其格式是RDD。具体实现代码如下:

import scala.math._
import org.apache.spark.rdd.RDD
import org.apache.spark.SparkContext._

/**
 * 用户评分.
 * @param userid 用户
 * @param itemid 评分物品
 * @param pref 评分
 */
case class ItemPref(
  val userid: String,
  val itemid: String,
  val pref: Double) extends Serializable
/**
 * 用户推荐.
 * @param userid 用户
 * @param itemid 推荐物品
 * @param pref 评分
 */
case class UserRecomm(
  val userid: String,
  val itemid: String,
  val pref: Double) extends Serializable
/**
 * 相似度.
 * @param itemid1 物品
 * @param itemid2 物品
 * @param similar 相似度
 */
case class ItemSimi(
  val itemid1: String,
  val itemid2: String,
  val similar: Double) extends Serializable

/**
 * 相似度计算.
 * 支持:同现相似度、欧氏距离相似度、余弦相似度
 *
 */
class ItemSimilarity extends Serializable {

  /**
   * 相似度计算.
   * @param user_rdd 用户评分
   * @param stype 计算相似度公式
   * @param RDD[ItemSimi] 返回物品相似度
   *
   */
  def Similarity(user_rdd: RDD[ItemPref], stype: String): (RDD[ItemSimi]) = {
    val simil_rdd = stype match {
      case "cooccurrence" =>
        ItemSimilarity.CooccurrenceSimilarity(user_rdd)
      case "cosine" =>
        ItemSimilarity.CosineSimilarity(user_rdd)
      case "euclidean" =>
        ItemSimilarity.EuclideanDistanceSimilarity(user_rdd)
      case _ =>
        ItemSimilarity.CooccurrenceSimilarity(user_rdd)
    }
    simil_rdd
  }

}

object ItemSimilarity {

  /**
   * 同现相似度矩阵计算.
   * w(i,j) = N(i)∩N(j)/sqrt(N(i)*N(j))
   * @param user_rdd 用户评分
   * @param RDD[ItemSimi] 返回物品相似度
   *
   */
  def CooccurrenceSimilarity(user_rdd: RDD[ItemPref]): (RDD[ItemSimi]) = {
    // 0 数据做准备
    val user_rdd1 = user_rdd.map(f => (f.userid, f.itemid, f.pref))
    val user_rdd2 = user_rdd1.map(f => (f._1, f._2))
    // 1 (用户:物品) 笛卡尔积 (用户:物品) => 物品:物品组合     
    val user_rdd3 = user_rdd2.join(user_rdd2)
    val user_rdd4 = user_rdd3.map(f => (f._2, 1))
    // 2 物品:物品:频次 
    val user_rdd5 = user_rdd4.reduceByKey((x, y) => x + y)
    // 3 对角矩阵 
    val user_rdd6 = user_rdd5.filter(f => f._1._1 == f._1._2)
    // 4 非对角矩阵 
    val user_rdd7 = user_rdd5.filter(f => f._1._1 != f._1._2)
    // 5 计算同现相似度(物品1,物品2,同现频次)
    val user_rdd8 = user_rdd7.map(f => (f._1._1, (f._1._1, f._1._2, f._2))).
      join(user_rdd6.map(f => (f._1._1, f._2)))
    val user_rdd9 = user_rdd8.map(f => (f._2._1._2, (f._2._1._1,
      f._2._1._2, f._2._1._3, f._2._2)))
    val user_rdd10 = user_rdd9.join(user_rdd6.map(f => (f._1._1, f._2)))
    val user_rdd11 = user_rdd10.map(f => (f._2._1._1, f._2._1._2, f._2._1._3, f._2._1._4, f._2._2))
    val user_rdd12 = user_rdd11.map(f => (f._1, f._2, (f._3 / sqrt(f._4 * f._5))))
    // 6 结果返回
    user_rdd12.map(f => ItemSimi(f._1, f._2, f._3))
  }

  /**
   * 余弦相似度矩阵计算.
   * T(x,y) = ∑x(i)y(i) / sqrt((x(i)*x(i)) * ∑(y(i)*y(i)))
   * @param user_rdd 用户评分
   * @param RDD[ItemSimi] 返回物品相似度
   *
   */
  def CosineSimilarity(user_rdd: RDD[ItemPref]): (RDD[ItemSimi]) = {
    // 0 数据做准备
    val user_rdd1 = user_rdd.map(f => (f.userid, f.itemid, f.pref))
    val user_rdd2 = user_rdd1.map(f => (f._1, (f._2, f._3)))
    // 1 (用户,物品,评分) 笛卡尔积 (用户,物品,评分) => (物品1,物品2,评分1,评分2)组合       
    val user_rdd3 = user_rdd2.join(user_rdd2)
    val user_rdd4 = user_rdd3.map(f => ((f._2._1._1, f._2._2._1), (f._2._1._2, f._2._2._2)))
    // 2 (物品1,物品2,评分1,评分2)组合 => (物品1,物品2,评分1*评分2) 组合 并累加       
    val user_rdd5 = user_rdd4.map(f => (f._1, f._2._1 * f._2._2)).reduceByKey(_ + _)
    // 3 对角矩阵 
    val user_rdd6 = user_rdd5.filter(f => f._1._1 == f._1._2)
    // 4 非对角矩阵 
    val user_rdd7 = user_rdd5.filter(f => f._1._1 != f._1._2)
    // 5 计算相似度
    val user_rdd8 = user_rdd7.map(f => (f._1._1, (f._1._1, f._1._2, f._2))).
      join(user_rdd6.map(f => (f._1._1, f._2)))
    val user_rdd9 = user_rdd8.map(f => (f._2._1._2, (f._2._1._1,
      f._2._1._2, f._2._1._3, f._2._2)))
    val user_rdd10 = user_rdd9.join(user_rdd6.map(f => (f._1._1, f._2)))
    val user_rdd11 = user_rdd10.map(f => (f._2._1._1, f._2._1._2, f._2._1._3, f._2._1._4, f._2._2))
    val user_rdd12 = user_rdd11.map(f => (f._1, f._2, (f._3 / sqrt(f._4 * f._5))))
    // 6 结果返回
    user_rdd12.map(f => ItemSimi(f._1, f._2, f._3))
  }

  /**
   * 欧氏距离相似度矩阵计算.
   * d(x, y) = sqrt(((x(i)-y(i)) * (x(i)-y(i))))
   * sim(x, y) = n / (1 + d(x, y))
   * @param user_rdd 用户评分
   * @param RDD[ItemSimi] 返回物品相似度
   *
   */
  def EuclideanDistanceSimilarity(user_rdd: RDD[ItemPref]): (RDD[ItemSimi]) = {
    // 0 数据做准备
    val user_rdd1 = user_rdd.map(f => (f.userid, f.itemid, f.pref))
    val user_rdd2 = user_rdd1.map(f => (f._1, (f._2, f._3)))
    // 1 (用户,物品,评分) 笛卡尔积 (用户,物品,评分) => (物品1,物品2,评分1,评分2)组合       
    val user_rdd3 = user_rdd2 join user_rdd2
    val user_rdd4 = user_rdd3.map(f => ((f._2._1._1, f._2._2._1), (f._2._1._2, f._2._2._2)))
    // 2 (物品1,物品2,评分1,评分2)组合 => (物品1,物品2,评分1-评分2) 组合 并累加       
    val user_rdd5 = user_rdd4.map(f => (f._1, (f._2._1 - f._2._2) * (f._2._1 - f._2._2))).reduceByKey(_ + _)
    // 3 (物品1,物品2,评分1,评分2)组合 => (物品1,物品2,1) 组合 并累加    计算重叠数
    val user_rdd6 = user_rdd4.map(f => (f._1, 1)).reduceByKey(_ + _)
    // 4 非对角矩阵 
    val user_rdd7 = user_rdd5.filter(f => f._1._1 != f._1._2)
    // 5 计算相似度
    val user_rdd8 = user_rdd7.join(user_rdd6)
    val user_rdd9 = user_rdd8.map(f => (f._1._1, f._1._2, f._2._2 / (1 + sqrt(f._2._1))))
    // 6 结果返回
    user_rdd9.map(f => ItemSimi(f._1, f._2, f._3))
  }

}

(2)协同过滤推荐
RecommendedItem类,计算物品推荐,设置模型参数后,调用Recommend方法,对推荐物品进行计算,最后返回推荐给用户的物品,格式为RDD。其计算原理是根据物品之间的相似度和用户对物品的评分对推荐物品计算,并且对用户已经喜好的物品进行过滤。

import org.apache.spark.rdd.RDD
import org.apache.spark.SparkContext._

  /**
   * 用户推荐计算.
   * 根据物品相似度、用户评分、指定最大推荐数量进行用户推荐
   */

class RecommendedItem {
  /**
   * 用户推荐计算.
   * @param items_similar 物品相似度
   * @param user_prefer 用户评分
   * @param r_number 推荐数量
   * @param RDD[UserRecomm] 返回用户推荐物品
   *
   */
  def Recommend(items_similar: RDD[ItemSimi],
    user_prefer: RDD[ItemPref],
    r_number: Int): (RDD[UserRecomm]) = {
    //   0 数据准备  
    val rdd_app1_R1 = items_similar.map(f => (f.itemid1, f.itemid2, f.similar))
    val user_prefer1 = user_prefer.map(f => (f.userid, f.itemid, f.pref))
    //   1 矩阵计算——i行与j列join
    val rdd_app1_R2 = rdd_app1_R1.map(f => (f._1, (f._2, f._3))).
      join(user_prefer1.map(f => (f._2, (f._1, f._3))))
    //   2 矩阵计算——i行与j列元素相乘
    val rdd_app1_R3 = rdd_app1_R2.map(f => ((f._2._2._1, f._2._1._1), f._2._2._2 * f._2._1._2))
    //   3 矩阵计算——用户:元素累加求和
    val rdd_app1_R4 = rdd_app1_R3.reduceByKey((x, y) => x + y)
    //   4 矩阵计算——用户:对结果过滤已有I2
    val rdd_app1_R5 = rdd_app1_R4.leftOuterJoin(user_prefer1.map(f => ((f._1, f._2), 1))).
      filter(f => f._2._2.isEmpty).map(f => (f._1._1, (f._1._2, f._2._1)))
    //   5 矩阵计算——用户:用户对结果排序,过滤
    val rdd_app1_R6 = rdd_app1_R5.groupByKey()
    val rdd_app1_R7 = rdd_app1_R6.map(f => {
      val i2 = f._2.toBuffer
      val i2_2 = i2.sortBy(_._2)
      if (i2_2.length > r_number) i2_2.remove(0, (i2_2.length - r_number))
      (f._1, i2_2.toIterable)
    })
    val rdd_app1_R8 = rdd_app1_R7.flatMap(f => {
      val id2 = f._2
      for (w <- id2) yield (f._1, w._1, w._2)
    })
    rdd_app1_R8.map(f => UserRecomm(f._1, f._2, f._3))
  }

  /**
   * 用户推荐计算.
   * @param items_similar 物品相似度
   * @param user_prefer 用户评分
   * @param RDD[UserRecomm] 返回用户推荐物品
   *
   */
  def Recommend(items_similar: RDD[ItemSimi],
    user_prefer: RDD[ItemPref]): (RDD[UserRecomm]) = {
    //   0 数据准备  
    val rdd_app1_R1 = items_similar.map(f => (f.itemid1, f.itemid2, f.similar))
    val user_prefer1 = user_prefer.map(f => (f.userid, f.itemid, f.pref))
    //   1 矩阵计算——i行与j列join
    val rdd_app1_R2 = rdd_app1_R1.map(f => (f._1, (f._2, f._3))).
      join(user_prefer1.map(f => (f._2, (f._1, f._3))))
    //   2 矩阵计算——i行与j列元素相乘
    val rdd_app1_R3 = rdd_app1_R2.map(f => ((f._2._2._1, f._2._1._1), f._2._2._2 * f._2._1._2))
    //   3 矩阵计算——用户:元素累加求和
    val rdd_app1_R4 = rdd_app1_R3.reduceByKey((x, y) => x + y)
    //   4 矩阵计算——用户:对结果过滤已有I2
    val rdd_app1_R5 = rdd_app1_R4.leftOuterJoin(user_prefer1.map(f => ((f._1, f._2), 1))).
      filter(f => f._2._2.isEmpty).map(f => (f._1._1, (f._1._2, f._2._1)))
    //   5 矩阵计算——用户:用户对结果排序,过滤
    val rdd_app1_R6 = rdd_app1_R5.map(f => (f._1, f._2._1, f._2._2)).
      sortBy(f => (f._1, f._3))
    rdd_app1_R6.map(f => UserRecomm(f._1, f._2, f._3))
  }

}

10.3 应用实战

10.3.1 数据说明

本次实战的数据集使用的是自建数据,数据格式是用户序号,物品序号以及用户对物品的评分:

1,1,1
1,2,1
2,1,1
2,3,1
3,3,1
3,4,1
4,2,1
4,4,1
5,1,1
5,2,1
5,3,1
6,4,1

10.3.2 代码详解

//导入需要的spark包,注意没有涉及到MLlib包
import org.apache.log4j.{ Level, Logger }
import org.apache.spark.{ SparkConf, SparkContext }
import org.apache.spark.rdd.RDD

//构建Spark对象
val conf = new SparkConf().setAppName("ItemCF")
val sc = new SparkContext(conf)
Logger.getRootLogger.setLevel(Level.WARN)

//读取样本数据
val data_path = "/mnt/hgfs/thunder-download/MLlib_rep/data/itemcf.txt"
val data = sc.textFile(data_path)
val userdata = data.map(_.split(",")).map(f => (ItemPref(f(0), f(1), 			  f(2).toDouble))).cache()

//建立模型
val mysimil = new ItemSimilarity()
val simil_rdd1 = mysimil.Similarity(userdata, "cooccurrence")
val recommd = new RecommendedItem
val recommd_rdd1 = recommd.Recommend(simil_rdd1, userdata, 30)

//打印结果
println(s"物品相似度矩阵: ${simil_rdd1.count()}")
simil_rdd1.collect().foreach { ItemSimi =>
      println(ItemSimi.itemid1 + ", " + ItemSimi.itemid2 + ", " + ItemSimi.similar)
    }
println(s"用戶推荐列表: ${recommd_rdd1.count()}")
recommd_rdd1.collect().foreach { UserRecomm =>
      println(UserRecomm.userid + ", " + UserRecomm.itemid + ", " + UserRecomm.pref)
    }    

输出结果:

计算得到的相似度矩阵(物品,物品,相似度):
2,4, 0.33333333333333
3,4, 0.33333333333333
4,2, 0.33333333333333
3,2, 0.33333333333333
1,2, 0.66666666666666
4,3, 0.33333333333333
2,3, 0.33333333333333
1,3, 0.66666666666666
2,1, 0.66666666666666
3,1, 0.66666666666666

计算得到给用户推荐的物品表
4, 3, 0.66666666666666
4, 1, 0.66666666666666
6, 2, 0.33333333333333
6, 3, 0.33333333333333
2, 4, 0.33333333333333
2, 2, 1.0
5, 4, 0.66666666666666
3, 2, 0.66666666666666
3, 1, 0.66666666666666
1, 4, 0.33333333333333
1, 3, 1.0
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
华为数字化转型必修课PDF是一本关于华为数字化转型的必修教材,旨在帮助企业了解和应对数字化时代的挑战。该教材包含了华为在数字化转型过程中的经验和教训,为企业提供了一套完整的转型方案和方法论。 首先,华为数字化转型必修课PDF从理论与实践相结合的角度出发,全面介绍了数字化转型的背景、必要性和现实意义。它提供了一系列的案例分析,详细解析了华为在数字化转型中所面临的问题和解决方案。通过这些案例,企业可以更好地理解数字化转型的过程和关键因素。 其次,该教材详细介绍了数字化转型的五大要素:战略与愿景、组织与文化、架构与能力、技术与平台、落地与实践。它提供了一套系统化的框架和方法来帮助企业在数字化转型中找到适合自身发展的路径。同时,华为还分享了自己的数字化转型实践,包括战略调整、组织架构调整、技术平台建设等方面的经验。 此外,华为数字化转型必修课PDF还提供了一些关键的工具和技巧来帮助企业进行数字化转型。比如,它介绍了如何通过建立敏捷的创新机制来加速转型进程;如何利用大数据和人工智能来发现和分析潜在的商机;如何构建数字化生态系统来实现持续创新和价值输出等等。 总之,华为数字化转型必修课PDF是一本非常实用的教材,适用于任何希望通过数字化转型来提升企业竞争力和创造更多价值的企业。它的内容丰富,可操作性强,能够帮助企业深入理解数字化转型的本质和要求,并提供了一套可行的转型方案和方法论。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值