replay-rec 0.20.1rc0__py3-none-any.whl → 0.20.2__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.
- replay/__init__.py +1 -1
- {replay_rec-0.20.1rc0.dist-info → replay_rec-0.20.2.dist-info}/METADATA +18 -12
- {replay_rec-0.20.1rc0.dist-info → replay_rec-0.20.2.dist-info}/RECORD +6 -61
- replay/experimental/__init__.py +0 -0
- replay/experimental/metrics/__init__.py +0 -62
- replay/experimental/metrics/base_metric.py +0 -603
- replay/experimental/metrics/coverage.py +0 -97
- replay/experimental/metrics/experiment.py +0 -175
- replay/experimental/metrics/hitrate.py +0 -26
- replay/experimental/metrics/map.py +0 -30
- replay/experimental/metrics/mrr.py +0 -18
- replay/experimental/metrics/ncis_precision.py +0 -31
- replay/experimental/metrics/ndcg.py +0 -49
- replay/experimental/metrics/precision.py +0 -22
- replay/experimental/metrics/recall.py +0 -25
- replay/experimental/metrics/rocauc.py +0 -49
- replay/experimental/metrics/surprisal.py +0 -90
- replay/experimental/metrics/unexpectedness.py +0 -76
- replay/experimental/models/__init__.py +0 -50
- replay/experimental/models/admm_slim.py +0 -257
- replay/experimental/models/base_neighbour_rec.py +0 -200
- replay/experimental/models/base_rec.py +0 -1386
- replay/experimental/models/base_torch_rec.py +0 -234
- replay/experimental/models/cql.py +0 -454
- replay/experimental/models/ddpg.py +0 -932
- replay/experimental/models/dt4rec/__init__.py +0 -0
- replay/experimental/models/dt4rec/dt4rec.py +0 -189
- replay/experimental/models/dt4rec/gpt1.py +0 -401
- replay/experimental/models/dt4rec/trainer.py +0 -127
- replay/experimental/models/dt4rec/utils.py +0 -264
- replay/experimental/models/extensions/spark_custom_models/__init__.py +0 -0
- replay/experimental/models/extensions/spark_custom_models/als_extension.py +0 -792
- replay/experimental/models/hierarchical_recommender.py +0 -331
- replay/experimental/models/implicit_wrap.py +0 -131
- replay/experimental/models/lightfm_wrap.py +0 -303
- replay/experimental/models/mult_vae.py +0 -332
- replay/experimental/models/neural_ts.py +0 -986
- replay/experimental/models/neuromf.py +0 -406
- replay/experimental/models/scala_als.py +0 -293
- replay/experimental/models/u_lin_ucb.py +0 -115
- replay/experimental/nn/data/__init__.py +0 -1
- replay/experimental/nn/data/schema_builder.py +0 -102
- replay/experimental/preprocessing/__init__.py +0 -3
- replay/experimental/preprocessing/data_preparator.py +0 -839
- replay/experimental/preprocessing/padder.py +0 -229
- replay/experimental/preprocessing/sequence_generator.py +0 -208
- replay/experimental/scenarios/__init__.py +0 -1
- replay/experimental/scenarios/obp_wrapper/__init__.py +0 -8
- replay/experimental/scenarios/obp_wrapper/obp_optuna_objective.py +0 -74
- replay/experimental/scenarios/obp_wrapper/replay_offline.py +0 -261
- replay/experimental/scenarios/obp_wrapper/utils.py +0 -85
- replay/experimental/scenarios/two_stages/__init__.py +0 -0
- replay/experimental/scenarios/two_stages/reranker.py +0 -117
- replay/experimental/scenarios/two_stages/two_stages_scenario.py +0 -757
- replay/experimental/utils/__init__.py +0 -0
- replay/experimental/utils/logger.py +0 -24
- replay/experimental/utils/model_handler.py +0 -186
- replay/experimental/utils/session_handler.py +0 -44
- {replay_rec-0.20.1rc0.dist-info → replay_rec-0.20.2.dist-info}/WHEEL +0 -0
- {replay_rec-0.20.1rc0.dist-info → replay_rec-0.20.2.dist-info}/licenses/LICENSE +0 -0
- {replay_rec-0.20.1rc0.dist-info → replay_rec-0.20.2.dist-info}/licenses/NOTICE +0 -0
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from dataclasses import dataclass
|
|
3
|
-
from typing import Any, Optional, Union
|
|
4
|
-
|
|
5
|
-
import numpy as np
|
|
6
|
-
import pandas as pd
|
|
7
|
-
from obp.policy.base import BaseOfflinePolicyLearner
|
|
8
|
-
from optuna import create_study
|
|
9
|
-
from optuna.samplers import TPESampler
|
|
10
|
-
from pyspark.sql import DataFrame
|
|
11
|
-
|
|
12
|
-
from replay.data import Dataset, FeatureHint, FeatureInfo, FeatureSchema, FeatureType
|
|
13
|
-
from replay.experimental.models.base_rec import BaseRecommender as ExperimentalBaseRecommender
|
|
14
|
-
from replay.experimental.scenarios.obp_wrapper.obp_optuna_objective import OBPObjective
|
|
15
|
-
from replay.experimental.scenarios.obp_wrapper.utils import split_bandit_feedback
|
|
16
|
-
from replay.models.base_rec import BaseRecommender
|
|
17
|
-
from replay.utils.spark_utils import convert2spark
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def obp2df(
|
|
21
|
-
action: np.ndarray, reward: np.ndarray, timestamp: np.ndarray, feedback_column: str
|
|
22
|
-
) -> Optional[pd.DataFrame]:
|
|
23
|
-
"""
|
|
24
|
-
Converts OBP log to the pandas DataFrame
|
|
25
|
-
"""
|
|
26
|
-
|
|
27
|
-
n_interactions = len(action)
|
|
28
|
-
|
|
29
|
-
df = pd.DataFrame(
|
|
30
|
-
{
|
|
31
|
-
"user_idx": np.arange(n_interactions),
|
|
32
|
-
"item_idx": action,
|
|
33
|
-
feedback_column: reward,
|
|
34
|
-
"timestamp": timestamp,
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
return df
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def context2df(context: np.ndarray, idx_col: np.ndarray, idx_col_name: str) -> Optional[pd.DataFrame]:
|
|
42
|
-
"""
|
|
43
|
-
Converts OBP log to the pandas DataFrame
|
|
44
|
-
"""
|
|
45
|
-
|
|
46
|
-
df1 = pd.DataFrame({idx_col_name + "_idx": idx_col})
|
|
47
|
-
cols = [str(i) + "_" + idx_col_name for i in range(context.shape[1])]
|
|
48
|
-
df2 = pd.DataFrame(context, columns=cols)
|
|
49
|
-
|
|
50
|
-
return df1.join(df2)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass
|
|
54
|
-
class OBPOfflinePolicyLearner(BaseOfflinePolicyLearner):
|
|
55
|
-
"""
|
|
56
|
-
Off-policy learner which wraps OBP data representation into replay format.
|
|
57
|
-
|
|
58
|
-
:param n_actions: Number of actions.
|
|
59
|
-
|
|
60
|
-
:param len_list: Length of a list of actions in a recommendation/ranking inferface,
|
|
61
|
-
slate size. When Open Bandit Dataset is used, 3 should be set.
|
|
62
|
-
|
|
63
|
-
:param replay_model: Any model from replay library with fit, predict functions.
|
|
64
|
-
|
|
65
|
-
:param dataset: Dataset of interactions (user_id, item_id, rating).
|
|
66
|
-
Constructing inside the fit method. Used for predict of replay_model.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
replay_model: Optional[Union[BaseRecommender, ExperimentalBaseRecommender]] = None
|
|
70
|
-
log: Optional[DataFrame] = None
|
|
71
|
-
max_usr_id: int = 0
|
|
72
|
-
item_features: DataFrame = None
|
|
73
|
-
_study = None
|
|
74
|
-
_logger: Optional[logging.Logger] = None
|
|
75
|
-
_objective = OBPObjective
|
|
76
|
-
|
|
77
|
-
def __post_init__(self) -> None:
|
|
78
|
-
"""Initialize Class."""
|
|
79
|
-
|
|
80
|
-
self.is_experimental_model = isinstance(self.replay_model, ExperimentalBaseRecommender)
|
|
81
|
-
if self.is_experimental_model:
|
|
82
|
-
self.feedback_column = "relevance"
|
|
83
|
-
else:
|
|
84
|
-
self.feedback_column = "rating"
|
|
85
|
-
|
|
86
|
-
self.feature_schema = FeatureSchema(
|
|
87
|
-
[
|
|
88
|
-
FeatureInfo(
|
|
89
|
-
column="user_idx",
|
|
90
|
-
feature_type=FeatureType.CATEGORICAL,
|
|
91
|
-
feature_hint=FeatureHint.QUERY_ID,
|
|
92
|
-
),
|
|
93
|
-
FeatureInfo(
|
|
94
|
-
column="item_idx",
|
|
95
|
-
feature_type=FeatureType.CATEGORICAL,
|
|
96
|
-
feature_hint=FeatureHint.ITEM_ID,
|
|
97
|
-
),
|
|
98
|
-
FeatureInfo(
|
|
99
|
-
column="rating",
|
|
100
|
-
feature_type=FeatureType.NUMERICAL,
|
|
101
|
-
feature_hint=FeatureHint.RATING,
|
|
102
|
-
),
|
|
103
|
-
FeatureInfo(
|
|
104
|
-
column="timestamp",
|
|
105
|
-
feature_type=FeatureType.NUMERICAL,
|
|
106
|
-
feature_hint=FeatureHint.TIMESTAMP,
|
|
107
|
-
),
|
|
108
|
-
]
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def logger(self) -> logging.Logger:
|
|
113
|
-
"""
|
|
114
|
-
:return: get library logger
|
|
115
|
-
"""
|
|
116
|
-
if self._logger is None:
|
|
117
|
-
self._logger = logging.getLogger("replay")
|
|
118
|
-
return self._logger
|
|
119
|
-
|
|
120
|
-
def fit(
|
|
121
|
-
self,
|
|
122
|
-
action: np.ndarray,
|
|
123
|
-
reward: np.ndarray,
|
|
124
|
-
timestamp: np.ndarray,
|
|
125
|
-
context: np.ndarray = None,
|
|
126
|
-
action_context: np.ndarray = None,
|
|
127
|
-
) -> None:
|
|
128
|
-
"""
|
|
129
|
-
Fits an offline bandit policy on the given logged bandit data.
|
|
130
|
-
This `fit` method wraps bandit data and calls `fit` method for the replay_model.
|
|
131
|
-
|
|
132
|
-
:param action: Actions sampled by the logging/behavior policy
|
|
133
|
-
for each data in logged bandit data, i.e., :math:`a_i`.
|
|
134
|
-
|
|
135
|
-
:param reward: Rewards observed for each data in logged bandit data, i.e., :math:`r_i`.
|
|
136
|
-
|
|
137
|
-
:param timestamp: Moment of time when user interacted with corresponding item.
|
|
138
|
-
|
|
139
|
-
:param context: Context vectors observed for each data, i.e., :math:`x_i`.
|
|
140
|
-
|
|
141
|
-
:param action_context: Context vectors observed for each action.
|
|
142
|
-
"""
|
|
143
|
-
|
|
144
|
-
log = convert2spark(obp2df(action, reward, timestamp, self.feedback_column))
|
|
145
|
-
self.log = log
|
|
146
|
-
|
|
147
|
-
user_features = None
|
|
148
|
-
self.max_usr_id = reward.shape[0]
|
|
149
|
-
|
|
150
|
-
if context is not None:
|
|
151
|
-
user_features = convert2spark(context2df(context, np.arange(context.shape[0]), "user"))
|
|
152
|
-
|
|
153
|
-
if action_context is not None:
|
|
154
|
-
self.item_features = convert2spark(context2df(action_context, np.arange(self.n_actions), "item"))
|
|
155
|
-
|
|
156
|
-
if self.is_experimental_model:
|
|
157
|
-
self.replay_model._fit_wrap(log, user_features, self.item_features)
|
|
158
|
-
else:
|
|
159
|
-
dataset = Dataset(
|
|
160
|
-
feature_schema=self.feature_schema,
|
|
161
|
-
interactions=log,
|
|
162
|
-
query_features=user_features,
|
|
163
|
-
item_features=self.item_features,
|
|
164
|
-
)
|
|
165
|
-
self.replay_model._fit_wrap(dataset)
|
|
166
|
-
|
|
167
|
-
def predict(self, n_rounds: int = 1, context: np.ndarray = None) -> np.ndarray:
|
|
168
|
-
"""Predict best actions for new data.
|
|
169
|
-
Action set predicted by this `predict` method can contain duplicate items.
|
|
170
|
-
If a non-repetitive action set is needed, please use the `sample_action` method.
|
|
171
|
-
|
|
172
|
-
:context: Context vectors for new data.
|
|
173
|
-
|
|
174
|
-
:return: Action choices made by a classifier, which can contain duplicate items.
|
|
175
|
-
If a non-repetitive action set is needed, please use the `sample_action` method.
|
|
176
|
-
"""
|
|
177
|
-
|
|
178
|
-
user_features = None
|
|
179
|
-
if context is not None:
|
|
180
|
-
user_features = convert2spark(
|
|
181
|
-
context2df(context, np.arange(self.max_usr_id, self.max_usr_id + n_rounds), "user")
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
users = convert2spark(pd.DataFrame({"user_idx": np.arange(self.max_usr_id, self.max_usr_id + n_rounds)}))
|
|
185
|
-
items = convert2spark(pd.DataFrame({"item_idx": np.arange(self.n_actions)}))
|
|
186
|
-
|
|
187
|
-
self.max_usr_id += n_rounds
|
|
188
|
-
|
|
189
|
-
if self.is_experimental_model:
|
|
190
|
-
action_dist = self.replay_model._predict_proba(
|
|
191
|
-
self.log, self.len_list, users, items, user_features, self.item_features, filter_seen_items=False
|
|
192
|
-
)
|
|
193
|
-
else:
|
|
194
|
-
dataset = Dataset(
|
|
195
|
-
feature_schema=self.feature_schema,
|
|
196
|
-
interactions=self.log,
|
|
197
|
-
query_features=user_features,
|
|
198
|
-
item_features=self.item_features,
|
|
199
|
-
check_consistency=False,
|
|
200
|
-
)
|
|
201
|
-
action_dist = self.replay_model._predict_proba(
|
|
202
|
-
dataset, self.len_list, users, items, filter_seen_items=False
|
|
203
|
-
)
|
|
204
|
-
return action_dist
|
|
205
|
-
|
|
206
|
-
def optimize(
|
|
207
|
-
self,
|
|
208
|
-
bandit_feedback: dict[str, np.ndarray],
|
|
209
|
-
val_size: float = 0.3,
|
|
210
|
-
param_borders: Optional[dict[str, list[Any]]] = None,
|
|
211
|
-
criterion: str = "ipw",
|
|
212
|
-
budget: int = 10,
|
|
213
|
-
new_study: bool = True,
|
|
214
|
-
) -> Optional[dict[str, Any]]:
|
|
215
|
-
"""Optimize model parameters using optuna.
|
|
216
|
-
Optimization is carried out over the IPW/DR/DM scores(IPW by default).
|
|
217
|
-
|
|
218
|
-
:param bandit_feedback: Bandit log data with fields
|
|
219
|
-
``[action, reward, context, action_context,
|
|
220
|
-
n_rounds, n_actions, position, pscore]`` as in OpenBanditPipeline.
|
|
221
|
-
|
|
222
|
-
:param val_size: Size of validation subset.
|
|
223
|
-
|
|
224
|
-
:param param_borders: Dictionary of parameter names with pair of borders
|
|
225
|
-
for the parameters optimization algorithm.
|
|
226
|
-
|
|
227
|
-
:param criterion: Score for optimization. Available are `ipw`, `dr` and `dm`.
|
|
228
|
-
|
|
229
|
-
:param budget: Number of trials for the optimization algorithm.
|
|
230
|
-
|
|
231
|
-
:param new_study: Flag to create new study or not for optuna.
|
|
232
|
-
|
|
233
|
-
:return: Dictionary of parameter names with optimal value of corresponding parameter.
|
|
234
|
-
"""
|
|
235
|
-
|
|
236
|
-
bandit_feedback_train, bandit_feedback_val = split_bandit_feedback(bandit_feedback, val_size)
|
|
237
|
-
|
|
238
|
-
if self.replay_model._search_space is None:
|
|
239
|
-
self.logger.warning("%s has no hyper parameters to optimize", str(self))
|
|
240
|
-
return None
|
|
241
|
-
|
|
242
|
-
if self._study is None or new_study:
|
|
243
|
-
self._study = create_study(direction="maximize", sampler=TPESampler())
|
|
244
|
-
|
|
245
|
-
search_space = self.replay_model._prepare_param_borders(param_borders)
|
|
246
|
-
if self.replay_model._init_params_in_search_space(search_space) and not self.replay_model._params_tried():
|
|
247
|
-
self._study.enqueue_trial(self.replay_model._init_args)
|
|
248
|
-
|
|
249
|
-
objective = self._objective(
|
|
250
|
-
search_space=search_space,
|
|
251
|
-
bandit_feedback_train=bandit_feedback_train,
|
|
252
|
-
bandit_feedback_val=bandit_feedback_val,
|
|
253
|
-
learner=self,
|
|
254
|
-
criterion=criterion,
|
|
255
|
-
k=self.len_list,
|
|
256
|
-
)
|
|
257
|
-
|
|
258
|
-
self._study.optimize(objective, budget)
|
|
259
|
-
best_params = self._study.best_params
|
|
260
|
-
self.replay_model.set_params(**best_params)
|
|
261
|
-
return best_params
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from obp.ope import RegressionModel
|
|
3
|
-
from sklearn.linear_model import LogisticRegression
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def get_est_rewards_by_reg(n_actions, len_list, bandit_feedback_train, bandit_feedback_test):
|
|
7
|
-
"""
|
|
8
|
-
Fit Logistic Regression to rewards from `bandit_feedback`.
|
|
9
|
-
"""
|
|
10
|
-
regression_model = RegressionModel(
|
|
11
|
-
n_actions=n_actions,
|
|
12
|
-
len_list=len_list,
|
|
13
|
-
action_context=bandit_feedback_train["action_context"],
|
|
14
|
-
base_model=LogisticRegression(max_iter=1000, random_state=12345),
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
regression_model.fit(
|
|
18
|
-
context=bandit_feedback_train["context"],
|
|
19
|
-
action=bandit_feedback_train["action"],
|
|
20
|
-
reward=bandit_feedback_train["reward"],
|
|
21
|
-
position=bandit_feedback_train["position"],
|
|
22
|
-
pscore=bandit_feedback_train["pscore"],
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
estimated_rewards_by_reg_model = regression_model.predict(
|
|
26
|
-
context=bandit_feedback_test["context"],
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
return estimated_rewards_by_reg_model
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def bandit_subset(borders: list[int], bandit_feedback: dict[str, np.ndarray]) -> dict[str, np.ndarray]:
|
|
33
|
-
"""
|
|
34
|
-
This function returns subset of a `bandit_feedback`
|
|
35
|
-
with borders specified in `borders`.
|
|
36
|
-
|
|
37
|
-
:param bandit_feedback: Bandit log data with fields
|
|
38
|
-
``[action, reward, context, action_context,
|
|
39
|
-
n_rounds, n_actions, position, pscore]``
|
|
40
|
-
as in OpenBanditPipeline.
|
|
41
|
-
:param borders: List with two values ``[left, right]``
|
|
42
|
-
:return: Returns subset of a `bandit_feedback` for each key with
|
|
43
|
-
indexes from `left`(including) to `right`(excluding).
|
|
44
|
-
"""
|
|
45
|
-
assert len(borders) == 2
|
|
46
|
-
|
|
47
|
-
left, right = borders
|
|
48
|
-
|
|
49
|
-
assert left < right
|
|
50
|
-
|
|
51
|
-
position = None if bandit_feedback["position"] is None else bandit_feedback["position"][left:right]
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
"n_rounds": right - left,
|
|
55
|
-
"n_actions": bandit_feedback["n_actions"],
|
|
56
|
-
"action": bandit_feedback["action"][left:right],
|
|
57
|
-
"position": position,
|
|
58
|
-
"reward": bandit_feedback["reward"][left:right],
|
|
59
|
-
"pscore": bandit_feedback["pscore"][left:right],
|
|
60
|
-
"context": bandit_feedback["context"][left:right],
|
|
61
|
-
"action_context": bandit_feedback["action_context"][left:right],
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def split_bandit_feedback(
|
|
66
|
-
bandit_feedback: dict[str, np.ndarray], val_size: int = 0.3
|
|
67
|
-
) -> tuple[dict[str, np.ndarray], dict[str, np.ndarray]]:
|
|
68
|
-
"""
|
|
69
|
-
Split `bandit_feedback` into two subsets.
|
|
70
|
-
:param bandit_feedback: Bandit log data with fields
|
|
71
|
-
``[action, reward, context, action_context,
|
|
72
|
-
n_rounds, n_actions, position, pscore]``
|
|
73
|
-
as in OpenBanditPipeline.
|
|
74
|
-
:param val_size: Number in range ``[0, 1]`` corresponding to the proportion of
|
|
75
|
-
train/val split.
|
|
76
|
-
:return: `bandit_feedback_train` and `bandit_feedback_val` split.
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
n_rounds = bandit_feedback["n_rounds"]
|
|
80
|
-
n_rounds_train = int(n_rounds * (1.0 - val_size))
|
|
81
|
-
|
|
82
|
-
bandit_feedback_train = bandit_subset([0, n_rounds_train], bandit_feedback)
|
|
83
|
-
bandit_feedback_val = bandit_subset([n_rounds_train, n_rounds], bandit_feedback)
|
|
84
|
-
|
|
85
|
-
return bandit_feedback_train, bandit_feedback_val
|
|
File without changes
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
from abc import abstractmethod
|
|
3
|
-
from typing import Optional
|
|
4
|
-
|
|
5
|
-
from lightautoml.automl.presets.tabular_presets import TabularAutoML
|
|
6
|
-
from lightautoml.tasks import Task
|
|
7
|
-
from pyspark.sql import DataFrame
|
|
8
|
-
|
|
9
|
-
from replay.utils.spark_utils import convert2spark, get_top_k_recs
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ReRanker:
|
|
13
|
-
"""
|
|
14
|
-
Base class for models which re-rank recommendations produced by other models.
|
|
15
|
-
May be used as a part of two-stages recommendation pipeline.
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
_logger: Optional[logging.Logger] = None
|
|
19
|
-
|
|
20
|
-
@property
|
|
21
|
-
def logger(self) -> logging.Logger:
|
|
22
|
-
"""
|
|
23
|
-
:returns: get library logger
|
|
24
|
-
"""
|
|
25
|
-
if self._logger is None:
|
|
26
|
-
self._logger = logging.getLogger("replay")
|
|
27
|
-
return self._logger
|
|
28
|
-
|
|
29
|
-
@abstractmethod
|
|
30
|
-
def fit(self, data: DataFrame, fit_params: Optional[dict] = None) -> None:
|
|
31
|
-
"""
|
|
32
|
-
Fit the model which re-rank user-item pairs generated outside the models.
|
|
33
|
-
|
|
34
|
-
:param data: spark dataframe with obligatory ``[user_idx, item_idx, target]``
|
|
35
|
-
columns and features' columns
|
|
36
|
-
:param fit_params: dict of parameters to pass to model.fit()
|
|
37
|
-
"""
|
|
38
|
-
|
|
39
|
-
@abstractmethod
|
|
40
|
-
def predict(self, data, k) -> DataFrame:
|
|
41
|
-
"""
|
|
42
|
-
Re-rank data with the model and get top-k recommendations for each user.
|
|
43
|
-
|
|
44
|
-
:param data: spark dataframe with obligatory ``[user_idx, item_idx]``
|
|
45
|
-
columns and features' columns
|
|
46
|
-
:param k: number of recommendations for each user
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class LamaWrap(ReRanker):
|
|
51
|
-
"""
|
|
52
|
-
LightAutoML TabularPipeline binary classification model wrapper for recommendations re-ranking.
|
|
53
|
-
Read more: https://github.com/sberbank-ai-lab/LightAutoML
|
|
54
|
-
"""
|
|
55
|
-
|
|
56
|
-
def __init__(
|
|
57
|
-
self,
|
|
58
|
-
params: Optional[dict] = None,
|
|
59
|
-
config_path: Optional[str] = None,
|
|
60
|
-
):
|
|
61
|
-
"""
|
|
62
|
-
Initialize LightAutoML TabularPipeline with passed params/configuration file.
|
|
63
|
-
|
|
64
|
-
:param params: dict of model parameters
|
|
65
|
-
:param config_path: path to configuration file
|
|
66
|
-
"""
|
|
67
|
-
self.model = TabularAutoML(
|
|
68
|
-
task=Task("binary"),
|
|
69
|
-
config_path=config_path,
|
|
70
|
-
**(params if params is not None else {}),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
def fit(self, data: DataFrame, fit_params: Optional[dict] = None) -> None:
|
|
74
|
-
"""
|
|
75
|
-
Fit the LightAutoML TabularPipeline model with binary classification task.
|
|
76
|
-
Data should include negative and positive user-item pairs.
|
|
77
|
-
|
|
78
|
-
:param data: spark dataframe with obligatory ``[user_idx, item_idx, target]``
|
|
79
|
-
columns and features' columns. `Target` column should consist of zeros and ones
|
|
80
|
-
as the model is a binary classification model.
|
|
81
|
-
:param fit_params: dict of parameters to pass to model.fit()
|
|
82
|
-
See LightAutoML TabularPipeline fit_predict parameters.
|
|
83
|
-
"""
|
|
84
|
-
|
|
85
|
-
params = {"roles": {"target": "target"}, "verbose": 1}
|
|
86
|
-
params.update({} if fit_params is None else fit_params)
|
|
87
|
-
data = data.drop("user_idx", "item_idx")
|
|
88
|
-
data_pd = data.toPandas()
|
|
89
|
-
self.model.fit_predict(data_pd, **params)
|
|
90
|
-
|
|
91
|
-
def predict(self, data: DataFrame, k: int) -> DataFrame:
|
|
92
|
-
"""
|
|
93
|
-
Re-rank data with the model and get top-k recommendations for each user.
|
|
94
|
-
|
|
95
|
-
:param data: spark dataframe with obligatory ``[user_idx, item_idx]``
|
|
96
|
-
columns and features' columns
|
|
97
|
-
:param k: number of recommendations for each user
|
|
98
|
-
:return: spark dataframe with top-k recommendations for each user
|
|
99
|
-
the dataframe columns are ``[user_idx, item_idx, relevance]``
|
|
100
|
-
"""
|
|
101
|
-
data_pd = data.toPandas()
|
|
102
|
-
candidates_ids = data_pd[["user_idx", "item_idx"]]
|
|
103
|
-
data_pd.drop(columns=["user_idx", "item_idx"], inplace=True)
|
|
104
|
-
self.logger.info("Starting re-ranking")
|
|
105
|
-
candidates_pred = self.model.predict(data_pd)
|
|
106
|
-
candidates_ids.loc[:, "relevance"] = candidates_pred.data[:, 0]
|
|
107
|
-
self.logger.info(
|
|
108
|
-
"%s candidates rated for %s users",
|
|
109
|
-
candidates_ids.shape[0],
|
|
110
|
-
candidates_ids["user_idx"].nunique(),
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
self.logger.info("top-k")
|
|
114
|
-
return get_top_k_recs(
|
|
115
|
-
recs=convert2spark(candidates_ids),
|
|
116
|
-
k=k,
|
|
117
|
-
)
|