Train a recommender

Rate a few shows and watch matrix factorization learn a map of shows and of your taste. Then run a feed that only learns from what it chooses to show.

Train a recommender

Rate a few shows. The model learns a vector for every show and for you from everyone's ratings, then ranks the shows you have not rated.

  • Stand-up special
    Your rating for Stand-up special
  • Baking contest
    Your rating for Baking contest
  • Office sitcom
    Your rating for Office sitcom
  • Nature documentary
    Your rating for Nature documentary
  • True crime series
    Your rating for True crime series
  • War documentary
    Your rating for War documentary
  • Courtroom drama
    Your rating for Courtroom drama
  • Heist thriller
    Your rating for Heist thriller
  • Monster horror
    Your rating for Monster horror
  • Space war epic
    Your rating for Space war epic
  • Animated musical
    Your rating for Animated musical
  • Fairy-tale adventure
    Your rating for Fairy-tale adventure
1
Your ratings0 of 12
Top pick (as for any new viewer)n/a
Error on ratings it learned fromn/a stars
Error on held-out ratingsn/a stars
Trainingstarting

Challenges

0 of 2 complete

  1. With a single rating, make your top pick a show from the bottom half of the list a brand-new viewer gets. (not yet)
  2. Set the penalty to 0, rate exactly two shows, and make the model predict more than 7 stars (or less than -1) for a show you have not rated. (not yet)

Try this

  • Before rating anything, note your top pick: with no ratings you get the same list as every new viewer.
  • Rate one show 5 stars and watch your arrow appear. Which shows rose, and where are they on the map?
  • Press Train again from a new random start: the map turns or flips, your predictions stay put.
  • Set the penalty to 0 and rate two shows that sit close together on the map, one 5 stars and one 1 star.

A feed that learns from itself

Each day, 200 new viewers are each shown one show they have not seen, and rate it. Every night the model retrains on all the ratings it has.

  • The model's own pick
  • Random pick (exploring)

Exploring: how often to show a random show instead of the best guess
Show results through
Average stars viewers gaven/a
Top three shows gotn/a
New releasen/a
Shows recommended at least oncen/a

Challenges

0 of 2 complete

  1. Get the new release recommended by the model's own choice, not a random pick, to at least 20 viewers over the three days. (not yet)
  2. Explore so much that viewers rate what they are shown lower, on average, than with exploring off. (not yet)

Try this

  • With exploring off, check the new release’s row, then press New luck a few times.
  • Choose Day 1 to see what every brand-new viewer is shown.
  • Raise exploring step by step and watch the new release and the average stars.
Matrix factorization by gradient descent in NumPyPython with NumPy
import numpy as np

shows = ["Sitcom", "Stand-up", "Baking", "Horror", "Space epic"]
# Five viewers (rows) rate the shows (columns), 1 to 5 stars; 0 = not rated.
# Viewer 4 is brand new and has rated nothing.
R = np.array([[5, 4, 0, 1, 0],
              [4, 0, 5, 1, 1],
              [1, 1, 0, 5, 4],
              [0, 2, 1, 4, 0],
              [0, 0, 0, 0, 0]], dtype=float)
known = R > 0
mu = R[known].mean()                      # the average rating

rng = np.random.default_rng(0)
k, lam, lr = 2, 0.5, 0.02                 # factors, penalty, step size
P = rng.normal(0, 0.1, (5, k))            # one taste vector per viewer
Q = rng.normal(0, 0.1, (5, k))            # one vector per show
bu, bi = np.zeros(5), np.zeros(5)         # viewer and show offsets

for step in range(3000):
    pred = mu + bu[:, None] + bi[None, :] + P @ Q.T
    err = np.where(known, R - pred, 0.0)  # only known ratings count
    # One gradient step on squared error + lam * (every parameter squared).
    P, Q = (P - lr * (-2 * err @ Q + 2 * lam * P),
            Q - lr * (-2 * err.T @ P + 2 * lam * Q))
    bu -= lr * (-2 * err.sum(axis=1) + 2 * lam * bu)
    bi -= lr * (-2 * err.sum(axis=0) + 2 * lam * bi)

pred = mu + bu[:, None] + bi[None, :] + P @ Q.T
rmse = np.sqrt(np.mean((R - pred)[known] ** 2))
print(f"error on known ratings: {rmse:.2f} stars")
for v, j in [(0, 2), (0, 4), (3, 0), (3, 4)]:
    print(f"viewer {v}, {shows[j]}: predicted {pred[v, j]:.1f}")
print("new viewer's taste vector is zero:", bool(np.allclose(P[4], 0)))
print("so they get the crowd's order:", [shows[j] for j in np.argsort(-pred[4])])
error on known ratings: 0.20 stars
viewer 0, Baking: predicted 4.8
viewer 0, Space epic: predicted 1.6
viewer 3, Sitcom: predicted 1.9
viewer 3, Space epic: predicted 3.3
new viewer's taste vector is zero: True
so they get the crowd's order: ['Sitcom', 'Horror', 'Baking', 'Stand-up', 'Space epic']

The ratings in both demos are synthetic. Each show has two hidden traits (light or dark, real or made up) and a quality; each viewer has a taste for each trait and a generosity. A rating is 3.4 stars plus quality, plus generosity, plus 1.4 times the dot product of taste and traits, plus noise with a standard deviation of 0.4 stars, rounded to whole stars from 1 to 5. The model sees only the ratings.

Try "embedding", "softmax", "overfitting", or "backpropagation".