本步骤我们计算出任意两个标的物之间的相似度,有了任意两个标的物之间的相似度,那么我们就可以为每个标的物计算出与它最相似的K个标的物了。假设有两个标的物,它们对应的向量(即图2中矩阵的列向量,分别是第i列和第j列)如下,其中n是用户数。那么的相似度计算,我们可以细化如下:
公式1:计算相似度我们仔细看一下上述公式,公式的分子就是下图矩阵中对应的i列和j列中同一行中的两个元素(红色矩形中的一对元素)相乘,并且将所有行上第i列和第j列的元素相乘得到的乘积相加(这里其实只需要考虑同一行对应的i列和j列的元素都非零的情况,如果只要第i列和第j列中有一个为零,乘积也为零)。公式中分母是第i行与第i行按照上面类似的方法相乘再相加后开根号的值,再乘以第j行与第j行按照上面类似的方法相乘再相加后开根号的值。
图3:计算两个列向量的cosine余弦可以拆解为简单的加减乘及开根号运算有了上面的简单分析,就容易分布式计算相似度了。下面我们就来讲解,在Spark上怎么简单地计算每个标的物的topK相似度。在Spark上计算相似度,最主要的目标是怎么将上面巨大的计算量(前面已经提到在互联网公司,往往用户数和标的物数都是非常巨大的)通过分布式技术实现,这样就可以利用多台服务器的计算能力,解决大计算问题。首先将所有用户操作过的标的物”收集“起来,形成一个用户行为RDD,具体的数据格式如下:其中uid是用户唯一识别编码,sid是标的物唯一识别编码,R是用户对标的物的评分(即矩阵中的元素)。对于中的某个用户来说,他操作过的标的物和,一定在该用户所在的行对应的列i和列j的元素非零,根据上面计算相似度的公式,需要将该用户对应的的评分乘起来。这个过程可以用下面的图4来说明。
图4:对用户U来说,将他所有操作过的标的物做笛卡尔积当所有用户都按照图4的方式转化为标的物对及得分(图4中右边的)时,我们就可以对标的物对Group(聚合),将相同的对合并,对应的得分相加,最终得到的RDD为:。这样,公式1中分子就计算出来了(上式中的Score即是公式1中的分子)。现在我们需要计算分母,这非常简单,只要从上面的RDD中将标的物sid1等于标的物sid2的列过滤出来就可以了,通过下图的操作,我们可以得到一个map。
图5:从中过滤出的元素,用于计算公式1中的分母最多含有标的物的数量(m)个的元素,相对来说不大,我们可以将广播(broadcast)出去。方便我们按照公式1除以分母,最终得到的相似度。通过上面这些步骤,公式1中的分子和分母基本都很容易计算出来了,我们通过下图的代码(下面的broadcast即是),就可以计算出每组对的相似度,最终得到的RDD为:,其中Sim为sid1和sid2的相似度。
图6:计算每组的相似度有了上面的准备,下面我们来说明一下怎么计算每个标的物的topK最相似的标的物。具体的计算过程可以用如下的SparkTransformation来实现。其中第三步的TopK需要我们自己实现一个函数,求这样的列表中评分最大的TopK个元素,实现也是非常容易的,这里不赘述
如果我们把每个标的物最相似的K个标的物及相似度看成一个列向量的话,那么我们计算出的标的物相似度其实可以看作如下矩阵,该矩阵每列K个非零元素。
图7:标的物相似度矩阵到此为止,我们通过Spark提供的一些Transformation操作及一些工程实现上的技巧计算出了每个标的物topK最相似的标的物。该计算方法可以横向拓展,所以再大的用户数和标的物数都可以轻松应对,最多可能需要多加一些服务器。3.2为用户生成推荐有了1中计算出的标的物topK最相似的标的物,下面我们来说明一下怎么为用户生成个性化推荐。生成个性化推荐有两种工程实现策略,一种是看成矩阵的乘积,另外一种是根据第二节2中”基于标的物的协同过滤“中的公式来计算,这两种方法本质上是一样的,只是工程实现上不一样。下面我们分别讲解这两种实现方案。(1)通过矩阵相乘为用户生成推荐
上面图2中的矩阵是用户行为矩阵,第i行第j列的元素代表了用户i对标的物j的偏好/评分,我们将该矩阵记为,其中n是用户数,m是标的物数。图7中的矩阵是标的物之间的相似度矩阵,我们将它记为,这是一个方阵。和其实都是稀疏矩阵,我们通过计算这两个矩阵的乘积(Spark上是可以直接计算两个稀疏矩阵的乘积的),最终的结果矩阵就可以方便用来为用户推荐了:。其中的第i行代表的是用户i对每个标的物的偏好得分,我们从这个列表中过滤掉用户操作过的标的物,然后按照得分从高到低降序排列取topN就是最终给用户的推荐。
(2)通计算公式为用户生成推荐
标的物相似度矩阵是稀疏矩阵,最多个非零元素(因为每个标的物只保留K个最相似的标的物),一般K取几十或者上百规模的数值,m如果是十万或者百万量级,存储空间在1G左右(例如,如果m=100万,K=100,相似度为双精度浮点数,那么非零元素占用的空间为100万*100*8Byte=763M),完全可以通过广播的形式将broadcast到每个Spark计算节点中。我们先将相似矩阵转化为下图的Map结构,再广播出去,方便利用公式计算相似度。
图8:标的物的topK相似列表利用Map数据结构来存储
有了标的物之间的相似度Map,为用户计算推荐的过程可以基于用户行为RDD,在每个Partition中,针对每个用户u计算u与每个标的物之间的偏好度(利用第二节2基于标的物的协同过滤中的公式),再取topN就得到该用户的推荐结果了。由于用户行为采用了RDD来表示,所以整个计算过程可以分布式进行,每个Partition分布在一台服务器上进行计算。具体的计算逻辑可以用下面的代码片段来实现。
图9:为每个用户计算topN推荐讲到这里,基于Spark平台离线实现协同过滤算法的工程方案就讲完了。该实现方案强依赖于Spark的数据结构及分布式计算函数,可能在不同的计算平台上(比如Flink、Tensorflow等)具体的实现方式会不一样,但是基本思路和原理是一样的,有兴趣并且平时使用这些平台的读者可以在这些计算平台上独自实现一下,算是对自己的一个挑战。四、近实时协同过滤算法的工程实现上面第三节中的协同过滤工程实现方案适合做离线批量计算,比较适合标的物增长较缓慢的场景及产品(比如电商、视频、音乐等),对于新闻、短视频这类增量非常大并且时效性强的产品(如今日头条、快手等)是不太合适的。那么我们是否可以设计出一套适合这类标的物快速增长的产品及场景下的协同过滤算法呢?实际上是可以的,下面我们来简单说一下怎么近实时实现简单的协同过滤算法。我们的近实时协同过滤算法基于Kafka、HBase和SparkStreaming等分布式技术来实现,核心思想跟第三节中的类似,只不过我们这里是实时更新的,具体的算法流程及涉及到的数据结构见下面图10。下面我们对实现原理做简单介绍,整个推荐过程一共分为4步。
5.2标的物关联标的物推荐(范式)虽然第二节没有直接讲标的物关联标的物的算法,但是讲到了怎么计算两个标的物之间的相似度(即图2中评分矩阵的列向量之间的相似度),我们利用该相似度可以计算某个标的物最相似的K个标的物(在第三节1中我们给出了实现标的物相似性的工程实现,在第四节4中我们也给出了近实时计算标的物相似度的实现方案)。那么这K个最相似的标的物就可以作为该标的物的关联推荐。下图是电视猫相似影片推荐,是一类标的物关联标的物推荐范式,这类推荐可以基于协同过滤算法中间过程中的标的物topN相似度计算来实现。
7.5冷启动问题前面在讲协同过滤算法的缺点时讲到协同过滤算法会存在严重的冷启动问题,主要表现在如下3个方面:(1)用户冷启动
(2)标的物冷启动
所谓标的物冷启动就是新的标的物加入系统,没有用户操作行为,这时协同过滤算法也无法将该标的物推荐给用户。可行的解决方案有三个: