推荐系统 | 重排序 —— 贝叶斯个性化排序(Bayesian Personalized Ranking, BPR)

目录

1.“排序推荐算法”分类

2.贝叶斯个性化排序(Bayesian Personalized Ranking, BPR)

2.1.应用场景

2.2.BPR建模思路

2.2.1.【符号说明】

2.2.2.【BPR算法前提假设】

2.2.3.【BPR算法的目标函数的形式化定义】

2.3.BPR优化目标函数

2.3.1.【总体目标函数求解】

2.3.2.【第一部分:似然概率求解】

2.3.3.【第二部分: 先验概率求解】

2.3.4.【综上:最终的优化目标函数形式】

2.4.BPR算法流程

2.5.总结

3.BPR程序(Python-TensorFlow)


1.“排序算法”分类

全量召回得到粗排结果,再使用更精准但是性能慢的算法进行排序;相当于算法的融合,得到更佳的推荐结果。初期你想直接用协同过滤的结果做推荐也不是不可以,但要考虑实时推荐的性能和优化推荐结果。

2.贝叶斯个性化排序(Bayesian Personalized Ranking, BPR)

2.1.应用场景

传统的近邻协同过滤推荐算法的核心思想是基于现有“用户-商品评分矩阵”计算用户之间的相似度,并通过评分预测公式对整个矩阵中的缺失评分进行预测,并依据评分的高低对用户进行推荐,实践证明使用起来也很有效。但是,在千万级别的商品中用户感兴趣的商品仅仅是个位数,通过对评分矩阵中的全部缺失评分进行预测的方式耗时耗力(笨方法)。因此,排序推荐更关心的是对于用户来说哪些极少数商品在用户心中有更高的优先级,也就是排序更靠前。

【现有基于隐式反馈数据的排序推荐算法问题】

BPR是基于用户的隐式反馈(传统处理方法:产生交互行为的记为1,未产生交互行为的记为0,并将交互行为矩阵作输入给模型进行训练。于是产生的问题是,我们希望模型在以后预测的缺失值,在训练时却都被认为是负类数据),为用户提供物品的推荐,并且是直接对排序进行优化

2.2.BPR建模思路

2.2.1.【符号说明】

<u,i,j>:用户u在同时有物品i和j的情况下点击了i,也即:对用户u来说,i的排序要比j靠前

m:用户反馈<u,i,j>的个数,也即训练样本的个数

:表示用户u的偏好,相比于商品j,用户u更加偏好于商品i(满足:完整性、反对称性和传递性),因此便可为每个用户构造大小为I×I的偏好矩阵

:所有用户偏好对构成的训练集(也可直接表示为D)

:表示BRP算法最终得到的预测排序矩阵

:分别表示分解后期望得到的用户矩阵和商品矩阵(k为自定义大小且远小于用户或商品的量级,矩阵的理解同SVD)

2.2.2.【BPR算法前提假设】

①每个用户之间的偏好行为相互独立,即用户u在商品i和j之间的偏好和其他用户无关

②同一用户对不同物品的偏序相互独立,也就是用户u在商品i和j之间的偏好和其他的商品无关

2.2.3.【BPR算法的目标函数的形式化定义】

总体目标函数如下: 

由于BPR是基于用户维度的,所以对于任意一个用户u,对应的任意一个物品i有:

 其中,表示W矩阵中对应用户行的第f个元素,表示H矩阵中对应行的第f个元素

 

2.3.BPR优化目标函数

2.3.1.【总体目标函数求解】

由贝叶斯定理可知:

(1).要解决的问题:求解最优参数W和H(表示为θ) → 事件 A

(2).已知条件:用户u对应的所有商品的全序关系 → 事件 B

故优化目标函数可表示为:

 由于假设了用户的偏好排序和其他用户无关,那么对于任意一个用户u,对所有的物品一样,所以有“后验概率∝(正比于)似然概率×先验概率”:

综上优化目标可表示为“似然概率”和“先验概率”两部分。

2.3.2.【第一部分:似然概率求解】

由“BPR算法的前提假设”可得:

 基于符号说明中所介绍的“用户偏好的性质(完整性+反对称性)”上式可简化为:

