IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 推荐系统的基础算法和企业级设计 -> 正文阅读

[人工智能]推荐系统的基础算法和企业级设计

推荐系统介绍

在互联网时代,为了能够给用户良好的体验并且增大用户的留存率,推荐系统应运而生。

简单的推荐系统就是依据统计的推荐,也就是说,这与用户无关,每个人的界面看到的东西都是一样的。比如电商中Hot的商品,或者电影观看网站的Top N的电影。只要能够获得所有用户的够买或者评分数据,我们就能够做出统计推荐。

另一部分的推荐则是个性化推荐,每个用户推到的东西都是不同的。这需要我们收集到用户的特定信息。比如对于电影推荐网站,如果一个用户从来没有给任何一部电影打过分,那么这种推荐方式就无法运作。当然,企业收集的用户信息是各方面的,任何的行为操作,比如浏览、点击、购买、评分等等,都可以用日志记录下来,这便可以构建用户的用户画像

除了个性化和非个性化作为分类依据,推荐系统也可以分为实时推荐离线推荐。离线推荐依靠大量的历史数据训练模型,然后得到推荐列表。这时对于算法的选取就十分重要了。

实时推荐的触发时机则是在用户某一个行为之后,比如对于电影网站,一个用户对某部电影打了一个分数,这个行为就会触发程序的逻辑并立刻给出一个推荐列表。

以上两种只是粗略的分类,具体到操作或者说算法,推荐系统可以分为协同过滤非协同过滤

非协同过滤(即基于物品的推荐)只依靠一个用户它自身的行为数据。它的前提假设是:用户在一段时间内的口味是相同的。这里就涉及到特征工程。用户有他的特征和标签,比如喜欢恐怖片,不喜欢科幻片之类;物品也有自己的标签,拿电影来说,导演、演员、喜剧元素、浪漫元素都可以成为它的标签,这些标签构成一个向量,因此我们可以计算它们之间的相似度,并且给用户推荐相似度高的电影。可以想象,如果用户特征向量中喜欢恐怖片的值很大,而某影片它的恐怖片元素值也很大,那么两个向量做内积就一定很大,这样就可以决定推荐力度。

至于协同过滤,则需要多用户数据。它可以分为基于近邻的协同过滤基于模型的协同过滤

基于近邻的协同过滤又可以分为基于用户的协同过滤基于物品的协同过滤

基于用户的协同过滤的基本假设是:相似的用户有相似的口味。所谓相似的用户,指的是向量的相似。比如我们有一个用户对电影的评分矩阵(问号表示没有看过):
在这里插入图片描述

显然我们觉得A和C相近,A与B相反。C没有看过《泰坦尼克号》,那么我们就可以给他推荐。实际应用中我们可以利用KNN算法找到某个用户的类别。

同理,基于物品的协同过滤是找到物品之间的相关性。与基于物品的推荐(非协同过滤)所不同的是,后者只是依靠电影本身的特征,而前者需要采集所有用户的历史数据。如果你在电商平台购买了某件商品,它立马会给出推荐:您可能还喜欢XXX。这就是基于物品的协同过滤的应用。

基于模型的协同过滤强调的是算法,它有SVDLFM等等。SVD是将一个评分矩阵做SVD分解,然后再做降维处理。LFMSVD十分像,后面我们将重点分析LFM

本文将以电影推荐系统作为例子,数据来源于https://grouplens.org/datasets/movielens/,给出推荐系统的基本算法和企业级设计。业务系统和推荐系统代码位于https://gitee.com/resuscitateocean/recommend,前端代码位于https://gitee.com/resuscitateocean/recommend_front。

推荐系统的技术选型和基础数据

技术选取

离线推荐部分的技术:

  • 基于统计的推荐:SparkCore+SparkSQL
  • 基于模型的推荐:SparkCore+SparkMLlib
  • 工作调度:Azkaban

这部分最为重要的是使用SparkMLlib训练数据,对于其中的算法和优缺点将在后面讨论。因为这是离线的部分,一段时间电影和评分数据增加了,需要重新训练数据,所以我们采用Azkaban调度任务。


实时推荐部分的技术:

  • 使用flume采集业务系统评分日志
  • flume的数据流向kafka,kafka将实时评分数据发送给实时推荐服务器
  • 实时推荐服务器使用Spark-Streaming结合设计的算法给出推荐
    数据库:
  • 业务数据库:MySQL
  • 缓存数据库:Redis

模型训练时我们会得到电影的相似度矩阵,这个数据量已经到了千万级别(9743部电影),使用MySQL分库分表存还是使用查询更快的ClickHouse还是其他数据库中间件需要有所斟酌。实验中本人人工缩小了电影数据量(只选了电影ID小于2000的数据),并用MySQL存了电影相似度。

整个架构拓扑陈列如下(其中虚线表示推送数据,实线表示拉取数据):

