nextrec 0.4.10__py3-none-any.whl → 0.4.12__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.
@@ -41,7 +41,8 @@ from nextrec.basic.features import DenseFeature, SequenceFeature, SparseFeature
41
41
  from nextrec.basic.layers import LR, EmbeddingLayer, PredictionLayer
42
42
  from nextrec.basic.model import BaseModel
43
43
 
44
- class EulerInteractionLayerPaper(nn.Module):
44
+
45
+ class EulerInteractionLayer(nn.Module):
45
46
  """
46
47
  Paper-aligned Euler Interaction Layer.
47
48
 
@@ -102,24 +103,32 @@ class EulerInteractionLayerPaper(nn.Module):
102
103
  self.bn = None
103
104
  self.ln = None
104
105
 
105
- def forward(self, r: torch.Tensor, p: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
106
+ def forward(
107
+ self, r: torch.Tensor, p: torch.Tensor
108
+ ) -> tuple[torch.Tensor, torch.Tensor]:
106
109
  """
107
110
  r, p: [B, m, d]
108
111
  return r_out, p_out: [B, n, d]
109
112
  """
110
113
  B, m, d = r.shape
111
- assert m == self.m and d == self.d, f"Expected [B,{self.m},{self.d}] got {r.shape}"
114
+ assert (
115
+ m == self.m and d == self.d
116
+ ), f"Expected [B,{self.m},{self.d}] got {r.shape}"
112
117
 
113
118
  # Euler Transformation: rectangular -> polar
114
- lam = torch.sqrt(r * r + p * p + self.eps) # [B,m,d]
115
- theta = torch.atan2(p, r) # [B,m,d]
116
- log_lam = torch.log(lam + self.eps) # [B,m,d]
119
+ lam = torch.sqrt(r * r + p * p + self.eps) # [B,m,d]
120
+ theta = torch.atan2(p, r) # [B,m,d]
121
+ log_lam = torch.log(lam + self.eps) # [B,m,d]
117
122
 
118
123
  # Generalized Multi-order Transformation
119
124
  # psi_k = sum_j alpha_{k,j} * theta_j + delta_k
120
125
  # l_k = exp(sum_j alpha_{k,j} * log(lam_j) + delta'_k)
121
- psi = torch.einsum("bmd,nmd->bnd", theta, self.alpha) + self.delta_phase # [B,n,d]
122
- log_l = torch.einsum("bmd,nmd->bnd", log_lam, self.alpha) + self.delta_logmod # [B,n,d]
126
+ psi = (
127
+ torch.einsum("bmd,nmd->bnd", theta, self.alpha) + self.delta_phase
128
+ ) # [B,n,d]
129
+ log_l = (
130
+ torch.einsum("bmd,nmd->bnd", log_lam, self.alpha) + self.delta_logmod
131
+ ) # [B,n,d]
123
132
  l = torch.exp(log_l) # [B,n,d]
124
133
 
125
134
  # Inverse Euler Transformation
@@ -153,7 +162,7 @@ class EulerInteractionLayerPaper(nn.Module):
153
162
  return r_out, p_out
154
163
 
155
164
 
156
- class ComplexSpaceMappingPaper(nn.Module):
165
+ class ComplexSpaceMapping(nn.Module):
157
166
  """
158
167
  Map real embeddings e_j to complex features via Euler's formula (Eq.6-7).
159
168
  For each field j:
@@ -174,63 +183,6 @@ class ComplexSpaceMappingPaper(nn.Module):
174
183
  r = mu * torch.cos(e)
175
184
  p = mu * torch.sin(e)
176
185
  return r, p
