推荐系统实践读书笔记(七)

第7章 推荐系统实例

前面几章介绍了各种各样的数据和基于这些数据的推荐算法。在实际系统中,前面几章提到的数据大都存在,因此如何设计一个真实的推荐系统处理不同的数据,根据不同的数据设计算法,并将这些算法融合到一个系统当中是本章讨论的主要问题。

本章首先介绍推荐系统的外围架构,然后介绍推荐系统的架构,并对架构中每个模块的设计进行深入讨论。

7.1 外围架构

这一节主要讨论推荐系统是如何和网站的其他系统接口的。

推荐系统和其他系统之间的关系

UI系统负责给用户展示网页并和用户交互。网站会通过日志系统将用户在UI上的各种各样的行为记录到用户行为日志中。日志可能存储在内存缓存里,也可能存储在数据库中,也可能存储在文件系统中。而推荐系统通过分析用户的行为日志,给用户生成推荐列表,最终展示到网站的界面上。因此可以发现,推荐系统要发挥强大的作用,除了推荐系统本身,主要还依赖于两个条件——界面展示用户行为数据

目前流行的推荐系统界面存在一些共性:

  • 通过一定方式展示物品,主要包括物品的标题、缩略图和介绍等。
  • 很多推荐界面都提供了推荐理由,理由可以增加用户对推荐结果的信任度。
  • 推荐界面还需要提供一些按钮让用户对推荐结果进行反馈,这样才能让推荐算法不断改 善用户的个性化推荐体验。

在设计推荐界面时,可以综合考虑其他网站的设计并结合自己网站的特点。

数据收集和存储

从产生行为的用户角度看,有些行为是只有注册用户才能产生的,而有些行为是所有用户都可以产生的。从规模上看,浏览网页、搜索记录的规模都很大,因为这种行为所有用户都能产生,而且平均每个用户都会产生很多这些行为。购买、收藏行为规模中等,因为只有注册用户才能产生这种行为,但购买行为又是电商网站的主要行为,所以它们相对于评论来说规模更大,但相对于网页浏览行为来说规模要小得多,最后剩下的行为是注册用户里的一小部分人才有的,所以规模不会很大。同样有些行为需要实时存取,而有些并不需要。

按照前面数据的规模和是否需要实时存取,不同的行为数据将被存储在不同的媒介中。一般来说,需要实时存取的数据存储在数据库和缓存中,而大规模的非实时地存取数据存储在分布式文件系统(如HDFS)中。

数据能否实时存取在推荐系统中非常重要,因为推荐系统的实时性主要依赖于能否实时拿到用户的新行为。只有快速拿到大量用户的新行为,推荐系统才能够实时地适应用户当前的需求,给用户进行实时推荐。

7.2 推荐系统架构

推荐系统联系用户和物品的方式主要有3种,在第4章开头部分介绍过,分别是:

  • 基于用户的推荐算法
  • 基于物品的推荐算法
  • 基于特征的推荐算法

其中上述三种都可以将其抽象为基于特征的推荐算法,因为用户喜欢的物品可以算是用户特征,同样与用户兴趣相似的其他用户也是一种用户特征。然后根据抽象设计一种基于特征的推荐系统架构。当用户到来之后,推荐系统需要为用户生成特征,然后对每个特征找到和特征相关的物品,从而最终生成用户的推荐列表。因而,推荐系统的核心任务就被拆解成两部分,一个是如何为给定用户生成特征,另一个是如何根据特征找到物品

用户特征种类很多,主要包括如下几类:

  • 人口统计学特征
  • 用户行为特征
  • 用户的话题特征 可以根据用户的历史行为利用话题模型(topic model)将电视剧和电影聚合成不同的话题,并且计算出每个用户对什么话题感兴趣。

同时推荐系统的推荐任务也有很多种,如果要在一个系统中把上面提到的各种特征和任务都统筹考虑,那么系统将会非常复杂,而且很难通过配置文件方便地配置不同特征和任务的权重。因此,推荐系统需要由多个推荐引擎组成,每个推荐引擎负责一类特征和一种任务,而推荐系统的任务只是将推荐引擎的结果按照一定权重或者优先级合并、排序然后返回。

