Usage¶
Training data X starts with two columns: user identifiers in column 0 and item
identifiers in column 1. Each row is one interaction. Any further columns are its
query context, which the recommenders here accept and ignore; only a
Cascade ranker learns from it. For ordinary recommenders the rows
are an unordered set of interactions, so their order does not matter. An optional y
array carries the interaction value (such as a rating or confidence) for each row; it is
separate from X and must have the same number of rows. This layout also applies when
X is a pandas DataFrame: the first two columns are used in order, regardless of their
column names.
Every recommender in skrecsys.recommendation and skrecsys.nn has the same four
methods:
import numpy as np
from skrecsys.recommendation import ItemKNNRecommender
X = np.array(
[
["alice", "matrix"],
["alice", "alien"],
["bob", "matrix"],
["bob", "blade runner"],
["carol", "alien"],
["carol", "blade runner"],
["carol", "heat"],
]
)
# fit: learn from interactions
rec = ItemKNNRecommender().fit(X)
# recommend: top-k items per user, best first; items a user already has are left out
items, scores = rec.recommend(["alice", "bob"], n_recommendations=2)
# items -> [["blade runner", "heat"], ["alien", "heat"]]
# scores -> [[1.0, 0.707], [1.0, 0.707]]
# predict: the score of given user-item pairs
rec.predict([["alice", "blade runner"], ["bob", "heat"]]) # -> [1.0, 0.707]
# partial_fit: add a new batch without refitting; new users and items are fine
rec.partial_fit([["dave", "heat"], ["dave", "alien"], ["alice", "blade runner"]])
rec.recommend(["alice", "dave"], n_recommendations=1)[0] # -> [["heat"], ["blade runner"]]
# also skip pairs the model hasn't been updated with yet, such as items just shown
rec.recommend(["dave"], n_recommendations=1, exclude_interactions=[["dave", "blade runner"]])[0]
# -> [["matrix"]]
fit(X, y=None)learns from scratch.yis optional: one rating or weight per row.recommend(users, n_recommendations=k)returns two(n_users, k)arrays: item IDs and their scores. Only users seen byfitorpartial_fitcan be queried.usersmay also be a matrix[user, context...], one row per query.candidates=limits the ranking to some items,exclude_seen=Falsekeeps a user's own items, andexclude_interactions=removes further user-item pairs.predict(pairs)scores given(user, item)pairs, both of which the model has seen.partial_fit(X, y=None)adds a batch to what the model knows. See incremental fitting for which models reproducefitexactly and which continue from where they stopped.
How these calls divide between training jobs and a serving process is covered under production lifecycle.
Model selection¶
Recommenders are scikit-learn estimators, so GridSearchCV and cross_validate work on
them. WarmStartKFold splits interactions so that every user and item in a test fold also
appears in its training fold, and make_recommender_scorer turns a top-k metric into a
scorer:
from sklearn.model_selection import GridSearchCV
from skrecsys.datasets import fetch_movielens_100k
from skrecsys.metrics import make_recommender_scorer, ndcg_at_k
from skrecsys.model_selection import WarmStartKFold
X, y = fetch_movielens_100k(return_X_y=True)
search = GridSearchCV(
ItemKNNRecommender(),
{"n_neighbors": [10, 50, 200], "shrink": [0.0, 10.0]},
cv=WarmStartKFold(n_splits=5, shuffle=True, random_state=0),
scoring=make_recommender_scorer(ndcg_at_k, k=10),
).fit(X)
A quality report usually needs several metrics at several cutoffs. evaluate_recommender asks
for one ranking of max(k) items per user and computes every metric at every cutoff from it.
Given lists, make_recommender_scorer does the same inside scikit-learn: it becomes a
multi-metric scorer.
from sklearn.model_selection import cross_validate
from skrecsys.metrics import evaluate_recommender, hit_rate_at_k, recall_at_k
train, test = next(WarmStartKFold(n_splits=5, shuffle=True, random_state=0).split(X))
rec = ItemKNNRecommender().fit(X[train])
evaluate_recommender(
rec, X[test], metrics=[ndcg_at_k, recall_at_k, hit_rate_at_k], k=[10, 50, 100]
) # {"ndcg@10": ..., "ndcg@50": ..., ..., "hit_rate@100": ...}
scores = cross_validate(
ItemKNNRecommender(),
X,
cv=WarmStartKFold(n_splits=5, shuffle=True, random_state=0),
scoring=make_recommender_scorer([ndcg_at_k, recall_at_k], k=[10, 100]),
) # scores["test_ndcg@10"], scores["test_recall@100"], ...
With a multi-metric scorer, GridSearchCV must be told which score to select by, for instance
refit="ndcg@10".