Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update content personalization #1333

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
517 changes: 122 additions & 395 deletions docs/examples/content-personalization.ipynb

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ River's mini-batch methods now support pandas v2. In particular, River conforms
## forest

- Simplify inner the structures of `forest.ARFClassifier` and `forest.ARFRegressor` by removing redundant class hierarchy. Simplify how concept drift logging can be accessed in individual trees and in the forest as a whole.

## reco

- Renamed `reco.base.Ranker` to `reco.base.Recommender`.
- Added a `sample(user, items)` method to `reco.base.Recommender`.
2 changes: 1 addition & 1 deletion river/bandit/lin_ucb.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class LinUCBDisjoint(bandit.base.ContextualPolicy):
References
----------
[^1]: [A Contextual-Bandit Approach to Personalized News Article Recommendation](https://arxiv.org/abs/1003.0146)
[^2:] [Contextual Bandits Analysis of LinUCB Disjoint Algorithm with Dataset](https://kfoofw.github.io/contextual-bandits-linear-ucb-disjoint/)
[^2]: [Contextual Bandits Analysis of LinUCB Disjoint Algorithm with Dataset](https://kfoofw.github.io/contextual-bandits-linear-ucb-disjoint/)

"""

Expand Down
6 changes: 3 additions & 3 deletions river/checks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from river.base import Estimator
from river.model_selection.base import ModelSelector
from river.reco.base import Ranker
from river.reco.base import Recommender

from . import anomaly, clf, common, model_selection, reco

Expand Down Expand Up @@ -54,7 +54,7 @@ def _yield_datasets(model: Estimator):

# Recommendation models can be regressors or classifiers, but they have requirements as to the
# structure of the data
if isinstance(utils.inspect.extract_relevant(model), Ranker):
if isinstance(utils.inspect.extract_relevant(model), Recommender):
if utils.inspect.isregressor(model):
yield _DummyDataset(
("Alice", "Superman", 8),
Expand Down Expand Up @@ -164,7 +164,7 @@ def yield_checks(model: Estimator) -> typing.Iterator[typing.Callable]:
if isinstance(utils.inspect.extract_relevant(model), ModelSelector):
dataset_checks.append(model_selection.check_model_selection_order_does_not_matter)

if isinstance(utils.inspect.extract_relevant(model), Ranker):
if isinstance(utils.inspect.extract_relevant(model), Recommender):
yield reco.check_reco_routine

if utils.inspect.isanomalydetector(model):
Expand Down
22 changes: 19 additions & 3 deletions river/reco/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
Reward = typing.Union[numbers.Number, bool] # noqa: UP007


__all__ = ["Ranker"]
__all__ = ["Recommender"]


class Ranker(base.Estimator):
class Recommender(base.Estimator):
"""Base class for ranking models.

Parameters
Expand Down Expand Up @@ -68,8 +68,24 @@ def predict_one(self, user: ID, item: ID, x: dict | None = None) -> Reward:

"""

def sample(self, user: ID, items: set[ID], x: dict | None = None):
"""Sample an item at random, based on the preference of a given user.

Parameters
----------
user
A user ID.
items
A set of items to rank.
x
Optional context to use.

"""
preferences = [max(1e-10, self.predict_one(user, item, x)) for item in items]
return self._rng.choices(items, weights=preferences, k=1)[0]

def rank(self, user: ID, items: set[ID], x: dict | None = None) -> list[ID]:
"""Rank models by decreasing order of preference for a given user.
"""Rank items by decreasing order of preference for a given user.

Parameters
----------
Expand Down
2 changes: 1 addition & 1 deletion river/reco/baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
__all__ = ["Baseline"]


class Baseline(reco.base.Ranker):
class Baseline(reco.base.Recommender):
"""Baseline for recommender systems.

A first-order approximation of the bias involved in target. The model equation is defined as:
Expand Down
6 changes: 2 additions & 4 deletions river/reco/biased_mf.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@

import numpy as np

from river import optim, stats, utils

from .base import Ranker
from river import optim, reco, stats, utils

__all__ = ["BiasedMF"]


class BiasedMF(Ranker):
class BiasedMF(reco.base.Recommender):
"""Biased Matrix Factorization for recommender systems.

The model equation is defined as:
Expand Down
4 changes: 2 additions & 2 deletions river/reco/funk_mf.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
__all__ = ["FunkMF"]


class FunkMF(reco.base.Ranker):
class FunkMF(reco.base.Recommender):
"""Funk Matrix Factorization for recommender systems.

The model equation is defined as:
Expand Down Expand Up @@ -131,7 +131,7 @@ def predict_one(self, user, item, x=None):

def learn_one(self, user, item, y, x=None):
# Calculate the gradient of the loss with respect to the prediction
g_loss = self.loss.gradient(y, self.predict_one(user, item))
g_loss = self.loss.gradient(y, self.predict_one(user, item, x=x))

# Clamp the gradient to avoid numerical instability
g_loss = utils.math.clamp(g_loss, minimum=-self.clip_gradient, maximum=self.clip_gradient)
Expand Down
2 changes: 1 addition & 1 deletion river/reco/normal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
__all__ = ["RandomNormal"]


class RandomNormal(reco.base.Ranker):
class RandomNormal(reco.base.Recommender):
"""Predicts random values sampled from a normal distribution.

The parameters of the normal distribution are fitted with running statistics. They parameters
Expand Down