177
-
178
- class EulerNetPaper(nn.Module):
179
- """
180
- Paper-aligned EulerNet core (embedding -> mapping -> L Euler layers -> linear regression).
181
- """
182
-
183
- def __init__(
184
- self,
185
- *,
186
- embedding_dim: int,
187
- num_fields: int,
188
- num_layers: int = 2,
189
- num_orders: int = 8, # n in paper
190
- use_implicit: bool = True,
191
- norm: str | None = "ln", # None | "bn" | "ln"
192
- ):
193
- super().__init__()
194
- self.d = embedding_dim
195
- self.m = num_fields
196
- self.L = num_layers
197
- self.n = num_orders
198
-
199
- self.mapping = ComplexSpaceMappingPaper(embedding_dim, num_fields)
200
-
201
- self.layers = nn.ModuleList([
202
- EulerInteractionLayerPaper(
203
- embedding_dim=embedding_dim,
204
- num_fields=(num_fields if i == 0 else num_orders), # stack: m -> n -> n ...
205
- num_orders=num_orders,
206
- use_implicit=use_implicit,
207
- norm=norm,
208
- )
209
- for i in range(num_layers)
210
- ])
211
-
212
- # Output regression (Eq.16-17)
213
- # After last layer: r,p are [B,n,d]. Concatenate to [B, n*d] each, then regress.
214
- self.w = nn.Linear(self.n * self.d, 1, bias=False) # for real
215
- self.w_im = nn.Linear(self.n * self.d, 1, bias=False) # for imag
216
-
217
- def forward(self, field_emb: torch.Tensor) -> torch.Tensor:
218
- """
219
- field_emb: [B, m, d] real embeddings e_j
220
- return: logits, shape [B,1]
221
- """
222
- r, p = self.mapping(field_emb) # [B,m,d]
223
-
224
- # stack Euler interaction layers
225
- for layer in self.layers:
226
- r, p = layer(r, p) # -> [B,n,d]
227
-
228
- r_flat = r.reshape(r.size(0), self.n * self.d)
229
- p_flat = p.reshape(p.size(0), self.n * self.d)
230
-
231
- z_re = self.w(r_flat)
232
- z_im = self.w_im(p_flat)
233
- return z_re + z_im # Eq.17 logits
234
186
 
235
187
 
236
188
  class EulerNet(BaseModel):
@@ -313,14 +265,23 @@ class EulerNet(BaseModel):
313
265
  "All interaction features must share the same embedding_dim in EulerNet."
314
266
  )
315
267
 
