序列召回基础+GRU4Rec论文阅读

1. 推荐系统简介

        推荐系统,即就是为当用户推荐一些他感兴趣的项目、商品、视频等等,当然在对于小的项目库中能进行很快的推荐,但是随着不断的增加,数据量剧增,这时候就需要我们进行分步骤进行推荐,这就把推荐系统细分为四个部分:召回、粗排、精排、重排

序列召回基础+GRU4Rec论文阅读_第1张图片

         (1)召回:待计算的候选集合大、计算速度快、模型简单、特征较少时,保证用户相关物品的召回率。

        (2)精排:获得精准的排序结果

        (3)重排:对排序的结果进行多样化考虑,提高用户的使用体验

1.1 推荐系统中的召回

        召回算法的核心:

算法结构简单

计算效率高

准确率不需要太高

每一种召回算法都会针对性解决某一类问题

        具体召回算法可以分为以下类别:

基于规则

基于协同过滤

向量召回

1.1.1 基于规则的召回算法

        基于项目item被点击的次数、购买的次数等等对项目进行召回,但是这种算法需要消耗极强的业务理解和极大的人力投入

1.1.2 基于协同过滤的召回算法

        (1)基于统计的协同过滤

                分为两种:

                基于User的协同过滤:相似的用户可能喜欢相同物品

                基于item的协同过滤:相似的物品可能被同个用户喜欢

        (2)基于MF的协同过滤

序列召回基础+GRU4Rec论文阅读_第2张图片

                计算User-Item的交互概率 

1.1.3 基于向量召回的召回算法

        (1)核心:通过某种算法生产出Item的向量表征,然后通过向量的相似度来进行召回

        (2)序列召回:通过提取用户的序列特征来生产User的向量表征,然后将User的向量在全部的Item向量中进行召回

2. 基于GRU4Rec的基础序列召回

2.1 GRU

序列召回基础+GRU4Rec论文阅读_第3张图片

注意:序列中的每一个节点都是一个向量

2.1.1 通过RNN提取出输入序列的特征:

        (1)取出gif.latex?y%5En的向量表征作为序列的特征,这里可以认为gif.latex?y%5En包含了gif.latex?x%5E1%2C%20x%5E2%2C%20...%2C%20x%5En的所有信息,所有可以简单的认为gif.latex?y%5En的结果代表序列的表征。

        (2)对每一个时间步的特征输出做一个Mean Pooling,也就是对gif.latex?Y%20%3D%20%5By%5E1%2Cy%5E2%2C...%2Cy%5En%5D做均值处理,以此得到序列的表征。

2.1.2 GRU的计算逻辑

序列召回基础+GRU4Rec论文阅读_第4张图片

3. 代码实践——基于Faiss的向量召回

        这里主要是对主要的模型网络层框架进行展示,其中代码来自百度飞浆课程——手把手教你读推荐论文:序列召回基础。

3.1 基础序列召回模型定义

class GRU4Rec(nn.Layer):
    def __init__(self, config):
        # 定义参数以及网络层的维度变换
        super(GRU4Rec, self).__init__()
        # 导入config
        self.config = config
        # embedding的维度 = 16
        self.embedding_dim = self.config['embedding_dim']
        # 最大长度max_length = 20
        self.max_length = self.config['max_length']
        # n_items = 15406
        self.n_items = self.config['n_items']
        # num_layers = 1
        self.num_layers = self.config['num_layers']
        
        # 项目过embedding:15406——>16
        self.item_emb = nn.Embedding(self.n_items, self.embedding_dim, padding_idx=0)
        # 过GRU
        self.gru = nn.GRU(
            # 输入层维度=16
            input_size=self.embedding_dim,
            # 隐藏层维度=16
            hidden_size=self.embedding_dim,
            # 网络层=1
            num_layers=self.num_layers,
            time_major=False,
        )
        # 损失函数
        self.loss_fun = nn.CrossEntropyLoss()
        # 对参数进行初始化
        self.reset_parameters()

    # 计算loss
    def calculate_loss(self,user_emb,pos_item):
        all_items = self.item_emb.weight
        # transpose()函数的作用就是调换数组的行列值的索引值,类似于求矩阵的转置
        scores = paddle.matmul(user_emb, all_items.transpose([1, 0]))
        return self.loss_fun(scores,pos_item)

    # 输出项目emb的权重
    def output_items(self):
        return self.item_emb.weight

    # 使用优化器对参数进行更新
    def reset_parameters(self, initializer=None):
        for weight in self.parameters():
            paddle.nn.initializer.KaimingNormal(weight)

    # 正向传播
    def forward(self, item_seq, mask, item, train=True):
        # 将项目过embedding
        seq_emb = self.item_emb(item_seq)
        # 过gru
        seq_emb,_ = self.gru(seq_emb)
        # 去最后一层隐藏层的输出作为user的embedding
        user_emb = seq_emb[:,-1,:] #取GRU输出的最后一个Hidden作为User的Embedding
        # 计算损失,对于不同的数据集传回不同
        if train:
            loss = self.calculate_loss(user_emb,item)
            output_dict = {
                'user_emb':user_emb,
                'loss':loss
            }
        else:
            output_dict = {
                'user_emb':user_emb
            }
        return output_dict