在这里插入图片描述

数据简介

所有的数据位于recommendations/recommend/dataloader/src/main/resources/

在这里插入图片描述

电影数据的类别使用|分隔,时间戳的单位为秒。由于电影数据只有三个字段,信息量很少,所以我们可以通过链接数据得到电影详情。具体方式可以参照recommendations/recommend/dataloader/src/main/resources/README。但是本项目并不会用到链接数据,因为电影的情况我们只用类别做特征提取。

算法讨论

基于统计学的推荐算法

本模块涉及到四个统计,分别是历史热门统计,近期热门统计,优质电影统计和各类别Top10优质电影统计。

首先“热门”我们这里的含义是评分个数,评分个数越多,热门性越大。所以历史热门统计直接对电影id做分组并求和。近期热门需要先对时间做分组排序,然后相同时间的再对电影id做分组求和。

“优质”的含义是评分平均值,但这是加权平均。

影响电影“优质性”的因素有电影的平均得分、对该电影的评分人数。之所以将对电影的评分人数拉进来作为考量,是因为假设一部电影只有一个人评分,然后是最高的5分;另一部电影有一万人评分,最后平均分是4.8分,那我们就认为后一部电影要比前一部电影好,因为它去除了个人的偏见和喜好。
我们将利用IMDB的加权平均算法计算电影的最终平均分:

W R = v v + m R + m v + m C WR=\frac{v}{v+m}R+\frac{m}{v+m}C WR=v+mv?R+v+mm?C

其中WR(Weighted Rating)代表最终的加权平均值。
v是对某部电影的评分数量。
m是参加公式计算的最小评分数量。
R是该电影的平均分。
C是所有电影的平均分。

从公式上看,它引入了评分人数以及整体电影的影响。观察两个极端:
如果 v > > m v>>m v>>m,则,也就是说 W R ≈ R WR\approx R WRR,当一部电影的评分人数极其多,那么我们最后的加权平均分和电影本身的平均分差不多。

如果 v = m v=m v=m,则 W R = 1 2 ( R + C ) WR=\frac{1}{2}(R+C) WR=21?(R+C),电影最终的评分将直接受到所有电影平均分的制约。

至于各类别的top10电影,由于每部电影的类别可能很多,所以我们需要把电影表和所有类别做笛卡尔乘积,筛选出包含某类别的数据。比如现在所有的类别为:


    val genres = List(
      "Action",
      "Adventure",
      "Animation",
      "Comedy",
      "Children",
      "Crime",
      "Documentary",
      "Drama",
      "Fantasy",
      "Film-Noir",
      "Horror",
      "Musical",
      "Mystery",
      "Romance",
      "Sci-Fi",
      "Thriller",
      "War",
      "Western"
    )

第一条电影数据为:

1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy

笛卡尔乘机后的结果是:

"Action",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Adventure",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Animation",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Comedy",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Children",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Crime",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Documentary",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Drama",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Fantasy",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Film-Noir",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Horror",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Musical",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Mystery",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Romance",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Sci-Fi",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Thriller",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"War",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Western"1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy

最后留下的是:

"Adventure",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Animation",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Children",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Comedy",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy
"Fantasy",1,Toy Story (1995),Adventure|Animation|Children|Comedy|Fantasy

将筛选后的结果根据类别group by,再根据每部电影的加权平均order by即可得到所有类别top10的优质电影。

LFM算法

LFM的全称是Latent Factor Model,即为隐藏因子模型。有监督学习中,我们常常会给物品打上标签,让数据根据这些标签进行学习。但是LFM是一种自我特征学习,它的特征是通过算法自己学成的,缺点就是无解释性。
比如有一个用户电影评分矩阵:

在这里插入图片描述

如果我们可以将其分解成两个矩阵的乘积:

在这里插入图片描述

f1,f2,f3,f4就是所谓的隐藏因子,我们不知道它是什么,它可以是浪漫因素,恐怖因素等等的。从数值上来说,它具备一定的解释性。比如对于user3来说,他的f3因素很大,而movie2的f3因素也很大,这个f3因素暂且可以理解为科幻片因素,所以user3这一行乘以movie2这一列的值就会很大(8),实际理解为评分很高。如果能够知道用户-特征矩阵和电影-特征矩阵的话,把它们乘回去就可以知道原评分矩阵中缺失的分数了。

更一般的,我们记 R u i ^ \hat{R_{ui}} Rui?^?为预测评分矩阵,其中 u u u表示每个用户, i i i表示每个物品或电影。

P P P为用户-特征矩阵, Q Q Q为物品-特征矩阵, K K K表示特征数。于是预测矩阵为:

在这里插入图片描述

记原评分矩阵为 R u i R_{ui} Rui?,有评分数据的集合为 R 0 R_0 R0?,损失函数为:

在这里插入图片描述

