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
« 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
5# UPDATE
6# @Time : 2020/10/21
7# @Author : Zhichao Feng
8# @email : fzcbupt@gmail.com
10r"""DCN
11################################################
12Reference:
13 Ruoxi Wang at al. "Deep & Cross Network for Ad Click Predictions." in ADKDD 2017.
15Reference code:
16 https://github.com/shenweichen/DeepCTR-Torch
17"""
19import torch
20from torch import nn
21from torch.nn.init import constant_, xavier_normal_
23from hopwise.model.abstract_recommender import ContextRecommender
24from hopwise.model.layers import MLPLayers
25from hopwise.model.loss import RegLoss
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.
32 """
34 def __init__(self, config, dataset):
35 super().__init__(config, dataset)
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"]
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 )
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]
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()
65 # parameters initialization
66 self.apply(self._init_weights)
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)
76 def cross_network(self, x_0):
77 r"""Cross network is composed of cross layers, with each layer having the following formula.
79 .. math:: x_{l+1} = x_0 {x_l^T} w_l + b_l + x_l
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.
85 Args:
86 x_0(torch.Tensor): Embedding vectors of all features, input of cross network.
88 Returns:
89 torch.Tensor:output of cross network, [batch_size, num_feature_field * embedding_size]
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
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)
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)
111 return output.squeeze(1)
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
119 def predict(self, interaction):
120 return self.sigmoid(self.forward(interaction))