replay-rec 0.20.3__py3-none-any.whl → 0.20.3rc0__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 (61) hide show
  1. replay/__init__.py +1 -1
  2. replay/experimental/__init__.py +0 -0
  3. replay/experimental/metrics/__init__.py +62 -0
  4. replay/experimental/metrics/base_metric.py +603 -0
  5. replay/experimental/metrics/coverage.py +97 -0
  6. replay/experimental/metrics/experiment.py +175 -0
  7. replay/experimental/metrics/hitrate.py +26 -0
  8. replay/experimental/metrics/map.py +30 -0
  9. replay/experimental/metrics/mrr.py +18 -0
  10. replay/experimental/metrics/ncis_precision.py +31 -0
  11. replay/experimental/metrics/ndcg.py +49 -0
  12. replay/experimental/metrics/precision.py +22 -0
  13. replay/experimental/metrics/recall.py +25 -0
  14. replay/experimental/metrics/rocauc.py +49 -0
  15. replay/experimental/metrics/surprisal.py +90 -0
  16. replay/experimental/metrics/unexpectedness.py +76 -0
  17. replay/experimental/models/__init__.py +50 -0
  18. replay/experimental/models/admm_slim.py +257 -0
  19. replay/experimental/models/base_neighbour_rec.py +200 -0
  20. replay/experimental/models/base_rec.py +1386 -0
  21. replay/experimental/models/base_torch_rec.py +234 -0
  22. replay/experimental/models/cql.py +454 -0
  23. replay/experimental/models/ddpg.py +932 -0
  24. replay/experimental/models/dt4rec/__init__.py +0 -0
  25. replay/experimental/models/dt4rec/dt4rec.py +189 -0
  26. replay/experimental/models/dt4rec/gpt1.py +401 -0
  27. replay/experimental/models/dt4rec/trainer.py +127 -0
  28. replay/experimental/models/dt4rec/utils.py +264 -0
  29. replay/experimental/models/extensions/spark_custom_models/__init__.py +0 -0
  30. replay/experimental/models/extensions/spark_custom_models/als_extension.py +792 -0
  31. replay/experimental/models/hierarchical_recommender.py +331 -0
  32. replay/experimental/models/implicit_wrap.py +131 -0
  33. replay/experimental/models/lightfm_wrap.py +303 -0
  34. replay/experimental/models/mult_vae.py +332 -0
  35. replay/experimental/models/neural_ts.py +986 -0
  36. replay/experimental/models/neuromf.py +406 -0
  37. replay/experimental/models/scala_als.py +293 -0
  38. replay/experimental/models/u_lin_ucb.py +115 -0
  39. replay/experimental/nn/data/__init__.py +1 -0
  40. replay/experimental/nn/data/schema_builder.py +102 -0
  41. replay/experimental/preprocessing/__init__.py +3 -0
  42. replay/experimental/preprocessing/data_preparator.py +839 -0
  43. replay/experimental/preprocessing/padder.py +229 -0
  44. replay/experimental/preprocessing/sequence_generator.py +208 -0
  45. replay/experimental/scenarios/__init__.py +1 -0
  46. replay/experimental/scenarios/obp_wrapper/__init__.py +8 -0
  47. replay/experimental/scenarios/obp_wrapper/obp_optuna_objective.py +74 -0
  48. replay/experimental/scenarios/obp_wrapper/replay_offline.py +261 -0
  49. replay/experimental/scenarios/obp_wrapper/utils.py +85 -0
  50. replay/experimental/scenarios/two_stages/__init__.py +0 -0
  51. replay/experimental/scenarios/two_stages/reranker.py +117 -0
  52. replay/experimental/scenarios/two_stages/two_stages_scenario.py +757 -0
  53. replay/experimental/utils/__init__.py +0 -0
  54. replay/experimental/utils/logger.py +24 -0
  55. replay/experimental/utils/model_handler.py +186 -0
  56. replay/experimental/utils/session_handler.py +44 -0
  57. {replay_rec-0.20.3.dist-info → replay_rec-0.20.3rc0.dist-info}/METADATA +11 -17
  58. {replay_rec-0.20.3.dist-info → replay_rec-0.20.3rc0.dist-info}/RECORD +61 -6
  59. {replay_rec-0.20.3.dist-info → replay_rec-0.20.3rc0.dist-info}/WHEEL +0 -0
  60. {replay_rec-0.20.3.dist-info → replay_rec-0.20.3rc0.dist-info}/licenses/LICENSE +0 -0
  61. {replay_rec-0.20.3.dist-info → replay_rec-0.20.3rc0.dist-info}/licenses/NOTICE +0 -0