这样做有两个好处

  • 可以方便地增加/删除引擎,控制不同引擎对推荐结果的影响。对于绝大多数需求,只需要通过不同的引擎组合实现。
  • 可以实现推荐引擎级别的用户反馈。每一个推荐引擎其实代表了一种推荐策略,而不同的用户可能喜欢不同的推荐策略。可以将每一种策略都设计成一个推荐引擎,然后通过分析用户对推荐结果的反馈了解用户比较喜欢哪些引擎推荐出来的结果,从而对不同的用户给出不同的引擎组合权重。

将推荐系统拆分成不同推荐引擎后,如何设计一个推荐引擎变成了推荐系统设计的核心部分。下一节讨论推荐引擎的设计方法。

7.3 推荐引擎的架构

推荐系统架构主要包括3部分:

  • 该部分负责(1)从数据库或者缓存中拿到用户行为数据,通过(2)分析不同行为,(3)生成当前用户的特征向量。不过如果是使用非行为特征,就不需要使用行为提取和分析模块了。该模块的输出是用户特征向量。
  • 该部分负责将用户的特征向量通过特征-物品相关矩阵转化为初始推荐物品列表。
  • 该部分负责对初始的推荐列表进行过滤、排名等处理,从而生成最终的推荐结果。

推荐引擎的架构图

下节对各个不同的部分分别详细解释。

7.3.1 生成用户特征向量

一般来说,用户的特征包括两种,一种是用户的注册信息中可以提取出来的,另一种特征主要是从用户的行为中计算出来的,本节着重讨论如何生成特征。

一个特征向量由特征以及特征的权重组成,在利用用户行为计算特征向量时需要考虑以下因素。

  • 用户行为的种类 不同行为的影响不同,大多时候很难确定什么行为更加重要,一般的标准就是用户付出代价越大的行为权重越高。
  • 用户行为产生的时间 距离现在越近的行为权重越高。
  • 用户行为的次数 用户对同一个物品的同一种行为发生的次数也反映了用户对物品的兴趣,行为次数多的物品对应的特征权重越高。
  • 物品的热门程度 用户对热门物品的行为不能够反映用户的兴趣,而冷门物品则可以能够反映。推荐引擎在生成用户特征时会加重不热门物品对应的特征的权重。

7.3.2 特征—物品相关推荐

在得到用户的特征向量后,可以根据离线相关表得到初始的物品推荐列表。离线相关表可以存储在MySQL中。
对于每个特征,我们可以在相关表中存储和它最相关的N个物品的ID。

在线使用的特征—物品相关表一般都不止一张。因为可能使用了不同的推荐算法。总之,对于一个推荐引擎可以在配置文件中配置很多相关表以及它们的权重,而在线服务在启动时会将这些相关表按照配置的权重相加,然后将最终的相关表保存在内存中,而在给用户进行推荐时,用的已经是加权后的相关表了。

特征—物品相关推荐模块还可以接受一个候选物品集合。候选物品集合的目的是保证推荐结果只包含候选物品集合中的物品。对推荐物品的范围进行限定。

书中对为什么不在过滤模块中将候选集合外的电视剧过滤掉,而要在相关推荐模块中处理候选物品列表作出了解释,不过我没有看明白,有机会再看一次这部分。

特征—物品相关推荐模块除了给用户返回物品推荐列表,还需要给推荐列表中的每个推荐结果产生一个解释列表,表明这个物品是因为哪些特征推荐出来的。下面的代码给出了相关推荐模块的大体工作流程。

1
2
3
4
5
6
7
def RecommendationCore(features, related_table):
ret = dict()
for fid, fweight in features.items()
for item, sim in related_table[fid].items():
ret[item].weight += sim * fweight
ret[item].reason[fid] = sim * fweight
return ret

7.3.3 过滤模块

