Cinemap · 2024–2026 Applied ML · recommender systems

A learned representation of film

Cinemap is a recommendation engine over 140,000+ films and shows. Each title is represented by five signal blocks: emotional affect, director style, personnel, microgenre, and cast career-trajectory. A contrastive metric-learning multi-layer perceptron fuses these into an 85-dimensional vector whose geometry reproduces an inferred human ground truth.

Role  Design · Engineering · ML pipeline / Platforms  iOS (React Native) · Web (Next.js) / Stack  Supabase · pgvector · Python
trycinemap.com/foryou
Cinemap web feed - a personalized, login-free For You feed
Cinemap mobile Discover tab
The problem

State-of-the-art recommenders optimise for predicting your next action - what you'll click, watch, or finish.

The engineering is excellent - collaborative filtering, two-tower retrieval, transformer sequence models over enormous interaction logs. But the objective is a proxy, and the proxy has drifted from the goal. Maximizing predicted engagement rewards habit and popularity, and converges on more of what you already do.

A recommender's job shouldn't be to predict what you will do next; it should be to help you discover what you should do next and help you get there.

Cinemap optimises for that directly: a content-first representation supervised by an inferred human ground truth of genuine similarity, so a recommendation targets what is worth watching, not what is most likely to be watched.

The prevailing objective

Predict the next action

Model the probability of your next click or watch and maximise it. Efficient at scale, but it optimises a proxy - engagement - that rewards popularity and habit over value.

objective: P(next engagement)
Cinemap's objective

Aid discovery

Model genuine similarity and quality from an inferred human ground truth, then guide the viewer toward it. The target is what someone should watch next, learned independently of behaviour.

objective: genuine similarity → discovery
The thesis

Every title becomes a point in a learned space.

Describe each film with five complementary signal blocks, then learn - not hand-tune - how to fuse them into a single 85-dimensional vector, supervised by an external human ground truth. Once every film is a point, each product surface is a geometric operation over that one space: browse is sampling, similarity is nearest-neighbours between films, and - because a viewer is placed in the same space (next) - personalization is retrieval between a user's vector and the films around it.

The representation · 5 blocks

What a film is

Five per-title signal blocks, each L2-normalised: emotional affect, director style, literal personnel, microgenre, and the career trajectory of the cast at the moment the film was made.

E·35 ⊕ D·35 ⊕ P·40 ⊕ G·50 ⊕ M·10 → 170-d
The supervision · inferred ground truth

What similar means

We don't hand-define similarity. We learn it from an inferred human ground truth - the implicit signal in how large communities co-curate films - that is independent of our own vectors. Contrastive metric learning pulls co-grouped titles together and pushes everything else apart.

InfoNCE · hard negatives · held-out ranking
embedding(title) = fθ( [ E·35 D·35 P·40 G·50 M·10 ] )    vector(85)  // trained to reproduce inferred human co-grouping · pgvector HNSW
The representation

Five signal blocks per title.

Every title is described by five independently-computed feature blocks. Each is L2-normalised, so it contributes a unit direction and the learned model is free to weight it; a title missing a block - an unknown director, absent cast data - simply falls back on the others.

E · 35-d

Emotional affect

LLM-scored

35 continuous feelings - tension, awe, dread, humour - a dense affective fingerprint.

D · 35-d

Director style

auteur + GAT

A director's emotional signature, graph-attention-enriched across kindred creators.

P · 40-d

Personnel

TF-IDF → SVD

Literal shared cast & crew identity, compressed to a dense collaboration vector.

G · 50-d

Microgenre

TF-IDF multi-hot

The scene a film belongs to - neo-noir, heist, elevated horror.

M · 10-d

Meta / stardom

career trajectory

Where each person was in their career at the release year - the rising-star signal.

[ E ⊕ D ⊕ P ⊕ G ⊕ M ] · 170-d
contrastive transform fθ
vector(85)
E · emotion

A language model scores 35 anchored affective dimensions per title, based on latent knowledge about each title as well as a corpus of text scraped from the internet about each title; the block is the unit-normalised score vector - the axis a genre label cannot express.

D · director style

Per director, a bias-corrected concentration of their films in emotion space - the auteur index - gates a leave-one-out centroid of their work. A graph-attention layer then blends in kindred directors, with attention weighted by similarity × distinctiveness, so a film inherits a whole stylistic family rather than one name.

P · personnel

A sparse title × person incidence over cast and crew, TF-IDF weighted so rare, distinctive collaborators count for more, then reduced by truncated SVD to a dense identity vector.

G · microgenre

A multi-hot over a fixed microgenre vocabulary, TF-IDF weighted so a distinctive tag outweighs a common one - and deliberately down-weighted during training so a literal premise never dominates tone.

M · meta / stardom

