Skip to content

Commit

Permalink
reformat
Browse files Browse the repository at this point in the history
  • Loading branch information
mdekstrand committed Nov 11, 2023
1 parent 3c4f9c1 commit 2a4c190
Show file tree
Hide file tree
Showing 34 changed files with 1,026 additions and 816 deletions.
4 changes: 3 additions & 1 deletion lenskit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,20 @@

from lenskit.algorithms import * # noqa: F401,F403

__version__ = '0.15.0'
__version__ = "0.15.0"


class DataWarning(UserWarning):
"""
Warning raised for detectable problems with input data.
"""

pass


class ConfigWarning(UserWarning):
"""
Warning raised for detectable problems with algorithm configurations.
"""

pass
22 changes: 12 additions & 10 deletions lenskit/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from abc import ABCMeta, abstractmethod
import inspect

__all__ = ['Algorithm', 'Recommender', 'Predictor', 'CandidateSelector']
__all__ = ["Algorithm", "Recommender", "Predictor", "CandidateSelector"]


class Algorithm(metaclass=ABCMeta):
Expand Down Expand Up @@ -68,10 +68,10 @@ def get_params(self, deep=True):
if hasattr(self, name) and name not in self.IGNORED_PARAMS:
value = getattr(self, name)
params[name] = value
if deep and hasattr(value, 'get_params'):
if deep and hasattr(value, "get_params"):
sps = value.get_params(deep)
for k, sv in sps.items():
params[name + '__' + k] = sv
params[name + "__" + k] = sv

return params

Expand Down Expand Up @@ -101,16 +101,16 @@ def predict(self, pairs, ratings=None):
raise NotImplementedError()

def upred(df):
user, = df['user'].unique()
items = df['item']
(user,) = df["user"].unique()
items = df["item"]
preds = self.predict_for_user(user, items)
preds.name = 'prediction'
res = df.join(preds, on='item', how='left')
preds.name = "prediction"
res = df.join(preds, on="item", how="left")
return res.prediction

res = pairs.loc[:, ['user', 'item']].groupby('user', sort=False).apply(upred)
res.reset_index(level='user', inplace=True, drop=True)
res.name = 'prediction'
res = pairs.loc[:, ["user", "item"]].groupby("user", sort=False).apply(upred)
res.reset_index(level="user", inplace=True, drop=True)
res.name = "prediction"
return res.loc[pairs.index.values]

@abstractmethod
Expand Down Expand Up @@ -173,6 +173,7 @@ def adapt(cls, algo):
algo(Predictor): the underlying rating predictor.
"""
from .basic import TopN

if isinstance(algo, Recommender):
return algo
else:
Expand Down Expand Up @@ -212,6 +213,7 @@ def rated_items(ratings):
"""
import pandas as pd
import numpy as np

if isinstance(ratings, pd.Series):
return ratings.index.values
elif isinstance(ratings, np.ndarray):
Expand Down
144 changes: 91 additions & 53 deletions lenskit/algorithms/als.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,7 @@

_logger = logging.getLogger(__name__)

PartialModel = namedtuple('PartialModel', [
'users', 'items',
'user_matrix', 'item_matrix'
])
PartialModel = namedtuple("PartialModel", ["users", "items", "user_matrix", "item_matrix"])


@njit
Expand Down Expand Up @@ -343,10 +340,22 @@ class BiasedMF(MFPredictor):
Random number generator or state (see :func:`lenskit.util.random.rng`).
progress: a :func:`tqdm.tqdm`-compatible progress bar function
"""

timer = None

def __init__(self, features, *, iterations=20, reg=0.1, damping=5, bias=True, method='cd',
rng_spec=None, progress=None, save_user_features=True):
def __init__(
self,
features,
*,
iterations=20,
reg=0.1,
damping=5,
bias=True,
method="cd",
rng_spec=None,
progress=None,
save_user_features=True,
):
self.features = features
self.iterations = iterations
self.regularization = reg
Expand Down Expand Up @@ -377,12 +386,18 @@ def fit(self, ratings, **kwargs):
pass # we just need to do the iterations

if self.user_features_ is not None:
_logger.info('trained model in %s (|P|=%f, |Q|=%f)', self.timer,
np.linalg.norm(self.user_features_, 'fro'),
np.linalg.norm(self.item_features_, 'fro'))
_logger.info(
"trained model in %s (|P|=%f, |Q|=%f)",
self.timer,
np.linalg.norm(self.user_features_, "fro"),
np.linalg.norm(self.item_features_, "fro"),
)
else:
_logger.info('trained model in %s (|Q|=%f)', self.timer,
np.linalg.norm(self.item_features_, 'fro'))
_logger.info(
"trained model in %s (|Q|=%f)",
self.timer,
np.linalg.norm(self.item_features_, "fro"),
)

del self.timer
return self
Expand All @@ -399,13 +414,14 @@ def fit_iters(self, ratings, **kwargs):
"""

