Feature engineering deep dive - from raw behavior to governed model input
The book's feature-engineering chapters are broad: feature construction, sparse and high-cardinality handling, categorical encodings, numeric transforms, temporal windows, user profiles, multimodal features, feature selection, SHAP/permutation analysis, and online experiments. This lesson linearizes that Q&A bank into one production path.
1 · A feature is a contract, not a column
The book defines feature engineering as transforming raw data into high-quality model inputs through extraction, transformation, combination, and selection. In production, add one more word: governance. A feature is not just "user 7-day watch time"; it is a reproducible contract from event to training row to online serving value.
| Feature contract field | Question to answer |
|---|---|
| Source | Which event/table/model creates it? |
| Entity | User, item, creator, context, or cross feature? |
| Clock | Event time, ingestion time, training snapshot time, or serving request time? |
| Window | Last 5 minutes, 1 hour, 7 days, 30 days, lifetime, or decayed history? |
| Encoding | Raw numeric, bucket, hash, embedding, target encoding, sequence, or vector? |
| Serving SLA | Seconds, minutes, daily batch, or static? |
| Fallback | What value is used when missing or stale? |
| Owner/version | Who changes it, and how do old training rows reproduce it? |
2 · Organize features by entity first
The book repeatedly separates user-side, video-side, context-side, and interaction features. That is the right starting point because each entity has a different freshness, sparsity, privacy, and cold-start problem.
| Entity | Examples from the book's short-video setting | Production concern |
|---|---|---|
| User | Age/region after desensitization, long-term interests, 7-day activity, completion history, recent sequence | Privacy, interest drift, missing profiles |
| Item/video | Category, tags, duration, clarity, cover quality, ASR text, CLIP/BERT embeddings, historical CTR/completion | Cold start, abuse, update latency |
| Creator | Follower count, historical quality, topic consistency, violation risk, creator cohort | Popularity bias and fairness |
| Context | Time of day, device, network, location bucket, app version, session source | Latency and compliance |
| Cross | User-category affinity, user-creator affinity, region-language match, time-of-day by topic | Cardinality explosion |
In an interview, naming the entity is the first sign that you know how the feature will be built. "CTR feature" is vague. "Smoothed item-category CTR in the last 6 hours, computed by event time and served from the online feature store" is a system feature.
3 · Transform numeric features by distribution
The book calls out normalization, binning, logarithmic transforms, and dynamic normalization. The linear rule is: look at the distribution, then choose the transform.
| Distribution problem | Transform | Short-video example | Why |
|---|---|---|---|
| Right-skewed counts | log1p(x), winsorization | Follower count, exposure count, watch seconds | Prevents head entities from dominating |
| Scale mismatch | Z-score, robust scaling, quantile transform | Watch time vs likes vs shares | Stabilizes multi-objective training |
| Small denominators | Bayesian smoothing | CTR for a new video with 20 impressions | Avoids noisy extreme rates |
| Time drift | Rolling baseline by day/region/surface | Holiday watch-time shift | Keeps feature comparable over time |
| Nonlinear thresholds | Bucket or percentile bin | Video duration, user activity tier | Lets models learn regime changes |
Dynamic normalization is useful but risky. If the online scaler updates hourly and offline training used a stale global mean, the model sees different feature semantics in training and serving. Version scalers like model artifacts.
4 · Encode categorical and high-cardinality features deliberately
The book spends many pages on sparse features and high-cardinality IDs. User IDs, video IDs, creator IDs, topics, and real-time hot tags can reach millions or billions of values. Direct one-hot encoding explodes; naive hashing loses information; target encoding leaks if time is wrong.
| Feature type | Useful encoding | When it fits | Failure mode |
|---|---|---|---|
| Low-cardinality category | One-hot or small embedding | Device type, coarse region, content vertical | Missing new labels |
| High-cardinality ID | Embedding table, hash bucket, dynamic embedding | User/video/creator IDs | Memory cost, cold embeddings, overfitting |
| Long-tail category | Frequency bucket + parent hierarchy | Rare tags, niche creators | Over-merge loses signal |
| Dynamic hot topic | Redis/KV online dictionary, temporary hash, async embedding training | Breaking news or viral memes | Online/offline encoding mismatch |
| Historical rate | Out-of-fold target encoding with smoothing | Category CTR, creator completion rate | Future-label leakage |
4.1 · Dynamic encoding and normalization at serving
The "dynamic hot topic" and "dynamic normalization" rows above name the technique but hide the hard part: a breaking-news hashtag or a holiday watch-time spike appears after the offline encoder was frozen. The serving system has to encode and normalize a value it has never seen, do it in milliseconds, and still produce a number that matches what offline training would have produced. Get the parity wrong and you get silent train-serve skew - see lesson 18 · serving; the streaming jobs that feed these stores are in lesson 36.
Two-tier encoding store. Split by access pattern, not by entity:
Three details make this safe. (a) Double-buffer swap: hold a live encoding table and a to-be-updated table; build the new one offline, then atomically flip the pointer, so a read never sees a half-written table - no read/write conflict on the hot path. (b) Consistent hashing of the temp code so the same unseen value gets the same bucket on every serving node; otherwise two replicas encode "#newmeme" differently and the model sees noise. (c) LRU eviction to bound Redis memory - a viral tag that cools off must fall back to the cold tier or you OOM the cluster.
encoding_version it was built under, and serving pins the matching version. Online/offline encoding mismatch is solved by version control, not by hoping the hash functions agree.
Dynamic numeric normalization - the three-tier quantile normalizer. A static global mean/σ cannot follow a holiday watch-time shift; recomputing it per request is too slow. The book's design layers three freshness tiers behind one lookup:
| Tier | Compute | Freshness | Role |
|---|---|---|---|
| Offline | T+1 batch global P1 / P99 | daily | Fallback / floor when fresher tiers miss |
| Near-line | Flink sliding-window (6 h) quantiles every 10 min into a Redis Sorted Set | ~10 min | Tracks trend; Sorted Set gives O(log n) quantile lookup by rank |
| Online | Normalize over the user's last ~100 behaviors at request time | per request | Personal, immediate scale |
To normalize x, look up its rank-quantile in the Sorted Set: x_norm = clip( (x − P1) / (P99 − P1), 0, 1 ) Worked example. Say the 10-min near-line window gives P1 = 2 s, P99 = 120 s. A 45-second watch normalizes to (45 − 2)/(120 − 2) = 43/118 ≈ 0.36. An hour later a viral long-form clip pushes P99 to 240 s; the same 45 s watch now maps to 43/238 ≈ 0.18 - the feature stays comparable as the population shifts, which a frozen scaler could never do.
Two stabilizers. EMA-smooth the quantile stats across windows - P99smootht = β·P99rawt + (1−β)·P99smootht−1 with β ≈ 0.3 (the smoothed value on the left, the raw window estimate and the previous smoothed value on the right) - so one anomalous 10-min window (a bot flood) cannot yank the scaler: if the raw new P99 jumps from a smoothed 120 to 400, the smoothed value moves only to 0.3·400 + 0.7·120 = 204, damping the spike. And on a timeout, degrade to the offline T+1 stats rather than blocking the request - a stale-but-sane number beats a missed SLA.
5 · Build temporal features as user-interest clocks
The book emphasizes time decay, sliding-window statistics, behavior intervals, and user interest drift. A good sequence of temporal features separates short-term intent from long-term taste.
| Clock | Feature examples | Use |
|---|---|---|
| Seconds/minutes | Current session topics, last 5 swipes, live fast-swipe rate | Immediate intent and fatigue |
| Hours | 1h category watch time, recent creator repeats, hot-topic response | Fresh trend adaptation |
| Days | 7-day completion by category, 7-day active sessions, interaction rate | Stable preference and activity |
| Weeks/months | Long-term interest vector, creator affinity, retention risk | Durable personalization |
For each window, specify event-time semantics and the exclusion boundary. The training join must reproduce what serving would have known before the scored impression. Late events may correct analytics, but they cannot travel back in time into a training feature unless you model that delay consistently.
6 · Convert behavior sequences into multiple feature layers
The book mentions raw behavior, statistical features, sequence embeddings, graph embeddings, and multimodal content features. Use them in layers (the multimodal layer - per-modality encoders and the early/mid/late fusion choice - is built in lesson 14 · multimodal):
Do not collapse everything into one user embedding too early. The interviewer may ask why the model cannot react to sudden interest drift. The answer is: because long-term preference and short-term session intent should be separate inputs, possibly fused by attention or gating.
6.1 · The sequence-encoder input recipe
The architecture choices (DIN target attention, DIEN's GRU, SIM's two-stage retrieval, MIMN memory) live in lesson 13 · user behavior sequences. What belongs here, in feature engineering, is the question the model cannot answer for you: what vector represents one behavior event before it enters the encoder? A raw item-ID embedding throws away two signals the book insists you keep - the type of the action and how long ago it happened. The standard recipe is an additive input per event:
xᵢ = e_item(idᵢ) + e_type(actionᵢ) + e_pos · decay(Δtᵢ) , decay(Δt) = 1 / (1 + ln(1 + Δt))
| Component | What it carries | Worked example |
|---|---|---|
e_item | Pretrained or jointly-learned item vector (64-d typical) | The watched video's content embedding |
e_type | Behavior-type embedding; a scalar gate is common (click = 0.3, like = 0.7, share = 0.9) | A "like" event weighs ~2.3× a bare "click" before attention even runs |
decay(Δt) | Time-decay position factor, downweighting stale events | See numbers below |
Worked decay. With decay(Δt) = 1/(1 + ln(1 + Δt)) and Δt in minutes (ln = natural log; the 1+ in the denominator keeps it bounded in (0, 1] and defined at Δt = 0): a brand-new event scores 1/(1+ln 1) = 1.0; 1 min ago 1/(1+ln 2) ≈ 0.59; 60 min ago 1/(1+ln 61) ≈ 0.20; 1 day ago (1440 min) 1/(1+ln 1441) ≈ 0.12. So a behavior from an hour back enters the encoder at ~33% of the weight of one from a minute back, and a day-old one at ~20% - the encoder still sees the old event (unlike a hard window cutoff), it just leans on it less. Aggregate the encoder outputs by attention-Top-K weighted mean or a learnable [CLS] token; the order itself is preserved by a positional encoding.
Self-attention over an n-event history is O(n²). Naively, every new swipe re-encodes the entire sequence from scratch: at n = 200 that is 200² = 40,000 attention scores recomputed on every single event, and a user generates events all session long. That blows the millisecond budget for an active user.
The fix is the same incremental-decode trick LLMs use, and it requires one design choice: make the encoder causal (each event attends only to earlier ones), so an arriving event cannot change any past event's representation. Then you can cache the user's last-N Key and Value matrices. When one new event arrives you compute Q/K/V for that one step only, append its K/V to the cache, and run attention of the new query against the cached keys - O(n) work for the new step, not the O(n²) of re-encoding all n events. At n = 200 a full re-encode forms the entire 200×200 = 40,000-entry attention matrix every event; the cached step computes ~200 scores instead, a ~200× cut that grows linearly, not quadratically, as the session lengthens. (A bidirectional [CLS] encoder can't reuse the cache fully - appending a token shifts every token's representation - which is exactly why streaming behavior encoders are built causal.)
Two more guards from the book. When a lifelong sequence exceeds n ≈ 1000, drop full attention for sparse attention - restrict each query to the last k ≈ 20 keys, turning O(n²) back into O(n·k) (at n = 1000, k = 20 that is 20,000 scores vs 1,000,000, a 50× cut) - or hand the long tail to SIM-style retrieval in lesson 13. And precompute the interest vector offline for the DAU top-10% (the heaviest users), so the online path only ever does the cheap incremental step; INT8-quantize the encoder to shrink the per-request memory footprint.
6.2 · Fusing long- and short-term interest at the feature layer
§6 said long-term taste and short-term intent must be separate inputs. The gating model that combines them - u = σ(g)·u_long + (1−σ(g))·u_short - is derived in lesson 13. The feature-engineering job is upstream of that gate: decide which behaviors are even allowed into the short-term pool, how fast stale ones fade, and when the pool should be thrown away entirely. Three mechanisms from the book, each with a number.
1 · Decay-weighted relevance. The short-term tower (a Transformer over the recent sequence) and the long-term tower (Embedding + MLP) are scored against each other by cross-attention, but every short-term behavior first carries a continuous decay weight e^(−λΔt). Pick λ from a target half-life: half-life h means e^(−λh) = 0.5, so λ = ln2 / h. For a 3-day half-life (Δt in days), λ = 0.693/3 ≈ 0.231; a behavior 1 day old then weighs e^(−0.231) ≈ 0.79, 3 days old 0.50 by construction, 7 days old e^(−1.62) ≈ 0.20. So a week-old action contributes a fifth of a fresh one - stale intent fades smoothly instead of dropping off a window edge.
Smooth decay is too slow when a user genuinely switches interest (sport → food). The book fires a hard reset on a two-part trigger: ≥ 3 consecutive interactions in a new category AND cosine similarity to the historical interest vector < 0.2. Both conditions matter - 3 interactions alone could be a misclick streak; low cosine alone could be one anomalous view. Together they say "this is a real pivot."
On trigger, the short-term pool is reset and old interest is force-forgotten by Newton's-law-of-cooling with coefficient α = 0.3: w_new = w_old · e^(−α·n) where n counts post-drift steps. After the reset, the old weight decays to e^(−0.3) ≈ 0.74 after 1 step, e^(−0.9) ≈ 0.41 after 3, e^(−1.5) ≈ 0.22 after 5 - roughly 78% of the stale sport interest is gone within 5 food interactions, far faster than the e^(−λΔt) half-life would manage.
3 · Confidence gating against misclick pollution. The cheapest way to corrupt a short-term pool is to count fat-finger taps. Gate the pool by engagement quality: only admit a behavior whose completion rate > 70%. A 2-second bounce off a 30-second video (completion 2/30 ≈ 7%) never enters the interest signal; a 25-second watch (25/30 ≈ 83%) does. This is why the drift trigger is robust - the 3 interactions it counts are already quality-filtered, so an accidental swipe-through can neither trigger a reset nor poison the pool. Run these clocks at multiple scales (minute / day / month) so the gate, the decay, and the reset each operate on the horizon they fit.
| Knob | Value | What it protects against |
|---|---|---|
| Decay e^(−λΔt), 3-day half-life | λ ≈ 0.231/day | Stale intent lingering with full weight |
| Drift trigger | ≥ 3 new-category hits AND cos < 0.2 | Reacting too slowly to a genuine pivot |
| Newton cooling on reset | α = 0.3 | Old interest bleeding into the new one |
| Confidence gate | completion > 70% | Misclicks polluting the pool / false drift |
7 · Use feature crosses when the interaction is business-real
The book's examples include user interest tag by video category, dwell time by creator popularity, region by language, and behavior sequence by content attribute. Feature crosses are valuable when the interaction has a plausible story.
| Cross | What it captures | Guardrail |
|---|---|---|
| User category affinity × current video category | Personal taste match | Watch for repetition and filter bubbles |
| User active hour × content type | Contextual intent | Do not encode private routine too granularly |
| Video duration × user completion history | Length tolerance | Normalize by video bucket |
| Creator trust × cold-start exposure | Safe exploration budget | Avoid permanent creator lock-in |
| Region × language | Localization fit | Avoid unfair suppression of minority content |
High-cardinality crosses should usually become embeddings, hashed crosses, factorization-machine interactions, or learned attention rather than raw one-hot features.
8 · Compress sparse interest features carefully
The book separately calls out high-dimensional sparse interest tags. These are not always best handled by bigger embeddings. Sometimes you need feature selection or dimensionality reduction before the deep model ever sees the input.
| Method | What it does | Use | Trade-off |
|---|---|---|---|
| Chi-square / mutual information | Filters tags by relation to the target | Fast pruning of sparse labels | Misses feature interactions |
| L1 regularization | Drives weak feature weights to zero | Embedded selection during training | Unstable for correlated tags |
| SVD/PCA | Projects sparse vectors into lower dimensions | Compressing user-interest matrices | Compressed dimensions lose business meaning |
| Autoencoder | Learns nonlinear compressed representation | Dense interest vector from sparse behavior | Harder to explain and monitor |
| Embedding layer | Learns dense ID/tag vectors end to end | Large-scale sparse categorical features | Memory, cold-start zeros, privacy risk |
Mini-scenario: a user has 1,000 possible interest tags but only 8 observed. You might first remove globally uninformative tags with mutual information, group rare tags into parent categories, then learn a dense embedding. If the resulting vector improves AUC but nobody can explain which interests changed, use SHAP or nearest-neighbor tag inspection before shipping it into a sensitive surface.
9 · Select features with offline and online evidence
The book lists filter methods, wrapper methods, embedded methods, permutation importance, SHAP, ablation, and online A/B. A linear workflow is:
- Filter obvious bad features. Missingness, leakage, instability, privacy risk, high compute cost.
- Train with candidate features. Check AUC/GAUC/NDCG/logloss and calibration by segment.
- Explain contribution. Use tree gain, coefficients, SHAP, or permutation importance.
- Ablate retraining. Remove feature groups and observe model quality and serving cost.
- Run online feature experiment. If expensive or high risk, gate the feature and test the business impact.
- Govern lifecycle. Deprecate stale features, version new ones, monitor drift.
| Method | Best use | Limit |
|---|---|---|
| Mutual information / chi-square | Fast initial screen | Misses interactions |
| L1 / tree gain / embedded methods | Model-aware selection | Depends on model family |
| Permutation importance | Global contribution | Can break correlated or sequential structure |
| SHAP | Direction and local explanations | Expensive at recommender scale |
| Ablation retrain | True offline contribution | Compute-heavy |
| Online A/B | Production value and latency trade-off | Requires traffic and careful isolation |
10 · Monitor features like product dependencies
The book explicitly mentions feature coverage, stability, PSI, feature lineage, and version management. A production feature has monitors at every stage:
| Monitor | Example | Action |
|---|---|---|
| Freshness | Last update age for 1h watch-time feature | Fallback to daily aggregate if stale |
| Coverage | Missing region or video embedding rate | Default, backfill, or block training |
| Distribution drift | PSI, quantile shift, top-value change | Investigate schema, traffic, or product change |
| Train-serve parity | Offline value differs from online value for same request | Fix transformation or timestamp join |
| Importance drift | Top features change sharply week over week | Check interest drift, leakage, or data bug |
| Cost | Feature lookup or computation adds latency | Cache, precompute, approximate, or remove |
Interview prompts you should be ready for
- "Design features for a short-video ranker." Answer by entity: user, video, creator, context, cross; then add temporal windows, multimodal vectors, and freshness.
- "How do you handle high-cardinality IDs?" Use embeddings, hashing, dynamic embeddings, hierarchy, smoothing, regularization, and cold-start content features.
- "What is point-in-time correctness?" Training features must contain only information available before the scored impression; future labels and backfilled aggregates leak.
- "How do you evaluate a new feature like swipe speed?" Offline contribution, SHAP/permutation, ablation retrain, online A/B, latency/cost, coverage, and long-term guardrails.
- "How do you manage feature drift?" Monitor coverage, freshness, distribution, importance, train-serve parity, and feature lineage; version and rollback.
- "A user's behavior sequence encoder is too slow at serving - what do you change without losing signal?" Cache last-N K/V and incrementally encode only the new event (O(n²)→O(n)); sparse-attend to the last k≈20 past n≈1000 or push the long tail to SIM retrieval; precompute interest vectors for the DAU top-10%; INT8 the encoder. Distinguish the input recipe (item+type+time-decay) from the architecture.
- "How does serving encode a brand-new hashtag and stay consistent with training?" Two-tier Redis-hot/HBase-cold store; temp consistent-hash bucket now + async-trained embedding later; LRU to bound memory; double-buffer swap to avoid read/write conflict; and crucially an
encoding_versionpinned on both sides so online and offline never disagree on the bucketing.