• 贝叶斯个性化排序(BPR)pytorch实现


    一、BPR算法的原理:

    1、贝叶斯个性化排序(BPR)算法小结
    https://www.cnblogs.com/pinard/p/9128682.html
    2、Bayesian Personalized Ranking 算法解析及Python实现
    https://www.cnblogs.com/wkang/p/10217172.html
    3、推荐系统中的排序学习
    https://lumingdong.cn/learning-to-rank-in-recommendation-system.html?unapproved=401&moderation-hash=fcc3652917e1688fe59997e11c916297#配对法(Pairwise)
    4、Recommender system using Bayesian personalized ranking
    https://towardsdatascience.com/recommender-system-using-bayesian-personalized-ranking-d30e98bba0b9
    5、推荐算法之贝叶斯个性化排序 BPR
    https://www.biaodianfu.com/bpr.html

    二、算法中的注意点

    根据完整性和反对称性,优化目标的第一部分

    [prod_{u in U}P(>_u| heta) = prod_{(u,i,j) in (U imes I imes I)}P(i >_u j| heta)^{delta((u,i,j) in Ds)}(1-P(i >_u j| heta))^{delta((u,j,i) otin Ds) } ]

    [delta(b)= egin{cases} 1& {if; b; is ;true}\ 0& {else} end{cases} ]

    可以简化为:

    [prod_{u in U}P(>_u| heta) = prod_{(u,i,j) in Ds}P(i >_u j| heta) ]

    如何理解这个转换:
    1、((u,i,j) in (U imes I imes I))情况下,(P(i >_u j| heta)^{delta((u,i,j) in Ds)})((1-P(i >_u j| heta))^{delta((u,j,i) otin Ds) })提供了相同的信息。

    这句话又如何理解:
    论文中的训练集(Ds:=left{(u,i,j)| i in I_u^+ cap j in I ackslash I_u^+ ight}),由于加了限定条件因为$(u,i,j) $中的 (i)(j) 都是有限制的。。
    其中 ((u,i,j) in Ds) 的表示是用户 (u) 更喜欢 (i) 胜过(j)(cap) 是表示交集;(I ackslash I_u^+) 表示物品集(I)除正例外其他剩余的样本,即用户没有评分过的item。
    ((u, i, j))只能取特定的值,而不是遍历整个三维数组(1, 1, 1)-(m, n, n)。

    (u_1)为例,其训练集合(Ds={(u1,i2,j1),(u1,i2,j4),(u1,i3,j1),(u1,i3,j4)})
    根据 (u1×I×I),它的负例为:({(u,j,i)∉ Ds}:={(u1,j2,i1),(u1,j2,i4),(u1,j3,i1),(u1,j3,i4)})
    可见(i,j)互换了位置。因此当第二部分的指数部分为1时:((u,j,i) otin D_s),必有((u,i,j) in Ds)。即当负例存在的时候,正例必定存在。所以,二者提供了相同的信息。
    2、只有当((u,i,j) in Ds)时,({delta((u,i,j) in Ds =1}),因此可以完成上述转换。

    三、pytorch实现

    源码来自: https://github.com/guoyang9/BPR-pytorch
    1、读取数据

    import numpy as np 
    import pandas as pd 
    import scipy.sparse as sp
    import torch.utils.data as data
    import torch
    import torch.nn as nn
    import os
    import time
    dataset = 'ml-1m'
    main_path = './Data/'
    train_rating = main_path + '{}.train.rating'.format(dataset)
    test_rating = main_path + '{}.test.rating'.format(dataset)
    test_negative = main_path + '{}.test.negative'.format(dataset)
    model_path = './models/'
    BPR_model_path = model_path + 'BPR.pth'
    
    # 1、训练集
    train_data = pd.read_csv(train_rating, sep='	', header=None, names=['user', 'item'], 
        usecols=[0, 1], dtype={0: np.int32, 1: np.int32})
    
    user_num = train_data['user'].max() + 1
    item_num = train_data['item'].max() + 1
    train_data = train_data.values.tolist()
    # 2、训练样本转换为稀疏矩阵
    train_mat = sp.dok_matrix((user_num, item_num), dtype=np.float32)
    for x in train_data:
        train_mat[x[0], x[1]] = 1.0
    
    # 3、测试集数据读取,即为每个用户赋值99个没评分过的item。 1个评分过的+99个未评分的。
    test_data = []
    with open(test_negative, 'r') as fd:
        line = fd.readline()
        while line != None and line != '':
            arr = line.split('	')
            u = eval(arr[0])[0]
            test_data.append([u, eval(arr[0])[1]])
            for i in arr[1:]:
                test_data.append([u, int(i)])
            line = fd.readline()
    

    2、构建BPR数据类

    # 根据继承pytorch的Dataset类定义BPR数据类
    class BPRData(data.Dataset):
        def __init__(self, features,
                     num_item, train_mat=None, num_ng=0, is_training=None):
            """features=train_data,num_item=item_num,train_mat,稀疏矩阵,num_ng,训练阶段默认为4,即采样4-1个负样本对应一个评分过的
            数据。
            """
            super(BPRData, self).__init__()
            """ Note that the labels are only useful when training, we thus 
                add them in the ng_sample() function.
            """
            self.features = features
            self.num_item = num_item
            self.train_mat = train_mat
            self.num_ng = num_ng
            self.is_training = is_training
    
        def ng_sample(self):
            assert self.is_training, 'no need to sampling when testing'
    
            self.features_fill = []
            for x in self.features:
                u, i = x[0], x[1]
                for t in range(self.num_ng):
                    j = np.random.randint(self.num_item)
                    while (u, j) in self.train_mat:
                        j = np.random.randint(self.num_item)
                    self.features_fill.append([u, i, j])
    
        def __len__(self):
            return self.num_ng * len(self.features) if 
                self.is_training else len(self.features)
    
        def __getitem__(self, idx):
            features = self.features_fill if 
                self.is_training else self.features
    
            user = features[idx][0]
            item_i = features[idx][1]
            item_j = features[idx][2] if 
                self.is_training else features[idx][1]
            return user, item_i, item_j
    

    3、生成DataLoader

    train_dataset = BPRData(
            train_data, item_num, train_mat, 4, True)
    test_dataset = BPRData(
            test_data, item_num, train_mat, 0, False)
    train_loader = data.DataLoader(train_dataset,
                                       batch_size=4096, shuffle=True, num_workers=0)
    test_loader = data.DataLoader(test_dataset,
                                      batch_size=100, shuffle=False, num_workers=0)
    

    4、定义BPR模型,完成前向传播过程

    class BPR(nn.Module):
        def __init__(self, user_num, item_num, factor_num):
            super(BPR, self).__init__()
            """
            user_num: number of users;
            item_num: number of items;
            factor_num: number of predictive factors.
            """
            self.embed_user = nn.Embedding(user_num, factor_num)
            self.embed_item = nn.Embedding(item_num, factor_num)
    
            nn.init.normal_(self.embed_user.weight, std=0.01)
            nn.init.normal_(self.embed_item.weight, std=0.01)
    
        def forward(self, user, item_i, item_j):
            user = self.embed_user(user)
            item_i = self.embed_item(item_i)
            item_j = self.embed_item(item_j)
    
            prediction_i = (user * item_i).sum(dim=-1)
            prediction_j = (user * item_j).sum(dim=-1)
            return prediction_i, prediction_j
    model = BPR(user_num, item_num, 16)
    model.cuda()
    

    5、定义优化器

    import torch.optim as optim
    lamb = 0.001
    lr = 0.01
    optimizer = optim.SGD(model.parameters(), lr=lr, weight_decay=lamb)
    

    6、定义一些评价指标

    def hit(gt_item, pred_items):
        if gt_item in pred_items:
            return 1
        return 0
    
    
    def ndcg(gt_item, pred_items):
        if gt_item in pred_items:
            index = pred_items.index(gt_item)
            return np.reciprocal(np.log2(index+2))
        return 0
    
    
    def metrics(model, test_loader, top_k):
        HR, NDCG = [], []
    
        for user, item_i, item_j in test_loader:
            user = user.cuda()
            item_i = item_i.cuda()
            item_j = item_j.cuda() # not useful when testing
    
            prediction_i, prediction_j = model(user, item_i, item_j)
            _, indices = torch.topk(prediction_i, top_k)
            recommends = torch.take(
                item_i, indices).cpu().numpy().tolist()
    
            gt_item = item_i[0].item()
            HR.append(hit(gt_item, recommends))
            NDCG.append(ndcg(gt_item, recommends))
        return np.mean(HR), np.mean(NDCG) 
    

    7、训练和测试

    # 6、训练过程,根据公式得到后验概率,然后求导,更新两个矩阵的值。
    import os
    epochs = 50
    top_k = 10
    best_hr = 0 # 记录命中率。
    import time
    for epoch in range(epochs):
        model.train() # 在使用pytorch构建神经网络的时候,训练过程中会在程序上方添加一句model.train(),作用是启用batch normalization和drop out。
        start_time = time.time()
        train_loader.dataset.ng_sample() # 训练阶段,这一步生成真正的训练样本
        for user,item_i,item_j in train_loader:
            user = user.cuda()
            item_i = item_i.cuda()
            item_j = item_j.cuda()
            model.zero_grad()
            prediction_i,prediction_j = model(user,item_i,item_j) # 调用forward()方法
            loss = -(prediction_i-prediction_j).sigmoid().log().sum() # 这里是最小化取了负号后对应的对数后验估计函数。可以使用梯度下降。
            loss.backward() # 在这里得到梯度
            optimizer.step() # 根据上面得到的梯度进行梯度回传。
        # 一个epoch训练结束,开始测试
        model.eval() # 测试过程中会使用model.eval(),这时神经网络会沿用batch normalization的值,并不使用drop out。
        HR, NDCG = metrics(model, test_loader, top_k)
        elapsed_time = time.time() - start_time
        print("The time elapse of epoch {:03d}".format(epoch) + " is: " +
             time.strftime("%H: %M: %S", time.gmtime(elapsed_time)))
        print("HR: {:.3f}	NDCG: {:.3f}".format(np.mean(HR), np.mean(NDCG)))
        if HR > best_hr:
            best_hr,best_ndcg,best_epoch = HR,NDCG,epoch
            if not os.path.exists(model_path):
                os.mkdir(model_path)
            torch.save(model,'{}BPR.pt'.format(model_path))
    print("End. Best epoch {:03d}: HR = {:.3f}, NDCG = {:.3f}".format(best_epoch, best_hr, best_ndcg))
    
  • 相关阅读:
    2019-2020-2 网络对抗技术 20175318 Exp0 kali Linux系统的安装(雾
    手动编译 TCP BBR v2 Alpha/Preview 内核
    Adobe Flash Player解除限制版
    ABBYY FineReader 15.0.18.1494 Corporate Multilingual破解
    易语言5.9精简学习增强版
    Debian9 快速开启 TCP BBR 实现高效单边加速
    ucosii-2(选做)
    Ubuntu 18.04/18.10快速开启Google BBR的方法
    基于 Nginx 的 v2+websocket+tls 域名伪装
    Google TCP升级版加速:BBR 2.0对比BBR Plus
  • 原文地址:https://www.cnblogs.com/leimu/p/13949945.html
Copyright © 2020-2023  润新知