nextrec 0.1.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.
Files changed (51) hide show
  1. nextrec/__init__.py +41 -0
  2. nextrec/__version__.py +1 -0
  3. nextrec/basic/__init__.py +0 -0
  4. nextrec/basic/activation.py +92 -0
  5. nextrec/basic/callback.py +35 -0
  6. nextrec/basic/dataloader.py +447 -0
  7. nextrec/basic/features.py +87 -0
  8. nextrec/basic/layers.py +985 -0
  9. nextrec/basic/loggers.py +124 -0
  10. nextrec/basic/metrics.py +557 -0
  11. nextrec/basic/model.py +1438 -0
  12. nextrec/data/__init__.py +27 -0
  13. nextrec/data/data_utils.py +132 -0
  14. nextrec/data/preprocessor.py +662 -0
  15. nextrec/loss/__init__.py +35 -0
  16. nextrec/loss/loss_utils.py +136 -0
  17. nextrec/loss/match_losses.py +294 -0
  18. nextrec/models/generative/hstu.py +0 -0
  19. nextrec/models/generative/tiger.py +0 -0
  20. nextrec/models/match/__init__.py +13 -0
  21. nextrec/models/match/dssm.py +200 -0
  22. nextrec/models/match/dssm_v2.py +162 -0
  23. nextrec/models/match/mind.py +210 -0
  24. nextrec/models/match/sdm.py +253 -0
  25. nextrec/models/match/youtube_dnn.py +172 -0
  26. nextrec/models/multi_task/esmm.py +129 -0
  27. nextrec/models/multi_task/mmoe.py +161 -0
  28. nextrec/models/multi_task/ple.py +260 -0
  29. nextrec/models/multi_task/share_bottom.py +126 -0
  30. nextrec/models/ranking/__init__.py +17 -0
  31. nextrec/models/ranking/afm.py +118 -0
  32. nextrec/models/ranking/autoint.py +140 -0
  33. nextrec/models/ranking/dcn.py +120 -0
  34. nextrec/models/ranking/deepfm.py +95 -0
  35. nextrec/models/ranking/dien.py +214 -0
  36. nextrec/models/ranking/din.py +181 -0
  37. nextrec/models/ranking/fibinet.py +130 -0
  38. nextrec/models/ranking/fm.py +87 -0
  39. nextrec/models/ranking/masknet.py +125 -0
  40. nextrec/models/ranking/pnn.py +128 -0
  41. nextrec/models/ranking/widedeep.py +105 -0
  42. nextrec/models/ranking/xdeepfm.py +117 -0
  43. nextrec/utils/__init__.py +18 -0
  44. nextrec/utils/common.py +14 -0
  45. nextrec/utils/embedding.py +19 -0
  46. nextrec/utils/initializer.py +47 -0
  47. nextrec/utils/optimizer.py +75 -0
  48. nextrec-0.1.1.dist-info/METADATA +302 -0
  49. nextrec-0.1.1.dist-info/RECORD +51 -0
  50. nextrec-0.1.1.dist-info/WHEEL +4 -0
  51. nextrec-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,130 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Huang T, Zhang Z, Zhang B, et al. FiBiNET: Combining feature importance and bilinear feature interaction
