手把手教你读推荐论文-Comirec(下)

Comirec:Controllable Multi-Interest Framework for Recommendation

1.论文解读

论文链接:https://arxiv.org/abs/2005.09347

在这里插入图片描述

Comirec是阿里发表在KDD 2020上的一篇工作,在这篇论文中提出了两个模型:Comirec-DR,Comirec-SA。我们在上一篇教程中着重介绍了Comirec-DR,我们在这里着重介绍Comirec-SA模型,Comirec-SA是一种基于Attention的多兴趣建模方法,我们先来看如何通过Attention提取单一兴趣,随即再将其推广到多兴趣建模

1.1 基于Attention的单一兴趣建模

这里我们将用户的行为序列计作 H ∈ R d × n H \in \mathbb{R}^{d \times n} HRd×n,这里的 n n n表示用户行为序列的长度, d d d表示Embedding向量的维度,这里的 H H H就代表着用户行为序列中所有Item的Embedding向量,这里我们引入两个可学习参数 W 1 ∈ R d a × d , w 2 ∈ R d a W_1 \in \mathbb{R}^{d_a \times d} ,w_2 \in \mathbb{R}^{d_a} W1Rda×d,w2Rda,我们知道引入Attention的核心目的是为了学习出某些东西的权重,以此来凸显重要性搞的特征,淡化重要性低的特征,在我们的Comirec-SA中,我们的特征重要性(也就是我们学习出来的Attention Score)是针对序列中每个Item的Attention Score,在有了Attention Score之后就可以对序列中的Item进行加权求和得到序列的单一兴趣表征了,我们下面先来看单一兴趣建模时的Attention Score计算:

a = s o f t m a x ( w 2 T t a n h ( W 1 H ) ) T a = {softmax(w_{2}^{T} tanh(W_{1}H))}^{T} a=softmax(w2Ttanh(W1H))T

可以看出,我们所得的 a ∈ R n a \in \mathbb{R}^{n} aRn,这里的 a a a就是我们对序列的Attention Score,再将 a a a与序列的所有Item的Embedding进行加权求和就可以得到单一兴趣的建模,下面我们将其推广到多兴趣建模上

1.2 基于Attention的多兴趣建模

我们在1.1中得到了单一兴趣建模的方法,我们可以把1.1中的 w 2 ∈ R d a w_2 \in \mathbb{R}^{d_a} w2Rda 扩充至 W 2 ∈ R d a × K W_2 \in \mathbb{R}^{d_a \times K} W2Rda×K,这样的话,我们的Attention Score的计算公式就变成:

A = s o f t m a x ( W 2 T t a n h ( W 1 H ) ) T A = {softmax(W_{2}^{T} tanh(W_{1}H))}^{T} A=softmax(W2Ttanh(W1H))T

可以得到 A ∈ R n × K A \in \mathbb{R}^{n \times K} ARn×K,这时我们可以通过

V u = H A V_u = HA Vu=HA

得到用户的多兴趣表征,这里的 V u ∈ R d × K V_u \in \mathbb{R}^{d \times K} VuRd×K,即为K个兴趣表征,其核心代码如下:

class MultiInterest_SA(nn.Layer):
    def __init__(self, embedding_dim, interest_num, d=None):
        super(MultiInterest_SA, self).__init__()
        self.embedding_dim = embedding_dim
        self.interest_num = interest_num
        if d == None:
            self.d = self.embedding_dim*4

        self.W1 = self.create_parameter(shape=[self.embedding_dim, self.d])
        self.W2 = self.create_parameter(shape=[self.d, self.interest_num]) 

    def forward(self, seq_emb, mask = None):
        '''
        seq_emb : batch,seq,emb
        mask : batch,seq,1
        '''
        H = paddle.einsum('bse, ed -> bsd', seq_emb, self.W1).tanh()
        mask = mask.unsqueeze(-1)
        A = paddle.einsum('bsd, dk -> bsk', H, self.W2) + -1.e9 * (1 - mask)
        A = F.softmax(A, axis=1)
        A = paddle.transpose(A,perm=[0, 2, 1])
        multi_interest_emb = paddle.matmul(A, seq_emb)
        return multi_interest_emb

1.3 多样性控制