λ \lambda λ项为正则化项,为了防止过拟合。可以想到,如果 K K K值很大,那么 λ \lambda λ项就会很大,损失函数的表现就会很差。

求解的最小值的方法之一是交替最小二乘法,即ALS。

因为 P P P Q Q Q两个矩阵都未知,所以我们可以先固定 P P P,求出最佳的 Q Q Q,再用上一步求出的 Q Q Q再去求 P P P,如此下去,直到 C C C收敛。

假设固定 Q Q Q,求最佳的 P P P。对于用户-特征矩阵来说,每一个用户 u u u是相互独立的,所以:

在这里插入图片描述

这样我们就把每个用户独立出来了。这时我们只需要求一个用户它的特征向量即可,所有用户的特征向量最终构成 P P P


在这里插入图片描述

,则

在这里插入图片描述


在这里插入图片描述

,得到

在这里插入图片描述

由于

在这里插入图片描述

,于是

在这里插入图片描述

我们把每一个 P u P_u Pu?求出来就会得到 P P P。然后反过来用相同的办法求 Q Q Q

求解 C C C的另一个方法是梯度下降法,其实 C C C的最小值问题的本质就是线性回归。交替最小二乘的精确求解和梯度下降的逼近求解本人都用python代码进行了实现,源码地址为:https://gitee.com/resuscitateocean/python_meachine_learning。

在spark中,只需调用ALS.train方法即可。当然,。当然, K K K的值, λ \lambda λ的值,我们需要传入并不断实验。当spark给出预测矩阵时,我们可以和原评分矩阵中的对应值相减然后平方求和(均方根误差)来评估模型的好坏。另外,spark还提供了隐式数据的训练。像电影评分类的数据,称为显性数据,而类似有没有点击过,有没有买过,有没有浏览过等等0-1类型的数据,称为隐式的评分,在train方法中需要传入implicitPrefs=true来训练这些数据。

还有需要指出的一点是,我们直接将原始数据带入模型进行计算了,然而原始数据可能会存在用户的偏见。比如user1和user2都很喜欢动作片,而user1是很严格的用户,他对《叶问1》,《叶问2》,《叶问3》的评分为(2,2,2),而user2是很宽松的用户,他对三部影片的评分为(5,5,5),从数据上看,user1和user2并非相似的用户,而实际上,他们都是喜欢动作片的。因此,我们需要对原始数据做一些处理,具体可以参考Improve_recommendation_system_by_integration这篇文章。

LFM是应用广泛的模型,但是它必须要有历史数据,也就是说,没有评过分的用户是无法进入模型计算的。从损失函数①我们也可以看到,如果用户没有评过分,那么前一项为,而正则化项 λ \lambda λ会鼓励参数尽可能的小,其结果就是 P u P_u Pu?全是0, Q i Q_i Qi?全是0,最后乘出的结果也是0,这显然是不合理的。

所以LFM无法解决冷启动问题。当新用户注册时,我们没办法给出推荐。所以很多app在你注册时会让你选择感兴趣的领域,比如音乐类的app,会让你挑选几种喜欢的口味的音乐。本项目是电影推荐,所以我们可以用电影的类别作为特征值,使用TF-IDF算法得到电影之间的相似度矩阵,当新用户注册时,我们可以给出一个页面让其选择感兴趣的类别,然后用电影相似度矩阵给出推荐。这便是本文第一部分介绍的非协同过滤(基于内容的推荐)。

实时推荐算法

LFM模型的副产品是电影相似度矩阵,这个可以存到数据库中。实时推荐将会用到这个矩阵。

现在一个用户看了某部电影,然后给出了评分,我们将依靠这条消息进行及时推荐。

难点在于,如果用户打了很低的分数,比如说1分,这对推荐系统是个考量。用户看了这部影片,说明他很感兴趣,但是又给了低分,说明他很失望。那我们究竟是推送类似电影呢还是不推送呢。

此时我们的算法是如此设计的:

首先,找到这部电影的相似电影(可以从电影相似度矩阵中找到),比如说,拿出最相似的20部,以此作为备选电影。

然后对每一部备选电影做考察。在缓存数据库中会存有用户最近的评分电影,比如说,有10部,我们取出一部备选电影,找到这部备选电影和用户最近的评分电影的相似度,用这个相似度乘以用户最近评分电影的评分求和,然后除以所有的相似度之和,相当于是做了一个加权。

接着我们定义评分大于等于3的电影为好电影,小于3的为差电影,如果用户最近评分中好电影多,我们会增大推荐力度,否则减小。

E u q E_{uq} Euq?为用户user的某一部备选电影 q q q的推荐等级分。

记用户的最近 L L L次评分为 R L RL RL,用户新打评分的电影为 p p p,与 p p p类似的电影的集合为 S S S,于是 p ∈ S p\in S pS

我们有