if self.bias:
_logger.info('[%s] fitting bias model', self.timer)
_logger.info("[%s] fitting bias model", self.timer)
self.bias.fit(ratings)

current, uctx, ictx = self._initial_model(ratings)

_logger.info('[%s] training biased MF model with ALS for %d features',
self.timer, self.features)
_logger.info(
"[%s] training biased MF model with ALS for %d features", self.timer, self.features
)
for epoch, model in enumerate(self._train_iters(current, uctx, ictx)):
self._save_params(model)
yield self
Expand All @@ -423,25 +439,25 @@ def _save_params(self, model):
def _initial_model(self, ratings):
# transform ratings using offsets
if self.bias:
_logger.info('[%s] normalizing ratings', self.timer)
_logger.info("[%s] normalizing ratings", self.timer)
ratings = self.bias.transform(ratings)

"Initialize a model and build contexts."
rmat, users, items = sparse_ratings(ratings)
n_users = len(users)
n_items = len(items)

_logger.debug('setting up contexts')
_logger.debug("setting up contexts")
trmat = rmat.transpose()

_logger.debug('initializing item matrix')
_logger.debug("initializing item matrix")
imat = self.rng.standard_normal((n_items, self.features))
imat /= np.linalg.norm(imat, axis=1).reshape((n_items, 1))
_logger.debug('|Q|: %f', np.linalg.norm(imat, 'fro'))
_logger.debug('initializing user matrix')
_logger.debug("|Q|: %f", np.linalg.norm(imat, "fro"))
_logger.debug("initializing user matrix")
umat = self.rng.standard_normal((n_users, self.features))
umat /= np.linalg.norm(umat, axis=1).reshape((n_users, 1))
_logger.debug('|P|: %f', np.linalg.norm(umat, 'fro'))
_logger.debug("|P|: %f", np.linalg.norm(umat, "fro"))

return PartialModel(users, items, umat, imat), rmat, trmat

Expand All @@ -461,24 +477,24 @@ def _train_iters(self, current, uctx, ictx):
assert ictx.nrows == n_items
assert ictx.ncols == n_users

if self.method == 'cd':
if self.method == "cd":
train = _train_matrix_cd
elif self.method == 'lu':
elif self.method == "lu":
train = _train_matrix_lu
else:
raise ValueError('invalid training method ' + self.method)
raise ValueError("invalid training method " + self.method)

if isinstance(self.regularization, tuple):
ureg, ireg = self.regularization
else:
ureg = ireg = self.regularization

for epoch in self.progress(range(self.iterations), desc='BiasedMF', leave=False):
for epoch in self.progress(range(self.iterations), desc="BiasedMF", leave=False):
du = train(uctx, current.user_matrix, current.item_matrix, ureg)
_logger.debug('[%s] finished user epoch %d', self.timer, epoch)
_logger.debug("[%s] finished user epoch %d", self.timer, epoch)
di = train(ictx, current.item_matrix, current.user_matrix, ireg)
_logger.debug('[%s] finished item epoch %d', self.timer, epoch)
_logger.info('[%s] finished epoch %d (|ΔP|=%.3f, |ΔQ|=%.3f)', self.timer, epoch, du, di)
_logger.debug("[%s] finished item epoch %d", self.timer, epoch)
_logger.info("[%s] finished epoch %d (|ΔP|=%.3f, |ΔQ|=%.3f)", self.timer, epoch, du, di)
yield current

def predict_for_user(self, user, items, ratings=None):
Expand Down Expand Up @@ -513,8 +529,9 @@ def predict_for_user(self, user, items, ratings=None):
return scores

def __str__(self):
return 'als.BiasedMF(features={}, regularization={})'.\
format(self.features, self.regularization)
return "als.BiasedMF(features={}, regularization={})".format(
self.features, self.regularization
)


