将用户行为表示为二分图模型。假设给用户\(u\)进行个性化推荐,要计算所有节点相对于用户\(u\)的相关度,则PersonalRank从用户\(u\)对应的节点开始游走,每到一个节点都以\(1-d\)的概率停止游走并从\(u\)重新开始,或者以\(d\)的概率继续游走,从当前节点指向的节点中按照均匀分布随机选择一个节点往下游走。这样经过很多轮游走之后,每个顶点被访问到的概率也会收敛趋于稳定,这个时候我们就可以用概率来进行排名了。

在执行算法之前,我们需要初始化每个节点的初始概率值。如果我们对用户\(u\)进行推荐,则令\(u\)对应的节点的初始访问概率为1,其他节点的初始访问概率为0,然后再使用迭代公式计算。

\[PR(i)=(1-d)r_i+d\sum_{j \in in(i)} \frac {PR(j)}{|out(i)|} \\
r_i =
\begin{cases}
1 \ \ i=u \\
0 \ \ i!=u
\end{cases}
\]

一般有两种算法实现,一种是矩阵化实现,一种是非矩阵化实现。

非矩阵化实现

根据userID与itemID建立二分图。在代码中,self.G代表全局有向图,为区分userID与itemID分别加了不同的前缀。另外,user-item对保存在图中,方向是相互的。接下来,就在图中根据概率进行转移。

其中G = dict(item_user,**user_item)的含义是将两个dict拼接成一个dict

import pandas as pd
import time class PersonalRank:
def __init__(self,X,Y):
X,Y = ['user_'+str(x) for x in X],['item_'+str(y) for y in Y]
self.G = self.get_graph(X,Y) def get_graph(self,X,Y):
"""
Args:
X: user id
Y: item id
Returns:
graph:dic['user_id1':{'item_id1':1}, ... ]
"""
item_user = dict()
for i in range(len(X)):
user = X[i]
item = Y[i]
if item not in item_user:
item_user[item] = {}
item_user[item][user]=1 user_item = dict()
for i in range(len(Y)):
user = X[i]
item = Y[i]
if user not in user_item:
user_item[user] = {}
user_item[user][item]=1
G = dict(item_user,**user_item)
return G def recommend(self, alpha, userID, max_depth,K=10):
# rank = dict()
userID = 'user_' + str(userID)
rank = {x: 0 for x in self.G.keys()}
rank[userID] = 1
# 开始迭代
begin = time.time()
for k in range(max_depth):
tmp = {x: 0 for x in self.G.keys()}
# 取出节点i和他的出边尾节点集合ri
for i, ri in self.G.items():
# 取节点i的出边的尾节点j以及边E(i,j)的权重wij,边的权重都为1,归一化后就是1/len(ri)
for j, wij in ri.items():
tmp[j] += alpha * rank[i] / (1.0 * len(ri))
tmp[userID] += (1 - alpha)
rank = tmp
end = time.time()
print('use_time', end - begin)
lst = sorted(rank.items(), key=lambda x: x[1], reverse=True)[:K]
for ele in lst:
print("%s:%.3f, \t" % (ele[0], ele[1])) if __name__ == '__main__':
moviesPath = '../data/ml-1m/movies.dat'
ratingsPath = '../data/ml-1m/ratings.dat'
usersPath = '../data/ml-1m/users.dat' # usersDF = pd.read_csv(usersPath,index_col=None,sep='::',header=None,names=['user_id', 'gender', 'age', 'occupation', 'zip'])
# moviesDF = pd.read_csv(moviesPath,index_col=None,sep='::',header=None,names=['movie_id', 'title', 'genres'])
ratingsDF = pd.read_csv(ratingsPath, index_col=None, sep='::', header=None,names=['user_id', 'movie_id', 'rating', 'timestamp'])
X=ratingsDF['user_id'][:1000]
Y=ratingsDF['movie_id'][:1000]
PersonalRank(X,Y).recommend(alpha=0.8,userID=1,max_depth=50,K=30)#输出对用户1推荐的 top10 item
# print('PersonalRank result',rank)

矩阵化实现

\[r = (1-\alpha)r_o + \alpha M^T r
\]

其中,\(r\)是\(m+n\)行,1列的矩阵,每一行代表该顶点对固定顶点的PR值;是\(m+n\)行,1列的矩阵,负责选取某一个顶点作为固定顶点,其数值只有1行为1,其余为0。\(M\)是m+n行,m+n列的矩阵,是转移矩阵,其值\(M_{ij}=\frac{1}{out(i)},j \in out(i) \ else \ 0\),即为顶点的出度倒数,若没有连接边则为0。上式可转换为:

\[r = (E-\alpha M^T)^{-1}(1-\alpha)r_o
\]

其中,\((E-\alpha M^T)^{-1}\)可以看做所有顶点的推荐结果,每一列代表一个顶点项,对该顶点的PR值。

