Coverage for hopwise/model/sequential_recommender/gru4recf.py: 80%
84 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 13:25 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-09-30 13:25 +0000
1# @Time : 2020/9/14 16:57
2# @Author : Hui Wang
3# @Email : hui.wang@ruc.edu.cn
5r"""GRU4RecF
6################################################
8Reference:
9 Balázs Hidasi et al. "Parallel Recurrent Neural Network Architectures for
10 Feature-rich Session-based Recommendations." in RecSys 2016.
12"""
14import torch
15from torch import nn
17from hopwise.model.abstract_recommender import SequentialRecommender
18from hopwise.model.init import xavier_normal_initialization
19from hopwise.model.layers import FeatureSeqEmbLayer
20from hopwise.model.loss import BPRLoss
23class GRU4RecF(SequentialRecommender):
24 r"""In the original paper, the authors proposed several architectures. We compared 3 different
25 architectures:
27 (1) Concatenate item input and feature input and use single RNN,
29 (2) Concatenate outputs from two different RNNs,
31 (3) Weighted sum of outputs from two different RNNs.
33 We implemented the optimal parallel version(2), which uses different RNNs to
34 encode items and features respectively and concatenates the two subparts'
35 outputs as the final output. The different RNN encoders are trained simultaneously.
36 """
38 def __init__(self, config, dataset):
39 super().__init__(config, dataset)
41 # load parameters info
42 self.embedding_size = config["embedding_size"]
43 self.hidden_size = config["hidden_size"]
44 self.num_layers = config["num_layers"]
45 self.dropout_prob = config["dropout_prob"]
47 self.selected_features = config["selected_features"]
48 self.pooling_mode = config["pooling_mode"]
49 self.device = config["device"]
50 self.num_feature_field = len(config["selected_features"])
52 self.loss_type = config["loss_type"]
54 # define layers and loss
55 self.item_embedding = nn.Embedding(self.n_items, self.embedding_size, padding_idx=0)
56 self.feature_embed_layer = FeatureSeqEmbLayer(
57 dataset,
58 self.embedding_size,
59 self.selected_features,
60 self.pooling_mode,
61 self.device,
62 )
63 self.item_gru_layers = nn.GRU(
64 input_size=self.embedding_size,
65 hidden_size=self.hidden_size,
66 num_layers=self.num_layers,
67 bias=False,
68 batch_first=True,
69 )
70 # For simplicity, we use same architecture for item_gru and feature_gru
71 self.feature_gru_layers = nn.GRU(
72 input_size=self.embedding_size * self.num_feature_field,
73 hidden_size=self.hidden_size,
74 num_layers=self.num_layers,
75 bias=False,
76 batch_first=True,
77 )
78 self.dense_layer = nn.Linear(self.hidden_size * 2, self.embedding_size)
79 self.dropout = nn.Dropout(self.dropout_prob)
80 if self.loss_type == "BPR":
81 self.loss_fct = BPRLoss()
82 elif self.loss_type == "CE":
83 self.loss_fct = nn.CrossEntropyLoss()
84 else:
85 raise NotImplementedError("Make sure 'loss_type' in ['BPR', 'CE']!")
87 # parameters initialization
88 self.apply(xavier_normal_initialization)
89 self.other_parameter_name = ["feature_embed_layer"]
91 def forward(self, item_seq, item_seq_len):
92 item_seq_emb = self.item_embedding(item_seq)
93 item_seq_emb_dropout = self.dropout(item_seq_emb)
94 item_gru_output, _ = self.item_gru_layers(item_seq_emb_dropout) # [B Len H]
96 sparse_embedding, dense_embedding = self.feature_embed_layer(None, item_seq)
97 sparse_embedding = sparse_embedding["item"]
98 dense_embedding = dense_embedding["item"]
99 # concat the sparse embedding and float embedding
100 feature_table = []
101 if sparse_embedding is not None:
102 feature_table.append(sparse_embedding)
103 if dense_embedding is not None:
104 feature_table.append(dense_embedding)
106 feature_table = torch.cat(feature_table, dim=-2)
107 # [batch len num_features hidden_size]
108 table_shape = feature_table.shape
110 feat_num, embedding_size = table_shape[-2], table_shape[-1]
111 feature_emb = feature_table.view(table_shape[:-2] + (feat_num * embedding_size,))
112 feature_gru_output, _ = self.feature_gru_layers(feature_emb) # [B Len H]
114 output_concat = torch.cat((item_gru_output, feature_gru_output), -1) # [B Len 2*H]
115 output = self.dense_layer(output_concat)
116 output = self.gather_indexes(output, item_seq_len - 1) # [B H]
117 return output # [B H]
119 def calculate_loss(self, interaction):
120 item_seq = interaction[self.ITEM_SEQ]
121 item_seq_len = interaction[self.ITEM_SEQ_LEN]
122 seq_output = self.forward(item_seq, item_seq_len)
123 pos_items = interaction[self.POS_ITEM_ID]
124 if self.loss_type == "BPR":
125 neg_items = interaction[self.NEG_ITEM_ID]
126 pos_items_emb = self.item_embedding(pos_items) # [B H]
127 neg_items_emb = self.item_embedding(neg_items) # [B H]
128 pos_score = torch.sum(seq_output * pos_items_emb, dim=-1) # [B]
129 neg_score = torch.sum(seq_output * neg_items_emb, dim=-1) # [B]
130 loss = self.loss_fct(pos_score, neg_score)
131 return loss
132 else: # self.loss_type = 'CE'
133 test_item_emb = self.item_embedding.weight
134 logits = torch.matmul(seq_output, test_item_emb.transpose(0, 1))
135 loss = self.loss_fct(logits, pos_items)
136 return loss
138 def predict(self, interaction):
139 item_seq = interaction[self.ITEM_SEQ]
140 item_seq_len = interaction[self.ITEM_SEQ_LEN]
141 test_item = interaction[self.ITEM_ID]
142 seq_output = self.forward(item_seq, item_seq_len)
143 test_item_emb = self.item_embedding(test_item)
144 scores = torch.mul(seq_output, test_item_emb).sum(dim=1) # [B]
145 return scores
147 def full_sort_predict(self, interaction):
148 item_seq = interaction[self.ITEM_SEQ]
149 item_seq_len = interaction[self.ITEM_SEQ_LEN]
150 seq_output = self.forward(item_seq, item_seq_len)
151 test_items_emb = self.item_embedding.weight
152 scores = torch.matmul(seq_output, test_items_emb.transpose(0, 1)) # [B n_items]
153 return scores