PaperLeet

BPR: Implicit Feedback and Personalized Pairwise Ranking

BPR · UAI 2009 · Medium · 30 minutes

Select unobserved items from complete user histories and train both factor tables with differentiable pairwise preferences.

Per-user candidate masks, stable BPR-MF negative log likelihood and full-table L2 prior; triple construction and tiny training loop are provided.

Functions to implement

BPR: Implicit Feedback and Personalized Pairwise Ranking

Learn a ranking for each user from observed-versus-unobserved preferences. This exercise selects the training triples and matrix-factorization objective from BPR Sections 3.2, 4.1 and 4.3.1.

1. Per-user negative candidates

eligible_negatives(interactions, user_ids) takes a Boolean [U,I] history and int64 [B] user IDs. Return Boolean [B,I]:

$$M_{b,j}=\neg S_{u_b,j}.$$

A True entry is unobserved by this user, making it an eligible comparison item rather than a known negative label. Exclude every item in that user's history. Preserve order and repeated users; another user's observed item remains eligible here. IDs are zero-based with no padding. Support empty batches, empty item axes, fully observed and entirely unseen users.

2. Pairwise matrix-factorization loss

Return requirements

eligible_negatives (required)

Return one Boolean Tensor [B,I] on the interaction device, where B is the number of requested user IDs and I the number of items. True means that item is unobserved by that row's user. Preserve requested order and duplicate users; empty batches/items retain their empty dimensions. No gradient or input mutation.

bpr_mf_loss (required)

Return one scalar floating Tensor, shape (), with the factor tables' dtype/device: mean pairwise loss over B triples plus the unaveraged, full-table L2 prior. Keep gradients to both user/item tables, including unselected rows in the prior. Empty triples contribute zero data loss while retaining both gradient graphs and the full prior. Do not detach, return a Python float or mutate inputs.

_check_mask (provided)

Provided validator: return Python None when interactions is Boolean [U,I] and same-device user_ids is int64 [B] with every ID in range. Raise ValueError when a checked condition fails; do not mutate inputs or return transformed tensors.

_check_loss (provided)

Provided validator: return Python None when finite factor tables [U,D]/[I,D], int64 triples [B,3], valid distinct positive/negative indices and nonnegative finite l2 satisfy the shared dtype/device contract. Raise ValueError when a checked condition fails; do not mutate inputs or return transformed tensors.

Formula and conventions

bpr_mf_loss(user_factors, item_factors, triples, l2=0.) takes matching float32/float64 [U,D] and [I,D] factor tables, plus int64 [B,3] rows (user, positive, unobserved), with D > 0. Triples are already constructed. Gather their factor rows and return the scalar:

$$ x_{uij}=w_u^\top(h_i-h_j), $$

$$ L=\frac{1}{\max(1,B)}\sum_{(u,i,j)}\operatorname{softplus}(-x_{uij}) +\lambda\left(\sum_{u,d}W_{u,d}^2+\sum_{i,d}H_{i,d}^2\right). $$

Use stable log-sigmoid/softplus rather than sigmoid followed by a direct log. Sum latent coordinates, average triples and retain all three selected-row gradient contributions. Repeated user/item IDs accumulate gradients. Preserve dtype, device, input values and support noncontiguous tables. Keep the supplied validation calls.

3. Global prior convention

l2 is finite and nonnegative. Penalize both complete tables, including rows not selected in this batch, exactly once with no additional factor of one half. Penalizing gathered vectors changes the objective when IDs repeat or are absent.

When l2=0, skip the prior calculation: squared norms of unused large factors can overflow and contaminate a finite likelihood with 0*Inf=NaN. Retain both full gradient tables through the data term.

The paper's BPR-Opt sums log likelihoods. This exercise normalizes the data term and exposes the prior coefficient at that scale: for a nonempty batch, l2 = lambda_paper / B corresponds to dividing the summed objective by B. An empty batch contributes zero likelihood while retaining the complete prior and both gradient graphs.

The pairwise score difference is essential. Two independent binary terms, as used in the SASRec exercise, define a different objective.

4. Verification

T0 checks shape, dtype, index validation, empty axes and unchanged inputs. T1 is hand-computable. T2 uses independent scalar likelihoods and central differences through both factor tables, repeated indices, gradients of unselected rows, batch replication, common item translation and extreme margins. Finite values and genuine gradients are required.

5. Actual training experiment

Three synthetic users like {0,1}, {2,3} and {4,5} respectively. Your mask supplies candidates for 24 training triples. Your objective trains all 27 factor entries for 120 actual Adam updates. The charts show its loss and personalized item scores.

Each cross-group item pair has opposing preferences from two users. A single global item-score order can satisfy at most half of these strict comparisons. The displayed 1/2 bound is counted from directions, not measured from a baseline model. Personalized factors must exceed it and recover all training preferences.

This is a PaperLeet teaching adaptation using full batches and one global prior coefficient. It does not reproduce LearnBPR's bootstrap SGD, separate role-specific regularizers, public datasets or the paper benchmarks. Accuracy and Recall@2 are training metrics, without held-out generalization claims.

6. Run your implementation

Complete the functions in the Python editor on the right, then click Run tests. Inspect each failed check, update your implementation, and run again. Passing every required check marks the problem as solved and unlocks the notes.

7. Sources and next steps

Read the original paper, publication record or arXiv archive. Compare with SASRec's sampled binary objective, ColBERT's paired softmax and TIGER's generated semantic IDs.

Read paper · arXiv:1205.2618

Related exercises