search_ads_recsys / 13 · user-behavior sequences lesson 13 / 39

User-behavior sequence modeling

A recommender never sees a user's mind — only their click stream. Every label it trains on, every interest vector it builds, is reconstructed from behavior the system itself shaped. This lesson follows one click from screen to gradient: how a tap becomes a label, why that label is biased before it is recorded, and how an ordered list of behaviors becomes the user vector that the ranker actually scores.

The premise of the whole lesson
The user is a latent variable. Their true preference is unobservable; you get noisy, biased, partial samples of it — the things they did, on the items you happened to show, in the order you showed them. Treating observed behavior as if it were ground-truth preference is the single most expensive mistake in applied recommendation. Most of this lesson is the gap between the two.

Two kinds of feedback, and why neither is the truth

Behavior splits into explicit feedback — actions whose purpose is to express preference (a like, a rating, a "not interested" tap, a follow) — and implicit feedback — the exhaust of normal use that we interpret as preference (a click, watch-time, dwell, a scroll-past, a replay). The trade is the eternal one between volume and clarity.

Explicit feedback is rare but high-precision: a 5-star rating means something. Implicit feedback is abundant — typically 100–1000× more of it — but every signal is contaminated. A 6-minute watch might mean fascination or a phone left face-up on a couch. A skip might mean dislike, "already seen it," or a passing distraction. Behavior modeling assigns each signal a strength and a noise level, then lets abundance compensate for ambiguity.

SignalTypePref. strengthVolumeDominant noise
Share / forwardexplicitvery hightinysocial signalling, not personal taste
Follow / subscribeexplicithightinyone-time, slow to decay
Like / favoriteexplicithighsmallunder-reported — most likers never tap
Completion / replayimplicitmedium–highlargelength-confounded; short clips auto-complete
Watch-time / dwellimplicitmediumlargeidle screen, autoplay; time ≠ attention
Click / openimplicitlow–mediumvery largeclickbait, mis-tap, curiosity
Skip / fast-scrollimplicit (neg.)weak-negativevery large"seen it", passive scroll — not dislike
"Not interested" / reportexplicit (neg.)very high-negativetinyalmost none — trust it
Watch-time is not attention, and completion is not love
Two confounders bite hardest. Length confounding: a 6-second clip completes by accident; a 6-minute one almost never does — so raw completion rate ranks short content artificially high. The fix is to model completion conditional on length (watch-time percentile within a duration bucket). Idle time: dwell counts a phone left on the table as deep engagement. Production systems gate watch-time on heartbeats and active-screen signals before it ever becomes a label. The same trap reappears as a ranking objective in lesson 12: optimize raw watch-time and you train for the couch.

How a click becomes a label — and why it is biased at birth

The flywheel is seductive: show items, log clicks, train on clicks, show better items. The hidden danger is in the very first arrow. The model trains on (item, clicked?) pairs — but the items in that log are not a random sample of the corpus. They are exactly the items a previous model chose to show, placed in positions the UI chose. The label is corrupted by the system that produced it, in three compounding ways.

The clean way to think about position bias is a factorization. A click happens only if the user examined the slot and found the item relevant — the examination hypothesis:

P(click | item, rank) = P(examine | rank) · P(relevant | item)

The first factor is the position propensity p_r — pure UI artifact. The second is what you actually want. As a rough calibration, a typical feed sees p_1 ≈ 1.0, p_3 ≈ 0.5, p_10 ≈ 0.2 — so an item at slot 10 needs to be ~5× more relevant to earn the same observed click rate as slot 1. If you can estimate p_r (by randomizing positions in a small traffic slice, or by "swap" experiments), you recover relevance two ways:

This is the same debiasing machinery as lesson 07. The mental shift is the whole game: a click is an observation of a biased process, not a measurement of preference. Model the process.

The one-line summary you should be able to give
Behavior is the only window into a latent user, and the window is dirty: implicit signals are noisy, explicit ones are sparse, and the label itself is generated by the very system you're training. Before you model the sequence, debias the signal.

