Source code for recbole.model.sequential_recommender.din

# -*- coding: utf-8 -*-
# @Time   : 2020/9/21
# @Author : Zhichao Feng
# @Email  : fzcbupt@gmail.com

# UPDATE
# @Time   : 2020/10/21
# @Author : Zhichao Feng
# @email  : fzcbupt@gmail.com

r"""
DIN
##############################################
Reference:
    Guorui Zhou et al. "Deep Interest Network for Click-Through Rate Prediction" in ACM SIGKDD 2018

Reference code:
    - https://github.com/zhougr1993/DeepInterestNetwork/tree/master/din
    - https://github.com/shenweichen/DeepCTR-Torch/tree/master/deepctr_torch/models

"""

import torch
import torch.nn as nn
from torch.nn.init import xavier_normal_, constant_

from recbole.model.abstract_recommender import SequentialRecommender
from recbole.model.layers import MLPLayers, SequenceAttLayer, ContextSeqEmbLayer
from recbole.utils import InputType, FeatureType


[docs]class DIN(SequentialRecommender): """Deep Interest Network utilizes the attention mechanism to get the weight of each user's behavior according to the target items, and finally gets the user representation. Note: In the official source code, unlike the paper, user features and context features are not input into DNN. We just migrated and changed the official source code. But You can get user features embedding from user_feat_list. Besides, in order to compare with other models, we use AUC instead of GAUC to evaluate the model. """ input_type = InputType.POINTWISE def __init__(self, config, dataset): super(DIN, self).__init__(config, dataset) # get field names and parameter value from config self.LABEL_FIELD = config["LABEL_FIELD"] self.embedding_size = config["embedding_size"] self.mlp_hidden_size = config["mlp_hidden_size"] self.device = config["device"] self.pooling_mode = config["pooling_mode"] self.dropout_prob = config["dropout_prob"] self.types = ["user", "item"] self.user_feat = dataset.get_user_feature() self.item_feat = dataset.get_item_feature() # init MLP layers # self.dnn_list = [(3 * self.num_feature_field['item'] + self.num_feature_field['user']) # * self.embedding_size] + self.mlp_hidden_size num_item_feature = sum( 1 if dataset.field2type[field] not in [FeatureType.FLOAT_SEQ, FeatureType.FLOAT] or field in config["numerical_features"] else 0 for field in self.item_feat.interaction.keys() ) self.dnn_list = [ 3 * num_item_feature * self.embedding_size ] + self.mlp_hidden_size self.att_list = [ 4 * num_item_feature * self.embedding_size ] + self.mlp_hidden_size mask_mat = ( torch.arange(self.max_seq_length).to(self.device).view(1, -1) ) # init mask self.attention = SequenceAttLayer( mask_mat, self.att_list, activation="Sigmoid", softmax_stag=False, return_seq_weight=False, ) self.dnn_mlp_layers = MLPLayers( self.dnn_list, activation="Dice", dropout=self.dropout_prob, bn=True ) self.embedding_layer = ContextSeqEmbLayer( dataset, self.embedding_size, self.pooling_mode, self.device ) self.dnn_predict_layers = nn.Linear(self.mlp_hidden_size[-1], 1) self.sigmoid = nn.Sigmoid() self.loss = nn.BCEWithLogitsLoss() # parameters initialization self.apply(self._init_weights) self.other_parameter_name = ["embedding_layer"] def _init_weights(self, module): if isinstance(module, nn.Embedding): xavier_normal_(module.weight.data) elif isinstance(module, nn.Linear): xavier_normal_(module.weight.data) if module.bias is not None: constant_(module.bias.data, 0)
[docs] def forward(self, user, item_seq, item_seq_len, next_items): max_length = item_seq.shape[1] # concatenate the history item seq with the target item to get embedding together item_seq_next_item = torch.cat((item_seq, next_items.unsqueeze(1)), dim=-1) sparse_embedding, dense_embedding = self.embedding_layer( user, item_seq_next_item ) # concat the sparse embedding and float embedding feature_table = {} for type in self.types: feature_table[type] = [] if sparse_embedding[type] is not None: feature_table[type].append(sparse_embedding[type]) if dense_embedding[type] is not None: feature_table[type].append(dense_embedding[type]) feature_table[type] = torch.cat(feature_table[type], dim=-2) table_shape = feature_table[type].shape feat_num, embedding_size = table_shape[-2], table_shape[-1] feature_table[type] = feature_table[type].view( table_shape[:-2] + (feat_num * embedding_size,) ) user_feat_list = feature_table["user"] item_feat_list, target_item_feat_emb = feature_table["item"].split( [max_length, 1], dim=1 ) target_item_feat_emb = target_item_feat_emb.squeeze(1) # attention user_emb = self.attention(target_item_feat_emb, item_feat_list, item_seq_len) user_emb = user_emb.squeeze(1) # input the DNN to get the prediction score din_in = torch.cat( [user_emb, target_item_feat_emb, user_emb * target_item_feat_emb], dim=-1 ) din_out = self.dnn_mlp_layers(din_in) preds = self.dnn_predict_layers(din_out) return preds.squeeze(1)
[docs] def calculate_loss(self, interaction): label = interaction[self.LABEL_FIELD] item_seq = interaction[self.ITEM_SEQ] user = interaction[self.USER_ID] item_seq_len = interaction[self.ITEM_SEQ_LEN] next_items = interaction[self.POS_ITEM_ID] output = self.forward(user, item_seq, item_seq_len, next_items) loss = self.loss(output, label) return loss
[docs] def predict(self, interaction): item_seq = interaction[self.ITEM_SEQ] user = interaction[self.USER_ID] item_seq_len = interaction[self.ITEM_SEQ_LEN] next_items = interaction[self.POS_ITEM_ID] scores = self.sigmoid(self.forward(user, item_seq, item_seq_len, next_items)) return scores