在得到初步的推荐列表后,需要先按照产品需求对结果进行过滤,过滤掉不符合要求的物品,然后再把推荐列表展现给用户。

一般来说,过滤模块会过滤掉以下物品:

  • 用户已经产生过行为物品 为了保证结果的新颖性
  • 候选物品以外的物品 候选物品集合一般有两个来源,一个是产品需求。另一个来源是用户自己的选择,过滤掉不满足用户所选条件的物品。

7.3.4 排名模块

对推荐列表进行排名可以更好地提升用户满意度,一般排名模块需要包括很多不同的子模块,下面对不同的模块分别加以介绍。

1.新颖性排名

新颖性排名模块的目的是给用户尽量推荐他们不知道的、长尾中的物品。虽然前面的过滤模块已经过滤掉了用户曾经有过行为的物品,保证了一定程度的新颖性,但是用户在当前网站对某个物品没有行为并不代表用户不知道这个物品。

要准确了解用户是否已经知道某个物品是非常困难的,因此只能通过某种近似的方式知道,比如使用如下公式对推荐结果中热门的物品进行降权。
$$
p_{ui} = \frac{p_{ui}}{\log{(1 + \alpha \cdot popularity(i))}}
$$
不过,要实现推荐结果的新颖性,仅仅在最后对热门物品进行降权是不够的,而应在推荐引擎的各个部分考虑新颖性问题。

本章提到推荐系统架构主要是基于物品的推荐算法的,因此回顾一下基于物品的推荐算法的基本公式:
$$
p_{ui} = \sum_{j \in N(u) \cap S(i,K)} w_{ji} r_{uj}
$$
在上述公式中$j$是联系用户和推荐物品的特征。最终$p_{ui}$的大小主要取决于两个参数——$w_{ji}$和$r_{uj}$。其中,$r_{uj}$在通过用户行为生成用户特征向量时计算,而$w_{ji}$是离线计算的物品相似度。如果要提高推荐结果的新颖性,在计算这两个数时都要考虑新颖性。与上面同理对$r_{uj}$和$w_{ji}$进行降权。
$$
r_{uj} = \frac{r_{uj}}{\log(1+\alpha \cdot popularity(j))}
$$

$$
w_{ji} = \frac{w_{ji}}{\log(1+\alpha \cdot popularity(i))} (popularity(i) > popularity(j))
$$

此外,也可以引入内容相似度矩阵,因为内容相似度矩阵中和每个物品相似的物品都不是很热门,所以引入内容相似度矩阵也能够提高最终推荐结果的新颖度。

2.多样性

增加多样性可以让推荐结果覆盖尽可能多的用户兴趣。这里需要指出的是提高多样性并不是时时刻刻都很好。

第一种提高多样性的方法是将推荐结果按照某种物品的内容属性分成几类,然后在每个类中都选择该类中排名最高的物品组合成最终的推荐列表。这种方法的好处是比较简单直观,但这种方法也有严重的缺点。首先,选择什么样的内容属性进行分类对结果的影响很大。其次,就算选择了某种类别,但物品是否属于某个类别是编辑确定的,并不一定能够得到用户的公认。

第二种提高推荐结果多样性的方法是控制不同推荐结果的推荐理由出现的次数。本章提出的推荐系统对于每个推荐出来的物品都有一个推荐理由,这个推荐理由一般是产生推荐结果的重要特征。那么,要提高推荐结果的多样性,就需要让推荐结果尽量来自不同的特征,具有不同的推荐理由,而不是所有的推荐结果都对应一个理由。

下面的代码根据推荐理由增加推荐结果的多样性,这里输入的recommendations是按照权重从大到小排序的,程序中每次拿出一个推荐结果,如果这个结果已经被用过了,就会对推荐结果的权重除以2降权(这里具体除以几可以在实际应用中自己调整),最终将推荐结果重新按照权重从大到小排序。

1
2
3
4
5
6
7
def ReasonDiversity(recommendations):
reasons = set()
for i in recommendations:
if i.reason in reasons:
i.weight /= 2
reasons.add(i.reason)
recommendations = sortByWeight(recommendations)