@@ -0,0 +1,127 @@
1
+ import logging
2
+
3
+ import pandas as pd
4
+ from tqdm import tqdm
5
+
6
+ from replay.utils import TORCH_AVAILABLE
7
+
8
+ from .utils import matrix2df
9
+
10
+ if TORCH_AVAILABLE:
11
+ import torch
12
+ from torch.nn import functional as func
13
+
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class TrainerConfig:
19
+ """
20
+ Config holder for trainer
21
+ """
22
+
23
+ epochs = 1
24
+ lr_scheduler = None
25
+
26
+ def __init__(self, **kwargs):
27
+ for key, value in kwargs.items():
28
+ setattr(self, key, value)
29
+
30
+ def update(self, **kwargs):
31
+ """
32
+ Arguments setter
33
+ """
34
+ for key, value in kwargs.items():
35
+ setattr(self, key, value)
36
+
37
+
38
+ class Trainer:
39
+ """
40
+ Trainer for DT4Rec
41
+ """
42
+
43
+ grad_norm_clip = 1.0
44
+
45
+ def __init__(
46
+ self,
47
+ model,
48
+ train_dataloader,
49
+ tconf,
50
+ val_dataloader=None,
51
+ experiment=None,
52
+ use_cuda=True,
53
+ ):
54
+ self.model = model
55
+ self.train_dataloader = train_dataloader
56
+ self.optimizer = tconf.optimizer
57
+ self.epochs = tconf.epochs
58
+ self.lr_scheduler = tconf.lr_scheduler
59
+ assert (val_dataloader is None) == (experiment is None)
60
+ self.val_dataloader = val_dataloader
61
+ self.experiment = experiment
62
+
63
+ # take over whatever gpus are on the system
64
+ self.device = "cpu"
65
+ if use_cuda and torch.cuda.is_available():
66
+ self.device = torch.cuda.current_device()
67
+ self.model = torch.nn.DataParallel(self.model).to(self.device)
68
+
69
+ def _move_batch(self, batch):
70
+ return [elem.to(self.device) for elem in batch]
71
+
72
+ def _train_epoch(self, epoch):
73
+ self.model.train()
74
+
75
+ losses = []
76
+ pbar = tqdm(
77
+ enumerate(self.train_dataloader),
78
+ total=len(self.train_dataloader),
79
+ )
80
+
81
+ for iter_, batch in pbar:
82
+ # place data on the correct device
83
+ states, actions, rtgs, timesteps, users = self._move_batch(batch)
84
+ targets = actions
85
+
86
+ # forward the model
87
+ logits = self.model(states, actions, rtgs, timesteps, users)
88
+
89
+ loss = func.cross_entropy(logits.reshape(-1, logits.size(-1)), targets.reshape(-1)).mean()
90
+ losses.append(loss.item())
91
+
92
+ # backprop and update the parametersx
93
+ self.model.zero_grad()
94
+ loss.backward()
95
+ torch.nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_norm_clip)
96
+ self.optimizer.step()
97
+ if self.lr_scheduler is not None:
98
+ self.lr_scheduler.step()
99
+
100
+ # report progress
101
+ if self.lr_scheduler is not None:
102
+ current_lr = self.lr_scheduler.get_lr()
103
+ else:
104
+ current_lr = self.optimizer.param_groups[-1]["lr"]
105
+ pbar.set_description(f"epoch {epoch+1} iter {iter_}: train loss {loss.item():.5f}, lr {current_lr}")
106
+
107
+ def _evaluation_epoch(self, epoch):
108
+ self.model.eval()
109
+ ans_df = pd.DataFrame(columns=["user_idx", "item_idx", "relevance"])
110
+ val_items = self.val_dataloader.dataset.val_items
111
+ with torch.no_grad():
112
+ for batch in tqdm(self.val_dataloader):
113
+ states, actions, rtgs, timesteps, users = self._move_batch(batch)
114
+ logits = self.model(states, actions, rtgs, timesteps, users)
115
+ items_relevances = logits[:, -1, :][:, val_items]
116
+ ans_df = ans_df.append(matrix2df(items_relevances, users.squeeze(), val_items))
117
+ self.experiment.add_result(f"epoch: {epoch}", ans_df)
118
+ self.experiment.results.to_csv("results.csv")
119
+
120
+ def train(self):
121
+ """
122
+ Run training loop
123
+ """
124
+ for epoch in range(self.epochs):
125
+ self._train_epoch(epoch)
126
+ if self.experiment is not None:
127
+ self._evaluation_epoch(epoch)
@@ -0,0 +1,264 @@
1
+ import bisect
2
+ import random
3
+ from typing import Union
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from tqdm import tqdm
8
+
9
+ from replay.utils import TORCH_AVAILABLE
10
+
11
+ if TORCH_AVAILABLE:
12
+ import torch
13
+ from torch.optim import Optimizer
14
+ from torch.optim.lr_scheduler import _LRScheduler
15
+ from torch.utils.data import Dataset
16
+
17
+
18
+ def set_seed(seed):
19
+ """
20
+ Set random seed in all dependicies
21
+ """
22
+ random.seed(seed)
23
+ np.random.seed(seed)
24
+ torch.manual_seed(seed)
25
+ torch.cuda.manual_seed_all(seed)
26
+
27
+
28
+ class StateActionReturnDataset(Dataset):
29
+ """
30
+ Create Dataset from user trajectories
31
+ """
32
+
33
+ def __init__(self, user_trajectory, trajectory_len):
34
+ self.user_trajectory = user_trajectory
35
+ self.trajectory_len = trajectory_len
36
+
37
+ self.len = 0
38
+ self.prefix_lens = [0]
39
+ for trajectory in self.user_trajectory:
40
+ self.len += max(1, len(trajectory["actions"]) - 30 + 1)
41
+ self.prefix_lens.append(self.len)
42
+
43
+ def __len__(self):
44
+ return self.len
45
+
46
+ def __getitem__(self, idx):
47
+ user_num = bisect.bisect_right(self.prefix_lens, idx) - 1
48
+ start = idx - self.prefix_lens[user_num]
49
+
50
+ user = self.user_trajectory[user_num]
51
+ end = min(len(user["actions"]), start + self.trajectory_len)
52
+ states = torch.tensor(np.array(user["states"][start:end]), dtype=torch.float32)
53
+ actions = torch.tensor(user["actions"][start:end], dtype=torch.long)
54
+ rtgs = torch.tensor(user["rtgs"][start:end], dtype=torch.float32)
55
+ # strange logic but work
56
+ timesteps = start
57
+
58
+ return states, actions, rtgs, timesteps, user_num
59
+
60
+
61
+ class ValidateDataset(Dataset):
62
+ """
63
+ Dataset for Validation
64
+ """
65
+
66
+ def __init__(self, user_trajectory, max_context_len, val_users, val_items):
67
+ self.user_trajectory = user_trajectory
68
+ self.max_context_len = max_context_len
69
+ self.val_users = val_users
70
+ self.val_items = val_items
71
+
72
+ def __len__(self):
73
+ return len(self.val_users)
74
+
75
+ def __getitem__(self, idx):
76
+ user_idx = self.val_users[idx]
77
+ user = self.user_trajectory[user_idx]
78
+ if len(user["actions"]) <= self.max_context_len:
79
+ start = 0
80
+ end = -1
81
+ else:
82
+ end = -1
83
+ start = end - self.max_context_len
84
+
85
+ states = torch.tensor(
86
+ np.array(user["states"][start - (start < 0) : end]),
87
+ dtype=torch.float32,
88
+ )
89
+ actions = torch.tensor(user["actions"][start:end], dtype=torch.long)
90
+ rtgs = torch.zeros(end - start + 1 if start < 0 else len(user["actions"]))
91
+ rtgs[start:end] = torch.tensor(user["rtgs"][start:end], dtype=torch.float32)
92
+ rtgs[end] = 10
93
+ timesteps = len(user["actions"]) + start if start < 0 else 0
94
+
95
+ return states, actions, rtgs, timesteps, user_idx
96
+
97
+
98
+ def pad_sequence(
99
+ sequences: Union[torch.Tensor, list[torch.Tensor]],
100
+ batch_first: bool = False,
101
+ padding_value: float = 0.0,
102
+ pos: str = "right",
103
+ ) -> torch.Tensor:
104
+ """
105
+ Pad sequence
106
+ """
107
+ if pos == "right":
108
+ padded_sequence = torch.nn.utils.rnn.pad_sequence(sequences, batch_first, padding_value)
109
+ elif pos == "left":
110
+ sequences = tuple(s.flip(0) for s in sequences)
111
+ padded_sequence = torch.nn.utils.rnn.pad_sequence(sequences, batch_first, padding_value)
112
+ _seq_dim = padded_sequence.dim()
113
+ padded_sequence = padded_sequence.flip(-_seq_dim + batch_first)
114
+ else:
115
+ msg = f"pos should be either 'right' or 'left', but got {pos}"
116
+ raise ValueError(msg)
117
+ return padded_sequence
118
+
119
+
120
+ class Collator:
121
+ """
122
+ Callable class to merge several items to one batch
123
+ """
124
+
125
+ def __init__(self, item_pad):
126
+ self.item_pad = item_pad
127
+
128
+ def __call__(self, batch):
129
+ states, actions, rtgs, timesteps, users_num = zip(*batch)
130
+
131
+ return (
132
+ pad_sequence(
133
+ states,
134
+ batch_first=True,
135
+ padding_value=self.item_pad,
136
+ pos="left",
137
+ ),
138
+ pad_sequence(
139
+ actions,
140
+ batch_first=True,
141
+ padding_value=self.item_pad,
142
+ pos="left",
143
+ ).unsqueeze(-1),
144
+ pad_sequence(rtgs, batch_first=True, padding_value=0, pos="left").unsqueeze(-1),
145
+ torch.tensor(timesteps).unsqueeze(-1).unsqueeze(-1),
146
+ torch.tensor(users_num).unsqueeze(-1),
147
+ )
148
+
149
+
150
+ def matrix2df(matrix, users=None, items=None):
151
+ """
152
+ Creata DataFrame from matrix
153
+ """
154
+ users = np.arange(matrix.shape[0]) if users is None else np.array(users.cpu())
155
+ if items is None:
156
+ items = np.arange(matrix.shape[1])
157
+ x1 = np.repeat(users, len(items))
158
+ x2 = np.tile(items, len(users))
159
+ x3 = np.array(matrix.cpu()).flatten()
160
+
161
+ return pd.DataFrame(np.array([x1, x2, x3]).T, columns=["user_idx", "item_idx", "relevance"])
162
+
163
+
164
+ class WarmUpScheduler(_LRScheduler):
165
+ """
166
+ Implementation of WarmUp
167
+ """
168
+
169
+ def __init__(
170
+ self,
171
+ optimizer: Optimizer,
172
+ dim_embed: int,
173
+ warmup_steps: int,
174
+ last_epoch: int = -1,
175
+ ) -> None:
176
+ self.dim_embed = dim_embed
177
+ self.warmup_steps = warmup_steps
178
+ self.num_param_groups = len(optimizer.param_groups)
179
+
180
+ super().__init__(optimizer, last_epoch)
181
+
182
+ def get_lr(self) -> float:
183
+ lr = calc_lr(self._step_count, self.dim_embed, self.warmup_steps)
184
+ return [lr] * self.num_param_groups
185
+
186
+
187
+ def calc_lr(step, dim_embed, warmup_steps):
188
+ """
189
+ Learning rate calculation
190
+ """
191
+ return dim_embed ** (-0.5) * min(step ** (-0.5), step * warmup_steps ** (-1.5))
192
+
193
+
194
+ def create_dataset(
195
+ df, user_num, item_pad, time_col="timestamp", user_col="user_idx", item_col="item_idx", relevance_col="relevance"
196
+ ):
197
+ """
198
+ Create dataset from DataFrame
199
+ """
200
+ user_trajectory = [{} for _ in range(user_num)]
201
+ df = df.sort_values(by=time_col)
202
+ for user_idx in tqdm(range(user_num)):
203
+ user_trajectory[user_idx]["states"] = [[item_pad, item_pad, item_pad]]
204
+ user_trajectory[user_idx]["actions"] = []
205
+ user_trajectory[user_idx]["rewards"] = []
206
+
207
+ user = user_trajectory[user_idx]
208
+ user_df = df[df[user_col] == user_idx]
209
+ for _, row in user_df.iterrows():
210
+ action = row[item_col]
211
+ user["actions"].append(action)
212
+ if row[relevance_col] > 3:
213
+ user["rewards"].append(1)
214
+ user["states"].append([user["states"][-1][1], user["states"][-1][2], action])
215
+ else:
216
+ user["rewards"].append(0)
217
+ user["states"].append(user["states"][-1])
218
+
219
+ user["rtgs"] = np.cumsum(user["rewards"][::-1])[::-1]
220
+ for key in user:
221
+ user[key] = np.array(user[key])
222
+
223
+ return user_trajectory
224
+
225
+
226
+ # For debug
227
+ def fast_create_dataset(
228
+ df,
229
+ user_num,
230
+ item_pad,
231
+ time_field="timestamp",
232
+ user_field="user_idx",
233
+ item_field="item_idx",
234
+ relevance_field="relevance",
235
+ ):
236
+ """
237
+ Create dataset from DataFrame
238
+ """
239
+ user_trajectory = [{} for _ in range(user_num)]
240
+ df = df.sort_values(by=time_field)
241
+ for user_idx in tqdm(range(user_num)):
242
+ user_trajectory[user_idx]["states"] = [[item_pad, item_pad, item_pad]]
243
+ user_trajectory[user_idx]["actions"] = []
244
+ user_trajectory[user_idx]["rewards"] = []
245
+
246
+ user = user_trajectory[user_idx]
247
+ user_df = df[df[user_field] == user_idx]
248
+ for idx, (_, row) in enumerate(user_df.iterrows()):
249
+ if idx >= 35:
250
+ break
251
+ action = row[item_field]
252
+ user["actions"].append(action)
253
+ if row[relevance_field] > 3:
254
+ user["rewards"].append(1)
255
+ user["states"].append([user["states"][-1][1], user["states"][-1][2], action])
256
+ else:
257
+ user["rewards"].append(0)
258
+ user["states"].append(user["states"][-1])
259
+
260
+ user["rtgs"] = np.cumsum(user["rewards"][::-1])[::-1]
261
+ for key in user:
262
+ user[key] = np.array(user[key])
263
+
264
+ return user_trajectory