BPB模型概念
BPR(Bayesian Personalized Ranking)推荐模型是基于贝叶斯后验优化的个性化排序算法。从user-iem矩阵训练出多个矩阵,而且一个矩阵表示一个用户的item偏好情况来获得用对多个item的偏好关系的推荐系统。本身不优化用户对物品的评分,只是借由评分来优化用户对物品的排序。
BPR是目前主流的利用基于物品对的协同过滤技术解决OCCF问题的算法之 。
BPR的核心是针对两个物品的相对偏好排序进行建模。最终为每个用户计算其对没有过行为物品的偏好排序,从而进行个性化推荐。
由此,训练集Ds可以构建为:U ∗ I ∗ I ,Ds是三元组( u , i , j )的集合。
BPR模型特点
1、强调个性化推荐,个性化物品偏好排名
2、用后验概率优化个性化推荐的排序
3、基于梯度下降的learnBPR极大化BPR-OPT
4、一般的推荐算法强调用户对项目的打分,只存在用户和单个项目的关系,不去考虑两个项目对用户的影响力,而BPR模型从u , i , j 出发求解u , i ,j的大小。
BPR模型过程
(1)从user-item中提取item-item矩阵
?表示无签到数据,有两种情况,第一种可能本身就是negative value,用户对item不感兴趣,第二种是缺失值,发生了浏览或者购买行为但是丢失了
- 表示用户u相对于item j更喜欢item i。
BPR推荐系统会考虑positive value和negative value,也就是所有item都会被个性化ranking,即使用户对某个item缺失值这个item也能够被ranking,而不是仅仅用negative value代替缺失值。
BPR算法步骤
基本假设
1.每个用户之间的偏好行为相互独立
2.统一用户对不同物品的喜好相互独立
数据pair预处理
BPR算法将用户对物品的评分(显示反馈1,隐式反馈0)处理为一个pair集合<i,j>,其中i为评分为1的物品,j为评分为0 的物品,假设用户有M个1的评分,N个0的评分,则该用户共有M*N个pair对。
数据集就由<u,i,j>表示,相对于物品j,用户u更喜欢物品i。
优化
基于前面提到的两个基本假设,优化问题就转化为了极大化以下目标:
θ为模型参数,包括用户的latent matrix P(表示用户的隐含因子矩阵P)物品的latent matrix Q(表达物品的隐含因子矩阵Q)
基于pair-wise的偏序优化,可以避免point-wise模型在对feature-item进行预测时失效(因为feature-item在训练时全被标记为“0”)的问题。
而且feature-item包括两类:1,用户真正讨厌的;2,用户missing的。
对于某个用户来说,在训练时都被标为"0"的item,在预测时的评分也可以排序,因此不影响ranking任务的完成。
即使用pair-wise的优化方式,可以对训练时标记为“0”的item在预测时进行ranking。
但这本身是“矬子里面拔高个”,且训练数据与用户的实际偏好不符。而且,从数据量考虑,也很不经济。
import random
from collections import defaultdict
import numpy as np
from sklearn.metrics import roc_auc_score
import scores
class BPR:
user_count = 10000
item_count = 5000
latent_factors = 10
lr = 0.01
reg = 0.01
train_count = 1000
train_data_path = 'train.txt'
test_data_path = 'test.txt'
size_u_i = user_count * item_count
U = np.random.rand(user_count, latent_factors) * 0.01
V = np.random.rand(item_count, latent_factors) * 0.01
biasV = np.random.rand(item_count) * 0.01
test_data = np.zeros((user_count, item_count))
test = np.zeros(size_u_i)
predict_ = np.zeros(size_u_i)
def load_data(self, path):
user_ratings = defaultdict(set)
with open(path, 'r') as f:
for line in f.readlines():
u, i = line.split(" ")
u = int(u)
i = int(i)
user_ratings[u].add(i)
return user_ratings
def load_test_data(self, path):
file = open(path, 'r')
for line in file:
line = line.split(' ')
user = int(line[0])
item = int(line[1])
self.test_data[user - 1][item - 1] = 1
def train(self, user_ratings_train):
for user in range(self.user_count):
# sample a user
u = random.randint(1, self.user_count)
if u not in user_ratings_train.keys():
continue
# sample a positive item from the observed items
i = random.sample(user_ratings_train[u], 1)[0]
# sample a negative item from the unobserved items
j = random.randint(1, self.item_count)
while j in user_ratings_train[u]:
j = random.randint(1, self.item_count)
u -= 1
i -= 1
j -= 1
r_ui = np.dot(self.U[u], self.V[i].T) + self.biasV[i]
r_uj = np.dot(self.U[u], self.V[j].T) + self.biasV[j]
r_uij = r_ui - r_uj
loss_func = -1.0 / (1 + np.exp(r_uij))
# update U and V
self.U[u] += -self.lr * (loss_func * (self.V[i] - self.V[j]) + self.reg * self.U[u])
self.V[i] += -self.lr * (loss_func * self.U[u] + self.reg * self.V[i])
self.V[j] += -self.lr * (loss_func * (-self.U[u]) + self.reg * self.V[j])
# update biasV
self.biasV[i] += -self.lr * (loss_func + self.reg * self.biasV[i])
self.biasV[j] += -self.lr * (-loss_func + self.reg * self.biasV[j])
def predict(self, user, item):
predict = np.mat(user) * np.mat(item.T)
return predict
def main(self):
user_ratings_train = self.load_data(self.train_data_path)
self.load_test_data(self.test_data_path)
for u in range(self.user_count):
for item in range(self.item_count):
if int(self.test_data[u][item]) == 1:
self.test[u * self.item_count + item] = 1
else:
self.test[u * self.item_count + item] = 0
# training
for i in range(self.train_count):
self.train(user_ratings_train)
predict_matrix = self.predict(self.U, self.V)
# prediction
self.predict_ = predict_matrix.getA().reshape(-1)
self.predict_ = pre_handel(user_ratings_train, self.predict_, self.item_count)
auc_score = roc_auc_score(self.test, self.predict_)
print('AUC:', auc_score)
# Top-K evaluation
scores.topK_scores(self.test, self.predict_, 5, self.user_count, self.item_count)
def pre_handel(set, predict, item_count):
# Ensure the recommendation cannot be positive items in the training set.
for u in set.keys():
for j in set[u]:
predict[(u - 1) * item_count + j - 1] = 0
return predict
if __name__ == '__main__':
bpr = BPR()
bpr.main()