316
- self.euler = EulerNetPaper(
317
- embedding_dim=self.embedding_dim,
318
- num_fields=self.num_fields,
319
- num_layers=num_layers,
320
- num_orders=num_orders,
321
- use_implicit=use_implicit,
322
- norm=norm,
268
+ self.num_layers = num_layers
269
+ self.num_orders = num_orders
270
+ self.mapping = ComplexSpaceMapping(self.embedding_dim, self.num_fields)
271
+ self.layers = nn.ModuleList(
272
+ [
273
+ EulerInteractionLayer(
274
+ embedding_dim=self.embedding_dim,
275
+ num_fields=(self.num_fields if i == 0 else self.num_orders),
276
+ num_orders=self.num_orders,
277
+ use_implicit=use_implicit,
278
+ norm=norm,
279
+ )
280
+ for i in range(self.num_layers)
281
+ ]
323
282
  )
283
+ self.w = nn.Linear(self.num_orders * self.embedding_dim, 1, bias=False)
284
+ self.w_im = nn.Linear(self.num_orders * self.embedding_dim, 1, bias=False)
324
285
 
325
286
  if self.use_linear:
326
287
  if len(self.linear_features) == 0:
@@ -336,7 +297,7 @@ class EulerNet(BaseModel):
336
297
 
337
298
  self.prediction_layer = PredictionLayer(task_type=self.task)
338
299
 
339
- modules = ["euler"]
300
+ modules = ["mapping", "layers", "w", "w_im"]
340
301
  if self.use_linear:
341
302
  modules.append("linear")
342
303
  self.register_regularization_weights(
@@ -354,7 +315,7 @@ class EulerNet(BaseModel):
354
315
  field_emb = self.embedding(
355
316
  x=x, features=self.interaction_features, squeeze_dim=False
356
317
  )
357
- y_euler = self.euler(field_emb)
318
+ y_euler = self.euler_forward(field_emb)
358
319
 
359
320
  if self.use_linear and self.linear is not None:
360
321
  linear_input = self.embedding(
@@ -363,3 +324,11 @@ class EulerNet(BaseModel):
363
324
  y_euler = y_euler + self.linear(linear_input)
364
325
 
365
326
  return self.prediction_layer(y_euler)
327
+
328
+ def euler_forward(self, field_emb: torch.Tensor) -> torch.Tensor:
329
+ r, p = self.mapping(field_emb)
330
+ for layer in self.layers:
331
+ r, p = layer(r, p)
332
+ r_flat = r.reshape(r.size(0), self.num_orders * self.embedding_dim)
333
+ p_flat = p.reshape(p.size(0), self.num_orders * self.embedding_dim)
334
+ return self.w(r_flat) + self.w_im(p_flat)
@@ -0,0 +1,275 @@
1
+ """
2
+ Date: create on 19/12/2025
3
+ Checkpoint: edit on 19/12/2025
4
+ Author: Yang Zhou, zyaztec@gmail.com
5
+ Reference:
6
+ [1] Juan Y, Zhuang Y, Chin W-S, et al. Field-aware Factorization Machines for CTR
7
+ Prediction[C]//RecSys. 2016: 43-50.
8
+
9
+ Field-aware Factorization Machines (FFM) extend FM by learning a distinct
10
+ embedding of each feature for every target field. For a pair of fields (i, j),
11
+ FFM uses v_{i,f_j} · v_{j,f_i} instead of a shared embedding, enabling richer
12
+ context-aware interactions and stronger CTR performance on sparse categorical
13
+ data.
14
+
15
+ Pipeline:
16
+ (1) Build field-aware embeddings v_{i,f} for each feature i toward every field f
17
+ (2) Compute first-order linear terms for sparse/sequence (and optional dense) fields
18
+ (3) For each field pair (i, j), compute v_{i,f_j} · v_{j,f_i}
19
+ (4) Sum linear + field-aware interaction logits and output prediction
20
+
21
+ Key Advantages:
22
+ - Field-aware embeddings capture asymmetric interactions between fields
23
+ - Improves CTR accuracy on sparse categorical features
24
+ - Retains interpretable second-order structure
25
+
26
+ FFM 在 FM 基础上引入字段感知机制:每个特征在不同目标字段下拥有不同的 embedding。
27
+ 对于字段对 (i, j),模型使用 v_{i,f_j} 与 v_{j,f_i} 的内积,从而更细粒度地建模
28
+ 跨字段交互,在稀疏高维 CTR 场景中表现更优。
29
+
30
+ 处理流程:
31
+ (1) 为每个特征 i 构造面向每个字段 f 的嵌入 v_{i,f}
32
+ (2) 计算一阶线性项(稀疏/序列特征,及可选的稠密特征)
33
+ (3) 对每一对字段 (i, j) 计算 v_{i,f_j} · v_{j,f_i}
34
+ (4) 将线性项与交互项相加得到最终预测
35
+
36
+ 主要优点:
37
+ - 字段感知嵌入可捕捉非对称交互
38
+ - 稀疏类别特征下预测更准确
39
+ - 保持二阶结构的可解释性
40
+ """
41
+
42
+ import torch
43
+ import torch.nn as nn
44
+
45
+ from nextrec.basic.features import DenseFeature, SequenceFeature, SparseFeature
46
+ from nextrec.basic.layers import AveragePooling, InputMask, PredictionLayer, SumPooling
47
+ from nextrec.basic.model import BaseModel
48
+ from nextrec.utils.torch_utils import get_initializer
49
+
50
+
51
+ class FFM(BaseModel):
52
+ @property
53
+ def model_name(self):
54
+ return "FFM"
55
+
56
+ @property
57
+ def default_task(self):
58
+ return "binary"
59
+
60
+ def __init__(
61
+ self,
62
+ dense_features: list[DenseFeature] | None = None,
63
+ sparse_features: list[SparseFeature] | None = None,
64
+ sequence_features: list[SequenceFeature] | None = None,
65
+ target: list[str] | str | None = None,
66
+ task: str | list[str] | None = None,
67
+ optimizer: str = "adam",
68
+ optimizer_params: dict | None = None,
69
+ loss: str | nn.Module | None = "bce",
70
+ loss_params: dict | list[dict] | None = None,
71
+ device: str = "cpu",
72
+ embedding_l1_reg=1e-6,
73
+ dense_l1_reg=1e-5,
74
+ embedding_l2_reg=1e-5,
75
+ dense_l2_reg=1e-4,
76
+ **kwargs,
77
+ ):
78
+ dense_features = dense_features or []
79
+ sparse_features = sparse_features or []
80
+ sequence_features = sequence_features or []
81
+ optimizer_params = optimizer_params or {}
82
+ if loss is None:
83
+ loss = "bce"
84
+
85
+ super(FFM, self).__init__(
86
+ dense_features=dense_features,
87
+ sparse_features=sparse_features,
88
+ sequence_features=sequence_features,
89
+ target=target,
90
+ task=task or self.default_task,
91
+ device=device,
92
+ embedding_l1_reg=embedding_l1_reg,
93
+ dense_l1_reg=dense_l1_reg,
94
+ embedding_l2_reg=embedding_l2_reg,
95
+ dense_l2_reg=dense_l2_reg,
96
+ **kwargs,
97
+ )
98
+
99
+ self.fm_features = sparse_features + sequence_features
100
+ if len(self.fm_features) < 2:
101
+ raise ValueError(
102
+ "FFM requires at least two sparse/sequence features to build field-aware interactions."
103
+ )
104
+
105
+ self.embedding_dim = self.fm_features[0].embedding_dim
106
+ if any(f.embedding_dim != self.embedding_dim for f in self.fm_features):
107
+ raise ValueError(
108
+ "All FFM features must share the same embedding_dim for field-aware interactions."
109
+ )
110
+ for feature in self.fm_features:
111
+ if isinstance(feature, SequenceFeature) and feature.combiner == "concat":
112
+ raise ValueError(
113
+ "FFM does not support SequenceFeature with combiner='concat' because it breaks shared embedding_dim."
114
+ )
115
+
116
+ self.field_aware_embeddings = nn.ModuleDict()
117
+ for src_feature in self.fm_features:
118
+ for target_field in self.fm_features:
119
+ key = self.field_aware_key(src_feature, target_field)
120
+ if key in self.field_aware_embeddings:
121
+ continue
122
+ self.field_aware_embeddings[key] = self.build_embedding(src_feature)
123
+
124
+ # First-order terms for sparse/sequence features: one hot
125
+ self.first_order_embeddings = nn.ModuleDict()
126
+ for feature in self.fm_features:
127
+ if feature.embedding_name in self.first_order_embeddings:
128
+ continue
129
+ emb = nn.Embedding(
130
+ num_embeddings=feature.vocab_size,
131
+ embedding_dim=1,
132
+ padding_idx=feature.padding_idx,
133
+ )
134
+ self.first_order_embeddings[feature.embedding_name] = emb
135
+
136
+ # Optional dense linear term
137
+ self.dense_features = list(dense_features)
138
+ dense_input_dim = sum([f.input_dim for f in self.dense_features])
139
+ self.linear_dense = (
140
+ nn.Linear(dense_input_dim, 1, bias=True) if dense_input_dim > 0 else None
141
+ )
142
+
143
+ self.prediction_layer = PredictionLayer(task_type=self.task)
144
+ self.input_mask = InputMask()
145
+ self.mean_pool = AveragePooling()
146
+ self.sum_pool = SumPooling()
147
+
148
+ self.embedding_params.extend(
149
+ emb.weight for emb in self.field_aware_embeddings.values()
150
+ )
151
+ self.embedding_params.extend(
152
+ emb.weight for emb in self.first_order_embeddings.values()
153
+ )
154
+ self.register_regularization_weights(
155
+ embedding_attr="field_aware_embeddings", include_modules=["linear_dense"]
156
+ )
157
+
158
+ self.compile(
159
+ optimizer=optimizer,
160
+ optimizer_params=optimizer_params,
161
+ loss=loss,
162
+ loss_params=loss_params,
163
+ )
164
+
165
+ def field_aware_key(
166
+ self, src_feature: SparseFeature | SequenceFeature, target_field
167
+ ) -> str:
168
+ return f"{src_feature.embedding_name}__to__{target_field.name}"
169
+
170
+ def build_embedding(self, feature: SparseFeature | SequenceFeature) -> nn.Embedding:
171
+ if getattr(feature, "pretrained_weight", None) is not None:
172
+ weight = feature.pretrained_weight
173
+ if weight is None:
174
+ raise ValueError(
175
+ f"[FFM Error]: Pretrained weight for '{feature.embedding_name}' is None."
176
+ )
177
+ if weight.shape != (feature.vocab_size, feature.embedding_dim):
178
+ raise ValueError(
179
+ f"[FFM Error]: Pretrained weight for '{feature.embedding_name}' has shape {weight.shape}, expected ({feature.vocab_size}, {feature.embedding_dim})."
180
+ )
181
+ embedding = nn.Embedding.from_pretrained(
182
+ embeddings=weight,
183
+ freeze=feature.freeze_pretrained,
184
+ padding_idx=feature.padding_idx,
185
+ )
186
+ embedding.weight.requires_grad = (
187
+ feature.trainable and not feature.freeze_pretrained
188
+ )
189
+ else:
190
+ embedding = nn.Embedding(
191
+ num_embeddings=feature.vocab_size,
192
+ embedding_dim=feature.embedding_dim,
193
+ padding_idx=feature.padding_idx,
194
+ )
195
+ embedding.weight.requires_grad = feature.trainable
196
+ initialization = get_initializer(
197
+ init_type=feature.init_type,
198
+ activation="linear",
199
+ param=feature.init_params,
200
+ )
201
+ initialization(embedding.weight)
202
+ return embedding
203
+
204
+ def embed_for_field(
205
+ self,
206
+ feature: SparseFeature | SequenceFeature,
207
+ target_field,
208
+ x: dict[str, torch.Tensor],
209
+ ) -> torch.Tensor:
210
+ key = self.field_aware_key(feature, target_field)
211
+ emb = self.field_aware_embeddings[key]
212
+ if isinstance(feature, SparseFeature):
213
+ return emb(x[feature.name].long())
214
+
215
+ seq_input = x[feature.name].long()
216
+ if feature.max_len is not None and seq_input.size(1) > feature.max_len:
217
+ seq_input = seq_input[:, -feature.max_len :]
218
+ seq_emb = emb(seq_input) # [B, L, D]
219
+ mask = self.input_mask(x, feature, seq_input)
220
+ if feature.combiner == "mean":
221
+ return self.mean_pool(seq_emb, mask)
222
+ if feature.combiner == "sum":
223
+ return self.sum_pool(seq_emb, mask)
224
+ raise ValueError(
225
+ f"[FFM Error]: Unsupported combiner '{feature.combiner}' for sequence feature '{feature.name}'."
226
+ )
227
+
228
+ def forward(self, x):
229
+ batch_size = x[self.fm_features[0].name].size(0)
230
+ device = x[self.fm_features[0].name].device
231
+ y_linear = torch.zeros(batch_size, 1, device=device)
232
+
233
+ # First-order dense part
234
+ if self.linear_dense is not None:
235
+ dense_inputs = [
236
+ x[f.name].float().view(batch_size, -1) for f in self.dense_features
237
+ ]
238
+ dense_stack = torch.cat(dense_inputs, dim=1) if dense_inputs else None
239
+ if dense_stack is not None:
240
+ y_linear = y_linear + self.linear_dense(dense_stack)
241
+
242
+ # First-order sparse/sequence part
243
+ first_order_terms = []
244
+ for feature in self.fm_features:
245
+ emb = self.first_order_embeddings[feature.embedding_name]
246
+ if isinstance(feature, SparseFeature):
247
+ term = emb(x[feature.name].long()) # [B, 1]
248
+ else:
249
+ seq_input = x[feature.name].long()
250
+ if feature.max_len is not None and seq_input.size(1) > feature.max_len:
251
+ seq_input = seq_input[:, -feature.max_len :]
252
+ mask = self.input_mask(x, feature, seq_input).squeeze(1) # [B, L]
253
+ seq_weight = emb(seq_input).squeeze(-1) # [B, L]
254
+ term = (seq_weight * mask).sum(dim=1, keepdim=True) # [B, 1]
255
+ first_order_terms.append(term)
256
+ if first_order_terms:
257
+ y_linear = y_linear + torch.sum(
258
+ torch.stack(first_order_terms, dim=1), dim=1
259
+ )
260
+
261
+ # Field-aware interactions
262
+ y_interaction = torch.zeros(batch_size, 1, device=device)
263
+ num_fields = len(self.fm_features)
264
+ for i in range(num_fields - 1):
265
+ feature_i = self.fm_features[i]
266
+ for j in range(i + 1, num_fields):
267
+ feature_j = self.fm_features[j]
268
+ v_i_fj = self.embed_for_field(feature_i, feature_j, x)
269
+ v_j_fi = self.embed_for_field(feature_j, feature_i, x)
270
+ y_interaction = y_interaction + torch.sum(
271
+ v_i_fj * v_j_fi, dim=1, keepdim=True
272
+ )
273
+
274
+ y = y_linear + y_interaction
275
+ return self.prediction_layer(y)
@@ -113,8 +113,6 @@ class LR(BaseModel):
113
113
  )
114
114
 
115
115
  def forward(self, x):
116
- input_linear = self.embedding(
117
- x=x, features=self.all_features, squeeze_dim=True
118
- )
116
+ input_linear = self.embedding(x=x, features=self.all_features, squeeze_dim=True)
119
117
  y = self.linear(input_linear)
120
118
  return self.prediction_layer(y)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
nextrec/utils/__init__.py CHANGED
@@ -36,7 +36,7 @@ from .data import (
36
36
  )
37
37
  from .embedding import get_auto_embedding_dim
38
38
  from .feature import normalize_to_list
39
- from .model import get_mlp_output_dim, merge_features
39
+ from .model import compute_pair_scores, get_mlp_output_dim, merge_features
40
40
  from .torch_utils import (
41
41
  add_distributed_sampler,
42
42
  concat_tensors,
@@ -88,6 +88,7 @@ __all__ = [
88
88
  # Model utilities
89
89
  "merge_features",
90
90
  "get_mlp_output_dim",
91
+ "compute_pair_scores",
91
92
  # Feature utilities
92
93
  "normalize_to_list",
93
94
  # Config utilities
nextrec/utils/console.py CHANGED
@@ -4,7 +4,7 @@ Console and CLI utilities for NextRec.
4
4
  This module centralizes CLI logging helpers, progress display, and metric tables.
5
5
 
6
6
  Date: create on 19/12/2025
7
- Checkpoint: edit on 19/12/2025
7
+ Checkpoint: edit on 20/12/2025
8
8
  Author: Yang Zhou, zyaztec@gmail.com
9
9
  """
10
10
 
@@ -242,6 +242,14 @@ def display_metrics_table(
242
242
  normalized_order.append(name)
243
243
  task_order = normalized_order
244
244
 
245
+ if not task_order and not grouped and not metrics:
246
+ if isinstance(loss, numbers.Number):
247
+ msg = f"Epoch {epoch}/{epochs} - {split} (loss={float(loss):.4f})"
248
+ if colorize is not None:
249
+ msg = colorize(msg)
250
+ logging.info(msg)
251
+ return
252
+
245
253
  if Console is None or Table is None or box is None:
246
254
  prefix = f"Epoch {epoch}/{epochs} - {split}:"
247
255
  segments: list[str] = []
nextrec/utils/model.py CHANGED
@@ -7,6 +7,8 @@ Author: Yang Zhou, zyaztec@gmail.com
7
7
 
8
8
  from collections import OrderedDict
9
9
 
10
+ import torch
11
+
10
12
 
11
13
  def merge_features(primary, secondary) -> list:
12
14
  merged: OrderedDict[str, object] = OrderedDict()
@@ -42,3 +44,15 @@ def select_features(
42
44
  )
43
45
 
44
46
  return [feature_map[name] for name in names]
47
+
48
+
49
+ def compute_pair_scores(model, data, batch_size: int = 512):
50
+ user_emb = model.encode_user(data, batch_size=batch_size)
51
+ item_emb = model.encode_item(data, batch_size=batch_size)
52
+ with torch.no_grad():
53
+ user_tensor = torch.as_tensor(user_emb, device=model.device)
54
+ item_tensor = torch.as_tensor(item_emb, device=model.device)
55
+ scores = model.compute_similarity(user_tensor, item_tensor)
56
+ if model.training_mode == "pointwise":
57
+ scores = torch.sigmoid(scores)
58
+ return scores.detach().cpu().numpy()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nextrec
3
- Version: 0.4.10
3
+ Version: 0.4.12
4
4
  Summary: A comprehensive recommendation library with match, ranking, and multi-task learning models
5
5
  Project-URL: Homepage, https://github.com/zerolovesea/NextRec
6
6
  Project-URL: Repository, https://github.com/zerolovesea/NextRec
@@ -66,7 +66,7 @@ Description-Content-Type: text/markdown
66
66
  ![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)
67
67
  ![PyTorch](https://img.shields.io/badge/PyTorch-1.10+-ee4c2c.svg)
68
68
  ![License](https://img.shields.io/badge/License-Apache%202.0-green.svg)
69
- ![Version](https://img.shields.io/badge/Version-0.4.10-orange.svg)
69
+ ![Version](https://img.shields.io/badge/Version-0.4.12-orange.svg)
70
70
 
71
71
  中文文档 | [English Version](README_en.md)
72
72
 
@@ -99,7 +99,7 @@ NextRec是一个基于PyTorch的现代推荐系统框架,旨在为研究工程
99
99
 
100
100
  ## NextRec近期进展
101
101
 
102
- - **12/12/2025** 在v0.4.10中加入了[RQ-VAE](/nextrec/models/representation/rqvae.py)模块。配套的[数据集](/dataset/ecommerce_task.csv)和[代码](tutorials/notebooks/zh/使用RQ-VAE构建语义ID.ipynb)已经同步在仓库中
102
+ - **12/12/2025** 在v0.4.12中加入了[RQ-VAE](/nextrec/models/representation/rqvae.py)模块。配套的[数据集](/dataset/ecommerce_task.csv)和[代码](tutorials/notebooks/zh/使用RQ-VAE构建语义ID.ipynb)已经同步在仓库中
103
103
  - **07/12/2025** 发布了NextRec CLI命令行工具,它允许用户根据配置文件进行一键训练和推理,我们提供了相关的[教程](/nextrec_cli_preset/NextRec-CLI_zh.md)和[教学代码](/nextrec_cli_preset)
104
104
  - **03/12/2025** NextRec获得了100颗🌟!感谢大家的支持
105
105
  - **06/12/2025** 在v0.4.1中支持了单机多卡的分布式DDP训练,并且提供了配套的[代码](tutorials/distributed)
@@ -240,11 +240,11 @@ nextrec --mode=train --train_config=path/to/train_config.yaml
240
240
  nextrec --mode=predict --predict_config=path/to/predict_config.yaml
241
241
  ```
242
242
 
243
- > 截止当前版本0.4.10,NextRec CLI支持单机训练,分布式训练相关功能尚在开发中。
243
+ > 截止当前版本0.4.12,NextRec CLI支持单机训练,分布式训练相关功能尚在开发中。
244
244
 
245
245
  ## 兼容平台
246
246
 
247
- 当前最新版本为0.4.10,所有模型和测试代码均已在以下平台通过验证,如果开发者在使用中遇到兼容问题,请在issue区提出错误报告及系统版本:
247
+ 当前最新版本为0.4.12,所有模型和测试代码均已在以下平台通过验证,如果开发者在使用中遇到兼容问题,请在issue区提出错误报告及系统版本:
248
248
 
249
249
  | 平台 | 配置 |
250
250
  |------|------|
@@ -260,7 +260,9 @@ nextrec --mode=predict --predict_config=path/to/predict_config.yaml
260
260
  | 模型 | 论文 | 年份 | 状态 |
261
261
  |------|------|------|------|
262
262
  | [FM](nextrec/models/ranking/fm.py) | Factorization Machines | ICDM 2010 | 已支持 |
263
+ | [LR](nextrec/models/ranking/lr.py) | Logistic Regression | - | 已支持 |
263
264
  | [AFM](nextrec/models/ranking/afm.py) | Attentional Factorization Machines: Learning the Weight of Feature Interactions via Attention Networks | IJCAI 2017 | 已支持 |
265
+ | [FFM](nextrec/models/ranking/ffm.py) | Field-aware Factorization Machines | RecSys 2016 | 已支持 |
264
266
  | [DeepFM](nextrec/models/ranking/deepfm.py) | DeepFM: A Factorization-Machine based Neural Network for CTR Prediction | IJCAI 2017 | 已支持 |
265
267
  | [Wide&Deep](nextrec/models/ranking/widedeep.py) | Wide & Deep Learning for Recommender Systems | DLRS 2016 | 已支持 |
266
268
  | [xDeepFM](nextrec/models/ranking/xdeepfm.py) | xDeepFM: Combining Explicit and Implicit Feature Interactions | KDD 2018 | 已支持 |
@@ -272,16 +274,24 @@ nextrec --mode=predict --predict_config=path/to/predict_config.yaml
272
274
  | [DIN](nextrec/models/ranking/din.py) | Deep Interest Network for Click-Through Rate Prediction | KDD 2018 | 已支持 |
273
275
  | [DIEN](nextrec/models/ranking/dien.py) | Deep Interest Evolution Network for Click-Through Rate Prediction | AAAI 2019 | 已支持 |
274
276
  | [MaskNet](nextrec/models/ranking/masknet.py) | MaskNet: Introducing Feature-wise Gating Blocks for High-dimensional Sparse Recommendation Data | 2020 | 已支持 |
277
+ | [EulerNet](nextrec/models/ranking/eulernet.py) | EulerNet: Efficient and Effective Feature Interaction Modeling with Euler's Formula | SIGIR 2021 | 已支持 |
275
278
 
276
279
  ### 召回模型
277
280
 
278
281
  | 模型 | 论文 | 年份 | 状态 |
279
282
  |------|------|------|------|
280
- | [DSSM](nextrec/models/match/dssm.py) | Learning Deep Structured Semantic Models | CIKM 2013 | 已支持 |
281
- | [DSSM v2](nextrec/models/match/dssm_v2.py) | DSSM with pairwise BPR-style optimization | - | 已支持 |
282
- | [YouTube DNN](nextrec/models/match/youtube_dnn.py) | Deep Neural Networks for YouTube Recommendations | RecSys 2016 | 已支持 |
283
- | [MIND](nextrec/models/match/mind.py) | Multi-Interest Network with Dynamic Routing | CIKM 2019 | 已支持 |
284
- | [SDM](nextrec/models/match/sdm.py) | Sequential Deep Matching Model | - | 已支持 |
283
+ | [DSSM](nextrec/models/retrieval/dssm.py) | Learning Deep Structured Semantic Models | CIKM 2013 | 已支持 |
284
+ | [DSSM v2](nextrec/models/retrieval/dssm_v2.py) | DSSM with pairwise BPR-style optimization | - | 已支持 |
285
+ | [YouTube DNN](nextrec/models/retrieval/youtube_dnn.py) | Deep Neural Networks for YouTube Recommendations | RecSys 2016 | 已支持 |
286
+ | [MIND](nextrec/models/retrieval/mind.py) | Multi-Interest Network with Dynamic Routing | CIKM 2019 | 已支持 |
287
+ | [SDM](nextrec/models/retrieval/sdm.py) | Sequential Deep Matching Model | - | 已支持 |
288
+
289
+ ### 序列推荐模型
290
+
291
+ | 模型 | 论文 | 年份 | 状态 |
292
+ |------|------|------|------|
293
+ | [SASRec](nextrec/models/sequential/sasrec.py) | Self-Attentive Sequential Recommendation | KDD 2018 | 开发中 |
294
+ | [HSTU](nextrec/models/sequential/hstu.py) | Actions speak louder than words: Trillion-parameter sequential transducers for generative recommendations | arXiv 2024 | 已支持 |
285
295
 
286
296
  ### 多任务模型
287
297
 
@@ -298,7 +308,18 @@ nextrec --mode=predict --predict_config=path/to/predict_config.yaml
298
308
  | 模型 | 论文 | 年份 | 状态 |
299
309
  |------|------|------|------|
300
310
  | [TIGER](nextrec/models/generative/tiger.py) | Recommender Systems with Generative Retrieval | NeurIPS 2023 | 开发中 |
301
- | [HSTU](nextrec/models/generative/hstu.py) | Hierarchical Sequential Transduction Units | - | 已支持 |
311
+
312
+ ### 表征模型
313
+
314
+ | 模型 | 论文 | 年份 | 状态 |
315
+ |------|------|------|------|
316
+ | [RQ-VAE](nextrec/models/representation/rqvae.py) | RQ-VAE: RQVAE for Generative Retrieval | - | 已支持 |
317
+ | [BPR](nextrec/models/representation/bpr.py) | Bayesian Personalized Ranking | UAI 2009 | 开发中 |
318
+ | [MF](nextrec/models/representation/mf.py) | Matrix Factorization Techniques for Recommender Systems | - | 开发中 |
319
+ | [AutoRec](nextrec/models/representation/autorec.py) | AutoRec: Autoencoders Meet Collaborative Filtering | WWW 2015 | 开发中 |
320
+ | [LightGCN](nextrec/models/representation/lightgcn.py) | LightGCN: Simplifying and Powering Graph Convolution Network for Recommendation | SIGIR 2020 | 开发中 |
321
+ | [S3Rec](nextrec/models/representation/s3rec.py) | S3-Rec: Self-Supervised Learning for Sequential Recommendation | CIKM 2020 | 开发中 |
322
+ | [CL4SRec](nextrec/models/representation/cl4srec.py) | CL4SRec: Contrastive Learning for Sequential Recommendation | 2021 | 开发中 |
302
323
 
303
324
  ---
304
325