在这里插入图片描述

s i m ( q , r ) sim(q,r) sim(q,r)表示备选电影 q q q和最近评分电影 r r r的相似度。 R r R_r Rr? r r r的评分。

s i m _ s u m sim\_sum sim_sum为备选电影 q q q和所有的 r r r相似度之和,当然,我们可以给定一个阈值,比如,0.7,相似度大于0.7的 r r r才能作为考量。

在这里插入图片描述
相当于是用用户最近评分过的电影给备选电影做一个衡量。

后面两个对数项为偏移量。相似度大于0.7的中好电影的个数为enhanceCount,差电影的个数为weakenCount。

我们用实际例子来理解这个公式。

假设用户看了《叶问1》,并且他没有其他最近看过的电影,那么备选电影 q q q能否真正推荐将直接取决于 R r R_r Rr? (因为 ∑ r ∈ R L s i m ( q , r ) = s i m _ s u m \sum\limits_{r\in RL}sim(q,r)=sim\_sum rRL?sim(q,r)=sim_sum),即用户对《叶问1》的评分,这当然是合理的。如果评分高,那么 q q q的最终推荐等级会高。

假设用户最近看过的电影里有《叶问2》,《叶问3》,《霍元甲》,如果这三部电影的评分都很高,那么 q q q的推荐等级就高。其实这里的问题是:对于 p p p的相似电影 q q q,对它的( q q q)相似电影中我看过的电影,我是如何看待的,我的喜好或不喜好将决定 q q q最终的推荐等级,当然,当前打下评分的 p p p也参与进了最近看过的相似电影中。这比直接取 p p p的相似电影要稳健得多,因为公式考虑了我对这个“类别”(相似性展现的)的整体感觉——并且是最近的感觉,这就去除了《叶问1》本身的偏见,比如确实不好,或者导演不喜欢之类的。

下面要解释对数偏移量。假设 q q q的相似电影《叶问1》,《叶问2》,《叶问3》的评分都是1(我最近看过的),然后相似度都是0.9,这说明相似电影 q q q的表现很差,或者说我实在不喜欢。如果没有偏移项,结果就是 0.9 × 1 + 0.9 × 1 + 0.9 × 1 0.9 + 0.9 + 0.9 \frac{0.9\times1 +0.9\times 1 + 0.9 \times 1}{0.9+0.9+0.9} 0.9+0.9+0.90.9×1+0.9×1+0.9×1?。此时最近看过的电影又来了一部《霍元甲》,同样与的相似度是0.9,然后评分是1分。那么计算结果就是 0.9 × 1 + 0.9 × 1 + 0.9 × 1 + 0.9 × 1 0.9 + 0.9 + 0.9 + 0.9 \frac{0.9\times1 +0.9\times 1 + 0.9 \times 1+ 0.9 \times 1}{0.9+0.9+0.9+0.9} 0.9+0.9+0.9+0.90.9×1+0.9×1+0.9×1+0.9×1?,得到的结果与之前是一样的。这样算法对“不喜欢程度”就没有惩罚了。再加了一部相似度高但评分低的电影,应该加大以 q q q为中心的“类别”的厌恶程度。偏移量正好可以做出这个反应。但是偏移量的变化率又不能大大去影响前一项加权平均的结果,所以采用缓慢增长的对数函数是个好的选择。

至于两部电影相似度的计算,机器学习中有欧几里得距离,曼哈顿距离,余弦相似度,皮尔森相似度,本项目采用余弦相似度作为电影特征向量相似的计算方法。

项目模块简介

dataloader模块加载csv格式的数据到mysql和elasticsearch(后续开发可以拓展搜索推荐相关,虽然本文没有介绍),并选出用户最近的20次评分存到redis用于实时推荐模块的读取。

statistic-recommend为统计推荐。采用spark core和spark sql完成,包括历史热门,最近热门,优质电影,各类别top10电影推荐。

offline-lfm-recommend为LFM的模型训练。采用spark MLlib完成。我们最终将得到用户推荐列表和电影推荐列表,所谓的电影推荐列表就是电影的相似度矩阵。

stream-recommend为实时推荐模块。采用spark streaming流式处理结合上一章讨论的实时推荐算法给出用户的实时推荐列表。

content-recommend为基于内容的推荐。将电影的类别作为特征,采用TF-IDF算法求出每部电影类别特征向量,然后计算它们的相似度。这个模块会和业务系统用户注册之后填写感兴趣类别的电影结合起来,解决冷启动问题。

kafka-stream为kafka流式处理,业务系统将用户评分写入日志,flume读到之后sink到本模块,本模块可以做一些ETL工作,然后把评分数据给stream-recommend模块。

recommedation_server为简单的业务系统,主要展示实时推荐的逻辑。

  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-10-16 19:38:58  更:2021-10-16 19:40:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 10:36:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码