【一】 上式中通过sigmoid函数表示,原因主要有两个:

① sigmoid函数满足“BPR算法的用户偏好性质” → 单调连续函数

② 方便后续进行优化计算,也就是方便后续求导

【二】上式中通过表示(其中分别表示中对应位置的值,也即“BPR算法的目标函数的形式化定义”中的最后一个公式),原因:

① 当;反之,

综上,第一部分的优化结果最终表示为:

2.3.3.【第二部分: 先验概率求解】

先验概率表示我们在不知道B事件(用户的偏好排序)的前提下,对A事件(θ)概率的主观判断。故原作者大胆使用了贝叶斯假设,即这个概率分布符合正态分布,且对应的均值是0,协方差矩阵是,即:

 而对于上式中假设的多维正态分布,其取对数之后与成正比(例如:下图为标准正态分布f(x)和取对数后的函数log2(f(x))图像),故上式可进一步转化为:

2.3.4.【综上:最终的优化目标函数形式】

 上式可通过“随机梯度下降法”或者“牛顿法”等优化算法对其进行优化。以“随机梯度下降算法”为例,θ的导数可表示为:

 由于:

 故可得:

因此,对应的各模型参数的迭代公式可表示为(其中α表示学习速率):

接下来则可以迭代更新各个参数了。

2.4.BPR算法流程

输入:训练集D三元组<u,i,j,label>,学习速率=α, 正则化参数=λ,分解矩阵维度=k

输出:模型参数,矩阵W,H


 

①. 随机初始化矩阵W,H

②. 迭代更新模型参数

③. 如果W,HW,H收敛,则算法结束,输出W,H,否则回到步骤②

④. 当得到最优的W,H后,就可以计算出每一个用户u对应的任意一个商品的排序分:,最终选择排序

分最高的若干商品输出。

2.5.总结

BPR是基于矩阵分解的一种排序算法,但是和funkSVD之类的算法比,其不是做全局的评分优化,而是针对每一个用户自己的商品喜好分贝做排序优化。因此在迭代优化的思路上完全不同。同时对于训练集的要求也是不一样的,funkSVD只需要用户物品对应评分数据二元组做训练集,而BPR则需要用户对商品的喜好排序三元组做训练集。

3.BPR程序(Python-TensorFlow)

参考博客:https://www.cnblogs.com/pinard/p/9163481.html

# --------------------库及原始数据的加载(MovieLens 100K)
import numpy
import tensorflow as tf
import os
import random
from collections import defaultdict

def load_data(data_path):
    user_ratings = defaultdict(set)
    max_u_id = -1
    max_i_id = -1
    with open(data_path, 'r') as f:
        for line in f.readlines():
            u, i, _, _ = line.split("\t")
            u = int(u)
            i = int(i)
            user_ratings[u].add(i)
            max_u_id = max(u, max_u_id)
            max_i_id = max(i, max_i_id)
    print ("max_u_id:", max_u_id)
    print ("max_i_id:", max_i_id)
    return max_u_id, max_i_id, user_ratings
    

data_path = os.path.join('D:\\tmp\\ml-100k', 'u.data')
user_count, item_count, user_ratings = load_data(data_path)


# --------------------挑选构造测试集三元组的数据
def generate_test(user_ratings):
    user_test = dict()
    for u, i_list in user_ratings.items():
        user_test[u] = random.sample(user_ratings[u], 1)[0]
    return user_test

user_ratings_test = generate_test(user_ratings)


# --------------------构造训练集三元组数据
def generate_train_batch(user_ratings, user_ratings_test, item_count, batch_size=512):
    t = []
    for b in range(batch_size):
        u = random.sample(user_ratings.keys(), 1)[0]
        i = random.sample(user_ratings[u], 1)[0]
        while i == user_ratings_test[u]:
            i = random.sample(user_ratings[u], 1)[0]
        
        j = random.randint(1, item_count)
        while j in user_ratings[u]:
            j = random.randint(1, item_count)
        t.append([u, i, j])
    return numpy.asarray(t)


