FWPRec Method
Experiments
The locked model · Table 2

How the model works

Two parts. An anchor holds your long-term taste. A notepad holds short-term corrections from your last 20 events. The final “you” is the anchor after the notepad has adjusted it, and every candidate is scored against it.

83,330Trainable numbers: four small write networks
20Recent events written into the notepad
2 × 64×64Notepads: likes M⁺ and dislikes M⁻

1 · Split the history

Everything before the last 20 events feeds the anchor, and only the likes count there. The last 20 events are written into the notepad, oldest first. Any click is either anchor or notepad, never both.

older events → anchor only the likes are averaged dislikes here are ignored last 20 events → notepad written one by one, oldest → newest time like dislike / skip

2 · The anchor

The anchor is the average of the older liked items, each one a frozen 64-number Qwen vector, scaled to length 1. Nothing in it is trained. If there are no older likes, it uses the likes among the last 20. If there are no likes at all, it is zero.

older dislike: ignored older likes → anchor u₀ average of likes, scaled to length 1 0 trained numbers

3 · What each event writes: k, c, β

For every event in the window, three small trained networks read the same input: (anchor, item, feedback). Each produces one piece of the write.

input x = [ anchor u₀ · item v · feedback r ] key k “which part of your taste?” anchor nudge k = Norm(anchor + nudge) value c “what should it point to now?” item nudge c = Norm(item + nudge) strength β “how hard to write, 0 to 1” like: β⁺ → M⁺ β⁻ = 0 dislike: β⁺ = 0 β⁻ → M⁻ β = gate(x) × feedback sign

4 · One write: fade, check, correct

Both notepads start blank (all zeros) on every request. Each of the 20 events then updates them in three moves: M ← ρ·M + β·(c − M·k)·kᵀ.

1. Fade multiply everything by ρ → old notes get fainter; happens on every event, even when β = 0 2. Check what does it already say for k? · k = M·k (says now) c (should say) the gap c − M·k is the surprise 3. Correct write only the gap, scaled by β + β·(c − M·k)·kᵀ already known → tiny write surprise → big write

5 · Read and score

After the last write, both notepads are applied to the anchor. The likes pad pulls “you” toward recent likes, and the dislikes pad pushes “you” away from recent dislikes. α (0.3 to 1.0) sets how hard dislikes push. Each candidate’s score is its dot product with the final you.

anchor + M⁺·anchor − α·M⁻·anchor final you scaled to length 1 score = final you · candidate matches recent likes old favourite unrelated like recent dislikes 0

6 · Walk-through: one user, 20 events

A toy user who has been a sport fan (the anchor) and lately keeps liking politics and skipping sport. These charts use the model’s exact update and read equations. The three trained networks are replaced by fixed stand-ins (k = Norm(anchor + 0.9·item), c = item, β = 0.8), so the numbers are illustrative, not measured.

  1. Surprises write big, repeats write small. The first politics like writes 0.80, while later politics likes write about 0.3, because the notepad already knows them. A first film like (C+) is new again, so it writes 0.91.
0.000.250.500.751.00event (topic, like + / dislike −)write sizeEvent 1: politics like → write size 0.80B+Event 2: politics like → write size 0.38B+Event 3: sport skip/dislike → write size 0.80A−Event 4: politics like → write size 0.24B+Event 5: film like → write size 0.91C+Event 6: politics like → write size 0.55B+Event 7: sport skip/dislike → write size 0.39A−Event 8: politics like → write size 0.33B+Event 9: politics like → write size 0.33B+Event 10: film like → write size 0.75C+Event 11: sport skip/dislike → write size 0.36A−Event 12: politics like → write size 0.48B+Event 13: politics like → write size 0.37B+Event 14: politics like → write size 0.31B+Event 15: sport like → write size 0.85A+Event 16: politics like → write size 0.57B+Event 17: sport skip/dislike → write size 0.34A−Event 18: politics like → write size 0.31B+Event 19: politics like → write size 0.28B+Event 20: politics like → write size 0.32B+like → M⁺dislike → M⁻A sportB politicsC film
  1. The ranking moves. With the anchor alone, the sport article leads by 0.62. As politics likes and sport skips are written, politics climbs (0.26 → 0.61) and sport sinks (0.88 → 0.65), and politics leads for much of the window. The single sport like at event 15 swings it back, which is why they finish nearly level.
