上篇博文我们已经写完统计推荐部分,现在我们将使用Vue+Element-ui+SpringBoot来快速搭建系统,展示出电影,并介绍个性化推荐部分。
1 系统页面设计
初步是想设计一个类似豆瓣电影推荐系统
- 用户登陆后,可以查看高分电影
- 可以查看推荐的电影
- 可以评分
1.1 前端模板下载
- 由于时间原因,这里选择了一个仿豆瓣电影系统模版,本意不是为了锻炼vue能力,怎么简单怎么来。
- 现在我们对该系统进行修改,使用Element-ui来快速开发。
1.2 后端系统搭建
- 使用SpringBoot进行快速开发
- 添加MongoDB的相关依赖,写接口测试是否获取数据成功
- 测试成功后,Vue写axios相关代码
注意:一定要注意版本问题,报错会很糟心...
data:
mongodb:
host: 服务器IP
port: 27017
database: recommender
username: "root"
password: "123456"
2. 基于隐语义模型的协同过滤算法
基于用户行为分析的推荐算法一般称为协同过滤算法。所谓协同过滤,就是指众多的用户可以齐心协力,通过不断地和网站互动,使自己的推荐列表能够不断过滤掉自己不感兴趣的物品,从而越来越满足自己的需求。常见实现方法的包括:
- 基于邻域的方法
- 隐语义模型
- 基于图的随机游走算法
我们使用隐语义模型(LFM),它的核心思想是通过发掘隐含特征(latent factor) 来完成推荐任务。后续我们将对此进行改进。
主要步骤:
- UserId 和 MovieID 做笛卡尔积,产生(uid,mid)的元组
- 通过模型预测(uid,mid)的元组。
- 将预测结果通过预测分值进行排序。
- 返回分值最大的 K 个电影,作为当前用户的推荐。
- 通过ALS计算出电影相似度,存入MongoDB数据库,这为后面实时推荐做准备
scala
// 核心程序
// 从rating数据中提取所有的uid和mid,并去重
val userRDD = ratingRDD.map(_._1).distinct()
val movieRDD = ratingRDD.map(_._2).distinct()
// 训练隐语义模型
val trainData = ratingRDD.map( x => Rating(x._1, x._2, x._3) )
val (rank, iterations, lambda) = (200, 5, 0.1)
val model = ALS.train(trainData, rank, iterations, lambda)
// 基于用户和电影的隐特征,计算预测评分,得到用户的推荐列表
// 计算user和movie的笛卡尔积,得到一个空评分矩阵
val userMovies = userRDD.cartesian(movieRDD)
// 调用model的predict方法预测评分
val preRatings = model.predict(userMovies)
val userRecs = preRatings
.filter(_.rating > 0) // 过滤出评分大于0的项
.map(rating => ( rating.user, (rating.product, rating.rating) ) )
.groupByKey()
.map{
case (uid, recs) => UserRecs( uid, recs.toList.sortWith(_._2>_._2).take(USER_MAX_RECOMMENDATION).map(x=>Recommendation(x._1, x._2)) )
}
.toDF()
userRecs.write
.option("uri", mongoConfig.uri)
.option("collection", USER_RECS)
.mode("overwrite")
.format("com.mongodb.spark.sql")
.save()
// 基于电影隐特征,计算相似度矩阵,得到电影的相似度列表
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.8) // 过滤出相似度大于0.8的
.groupByKey()
.map{
case (mid, items) => MovieRecs( mid, items.toList.sortWith(_._2 > _._2).map(x => Recommendation(x._1, x._2)) )
}
.toDF()
movieRecs.write
.option("uri", mongoConfig.uri)
.option("collection", MOVIE_RECS)
.mode("overwrite")
.format("com.mongodb.spark.sql")
.save()
但该方法存在下列缺点:
- 很难实现实时的推荐。
- 推荐模型的更新,需要在用户行为记录上反复迭代,每次训练都很耗时。
- 冷启动问题明显。