#-*-coding:utf-8-*-
"""
author:jamest
date:20190310
PersonalRank function with Matrix
"""
import pandas as pd
import numpy as np
import time
import operator
from scipy.sparse import coo_matrix
from scipy.sparse.linalg import gmres class PersonalRank:
def __init__(self,X,Y):
X,Y = ['user_'+str(x) for x in X],['item_'+str(y) for y in Y]
self.G = self.get_graph(X,Y) def get_graph(self,X,Y):
"""
Args:
X: user id
Y: item id
Returns:
graph:dic['user_id1':{'item_id1':1}, ... ]
"""
item_user = dict()
for i in range(len(X)):
user = X[i]
item = Y[i]
if item not in item_user:
item_user[item] = {}
item_user[item][user]=1 user_item = dict()
for i in range(len(Y)):
user = X[i]
item = Y[i]
if user not in user_item:
user_item[user] = {}
user_item[user][item]=1
G = dict(item_user,**user_item)
return G def graph_to_m(self):
"""
Returns:
a coo_matrix sparse mat M
a list,total user item points
a dict,map all the point to row index
""" graph = self.G
vertex = list(graph.keys())
address_dict = {}
total_len = len(vertex)
for index in range(len(vertex)):
address_dict[vertex[index]] = index
row = []
col = []
data = []
for element_i in graph:
weight = round(1/len(graph[element_i]),3)
row_index= address_dict[element_i]
for element_j in graph[element_i]:
col_index = address_dict[element_j]
row.append(row_index)
col.append(col_index)
data.append(weight)
row = np.array(row)
col = np.array(col)
data = np.array(data)
m = coo_matrix((data,(row,col)),shape=(total_len,total_len))
return m,vertex,address_dict def mat_all_point(self,m_mat,vertex,alpha):
"""
get E-alpha*m_mat.T
Args:
m_mat
vertex:total item and user points
alpha:the prob for random walking
Returns:
a sparse
"""
total_len = len(vertex)
row = []
col = []
data = []
for index in range(total_len):
row.append(index)
col.append(index)
data.append(1)
row = np.array(row)
col = np.array(col)
data = np.array(data)
eye_t = coo_matrix((data,(row,col)),shape=(total_len,total_len))
return eye_t.tocsr()-alpha*m_mat.tocsr().transpose() def recommend_use_matrix(self, alpha, userID, K=10,use_matrix=True):
"""
Args:
alpha:the prob for random walking
userID:the user to recom
K:recom item num
Returns:
a dic,key:itemid ,value:pr score
"""
m, vertex, address_dict = self.graph_to_m()
userID = 'user_' + str(userID)
print('add',address_dict)
if userID not in address_dict:
return []
score_dict = {}
recom_dict = {}
mat_all = self.mat_all_point(m,vertex,alpha)
index = address_dict[userID]
initial_list = [[0] for row in range(len(vertex))]
initial_list[index] = [1]
r_zero = np.array(initial_list)
res = gmres(mat_all,r_zero,tol=1e-8)[0]
for index in range(len(res)):
point = vertex[index]
if len(point.strip().split('_'))<2:
continue
if point in self.G[userID]:
continue
score_dict[point] = round(res[index],3)
for zuhe in sorted(score_dict.items(),key=operator.itemgetter(1),reverse=True)[:K]:
point,score = zuhe[0],zuhe[1]
recom_dict[point] = score
return recom_dict if __name__ == '__main__':
moviesPath = '../data/ml-1m/movies.dat'
ratingsPath = '../data/ml-1m/ratings.dat'
usersPath = '../data/ml-1m/users.dat' # usersDF = pd.read_csv(usersPath,index_col=None,sep='::',header=None,names=['user_id', 'gender', 'age', 'occupation', 'zip'])
# moviesDF = pd.read_csv(moviesPath,index_col=None,sep='::',header=None,names=['movie_id', 'title', 'genres'])
ratingsDF = pd.read_csv(ratingsPath, index_col=None, sep='::', header=None,names=['user_id', 'movie_id', 'rating', 'timestamp'])
X=ratingsDF['user_id'][:1000]
Y=ratingsDF['movie_id'][:1000]
rank = PersonalRank(X,Y).recommend_use_matrix(alpha=0.8,userID=1,K=30)
print('PersonalRank result',rank)

参考:

推荐系统概述(一)

Github