这里论文提出了一种多样性策略,即我们希望给用户所推荐的商品的多样性更强一些,那我们该怎么衡量多样性呢,这里作者提出将Item的类别作为衡量多样性的基础,例如,我们可以通过下式来衡量两个Item i,j之间的多样性:
g ( i , j ) = σ ( C A T E ( i ) ≠ C A T E ( j ) ) g(i,j)= \sigma(CATE(i) \ne CATE(j)) g(i,j)=σ(CATE(i)=CATE(j))
这里的 σ \sigma σ为指示函数,这里就是如果两个item的类别不相同,那么其结果就是1,反之就是0,可以看出如果一个推荐集合中两两Item的多样性得分大的话,那么可以认为这个推荐结果中的Item的类别分布较为分散,也就可以认为推荐结果的多样性较高了,但是这个时候要注意,当推荐结果的多样性较高的时候,往往推荐的精度就会下降,这是一个Trade Off,所以我们需要一个目标来量化我们的推荐结果,这里的目标函数如下:

在这里插入图片描述

可以看出,我们的目标函数中即包含了多样性的指标,还包含了推荐精度的指标,这里通过 λ \lambda λ 来控制这两部分的占比, λ \lambda λ越大,可以认为对多样性的需求就越高与此同时,模型的精度可能就会小一点。在有了目标函数之后,作者这里提出使用一种贪心的做法来进行目标函数的优化,其优化的流程如下:

在这里插入图片描述

我们这里就不对这个多样性进行代码实现了,感兴趣的小伙伴可以查看作者原汁原味的实现:https://github.com/THUDM/ComiRec/blob/a576eed8b605a531f2971136ce6ae87739d47693/src/train.py#L50-L57

2.代码实践

!pip install faiss
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
Collecting faiss
  Downloading https://pypi.tuna.tsinghua.edu.cn/packages/ef/2e/dc5697e9ff6f313dcaf3afe5ca39d7d8334114cbabaed069d0026bbc3c61/faiss-1.5.3-cp37-cp37m-manylinux1_x86_64.whl (4.7 MB)