class ImplicitMF(MFPredictor):
Expand Down Expand Up @@ -561,10 +578,22 @@ class ImplicitMF(MFPredictor):
Random number generator or state (see :func:`lenskit.util.random.rng`).
progress: a :func:`tqdm.tqdm`-compatible progress bar function
"""

timer = None

def __init__(self, features, *, iterations=20, reg=0.1, weight=40, use_ratings=False,
method='cg', rng_spec=None, progress=None, save_user_features=True):
def __init__(
self,
features,
*,
iterations=20,
reg=0.1,
weight=40,
use_ratings=False,
method="cg",
rng_spec=None,
progress=None,
save_user_features=True,
):
self.features = features
self.iterations = iterations
self.reg = reg
Expand All @@ -582,14 +611,20 @@ def fit(self, ratings, **kwargs):
pass

if self.user_features_ is not None:
_logger.info('[%s] finished training model with %d features (|P|=%f, |Q|=%f)',
self.timer, self.features,
np.linalg.norm(self.user_features_, 'fro'),
np.linalg.norm(self.item_features_, 'fro'))
_logger.info(
"[%s] finished training model with %d features (|P|=%f, |Q|=%f)",
self.timer,
self.features,
np.linalg.norm(self.user_features_, "fro"),
np.linalg.norm(self.item_features_, "fro"),
)
else:
_logger.info('[%s] finished training model with %d features (|Q|=%f)',
self.timer, self.features,
np.linalg.norm(self.item_features_, 'fro'))
_logger.info(
"[%s] finished training model with %d features (|Q|=%f)",
self.timer,
self.features,
np.linalg.norm(self.item_features_, "fro"),
)

# unpack the regularization
if isinstance(self.reg, tuple):
Expand All @@ -605,10 +640,12 @@ def fit(self, ratings, **kwargs):
def fit_iters(self, ratings, **kwargs):
current, uctx, ictx = self._initial_model(ratings)

_logger.info('[%s] training implicit MF model with ALS for %d features',
self.timer, self.features)
_logger.info('have %d observations for %d users and %d items',
uctx.nnz, uctx.nrows, ictx.nrows)
_logger.info(
"[%s] training implicit MF model with ALS for %d features", self.timer, self.features
)
_logger.info(
"have %d observations for %d users and %d items", uctx.nnz, uctx.nrows, ictx.nrows
)
for model in self._train_iters(current, uctx, ictx):
self._save_model(model)
yield self
Expand All @@ -624,37 +661,37 @@ def _save_model(self, model):

def _train_iters(self, current, uctx, ictx):
"Generator of training iterations."
if self.method == 'lu':
if self.method == "lu":
train = _train_implicit_lu
elif self.method == 'cg':
elif self.method == "cg":
train = _train_implicit_cg
else:
raise ValueError('unknown solver ' + self.method)
raise ValueError("unknown solver " + self.method)

if isinstance(self.reg, tuple):
ureg, ireg = self.reg
else:
ureg = ireg = self.reg

for epoch in self.progress(range(self.iterations), desc='ImplicitMF', leave=False):
for epoch in self.progress(range(self.iterations), desc="ImplicitMF", leave=False):
du = train(uctx, current.user_matrix, current.item_matrix, ureg)
_logger.debug('[%s] finished user epoch %d', self.timer, epoch)
_logger.debug("[%s] finished user epoch %d", self.timer, epoch)
di = train(ictx, current.item_matrix, current.user_matrix, ireg)
_logger.debug('[%s] finished item epoch %d', self.timer, epoch)
_logger.info('[%s] finished epoch %d (|ΔP|=%.3f, |ΔQ|=%.3f)', self.timer, epoch, du, di)
_logger.debug("[%s] finished item epoch %d", self.timer, epoch)
_logger.info("[%s] finished epoch %d (|ΔP|=%.3f, |ΔQ|=%.3f)", self.timer, epoch, du, di)
yield current

def _initial_model(self, ratings):
"Initialize a model and build contexts."

if not self.use_ratings:
ratings = ratings[['user', 'item']]
ratings = ratings[["user", "item"]]

rmat, users, items = sparse_ratings(ratings)
n_users = len(users)
n_items = len(items)

_logger.debug('setting up contexts')
_logger.debug("setting up contexts")
# force values to exist
if rmat.values is None:
rmat.values = np.ones(rmat.nnz)
Expand Down Expand Up @@ -685,5 +722,6 @@ def predict_for_user(self, user, items, ratings=None):
return self.score_by_ids(user, items)

def __str__(self):
return 'als.ImplicitMF(features={}, reg={}, w={})'.\
format(self.features, self.reg, self.weight)
return "als.ImplicitMF(features={}, reg={}, w={})".format(
self.features, self.reg, self.weight
)
Loading

0 comments on commit 2a4c190

Please sign in to comment.