一 基本概念
基于图的模型是推荐系统中相当重要的一种方法,以下内容的基本思想是将用户行为数据表示为一系列的二元组,每一个二元组(u,i)代表用户u对物品i产生过行为,这样便可以将这个数据集表示为一个二分图。
假设我们有以下的数据集,只考虑用户喜不喜欢该物品而不考虑用户对物品的喜欢程度,
其中用户user=[A,B,C],物品item=[a,b,c],用户和物品有以下的关系:
上述便是一个典型的二分图,我们用G(V,E)来表示,其中V为用户user和物品item组成的顶点集即[A,B,C,a,b,c],而E则代表每一个二元组(u,i)之间对应的边e(u,i),我们这里不考虑用户对物品的喜爱程度,即默认喜爱则e=1,不喜爱则e=0。
那么我们如何使用上述的二分图模型进行物品的推荐呢?根据用户与物品的相关性,对于相关性高的顶点有如下的定义:
(1)两个顶点之间有很多路径相连
(2)连接两个顶点之间的路径长度都比较短
(3)连接两个顶点之间的路径不会经过度比较大的顶点
上面有一个概念需要理解,度,顶点的度是指和该顶点相关联的边数。
基于上述的定义,我们这里使用基于随机游走的PersonalRank算法来计算,那么这个算法是什么意思呢?
在解释之前,我们先理解一下另一个算法,pageRank算法,这个一个用来衡量搜索引擎中特定网页相对于其他网页重要性的算法,使用这个算法作为搜索结果网页排名相当重要的一部分。
它的基本思想是,假设网页之前通过超链接相互连接,互联网上的所有网页便构成了一张图。用户随机的打开一个网页,并通过超链接跳转到另一个网页。每当用户到达一个网页,他都有两种选择,停留在当前网页或者通过继续访问其他网页。如果用户继续访问网页的概率为d,那么用户停留在当前网页的概率便是1-d。如果用户继续访问其他网页,则会以均匀分布的方式随机访问当前网页指向的另一网页,这是一个随机游走的过程。当用户多次访问网页后,每一个网页被访问到的概率便会收敛到某个值,而计算出来的结果便可以用于网页排名,我们用以下的公式来表示:
其中PR(i)是网页i被访问到的概率,d代表用户继续访问网页的概率,N为所有网页的数量,in(i)代表所有指向网页i的网页集合,out(j)代表网页j指向的其他网页集合。
接下来我们分析一下这个公式,网页i被访问到的概率由两部分组成:
第一部分是网页i作为起点,第一个被用户点击后停留在当前页面的概率,即:
第二部分是用户点击其他网页后(无论网页i是不是起点),再次跳转回到网页i的概率:
这两部分的和便是网页i被点击到的概率。
介绍完pageRank算法后,我们再来看看PersonalRank算法,这个算法是基于pageRank算法进行了一些变化,在pageRank算法中,计算出来的是每一个顶点相对其他顶点的相关性,代入到我们的用户物品二分图中,这显然不是我们想要的,我们需要的是所有物品相对于特定某个用户的相关性,有公式如下:
对比pageRank,不同点只在于r的值不同,u代表根节点,即我们的目标用户节点,意思便是我们每次都是从目标用户节点出发,进行随机游走,而不同于pageRank的起点是随机从所有网页中进行选择,personalRank算法得出的结果便是所有顶点相对于目标用户结点的相关性。
二 实战
数据集:采用movielens的1M数据集
1 获取ratings数据集的数据
def getResource(csvpath):
'''
获取原始数据
:param csvpath: csv路径
:return: frame
'''
frame = pd.read_csv(csvpath)
return frame
2 整理用户及物品二分图,不考虑权重,只要用户对电影评过分便认为喜欢,e=1
(1)用户顶点二元组
def getUserGraph(frame, userID=1):
'''
获取目标用户二分图, 不计权重
:param frame: ratings数据
:param userID: 目标ID
:return: 二分图字典
'''
print(userID)
itemList = list(set(frame[frame['UserID']==userID]['MovieID']))
graphDict = {'i'+str(item): 1 for item in itemList}
return graphDict
(2)物品顶点二元组
def getItemGraph(frame, itemID=1):
'''
获取目标物品二分图, 不计权重
:param frame: ratings数据
:param userID: 目标ID
:return: 二分图字典
'''
print(itemID)
userList = list(set(frame[frame['MovieID']==itemID]['UserID']))
graphDict = {'u'+str(user): 1 for user in userList}
return graphDict
(3)整理成规范二分图G
def initGraph(frame):
'''
初始化二分图
:param frame: ratings数据集
:return: 二分图
'''
userList = list(set(frame['UserID']))
itemList = list(set(frame['MovieID']))
G = {'u'+str(user): getUserGraph(frame, user) for user in userList}
for item in itemList: G['i'+str(item)] = getItemGraph(frame, item)
return G
3 利用PersonalRank算法进行计算
def personalRank(G, alpha, userID, iterCount=20):
'''
随机游走迭代
:param G: 二分图
:param alpha: 随机游走的概率
:param userID: 目标用户
:param iterCount: 迭代次数
:return: series
'''
rank = {g: 0 for g in G.keys()}
rank['u'+str(userID)] = 1 #根节点为起点选择概率为1,其他顶点为0
for k in range(iterCount):
tmp = {g: 0 for g in G.keys()}
for i, ri in G.items(): #遍历每一个顶点
for j, wij in ri.items(): #遍历每个顶点连接的顶点
tmp[j] += alpha * rank[i] / len(ri)
tmp['u' + str(userID)] += 1 - alpha #根顶点r=1,加上1-alpha
rank = tmp
series = pd.Series(list(rank.values()), index=list(rank.keys()))
series = series.sort_values(ascending=False)
return series #返回排序后的series
4 推荐和目标用户没有直接相连而且是物品的顶点
def recommend(frame, series, userID, TopN=10):
'''
推荐TopN个用户没有评分的物品
:param frame: ratings数据
:param series: series
:param userID: 目标用户
:param TopN: TopN
:return: 推荐物品
'''
itemList = ['i'+str(i) for i in list(set(frame[frame['UserID']==userID]['MovieID']))]
recommendList = [{u: series[u]} for u in list(series.index) if u not in itemList and 'u' not in u]
return recommendList[:TopN]
5 迭代结果如下,可以看出经过15次左右的迭代,结果就基本趋于稳定了
[{'i3571': 0.0}, {'i830': 0.0}, {'i2942': 0.0}, {'i1725': 0.0}, {'i2445': 0.0}, {'i3822': 0.0}, {'i102': 0.0}, {'i2180': 0.0}, {'i264': 0.0}, {'i2615': 0.0}]
[{'i3867': 0.0}, {'i3922': 0.0}, {'i2061': 0.0}, {'i1020': 0.0}, {'i1395': 0.0}, {'i1877': 0.0}, {'i135': 0.0}, {'i2341': 0.0}, {'i1115': 0.0}, {'i3308': 0.0}]
[{'i2858': 0.00083046633303730691}, {'i1196': 0.00071671776671615873}, {'i1210': 0.00067806429079155233}, {'i593': 0.0006350175259251657}, {'i2396': 0.00061076227633024617}, {'i1198': 0.00060722720491067177}, {'i480': 0.00059923101084963497}, {'i2571': 0.00058276347994485863}, {'i318': 0.00058003203495347985}, {'i589': 0.00057600264742804167}]
[{'i2858': 0.0003321865332149249}, {'i1196': 0.00028668710668646325}, {'i1210': 0.00027122571631662092}, {'i593': 0.00025400701037006602}, {'i2396': 0.00024430491053209827}, {'i1198': 0.00024289088196426857}, {'i480': 0.00023969240433985376}, {'i2571': 0.00023310539197794378}, {'i318': 0.00023201281398139187}, {'i589': 0.00023040105897121686}]
[{'i2858': 0.00060349653055173386}, {'i1196': 0.00052196086934052385}, {'i1210': 0.00049776616762635677}, {'i593': 0.00045784751864878575}, {'i480': 0.00044765392929357667}, {'i1198': 0.0004410926232734298}, {'i589': 0.00043557149444675361}, {'i2571': 0.00043437223855112258}, {'i2396': 0.00043412826788239101}, {'i110': 0.00041807554733736419}]
[{'i2858': 0.00044071053214964735}, {'i1196': 0.00038079661174808798}, {'i1210': 0.00036184189684051532}, {'i593': 0.00033554321368155346}, {'i480': 0.00032287701432134349}, {'i1198': 0.00032217157848793285}, {'i2396': 0.00032023425347221535}, {'i2571': 0.00031361213060721635}, {'i589': 0.00031246923316143255}, {'i110': 0.00030344760820981306}]
[{'i2858': 0.00053694131852169088}, {'i1196': 0.00046458802316738787}, {'i1210': 0.00044259993946193073}, {'i593': 0.00040788262090828896}, {'i480': 0.00039758669454385479}, {'i1198': 0.00039265396543004474}, {'i2396': 0.00038687577725018962}, {'i589': 0.00038649101791954219}, {'i2571': 0.00038600611873183192}, {'i110': 0.00037182824192947688}]
[{'i2858': 0.0004792028466984644}, {'i1196': 0.0004143131763158079}, {'i1210': 0.00039414511388908164}, {'i593': 0.00036447897657224798}, {'i480': 0.00035276088641034826}, {'i1198': 0.00035036453326477753}, {'i2396': 0.00034689086298340466}, {'i2571': 0.0003425697258570615}, {'i589': 0.00034207794706467542}, {'i110': 0.00033079986169767844}]
[{'i2858': 0.00051376165005418437}, {'i1196': 0.00044444351663661672}, {'i1210': 0.00042319333008984872}, {'i593': 0.0003904669163388696}, {'i480': 0.00037967291853824856}, {'i1198': 0.00037570074620764637}, {'i2396': 0.00037078219926530098}, {'i589': 0.00036875659476582576}, {'i2571': 0.00036865439148347004}, {'i110': 0.00035541212342766543}]
[{'i2858': 0.00049302636804075199}, {'i1196': 0.00042636531244413177}, {'i1210': 0.00040576440036938898}, {'i593': 0.00037487415247889689}, {'i480': 0.00036352569926150825}, {'i1198': 0.00036049901844192547}, {'i2396': 0.00035644739749616432}, {'i2571': 0.00035300359210762509}, {'i589': 0.00035274940614513588}, {'i110': 0.00034064476638967305}]
[{'i2858': 0.00050546190424658681}, {'i1196': 0.00043721087645157386}, {'i1210': 0.0004162214547918957}, {'i593': 0.00038422614151956412}, {'i480': 0.00037321662425625601}, {'i1198': 0.0003696182084851502}, {'i2396': 0.00036504184399821651}, {'i2571': 0.00036239701620892931}, {'i589': 0.00036235686867035807}, {'i110': 0.0003495057342957748}]
[{'i2858': 0.00049800058252308604}, {'i1196': 0.00043070353804710909}, {'i1210': 0.00040994722213839196}, {'i593': 0.00037861494809516286}, {'i480': 0.0003674020692594065}, {'i1198': 0.00036414669445921434}, {'i2396': 0.00035988517609698454}, {'i2571': 0.00035676096174814604}, {'i589': 0.00035659239115522413}, {'i110': 0.00034418915355211341}]
[{'i2858': 0.00050247696507266541}, {'i1196': 0.00043460788156115355}, {'i1210': 0.00041371180788641855}, {'i593': 0.00038198137743991227}, {'i480': 0.0003708910680577479}, {'i1198': 0.00036742949422326811}, {'i2396': 0.00036297872662805993}, {'i2571': 0.00036014288057225089}, {'i589': 0.0003600513587190007}, {'i110': 0.00034737918324631687}]
[{'i2858': 0.00049979113554291703}, {'i1196': 0.00043226527545272642}, {'i1210': 0.00041145305643760225}, {'i593': 0.00037996151983306353}, {'i480': 0.00036879766877874302}, {'i1198': 0.00036545981436483658}, {'i2396': 0.00036112259630941473}, {'i2571': 0.00035811372927778781}, {'i589': 0.0003579759781807344}, {'i110': 0.00034546516542979523}]
[{'i2858': 0.00050140260178169259}, {'i1196': 0.0004336708357653612}, {'i1210': 0.00041280831402142163}, {'i593': 0.00038117341069881573}, {'i480': 0.00037005373269728003}, {'i1198': 0.00036664161465205691}, {'i2396': 0.00036223624105388892}, {'i2571': 0.00035933124573009519}, {'i589': 0.00035922123055598534}, {'i110': 0.00034661358443908758}]
[{'i2858': 0.00050043572203842799}, {'i1196': 0.00043282749957778078}, {'i1210': 0.00041199515947113016}, {'i593': 0.00038044627617936426}, {'i480': 0.0003693000943461583}, {'i1198': 0.0003659325344797252}, {'i2396': 0.00036156805420720528}, {'i2571': 0.0003586007358587113}, {'i589': 0.00035847407913083547}, {'i110': 0.00034592453303351333}]
[{'i2858': 0.00050101584739736013}, {'i1196': 0.0004333335010326456}, {'i1210': 0.00041248305287340628}, {'i593': 0.00038088255489434063}, {'i480': 0.00036975227950034447}, {'i1198': 0.00036635798197326311}, {'i2396': 0.00036196896372303849}, {'i2571': 0.00035903904402319342}, {'i589': 0.00035892237202062873}, {'i110': 0.00034633796465562979}]
[{'i2858': 0.00050066777218199944}, {'i1196': 0.00043302990015972582}, {'i1210': 0.0004121903168320396}, {'i593': 0.00038062078766535462}, {'i480': 0.00036948096840783211}, {'i1198': 0.00036610271347714009}, {'i2396': 0.00036172841801353889}, {'i2571': 0.00035877605912450363}, {'i589': 0.00035865339628675218}, {'i110': 0.00034608990568235995}]
[{'i2858': 0.00050087661711107777}, {'i1196': 0.00043321206065948117}, {'i1210': 0.00041236595851715116}, {'i593': 0.00038077784783332579}, {'i480': 0.00036964375524926104}, {'i1198': 0.00036625587452236827}, {'i2396': 0.00036187274523237836}, {'i2571': 0.00035893385025763939}, {'i589': 0.00035881478189912932}, {'i110': 0.00034623874113694431}]
[{'i2858': 0.00050075131015363142}, {'i1196': 0.00043310276435962864}, {'i1210': 0.00041226057350608519}, {'i593': 0.00038068361173254341}, {'i480': 0.00036954608314440407}, {'i1198': 0.00036616397789523172}, {'i2396': 0.00036178614890107399}, {'i2571': 0.00035883917557775791}, {'i589': 0.00035871795053170325}, {'i110': 0.0003461494398641943}]
以上就是基于随机游走的PersonalRank算法的介绍了,详细代码可以在我的github上找到: https://github.com/lpty