3.2  基于Faiss的向量召回

def get_predict(model, test_data, hidden_size, topN=20):

    item_embs = model.output_items().cpu().detach().numpy()
    item_embs = normalize(item_embs, norm='l2')
    gpu_index = faiss.IndexFlatIP(hidden_size)
    gpu_index.add(item_embs)
    
    test_gd = dict()
    preds = dict()
    
    user_id = 0

    for (item_seq, mask, targets) in tqdm(test_data):

        # 获取用户嵌入
        # 多兴趣模型,shape=(batch_size, num_interest, embedding_dim)
        # 其他模型,shape=(batch_size, embedding_dim)
        user_embs = model(item_seq,mask,None,train=False)['user_emb']
        user_embs = user_embs.cpu().detach().numpy()

        # 用内积来近邻搜索,实际是内积的值越大,向量越近(越相似)
        if len(user_embs.shape) == 2:  # 非多兴趣模型评估
            user_embs = normalize(user_embs, norm='l2').astype('float32')
            D, I = gpu_index.search(user_embs, topN)  # Inner Product近邻搜索,D为distance,I是index
#             D,I = faiss.knn(user_embs, item_embs, topN,metric=faiss.METRIC_INNER_PRODUCT)
            for i, iid_list in enumerate(targets):  # 每个用户的label列表,此处item_id为一个二维list,验证和测试是多label的
                test_gd[user_id] = iid_list
                preds[user_id] = I[i,:]
                user_id +=1
        else:  # 多兴趣模型评估
            ni = user_embs.shape[1]  # num_interest
            user_embs = np.reshape(user_embs,
                                   [-1, user_embs.shape[-1]])  # shape=(batch_size*num_interest, embedding_dim)
            user_embs = normalize(user_embs, norm='l2').astype('float32')
            D, I = gpu_index.search(user_embs, topN)  # Inner Product近邻搜索,D为distance,I是index
#             D,I = faiss.knn(user_embs, item_embs, topN,metric=faiss.METRIC_INNER_PRODUCT)
            for i, iid_list in enumerate(targets):  # 每个用户的label列表,此处item_id为一个二维list,验证和测试是多label的
                recall = 0
                dcg = 0.0
                item_list_set = []

                # 将num_interest个兴趣向量的所有topN近邻物品(num_interest*topN个物品)集合起来按照距离重新排序
                item_list = list(
                    zip(np.reshape(I[i * ni:(i + 1) * ni], -1), np.reshape(D[i * ni:(i + 1) * ni], -1)))
                item_list.sort(key=lambda x: x[1], reverse=True)  # 降序排序,内积越大,向量越近
                for j in range(len(item_list)):  # 按距离由近到远遍历推荐物品列表,最后选出最近的topN个物品作为最终的推荐物品
                    if item_list[j][0] not in item_list_set and item_list[j][0] != 0:
                        item_list_set.append(item_list[j][0])
                        if len(item_list_set) >= topN:
                            break
                test_gd[user_id] = iid_list
                preds[user_id] = item_list_set
                user_id +=1
    return test_gd, preds

def evaluate(preds,test_gd, topN=50):
    total_recall = 0.0
    total_ndcg = 0.0
    total_hitrate = 0
    for user in test_gd.keys():
        recall = 0
        dcg = 0.0
        item_list = test_gd[user]
        for no, item_id in enumerate(item_list):
            if item_id in preds[user][:topN]:
                recall += 1
                dcg += 1.0 / math.log(no+2, 2)
            idcg = 0.0
            for no in range(recall):
                idcg += 1.0 / math.log(no+2, 2)
        total_recall += recall * 1.0 / len(item_list)
        if recall > 0:
            total_ndcg += dcg / idcg
            total_hitrate += 1
    total = len(test_gd)
    recall = total_recall / total
    ndcg = total_ndcg / total
    hitrate = total_hitrate * 1.0 / total
    return {f'recall@{topN}': recall, f'ndcg@{topN}': ndcg, f'hitrate@{topN}': hitrate}

# 指标计算
def evaluate_model(model, test_loader, embedding_dim,topN=20):
    test_gd, preds = get_predict(model, test_loader, embedding_dim, topN=topN)
    return evaluate(preds, test_gd, topN=topN)