0.250.500.751.00after event … (0 = anchor only)candidate score05101520politics article (liked lately), after event 0: score 0.259politics article (liked lately), after event 1: score 0.643politics article (liked lately), after event 2: score 0.674politics article (liked lately), after event 3: score 0.732politics article (liked lately), after event 4: score 0.775politics article (liked lately), after event 5: score 0.426politics article (liked lately), after event 6: score 0.640politics article (liked lately), after event 7: score 0.636politics article (liked lately), after event 8: score 0.697politics article (liked lately), after event 9: score 0.745politics article (liked lately), after event 10: score 0.400politics article (liked lately), after event 11: score 0.383politics article (liked lately), after event 12: score 0.567politics article (liked lately), after event 13: score 0.637politics article (liked lately), after event 14: score 0.631politics article (liked lately), after event 15: score 0.292politics article (liked lately), after event 16: score 0.509politics article (liked lately), after event 17: score 0.544politics article (liked lately), after event 18: score 0.582politics article (liked lately), after event 19: score 0.629politics article (liked lately), after event 20: score 0.608politics article+0.26 → +0.61sport article (skipped lately), after event 0: score 0.876sport article (skipped lately), after event 1: score 0.711sport article (skipped lately), after event 2: score 0.670sport article (skipped lately), after event 3: score 0.527sport article (skipped lately), after event 4: score 0.516sport article (skipped lately), after event 5: score 0.502sport article (skipped lately), after event 6: score 0.465sport article (skipped lately), after event 7: score 0.388sport article (skipped lately), after event 8: score 0.472sport article (skipped lately), after event 9: score 0.404sport article (skipped lately), after event 10: score 0.525sport article (skipped lately), after event 11: score 0.510sport article (skipped lately), after event 12: score 0.498sport article (skipped lately), after event 13: score 0.499sport article (skipped lately), after event 14: score 0.590sport article (skipped lately), after event 15: score 0.891sport article (skipped lately), after event 16: score 0.760sport article (skipped lately), after event 17: score 0.706sport article (skipped lately), after event 18: score 0.633sport article (skipped lately), after event 19: score 0.617sport article (skipped lately), after event 20: score 0.647sport article+0.88 → +0.65

Hover a bar or point for its value. Toy settings: 16-number vectors, ρ = 0.95, α = 0.5. Generated by experiments/isolated/site_figures/how_it_works_charts.py.

7 · How far back the notepad remembers

Fade alone decides how much of an event’s note survives until read time: ρ raised to the number of later events. This chart is exact, not a toy. The screens picked ρ = 0.9 on Amazon, ML-1M and KuaiRec, so the oldest of the 20 events keeps only about 14%. They picked ρ = 1.0 on MIND-large, EB-NeRD and KuaiRand, so nothing fades and only later corrections overwrite old notes.

0.000.250.500.751.00event position in the 20-event windowshare of note leftoldest51015newestρ = 1.0 (no fade), position 1: 1.000 of the note leftρ = 1.0 (no fade), position 2: 1.000 of the note leftρ = 1.0 (no fade), position 3: 1.000 of the note leftρ = 1.0 (no fade), position 4: 1.000 of the note leftρ = 1.0 (no fade), position 5: 1.000 of the note leftρ = 1.0 (no fade), position 6: 1.000 of the note leftρ = 1.0 (no fade), position 7: 1.000 of the note leftρ = 1.0 (no fade), position 8: 1.000 of the note leftρ = 1.0 (no fade), position 9: 1.000 of the note leftρ = 1.0 (no fade), position 10: 1.000 of the note leftρ = 1.0 (no fade), position 11: 1.000 of the note leftρ = 1.0 (no fade), position 12: 1.000 of the note leftρ = 1.0 (no fade), position 13: 1.000 of the note leftρ = 1.0 (no fade), position 14: 1.000 of the note leftρ = 1.0 (no fade), position 15: 1.000 of the note leftρ = 1.0 (no fade), position 16: 1.000 of the note leftρ = 1.0 (no fade), position 17: 1.000 of the note leftρ = 1.0 (no fade), position 18: 1.000 of the note leftρ = 1.0 (no fade), position 19: 1.000 of the note leftρ = 1.0 (no fade), position 20: 1.000 of the note leftρ = 1.0 (no fade)ρ = 0.9, position 1: 0.135 of the note leftρ = 0.9, position 2: 0.150 of the note leftρ = 0.9, position 3: 0.167 of the note leftρ = 0.9, position 4: 0.185 of the note leftρ = 0.9, position 5: 0.206 of the note leftρ = 0.9, position 6: 0.229 of the note leftρ = 0.9, position 7: 0.254 of the note leftρ = 0.9, position 8: 0.282 of the note leftρ = 0.9, position 9: 0.314 of the note leftρ = 0.9, position 10: 0.349 of the note leftρ = 0.9, position 11: 0.387 of the note leftρ = 0.9, position 12: 0.430 of the note leftρ = 0.9, position 13: 0.478 of the note leftρ = 0.9, position 14: 0.531 of the note leftρ = 0.9, position 15: 0.590 of the note leftρ = 0.9, position 16: 0.656 of the note leftρ = 0.9, position 17: 0.729 of the note leftρ = 0.9, position 18: 0.810 of the note leftρ = 0.9, position 19: 0.900 of the note leftρ = 0.9, position 20: 1.000 of the note leftρ = 0.9: oldest event keeps 0.14

Pieces

PieceWhat it isTrained?
Item vectorsFrozen 64-number Qwen3 embeddingsNo
Anchor u₀Average of older liked itemsNo
Notepads M⁺, M⁻Two 64×64 matrices, rebuilt from blank on every requestNo (working state)
Key, value, two strength networksTurn (anchor, item, feedback) into k, c, β⁺, β⁻Yes: 83,330 numbers
ρ, αFade rate and dislike push, chosen per dataset on validationNo (screened)

This page describes the locked paper-literal model (experiments/isolated/fwprec_paper_literal/paper_model.py) that produced the FWPRec row of Table 2. The notepad keeps nothing between requests: on the next request the window slides by one, the anchor is recomputed and the notepad is rebuilt. An earlier variant that kept the notepad across visits is a separate run: keep the pad. How the anchor and the notepad move over time is on The idea.