Reverie reads a viewer's watch history and predicts what they will enjoy watching next, across both movies and TV shows. It pairs two neural models with a cinematic web app:
- A sequential GRU (the course deliverable) that learns evolving taste from watch sequences and re-ranks the moment you reject a pick.
- A neural collaborative-filtering model (post-submission product work) trained on ~9.9M Letterboxd ratings, which fixes what the GRU could not do: recommend modern films it was never trained on, and slot a brand-new person in as a user.
Built for the IE University Deep Learning final project, then extended into a real product.
Team: Amaly Attia · Em Echeverria · Lea Sarouphim Hochar · Stephan Pentchev · Cecile Tambey
Problem. Streaming choice overload. Households now juggle four or more services and tens of thousands of titles, and the average viewer spends roughly 10 minutes per session just deciding what to watch (Nielsen), with many giving up without watching anything. For couples and groups the negotiation is worse. That wasted time is the user's pain; for platforms it surfaces as disengagement and churn.
Value proposition.
- Saves time. Reverie replaces ten minutes of scrolling with a ranked shortlist in under a second. Its high-confidence "you will love it" picks held a precision of 1.00 on held-out real viewers, trustworthy enough to act on immediately.
- Solves the group decision. The Blend intersects two people's recommendations so couples and friends skip the back and forth.
- Drives retention (platform framing). Personalization is the churn lever. Netflix has publicly attributed roughly $1B per year to its recommender through reduced churn; a single point of churn reduction on 1M subscribers at €15 per month is about €1.8M per year retained. Reverie is that engine, productized. (Industry figures, illustrative of the lever, not a measured claim about this model.)
Who it is for. Consumers drowning in streaming choice (the MVP here), and, as a B2B engine, any platform that lives or dies on retention.
- Sequential model (the GRU): a recurrent network trained on MovieLens watch sequences. It models taste as a sequence — what you watched recently predicts what you watch next far better than a static average profile.
- Collaborative model (NCF): user and movie embeddings plus bias terms and a
frozen content tower, trained on millions of Letterboxd ratings. It beats every naive
baseline globally and for 94% of individual users, and its high-confidence "you will
love it" picks held a precision of 1.00 across two real held-out viewers. See
notebooks/ncf_collaborative.ipynb. - Movies + TV: the GRU softmax head ranks movies directly; a derived, mean-centered taste vector scores any title (movie or TV) by genre similarity, bridging to a catalog it was never trained on.
- Learns from mistakes: a thumbs-down is appended to the live history as a low rating; the model re-runs and the recommendations re-rank instantly — no retraining.
- A real product app: accounts, a swipe-or-cards watch-history builder (save to a watchlist, rate what you have seen, the deck tunes to each pick), a template taste blurb, friends, and a "Blend" that intersects two people's recommendations.
See PROJECT_PLAN.md, ARCHITECTURE.md,
AUDIT.md, and EXPERIMENTS.md for the full design,
adversarial pre-build review, and experiment protocol.
The RNN beats every baseline with non-overlapping 95% confidence intervals and paired Wilcoxon p ≈ 0 — including item-kNN, the strong baseline shallow RNNs often lose to.
| Model | HR@10 | MRR |
|---|---|---|
| Reverie (GRU) | 0.257 | 0.120 |
| item-kNN | 0.076 | 0.035 |
| recent-genre pop | 0.045 | 0.023 |
| most-popular | 0.042 | 0.022 |
Feature ablation (E6): genre lifts HR@10 to 0.281; rating alone hurts HR@10 but the full hybrid (genre + rating) wins on MRR (0.1345) and is the chosen configuration — it also powers the app's thumbs-down feedback feature. Official test result (3 seeds): HR@10 = 0.242 ± 0.002.
The collaborative NCF model is a rating regression (1 to 10, loss MSE, reported as RMSE) trained on ~9.9M ratings. It beats all three naive baselines on the held-out test set, and beats the toughest per-movie baseline for the large majority of individual users.
| Predictor | Test RMSE |
|---|---|
| global-mean baseline | 2.072 |
| user-mean baseline | 1.963 |
| movie-mean baseline | 1.619 |
| neural net (NCF) | 1.372 |
Per user, the net beats the movie-mean baseline for 94% of viewers (median per-user
RMSE 1.320). Two real people were slotted in and their most recent films held out by
date: exact rating prediction for a brand-new, lightly-rated user sits near their personal
average (the cold-start floor), but the model's high-confidence "will they love it
(4+ stars)" picks had precision 1.00 for both, zero false alarms — the trustworthy
short list a recommender actually needs. Full writeup:
notebooks/ncf_collaborative.ipynb.
watch history ─▶ [movie emb ⊕ genre lookup ⊕ rating] ─▶ GRU ─▶ softmax (next movie)
│
└─▶ taste vector (content match → TV + movies)
Built with the audit's correctness contract: all-prefix training windows with leave-one-out evaluation, train-only statistics (no leakage), reserved padding index, explicit masking, and full-catalog ranking metrics with confidence intervals.
- Model / API: Python, TensorFlow/Keras, FastAPI, scikit-learn, pandas/numpy
- Frontend: React + Vite + TypeScript + Tailwind + shadcn/ui + recharts
src/ core library, grouped by model family
gru_model/ the primary sequence recommender
model.py GRU architecture (build_gru / build_hybrid_gru)
data_prep.py MovieLens clean/encode + sequence-building
train.py GRU training pipeline -> writes artifacts/
evaluate.py ranking metrics + bootstrap CIs
baselines.py popularity / recent-genre / item-kNN baselines
recommend.py inference seam (the only GRU serving path)
run_test.py official GRU test evaluation (frozen config, 3 seeds)
data_verify.py dataset sanity checks; track.py run logging
ncf_model/ collaborative baseline (user + movie embeddings + content tower)
model.py NCF architecture (build_ncf)
data.py loaders for the NCF artifacts
baselines.py global / movie / user mean baselines + RMSE
recommend.py serving seam: rank the modern catalog by your nearest favorites
catalog/ shared data + enrichment (used by app and scripts)
importers.py parse Letterboxd + Netflix, normalise/map titles
tv_catalog.py ingest TV catalog, build content embeddings
build_posters.py / build_providers.py TMDB poster + streaming-provider caches
scripts/ pipelines: build_ncf_dataset, train_ncf, eval_ncf, build_modern_catalog
app/ FastAPI service, layered:
api.py app factory + router wiring
routers/ auth, catalog (+ browse), history, watchlist, friends, blend
services/ recommend, friend, blend, taste, blurb, copy (business logic)
models.py db.py SQLAlchemy ORM (User, HistoryItem, WatchlistItem, Friendship) + session
enrich.py movieId -> title/genre/poster/rating (single authority; modern or ml-1m)
web/ React frontend (Vite + Tailwind + shadcn/ui)
notebooks/ temporal-validation + ncf_collaborative writeups
experiments/ one-off GRU research scripts (E0, E3, E4, E5, baselines, ablation) — done
presentation/ the final slide deck (reverie-deck.html, self-contained) + team PDF/PPTX
artifacts/ trained weights + encoders + NCF artifacts (generated locally, not committed)
data/ datasets (not committed — see below)
*.md plan, architecture, audit, experiment protocol
The final deck is a self-contained, animated HTML file: open
presentation/reverie-deck.html in a browser, press
f for fullscreen, and use the arrow keys. It explains both models with the course
concepts, a full system architecture diagram, and the real result graphs. Print to PDF
(Cmd+P) for a static copy, or screen-record / export the animations to GIF for tools
like Canva.
No datasets are committed. The training data is public; personal demo data is private and intentionally excluded.
-
Training (primary): MovieLens
ml-1m— 1M ratings, 6,040 users.curl -O https://files.grouplens.org/datasets/movielens/ml-1m.zip unzip ml-1m.zip -d data/
-
TV catalog: an IMDb TV-shows CSV (title, genres, description) in
data/. -
Personal demo (optional): a user's own Letterboxd / Netflix export. These contain private information and are never committed (see
.gitignore).
The app serves a modern catalog (post-2000 films) from the collaborative model by
default (REVERIE_CATALOG_MODE=modern). Build it once from the Letterboxd metadata and
the trained NCF artifacts:
python -m scripts.build_modern_catalog --movies ~/Downloads/movie_data.csv \
--ncf artifacts/ncf --limit 8000 --min-votes 50 --out artifacts/modernPosters come straight from the dataset (Letterboxd CDN), so no TMDB key is needed. Set
REVERIE_CATALOG_MODE=ml1m to fall back to the original MovieLens GRU demo. Switching
modes changes the movie id space (tmdb ids vs MovieLens ids), so start a fresh reverie.db
when you switch.
# Backend (Python 3.11; matches the course conda env)
pip install -r requirements.txt
# 1. Train the model -> writes artifacts/
python -m src.gru_model.train
# 2. Start the API (Terminal 2)
uvicorn app.api:app --port 8000Terminal 3 - Frontend (PowerShell):
# 3. Frontend
cd web
npm install
npm run devThen open http://localhost:5173
No need to retrain or reinstall. Just start the API and frontend in two terminals:
PowerShell:
# Terminal 1 - API (activate venv first, then run)
.venv\Scripts\Activate.ps1
uvicorn app.api:app --port 8000# Terminal 2 - Frontend
cd web
npm run devBash (Git Bash / Mac / Linux):
# Terminal 1 - API (activate venv first, then run)
source .venv/Scripts/activate
uvicorn app.api:app --port 8000# Terminal 2 - Frontend
cd web && npm run devThen open http://localhost:5173
Things explored or considered during the project but not implemented — good starting points if the project is extended beyond the course:
- ml-latest-small comparison (E1): we chose ml-1m from the start based on size (1M ratings vs 100K) and never ran a formal comparison. ml-latest-small is too sparse for a reliable softmax over 9,700 items — the decision was clear enough not to need an experiment.
- Logit adjustment (popularity-bias correction): subtract the log train-prior from the softmax logits to stop the model over-recommending blockbusters. The current model already beats all baselines without it, but this would be the next thing to try if diversity of recommendations matters more than raw HR@10.
- Multi-layer GRU: stack a second GRU layer on top of the first. Not done because a single layer was sufficient on this dataset size (6K users) and adding layers risks overfitting.
- GPU training via WSL2: the current setup trains on CPU (TensorFlow dropped native Windows GPU support after v2.10). WSL2 with CUDA would cut training time from ~13s/epoch to ~2s/epoch on the same machine.
- Transformer-based recommender (e.g. SASRec): attention-based sequential model that outperforms GRU on very large datasets. Overkill for 1M ratings but worth exploring at scale.
Personal streaming exports include billing, payment, IP, account, and device records.
Reverie only ever reads viewing/ratings files, and .gitignore blocks all personal and
sensitive data from this public repository.
Reverie is built on public data, with thanks to:
- MovieLens (GroupLens, University of Minnesota) — the
ml-1mratings used to train the sequential GRU model. - The Movie Database (TMDB) — posters, backdrops, genres, ratings, and streaming availability via the TMDB API. This product uses the TMDB API but is not endorsed or certified by TMDB.
- samlearner, "Letterboxd Movie Ratings Data" (Kaggle) — the multi-user Letterboxd ratings used for the collaborative recommender.
- gsimonx37, "Letterboxd" (Kaggle) — Letterboxd movie metadata (cast, crew, themes, posters) used for the modern catalog and content features.
Personal watch histories (Letterboxd / Netflix exports) are never committed and are used only locally.