Coverage for hopwise/model/context_aware_recommender/dcn.py: 100%

53 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-09-30 13:25 +0000

1# @Time : 2020/10/4 

2# @Author : Zhichao Feng 

3# @Email : fzcbupt@gmail.com 

4 

5# UPDATE 

6# @Time : 2020/10/21 

7# @Author : Zhichao Feng 

8# @email : fzcbupt@gmail.com 

9 

10r"""DCN 

11################################################ 

12Reference: 

13 Ruoxi Wang at al. "Deep & Cross Network for Ad Click Predictions." in ADKDD 2017. 

14 

15Reference code: 

16 https://github.com/shenweichen/DeepCTR-Torch 

17""" 

18 

19import torch 

20from torch import nn 

21from torch.nn.init import constant_, xavier_normal_ 

22 

23from hopwise.model.abstract_recommender import ContextRecommender 

24from hopwise.model.layers import MLPLayers 

25from hopwise.model.loss import RegLoss 

26 

27 

28class DCN(ContextRecommender): 

29 """Deep & Cross Network replaces the wide part in Wide&Deep with cross network, 

30 automatically construct limited high-degree cross features, and learns the corresponding weights. 

31 

32 """ 

33 

34 def __init__(self, config, dataset): 

35 super().__init__(config, dataset) 

36 

37 # load parameters info 

38 self.mlp_hidden_size = config["mlp_hidden_size"] 

39 self.cross_layer_num = config["cross_layer_num"] 

40 self.reg_weight = config["reg_weight"] 

41 self.dropout_prob = config["dropout_prob"] 

42 

43 # define layers and loss 

44 # init weight and bias of each cross layer 

45 self.cross_layer_w = nn.ParameterList( 

46 nn.Parameter(torch.randn(self.num_feature_field * self.embedding_size).to(self.device)) 

47 for _ in range(self.cross_layer_num) 

48 ) 

49 self.cross_layer_b = nn.ParameterList( 

50 nn.Parameter(torch.zeros(self.num_feature_field * self.embedding_size).to(self.device)) 

51 for _ in range(self.cross_layer_num) 

52 ) 

53 

54 # size of mlp hidden layer 

55 size_list = [self.embedding_size * self.num_feature_field] + self.mlp_hidden_size 

56 # size of cross network output 

57 in_feature_num = self.embedding_size * self.num_feature_field + self.mlp_hidden_size[-1] 

58 

59 self.mlp_layers = MLPLayers(size_list, dropout=self.dropout_prob, bn=True) 

60 self.predict_layer = nn.Linear(in_feature_num, 1) 

61 self.reg_loss = RegLoss() 

62 self.sigmoid = nn.Sigmoid() 

63 self.loss = nn.BCEWithLogitsLoss() 

64 

65 # parameters initialization 

66 self.apply(self._init_weights) 

67 

68 def _init_weights(self, module): 

69 if isinstance(module, nn.Embedding): 

70 xavier_normal_(module.weight.data) 

71 elif isinstance(module, nn.Linear): 

72 xavier_normal_(module.weight.data) 

73 if module.bias is not None: 

74 constant_(module.bias.data, 0) 

75 

76 def cross_network(self, x_0): 

77 r"""Cross network is composed of cross layers, with each layer having the following formula. 

78 

79 .. math:: x_{l+1} = x_0 {x_l^T} w_l + b_l + x_l 

80 

81 :math:`x_l`, :math:`x_{l+1}` are column vectors denoting the outputs from the l -th and 

82 (l + 1)-th cross layers, respectively. 

83 :math:`w_l`, :math:`b_l` are the weight and bias parameters of the l -th layer. 

84 

85 Args: 

86 x_0(torch.Tensor): Embedding vectors of all features, input of cross network. 

87 

88 Returns: 

89 torch.Tensor:output of cross network, [batch_size, num_feature_field * embedding_size] 

90 

91 """ 

92 x_l = x_0 

93 for i in range(self.cross_layer_num): 

94 xl_w = torch.tensordot(x_l, self.cross_layer_w[i], dims=([1], [0])) 

95 xl_dot = (x_0.transpose(0, 1) * xl_w).transpose(0, 1) 

96 x_l = xl_dot + self.cross_layer_b[i] + x_l 

97 return x_l 

98 

99 def forward(self, interaction): 

100 dcn_all_embeddings = self.concat_embed_input_fields(interaction) # [batch_size, num_field, embed_dim] 

101 batch_size = dcn_all_embeddings.shape[0] 

102 dcn_all_embeddings = dcn_all_embeddings.view(batch_size, -1) 

103 

104 # DNN 

105 deep_output = self.mlp_layers(dcn_all_embeddings) 

106 # Cross Network 

107 cross_output = self.cross_network(dcn_all_embeddings) 

108 stack = torch.cat([cross_output, deep_output], dim=-1) 

109 output = self.predict_layer(stack) 

110 

111 return output.squeeze(1) 

112 

113 def calculate_loss(self, interaction): 

114 label = interaction[self.LABEL] 

115 output = self.forward(interaction) 

116 l2_loss = self.reg_weight * self.reg_loss(self.cross_layer_w) 

117 return self.loss(output, label) + l2_loss 

118 

119 def predict(self, interaction): 

120 return self.sigmoid(self.forward(interaction))