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

1# @Time : 2020/9/14 16:57 

2# @Author : Hui Wang 

3# @Email : hui.wang@ruc.edu.cn 

4 

5r"""GRU4RecF 

6################################################ 

7 

8Reference: 

9 Balázs Hidasi et al. "Parallel Recurrent Neural Network Architectures for 

10 Feature-rich Session-based Recommendations." in RecSys 2016. 

11 

12""" 

13 

14import torch 

15from torch import nn 

16 

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 

21 

22 

23class GRU4RecF(SequentialRecommender): 

24 r"""In the original paper, the authors proposed several architectures. We compared 3 different 

25 architectures: 

26 

27 (1) Concatenate item input and feature input and use single RNN, 

28 

29 (2) Concatenate outputs from two different RNNs, 

30 

31 (3) Weighted sum of outputs from two different RNNs. 

32 

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 """ 

37 

38 def __init__(self, config, dataset): 

39 super().__init__(config, dataset) 

40 

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"] 

46 

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"]) 

51 

52 self.loss_type = config["loss_type"] 

53 

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']!") 

86 

87 # parameters initialization 

88 self.apply(xavier_normal_initialization) 

89 self.other_parameter_name = ["feature_embed_layer"] 

90 

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] 

95 

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) 

105 

106 feature_table = torch.cat(feature_table, dim=-2) 

107 # [batch len num_features hidden_size] 

108 table_shape = feature_table.shape 

109 

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] 

113 

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] 

118 

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 

137 

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 

146 

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