nextrec 0.4.34__py3-none-any.whl → 0.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,192 +0,0 @@
1
- """
2
- Date: create on 01/01/2026 - prerelease version: still need to align with the source paper
3
- Checkpoint: edit on 01/14/2026
4
- Author: Yang Zhou, zyaztec@gmail.com
5
- Reference:
6
- - [1] Sheng XR, Zhao L, Zhou G, Ding X, Dai B, Luo Q, Yang S, Lv J, Zhang C, Deng H, Zhu X. One Model to Serve All: Star Topology Adaptive Recommender for Multi-Domain CTR Prediction. arXiv preprint arXiv:2101.11427, 2021.
7
- URL: https://arxiv.org/abs/2101.11427
8
- - [2] MMLRec-A-Unified-Multi-Task-and-Multi-Scenario-Learning-Benchmark-for-Recommendation: https://github.com/alipay/MMLRec-A-Unified-Multi-Task-and-Multi-Scenario-Learning-Benchmark-for-Recommendation/
9
-
10
- STAR uses shared-specific linear layers to adapt representations per task while
11
- optionally reusing shared parameters. It can also apply domain-specific batch
12
- normalization on the first hidden layer when a domain mask is provided.
13
- """
14
-
15
- from __future__ import annotations
16
-
17
- import torch
18
- import torch.nn as nn
19
-
20
- from nextrec.basic.activation import activation_layer
21
- from nextrec.basic.features import DenseFeature, SequenceFeature, SparseFeature
22
- from nextrec.basic.heads import TaskHead
23
- from nextrec.basic.layers import DomainBatchNorm, EmbeddingLayer
24
- from nextrec.basic.model import BaseModel
25
- from nextrec.utils.types import TaskTypeInput, TaskTypeName
26
-
27
-
28
- class SharedSpecificLinear(nn.Module):
29
- """
30
- Shared-specific linear layer: task-specific projection plus optional shared one.
31
- """
32
-
33
- def __init__(
34
- self,
35
- input_dim: int,
36
- output_dim: int,
37
- nums_task: int,
38
- use_shared: bool = True,
39
- ) -> None:
40
- super().__init__()
41
- self.use_shared = use_shared
42
- self.shared = nn.Linear(input_dim, output_dim) if use_shared else None
43
- self.specific = nn.ModuleList(
44
- [nn.Linear(input_dim, output_dim) for _ in range(nums_task)]
45
- )
46
-
47
- def forward(self, x: torch.Tensor, task_idx: int) -> torch.Tensor:
48
- output = self.specific[task_idx](x)
49
- if self.use_shared and self.shared is not None:
50
- output = output + self.shared(x)
51
- return output
52
-
53
-
54
- class STAR(BaseModel):
55
- """
56
- STAR: shared-specific multi-task tower with optional domain-specific batch norm.
57
- """
58
-
59
- @property
60
- def model_name(self) -> str:
61
- return "STAR"
62
-
63
- @property
64
- def default_task(self) -> TaskTypeName | list[TaskTypeName]:
65
- nums_task = self.nums_task if hasattr(self, "nums_task") else None
66
- if nums_task is not None and nums_task > 0:
67
- return ["binary"] * nums_task
68
- return ["binary"]
69
-
70
- def __init__(
71
- self,
72
- dense_features: list[DenseFeature] | None = None,
73
- sparse_features: list[SparseFeature] | None = None,
74
- sequence_features: list[SequenceFeature] | None = None,
75
- target: list[str] | str | None = None,
76
- task: TaskTypeInput | list[TaskTypeInput] | None = None,
77
- mlp_params: dict | None = None,
78
- use_shared: bool = True,
79
- **kwargs,
80
- ) -> None:
81
- dense_features = dense_features or []
82
- sparse_features = sparse_features or []
83
- sequence_features = sequence_features or []
84
- mlp_params = mlp_params or {}
85
- mlp_params.setdefault("hidden_dims", [256, 128])
86
- mlp_params.setdefault("activation", "relu")
87
- mlp_params.setdefault("dropout", 0.0)
88
- mlp_params.setdefault("norm_type", "none")
89
-
90
- if target is None:
91
- target = []
92
- elif isinstance(target, str):
93
- target = [target]
94
-
95
- self.nums_task = len(target) if target else 1
96
-
97
- super().__init__(
98
- dense_features=dense_features,
99
- sparse_features=sparse_features,
100
- sequence_features=sequence_features,
101
- target=target,
102
- task=task,
103
- **kwargs,
104
- )
105
-
106
- if not mlp_params["hidden_dims"]:
107
- raise ValueError("mlp_params['hidden_dims'] must not be empty.")
108
-
109
- norm_type = mlp_params["norm_type"]
110
- self.dnn_use_bn = norm_type == "batch_norm"
111
- self.dnn_dropout = mlp_params["dropout"]
112
-
113
- self.embedding = EmbeddingLayer(features=self.all_features)
114
- input_dim = self.embedding.input_dim
115
-
116
- layer_units = [input_dim] + list(mlp_params["hidden_dims"])
117
- self.star_layers = nn.ModuleList(
118
- [
119
- SharedSpecificLinear(
120
- input_dim=layer_units[idx],
121
- output_dim=layer_units[idx + 1],
122
- nums_task=self.nums_task,
123
- use_shared=use_shared,
124
- )
125
- for idx in range(len(mlp_params["hidden_dims"]))
126
- ]
127
- )
128
- self.activation_layers = nn.ModuleList(
129
- [
130
- activation_layer(mlp_params["activation"])
131
- for _ in range(len(mlp_params["hidden_dims"]))
132
- ]
133
- )
134
- if mlp_params["dropout"] > 0:
135
- self.dropout_layers = nn.ModuleList(
136
- [
137
- nn.Dropout(mlp_params["dropout"])
138
- for _ in range(len(mlp_params["hidden_dims"]))
139
- ]
140
- )
141
- else:
142
- self.dropout_layers = nn.ModuleList(
143
- [nn.Identity() for _ in range(len(mlp_params["hidden_dims"]))]
144
- )
145
-
146
- self.domain_bn = (
147
- DomainBatchNorm(
148
- num_features=mlp_params["hidden_dims"][0], num_domains=self.nums_task
149
- )
150
- if self.dnn_use_bn
151
- else None
152
- )
153
-
154
- self.final_layer = SharedSpecificLinear(
155
- input_dim=mlp_params["hidden_dims"][-1],
156
- output_dim=1,
157
- nums_task=self.nums_task,
158
- use_shared=use_shared,
159
- )
160
- self.prediction_layer = TaskHead(
161
- task_type=self.task, task_dims=[1] * self.nums_task
162
- )
163
-
164
- self.grad_norm_shared_modules = ["embedding", "star_layers", "final_layer"]
165
- self.register_regularization_weights(
166
- embedding_attr="embedding",
167
- include_modules=["star_layers", "final_layer"],
168
- )
169
-
170
- def forward(
171
- self, x: dict[str, torch.Tensor], domain_mask: torch.Tensor | None = None
172
- ) -> torch.Tensor:
173
- input_flat = self.embedding(x=x, features=self.all_features, squeeze_dim=True)
174
-
175
- task_outputs = []
176
- for task_idx in range(self.nums_task):
177
- output = input_flat
178
- for layer_idx, layer in enumerate(self.star_layers):
179
- output = layer(output, task_idx)
180
- output = self.activation_layers[layer_idx](output)
181
- output = self.dropout_layers[layer_idx](output)
182
- if (
183
- layer_idx == 0
184
- and self.dnn_use_bn
185
- and self.domain_bn is not None
186
- and domain_mask is not None
187
- ):
188
- output = self.domain_bn(output, domain_mask)
189
- task_outputs.append(self.final_layer(output, task_idx))
190
-
191
- logits = torch.cat(task_outputs, dim=1)
192
- return self.prediction_layer(logits)