From a sequence of behaviors to an interest vector

A single click is a weak signal; an ordered sequence of clicks is a trajectory through interest space. Sequence modeling turns a user's recent interactions — [v₁, v₂, …, v_n], each item an embedding plus side-features (category, dwell, timestamp) — into one user vector, then matches it against a candidate. Four generations of mechanism, each fixing the one before:

MechanismHow it summarizes the sequenceWins / costs
Pooling (avg / sum)mean of item embeddings — order-blindcheap, O(n); "average of everything" washes out a focused interest
RNN / GRUrecurrent hidden state, recency-weightedcaptures order & short-term drift; sequential → hard to parallelize, weak on long range
Target-attention (DIN)weight each past item by relevance to the candidateactivates only the history that matters for this item; O(n) per candidate
Self-attention (Transformer / BST)every item attends to every otherlong-range, parallel; O(n²), latency-heavy at serve time
DIEN (GRU + attention + AUGRU)extract interest, then model its evolution toward the candidatecaptures drift over time; most complex of the lineage

DIN — target attention over the behavior sequence

A user who watches cooking, gaming, and travel videos has no single "interest." Pooling produces a muddy average that matches nothing well. DIN's (Deep Interest Network, Zhou et al. 2018) insight: the relevant slice of history depends on what you're scoring. When ranking a recipe, attend to the cooking clips and ignore the rest; when ranking a game trailer, do the reverse. The user vector becomes a candidate-conditional weighted sum of the history:

u(cand) = Σᵢ αᵢ · vᵢ ,   αᵢ = softmax( a(vᵢ, v_cand) )