3.时间多样性

时间多样性主要是为了保证用户不要每天来推荐系统都看到同样的推荐结果。在第5章已经提到,提高推荐系统的时间多样性要从两个地方着手。首先要保证推荐系统的实时性,在用户有新行为时实时调整推荐结果以满足用户最近的需求。这一点,在本章的推荐系统设计中已经考虑到了。如果用户有实时行为发生,那么行为提取和分析模块就能实时拿到行为数据并转化为新的特征,然后经过特征-物品相关模块转换成和新特征最相关的物品,因而推荐列表中就立即反应了用户最新行为的影响。提高推荐结果多样性的第二个方面是要在用户没有新的行为时,也要保证推荐结果每天都有变化。要实现这一点,只能通过如下方式。

  • 记录用户每次登陆推荐系统看到的推荐结果。
  • 将这些结果发回日志系统。这种数据不需要实时存储,只要能保证小于一天的延时就足够了。
  • 在用户登录时拿到用户昨天及之前看过的推荐结果列表,从当前推荐结果中将用户已经看到的推荐结果降权。

4.用户反馈

排名模块最重要的部分就是用户反馈模块。用户反馈模块主要通过分析用户之前和推荐结果的交互日志,预测用户会对什么样的推荐结果比较感兴趣。

如果推荐系统的目标是提高用户对推荐结果的点击率,那么可以利用点击模型(click model)预测用户是否会点击推荐结果。点击模型在很多领域得到了广泛应用,比如搜索结果的点击预测(参见论文“A dynamic bayesian network click model for web search ranking”,作者为Olivier Chapelle和Ya Zhang)、 搜索广告的点击预测(参见论文“Online learning from click data for sponsored search”,作者为Massimiliano Ciaramita、Vanessa Murdock和Vassilis Plachouras )、上下文广告的点击预测(参见论文“Contextual advertising by combining relevance with click feedback”,作者为Deepayan chakrabarti、Deepak Agarwal和Vanja Josifovski。)。点击预测的主要问题是预测用户看到某个推荐结果时是否会点击。那么要进行点击率预测,首先需要提取特征。在推荐系统的点击率预测中可以用如下特征预测用户$u$会不会点击物品$i$:

  • 用户$u$相关的特征,比如年龄、性别、活跃程度、之前有没有点击行为;
  • 物品$i$相关的特征,比如流行度,平均分,内容属性;
  • 物品$i$在推荐列表中的位置。用户的点击和用户界面的设计有很高的相关性,因此物品$i$在推荐列表中的位置对预测用户是否点击很重要;
  • 用户之前是否点击过和推荐物品$i$具有同样推荐解释的其他推荐结果;
  • 用户之前是否点击过和推荐物品$i$来自同样推荐引擎的其他推荐结果。

点击模型需要离线计算好,在线将模型加载到内存中。为了提高在线预测的效率,一般只可以使用线性模型。

7.4 扩展阅读

关于推荐系统架构方面的文章很多,不过详细介绍架构的技术报告不多。知名公司亚马逊和Netflix等都只给出了一些简单的线索。本章提到的推荐系统架构主要是基于本书作者在Hulu工作时使用的架构抽象发挥出来的,对于Hulu架构感兴趣的读者可以参考Hulu的技术博客(参见http://tech.hulu.com/blog/2011/09/19/recommendation-system/ )。
MyMedia(参见http://mymediaproject.codeplex.com/ )是一个比较著名的开源推荐系统架构。它是由欧洲研究人员开发的一个推荐系统开源框架。该框架同时支持评分预测和TopN推荐,全面支持各种数据和各种算法,对该项目感兴趣的用户可以访问该项目的网站http://www.mymediaproject.org/default.aspx
本章提出的推荐系统架构基本上是从基于物品的推荐算法衍生出来的,因此本章的架构并不适合用来解决社会化推荐问题。如果要了解社会化推荐方面的架构,可以参考Twitter公开的一些文档。