4. GRU4Rec论文阅读(SESSION-BASED RECOMMENDATIONS WITH RECURRENT NEURAL NETWORKS)

4.1 主要工作

        这篇论文主要是讲RNNs应用于推荐系统领域,并提出了一种基于RNN的基于会话的建议的方法,并为了考虑更加实际的方面,在经典的RNNs基础上进行了一定的修改,如:对排名损失函数进行一些修改。

We therefore propose an RNN-based approach for session-based recommendations. Our approach also considers practical aspects of the task and introduces several modififications to classic RNNs such as a ranking loss function that make it more viable for this specifific problem.Experimental results on two data-sets show marked improvements over widely
used approaches.

4.2 相关工作

        这里提到了推荐系统领域现有的两种占主要地位的方法——factor models和neighborhood methods。

4.2.1 factor models

        将推荐问题视为一个矩阵补全/重构问题,然后利用潜在因子向量来填充缺失的项。(未在基于会话的推荐中应用)

Factor models work by decomposing the sparse user-item interactions matrix to a set
of d dimensional vectors one for each item and user in the dataset. The recommendation problem is then treated as a matrix completion/reconstruction problem whereby the latent factor vectors are then used to fifill the missing entries by e.g. taking the dot product of the corresponding user–item latent factors.

4.2.2 neighborhood methods

        计算项目(或用户)之间的相似性。(在基于会话的建议中已被广泛应用)

 

neighborhood methods, which rely on computing similarities between items (or users) are based on co-occurrences of items in sessions (or user profifiles). Neighborhood methods have been used extensively in session-based recommendations.

4.3 RNNs模型公式

gif.latex?h_t%3Dg%28Wx_t+Uh_%7Bt-1%7D%29
gif.latex?h_t%3D%281-z_t%29h_%7Bt-1%7D+z_t%5Cwidehat%7Bh%7D_t

gif.latex?z_t%3D%5Csigma%20%28W_zx_t+U_zh_%7Bt-1%7D%29
gif.latex?%5Cwidehat%7Bh%7D_t%3Dtanh%28Wx_t+U%28r_t%5Codot%20h_%7Bt-1%7D%29%29

gif.latex?r_t%3D%5Csigma%20%28W_rx_t+U_rh_%7Bt-1%7D%29

4.4 网络的总体架构——CUSTOMIZING THE GRU MODEL

        基于上面介绍的两种方法存在的优点以及不足,论文提出了一个基于RNNs的会话推荐模型。

        (1)输入时进行one-hot编码;

        (2)过一层embedding层;

        (3)将过embedding后的结果输入到GRU网络层(输入的时候可以输出到更深层的GRU网络层);

        (4)输出经过一个前馈神经网络层。

序列召回基础+GRU4Rec论文阅读_第5张图片

 General architecture of the network. Processing of one event of the event stream at once.

        以下是本文对传统的RNNs进行的一些修改 

 4.4.1 SESSION-PARALLEL MINI-BATCHES

        为了更好的并行计算,论文采用了 mini-batch 的处理,即把不同的session拼接起来:

序列召回基础+GRU4Rec论文阅读_第6张图片

4.4.2 SAMPLING ON THE OUTPUT

        (1)根据项目的受欢迎程度的比例进行抽样。

        (2)基于流行度的抽样,因为一个项目出现在小批量的其他训练例子中的可能性与它的受欢迎程度成正比。

4.4.3 RANKING LOSS

        (1)BPR

gif.latex?L_s%3D-%5Cfrac%7B1%7D%7BN_s%7D%5Ccdot%20%5Csum_%7Bj%3D1%7D%5E%7BN_s%7Dlos%28%5Csigma%20%28%5Cwidehat%7Br%7D_%7Bs%2Ci%7D-%5Cwidehat%7Br_%7Bs%2Cj%7D%7D%29%29

        (2)TOP1

 gif.latex?L_s%3D%5Cfrac%7B1%7D%7BN_s%7D%5Ccdot%20%5Csum_%7Bj%3D1%7D%5E%7BN_s%7D%5Csigma%20%28%5Cwidehat%7Br%7D_%7Bs%2Cj%7D-%5Cwidehat%7Br%7D_%7Bs%2Ci%7D%29+%5Csigma%20%28%5Cwidehat%7Br%7D%5E2_%7Bs%2Cj%7D%29

 

 

飞桨AI Studio - 人工智能学习与实训社区 (baidu.com)​​​​​​

https://aistudio.baidu.com/aistudio/course/introduce/27783

 

你可能感兴趣的:(推荐系统论文学习,人工智能,rnn,神经网络,gru)