Skip to content
Open
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
4 changes: 4 additions & 0 deletions implicit/cpu/topk.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ def topk(items, query, int k, item_norms=None, filter_query_items=None, filter_i
query = query.reshape((1, len(query)))

cdef int query_rows = query.shape[0]

# we can't return more results than there are items
k = min(k, items.shape[0])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably also fix this in the GPU code

void KnnQuery::topk_impl(const Matrix &items, const Matrix &query, int k,


indices = np.zeros((query_rows, k), dtype="int32")
distances = np.zeros((query_rows, k), dtype=query.dtype)

Expand Down
8 changes: 6 additions & 2 deletions implicit/evaluation.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ def ranking_metrics_at_k(model, train_user_items, test_user_items, int K=10,
test_user_items = test_user_items.tocsr()

cdef int users = test_user_items.shape[0], items = test_user_items.shape[1]
cdef int u, i, batch_idx
cdef int u, i, batch_idx, num_recs
# precision
cdef double relevant = 0, pr_div = 0, total = 0
# map
Expand Down Expand Up @@ -434,6 +434,10 @@ def ranking_metrics_at_k(model, train_user_items, test_user_items, int K=10,
ids, _ = model.recommend(batch, train_user_items[np.asarray(batch)], N=K)
start_idx += batch_size

# the model can return fewer than K items (for instance if K is larger than
# the number of items), so only look at the items that were actually returned
num_recs = min(K, ids.shape[1])

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just num_recs = min(K, items) ? (The model shouldn't be returning fewer than this).


with nogil:
for batch_idx in range(len(batch)):
u = batch[batch_idx]
Expand All @@ -450,7 +454,7 @@ def ranking_metrics_at_k(model, train_user_items, test_user_items, int K=10,
num_pos_items = likes.size()
num_neg_items = items - num_pos_items

for i in range(K):
for i in range(num_recs):
if likes.find(ids[batch_idx, i]) != likes.end():
relevant += 1
hit += 1
Expand Down
36 changes: 35 additions & 1 deletion tests/evaluation_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,20 @@
from scipy.sparse import csr_matrix, random

import implicit
from implicit.evaluation import leave_k_out_split, precision_at_k, train_test_split
from implicit.als import AlternatingLeastSquares
from implicit.bpr import BayesianPersonalizedRanking
from implicit.evaluation import (
leave_k_out_split,
precision_at_k,
ranking_metrics_at_k,
train_test_split,
)
from implicit.lmf import LogisticMatrixFactorization

pytest.importorskip("h5py")

from implicit.datasets.movielens import get_movielens # noqa
from recommender_base_test import get_checker_board # noqa


def _get_sample_matrix():
Expand Down Expand Up @@ -117,3 +126,28 @@ def test_evaluate_movielens_100k():
model.fit(train)

assert precision_at_k(model, train, test) > 0.2


@pytest.mark.parametrize(
"model",
[
AlternatingLeastSquares(factors=8, use_gpu=False, random_state=42),
BayesianPersonalizedRanking(factors=8, use_gpu=False, random_state=42),
LogisticMatrixFactorization(factors=8, use_gpu=False, random_state=42),
],
)
def test_ranking_metrics_k_larger_than_number_of_items(model):
user_items = get_checker_board(50)
model.fit(user_items, show_progress=False)
test_user_items = csr_matrix(np.eye(50))

# asking for more items than there are in the model should only return the available
# items, and the metrics shouldn't count any item twice
ids, _ = model.recommend(np.arange(50), user_items, N=100)
assert ids.shape == (50, 50)

all_items = ranking_metrics_at_k(model, user_items, test_user_items, K=50, show_progress=False)
too_many = ranking_metrics_at_k(model, user_items, test_user_items, K=100, show_progress=False)

assert too_many == pytest.approx(all_items)
assert all(0 <= value <= 1 for value in too_many.values())
Loading