Skip to content

Commit

Permalink
Merge pull request #281 from mdekstrand/feature/split-implicit
Browse files Browse the repository at this point in the history
Split implicit wrappers into new package
  • Loading branch information
mdekstrand committed Oct 24, 2021
2 parents 76bd820 + 28822ff commit 752c855
Show file tree
Hide file tree
Showing 7 changed files with 5 additions and 272 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/test-suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
if [ $RUNNER = Linux -a $PYVER = 3.8 ]; then
echo "Using most extras"
# we'll test tensorflow in a separate run
e_opt="-e implicit,sklearn"
e_opt="-e sklearn"
fi
bash lkbuild/lock-for-ci.sh -v $PYVER -b $BLAS_IMPL $e_opt
env:
Expand Down
1 change: 1 addition & 0 deletions docs/addons.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ to implementations in other packages.
.. toctree::
:caption: External Algorithms

Implicit wrappers <https://lkpy.lenskit.org/projects/lenskit-implicit>
Poisson factorization <https://lkpy.lenskit.org/projects/lenskit-hpf/>
5 changes: 0 additions & 5 deletions docs/algorithms.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,3 @@ Add-On Packages

See `add-on algorithms <addons.rst>`_ for additional algorithm families and bridges to other
packages.

.. autosummary::

implicit.BPR
implicit.ALS
16 changes: 0 additions & 16 deletions docs/implicit.rst

This file was deleted.

126 changes: 1 addition & 125 deletions lenskit/algorithms/implicit.py
Original file line number Diff line number Diff line change
@@ -1,125 +1 @@
import logging
import inspect
import pandas as pd
import numpy as np

from ..data import sparse_ratings
from . import Recommender, Predictor

_logger = logging.getLogger(__name__)


class BaseRec(Recommender, Predictor):
"""
Base class for Implicit-backed recommenders.
Args:
delegate(implicit.RecommenderBase):
The delegate algorithm.
Attributes:
delegate(implicit.RecommenderBase):
The :py:mod:`implicit` delegate algorithm.
matrix_(scipy.sparse.csr_matrix):
The user-item rating matrix.
user_index_(pandas.Index):
The user index.
item_index_(pandas.Index):
The item index.
"""

def __init__(self, delegate):
self.delegate = delegate

def fit(self, ratings, **kwargs):
matrix, users, items = sparse_ratings(ratings, scipy=True)
iur = matrix.T.tocsr()

_logger.info('training %s on %s matrix (%d nnz)', self.delegate, iur.shape, iur.nnz)

self.delegate.fit(iur)

self.matrix_ = matrix
self.user_index_ = users
self.item_index_ = items

return self

def recommend(self, user, n=None, candidates=None, ratings=None):
try:
uid = self.user_index_.get_loc(user)
except KeyError:
return pd.DataFrame({'item': []})

if candidates is None:
i_n = n if n is not None else len(self.item_index_)
recs = self.delegate.recommend(uid, self.matrix_, N=i_n)
else:
cands = self.item_index_.get_indexer(candidates)
cands = cands[cands >= 0]
recs = self.delegate.rank_items(uid, self.matrix_, cands)

if n is not None:
recs = recs[:n]
rec_df = pd.DataFrame.from_records(recs, columns=['item_pos', 'score'])
rec_df['item'] = self.item_index_[rec_df.item_pos]
return rec_df.loc[:, ['item', 'score']]

def predict_for_user(self, user, items, ratings=None):
try:
uid = self.user_index_.get_loc(user)
except KeyError:
return pd.Series(np.nan, index=items)

iids = self.item_index_.get_indexer(items)
iids = iids[iids >= 0]

ifs = self.delegate.item_factors[iids]
uf = self.delegate._user_factor(uid, None, False)
scores = ifs.dot(uf)
scores = pd.Series(scores, index=self.item_index_[iids])
return scores.reindex(items)

def __getattr__(self, name):
if 'delegate' not in self.__dict__:
raise AttributeError()
dd = self.delegate.__dict__
if name in dd:
return dd[name]
else:
raise AttributeError()

def get_params(self, deep=True):
dd = self.delegate.__dict__
sig = inspect.signature(self.delegate.__class__)
names = list(sig.parameters.keys())
return dict([(k, dd.get(k)) for k in names])

def __str__(self):
return 'Implicit({})'.format(self.delegate)


class ALS(BaseRec):
"""
LensKit interface to :py:mod:`implicit.als`.
"""
def __init__(self, *args, **kwargs):
"""
Construct an ALS recommender. The arguments are passed as-is to
:py:class:`implicit.als.AlternatingLeastSquares`.
"""
from implicit.als import AlternatingLeastSquares
super().__init__(AlternatingLeastSquares(*args, **kwargs))


class BPR(BaseRec):
"""
LensKit interface to :py:mod:`implicit.bpr`.
"""
def __init__(self, *args, **kwargs):
"""
Construct an ALS recommender. The arguments are passed as-is to
:py:class:`implicit.als.BayesianPersonalizedRanking`.
"""
from implicit.bpr import BayesianPersonalizedRanking
super().__init__(BayesianPersonalizedRanking(*args, **kwargs))
from lenskit_implicit import BaseRec, ALS, BPR # noqa: F401
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ demo = [
"nbval >= 0.9",
"matplotlib ~= 3.4",
]
hpf = ["lenskit-hpf"]
implicit = ["implicit"]
sklearn = ["scikit-learn >= 0.22"]
tf = ["tensorflow >=2.1,<2.6"]
hpf = ["lenskit-hpf"]
implicit = ["lenskit-implicit"]

[tool.flit.sdist]
exclude = [
Expand Down
123 changes: 0 additions & 123 deletions tests/test_implicit.py

This file was deleted.

0 comments on commit 752c855

Please sign in to comment.