Each person sampled at the film's release year: a recency-weighted, billing-discounted popularity (stardom-at-the-time), a breakout signal (future minus past), career stage and experience - aggregated director vs cast, then standardised. This captures dynamics like whether a film features a breakout performance.

Progressive personalization

A viewer is a point in the same space as the films.

The films are points; a viewer is placed in that same 85-dimensional space, as a small set of taste vectors (centroids). Recommendation is then a single geometric operation - retrieve the film vectors nearest a user's taste vectors - and personalization is the progressive motion of those user points through the film cloud as a session accumulates evidence.

recommend(user) = argtopₖ  cos( user taste vector , film vector )  // same 85-d space as film–film similarity · then exploit ⊕ explore
signal
Explicit + implicit
Likes and saves move the user's points; dwell time and skips nudge them as a softer signal.
estimate
Taste centroids
k centroids over the interacted films' vectors - multi-modal taste, not one muddy average.
serve
Exploit + explore
~75% the film vectors nearest those centroids, quality- and diversity-ranked; ~25% exploration.
update
Read live
The feed re-reads the centroids each request, so it shifts within ~2s of an interaction.
Representation

Multi-modal taste

Several centroids let a viewer who likes both bleak thrillers and warm comedies keep both, rather than averaging into a midpoint between them that recommends neither.

Update rule

Recency-weighted, scroll-resistant

Explicit signals are weighted up to 4× by recency and trigger a debounced recompute from full history. Passive dwell folds in as a capped soft signal, so scrolling can never outvote a deliberate like.

Serving

Exploit / explore

Each page mixes proximity to the user's vectors with a fixed exploration quota, capped to one film per microgenre - enough novelty to keep the estimate improving without leaving the neighbourhood.

Reading the axes

The emotional fingerprint.

Nine of the 35 affective dimensions rendered as a radar: a per-title read-out of the emotional profile, and - with draggable vertices - a control that filters the catalog by feeling.

Emotion and microgenre are both axes of the same vector, so you can filter on their combinations (tense and heist; heartwarming and sci-fi; funny and horror). That is not keyword search: it is direct navigation of the embedding space, moving to a coordinate and pulling the titles around it.

Now showing: Inception

Romantic Funny Dark Epic Unpredictable Fantastical Tense Violent Scary
Surface 01 · iOS

Traverse the graph directly.

The surfaces let you move directly through the embedding space and pull its levers by hand.

Pinterest-style poster grids are graph traversal, each tap stepping to a neighbouring node; a TikTok-style trailer feed is discovery along your taste; and mood sliders, microgenres and seeds are direct controls on where in the space you sit.

Discover tab
Discover

Trailer-first For You

A full-bleed, auto-playing feed with the title's emotional radar overlaid and live mood chips.

Map the mood filter
Filter · vibe

Map the mood

Drag dots on the radar to filter by feeling - the affective axis turned into a direct-manipulation control.

Themes filter
Filter · themes

Microgenres

Curated tags - Cyberpunk, Home Invasion, '70s Paranoia - combined as OR sets and used as a diversity constraint.

Guided onboarding
Guided

Seed → similar

A wizard that turns "what do you wish you could watch for the first time?" into seed nodes for the engine.

Boards list
Boards

Editor & public lists

Curated and user lists - editorial collections sit beside anything the community makes public.

Board detail
Boards · detail

Make it personal

A masonry of posters with a human voice - “movies my friend Mike will enjoy over 4–5 Miller Lites.”

Movie detail with emotional profile
Title detail

Emotional profile

Ratings, streaming, trailer and the signature radar - the affective fingerprint made legible.

Streaming filter
Filter · streaming

Where you actually have it

Per-country availability with array-overlap filtering, so results respect the services you pay for.

← scroll · drag to explore the surfaces →

Surface 02 · web

The same space, login-free in the browser.

The web "For You" feed estimates a taste vector client-side from implicit signals - trailer dwell time and skips - with no account, then retrieves the film vectors nearest it: the user-to-film case of the same geometry the "More Like This" rail runs film-to-film. A progress indicator reflects the confidence of the session estimate as evidence accumulates.

Cinemap web feed showing Inception with a personalization progress bar and emotional radar
Cinemap web detail panel - ratings, streaming, and a More Like This rail powered by nearest-neighbor search
Detail · more like this

Open any title and the “More Like This” rail is pure geometric retrieval - approximate nearest neighbors in the 85-d space, oversampled and diversity-capped so a single prolific director can't dominate the results.

dwell ≥ 3s → seed dwell ≤ 1.5s → skip centroid → get_anon_recommendations
140k+
Titles in the space
85
Embedding dimensions
35
Affective dimensions
~20ms
Person traversal (was ~5s)
Thanks for scrolling

Designed, built & trained.

Cinemap is a solo project - the data pipeline, representation-learning model, backend, and both clients - developed since 2020. Always happy to chat about recommender systems, applied ML, or movies.