在推荐算法中,排序学习(Learning to Rank,LTR)是一个重要的技术,用于确定推荐系统中给用户推荐物品的排序顺序。排序学习的目标是通过机器学习方法,将候选物品进行排序,以便将最相关和最有吸引力的物品展示给用户。
在排序学习中,通常使用的是有监督学习方法。
排序学习的关键挑战之一是特征选择和构建。选择合适的特征可以提高排序模型的效果。常用的特征包括用户的个人信息、历史行为、社交网络关系等,以及物品的属性、标签等。此外,还可以考虑上下文信息,如时间、地理位置等,以提供更精准的推荐。
传统的检索模型所考虑的因素并不多,主要是利用词频、逆文档频率和文档长度、文档重要度这几个因子来人工拟合排序公式,且其中大多数模型都包含参数,也就需要通过不断的实验确定最佳的参数组合,以此来形成相关性打分。这种方式非常简单高效,但是也同时存在很多问题:
LTR 则是基于特征,通过机器学习算法训练来学习到最佳的拟合公式,相比传统的排序方法,优势有很多:
排序学习在推荐领域扮演着关键的角色,它的作用如下:
总的来说,排序学习在推荐领域的作用是优化推荐结果的排序顺序,提供个性化、准确和满意度高的推荐体验。它能够改善推荐系统的性能,提升用户满意度,增加用户粘性,促进业务增长。
推荐的整个流程可以分为召回、排序、重排序这三个阶段,通俗来说,召回就是找到用户可能喜欢的几百条资讯,排序就是对这几百条资讯利用机器学习的方法预估用户对每条资讯的偏好程度,一般以点击率衡量,也就是点击率预估问题。不难看出,排序学习在推荐领域主要用于排序阶段,最常用的是 Pointwise 排序方法;重排序更多是考虑业务逻辑的规则过滤,如在推荐结果的多样性、时效性、新颖性等方面进行控制。
在没有 Learning to Rank 之前,基于内容的推荐算法和基于邻域的协同过滤虽然也能预测用户的偏好,可以帮助用户召回大量的物品,但是我们必须知道,推荐系统中更重要的目标是排序,因为真正最后推荐给用户的只有少数物品,我们更关心这些召回物品中哪些才是用户心中更加喜欢的,也就是排序更靠前,这便是 Top-N 推荐。
排序学习的模型通常可以分为单点法(Pointwise Approach)、配对法(Pairwise Approach)和列表法(Listwise Approach)三大类。这些方法针对排序问题的不同方面进行建模和处理。
这三种方法各有特点,适用于不同的排序场景和问题。单点法简单直接,适用于简单排序问题;配对法考虑物品之间的相对顺序,适用于处理排序差异大的样本;列表法直接优化整个排序列表,适用于追求整体排序质量的场景。根据实际情况和需求,可以选择合适的排序学习方法来解决推荐排序问题。
Pointwise方法是排序学习中的一种常见方法,也称为单点法。它将排序问题转化为一个回归或分类问题,通过对每个样本进行独立的预测和排序。
在Pointwise方法中,每个样本都被视为一个独立的实例,模型的输入是样本的特征,输出是预测的排序分数或概率。具体步骤如下:
Pointwise方法的优点在于:
简单、直接,并且易于实现。它适用于简单的排序问题,特别是当物品之间的差异较小,且样本的数量较大时,可以取得不错的结果。
然而,Pointwise方法忽略了物品之间的相对顺序和排序的整体性,可能无法充分利用物品之间的关系和排序的结构信息。在一些复杂的排序问题中,配对法(Pairwise Approach)和列表法(Listwise Approach)通常更加适用,可以更好地处理排序问题。
Pairwise方法是排序学习中的一种常见方法,也被称为配对法。它通过比较和排序样本中的配对来进行排序学习,关注物品之间的相对顺序,而不仅仅是单个样本的排序分数。
在Pairwise方法中,模型的输入是一对物品的特征差异,输出是判断哪个物品更适合排在前面的分数。具体步骤如下:
Pairwise方法相比于Pointwise方法,更加关注物品之间的相对排序关系,能够更好地处理排序问题。它通过比较配对样本中的物品,学习到它们之间的相对排序偏好。
优点:
缺点:
常见的Pairwise方法包括RankNet、RankBoost和RankSVM等。这些方法通过学习特征差异的权重和模型参数,可以建模物品之间的相对排序偏好。它们在推荐系统和信息检索等领域得到了广泛应用,并取得了良好的排序效果。
Pairwise方法能够更好地处理物品之间的相对排序关系,相比于Pointwise方法能够充分利用样本中的排序信息。然而,Pairwise方法需要构造配对样本,会增加训练样本的数量,并且计算复杂度也相对较高。在一些复杂的排序问题中,列表法(Listwise Approach)通常更适用,可以直接优化整个排序列表的质量。
Listwise方法是排序学习中的一种常见方法,也被称为列表法。它直接优化整个排序列表的质量,考虑了整体排序顺序,相比于Pointwise和Pairwise方法,更加全面和综合地处理排序问题。
在Listwise方法中,模型的输入是一组物品的特征向量,输出是对这组物品的排序得分。具体步骤如下:
Listwise方法的优点在于直接优化整个排序列表的质量,考虑了物品之间的全局排序关系。相比于Pointwise和Pairwise方法,Listwise方法能够更好地捕捉到整体排序结构中的复杂模式和关联信息。
然而,Listwise方法也面临一些挑战和限制。由于需要直接优化整个排序列表,Listwise方法的训练和优化过程相对复杂,计算复杂度较高。此外,Listwise方法对于大规模数据集和高维特征空间可能存在一定的挑战。
尽管如此,Listwise方法在推荐系统、搜索引擎和广告排序等领域仍然得到广泛应用,并且在提高排序质量和用户体验方面具有显著效果。
Listwise方法是排序学习中最为综合和全面的方法之一,它通过直接优化整个排序列表的质量来解决排序问题。相比于Pointwise和Pairwise方法,Listwise方法能够更充分地利用排序列表的结构信息,提高排序的准确性和效果。
Listwise方法的特点如下:
BPR(Bayesian Personalized Ranking)算法是一种常用的排序学习算法,通过建模个性化排序问题来进行推荐。它基于概率模型,通过最大化物品排名的后验概率来学习用户对物品的偏好。尽管BPR算法存在一些限制,但它在大规模数据集和高维特征空间中具有优势,并在推荐系统中得到广泛应用。
BPR算法的核心思想是将排序问题转化为二分类问题,通过比较用户对两个物品的偏好来建立排序模型。具体步骤如下:
BPR算法有以下的限制:
在很多推荐场景中,我们都是基于现有的用户和商品之间的一些数据,得到用户对所有商品的评分,选择高分的商品推荐给用户,这是funkSVD之类算法的做法,使用起来也很有效。但是在有些推荐场景中,我们是为了在千万级别的商品中推荐个位数的商品给用户,此时,我们更关心的是用户来说,哪些极少数商品在用户心中有更高的优先级,也就是排序更靠前。也就是说,我们需要一个排序算法,这个算法可以把每个用户对应的所有商品按喜好排序。BPR就是这样的一个我们需要的排序算法。
U代表所有用户user集合;I代表所有物品item集合;S代表所有用户的隐式反馈。
可知,⊆×。
如下图所示,只要用户对某个物品产生过行为(如点击、收藏、加入购物车等),就标记为+
,所有+
样本构成了S。那些为观察到的数据(即用户没有产生行为的数据)标记为?
。
因为是基于贝叶斯的 Pairwise 方法,BPR 有两个基本假设:
在原始论文中,BPR 用来解决隐式反馈的推荐排序问题,假设有用户集 U 和物品集 I,当用户 u(∈)在物品展示页面点击了物品 i(∈)却没有点击同样曝光在展示页面的物品 j(∈),则说明对于物品 i 和物品 j,用户 u 可能更加偏好物品 i,用 Pairwise 的思想则是物品 i 的排序要比物品 j 的排序更靠前,这个偏序关系可以写成一个三元组 <,,>,为了简化表述,我们用 > 符号表示用户 u 的偏好,<,,> 可以表示为:>。单独用 > 代表用户 u 对应的所有商品中两两偏序关系,可知 >⊂2,且 > 满足下面的特性:
记 S 为所有用户对所有物品发生的隐式反馈集合,这里针对隐式反馈问题对评分矩阵进行了简化,即点击过则视为正例,记为 1;没点击过则视为负例,通常会置为 0,如下图所示:
对该数据进行模型拟合。这意味着该模型被优化为预测S中元素的值为1,其余元素的值为0。这种方法的问题在于,在训练过程中,模型未来应该排序的所有元素((U ×I) \ S)都以负反馈的形式呈现给学习算法。这意味着一个具有足够表达能力(可以精确拟合训练数据)的模型根本无法排序,因为它预测的只是0。这种机器学习方法能够预测排名的唯一原因是防止过拟合的策略,比如正则化。
我们知道,矩阵分解是通过预测用户对候选物品的评分,然后根据这个预测评分去排序,最后再推荐给用户。这种方法是一种典型的 Pointwise 方法,无论是预测评分还是预测隐式反馈,本质上都是在预测用户对一个物品的偏好程度。
但是这种方法有很大的问题,因为很多时候我们只能收集到少数正例样本,剩下的数据其实是真实负例和缺失值的混合构成(这里的缺失值是指训练数据中除正例和负例外的未知数据,可以理解为未曝光或者曝光了的但是用户可能没有注意到缺失数据,所以缺失值中的样本即有可能是正例,也有可能是负例),而我们用这种方法构建训练数据的时候,往往无法确定负例到底是哪些,就只能把除正例以外的其他部分都当作是负例,这就会使得训练数据中负例的一部分其实是缺失值。把缺失值当作是负样本,再以预测误差为评判标准去使劲逼近这些样本。逼近正样本没问题,但是同时逼近的负样本只是缺失值而已,真正呈现在用户面前,并不能确定是不喜欢还是喜欢。而且,这样的模型仅能预测正例或负例,对于类别内的样本无法深入区别其重要性,不利于排序。
当然,对于这种情况,我们也可以用一些其他方法来规避这些问题,比如负例采样,比如按预测概率排序,但这些方法也仅仅是 “缓兵之计”,对于解决排序问题来说并不完善。
我们使用了一种不同的方法,使用项目对作为训练数据,并对正确排名项目对进行优化,而不是对单个项目进行评分,因为这比仅仅用负值替换缺失值更好地代表了问题。从S开始,我们尝试为≻u的每个用户部分进行重构。如果一个项目i被用户u浏览过——即:(u, i)∈S -那么我们假设用户更喜欢这个项目而不是其他所有未观察到的项目。例如,在下图中,用户u1看过物品i2,但没有看过物品i1,所以我们假设这个用户喜欢物品i2胜过物品i1: i2 ≻u i1。对于两个都被用户看到的物品,我们无法推断出任何偏好。对于用户尚未见过的两个项目也是如此(例如,用户u1)的项目i1和i4。为了形式化,我们通过以下方式创建训练数据DS:U ×I ×I:
其中 (,,)∈ 的表示是用户 u 更喜欢 i 胜过 j;∧ 是离散数学中的符号,表示 and,等同于 ∩;∖+ 表示物品集 除正例外其他剩余的样本。
首先 BPR 利用 Pairwise 的思想来构建偏序关系,它依然没有从无反馈数据中去区分负例样本和缺失值,不过和之前的方法不一样的是,BPR 不是单纯地将无反馈数据都看做是负例,而是与正例结合一起来构建偏序关系。这里的核心假设是,某用户对他有过反馈的物品的偏好程度一定比没有反馈过的物品高(这里的反馈一般指隐式反馈,如点击浏览等,不涉及负反馈),未反馈的物品包括真正的负例以及缺失值。BPR 试图通过用户的反馈矩阵 S 来为每一个用户构建出完整的偏序关系,也称全序关系,用 > 表示。
图左边的矩阵是反馈数据集 S,+
代表有反馈数据,?
代表无反馈数据。
图右边的小矩阵是每个用户的偏序关系矩阵,+
代表用户喜欢 i 胜过喜欢 j,作为训练数据的正例;-
代表用户喜欢 j 胜过喜欢 i,作为训练数据的负例;?
代表同类数据无法确定偏序关系。
相比于单点法的矩阵分解,基于配对法的 BPR 有两大优势:
通过上面的样本构造便可以得到我们的训练样本,每一个样本都是由用户 u、物品 i、物品 j、以及两个物品的偏序关系 y 构成的四元组 (,,,),其中 y 表示两个物品偏序关系并作为 label,通常用 0、1 标注,如物品 i 的偏序大于物品 j 的,则 y 标注为 1,反之则标注为 0。
接下来我们来看看如何构建目标函数。
首先我们必须清楚,这回的目标不再是像 MF 的均方根误差最小,而是需要满足物品相对排序最佳。一个简单的思路就是当得到了用户和物品的推荐分数后,就可以计算四元组的样本中,物品 i 和物品 j 的分数差,这个分数可能是正数,也可能是负数,也可能是 0。
我们可以通过预测分数的差值的大小来判断排序是否良好,最希望的情况是:如果物品 i 和物品 j 偏序关系的标注为 1,那么希望两者分数之差是个正数,而且越大越好;如果物品 i 和物品 j 的相对顺序是 0,则希望分数之差是负数,且越小越好。
可以看出,这种方式来构建目标函数的话,依然是需要预测分数的,而这个分数的预测最常用的就是矩阵分解的方法,原论文中还用到了另外一种方法,叫自适应 K 近邻(Adaptive KNN)。这里我们这里只看矩阵分解。当然 BPR 中的预测的分数无论在显示反馈还是隐式反馈情境下,都只是用来辅助度量相对排序,其评分并不作为实际的意义上的评分。
同 FunkSVD 矩阵一样,预测分数 ̂ 的问题可以视作对矩阵进行估计 :×。通过矩阵分解我们可以将目标矩阵 X 近似分解为两个低秩矩阵 (||×) 和 (||×) 的乘积:
其中 是特征矩阵的维度。 中的每一行 可以视作描述一个用户的特征向量,类似地 中的每一行 可以视作描述物品 的特征向量。因此,预测公式也可以写成:
其中,⟨⋅,⋅⟩ 代表向量点积。
有了这些基础的理解后,接下来我们来看 BPR 如何在矩阵分解的基础上来构建排序的目标函数,BPR 的过程实际上是一种优化过程。
BPR 基于最大后验估计(,|>) 来求解模型参数 ,,这里我们用 来表示参数 和 , > 代表用户 u 对应的所有商品的全序关系,则优化目标是 (|>)。根据贝叶斯公式,我们有:
由于我们求解假设了用户的排序和其他用户无关,那么对于任意一个用户 u 来说,(>) 对所有的物品一样,所以有:
这个优化目标转化为两部分。第一部分和样本数据集 有关,第二部分和样本数据集 无关。
对于第一部分,由于我们假设每个用户之间的偏好行为相互独立,同一用户对不同物品的偏序相互独立,所以有:
其中,
根据上面讲到的完整性和反对称性,优化目标的第一部分可以简化为:
而对于 (>|) 这个概率,我们可以使用下面这个式子来代替:
其中,() 是 sigmoid 函数,即:
这里你也许会问,为什么可以用这个 sigmoid 函数来代替呢? 其实这里的代替可以选择其他的函数,不过式子需要满足 BPR 的完整性,反对称性和传递性。原论文作者这么做除了是满足这三个性质外,另一个原因是为了方便优化计算。
对于 ̂ () 这个式子,我们要满足当 > 时,̂ ()>0, 反之当 > 时,̂ ()<0,最简单的表示这个性质的方法就是:
而 ̂ () , ̂ (),就是我们的矩阵 ̂ 对应位置的值。这里为了方便,我们不写 ,这样上式可以表示为:
注意上面的这个式子也不是唯一的,只要可以满足上面提到的当 > 时,̂ ()>0,以及对应的相反条件即可。这里我们仍然按原论文的式子来。
最终,我们的第一部分优化目标转化为:
对于第二部分 (),原作者大胆使用了贝叶斯假设,即这个概率分布符合正态分布,且对应的均值是 0,协方差矩阵是 , 即
后面我们做优化时,需要计算 (),而对于上面假设的这个多维正态分布,其对数和 ||||2 成正比。即:
最终对于我们的最大对数后验估计函数:
由于
再添加负号以求最小损失,并将 拆分,由此我们便得到 BPR 最终的损失函数:
BPR 可以用常用的梯度下降或者牛顿法来优化,我们以梯度下降为例:
对 求导:
这样我们可以求出:
下面简要总结下 BPR 的算法训练流程:
输入:训练集 D 三元组,学习率 , 正则化参数 ,分解矩阵维度 k。
输出:模型参数,矩阵 P、Q
当我们拿到 P、Q 后,就可以计算出每一个用户 u 对应的任意一个商品的排序分:̂ =,最终选择排序分最高的若干商品输出。
BPR 其实可以看作在准确率之上更加高层的排序算法框架,可以将 Pointwise 变成 Pairwise,因此,实际上它不仅仅局限于矩阵分解,理论上 Pointwise 算法都可以使用 BPR,难点在于求损失函数。另外,基于 MF 的 BPR 经过了很好的验证,使用范围很广,所以但凡适用于 MF 的推荐场景,基本都可以在其上层添加 BPR 进行排序优化。
在实际产品中,BPR 之类的推荐排序在海量数据中选择极少量数据做推荐的时候有优势,因此在很多互联网大厂中应用也很广泛。
本代码使用ml-100k数据集进行训练与测试,提取出ml-100k数据集中评分高于4分的(满分5分)作为用户对该电影进行过标记(好印象)。
总共包含943个用户,1682部电影。
BPR主函数代码,BPR_basical.py:
import random
from collections import defaultdict
import numpy as np
from sklearn.metrics import roc_auc_score
import scores
'''
函数说明:BPR类(包含所需的各种参数)
Parameters:
无
Returns:
无
'''
class BPR:
# 数据集为ml-100k选取用户评分高于4分的作为用户标记过的电影
#用户数
user_count = 943
#项目数
item_count = 1682
#k个主题,k数
latent_factors = 20
#步长α
lr = 0.01
#参数λ
reg = 0.01
#训练次数
train_count = 10000
#训练集
train_data_path = 'train.txt'
#测试集
test_data_path = 'test.txt'
#U-I的大小
size_u_i = user_count * item_count
# 随机设定的U,V矩阵(即公式中的Puk和Qik)矩阵
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
#生成一个用户数*项目数大小的全0矩阵
test_data = np.zeros((user_count, item_count))
# print("test_data_type",type(test_data))
#生成一个一维的全0矩阵
test = np.zeros(size_u_i)
#再生成一个一维的全0矩阵
predict_ = np.zeros(size_u_i)
#获取U-I数据对应
'''
函数说明:通过文件路径,获取U-I数据
Paramaters:
输入要读入的文件路径path
Returns:
输出一个字典user_ratings,包含用户-项目的键值对
'''
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
'''
函数说明:通过文件路径,获取测试集数据
Paramaters:
测试集文件路径path
Returns:
输出一个numpy.ndarray文件(n维数组)test_data,其中把含有反馈信息的数据置为1
'''
#获取测试集的评分矩阵
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
'''
函数说明:对训练集数据字典处理,通过随机选取,(用户,交互,为交互)三元组,更新分解后的两个矩阵
Parameters:
输入要处理的训练集用户项目字典
Returns:
对分解后的两个矩阵以及偏置矩阵分别更新
'''
def train(self, user_ratings_train):
for user in range(self.user_count):
# 随机获取一个用户
u = random.randint(1, self.user_count) #找到一个user
# 训练集和测试集的用户不是全都一样的,比如train有948,而test最大为943
if u not in user_ratings_train.keys():
continue
# 从用户的U-I中随机选取1个Item
i = random.sample(user_ratings_train[u], 1)[0] #从随机获取的用户u的评分列表中,找到一个被评分item
# 随机选取一个用户u没有评分的项目
j = random.randint(1, self.item_count)
while j in user_ratings_train[u]:
j = random.randint(1, self.item_count) #从所有电影item列表中,找到一个没有被用户u评分过的item,这样可以保证用户u更加偏好物品i
#构成一个三元组(uesr,item_have_score,item_no_score)
# python中的取值从0开始
u = u - 1
i = i - 1
j = j - 1
#BPR
r_ui = np.dot(self.U[u], self.V[i].T) + self.biasV[i] # .T表示将原来的向量转置
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))
# 更新2个矩阵
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])
# 更新偏置项
self.biasV[i] += -self.lr * (loss_func + self.reg * self.biasV[i])
self.biasV[j] += -self.lr * (-loss_func + self.reg * self.biasV[j])
'''
函数说明:通过输入分解后的用户项目矩阵得到预测矩阵predict
Parameters:
输入分别后的用户项目矩阵
Returns:
输出相乘后的预测矩阵,即我们所要的评分矩阵
'''
def predict(self, user, item):
predict = np.mat(user) * np.mat(item.T)
return predict
#主函数
def main(self):
#获取U-I的{1:{2,5,1,2}....}数据
user_ratings_train = self.load_data(self.train_data_path)
#获取测试集的评分矩阵
self.load_test_data(self.test_data_path)
#将test_data矩阵拍平
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
#训练
for i in range(self.train_count):
print(f'--------共{self.train_count}轮训练,目前正在进行第{i + 1}轮测试--------')
self.train(user_ratings_train) #训练10000次完成
predict_matrix = self.predict(self.U, self.V) #将训练完成的矩阵內积
# 输出给用户0推荐的5部电影
predict_matrix_np = predict_matrix.getA()
sort_0 = np.argsort(-predict_matrix_np[0])
print('给用户0推荐的5部电影编号为:')
for i in range(5):
print(sort_0[i])
# 预测
self.predict_ = predict_matrix.getA().reshape(-1) #.getA()将自身矩阵变量转化为ndarray类型的变量
print("predict_new", self.predict_)
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)
'''
函数说明:对结果进行修正,即用户已经产生交互的用户项目进行剔除,只保留没有产生用户项目的交互的数据
Paramaters:
输入用户项目字典集,以及一维的预测矩阵,项目个数
Returns:
输出修正后的预测评分一维的预测矩阵
'''
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()
计算模型指标的代码:scores.py(与BPR_basical.py文件放在同一个子目录下):
# -*- coding: utf-8 -*-
"""scores.ipynb
Automatically generated by Colaboratory.
Original file is located at
https://colab.research.google.com/drive/17qoo1U4Iw58GRDDIyCaB2GmbRUg1gTPd
"""
import heapq
import numpy as np
import math
#计算项目top_K分数
def topK_scores(test, predict, topk, user_count, item_count):
PrecisionSum = np.zeros(topk+1)
RecallSum = np.zeros(topk+1)
F1Sum = np.zeros(topk+1)
NDCGSum = np.zeros(topk+1)
OneCallSum = np.zeros(topk+1)
DCGbest = np.zeros(topk+1)
MRRSum = 0
MAPSum = 0
total_test_data_count = 0
for k in range(1, topk+1):
DCGbest[k] = DCGbest[k - 1]
DCGbest[k] += 1.0 / math.log(k + 1)
for i in range(user_count):
user_test = []
user_predict = []
test_data_size = 0
for j in range(item_count):
if test[i * item_count + j] == 1.0:
test_data_size += 1
user_test.append(test[i * item_count + j])
user_predict.append(predict[i * item_count + j])
if test_data_size == 0:
continue
else:
total_test_data_count += 1
predict_max_num_index_list = map(user_predict.index, heapq.nlargest(topk, user_predict))
predict_max_num_index_list = list(predict_max_num_index_list)
hit_sum = 0
DCG = np.zeros(topk + 1)
DCGbest2 = np.zeros(topk + 1)
for k in range(1, topk + 1):
DCG[k] = DCG[k - 1]
item_id = predict_max_num_index_list[k - 1]
if user_test[item_id] == 1:
hit_sum += 1
DCG[k] += 1 / math.log(k + 1)
# precision, recall, F1, 1-call
prec = float(hit_sum / k)
rec = float(hit_sum / test_data_size)
f1 = 0.0
if prec + rec > 0:
f1 = 2 * prec * rec / (prec + rec)
PrecisionSum[k] += float(prec)
RecallSum[k] += float(rec)
F1Sum[k] += float(f1)
if test_data_size >= k:
DCGbest2[k] = DCGbest[k]
else:
DCGbest2[k] = DCGbest2[k-1]
NDCGSum[k] += DCG[k] / DCGbest2[k]
if hit_sum > 0:
OneCallSum[k] += 1
else:
OneCallSum[k] += 0
# MRR
p = 1
for mrr_iter in predict_max_num_index_list:
if user_test[mrr_iter] == 1:
break
p += 1
MRRSum += 1 / float(p)
# MAP
p = 1
AP = 0.0
hit_before = 0
for mrr_iter in predict_max_num_index_list:
if user_test[mrr_iter] == 1:
AP += 1 / float(p) * (hit_before + 1)
hit_before += 1
p += 1
MAPSum += AP / test_data_size
print('MAP:', MAPSum / total_test_data_count)
print('MRR:', MRRSum / total_test_data_count)
print('Prec@5:', PrecisionSum[4] / total_test_data_count)
print('Rec@5:', RecallSum[4] / total_test_data_count)
print('F1@5:', F1Sum[4] / total_test_data_count)
print('NDCG@5:', NDCGSum[4] / total_test_data_count)
print('1-call@5:', OneCallSum[4] / total_test_data_count)
return
数据集也都与上面两个文件放在同一个文件夹下,数据集地址:
链接: https://pan.baidu.com/s/1pB0YDG6zhLT33FKn1n3bUA 提取码: csym
配置好环境后直接运行BPR_basical.py文件即可。
结果展示:
贝叶斯个性化排序(BPR)算法小结
用tensorflow学习贝叶斯个性化排序(BPR)
推荐系统中的排序学习
BPR 贝叶斯个性化排序
BPR贝叶斯个性化推荐算法—推荐系统基础算法(含python代码实现以及详细例子讲解)
推荐算法之贝叶斯个性化排序 BPR
原论文