个性化召回算法实践(三)——PersonalRank算法的更多相关文章

  1. 个性化排序算法实践(三)——deepFM算法

    FM通过对于每一位特征的隐变量内积来提取特征组合,最后的结果也不错,虽然理论上FM可以对高阶特征组合进行建模,但实际上因为计算复杂度原因,一般都只用到了二阶特征组合.对于高阶特征组合来说,我们很自然想 ...

  2. 个性化排序算法实践(五)——DCN算法

    wide&deep在个性化排序算法中是影响力比较大的工作了.wide部分是手动特征交叉(负责memorization),deep部分利用mlp来实现高阶特征交叉(负责generalizatio ...

  3. 个性化召回算法实践(一)——CF算法

    协同过滤推荐(Collaborative Filtering Recommendation)主要包括基于用户的协同过滤算法与基于物品的协同过滤算法. 下面,以movielens数据集为例,分别实践这两 ...

  4. 个性化召回算法实践(四)——ContentBased算法

    ContentBased算法的思想非常简单:根据用户过去喜欢的物品(本文统称为 item),为用户推荐和他过去喜欢的物品相似的物品.而关键就在于这里的物品相似性的度量,这才是算法运用过程中的核心. C ...

  5. 个性化召回算法实践(二)——LFM算法

    LFM算法核心思想是通过隐含特征(latent factor)联系用户兴趣和物品,找出潜在的主题和分类.LFM(latent factor model)通过如下公式计算用户u对物品i的兴趣: \[ P ...

  6. 个性化排序算法实践(一)——FM算法

    因子分解机(Factorization Machine,简称FM)算法用于解决大规模稀疏数据下的特征组合问题.FM可以看做带特征交叉的LR. 理论部分可参考FM系列,通过将FM的二次项化简,其复杂度可 ...

  7. 个性化排序算法实践(二)——FFM算法

    场感知分解机(Field-aware Factorization Machine ,简称FFM)在FM的基础上进一步改进,在模型中引入类别的概念,即field.将同一个field的特征单独进行one- ...

  8. [迷宫中的算法实践]迷宫生成算法——递归分割算法

    Recursive division method        Mazes can be created with recursive division, an algorithm which wo ...

  9. 算法实践--最小生成树(Kruskal算法)

    什么是最小生成树(Minimum Spanning Tree) 每两个端点之间的边都有一个权重值,最小生成树是这些边的一个子集.这些边可以将所有端点连到一起,且总的权重最小 下图所示的例子,最小生成树 ...

随机推荐

  1. 未处理的异常:system.io.file load exception:无法加载文件或程序集“ 。。。。 找到的程序集的清单定义与程序集引用不匹配。

    问题描述: 添加控制器的时候,突然就报了这个错: Unhandled Exception: System.IO.FileLoadException: Could not load file or as ...

  2. 预训练中Word2vec,ELMO,GPT与BERT对比

    预训练 先在某个任务(训练集A或者B)进行预先训练,即先在这个任务(训练集A或者B)学习网络参数,然后存起来以备后用.当我们在面临第三个任务时,网络可以采取相同的结构,在较浅的几层,网络参数可以直接加 ...

  3. 【GStreamer开发】GStreamer基础教程03——动态pipeline

    本教程介绍pipeline的一种新的创建方式--在运行中创建,而不是在运行前一次性的创建结束. 介绍 在这篇教程里的pipeline并非在运行前就全部创建结束的.放松一下,这样做没有任何问题.如果我们 ...

  4. PHP与Cookie

    不管什么语言写的cookie,本质上没区别. cookie 常用于识别用户.cookie 是服务器留在用户计算机中的小文件.每当相同的计算机通过浏览器请求页面时,它同时会发送 cookie.通过 PH ...

  5. Go资源被墙最快解决方案

    原文链接:https://juejin.im/post/5cd945946fb9a032060c47a3,补充,最新的1.13版本go里默认就是用go module,直接设置代理即可: 注:如果没法升 ...

  6. MacOs 10.14.3 Matlab2018b 解决“找不到已安装的编译器 'Xcode Clang++'。错误使用mex,未找到合适的编译器”问题

    这是目前网上state of the art 的解决方案: 最开始用的是matlab2017,结果爆了这么一个问题“找不到已安装的编译器 'Xcode Clang++'.错误使用mex,未找到合适的编 ...

  7. 用pytorch1.0快速搭建简单的神经网络

    用pytorch1.0搭建简单的神经网络 import torch import torch.nn.functional as F # 包含激励函数 # 建立神经网络 # 先定义所有的层属性(__in ...

  8. oracle 常用sql 经典sql函数使用 sql语法

    各种树操作, 用来查询表中带有子父节点的信息 Oracle 树操作(select-start with-connect by-prior) select m.org_id from sm_organ ...

  9. C++枚举类型教案

    一.枚举类型的应用场景 只需要将需要的变量值一一列举出来,便构成一个枚举类型. 二.枚举类型的定义 ·定义方式: enum 枚举类型名字{枚举常量表}: ·关键字enum:说明接下来定义的是一个枚举类 ...

  10. Vue基础语法(样式绑定,事件处理,表单,Vue组件)

    样式绑定 事件处理 表单 Vue组件 样式绑定 <!DOCTYPE html> <html> <head> <meta charset="utf-8 ...