7
+ for click-through rate prediction[C]//RecSys. 2019: 169-177.
8
+ """
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+ from nextrec.basic.model import BaseModel
14
+ from nextrec.basic.layers import (
15
+ BiLinearInteractionLayer,
16
+ EmbeddingLayer,
17
+ LR,
18
+ MLP,
19
+ PredictionLayer,
20
+ SENETLayer,
21
+ )
22
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
23
+
24
+
25
+ class FiBiNET(BaseModel):
26
+ @property
27
+ def model_name(self):
28
+ return "FiBiNET"
29
+
30
+ @property
31
+ def task_type(self):
32
+ return "binary"
33
+
34
+ def __init__(self,
35
+ dense_features: list[DenseFeature] | list = [],
36
+ sparse_features: list[SparseFeature] | list = [],
37
+ sequence_features: list[SequenceFeature] | list = [],
38
+ mlp_params: dict = {},
39
+ bilinear_type: str = "field_interaction",
40
+ senet_reduction: int = 3,
41
+ target: list[str] | list = [],
42
+ optimizer: str = "adam",
43
+ optimizer_params: dict = {},
44
+ loss: str | nn.Module | None = "bce",
45
+ device: str = 'cpu',
46
+ model_id: str = "baseline",
47
+ embedding_l1_reg=1e-6,
48
+ dense_l1_reg=1e-5,
49
+ embedding_l2_reg=1e-5,
50
+ dense_l2_reg=1e-4):
51
+
52
+ super(FiBiNET, self).__init__(
53
+ dense_features=dense_features,
54
+ sparse_features=sparse_features,
55
+ sequence_features=sequence_features,
56
+ target=target,
57
+ task=self.task_type,
58
+ device=device,
59
+ embedding_l1_reg=embedding_l1_reg,
60
+ dense_l1_reg=dense_l1_reg,
61
+ embedding_l2_reg=embedding_l2_reg,
62
+ dense_l2_reg=dense_l2_reg,
63
+ early_stop_patience=20,
64
+ model_id=model_id
65
+ )
66
+
67
+ self.loss = loss
68
+ if self.loss is None:
69
+ self.loss = "bce"
70
+
71
+ self.linear_features = sparse_features + sequence_features
72
+ self.deep_features = dense_features + sparse_features + sequence_features
73
+ self.interaction_features = sparse_features + sequence_features
74
+
75
+ if len(self.interaction_features) < 2:
76
+ raise ValueError("FiBiNET requires at least two sparse/sequence features for interactions.")
77
+
78
+ self.embedding = EmbeddingLayer(features=self.deep_features)
79
+
80
+ self.num_fields = len(self.interaction_features)
81
+ self.embedding_dim = self.interaction_features[0].embedding_dim
82
+ if any(f.embedding_dim != self.embedding_dim for f in self.interaction_features):
83
+ raise ValueError("All interaction features must share the same embedding_dim in FiBiNET.")
84
+
85
+ self.senet = SENETLayer(num_fields=self.num_fields, reduction_ratio=senet_reduction)
86
+ self.bilinear_standard = BiLinearInteractionLayer(
87
+ input_dim=self.embedding_dim,
88
+ num_fields=self.num_fields,
89
+ bilinear_type=bilinear_type,
90
+ )
91
+ self.bilinear_senet = BiLinearInteractionLayer(
92
+ input_dim=self.embedding_dim,
93
+ num_fields=self.num_fields,
94
+ bilinear_type=bilinear_type,
95
+ )
96
+
97
+ linear_dim = sum([f.embedding_dim for f in self.linear_features])
98
+ self.linear = LR(linear_dim)
99
+
100
+ num_pairs = self.num_fields * (self.num_fields - 1) // 2
101
+ interaction_dim = num_pairs * self.embedding_dim * 2
102
+ self.mlp = MLP(input_dim=interaction_dim, **mlp_params)
103
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
104
+
105
+ # Register regularization weights
106
+ self._register_regularization_weights(
107
+ embedding_attr='embedding',
108
+ include_modules=['linear', 'senet', 'bilinear_standard', 'bilinear_senet', 'mlp']
109
+ )
110
+
111
+ self.compile(
112
+ optimizer=optimizer,
113
+ optimizer_params=optimizer_params,
114
+ loss=loss
115
+ )
116
+
117
+ def forward(self, x):
118
+ input_linear = self.embedding(x=x, features=self.linear_features, squeeze_dim=True)
119
+ y_linear = self.linear(input_linear)
120
+
121
+ field_emb = self.embedding(x=x, features=self.interaction_features, squeeze_dim=False)
122
+ senet_emb = self.senet(field_emb)
123
+
124
+ bilinear_standard = self.bilinear_standard(field_emb).flatten(start_dim=1)
125
+ bilinear_senet = self.bilinear_senet(senet_emb).flatten(start_dim=1)
126
+ deep_input = torch.cat([bilinear_standard, bilinear_senet], dim=1)
127
+ y_deep = self.mlp(deep_input)
128
+
129
+ y = y_linear + y_deep
130
+ return self.prediction_layer(y)
@@ -0,0 +1,87 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Rendle S. Factorization machines[C]//ICDM. 2010: 995-1000.
7
+ """
8
+
9
+ import torch.nn as nn
10
+
11
+ from nextrec.basic.model import BaseModel
12
+ from nextrec.basic.layers import EmbeddingLayer, FM as FMInteraction, LR, PredictionLayer
13
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
14
+
15
+
16
+ class FM(BaseModel):
17
+ @property
18
+ def model_name(self):
19
+ return "FM"
20
+
21
+ @property
22
+ def task_type(self):
23
+ return "binary"
24
+
25
+ def __init__(self,
26
+ dense_features: list[DenseFeature] | list = [],
27
+ sparse_features: list[SparseFeature] | list = [],
28
+ sequence_features: list[SequenceFeature] | list = [],
29
+ target: list[str] | list = [],
30
+ optimizer: str = "adam",
31
+ optimizer_params: dict = {},
32
+ loss: str | nn.Module | None = "bce",
33
+ device: str = 'cpu',
34
+ model_id: str = "baseline",
35
+ embedding_l1_reg=1e-6,
36
+ dense_l1_reg=1e-5,
37
+ embedding_l2_reg=1e-5,
38
+ dense_l2_reg=1e-4):
39
+
40
+ super(FM, self).__init__(
41
+ dense_features=dense_features,
42
+ sparse_features=sparse_features,
43
+ sequence_features=sequence_features,
44
+ target=target,
45
+ task=self.task_type,
46
+ device=device,
47
+ embedding_l1_reg=embedding_l1_reg,
48
+ dense_l1_reg=dense_l1_reg,
49
+ embedding_l2_reg=embedding_l2_reg,
50
+ dense_l2_reg=dense_l2_reg,
51
+ early_stop_patience=20,
52
+ model_id=model_id
53
+ )
54
+
55
+ self.loss = loss
56
+ if self.loss is None:
57
+ self.loss = "bce"
58
+
59
+ self.fm_features = sparse_features + sequence_features
60
+ if len(self.fm_features) == 0:
61
+ raise ValueError("FM requires at least one sparse or sequence feature.")
62
+
63
+ self.embedding = EmbeddingLayer(features=self.fm_features)
64
+
65
+ fm_input_dim = sum([f.embedding_dim for f in self.fm_features])
66
+ self.linear = LR(fm_input_dim)
67
+ self.fm = FMInteraction(reduce_sum=True)
68
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
69
+
70
+ # Register regularization weights
71
+ self._register_regularization_weights(
72
+ embedding_attr='embedding',
73
+ include_modules=['linear']
74
+ )
75
+
76
+ self.compile(
77
+ optimizer=optimizer,
78
+ optimizer_params=optimizer_params,
79
+ loss=loss
80
+ )
81
+
82
+ def forward(self, x):
83
+ input_fm = self.embedding(x=x, features=self.fm_features, squeeze_dim=False)
84
+ y_linear = self.linear(input_fm.flatten(start_dim=1))
85
+ y_fm = self.fm(input_fm)
86
+ y = y_linear + y_fm
87
+ return self.prediction_layer(y)
@@ -0,0 +1,125 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Pan Z, Sun F, Liu J, et al. MaskNet: Introducing feature-wise gating blocks for high-dimensional
7
+ sparse recommendation data (CCF-Tencent CTR competition solution, 2020).
8
+ """
9
+
10
+ import torch
11
+ import torch.nn as nn
12
+
13
+ from nextrec.basic.model import BaseModel
14
+ from nextrec.basic.layers import EmbeddingLayer, LR, MLP, PredictionLayer
15
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
16
+
17
+
18
+ class MaskNet(BaseModel):
19
+ @property
20
+ def model_name(self):
21
+ return "MaskNet"
22
+
23
+ @property
24
+ def task_type(self):
25
+ return "binary"
26
+
27
+ def __init__(self,
28
+ dense_features: list[DenseFeature] | list = [],
29
+ sparse_features: list[SparseFeature] | list = [],
30
+ sequence_features: list[SequenceFeature] | list = [],
31
+ num_blocks: int = 3,
32
+ mask_hidden_dim: int = 64,
33
+ block_dropout: float = 0.1,
34
+ mlp_params: dict = {},
35
+ target: list[str] | list = [],
36
+ optimizer: str = "adam",
37
+ optimizer_params: dict = {},
38
+ loss: str | nn.Module | None = "bce",
39
+ device: str = 'cpu',
40
+ model_id: str = "baseline",
41
+ embedding_l1_reg=1e-6,
42
+ dense_l1_reg=1e-5,
43
+ embedding_l2_reg=1e-5,
44
+ dense_l2_reg=1e-4):
45
+
46
+ super(MaskNet, self).__init__(
47
+ dense_features=dense_features,
48
+ sparse_features=sparse_features,
49
+ sequence_features=sequence_features,
50
+ target=target,
51
+ task=self.task_type,
52
+ device=device,
53
+ embedding_l1_reg=embedding_l1_reg,
54
+ dense_l1_reg=dense_l1_reg,
55
+ embedding_l2_reg=embedding_l2_reg,
56
+ dense_l2_reg=dense_l2_reg,
57
+ early_stop_patience=20,
58
+ model_id=model_id
59
+ )
60
+
61
+ self.loss = loss
62
+ if self.loss is None:
63
+ self.loss = "bce"
64
+
65
+ self.mask_features = sparse_features + sequence_features
66
+ if len(self.mask_features) == 0:
67
+ raise ValueError("MaskNet requires at least one sparse/sequence feature.")
68
+
69
+ self.embedding = EmbeddingLayer(features=self.mask_features)
70
+ self.num_fields = len(self.mask_features)
71
+ self.embedding_dim = self.mask_features[0].embedding_dim
72
+ if any(f.embedding_dim != self.embedding_dim for f in self.mask_features):
73
+ raise ValueError("MaskNet expects identical embedding_dim across mask_features.")
74
+
75
+ self.num_blocks = max(1, num_blocks)
76
+ self.field_dim = self.num_fields * self.embedding_dim
77
+
78
+ self.linear = LR(self.field_dim)
79
+ self.mask_generators = nn.ModuleList()
80
+ for _ in range(self.num_blocks):
81
+ self.mask_generators.append(
82
+ nn.Sequential(
83
+ nn.Linear(self.field_dim, mask_hidden_dim),
84
+ nn.ReLU(),
85
+ nn.Linear(mask_hidden_dim, self.num_fields)
86
+ )
87
+ )
88
+
89
+ self.block_dropout = nn.Dropout(block_dropout)
90
+ self.final_mlp = MLP(input_dim=self.field_dim * self.num_blocks, **mlp_params)
91
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
92
+
93
+ self._register_regularization_weights(
94
+ embedding_attr='embedding',
95
+ include_modules=['linear', 'mask_generators', 'final_mlp']
96
+ )
97
+
98
+ self.compile(
99
+ optimizer=optimizer,
100
+ optimizer_params=optimizer_params,
101
+ loss=loss
102
+ )
103
+
104
+ def forward(self, x):
105
+ field_emb = self.embedding(x=x, features=self.mask_features, squeeze_dim=False)
106
+ flat_input = field_emb.flatten(start_dim=1)
107
+ y_linear = self.linear(flat_input)
108
+
109
+ block_input = field_emb
110
+ mask_input = flat_input
111
+ block_outputs = []
112
+ for mask_gen in self.mask_generators:
113
+ mask_logits = mask_gen(mask_input)
114
+ mask = torch.sigmoid(mask_logits).unsqueeze(-1)
115
+ masked_emb = block_input * mask
116
+ block_output = self.block_dropout(masked_emb.flatten(start_dim=1))
117
+ block_outputs.append(block_output)
118
+ mask_input = block_output
119
+ block_input = masked_emb.view_as(field_emb)
120
+
121
+ stacked = torch.cat(block_outputs, dim=1)
122
+ y_deep = self.final_mlp(stacked)
123
+
124
+ y = y_linear + y_deep
125
+ return self.prediction_layer(y)
@@ -0,0 +1,128 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Qu Y, Cai H, Ren K, et al. Product-based neural networks for user response prediction[C]//ICDM. 2016: 1149-1154.
7
+ """
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+
12
+ from nextrec.basic.model import BaseModel
13
+ from nextrec.basic.layers import EmbeddingLayer, MLP, PredictionLayer
14
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
15
+
16
+
17
+ class PNN(BaseModel):
18
+ @property
19
+ def model_name(self):
20
+ return "PNN"
21
+
22
+ @property
23
+ def task_type(self):
24
+ return "binary"
25
+
26
+ def __init__(self,
27
+ dense_features: list[DenseFeature] | list = [],
28
+ sparse_features: list[SparseFeature] | list = [],
29
+ sequence_features: list[SequenceFeature] | list = [],
30
+ mlp_params: dict = {},
31
+ product_type: str = "inner",
32
+ outer_product_dim: int | None = None,
33
+ target: list[str] | list = [],
34
+ optimizer: str = "adam",
35
+ optimizer_params: dict = {},
36
+ loss: str | nn.Module | None = "bce",
37
+ device: str = 'cpu',
38
+ model_id: str = "baseline",
39
+ embedding_l1_reg=1e-6,
40
+ dense_l1_reg=1e-5,
41
+ embedding_l2_reg=1e-5,
42
+ dense_l2_reg=1e-4):
43
+
44
+ super(PNN, self).__init__(
45
+ dense_features=dense_features,
46
+ sparse_features=sparse_features,
47
+ sequence_features=sequence_features,
48
+ target=target,
49
+ task=self.task_type,
50
+ device=device,
51
+ embedding_l1_reg=embedding_l1_reg,
52
+ dense_l1_reg=dense_l1_reg,
53
+ embedding_l2_reg=embedding_l2_reg,
54
+ dense_l2_reg=dense_l2_reg,
55
+ early_stop_patience=20,
56
+ model_id=model_id
57
+ )
58
+
59
+ self.loss = loss
60
+ if self.loss is None:
61
+ self.loss = "bce"
62
+
63
+ self.field_features = sparse_features + sequence_features
64
+ if len(self.field_features) < 2:
65
+ raise ValueError("PNN requires at least two sparse/sequence features.")
66
+
67
+ self.embedding = EmbeddingLayer(features=self.field_features)
68
+ self.num_fields = len(self.field_features)
69
+ self.embedding_dim = self.field_features[0].embedding_dim
70
+ if any(f.embedding_dim != self.embedding_dim for f in self.field_features):
71
+ raise ValueError("All field features must share the same embedding_dim for PNN.")
72
+
73
+ self.product_type = product_type.lower()
74
+ if self.product_type not in {"inner", "outer"}:
75
+ raise ValueError("product_type must be 'inner' or 'outer'.")
76
+
77
+ self.num_pairs = self.num_fields * (self.num_fields - 1) // 2
78
+ if self.product_type == "outer":
79
+ self.outer_dim = outer_product_dim or self.embedding_dim
80
+ self.kernel = nn.Linear(self.embedding_dim, self.outer_dim, bias=False)
81
+ product_dim = self.num_pairs * self.outer_dim
82
+ else:
83
+ self.outer_dim = None
84
+ product_dim = self.num_pairs
85
+
86
+ linear_dim = self.num_fields * self.embedding_dim
87
+ self.mlp = MLP(input_dim=linear_dim + product_dim, **mlp_params)
88
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
89
+
90
+ modules = ['mlp']
91
+ if self.product_type == "outer":
92
+ modules.append('kernel')
93
+ self._register_regularization_weights(
94
+ embedding_attr='embedding',
95
+ include_modules=modules
96
+ )
97
+
98
+ self.compile(
99
+ optimizer=optimizer,
100
+ optimizer_params=optimizer_params,
101
+ loss=loss
102
+ )
103
+
104
+ def forward(self, x):
105
+ field_emb = self.embedding(x=x, features=self.field_features, squeeze_dim=False)
106
+ linear_signal = field_emb.flatten(start_dim=1)
107
+
108
+ if self.product_type == "inner":
109
+ interactions = []
110
+ for i in range(self.num_fields - 1):
111
+ vi = field_emb[:, i, :]
112
+ for j in range(i + 1, self.num_fields):
113
+ vj = field_emb[:, j, :]
114
+ interactions.append(torch.sum(vi * vj, dim=1, keepdim=True))
115
+ product_signal = torch.cat(interactions, dim=1)
116
+ else:
117
+ transformed = self.kernel(field_emb) # [B, F, outer_dim]
118
+ interactions = []
119
+ for i in range(self.num_fields - 1):
120
+ vi = transformed[:, i, :]
121
+ for j in range(i + 1, self.num_fields):
122
+ vj = transformed[:, j, :]
123
+ interactions.append(vi * vj)
124
+ product_signal = torch.stack(interactions, dim=1).flatten(start_dim=1)
125
+
126
+ deep_input = torch.cat([linear_signal, product_signal], dim=1)
127
+ y = self.mlp(deep_input)
128
+ return self.prediction_layer(y)
@@ -0,0 +1,105 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Cheng H T, Koc L, Harmsen J, et al. Wide & deep learning for recommender systems[C]
7
+ //Proceedings of the 1st workshop on deep learning for recommender systems. 2016: 7-10.
8
+ (https://arxiv.org/abs/1606.07792)
9
+ """
10
+
11
+ import torch
12
+ import torch.nn as nn
13
+
14
+ from nextrec.basic.model import BaseModel
15
+ from nextrec.basic.layers import LR, EmbeddingLayer, MLP, PredictionLayer
16
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
17
+
18
+
19
+ class WideDeep(BaseModel):
20
+ @property
21
+ def model_name(self):
22
+ return "WideDeep"
23
+
24
+ @property
25
+ def task_type(self):
26
+ return "binary"
27
+
28
+ def __init__(self,
29
+ dense_features: list[DenseFeature],
30
+ sparse_features: list[SparseFeature],
31
+ sequence_features: list[SequenceFeature],
32
+ mlp_params: dict,
33
+ target: list[str] = [],
34
+ optimizer: str = "adam",
35
+ optimizer_params: dict = {},
36
+ loss: str | nn.Module | None = "bce",
37
+ device: str = 'cpu',
38
+ model_id: str = "baseline",
39
+ embedding_l1_reg=1e-6,
40
+ dense_l1_reg=1e-5,
41
+ embedding_l2_reg=1e-5,
42
+ dense_l2_reg=1e-4):
43
+
44
+ super(WideDeep, self).__init__(
45
+ dense_features=dense_features,
46
+ sparse_features=sparse_features,
47
+ sequence_features=sequence_features,
48
+ target=target,
49
+ task=self.task_type,
50
+ device=device,
51
+ embedding_l1_reg=embedding_l1_reg,
52
+ dense_l1_reg=dense_l1_reg,
53
+ embedding_l2_reg=embedding_l2_reg,
54
+ dense_l2_reg=dense_l2_reg,
55
+ early_stop_patience=20,
56
+ model_id=model_id
57
+ )
58
+
59
+ self.loss = loss
60
+ if self.loss is None:
61
+ self.loss = "bce"
62
+
63
+ # Wide part: use all features for linear model
64
+ self.wide_features = sparse_features + sequence_features
65
+
66
+ # Deep part: use all features
67
+ self.deep_features = dense_features + sparse_features + sequence_features
68
+
69
+ # Embedding layer for deep part
70
+ self.embedding = EmbeddingLayer(features=self.deep_features)
71
+
72
+ # Wide part: Linear layer
73
+ wide_dim = sum([f.embedding_dim for f in self.wide_features])
74
+ self.linear = LR(wide_dim)
75
+
76
+ # Deep part: MLP
77
+ deep_emb_dim_total = sum([f.embedding_dim for f in self.deep_features if not isinstance(f, DenseFeature)])
78
+ dense_input_dim = sum([getattr(f, "embedding_dim", 1) or 1 for f in dense_features])
79
+ self.mlp = MLP(input_dim=deep_emb_dim_total + dense_input_dim, **mlp_params)
80
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
81
+
82
+ # Register regularization weights
83
+ self._register_regularization_weights(
84
+ embedding_attr='embedding',
85
+ include_modules=['linear', 'mlp']
86
+ )
87
+
88
+ self.compile(
89
+ optimizer=optimizer,
90
+ optimizer_params=optimizer_params,
91
+ loss=loss
92
+ )
93
+
94
+ def forward(self, x):
95
+ # Deep part
96
+ input_deep = self.embedding(x=x, features=self.deep_features, squeeze_dim=True)
97
+ y_deep = self.mlp(input_deep) # [B, 1]
98
+
99
+ # Wide part
100
+ input_wide = self.embedding(x=x, features=self.wide_features, squeeze_dim=True)
101
+ y_wide = self.linear(input_wide)
102
+
103
+ # Combine wide and deep
104
+ y = y_wide + y_deep
105
+ return self.prediction_layer(y)
@@ -0,0 +1,117 @@
1
+ """
2
+ Date: create on 09/11/2025
3
+ Author:
4
+ Yang Zhou,zyaztec@gmail.com
5
+ Reference:
6
+ [1] Lian J, Zhou X, Zhang F, et al. xdeepfm: Combining explicit and implicit feature interactions
7
+ for recommender systems[C]//Proceedings of the 24th ACM SIGKDD international conference on
8
+ knowledge discovery & data mining. 2018: 1754-1763.
9
+ (https://arxiv.org/abs/1803.05170)
10
+ """
11
+
12
+ import torch
13
+ import torch.nn as nn
14
+
15
+ from nextrec.basic.model import BaseModel
16
+ from nextrec.basic.layers import LR, EmbeddingLayer, MLP, CIN, PredictionLayer
17
+ from nextrec.basic.features import DenseFeature, SparseFeature, SequenceFeature
18
+
19
+
20
+ class xDeepFM(BaseModel):
21
+ @property
22
+ def model_name(self):
23
+ return "xDeepFM"
24
+
25
+ @property
26
+ def task_type(self):
27
+ return "binary"
28
+
29
+ def __init__(self,
30
+ dense_features: list[DenseFeature],
31
+ sparse_features: list[SparseFeature],
32
+ sequence_features: list[SequenceFeature],
33
+ mlp_params: dict,
34
+ cin_size: list[int] = [128, 128],
35
+ split_half: bool = True,
36
+ target: list[str] = [],
37
+ optimizer: str = "adam",
38
+ optimizer_params: dict = {},
39
+ loss: str | nn.Module | None = "bce",
40
+ device: str = 'cpu',
41
+ model_id: str = "baseline",
42
+ embedding_l1_reg=1e-6,
43
+ dense_l1_reg=1e-5,
44
+ embedding_l2_reg=1e-5,
45
+ dense_l2_reg=1e-4):
46
+
47
+ super(xDeepFM, self).__init__(
48
+ dense_features=dense_features,
49
+ sparse_features=sparse_features,
50
+ sequence_features=sequence_features,
51
+ target=target,
52
+ task=self.task_type,
53
+ device=device,
54
+ embedding_l1_reg=embedding_l1_reg,
55
+ dense_l1_reg=dense_l1_reg,
56
+ embedding_l2_reg=embedding_l2_reg,
57
+ dense_l2_reg=dense_l2_reg,
58
+ early_stop_patience=20,
59
+ model_id=model_id
60
+ )
61
+
62
+ self.loss = loss
63
+ if self.loss is None:
64
+ self.loss = "bce"
65
+
66
+ # Linear part and CIN part: use sparse and sequence features
67
+ self.linear_features = sparse_features + sequence_features
68
+
69
+ # Deep part: use all features
70
+ self.deep_features = dense_features + sparse_features + sequence_features
71
+
72
+ # Embedding layer
73
+ self.embedding = EmbeddingLayer(features=self.deep_features)
74
+
75
+ # Linear part
76
+ linear_dim = sum([f.embedding_dim for f in self.linear_features])
77
+ self.linear = LR(linear_dim)
78
+
79
+ # CIN part: Compressed Interaction Network
80
+ num_fields = len(self.linear_features)
81
+ self.cin = CIN(input_dim=num_fields, cin_size=cin_size, split_half=split_half)
82
+
83
+ # Deep part: DNN
84
+ deep_emb_dim_total = sum([f.embedding_dim for f in self.deep_features if not isinstance(f, DenseFeature)])
85
+ dense_input_dim = sum([getattr(f, "embedding_dim", 1) or 1 for f in dense_features])
86
+ self.mlp = MLP(input_dim=deep_emb_dim_total + dense_input_dim, **mlp_params)
87
+ self.prediction_layer = PredictionLayer(task_type=self.task_type)
88
+
89
+ # Register regularization weights
90
+ self._register_regularization_weights(
91
+ embedding_attr='embedding',
92
+ include_modules=['linear', 'cin', 'mlp']
93
+ )
94
+
95
+ self.compile(
96
+ optimizer=optimizer,
97
+ optimizer_params=optimizer_params,
98
+ loss=loss
99
+ )
100
+
101
+ def forward(self, x):
102
+ # Get embeddings for linear and CIN (sparse features only)
103
+ input_linear = self.embedding(x=x, features=self.linear_features, squeeze_dim=False)
104
+
105
+ # Linear part
106
+ y_linear = self.linear(input_linear.flatten(start_dim=1))
107
+
108
+ # CIN part
109
+ y_cin = self.cin(input_linear) # [B, 1]
110
+
111
+ # Deep part
112
+ input_deep = self.embedding(x=x, features=self.deep_features, squeeze_dim=True)
113
+ y_deep = self.mlp(input_deep) # [B, 1]
114
+
115
+ # Combine all parts
116
+ y = y_linear + y_cin + y_deep
117
+ return self.prediction_layer(y)