Score every user against every item
Medium12 min11 hidden tests
A shopping app's recommender keeps an embedding for every user and every item, plus one bias per item for how popular it is overall. The score of a user for an item is the dot product of their embeddings plus that item's bias. Write score_items(users, items, item_bias) that scores a whole batch with one matrix product. Mismatched shapes are a common silent bug here, so check them first and return None when they do not fit.
Drag the users
scores = users @ items.T + item_bias| item 0 +0.5 | item 1 +0 | item 2 −1 | |
|---|---|---|---|
| user 0 | 3.5 | 0 | 1 |
| user 1 | 2.5 | 2 | 3 |
Shapes: users (2, 2) @ items.T (2, 3) + bias (3,) gives (2, 3). User 0 and item 0: 1 × 3 + 0 × 1 = 3, plus bias 0.5 = 3.5. Point a user toward an item and their score climbs.
User 0, item 0 score 3.5.
Examples
Two users and three items give a (2, 3) table
score_items([[1, 0], [0, 2]], [[3, 1], [0, 1], [2, 2]], [0.5, 0.0, -1.0])
returns [[3.5, 0, 1], [2.5, 2, 3]]
Users have 3 numbers and items have 2, so the shapes do not fit
score_items([[1, 2, 3]], [[1, 1], [2, 2]], [0.0, 0.0])
returns None
Requirements
usershas shape (u, d),itemshas shape (m, e), anditem_biasis a list or 1-D array of length k.- When d equals e and k equals m, return a float array of shape (u, m). Entry [i, j] is user i's embedding dotted with item j's, plus
item_bias[j]. - Otherwise return
None. A bias of length 1 is a mismatch whenever m is larger than 1, even though NumPy would broadcast it without complaint. - Compute the scores with one matrix product and broadcasting, without Python loops.
The idea behind it
Many recommenders score with dot products. Matrix factorization and two-tower models both learn a vector for each user and each item, and a user's predicted interest in an item is their dot product. A per-item bias adds how popular the item is overall.
Scoring one pair at a time in Python is slow. Stack the users into a (u, d) matrix and the items into an (m, d) matrix, and one product, U @ V.T, gives the whole (u, m) table. Matrix products are what CPUs and GPUs do fastest.
Broadcasting then adds the (m,) bias to every row. That same rule is a trap. NumPy stretches any axis of length 1, so a bias of length 1 silently gets added to every item, and a model wired up with the wrong tensor still runs and returns numbers. Checking shapes at the boundary turns that kind of bug into a clear failure. Production code would raise a ValueError; here the function returns None so the tests can check it.