第06课:融合了用户兴趣的推荐才更具个性(工程示例)

紧接上一篇内容,我们在了解基于用户画像的推荐原理基础上,再结合所能获取到公开数据集,进行该算法机制的构建。这里我们将直接进入工程示例实现的讲解。

关于数据集

至于说取原始数据的过程,这里就不多说了,具体的可以看上示例(基于内容的工程示例),这里就不贴代码片段了,这里所有表的数据都会用到,所以都要获取,过程都是一致的。

第一步,先进行 movie 候选集的处理,包括 Tag 预处理、合并以及类目年份的获取。

我们进行相似 Tag 合并操作,返回的数据形态是(mvieid、tag)集合,但 Tag 会做提前进行预处理,过程依然跟上次一样,进行编辑距离相近的词合并。

val tagsStandardizeTmp = tagsStandardize.collect()
val tagsSimi = tagsStandardize.map{
  f=>
    var retTag = f._2
    if (f._2.toString.split(" ").size == 1) {
      var simiTmp = ""

      val tagsTmpStand = tagsStandardizeTmp
                    .filter(_._2.toString.split(" ").size != 1 )
                    .filter(f._2.toString.size < _._2.toString.size)
                    .sortBy(_._2.toString.size)
      var x = 0
     val loop = new Breaks
      tagsTmpStand.map{
        tagTmp=>
          val flag = getEditSize(f._2.toString,tagTmp._2.toString)
          if (flag == 1){
            retTag = tagTmp._2
            loop.break()
          }
      }
      (f._1,retTag)
    } else {
      f
    }
}

先将预处理之后的 movie-tag 数据进行统计频度,直接作为 Tag 权重,形成 (movie,tagList(tag,score))这种数据集形态。

val movieTagList = tagsSimi.map(f=>((f._1,f._2),1)).reduceByKey(_+_).groupBy(k=>k._1._1).map{
  f=>
    (f._1,f._2.map{
      ff=>
        (ff._1._2,ff._2)
    }.toList.sortBy(_._2).reverse.take(10).toMap)
}

接着进行 genre 类别以及抽取电影属性的年份属性,其中涉及的正则方法见上一个实例,这里就不重复给出了。

val moviesGenresYear = moviesData.rdd.map{
  f=>
    val movieid = f.get(0)
    val genres = f.get(2)
    val year = movieYearRegex.movieYearReg(f.get(1).toString)
    val rate = f.get(3).asInstanceOf[java.math.BigDecimal].doubleValue()
    (movieid,(genres,year,rate))
}

最终将三种不同的属性进行合并,以形成电影的处理过的候选集,当然还有电影的平均评分 rate 属性,这是判断电影基本水平的标志。

val movieContent = movieTagList.join(moviesGenresYear).filter(f=>f._2._2._3 < 2.5).sortBy(f=>f._2._2._3,false).map{
  f=>
    //userid,taglist,genre,year,rate
    (f._1,f._2._1,f._2._2._1,f._2._2._2,f._2._2._3)
}.collect()

第二步,我们进行用户画像属性的获取。

先通过 rating 评分表与 tags 表进行关联 join,获取用户直接与 tag 的关联关系,这样评分数据就可以当成单个 tag 的权重进行计算了,并且通过 DataFrame 的 API 操作会更方便,所以可以先将之前处理的 tagsSimi 转换成 DF,然后直接可以使用类似 SQL 的逻辑关系了。

val schemaString = "movieid tag"
val schema = StructType(schemaString.split(" ").map(fieldName=>StructField(fieldName,StringType,true)))
val tagsSimiDataFrame = sparkSession.createDataFrame(tagsSimi.map(f=>Row(f._1,f._2.toString.trim)),schema)
//对rating(userid,movieid,rate),tags(movieid,tag)进行join,以movieid关联
//join步骤,将(userId, movieId, rate)与(movieId, tag)按照movieId字段进行连接
val tagRateDataFrame = ratingData.join(tagsSimiDataFrame,ratingData("movieid")===tagsSimiDataFrame("movieid"),"inner").select("userid","tag","rate")

接着进行类似 reduce 操作,在 SQL 中就是分组合并,将 (userId, tag, rate) 中 (userId, tag) 相同的分数 rate 相加。

val userPortraitTag = tagRateDataFrame.groupBy("userid","tag").sum("rate").rdd.map{
  f=>
    (f.get(0),f.get(1),f.get(2).asInstanceOf[java.math.BigDecimal].doubleValue())
}.groupBy(f=>f._1).map{
  f=>
    val userid = f._1
    val tagList = f._2.toList.sortBy(_._3)
      .reverse.map(k=>(k._2,k._3)).take(20)
    (userid,tagList.toMap)
}