# --------------------构造测试集三元组数据
def generate_test_batch(user_ratings, user_ratings_test, item_count):
    for u in user_ratings.keys():
        t = []
        i = user_ratings_test[u]
        for j in range(1, item_count+1):
            if not (j in user_ratings[u]):
                t.append([u, i, j])
        yield numpy.asarray(t)


# --------------------构造BPR数据流
def bpr_mf(user_count, item_count, hidden_dim):
    u = tf.placeholder(tf.int32, [None])
    i = tf.placeholder(tf.int32, [None])
    j = tf.placeholder(tf.int32, [None])

    with tf.device("/cpu:0"):
        user_emb_w = tf.get_variable("user_emb_w", [user_count+1, hidden_dim], 
                            initializer=tf.random_normal_initializer(0, 0.1))
        item_emb_w = tf.get_variable("item_emb_w", [item_count+1, hidden_dim], 
                                initializer=tf.random_normal_initializer(0, 0.1))
        
        u_emb = tf.nn.embedding_lookup(user_emb_w, u)
        i_emb = tf.nn.embedding_lookup(item_emb_w, i)
        j_emb = tf.nn.embedding_lookup(item_emb_w, j)
    
    # MF predict: u_i > u_j
    x = tf.reduce_sum(tf.multiply(u_emb, (i_emb - j_emb)), 1, keep_dims=True)
    
    # AUC for one user:
    # reasonable iff all (u,i,j) pairs are from the same user
    # 
    # average AUC = mean( auc for each user in test set)
    mf_auc = tf.reduce_mean(tf.to_float(x > 0))
    
    l2_norm = tf.add_n([
            tf.reduce_sum(tf.multiply(u_emb, u_emb)), 
            tf.reduce_sum(tf.multiply(i_emb, i_emb)),
            tf.reduce_sum(tf.multiply(j_emb, j_emb))
        ])
    
    regulation_rate = 0.0001
    bprloss = regulation_rate * l2_norm - tf.reduce_mean(tf.log(tf.sigmoid(x)))
    
    train_op = tf.train.GradientDescentOptimizer(0.01).minimize(bprloss)
    return u, i, j, mf_auc, bprloss, train_op


# --------------------模型求解
with tf.Graph().as_default(), tf.Session() as session:
    u, i, j, mf_auc, bprloss, train_op = bpr_mf(user_count, item_count, 20)
    session.run(tf.initialize_all_variables())
    for epoch in range(1, 4):
        _batch_bprloss = 0
        for k in range(1, 5000): # uniform samples from training set
            uij = generate_train_batch(user_ratings, user_ratings_test, item_count)

            _bprloss, _train_op = session.run([bprloss, train_op], 
                                feed_dict={u:uij[:,0], i:uij[:,1], j:uij[:,2]})
            _batch_bprloss += _bprloss
        
        print ("epoch: ", epoch)
        print ("bpr_loss: ", _batch_bprloss / k)
        print ("_train_op")

        user_count = 0
        _auc_sum = 0.0

        # each batch will return only one user's auc
        for t_uij in generate_test_batch(user_ratings, user_ratings_test, item_count):

            _auc, _test_bprloss = session.run([mf_auc, bprloss],
                                    feed_dict={u:t_uij[:,0], i:t_uij[:,1], j:t_uij[:,2]}
                                )
            user_count += 1
            _auc_sum += _auc
        print ("test_loss: ", _test_bprloss, "test_auc: ", _auc_sum/user_count)
        print ("")
    variable_names = [v.name for v in tf.trainable_variables()]
    values = session.run(variable_names)
    for k,v in zip(variable_names, values):
        print("Variable: ", k)
        print("Shape: ", v.shape)
        print(v)


# --------------------用户推荐
session1 = tf.Session()
u1_dim = tf.expand_dims(values[0][0], 0)
u1_all = tf.matmul(u1_dim, values[1],transpose_b=True)
result_1 = session1.run(u1_all)
# print (result_1)

print("以下是给用户0的推荐:")
p = numpy.squeeze(result_1)
p[numpy.argsort(p)[:-5]] = 0
for index in range(len(p)):
    if p[index] != 0:
        print (index, p[index])

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章