Loading your learning…
Loading your learning…
Predict a click using interactions between person, item, and context features. Input x [B, D] contains B examples with D numeric features each. A cross branch repeatedly scales the original features by a learned weighted sum of the current features, then adds a bias and the previous result. A separate deep branch transforms the same input with a linear layer and ReLU. Concatenate both outputs and use a head to return B logits: unrestricted click scores. The main section trains the complete model for one illustrative update.
import torchfrom torch import nn class CrossNetworkV1(nn.Module): def __init__(self, dim, depth=2): super().__init__() self.weights = nn.ParameterList([ nn.Parameter(torch.randn(dim) * dim ** -0.5) for _ in range(depth) ]) self.biases = nn.ParameterList([ nn.Parameter(torch.zeros(dim)) for _ in range(depth) ]) def forward(self, x0): # x0: [B, D]; each row is one example's combined features. x = x0 for weight, bias in zip(self.weights, self.biases): projection = (x * weight).sum(dim=-1, keepdim=True) # [B, 1] x = x0 * projection + bias + x # [B, D] return x class DeepCrossPredictor(nn.Module): def __init__(self, dim): super().__init__() self.cross = CrossNetworkV1(dim, depth=2) self.deep = nn.Sequential(nn.Linear(dim, 16), nn.ReLU()) self.head = nn.Linear(dim + 16, 1) def forward(self, x): combined = torch.cat([self.cross(x), self.deep(x)], dim=-1) return self.head(combined).squeeze(-1) # [B] if __name__ == "__main__": torch.manual_seed(0) x = torch.tensor([[2., 3.], [1., 4.]]) # [B=2, D=2] labels = torch.tensor([1., 0.]) model = DeepCrossPredictor(dim=2) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) optimizer.zero_grad() logits = model(x) loss = nn.BCEWithLogitsLoss()(logits, labels) loss.backward() optimizer.step() with torch.no_grad(): print("Probabilities:", model(x).sigmoid()) # A toy update shows the wiring, not prediction quality.The cross branch returns [B, D]. The deep branch maps [B, D] to [B, 16] using a learned Linear layer and ReLU, which keeps positive values and sets negative ones to zero. Concatenating along dim=-1 gives [B, D+16]. These are two complementary ways to transform the same features.
Concatenation keeps both representations side by side. Adding the branches would need equal widths and would mix their features together.
return x class DeepCrossPredictor(nn.Module): def __init__(self, dim): super().__init__() self.cross = CrossNetworkV1(dim, depth=2) self.deep = nn.Sequential(nn.Linear(dim, 16), nn.ReLU()) self.head = nn.Linear(dim + 16, 1) def forward(self, x):