在处理完用户的兴趣 Tag 之后,处理其他属性,如 Year 属性。

val userPortraitYear = userYear.rdd.map(f=>(f.get(0),f.get(1),f.get(2))).groupBy(f=>f._1).map{
  f=>
    val userid = f._1
    val yearList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10)
    (userid,yearList)
}

进行用户的 genre 偏好处理。

val userPortraitGenre = userGenre.rdd.map(f=>(f.get(0),f.get(1),f.get(2))).groupBy(f=>f._1).map{
  f=>
    val userid = f._1
    val genreList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10)
    (userid,genreList)
}

对于每一个用户来说,在计算待推荐列表时,都需要移除自身已经看过的电影,先获取用户的观看列表。

val userMovieGet = ratingData.rdd.map(f=>(f.get(0),f.get(1))).groupByKey()

第三步,进行电影画像与用户画像的匹配计算。

在实际的计算过程中,每个同纬度的属性进行相似计算,最终外层通过权重模型进行打分,然后重新排序,获取每个用户的对应的待推荐电影 TopN,记得要移除自身已看过的电影列表。

val portraitBaseReData = userPortraitTag.join(userPortraitYear).join(userPortraitGenre).join(userMovieGet).map{
  f=>
    val userid = f._1
    val userTag = f._2._1._1._1
    val userYear = f._2._1._1._2
    val userGenre = f._2._1._2
    //用于做差集计算,移除已经看过的电影
    val userMovieList = f._2._2.toList
    val movieRe = movieContent.map{
      ff=>
        val movieid = ff._1
        val movieTag = ff._2
        val movieGenre = ff._3
        val movieYear = ff._4
        val movieRate = ff._5
        val simiScore = getSimiScore(userTag ,movieTag,userGenre,movieGenre,userYear,movieYear,movieRate)
        (movieid,simiScore)
    }.diff(userMovieList).sortBy(k=>k._2).reverse.take(20)
    (userid,movieRe)
}.flatMap(f=>f._2.map(ff=>(f._1,ff._1,ff._2)))

其中函数 getSimiScore 相关的计算逻辑如下。

def getSimiScore(userTag:Map[Any,Double],movieTag:Map[Any,Int],
                 userGenre:List[(Any,Double)],movieGenre:Any,
                 userYear:List[(Any,Double)],movieYear:Any,
                 movieRate:Double): Double ={
  val tagSimi = getCosTags(userTag,movieTag)
  val genreSimi = getGenreOrYear(userGenre,movieGenre)
  val yearSimi = getGenreOrYear(userYear,movieYear)
  val rateSimi = getRateSimi(movieRate)
  val score = 0.4*genreSimi + 0.3*tagSimi + 0.1*yearSimi + 0.2*rateSimi
  score
}

至于每个维度的计算过程,这里就不列了,大同小异,只要逻辑走的通即可。

第四步,对结果进行存储。

最后,将计算的结果保存下来,同样,需要先进行表结构定义。

val schemaPortraitStr = "userid movieid score"
val schemaPortrait = StructType(schemaPortraitStr.split(" ").map(fieldName=>StructField(fieldName,if (fieldName.equals("score")) DoubleType else  StringType,true)))
val portraitBaseReDataFrame = sparkSession.createDataFrame(portraitBaseReData.map(f=>Row(f._1,f._2,f._3)),schemaPortrait)
//将结果存入hive
val portraitBaseReTmpTableName = "mite_portraitbasetmp"
val portraitBaseReTableName = "mite8.mite_portrait_base_re"
portraitBaseReDataFrame.registerTempTable(portraitBaseReTmpTableName)
sparkSession.sql("insert into table " + portraitBaseReTableName + " select * from " + portrait)

至此,所有代码主体逻辑已经清晰了,其实说白了就是一个计算用户画像的过程,然后画像与待推荐主体之间的关联性。

最后

在下一篇里,我们将持续继续对常规推荐算法进行了解,并且下篇的推荐算法将是一个推荐领域中最经典的推荐算法。

同样,我们还是以原理解析,然后到实际的工程示例构建,再到代码工程的讲解为路径进行学习。

友情提示:我们所有的工程示例中,鉴于篇幅,只会给出核心代码(实际上已经很全了),如果想要获取完整的工程代码包,请联系作者额外获取,作者微信号:mute88,添加时请注明来意。

(全文完)

(转载本站文章请注明作者和出处 第06课:融合了用户兴趣的推荐才更具个性(工程示例)