推荐系统学习 -- 利用用户行为数据
一、用户行为数据
一个用户行为表示为6部分,即产生行为的用户和行为的对象、行为的种类、产生行为的上下文、行为的内容和权重。用户行为的统一表示如下:
user id 产生行为的用户的唯一标识
item id 产生行为的对象的唯一标识
behavior type 行为的种类(比如是购买还是浏览)
context 产生行为的上下文,包括时间和地点等
behavior weight 行为的权重(如果是观看视频的行为,那么这个权重可以是观看时长;如果是打分行为,这个权重可以是分数)
behavior content 行为的内容 (如果是评论行为,那么就是评论的文本;如果是打标签的行为,就是标签)
二、用户行为分析
在利用用户行为数据设计推荐算法之前,研究人员首先需要对用户行为数据进行分析,了解数据中蕴含的一般规律,这样才能对算法的设计起到指导作用。
1、用户活跃度和物品流行度的分布:
很多关于互联网数据的研究发现,互联网上的很多数据分布都满足一种称为Power Law的分布,这个分布在互联网领域也称长尾分布。
2、用户活跃度和物品流行度的关系:
一般来说,不活跃的用户要么是新用户,要么是只来过网站一两次的老用户。那么,不同活跃度的用户喜欢的物品的流行度是否有差别?一般认为,新用户倾向于浏览热门的物品,因为他
们对网站还不熟悉,只能点击首页的热门物品,而老用户会逐渐开始浏览冷门的物品。这表明用户越活跃,越倾向于浏览冷门的物品。
仅仅基于用户行为数据设计的推荐算法一般称为协同过滤算法。学术界对协同过滤算法进行了深入研究,提出了很多方法,比如基于邻域的方法(neighborhood-based)、隐语义模型
(latent factor model)、基于图的随机游走算法(random walk on graph)等。在这些方法中,最著名的、在业界得到最广泛应用的算法是基于邻域的方法,
而基于邻域的方法主要包含下面两种算法:
1)基于用户的协同过滤算法 这种算法给用户推荐和他兴趣相似的其他用户喜欢的物品。
2)基于物品的协同过滤算法 这种算法给用户推荐和他之前喜欢的物品相似的物品。
3、实验设计和算法测评
训练集和测试集划分,交叉验证
评测指标:recall、precision、coverage、popularity
新颖度,这里用推荐列表中物品的平均流行度度量推荐结果的新颖度。如果推荐出的物品都很热门,说明推荐的新颖度较低,否则说明推荐结果比较新颖。
注意:这里,在计算平均流行度时对每个物品的流行度取对数,这是因为物品的流行度分布满足长尾分布,在取对数后,流行度的平均值更加稳定。
4、基于邻域的算法
基于邻域的算法是推荐系统中最基本的算法,该算法不仅在学术界得到了深入研究,而且在业界得到了广泛应用。
基于邻域的算法分为两大类,一类是基于用户的协同过滤算法,另一类是
基于物品的协同过滤算法。下面几节将对这两种算法进行深入介绍,对比它们的优缺点并提出改进方案。
A 基于用户的协同过滤算法
I . 基于用户的协同过滤算法主要包括两个步骤:
(1) 找到和目标用户兴趣相似的用户集合。
(2) 找到这个集合中的用户喜欢的,且目标用户没有听说过的物品推荐给目标用户。
兴趣相似度:
步骤(1)的关键就是计算两个用户的兴趣相似度。这里,协同过滤算法主要利用行为的相似度计算兴趣的相似度。给定用户u和用户v,令N(u)表示用户u曾经有过正反馈的物品集合,令N(v)
为用户v曾经有过正反馈的物品集合。那么,我们可以通过如下的
Jaccard公式简单地计算u和v的兴趣相似度:
或者通过余弦相似度计算:
def UserSimilarity(train): W = dict() for u in train.keys(): for v in train.keys(): if u == v: continue W[u][v] = len(train[u] & train[v]) W[u][v] /= math.sqrt(len(train[u]) * len(train[v]) * 1.0) return W
该代码对两两用户都利用余弦相似度计算相似度。这种方法的时间复杂度是O(|U|*|U|),这在用户数很大时非常耗时。事实上,很多用户相互之间并没有对同样的物品产生过行为,即很多时
用户间交集为0。上面的算法将很多时间浪费在了计算这种用户之间的相似度上。如果换一个思路,我们可以首先计算出交集不为0的用户对(u,v),然后再对这种情况除以分母。
为此,可以首先建立物品到用户的倒排表,对于每个物品都保存对该物品产生过行为的用户列表。
参数K是UserCF的一个重要参数,它的调整对推荐算法的各种指标都会产生一定的影响。
准确率和召回率 可以看到,推荐系统的精度指标(准确率和召回率)并不和参数K成线性关系。在MovieLens数据集中,选择K=80左右会获得比较高的准确率和召回率。因此选
择合适的K对于获得高的推荐系统精度比较重要。当然,推荐结果的精度对K也不是特别敏感,只要选在一定的区域内,就可以获得不错的精度。
流行度 可以看到,在3个数据集上K越大则UserCF推荐结果就越热门。这是因为K决定了UserCF在给你做推荐时参考多少和你兴趣相似的其他用户的兴趣,那么如果K越大,参
考的人越多,结果就越来越趋近于全局热门的物品。
覆盖率 可以看到,在3个数据集上,K越大则UserCF推荐结果的覆盖率越低。覆盖率的降低是因为流行度的增加,随着流行度增加,UserCF越来越倾向于推荐热门的物品,从
而对长尾物品的推荐越来越少,因此造成了覆盖率的降低。
II. 用户相似度计算的改进
上一节介绍了计算用户兴趣相似度的最简单的公式(余弦相似度公式),但这个公式过于粗糙,本节将讨论如何改进该公式来提高UserCF的推荐性能。
以图书为例,如果两个用户都曾经买过《新华字典》,这丝毫不能说明他们兴趣相似,因为绝大多数中国人小时候都买过《新华字典》。但如果两个用户都买过《数据挖掘导论》,那可以认为他们的兴趣比较相似,因为只有研究数据挖掘的人才会买这本书。换句话说,两个用户对冷门物品采取过同样的行为更能说明他们兴趣的相似度。因此,John S. Breese在论文①中提出了如下公式,根据用户行为计算用户的兴趣相似度:
将上述相似度记为UserCF-IIF,通过实验评测UserCF-IIF的推荐性能,并将其和UserCF进行对比。在上一节的实验中,K=80时UserCF的性能最好,因此这里的实验同样选取K=80。
UserCF-IIF在各项性能上略优于UserCF。这说明在计算用户兴趣相似度时考虑物品的流行度对提升推荐结果的质量确实有帮助。
III. 实际在线系统使用UserCF的例子
相比我们后面要讨论的基于物品的协同过滤算法(ItemCF), UserCF在目前的实际应用中使用并不多。其中最著名的使用者是Digg,它在2008年对推荐系统进行了新的尝试①。Digg使用推荐系统的原因也是信息过载,它的研究人员经过统计发现,每天大概会有15 000篇新的文章,而每个用户的精力是有限的,而且兴趣差别很大。因此Digg觉得应该通过推荐系统帮用户从这么多篇文章中找到真正令他们感兴趣的内容,同时使每篇文章都有机会被展示给用户。利用对文章的 顶 和 踩 来找出相同的兴趣的用户,使用用户协同过滤来实现推荐。
B 基于物品的协同过滤算法
基于物品的协同过滤(item-based collaborative filtering)算法是目前业界应用最多的算法。由亚马逊提出。
基于物品的协同过滤算法(简称ItemCF)给用户推荐那些和他们之前喜欢的物品相似的物品。比如,该算法会因为你购买过《数据挖掘导论》而给你推荐《机器学习》。
不过,ItemCF算法并不利用物品的内容属性计算物品之间的相似度,它主要通过分析用户的行为记录计算物品之间的相似度。
基于物品的协同过滤算法主要分为两步:
(1) 计算物品之间的相似度。
(2) 根据物品的相似度和用户的历史行为给用户生成推荐列表。
亚马逊显示相关物品推荐时的标题是“Customers Who Bought This Item Also Bought”从这句话的定义出发,我们可以用下面的公式定
义物品的相似度:
和UserCF算法类似,用ItemCF算法计算物品相似度时也可以首先建立用户—物品倒排表(即对每个用户建立一个包含他喜欢的物品的列表),然后对于每个用户,将他物品列表中的物品两
两在共现矩阵C中加1。
def ItemSimilarity(train): #calculate co-rated users between items C = dict() N = dict() for u, items in train.items(): for i in users: N[i] += 1 for j in users: if i == j: continue C[i][j] += 1 #calculate finial similarity matrix W W = dict() for i,related_items in C.items(): for j, cij in related_items.items(): W[u][v] = cij / math.sqrt(N[i] * N[j]) return W
在MovieLens数据集上利用上面的程序计算电影之间相似度的结果。尽管在计算过程中没有利用任何内容属性,但利用ItemCF计算的结果却是可以从内容上看
出某种相似度的。一般来说,同系列的电影、同主角的电影、同风格的电影、同国家和地区的电影会有比较大的相似度。
该公式的含义是,和用户历史上感兴趣的物品越相似的物品,越有可能在用户的推荐列表中获得比较高的排名。
从这个例子可以看到,ItemCF的一个优势就是可以提供推荐解释,即利用用户历史上喜欢的物品为现在的推荐结果进行解释。
1)用户活跃度对物品相似度的影响
从前面的讨论可以看到,在协同过滤中两个物品产生相似度是因为它们共同出现在很多用户的兴趣列表中。
换句话说,每个用户的兴趣列表都对物品的相似度产生贡献。那么,是不是每个用户的贡献都相同呢?
假设有这么一个用户,他是开书店的,并且买了当当网上80%的书准备用来自己卖。另外可以看到,这个用户虽然活跃,但是买这些书并非都是出于自身的兴趣,而且这些书覆\
盖了当当网图书的很多领域,所以这个用户对于他所购买书的两两相似度的贡献应该远远小于一个只买了十几本自己喜欢的书的文学青年。
John S. Breese在论文①中提出了一个称为IUF(Inverse User Frequence),即用户活跃度对数的倒数的参数,他也认为活跃用户对物品相似度的贡献应该小于不活跃的用户,他提出应该增加IUF
参数来修正物品相似度的计算公式:
当然,上面的公式只是对活跃用户做了一种软性的惩罚,但对于很多过于活跃的用户,比如上面那位买了当当网80%图书的用户,为了避免相似度矩阵过于稠密,我们在实际计算中一般直
接忽略他的兴趣列表,而不将其纳入到相似度计算的数据集中。直接去除。
ItemCF-IUF在准确率和召回率两个指标上和ItemCF相近,但ItemCF-IUF明显提高了推荐结果的覆盖率,降低了推荐结果的流行度。从这个意义上说,ItemCF-IUF确实改进了ItemCF的综合性能。
2) 物品相似度的归一化
Karypis在研究中发现如果将ItemCF的相似度矩阵按最大值归一化,可以提高推荐的准确率。相似度的归一化也可以提高推荐的多样性。
那么,对于两个不同的类,什么样的类其类内物品之间的相似度高,什么样的类其类内物品相似度低呢?一般来说,热门的类其类内物品相似度一般比较大。如果不进行归一化,就会推荐
比较热门的类里面的物品,而这些物品也是比较热门的。因此,推荐的覆盖率就比较低。相反,如果进行相似度的归一化,则可以提高推荐系统的覆盖率。
从实验结果可以看到,归一化确实能提高ItemCF的性能,其中各项指标都有了比较明显的提高。准确率、召回率、覆盖度都有提升,流行度降低(越低越好,说明新颖度高)。
3)UserCF和ItemCF的综合比较
首先回顾一下UserCF算法和ItemCF算法的推荐原理。UserCF给用户推荐那些和他有共同兴趣爱好的用户喜欢的物品,而ItemCF给用户推荐那些和他之前喜欢的物品类似的物品。
从这个算法的原理可以看到,UserCF的推荐结果着重于反映和用户兴趣相似的小群体的热点,而ItemCF的推荐结果着重于维系用户的历史兴趣。换句话说,UserCF的推荐更社会化,反映了用户所在的小型兴趣群体中物品的热门程度,而ItemCF的推荐更加个性化,反映了用户自己的兴趣传承。
新闻网站,新闻推荐中,使用UserCF更合适。但是,在图书、电子商务和电影网站,比如亚马逊、豆瓣、Netflix中,ItemCF则能极大地发挥优势。
在早期的研究中,大部分研究人员都是让少量的用户对大量的物品进行评价,然后研究用户兴趣的模式。那么,对于他们来说,因为用户很少,计算用户兴趣相似度是最快也是最简单
的方法。但在实际的互联网中,用户数目往往非常庞大,而在图书、电子商务网站中,物品的数目则是比较少的。此外,物品的相似度相对于用户的兴趣一般比较稳定,因此使用ItemCF是
比较好的选择。当然,新闻网站是个例外,在那儿,物品的相似度变化很快,物品数目庞大,相反用户兴趣则相对固定(都是喜欢看热门的),所以新闻网站的个性化推荐使用UserCF算法的更多。
首先要指出的是,离线实验的性能在选择推荐算法时并不起决定作用。首先应该满足产品的需求,比如如果需要提供推荐解释,那么可能得选择ItemCF算法。其次,需要看实现代价,比如
若用户太多,很难计算用户相似度矩阵,这个时候可能不得不抛弃UserCF算法。最后,离线指标和点击率等在线指标不一定成正比。而且,这里对比的是最原始的UserCF和ItemCF算法,
这两种算法都可以进行各种各样的改进。一般来说,这两种算法经过优化后,最终得到的离线性能是近似的。
哈利波特问题:
不过,上述方法还不能彻底地解决哈利波特问题。每个用户一般都会在不同的领域喜欢一种物品。以电视为例,看新闻联播是父辈每天的必修课,他们每天基本就看新闻联播,而且每
天不看别的新闻,就看这一种新闻。此外,他们很多都是电视剧迷,都会看央视一套8点的电视剧。那么,最终结果就是黄金时间的电视剧都和新闻联播相似,而新闻联播和其他新闻的相似
度很低。
上面的问题换句话说就是,两个不同领域的最热门物品之间往往具有比较高的相似度。这个时候,仅仅靠用户行为数据是不能解决这个问题的,因为用户的行为表示这种物品之间应该相似
度很高。此时,我们只能依靠引入物品的内容数据解决这个问题,比如对不同领域的物品降低权重等。这些就不是协同过滤讨论的范畴了。
5、隐语义模型
1) LFM(latent factor model)隐语义模型逐渐成为推荐系统领域耳熟能详的名词。其实该算法最早在文本挖掘领域被提出,用于找到文本的隐含语义。相关的名词有LSI、pLSA、LDA和Topic Model.
隐语义模型是最近几年推荐系统领域最为热门的研究话题,它的核心思想是通过隐含特征(latent factor)联系用户兴趣和物品。
那么如何给A和B推荐图书呢?
对于UserCF,首先需要找到和他们看了同样书的其他用户(兴趣相似的用户),然后给他们推荐那些用户喜欢的其他书。
对于ItemCF,需要给他们推荐和他们已经看的书相似的书,比如作者B看了很多关于数据挖掘的书,可以给他推荐机器学习或者模式识别方面的书。
还有一种方法,可以对书和物品的兴趣进行分类。对于某个用户,首先得到他的兴趣分类,然后从分类中挑选他可能喜欢的物品。
总结一下,这个基于兴趣分类的方法大概需要解决3个问题。
如何给物品进行分类?
如何确定用户对哪些类的物品感兴趣,以及感兴趣的程度?
对于一个给定的类,选择哪些属于这个类的物品推荐给用户,以及如何确定这些物品在一个类中的权重?
对于第一个问题的简单解决方案是找编辑给物品分类,但编辑分类存在着许多问题。
研究人员提出:为什么我们不从数据出发,自动地找到那些类,然后进行个性化推荐?于是,隐含语义分析技术(latent variable analysis)出现了。隐含语义分析技术
因为采取基于用户行为统计的自动聚类。
隐含语义分析技术的分类来自对用户行为的统计,代表了用户对物品分类的看法。
隐含语义分析技术允许我们指定最终有多少个分类,这个数字越大,分类的粒度就会越细,反正分类粒度就越粗。
隐含语义分析技术会计算出物品属于每个类的权重,因此每个物品都不是硬性地被分到某一个类中。
隐含语义分析技术给出的每个分类都不是同一个维度的,它是基于用户的共同兴趣计算出来的,如果用户的共同兴趣是某一个维度,那么LFM给出的类也是相同的维度。
隐含语义分析技术可以通过统计用户行为决定物品在每个类中的权重,如果喜欢某个类的用户都会喜欢某个物品,那么这个物品在这个类中的权重就可能比较高。
隐含语义分析技术从诞生到今天产生了很多著名的模型和方法,其中和该技术相关且耳熟能详的名词有pLSA、LDA、隐含类别模型(latent class model)、隐含主题模型(latent topic model)、
矩阵分解(matrix factorization)。
推荐系统的用户行为分为显性反馈和隐性反馈。LFM在显性反馈数据(也就是评分数据)上解决评分预测问题并达到了很好的精度。
隐性反馈数据集,这种数据集的特点是只有正样本(用户喜欢什么物品),而没有负样本(用户对什么物品不感兴趣)。
首先如何给每个用户生成负样本:
通过2011年的KDD Cup的Yahoo! Music推荐系统比赛,我们发现对负样本采样时应该遵循以下原则:
对每个用户,要保证正负样本的平衡(数目相似)。
对每个用户采样负样本时,要选取那些很热门,而用户却没有行为的物品。
我们同样通过离线实验评测LFM的性能。首先,我们在MovieLens数据集上用LFM计算出用户兴趣向量p和物品向量q,然后对于每个隐类找出权重最大的物品。
结果表明,每一类的电影都是合理的,都代表了一类用户喜欢看的电影。从而说明LFM确实可以实现通过用户行为将物品聚类的功能。
其次,我们通过实验对比了LFM在TopN推荐中的性能。在LFM中,重要的参数有4个:
隐特征的个数F;
学习速率alpha;
正则化参数lambda;
负样本/正样本比例 ratio。
通过实验发现,ratio参数对LFM的性能影响最大。因此,固定F=100、alpha=0.02、lambda=0.01,然后研究负样本/正样本比例ratio对推荐结果性能的影响。
随着负样本数目的增加,LFM的准确率和召回率有明显提高。不过当ratio>10以后,准确率和召回率基本就比较稳定了。同时,随着负样本数目的增加,覆盖率不
断降低,而推荐结果的流行度不断增加,说明ratio参数控制了推荐算法发掘长尾的能力。当数据集非常稀疏时,LFM的性能会明显下降,甚至不如UserCF和ItemCF的性能。
2)基于LFM的实际系统的例子: 雅虎新闻推荐,实时性问题和冷启动问题。
3)LFM和基于邻域的方法的比较:
LFM是一种基于机器学习的方法,具有比较好的理论基础。这个方法和基于邻域的方法(比如UserCF、ItemCF)相比,各有优缺点。
理论基础 LFM具有比较好的理论基础,它是一种学习方法,通过优化一个设定的指标建立最优的模型。基于邻域的方法更多的是一种基于统计的方法,并没有学习过程。
离线计算的空间复杂度 基于邻域的方法需要维护一张离线的相关表。在离线计算相关表的过程中,如果用户/物品数很多,将会占据很大的内存。假设有M个用户和N个物品,
在计算相关表的过程中,我们可能会获得一张比较稠密的临时相关表(尽管最终我们对每个物品只保留K个最相关的物品,但在中间计算过程中稠密的相关表是不可避免的),
那么假设是用户相关表,则需要O(M*M)的空间,而对于物品相关表,则需要O(N*N)的空间。而LFM在建模过程中,如果是F个隐类,那么它需要的存储空间是O(F*(M+N)),这在
M和N很大时可以很好地节省离线计算的内存。在Netflix Prize中,因为用户数很庞大(40多万),很少有人使用UserCF算法(据说需要30 GB左右的内存),而LFM由于大量节
省了训练过程中的内存(只需要4 GB),从而成为Netflix Prize中最流行的算法。
离线计算的时间复杂度 假设有M个用户、N个物品、K条用户对物品的行为记录。那么,UserCF计算用户相关表的时间复杂度是O(N * (K/N)^2),而ItemCF计算物品相关表的时间
复杂度是O(M*(K/M)^2)。而对于LFM,如果用F个隐类,迭代S次,那么它的计算复杂度是O(K * F * S)。那么,如果K/N > F*S,则代表UserCF的时间复杂度低于LFM,如果
K/M>F*S,则说明ItemCF的时间复杂度低于LFM。在一般情况下,LFM的时间复杂度要稍微高于UserCF和ItemCF,这主要是因为该算法需要多次迭代。但总体上,这两种算法
在时间复杂度上没有质的差别。
在线实时推荐 UserCF和ItemCF在线服务算法需要将相关表缓存在内存中,然后可以在线进行实时的预测。以ItemCF算法为例,一旦用户喜欢了新的物品,就可以通过查询内
存中的相关表将和该物品相似的其他物品推荐给用户。因此,一旦用户有了新的行为,而且该行为被实时地记录到后台的数据库系统中,他的推荐列表就会发生变化。而从LFM
的预测公式可以看到,LFM在给用户生成推荐列表时,需要计算用户对所有物品的兴趣权重,然后排名,返回权重最大的N个物品。那么,在物品数很多时,这一过程的时间
复杂度非常高,可达O(M*N*F)。因此,LFM不太适合用于物品数非常庞大的系统,如果要用,我们也需要一个比较快的算法给用户先计算一个比较小的候选列表,然后再用
LFM重新排名。另一方面,LFM在生成一个用户推荐列表时速度太慢,因此不能在线实时计算,而需要离线将所有用户的推荐结果事先计算好存储在数据库中。因此,LFM不
能进行在线实时推荐,也就是说,当用户有了新的行为后,他的推荐列表不会发生变化。
推荐解释 ItemCF算法支持很好的推荐解释,它可以利用用户的历史行为解释推荐结果。但LFM无法提供这样的解释,它计算出的隐类虽然在语义上确实代表了一类兴趣和物品,
却很难用自然语言描述并生成解释展现给用户。
6、基于图的模型
1) 基于图的模型(graph-based model)是推荐系统中的重要内容。其实,很多研究人员把基于邻域的模型也称为基于图的模型,因为可以把基于邻域的模型看做基于图的模型的简单形式。
2) 基于图的推荐算法
将用户行为表示为二分图模型后,下面的任务就是在二分图上给用户进行个性化推荐。如果将个性化推荐算法放到二分图模型上,那么给用户u推荐物品的任务就可以转化为度量用户顶点
vu和与vu没有边直接相连的物品节点在图上的相关性,相关性越高的物品在推荐列表中的权重就越高。
一般来说图中顶点的相关性主要取决于下面3个因素:
两个顶点之间的路径数;
两个顶点之间路径的长度;
两个顶点之间的路径经过的顶点。
而相关性高的一对顶点一般具有如下特征:
两个顶点之间有很多路径相连;
连接两个顶点之间的路径长度都比较短;
连接两个顶点之间的路径不会经过出度比较大的顶点
计算图中顶点之间相关性的方法: 一种基于随机游走的PersonalRank算法:
虽然PersonalRank算法可以通过随机游走进行比较好的理论解释,但该算法在时间复杂度上有明显的缺点。因为在为每个用户进行推荐时,都需要在整个用户物品二分图上进行迭代,直到
整个图上的每个顶点的PR值收敛。这一过程的时间复杂度非常高.
为了解决PersonalRank每次都需要在全图迭代并因此造成时间复杂度很高的问题,这里给出两种解决方案。第一种很容易想到,就是减少迭代次数,在收敛之前就停止。这样会影响最终的
精度,但一般来说影响不会特别大。另一种方法就是从矩阵论出发,重新设计算法。
对矩阵运算比较熟悉的读者可以轻松将PersonalRank转化为矩阵的形式。令M为用户物品二分图的转移概率矩阵:
实现代码如下:
#矩阵的方法 #coding:utf-8 import numpy as np from numpy.linalg import solve import time from scipy.sparse.linalg import gmres, lgmres from scipy.sparse import csr_matrix if __name__ == '__main__': alpha = 0.8 vertex = ['A', 'B', 'C', 'a', 'b', 'c', 'd'] M = np.array([[0, 0, 0, 0.5, 0, 0.5, 0], [0, 0, 0, 0.25, 0.25, 0.25, 0.25], [0, 0, 0, 0, 0, 0.5, 0.5], [0.5, 0.5, 0, 0, 0, 0, 0], [0, 1.0, 0, 0, 0, 0, 0], [0.333, 0.333, 0.333, 0, 0, 0, 0], [0, 0.5, 0.5, 0, 0, 0, 0]]) r0 = np.array([[1], [0], [0], [0], [0], [0], [0]]) # 从'A'开始游走 # 直接解线性方程法 n = M.shape[0] A = np.eye(n) - alpha * M.T b = (1 - alpha) * r0 begin = time.time() r = solve(A, b) end = time.time() print('user time', end - begin) rank = {} for j in np.arange(n): rank[vertex[j]] = r[j] for ele in vertex: print(ele, rank[ele]) # 采用CSR法对稀疏矩阵进行压缩存储,然后解线性方程 data = list() row_ind = list() col_ind = list() for row in np.arange(n): for col in np.arange(n): if (A[row, col] != 0): data.append(A[row, col]) row_ind.append(row) col_ind.append(col) AA = csr_matrix((data, (row_ind, col_ind)), shape=(n, n)) begin = time.time() r = gmres(AA, b, tol=1e-08, maxiter=1)[0] end = time.time() print("user time", end - begin) rank = {} for j in np.arange(n): rank[vertex[j]] = r[j] for ele in vertex: print(ele, rank[ele])
('user time', 0.009312868118286133) ('A', array([0.3137876])) ('B', array([0.16565576])) ('C', array([0.07569236])) ('a', array([0.15864619])) ('b', array([0.03313115])) ('c', array([0.18892314])) ('d', array([0.0634081])) ('user time', 0.006492137908935547) ('A', 0.31378760114963755) ('B', 0.1656557621557125) ('C', 0.07569236305439948) ('a', 0.15864619289099752) ('b', 0.0331311524311425) ('c', 0.1889231381127573) ('d', 0.06340809765290228)
#循环的方法 def PersonalRank(G, alpha, root, max_step): rank = {index: 0 for index in G.keys()} for i in range(max_step): for j in rank.keys(): temp = 0 for k in G[j]: temp += alpha * rank[k] / len(G[k]) rank[j] = temp if j == root: rank[j] += 1 - alpha return rank if __name__ == "__main__": G = {'A': ['a', 'c'], 'B': ['a', 'b', 'c', 'd'], 'C': ['c', 'd'], 'a': ['A', 'B'], 'b': ['B'], 'c': ['A', 'B', 'C'], 'd': ['B', 'C']} rank = PersonalRank(G, 0.8, 'A', 1000) vertex = ['A', 'B', 'C', 'a', 'b', 'c', 'd'] result = [] for index in vertex: result.append([index, rank[index]]) result.sort(key=lambda x: x[1], reverse=True) for ele in result: print (ele[0],ele[1])
A 0.3137876011496375 c 0.18892313811275727 B 0.16565576215571243 a 0.1586461928909975 C 0.07569236305439944 d 0.06340809765290226 b 0.03313115243114249
从上面结果可以看出,从从A点开始游走,到达概率c>B>a>C>d>b
因为A已经访问过a和c了,所以给A推荐d的概率要大于b的概率
参考资料:
1. https://cloud.tencent.com/developer/article/1098856 推荐算法图推荐-基于随机游走的personalrank算法实现
2.https://blog.csdn.net/bbbeoy/article/details/78646635 基于图的推荐算法(PersonalRank)
3.https://donche.github.io/2017/10/31/PageRankNPersonalRank.html PageRank 与基于图的推荐算法
4. https://blog.csdn.net/love_data_scientist/article/details/95047023
5. https://blog.csdn.net/HHTNAN/article/details/79899069