where a(·,·) is a small MLP — the "activation unit" — fed [vᵢ, v_cand, vᵢ − v_cand, vᵢ ⊙ v_cand]. The explicit difference and product terms hand the unit the cross features instead of forcing it to learn subtraction. Note that u changes per candidate: the same history yields a different user vector for every item scored. (DIN's original paper softmaxes without normalization to preserve interest intensity — a user with 20 cooking clips should activate harder than one with 2 — a detail worth naming in an interview.)

DIEN — modeling how interest evolves

DIEN (Deep Interest Evolution Network, Zhou et al. 2019) adds time. DIN attends over raw clicked-item embeddings; DIEN first threads a GRU through the history to extract an interest state at each step (the "interest extractor," supervised by an auxiliary loss that predicts the next behavior), then runs a second AUGRU — a GRU whose update gate is scaled by the target-attention weight — to model how interest grows and fades toward the candidate. A fading interest contributes less than a surging one. The cost is the GRU's sequential dependency: it does not parallelize the way self-attention does, which is why high-QPS systems often prefer DIN or a Transformer block (BST) with a hard length cap.

Both DIN and DIEN live inside the ranker from lesson 04 — they are the sub-network that consumes the "sparse multi-valued" user-history feature and emits a pooled vector that concatenates with the dense and sparse-ID embeddings before the top MLP.

Lifelong sequences — why a flat attention over 10k events is dead on arrival

DIN, DIEN, and a BST-style Transformer all share an unspoken assumption: the history is short — last 50, maybe a few hundred events. That cap is not a stylistic choice; it is forced by the serving budget. The deeper the user, the more this hurts: the behavior that explains this click — a phone bought eleven months ago, a recipe saved last spring — is often thousands of events back, exactly where the length cap throws it away. So the question that dominates senior interviews: how do you attend over a lifelong sequence of 10,000+ behaviors?

Start with why you cannot just raise the cap. Walk the cost of a single ranking request, batch of B = 1000 candidates, history length n, embedding dim d = 64:

ApproachPer-candidate costn = 100n = 10,000
DIN target-attentionO(n·d) — one activation MLP per past item100 · 64 ≈ 6.4k MAC10,000 · 64 ≈ 640k MAC · 1000 cand = 6.4×10⁸ per request
BST self-attentionO(n²·d) — every item attends to every other100² · 64 ≈ 6.4×10⁵10,000² · 64 ≈ 6.4×10⁹ — ~10⁴× worse, before the ×1000 candidates

The self-attention row is the killer: means going from 100 → 10,000 events is not 100× the cost, it is 100² = 10,000×. Even the linear DIN row, multiplied across 1000 candidates, blows a 50 ms budget. And it is wasteful: of those 10,000 events, maybe 200 have anything to do with the candidate. The fix in every production answer is the same shape — retrieve first, attend second. Three named architectures, each a different bet on where you pay the cost.

SIM — two-stage GSU → ESU (retrieve, then attend)

SIM (Search-based Interest Model, Pi et al. 2020) splits the problem into a cheap General Search Unit (GSU) and an expensive Exact Search Unit (ESU). The GSU pre-filters the lifelong sequence down to the few-hundred behaviors relevant to the candidate; the ESU then runs full DIN-style target attention over only that subset. Two flavors of GSU:

Worked saving. Lifelong n = 10,000, candidate category holds 250 of them, K = 200 retained. The ESU now runs DIN over 200 items, not 10,000: 200 · 64 ≈ 1.3×10⁴ MAC per candidate vs 6.4×10⁵ for flat DIN — a ~50× cut — and the GSU cost is a hash lookup, not a matrix multiply. The bet SIM makes: the candidate's category is a good enough filter that the dropped 9,750 events held no signal for this item. Usually true; the failure mode is a cross-category interest (cooking history predicting a kitchen-gadget purchase) that hard search misses and only soft search recovers.

MIMN — fold the sequence into a fixed memory, so serving cost stops growing

SIM still stores the full lifelong sequence and searches it per request. MIMN (Multi-channel user Interest Memory Network, Pi et al. 2019) makes a different bet: never store the raw sequence at serve time at all. It keeps a small fixed-size memory matrix M — say m = 4 slots × d = 64, one "channel" per coarse interest — and a Neural-Turing-Machine controller that incrementally writes each new behavior into the slots as it streams in:

Mₜ = (1 − wₜ ⊗ eₜ) ⊙ Mₜ₋₁ + wₜ ⊗ aₜ

where wₜ ∈ ℝm is a soft write-address over the m slots, eₜ and aₜ ∈ ℝd are the erase and add vectors over the d feature dimensions, and ⊗ is the outer product — so each of wₜ ⊗ eₜ and wₜ ⊗ aₜ is an m×d matrix shaped exactly like M (the address selects the slot, the vector fills its row), making the update well-typed. The point is the dimensions: M is 4×64 = 256 numbers no matter whether the user has 100 lifelong events or 100,000. Serving reads a fixed 256-float state — cost decoupled from sequence length entirely. The write happens once per behavior in the streaming pipeline (below), never at request time. The trade is lossy compression: four slots cannot hold the fidelity of 10,000 raw events, so MIMN is paired with a separate short-term DIN tower for recent precision — memory for the lifelong gist, attention for the fresh detail.

DSIN — the sessionization the lesson described, made concrete

Later this lesson sessionizes the stream conceptually. DSIN (Deep Session Interest Network, Feng et al. 2019) is the architecture: cut the sequence into sessions at the ~30-min gap, then process within and across sessions separately.

The cost discipline is the same retrieve-then-attend idea wearing different clothes: self-attention is confined to within a session (small k), and the expensive cross-session modeling runs over a handful of session vectors, not thousands of raw events.

ModelCore trickServing cost scales withBest when
SIMGSU hard/soft retrieve → ESU attentionK retained (~hundreds), not nlifelong sequence, candidate-category filter is informative
MIMNNTM memory, incremental writefixed memory (m·d), independent of nextreme length + tight serve budget; pair with short-term tower
DSINintra-session self-attn + inter-session Bi-LSTM#sessions × session lengthstrongly session-structured behavior (shopping, search)
The one sentence that answers "how do you handle a 10k history?"
Flat attention is O(n) (DIN) or O(n²) (self-attn) per candidate, so it dies past a few hundred events. Retrieve, then attend: SIM hard-searches the candidate's category out of the lifelong log (10k → ~hundreds) then runs DIN on that; MIMN folds the whole sequence into a fixed-size memory so serving cost stops depending on length; DSIN splits into sessions and confines the quadratic cost to one short session. All three move the heavy lifting off the per-request path.

Short- vs long-term interest — two clocks

Behavior carries two timescales that fight each other. Long-term interest is stable taste: someone is, over years, "into" sci-fi and indie music. Short-term interest is the session's intent: tonight they're shopping for a tent, and every recommendation should bend toward camping for twenty minutes — then snap back. Collapse the two and you get a recommender that is either too sluggish to react or too jittery to be coherent.

The standard architecture keeps two encoders: a long-term tower over months of history (heavier, recomputed offline / daily) and a short-term tower over the last N events (recomputed in near-real-time, seconds-fresh), fused by a learned gate:

u = σ(g) ⊙ u_long + (1 − σ(g)) ⊙ u_short

The gate σ(g) depends on context — when the session signal is strong and decisive (a burst of camping clicks), the gate shifts weight to u_short. This is the offline / nearline / online split: the long tower can be a cached two-tower user embedding refreshed nightly; the short tower must run on the freshest event stream.

Time decay: making "recent" precise

Inside both the gate and the attention, recency is encoded by a decay weight on each past behavior. The workhorse is exponential decay on the gap Δt since the action:

wᵢ = w_base(actionᵢ) · e^(−λ Δtᵢ)

Two knobs do the work. w_base encodes signal strength — a share weighs more than a click (e.g. share 5, like 3, watch 1). λ sets the half-life: t½ = ln 2 / λ. A short-video feed uses a fast decay (interest turns over in hours); a movie service uses a slow one (taste persists for months). Choosing λ is choosing how long the past gets to vote — one of the most impactful, least-glamorous hyperparameters in the system.

Work the numbers. Pick a 48-hour half-life, so λ = ln 2 / 48 ≈ 0.01444 per hour. A behavior from Δt = 24 h ago keeps e^(−0.01444·24) = e^(−0.347) ≈ 0.71 of its weight; from 72 h ago, e^(−0.01444·72) = e^(−1.04) ≈ 0.35; from one week (168 h) ago, e^(−2.42) ≈ 0.089 — under a tenth, effectively forgotten. Halve the half-life to 24 h (λ ≈ 0.0289) and that same 72-hour behavior drops to e^(−2.08) ≈ 0.125 instead of 0.35 — the feed reacts roughly 3× faster to fresh intent but throws away more of yesterday. That single knob is the dial between "jittery" and "sluggish."

Scalar decay vs learned time embeddings (Time2Vec)

Exponential decay imposes one assumption: forgetting is monotonic — older always matters less. That is wrong for any periodic behavior. A user who opens a cooking feed every Sunday morning has a strong signal at Δt = 7 days that a decay curve actively suppresses, because by then e^(−λ·168h) has crushed it. The fix is to stop hand-coding the time response and let the model learn it.

The trade is the usual one: scalar decay is one hyperparameter, robust, and free; Time2Vec adds parameters and needs enough data per user to learn a stable periodicity, so it earns its keep on heavy users and over-fits on sparse ones. Many systems use both — decay as a feature and a small learned time embedding — and let the attention weigh them.

Behavior denoising — what reaches the sequence in the first place

Everything above assumes the sequence is clean. It is not. A real click stream is laced with mis-taps, inertial fast-scrolls, and autoplay impressions — behaviors that carry no intent but, if fed to the attention, get a weight and corrupt the user vector. Denoising happens at two stages, and the trade-off between them is concrete.

So the recipe is layered: hard-filter the obvious noise to shrink the sequence (purity + cheaper attention), then let attention soft-denoise the rest. Two refinements from practice: a noise classifier trained on a user's own positives (their liked/completed videos) scores whether a new behavior is anomalous for that user; and contrastive learning builds augmented views of a behavior sequence (drop/mask/reorder some events) and trains the encoder to map them close, which makes the user vector robust to exactly the noise you could not filter.

Don't filter your way into a bubble
Aggressive filtering has a second-order cost the dwell threshold hides: exploratory behavior — a curious tap into a new vertical — often looks like noise (short dwell, no follow-through) and gets dropped. Filter too hard and the sequence becomes a tight loop of the user's existing interests, which is a filter bubble built by your data pipeline rather than your ranker. The standard guard is to keep a fraction of low-confidence exploratory behavior unfiltered, so the model still sees the edges of the user's taste.

Sessionization — cutting the stream into intents

The raw event log is one unbroken stream per user. But a session — a bounded burst of activity with a coherent intent — is the natural unit of short-term interest. You sessionize by cutting the stream wherever the gap between consecutive events exceeds a threshold (commonly ~30 minutes of inactivity, tuned per product): new session if (tᵢ − tᵢ₋₁) > τ_gap.

Sessionization earns its keep three ways. It scopes short-term interest (the camping intent lives in this session, not last week's). It de-noises sequences: behaviors from different sessions shouldn't blur together in the GRU, since a 30-minute gap usually means the intent reset. And it powers session-based metrics — sessions/user, depth-per-session, completion within session — that often track satisfaction better than raw event counts.

Interest drift is why a frozen model rots
Interests evolve mid-session (tents → sleeping bags → hiking boots) and across life (a new parent's feed should change in days, not quarters). A model trained on last month's behavior and never refreshed keeps recommending the user's past self. The value of a behavioral feature decays on the same clock as the interest it encodes, so a "recent-30-clicks" feature computed hourly is, for a fast feed, already an hour stale. Freshness is not a nice-to-have; it is the difference between modeling the user and modeling their ghost.

The real-time behavior pipeline — putting a number on "seconds-fresh"

The short-term tower is only as good as the freshness of the sequence it reads. "Seconds-fresh" is not a vibe; it is a latency budget with a specific architecture behind it. The path from a thumb-tap to an updated user vector, with the numbers a short-video stack actually targets:

[client SDK] tap / dwell / swipe — pre-aggregate same-type events │ in a 10s window on the edge → ~30%+ bandwidth saved, │ (drops mis-taps before the wire); SDK memory < 50 MB ▼ [Kafka] partition by hash(user_id) → all of one user's events │ land on one partition, in order (order matters: the GRU │ and attention read a *sequence*; reordering corrupts it) ▼ [Flink] sliding window: 30s window, 10s slide → recompute the │ user's short-term interest vector every 10s ▼ ┌──────────────┬──────────────────────────────────────────┐ │ Redis │ HBase │ │ online read │ offline backfill / long-term sequence │ │ < 200 ms │ store (cheap, durable, not latency-bound) │ └──────────────┴──────────────────────────────────────────┘ end-to-end tap → usable feature: < 3 s

Read the budget as a chain of constraints. Edge pre-aggregation merges a burst of identical events (four fast swipes → one summarized event) so the wire and Kafka see ~30% less traffic and the obvious mis-taps die before they cost anything. Partition by user-hash is the load-bearing detail: a sequence model is meaningless if events arrive out of order, so every event for a user must route to the same Kafka partition, which preserves per-user ordering for free. Flink's 30 s window / 10 s slide sets the recompute cadence — a fresh short-term vector every 10 seconds. Dual-write Redis + HBase splits roles: Redis serves the online read on the request path under ~200 ms; HBase is the durable store for backfill and the lifelong sequence SIM searches. End to end, tap → usable feature lands under 3 s, and a Douyin-class intent-prediction loop targets < 200 ms for the real-time sequence update itself. (This is the streaming machinery of lesson 17 (real-time streaming) and lesson 36 (streaming pipeline ops) — see them for the Kafka/Flink internals; here we only care that it delivers a fresh, ordered sequence on budget.)

The failure mode this pipeline introduces: online/offline skew
The short-term vector is computed by Flink in production; the same feature in your training data is computed by a batch job over logs. If the two definitions drift — different window edges, different dedup, a feature available at serve time but leaked from the future at train time — the model trains on one distribution and serves on another. This is the concrete cause behind the lesson's "offline AUC up, online engagement down" puzzle: the sequence the model learned from is not the sequence it gets. The guard is a single shared feature definition and periodic train/serve distribution re-alignment (KL between the two).

Sudden interest shifts — reacting in a session, not a quarter

Time-decay and sessionization handle gradual drift. They are too slow for a mutation — a user who pivots from entertainment to study content tonight, a signal the slow tower won't register for days. Treating this as ordinary drift means the feed keeps serving the old self through the whole pivot. The named recipe:

This is the sharp, named version of the earlier interest-drift callout: detect the change-point, gate out the false alarms, and buy fast evidence with a bounded exploration budget.

Interactive · target attention in action

One toy history of 8 past behaviors. Pick the candidate you're scoring — the widget computes a DIN-style attention weight αᵢ for each past item (softmax over category match × recency), and shows which behaviors "light up." Watch the same history produce a completely different user vector depending on what you score.

Score a candidate against the user's history
Attention weight αᵢ ∝ exp( categoryMatch(vᵢ, cand) · simStrength − λ·ageᵢ ). Heavier bars are the history the model is actually using to score this candidate. The toy numbers are directional, not literal.
top-attended item
attention mass on match
effective history used
regime
Reading

Behavior becomes features — the handoff to the rest of the stack

Everything above lands as features feeding the ranking model. Three families, increasingly powerful and increasingly perishable:

Two failure modes, both already named: negative signals are features too — skips, "not interested," and fast-scrolls are not the absence of data, they are weak-negative labels (treating un-clicked-but-shown items as hard negatives is the cheap version); and optimizing only immediate behavior is a trap — clicks and watch-time are the easiest labels to get and the easiest to over-fit, which is precisely how a feed wins every short-term metric while quietly burning long-term retention. That next-click-vs-long-term-value tension is the heart of multi-task / multi-objective ranking.

Turning raw signals into a shippable strength score

The opening table assigned each signal a qualitative "strength." Here is the concrete recipe that turns that into a number the model can consume — every line a guard against a specific pathology:

CVR labels and sample-selection bias — the ESMM hook (canonical home: lesson 12)
The "second-chance" and "click → long-watch / convert" signals above feed a CVR (conversion-rate) head, and that head has a classic trap worth naming here even though we don't re-derive it: a CVR model is trained only on clicked items (you only observe whether a click converts) but served on every impression — a sample-selection bias — and conversions are rare, so the data is sparse. ESMM fixes both by modeling pCTR and pCTCVR over the entire impression space and deriving pCVR = pCTCVR / pCTR, so CVR is learned without the click-only bias. The mechanism lives in lesson 12 (multi-task ranking) — follow it there.

Interview prompts you should be ready for

  1. "Why can't you just train on clicks?" (The click is generated by a biased process: only-shown items get feedback (selection bias), top slots get more clicks regardless of relevance (position bias), packaging shifts clicks (presentation bias). Train naively and you learn the UI and the previous ranker, not the user.)
  2. "Write the examination hypothesis and say what each factor is." (P(click | item, rank) = P(examine | rank) · P(relevant | item). First factor is the position propensity p_r — a UI artifact you estimate by randomization; second is the relevance you actually want. Debias via position-as-feature or IPW.)
  3. "Explain DIN's attention. Why is it 'target' attention, and what's in the activation unit?" (Each past item is weighted by an MLP scoring its relevance to the candidate, so the user vector is candidate-conditional — recompute per item. The unit eats [vᵢ, v_cand, vᵢ−v_cand, vᵢ⊙v_cand]; the difference/product terms are explicit crosses. DIN deliberately skips softmax normalization to keep interest intensity.)
  4. "DIN vs DIEN — what does DIEN add and what does it cost?" (DIEN adds a GRU interest-extractor with an auxiliary next-behavior loss, then an AUGRU whose update gate is scaled by attention to model interest evolution toward the candidate. Cost: sequential GRU doesn't parallelize, so it's slower at high QPS — many teams use DIN or a length-capped Transformer instead.)
  5. "Pooling vs RNN vs attention vs self-attention for a 100-item history — pick one for a 50 ms ranker and defend it." (Self-attention is O(n²) and latency-heavy; RNN is sequential. Target-attention is O(n) per candidate and only lights up the relevant slice — usually the production sweet spot. Pooling only if latency is brutal and histories are short/focused.)
  6. "How do you keep short-term intent from being drowned by long-term taste?" (Two encoders — long-term tower refreshed offline, short-term tower on the fresh event stream — fused by a context gate σ(g) that shifts to short-term when session signal is strong. Plus exponential time decay e^(−λΔt) and sessionization at a ~30-min gap.)
  7. "Your offline AUC went up but online engagement dropped. The new model added a Transformer over user history. What happened?" (Several real causes: training-time position/feedback bias the offline metric rewards but online doesn't; the history feature is stale at serve time (the freshness gap); over-fitting to immediate watch-time at the expense of retention; or train/serve skew in how the sequence is truncated/padded — the Flink-vs-batch feature-definition skew is the usual culprit.)
  8. "The user has 10,000 lifelong behaviors. DIN over all of them is O(n)·1000 candidates and self-attention is O(n²) — both blow the latency budget. How do you model it?" (Retrieve, then attend. SIM: a General Search Unit hard-searches the candidate's category (or soft-searches by embedding inner-product) to pull the ~200 relevant behaviors out of the 10k, then the Exact Search Unit runs DIN target-attention over just those — a ~50× cut. MIMN folds the whole stream into a fixed-size NTM memory written incrementally, so serving cost is independent of n. DSIN splits into sessions and confines self-attention to one short session. All move the heavy work off the per-request path.)
  9. "Why isn't exponential time-decay enough for recency, and what replaces it?" (Decay imposes monotonic forgetting — older always matters less — which suppresses periodic behavior, e.g. a user who opens cooking every Sunday has a real signal at Δt = 7 days that e^(−λ·168h) crushes. Replace/augment with time-bucketing (per-bucket embeddings) and Time2Vec (linear term for trend + sin terms whose learned frequencies capture daily/weekly periodicity), concatenated into the attention so the model learns the time response. Cost: more params, needs data per user, over-fits on sparse users.)
  10. "Walk me through denoising a behavior sequence and the trade-off at each stage." (Hard pre-filter the obvious noise — drop play < 500 ms / slide-dwell < 300 ms — which lifts purity ~15–20% and shrinks the sequence (cheaper attention) but mis-kills ~5% of genuine fast browsing. Then let attention soft-denoise the rest: keep the noisy events but learn near-zero weights, recovering latent signal at ~30% more compute. Add a per-user noise classifier and contrastive augmentation. Guard against over-filtering into a bubble by leaving some exploratory behavior unfiltered.)
Takeaway
The user is latent; behavior is a biased sample of preference. A click = examination × relevance — debias it (position-as-feature or IPW) or you train on the UI. Sequences beat single events, and target attention (DIN) is the key idea: weight the user's past by relevance to this candidate, so the same history yields a different user vector per item. DIEN adds interest evolution. Past a few hundred events, flat attention dies — so retrieve then attend (SIM's GSU→ESU), or fold the lifelong stream into a fixed memory (MIMN), or split it into sessions (DSIN). A short/long gate plus time-decay (or learned Time2Vec) handles the two clocks; sessionization scopes intent; hard + attention denoising cleans the input; and a sub-3s Kafka→Flink→Redis pipeline keeps the short tower fresh. These blocks live inside the ranker — and the labels they consume are only as good as your debiasing.