Repository navigation
Conversation
ranking_eval built two full-length int masks and called np.nonzero three times per query, although the candidate item array is the same for every query. Build it once with np.arange and slice it for the single positive item; keep the mask code as the general fallback. Also read sessions directly from test_set.sessions (same order as si_iter) and pick the rank method and result container once. Metric values, numpy types, result containers and warnings are unchanged. Benchmark: 217.5 ms -> 169.8 ms (22% faster) for NextItemEvaluation.evaluate on 1200 test sessions. Co-Authored-By: Aiden <aiden@weco.ai>
Check that the single-item path and the binary-mask path give the same results, with and without exclude_unknowns, on a test item that is unknown to the training data. Co-Authored-By: Aiden <aiden@weco.ai>
|
Thanks @dexhunter for the fix. I think the changes make sense to me. Just a small comment on the test case introduced. |
|
@hieuddo would you mind running some benchmark to see if the improvement is significant? |
Replace the path-vs-path comparison with hand-computed HitRatio@2 and Recall@2 values for SPop on the toy split. Both the single-item path and the binary-mask path must give these values, with and without exclude_unknowns. The same values come from the master implementation. Co-Authored-By: Aiden <aiden@weco.ai>
|
Thanks for the review. I updated the test in 5b5ae2f, see the inline reply. For the benchmark: I ran the timing again on the current branch. The setup is two |
|
The 22% headline describes a ranking-loop improvement on a scaled SID-test-shaped workload with semantic metrics disabled. The referenced test uses predetermined scores rather than learned inference. To examine the improvement with neural inference included, we compared upstream against this PR using trained
¹ Median across nine runs. Models were fitted and prepared outside timing: model.fit(evaluator.train_set)
model.transform(evaluator.test_set)We then substituted each evaluator implementation and timed this call with sid-metrics b.evaluation.ranking_eval = FUNCTIONS[label] # upstream or PR
result = evaluator.eval(
model, evaluator.train_set, evaluator.test_set,
True, # exclude_unknowns
[b.Recall(10), b.NDCG(10)],
user_based=False,
mode="last",
semantic_id_metrics=enabled,
)Both versions used the same 440 candidates and 120 Yoochoose sessions. Outputs matched exactly. So in summary, I think the PR does remove genuine redundant code, particularly repeated candidate-array construction and mask processing. That cleanup is useful. However, these results suggest the large percentage primarily reflects overhead reduction around fixed scoring, rather than a comparable improvement when inference is involved. |
|
Thanks @hieuddo for the careful measurement with trained models. I agree with your reading. The change only removes per-query work around The headline number is too strong for real models. If you prefer, I can change the title to describe this as a cleanup that cuts evaluation overhead, without the 22% figure. Outputs stay identical either way. |
|
@dexhunter @hieuddo thanks both for the discussion. I think we should revise the headline with softer claim on the perf improvement. This is a good work and we should merge it. |
|
Thanks @qtuantruong. I changed the title and the TL;DR to use a softer claim.
The code is the same as in 5b5ae2f. Outputs stay identical. |
TL;DR
ranking_eval.NextItemEvaluation.evaluateuses it for every test session.pytest tests/cornac/eval_methods tests/cornac/modelspasses (107 passed, 1 skipped). Metric values are bit-for-bit identical.What & Why
Before, each query built two full-length integer masks (one entry per item). Then it called
np.nonzerothree times to get the positive items, the negative items and all candidate items. The candidate items are the same for every query. The loop also looked up the rank method, the test set attributes and theuser_basedbranch again for every query.Now the code builds
item_indices = np.arange(num_candidates)once. For the usual case of one positive item, it slices that array to get the positive and negative items. Other cases (for example several positives) still use the old mask code. The loop reads sessions directly fromtest_set.sessions, in the same order assi_iter. It also picks the rank method and the result container once.The arrays passed to
model.rankand to the metrics have the same values, order and dtype as before. So the scores, the warnings and the result dicts stay identical.Results
Median of 7 runs,
Recall@10andNDCG@10,exclude_unknowns=True, plainNextItemRecommenderwith fixed scores (except the first row).semantic_id_metricsoff, 440 items, 1200 sessions (test_semantic_views_require_explicit_opt_inshape, scaled up)mode="last"mode="last"mode="next"Why this is safe and easy to maintain
The change touches only
ranking_evaland keeps the old mask code as the general fallback. The new fast path is a short helper with a comment. Outputs are unchanged: metric values, numpy types, result containers and warnings match exactly.tests/cornac/eval_methodscovers thesemantic_id_metrics=True,exclude_unknowns=Falseandmode="next"paths.Validation
pytest tests/cornac/models/test_semantic_id_adapters.py tests/cornac/data/test_dataset.py tests/cornac/eval_methods/test_semantic_id_metrics.py tests/cornac/eval_methods/test_next_item_evaluation.py-> 96 passed, 1 skipped.pytest tests/cornac/eval_methods tests/cornac/models-> 107 passed, 1 skipped.OMP_NUM_THREADS=1).NextItemEvaluation.from_splits(train, test, fmt="USIT").evaluate(model, [Recall(10), NDCG(10)], user_based=False)before and after the patch on a 1200-session test split.Description
See the sections above. No public signature changes and no new dependencies.
Related Issues
None.
Checklist:
README.md(if you are adding a new model).examples/README.md(if you are adding a new example).datasets/README.md(if you are adding a new dataset).AI assistance: Codex (gpt-6.1-sol) generated the candidate changes. Weco Observe tracked them, and an automated harness checked each candidate for identical outputs and measured its speed.
Weco share link: https://dashboard.weco.ai/share/vG4ePh8hQlA092gOCUDxcR2_Ce0ea6D1