[2K     [90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━[0m [32m4.7/4.7 MB[0m [31m2.5 MB/s[0m eta [36m0:00:00[0m00:01[0m00:01[0m
[?25hRequirement already satisfied: numpy in /opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages (from faiss) (1.19.5)
Installing collected packages: faiss
Successfully installed faiss-1.5.3

[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m A new release of pip available: [0m[31;49m22.1.2[0m[39;49m -> [0m[32;49m22.3[0m
[1m[[0m[34;49mnotice[0m[1;39;49m][0m[39;49m To update, run: [0m[32;49mpip install --upgrade pip[0m
import paddle
from paddle import nn
from paddle.io import DataLoader, Dataset
import paddle.nn.functional as F
import pandas as pd
import numpy as np
import copy
import os
import math
import random
from sklearn.metrics import roc_auc_score,log_loss
from sklearn.preprocessing import normalize
from tqdm import tqdm
from collections import defaultdict
from sklearn.manifold import TSNE
import seaborn as sns
from matplotlib import pyplot as plt
import faiss
import warnings
warnings.filterwarnings("ignore")
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/__init__.py:107: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import MutableMapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/rcsetup.py:20: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Iterable, Mapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/colors.py:53: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Sized

2.1 Dataset

class SeqnenceDataset(Dataset):
    def __init__(self, config, df, phase='train'):
        self.config = config
        self.df = df
        self.max_length = self.config['max_length']
        self.df = self.df.sort_values(by=['user_id', 'timestamp'])
        self.user2item = self.df.groupby('user_id')['item_id'].apply(list).to_dict()
        self.user_list = self.df['user_id'].unique()
        self.phase = phase

    def __len__(self, ):
        return len(self.user2item)

    def __getitem__(self, index):
        if self.phase == 'train':
            user_id = self.user_list[index]
            item_list = self.user2item[user_id]
            hist_item_list = []
            hist_mask_list = []

            k = random.choice(range(4, len(item_list)))  # 从[8,len(item_list))中随机选择一个index
            # k = np.random.randint(2,len(item_list))
            item_id = item_list[k]  # 该index对应的item加入item_id_list

            if k >= self.max_length:  # 选取seq_len个物品
                hist_item_list.append(item_list[k - self.max_length: k])
                hist_mask_list.append([1.0] * self.max_length)
            else:
                hist_item_list.append(item_list[:k] + [0] * (self.max_length - k))
                hist_mask_list.append([1.0] * k + [0.0] * (self.max_length - k))

            return paddle.to_tensor(hist_item_list).squeeze(0), paddle.to_tensor(hist_mask_list).squeeze(
                0), paddle.to_tensor([item_id])
        else:
            user_id = self.user_list[index]
            item_list = self.user2item[user_id]
            hist_item_list = []
            hist_mask_list = []

            k = int(0.8 * len(item_list))
            # k = len(item_list)-1

            if k >= self.max_length:  # 选取seq_len个物品
                hist_item_list.append(item_list[k - self.max_length: k])
                hist_mask_list.append([1.0] * self.max_length)
            else:
                hist_item_list.append(item_list[:k] + [0] * (self.max_length - k))
                hist_mask_list.append([1.0] * k + [0.0] * (self.max_length - k))

            return paddle.to_tensor(hist_item_list).squeeze(0), paddle.to_tensor(hist_mask_list).squeeze(
                0), item_list[k:]

    def get_test_gd(self):
        self.test_gd = {}
        for user in self.user2item:
            item_list = self.user2item[user]
            test_item_index = int(0.8 * len(item_list))
            self.test_gd[user] = item_list[test_item_index:]
        return self.test_gd

2.2 Comirec-SA 模型定义

class MultiInterest_SA(nn.Layer):
    def __init__(self, embedding_dim, interest_num, d=None):
        super(MultiInterest_SA, self).__init__()
        self.embedding_dim = embedding_dim
        self.interest_num = interest_num
        if d == None:
            self.d = self.embedding_dim*4

        self.W1 = self.create_parameter(shape=[self.embedding_dim, self.d])
        self.W2 = self.create_parameter(shape=[self.d, self.interest_num]) 

    def forward(self, seq_emb, mask = None):
        '''
        seq_emb : batch,seq,emb
        mask : batch,seq,1
        '''
        H = paddle.einsum('bse, ed -> bsd', seq_emb, self.W1).tanh()
        mask = mask.unsqueeze(-1)
        A = paddle.einsum('bsd, dk -> bsk', H, self.W2) + -1.e9 * (1 - mask)
        A = F.softmax(A, axis=1)
        A = paddle.transpose(A,perm=[0, 2, 1])
        multi_interest_emb = paddle.matmul(A, seq_emb)
        return multi_interest_emb
class ComirecSA(nn.Layer):
    def __init__(self, config):
        super(ComirecSA, self).__init__()

        self.config = config
        self.embedding_dim = self.config['embedding_dim']
        self.max_length = self.config['max_length']
        self.n_items = self.config['n_items']

        self.item_emb = nn.Embedding(self.n_items, self.embedding_dim, padding_idx=0)
        self.multi_interest_layer = MultiInterest_SA(self.embedding_dim,interest_num=self.config['K'])
        self.loss_fun = nn.CrossEntropyLoss()
        self.reset_parameters()

    def calculate_loss(self,user_emb,pos_item):
        all_items = self.item_emb.weight
        scores = paddle.matmul(user_emb, all_items.transpose([1, 0]))
        return self.loss_fun(scores,pos_item)

    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):

        if train:
            seq_emb = self.item_emb(item_seq)  # Batch,Seq,Emb
            item_e = self.item_emb(item).squeeze(1)

            multi_interest_emb = self.multi_interest_layer(seq_emb, mask)  # Batch,K,Emb

            cos_res = paddle.bmm(multi_interest_emb, item_e.squeeze(1).unsqueeze(-1))
            k_index = paddle.argmax(cos_res, axis=1)

            best_interest_emb = paddle.rand((multi_interest_emb.shape[0], multi_interest_emb.shape[2]))
            for k in range(multi_interest_emb.shape[0]):
                best_interest_emb[k, :] = multi_interest_emb[k, k_index[k], :]

            loss = self.calculate_loss(best_interest_emb,item)
            output_dict = {
                'user_emb': multi_interest_emb,
                'loss': loss,
            }
        else:
            seq_emb = self.item_emb(item_seq)  # Batch,Seq,Emb
            multi_interest_emb = self.multi_interest_layer(seq_emb, mask)  # Batch,K,Emb
            output_dict = {
                'user_emb': multi_interest_emb,
            }
        return output_dict

2.3 Pipeline

config = {
    'train_path':'/home/aistudio/data/data173799/train_enc.csv',
    'valid_path':'/home/aistudio/data/data173799/valid_enc.csv',
    'test_path':'/home/aistudio/data/data173799/test_enc.csv',
    'lr':1e-4,
    'Epoch':100,
    'batch_size':256,
    'embedding_dim':16,
    'max_length':20,
    'n_items':15406,
    'K':4
}
def my_collate(batch):
    hist_item, hist_mask, item_list = list(zip(*batch))

    hist_item = [x.unsqueeze(0) for x in hist_item]
    hist_mask = [x.unsqueeze(0) for x in hist_mask]

    hist_item = paddle.concat(hist_item,axis=0)
    hist_mask = paddle.concat(hist_mask,axis=0)
    return hist_item,hist_mask,item_list
def save_model(model, path):
    if not os.path.exists(path):
        os.makedirs(path)
    paddle.save(model.state_dict(), path + 'model.pdparams')


def load_model(model, path):
    state_dict = paddle.load(path + 'model.pdparams')
    model.set_state_dict(state_dict)
    print('model loaded from %s' % path)
    return model

2.4 基于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)
# 读取数据
train_df = pd.read_csv(config['train_path'])
valid_df = pd.read_csv(config['valid_path'])
test_df = pd.read_csv(config['test_path'])
train_dataset = SeqnenceDataset(config, train_df, phase='train')
valid_dataset = SeqnenceDataset(config, valid_df, phase='test')
test_dataset = SeqnenceDataset(config, test_df, phase='test')
train_loader = DataLoader(dataset=train_dataset, batch_size=config['batch_size'], shuffle=True,num_workers=8)
valid_loader = DataLoader(dataset=valid_dataset, batch_size=config['batch_size'], shuffle=False,collate_fn=my_collate)
test_loader = DataLoader(dataset=test_dataset, batch_size=config['batch_size'], shuffle=False,collate_fn=my_collate)
model = ComirecSA(config)
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=config['lr'])
log_df = pd.DataFrame()
best_reacall = -1

exp_path = './exp/ml-20m_softmax/ComirecSA_{}_{}_{}/'.format(config['lr'],config['batch_size'],config['embedding_dim'])
os.makedirs(exp_path,exist_ok=True,mode=0o777)
patience = 5
last_improve_epoch = 1
log_csv = exp_path+'log.csv'
# *****************************************************train*********************************************
for epoch in range(1, 1 + config['Epoch']):
    # try :
        pbar = tqdm(train_loader)
        model.train()
        loss_list = []
        acc_50_list = []
        print()
        print('Training:')
        print()
        for batch_data in pbar:
            (item_seq, mask, item) = batch_data

            output_dict = model(item_seq, mask, item)
            loss = output_dict['loss']

            loss.backward()
            optimizer.step()
            optimizer.clear_grad()

            loss_list.append(loss.item())

            pbar.set_description('Epoch [{}/{}]'.format(epoch,config['Epoch']))
            pbar.set_postfix(loss = np.mean(loss_list))
    # *****************************************************valid*********************************************

        print('Valid')
        recall_metric = evaluate_model(model, valid_loader, config['embedding_dim'], topN=50)
        print(recall_metric)
        recall_metric['phase'] = 'valid'
        log_df = log_df.append(recall_metric, ignore_index=True)
        log_df.to_csv(log_csv)

        if recall_metric['recall@50'] > best_reacall:
            save_model(model,exp_path)
            best_reacall = recall_metric['recall@50']
            last_improve_epoch = epoch

        if epoch - last_improve_epoch > patience:
            break

print('Testing')
model = load_model(model,exp_path)
recall_metric = evaluate_model(model, test_loader, config['embedding_dim'], topN=50)
print(recall_metric)
recall_metric['phase'] = 'test'
log_df = log_df.append(recall_metric, ignore_index=True)
log_df.to_csv(log_csv)
W1029 21:17:55.752853   220 gpu_resources.cc:61] Please NOTE: device: 0, GPU Compute Capability: 7.0, Driver API Version: 11.2, Runtime API Version: 11.2
W1029 21:17:55.757241   220 gpu_resources.cc:91] device: 0, cuDNN Version: 8.2.
  0%|          | 0/431 [00:00<?, ?it/s]


Training:



Epoch [1/100]: 100%|██████████| 431/431 [02:05<00:00,  3.43it/s, loss=9.63]
  2%|▏         | 1/54 [00:00<00:10,  5.28it/s]

Valid


100%|██████████| 54/54 [00:05<00:00,  9.92it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10130736646658137, 'ndcg@50': 0.2860482099351352, 'hitrate@50': 0.5432601425039988}

Training:



Epoch [2/100]: 100%|██████████| 431/431 [02:03<00:00,  3.49it/s, loss=9.47]
  2%|▏         | 1/54 [00:00<00:07,  6.66it/s]

Valid


100%|██████████| 54/54 [00:05<00:00,  9.95it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10802806491215076, 'ndcg@50': 0.2975078620793403, 'hitrate@50': 0.5580921913625128}

Training:



Epoch [3/100]: 100%|██████████| 431/431 [02:04<00:00,  3.46it/s, loss=9.14]
  2%|▏         | 1/54 [00:00<00:08,  6.34it/s]

Valid


100%|██████████| 54/54 [00:05<00:00,  9.18it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.11202914207590516, 'ndcg@50': 0.30394059024665426, 'hitrate@50': 0.5652900974262033}

Training:



Epoch [4/100]: 100%|██████████| 431/431 [02:03<00:00,  3.49it/s, loss=8.79]
  2%|▏         | 1/54 [00:00<00:07,  6.83it/s]

Valid


100%|██████████| 54/54 [00:05<00:00, 10.00it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10995670319755928, 'ndcg@50': 0.30301696713211984, 'hitrate@50': 0.5668896321070234}

Training:



Epoch [5/100]: 100%|██████████| 431/431 [02:04<00:00,  3.47it/s, loss=8.47]
  2%|▏         | 1/54 [00:00<00:07,  6.80it/s]

Valid


100%|██████████| 54/54 [00:05<00:00, 10.43it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.1079689262422746, 'ndcg@50': 0.3008134676348343, 'hitrate@50': 0.5665261014977461}

Training:



Epoch [6/100]: 100%|██████████| 431/431 [02:04<00:00,  3.46it/s, loss=8.18]
  2%|▏         | 1/54 [00:00<00:10,  5.28it/s]

Valid


100%|██████████| 54/54 [00:05<00:00,  9.98it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10716297645121986, 'ndcg@50': 0.300018519353774, 'hitrate@50': 0.5671077504725898}

Training:



Epoch [7/100]: 100%|██████████| 431/431 [02:06<00:00,  3.40it/s, loss=7.95]
  2%|▏         | 1/54 [00:00<00:07,  6.99it/s]

Valid


100%|██████████| 54/54 [00:05<00:00,  9.64it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10672819426521096, 'ndcg@50': 0.29885539240224, 'hitrate@50': 0.5617274974552857}

Training:



Epoch [8/100]: 100%|██████████| 431/431 [02:30<00:00,  2.87it/s, loss=7.76]
  2%|▏         | 1/54 [00:00<00:07,  6.90it/s]

Valid


100%|██████████| 54/54 [00:05<00:00, 10.73it/s]
  0%|          | 0/431 [00:00<?, ?it/s]

{'recall@50': 0.10855969728895347, 'ndcg@50': 0.3012225757637584, 'hitrate@50': 0.5653628035480588}

Training:



Epoch [9/100]: 100%|██████████| 431/431 [02:04<00:00,  3.46it/s, loss=7.61]
  2%|▏         | 1/54 [00:00<00:07,  7.21it/s]

Valid


100%|██████████| 54/54 [00:04<00:00, 10.92it/s]
  2%|▏         | 1/54 [00:00<00:07,  7.27it/s]

{'recall@50': 0.10954863327858669, 'ndcg@50': 0.30189823202481225, 'hitrate@50': 0.5653628035480588}
Testing
model loaded from ./exp/ml-20m_softmax/ComirecSA_0.0001_256_16/


100%|██████████| 54/54 [00:05<00:00, 11.62it/s]


{'recall@50': 0.1141585486133601, 'ndcg@50': 0.3081236008368031, 'hitrate@50': 0.5703676470588235}
log_df
hitrate@50ndcg@50phaserecall@50
00.5432600.286048valid0.101307
10.5580920.297508valid0.108028
20.5652900.303941valid0.112029
30.5668900.303017valid0.109957
40.5665260.300813valid0.107969
50.5671080.300019valid0.107163
60.5617270.298855valid0.106728
70.5653630.301223valid0.108560
80.5653630.301898valid0.109549
90.5703680.308124test0.114159

2.5 基于TSNE的Item Embedding分布可视化

def plot_embedding(data, title):
    x_min, x_max = np.min(data, 0), np.max(data, 0)
    data = (data - x_min) / (x_max - x_min)
 
    fig = plt.figure(dpi=120)
    plt.scatter(data[:,0], data[:,1], marker='.')
    
    plt.xticks([])
    plt.yticks([])
    plt.title(title)
    plt.show()
item_emb = model.output_items().numpy()
tsne_emb = TSNE(n_components=2).fit_transform(item_emb)
plot_embedding(tsne_emb,'ComirecSA Item Embedding')

在这里插入图片描述

3.总结

至此我们就将Comirec中的两个模型都介绍完了,在我们这篇教程中着重介绍了基于Attention提取多兴趣表征的方法,同时对论文中的多样性控制策略也进行了简单的介绍

此文章为搬运
原项目链接

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值