nextrec 0.4.34__py3-none-any.whl → 0.5.0__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.
- nextrec/__version__.py +1 -1
- nextrec/basic/activation.py +7 -13
- nextrec/basic/layers.py +28 -94
- nextrec/basic/model.py +512 -4
- nextrec/cli.py +101 -18
- nextrec/data/data_processing.py +8 -13
- nextrec/data/preprocessor.py +449 -846
- nextrec/models/ranking/afm.py +4 -9
- nextrec/models/ranking/dien.py +7 -8
- nextrec/models/ranking/ffm.py +2 -2
- nextrec/models/retrieval/sdm.py +1 -2
- nextrec/models/sequential/hstu.py +0 -2
- nextrec/utils/onnx_utils.py +252 -0
- {nextrec-0.4.34.dist-info → nextrec-0.5.0.dist-info}/METADATA +10 -4
- {nextrec-0.4.34.dist-info → nextrec-0.5.0.dist-info}/RECORD +18 -18
- nextrec/models/multi_task/[pre]star.py +0 -192
- {nextrec-0.4.34.dist-info → nextrec-0.5.0.dist-info}/WHEEL +0 -0
- {nextrec-0.4.34.dist-info → nextrec-0.5.0.dist-info}/entry_points.txt +0 -0
- {nextrec-0.4.34.dist-info → nextrec-0.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|