search_ads_recsys / 28 · feature engineering lesson 28 / 39

Feature engineering as a discipline

"Deep models learn the crosses, so feature engineering is dead" is the most confidently-wrong thing a candidate can say. The embedding table learns interactions among features you give it. It cannot conjure a signal you never extracted, it cannot un-leak a target you smuggled in, and it cannot fix a high-cardinality column that blew your table to 40 GB. This lesson is the craft that lives below the model.

What the deep model actually automated — and what it didn't

Lesson 04 told the architecture story: LR → FM → Wide&Deep → DLRM → DCN-v2 is the history of moving feature crosses from hand-written recipes into learned cross-layers. That is real, and it killed the era of engineers typing query_token × ad_id by hand. But it automated exactly one thing — the interaction between features already present in the input. Everything upstream of that is untouched and still on you.

WHAT THE DEEP RANKER DOES AND DOESN'T DO FOR YOU ────────────────────────────────────────────────────────────── raw logs ─► [ extract a signal ] ◄── YOU. the model can't ─► [ encode categoricals ] ◄── YOU. vocab/hash/embed ─► [ scale/transform num ] ◄── YOU (for non-tree models) ─► [ guard against leakage]◄── YOU. point-in-time joins ─► [ make it computable ]◄── YOU. same code at serve at serving time ] ───────────────────────────────────────────── ─► [ learn crosses among ] ◄── THE MODEL. this part the features above ] it genuinely automates

The mental model: the cross-network is a powerful interaction engine sitting on top of a representation you built. Give it garbage — a leaky target encoding, a min-maxed feature whose serving-time max is unknown, a 10⁹-cardinality column you dumped raw into an embedding table — and it will faithfully learn the interactions of your garbage. Feature engineering didn't move into the model. It moved into the plumbing: encoding, scaling, leakage control, and serving parity.

The one-line summary you should be able to give
Deep models learn interactions among the features you supply; they do not extract signal, encode categoricals, scale numerics, or prevent leakage for you. Feature engineering is now the discipline of building a correct, computable-at-serving-time representation — and getting that wrong is the single most common reason a "fancy model" underperforms a careful one.

Categorical encoding — the cardinality decides everything

A categorical column has no native numeric meaning, so you must choose how it enters the model. The wrong choice either injects fake structure, explodes dimensionality, or leaks the label. The deciding variable is almost always cardinality — how many distinct values — and the secondary one is whether the model is a tree (splits on order) or something distance/dot-product based.

One-hot vs label/ordinal — the false-order trap

One-hot maps each of k categories to a basis vector — no spurious order, but k new columns. Fine at k=12 (device type), insane at k=10⁸ (user_id). Label / ordinal encoding maps categories to integers 0,1,2,…. That is correct only when the values have an order (size = S<M<L). For a nominal feature — country, ad_creative_id — it injects a lie: it tells a linear model or a distance metric that country=3 is "between" 2 and 4 and "twice" 1. Trees survive it (a split is just x < t, and the model can chop the integer line into the right buckets given enough depth), which is exactly why label encoding is a defensible default for GBDTs and a bug for everything else.

Frequency / count encoding

Replace each category with how often it appears: enc(c) = count(c) or its normalized frequency. Cheap, one column, no leakage of the label (it uses only the feature distribution). It encodes a real signal surprisingly often — a rare merchant and a giant marketplace behave differently — but it collides two categories with the same frequency into the same number, so use it as a supplement, not a replacement for identity.

Target / mean encoding — powerful, and a loaded gun

Replace each category with the mean label observed for it: for a CTR problem, enc(c) = E[y | c], the empirical click-through-rate of that category. This is wildly effective — it compresses a high-cardinality column into one column that is already half the prediction. It is also the single most common source of target leakage in applied ML, because the encoding of row i is computed from a statistic that includes row i's own label.

The leak is sharpest for rare categories. A category that appears once gets enc(c) = y_i — the encoding is the label. The model learns "when this feature equals 0.0, predict 0; when it equals 1.0, predict 1," scores a gorgeous training AUC, and collapses in production where that category's encoding is computed from other rows. Two fixes, and you need both:

enc(c) = ( n_c · ȳ_c + m · ȳ ) / ( n_c + m )

where n_c is the category's count, ȳ_c its raw mean, ȳ the global mean, and m a smoothing strength (effectively "how many imaginary global-average observations to add"). When n_c is large the encoding trusts the category; when it is tiny it falls back to the population rate — which is exactly the right Bayesian behavior, and structurally the same shrinkage you saw stabilize cold-start priors in 15 · Cold start.

Worked numbers — smoothing a rare vs a frequent merchant
Global CTR ȳ = 2%, smoothing m = 20. A rare merchant seen 2 times with 1 click has a raw mean ȳ_c = 50% — wildly noisy. Smoothed: (2·0.50 + 20·0.02)/(2+20) = (1.0 + 0.4)/22 = 0.064 ≈ 6.4% — pulled hard back toward the 2% population rate, because 2 observations earn almost no trust against 20 imaginary global ones. A frequent merchant seen 10,000 times with a 3% raw rate: (10000·0.03 + 20·0.02)/(10000+20) = (300 + 0.4)/10020 ≈ 0.0300 = 3.0% — essentially unchanged, because 10,000 real observations swamp the 20 imaginary ones. That asymmetry is the whole point: m is the count at which a category is trusted half-and-half (here n_c = 20 would give a 50/50 blend), so the noisy tail is protected from its own variance while the well-measured head keeps its real signal.
NAIVE (LEAKY) OUT-OF-FOLD (SAFE) ───────────────────────── ───────────────────────── encode row i ◄── mean over encode rows in fold j ALL rows incl. row i ◄── mean over folds ≠ j only │ │ ▼ ▼ enc(c) "sees" y_i enc(c) never sees its own y │ │ ▼ ▼ train AUC 0.95 ✗ val AUC 0.71 train AUC 0.82 ✓ val AUC 0.80 (memorized the label) (gap reflects real signal)

The hashing trick — when cardinality is the enemy

Lesson 04 introduced this for the embedding table: a deterministic hash h(c) mod B maps an unbounded category space into B fixed buckets, so a 10⁹ user_id space fits in 10⁶ rows with no vocabulary to maintain and zero handling needed for unseen IDs at serving. The cost is collisions — two distinct IDs share a row and become indistinguishable to the model. You trade a controlled amount of identity for a bounded memory footprint; you size B so that popular values rarely collide (collisions in the cold tail are cheap, collisions among your top advertisers are not). Double-hashing or a per-feature hash seed reduces systematic collisions.

Hierarchical / taxonomy encoding

Interest tags are rarely flat — they live in a tree: sports → basketball → NBA → Lakers. A flat one-hot of the leaf wastes the structure and starves rare leaves of data. Encode the path: emit a (coarse, mid, fine) tuple — category, subcategory, tag — each its own embedding, and let the model pool them. A cold leaf tag still inherits a well-trained category embedding, so it degrades gracefully toward its parent rather than to noise. This is target-encoding's smoothing idea expressed structurally: borrow strength from the parent when the leaf is sparse.

That paragraph is the intuition; below are the four named schemes you should be able to reach for by name, and — more importantly — why flat encoding actively fails on a deep taxonomy in a way none of the schemes above (one-hot, target, hashing) repair on their own.

The long-tail failure mode of flat encoding — why hierarchical encoding has to exist
A production catalog taxonomy is not k=50; it is electronics → phone → android → pixel-8-128gb repeated for millions of leaf SKUs, and the leaf-count distribution is brutally power-law: a handful of head leaves carry most impressions and the overwhelming majority are seen a handful of times — many exactly once. Now look at what each flat scheme does to that tail. Flat one-hot emits a column (or embedding row) per leaf, so 10⁶ leaves is a 10⁶-wide block where 99% of rows train on <10 gradient updates — the cold-leaf vectors never leave their random init, and the cross-network learns interactions of noise. Flat target encoding is worse: a leaf seen n=1 encodes to its own label (the leakage from the section above, now multiplied across millions of singletons), and even with smoothing toward the global mean ȳ a rare pixel-8-128gb collapses all the way to the platform average — discarding the very real, very learnable signal that it is a phone. Flat hashing bounds memory but blends the cold leaf into an arbitrary bucket with unrelated SKUs, so its representation is a random other category's, not its parent's. The structural fix in all four schemes below is the same move: a sparse leaf should fall back to its parent, not to global noise — because phone has thousands of impressions even when pixel-8-128gb has three.

Four named hierarchical schemes

1 · Path encoding. Serialize the full path to a single token — "electronics/phone/android" — then hash or one-hot that string. It preserves the complete ancestry in one feature and is trivial to compute online (it is just a string concat + hash, far cheaper than a graph embedding). The catch is that it worsens sparsity rather than curing it: electronics/phone/android and electronics/phone/ios are as unrelated to the model as two random hashes — the shared electronics/phone prefix buys you nothing, because the model never sees the prefix as its own feature. Path encoding is best as a supplementary high-precision identity alongside the level features below, not a standalone fix for the tail.

2 · Parent-conditioned target encoding with per-level Bayesian smoothing. This is the scheme that directly repairs the long-tail failure above, and it is the hierarchical generalization of the out-of-fold + smoothed target encoding from the section above. Instead of smoothing every level toward the global mean, you smooth each level toward its parent's encoded mean, walking down the tree: target-encode L1 (electronics) toward the global rate; then encode L2 (phone) toward the L1 estimate; then the leaf (pixel-8-128gb) toward its L2 parent. Each level uses the same shrinkage you saw for flat smoothing, but with the parent mean in the prior slot:

enc(leaf) = ( n · leaf_mean + α · parent_mean ) / ( n + α )

where n is the leaf's own count, leaf_mean its raw observed CTR, parent_mean the (already-smoothed) encoding of its immediate parent, and α the per-level smoothing strength — the count at which the leaf is trusted half-and-half against its parent. The one change from flat smoothing is the prior: a rare leaf now falls back to phone's well-measured rate, not to the platform average — which is exactly the signal flat target encoding threw away. (Keep the same out-of-fold discipline at every level: each level's means are computed from folds that exclude the row being encoded, or the leak you fixed flatly re-opens hierarchically.)

Worked numbers — the same leaf, n=3 vs n=300, smoothing toward the parent
Take phone (the parent) measured over tens of thousands of impressions at a clean parent_mean = 4.0%, and set the per-level smoothing α = 10. Now encode a leaf SKU under it.

Cold leaf, n = 3, with 2 clicks → raw leaf_mean = 2/3 ≈ 66.7% (absurdly noisy on 3 samples). Parent-conditioned: enc = (3·0.667 + 10·0.04)/(3+10) = (2.00 + 0.40)/13 = 2.40/13 ≈ 0.185 = 18.5%. Flat smoothing toward the 2% global rate would instead give (3·0.667 + 10·0.02)/13 ≈ 0.169 = 16.9% — only ~1.5 points lower, because here the wild 66.7% leaf dominates both estimates. That gap is no accident: parent-minus-flat is exactly α(parent_mean − global_mean)/(n + α) = 10·(0.04 − 0.02)/13 ≈ 1.5 pts, the same regardless of the leaf's own noise. It becomes decisive when the leaf is nearly silent: a truly cold 0-click SKU (leaf_mean = 0) lands at (0 + 0.40)/13 ≈ 3.1% under parent-conditioning but only (0 + 0.20)/13 ≈ 1.5% flat — it inherits "this is a phone" (≈4%) instead of "this is an average platform item" (≈2%), which is the signal flat target encoding throws away. Either way the 3 observations earn only 3/(3+10) ≈ 23% of the weight, so the estimate stays sane.

Warm leaf, n = 300, with a stable leaf_mean = 7.0%: enc = (300·0.07 + 10·0.04)/(300+10) = (21.0 + 0.4)/310 = 21.4/310 ≈ 0.069 = 6.9% — essentially the leaf's own 7%, because 300 real observations swamp the 10-count prior (300/310 ≈ 97% weight on the leaf). That asymmetry is the whole point: at n=3 the leaf is ~77% parent / 23% itself and is rescued from its own variance; at n=300 it is ~97% itself and keeps its real, distinct signal. Same α, same formula — the data count n decides how much identity the leaf has earned.
SHRINKAGE WEIGHT ON THE LEAF'S OWN MEAN = n / (n + α), α = 10 ─────────────────────────────────────────────────────────────────── n=1 ▓░░░░░░░░░ 9% → almost entirely the parent's 4% (rescued) n=3 ▓▓░░░░░░░░ 23% → mostly parent, slight leaf tilt n=10 ▓▓▓▓▓░░░░░ 50% → the half-trust crossover point (n = α) n=30 ▓▓▓▓▓▓▓▓░░ 75% → leaf dominates, parent is a gentle prior n=300 ▓▓▓▓▓▓▓▓▓▓ 97% → essentially the leaf's own 7% (its signal) ─────────────────────────────────────────────────────────────────── flat smoothing puts the PARENT slot = global mean ȳ; hierarchical puts it = parent_mean, so a cold leaf inherits "phone", not "average".

3 · Graph embedding of the taxonomy tree (Node2Vec). Treat the taxonomy as a graph — nodes are categories at every level, edges are parent–child links — and run Node2Vec (the random-walk embedding from 24 · GNN) over it to learn a dense vector per node where tree-adjacent categories land near each other in space. A cold leaf's vector is then close to its siblings' and parent's by construction, so it shares strength geometrically rather than via an explicit shrinkage formula. This is the most expressive option and composes cleanly with behavior features (concatenate the node vector with the user/item embeddings), but it is the heaviest to maintain: re-running Node2Vec when the taxonomy changes is an offline batch job, which is why path/level encodings are preferred when the online compute budget is tight.

4 · Multi-granularity bucketing. Encode different levels with different schemes matched to their cardinality: L1 (~20 top categories) as plain one-hot, L2 (~500) and the leaf as learned embeddings, and feed all of them. The model pools a cheap exact L1 signal with richer learned L2/leaf vectors. The one caveat the source flags: keep the dimensions consistent where you intend to add or attention-pool across levels — if L1 is a 20-d one-hot and the leaf is a 32-d embedding you cannot sum them, so either project to a common width or keep them as separate concatenated fields the model reconciles itself.

Cold leaves: parent → child transfer init

The four schemes handle a leaf that has some data; a brand-new leaf (a SKU added this morning, a freshly-minted hashtag) has none, and its embedding row is still at random init. Don't ship random noise — initialize the new leaf's embedding from its parent's (copy phone's vector into the new pixel-9 row, optionally with a little jitter), so from its very first impression it predicts as a generic phone and then specializes as data arrives. This is the embedding-space twin of the n=0 case of the shrinkage formula — at n=0, enc = parent_mean exactly — and it is the same cold-start strength-borrowing you saw in 15 · Cold start, here applied structurally through the taxonomy instead of through a content model. (The deeper deep-dive on entity-organized feature stores and these encoders lives in 35 · Feature engineering deep dive.)

When to embed vs encode

SchemeCardinality fitAdds dimsLeakage riskUse when
One-hotLow (< ~50)k columnsNoneFew nominal values; linear/tree model; interpretability matters.
Label / ordinalAny1NoneGenuinely ordered values, or a tree model that can re-split the integer line.
Frequency / countMid–high1None (uses feature dist. only)Popularity itself is signal; supplement to identity.
Target / meanMid–high1High — needs OOF + smoothingHigh-cardinality + strong label correlation; tree models especially.
Hashing trickVery high (10⁶+)B bucketsNone (collisions, not leak)Unbounded / streaming vocab; memory-bounded embedding tables.
EmbeddingMid–very highd (learned)NoneDeep model, enough data per row to train the table; identity matters.
HierarchicalHigh, nested (10⁶+ leaves)d × levelsNone (parent-cond. TE inherits the OOF rule)Deep taxonomy with a power-law leaf tail; path / parent-conditioned target encoding / Node2Vec / multi-granularity — want a sparse leaf to fall back to its parent, not to global noise.

The senior heuristic: embed if you have a deep model and enough exposures per value to train a vector; target-encode (OOF + smoothed) if you are on a GBDT or the cardinality is high but exposures are thin; hash when the vocabulary is unbounded or memory-bound. These compose — it is normal to hash then embed, and to feed both an embedding and a smoothed target encoding of the same column.

Numerical features — scaling, skew, bins, and the streaming wrinkle

Scaling, and why it matters for some models and not others

Three standard scalers. Standardization (z-score) centers and unit-variances a feature:

z = ( x − μ ) / σ

Min-max squashes to [0,1] via (x − min)/(max − min); robust scaling uses the median and IQR, (x − median)/IQR, so a few wild outliers don't dominate. The crucial fact interviewers probe: scaling matters enormously for some models and not at all for others.

Skew handling — log, Box-Cox, Yeo-Johnson

Prices, counts, watch-times, and bids are heavy-right-tailed: a few huge values, a long thin tail. A linear or NN model handles a roughly-symmetric input far better, so you compress the tail. The workhorse is log1p, log(1+x), which handles zeros (where plain log x blows up) and turns multiplicative structure additive. For a principled, data-fit transform there is the Box-Cox family,

(x^λ − 1) / λ for λ ≠ 0, and ln x for λ = 0

which finds the power λ that best normalizes the data — but Box-Cox requires x > 0. When the feature has zeros or negatives (a net P&L, a signed delta), reach for Yeo-Johnson, the generalization that is defined on the whole real line. The discipline: pick the transform on the training distribution, freeze λ (and μ, σ), and apply the frozen parameters at serving — re-fitting at serving time is itself a leak.

Binning / discretization — and why bucketizing a monotone feature helps

Discretization replaces a continuous value with the bucket it falls in. Equal-width bins cut the range into uniform intervals (sensitive to outliers — one huge value stretches every bin). Equal-frequency / quantile bins put the same count in each bucket (robust, the usual choice). Lesson 04 already flagged the punchline: log(1+price) in 32 quantile buckets often beats raw price, because a model that can't bend a feature can be handed the bend pre-made. An LR or a shallow MLP fits a monotone-but-curved CTR-vs-age relationship slowly; bucketize age and give each bucket its own weight/embedding and the model expresses the curve trivially. Trees don't need this (they bin internally), but for everything linear it converts a hard non-linear fit into a free one — at the cost of within-bucket resolution.

MethodWhat it doesMatters forReach for it when
Z-scoreCenter + unit varianceLinear, NN, distanceRoughly-Gaussian feature; default for NN inputs.
Min-maxSquash to [0,1]NN (bounded activations), imagesKnown fixed bounds; dangerous if serving exceeds train max.
Robust (median/IQR)Outlier-resistant centeringLinear, NN with outliersHeavy outliers you can't or won't clip.
log1pCompress right tail, handle 0Linear, NNCounts, prices, watch-time; multiplicative structure.
Box-CoxLearned power-normalizeLinear, NNStrictly positive feature; want best normality fit.
Yeo-JohnsonBox-Cox for ℝ (neg/zero ok)Linear, NNSigned / zero-containing feature needing normalization.
Quantile binsDiscretize to bucketsLinear, shallow MLPCurved monotone effect a flat model can't bend; robust to outliers.

Online / streaming normalization — you don't have a global mean

Every formula above assumes you can compute μ and σ over the whole dataset. In a streaming system (17 · Real-time streaming) you cannot — the data arrives forever and the "global mean" doesn't exist yet. Two consequences:

Feature selection & importance — three families and the right default

More features is not more signal. Noise features add variance, inflate serving cost, widen the leakage surface, and (for linear models) wreck coefficients via collinearity. Selection methods fall into three families:

FamilyHow it scoresCostExamplesCatch
FilterUnivariate stat between feature and label, model-freeCheapMutual information, chi-square, correlationBlind to interactions — drops a feature useless alone but powerful in a cross.
WrapperTrain the model on feature subsets, keep what helpsExpensive (many fits)Forward/backward selection, RFECombinatorial; can overfit the selection to the val set.
EmbeddedSelection is part of model fitting~Free (one fit)L1 / Lasso (zeros out weights), tree gain / split countTied to one model's inductive bias; L1 arbitrary among correlated features.

Permutation importance is the right model-agnostic default. Train the model, measure validation score, then shuffle one feature's column and measure how much the score drops — a big drop means the model relied on that feature. It works on any model, measures importance as actually used, and needs no retraining. Its sharp caveat: under correlated features it under-credits both. Shuffle feature A while its near-duplicate B stays intact and the model leans on B, so A looks unimportant — and vice versa — so two genuinely-important correlated features can both score near zero. The fix is to permute correlated groups together, or to reach for SHAP (21 · Explainability), which attributes a prediction across features with better-behaved theory and is increasingly the selection-and-debugging tool of choice.

Multicollinearity and VIF — a linear-model disease

When features are linearly redundant, a linear model's coefficients become unstable: the fit can put a giant positive weight on one and a giant negative on its correlate, and they nearly cancel — the prediction is fine but every coefficient is nonsense, so any story you tell from the weights is fiction. The diagnostic is the Variance Inflation Factor: regress feature j on all the others, take its R²ⱼ, and

VIFⱼ = 1 / ( 1 − R²ⱼ )

A VIFⱼ near 1 means feature j is independent of the rest; > 5–10 flags serious collinearity. The senior nuance interviewers love: collinearity wrecks linear-model coefficients, not tree predictions. A GBDT just picks one of the redundant features at each split and predicts identically — the prediction is robust; it's the interpretation (and any linear coefficient) that breaks. So VIF matters when you're shipping or explaining a linear model, and is largely irrelevant to a deep ranker's accuracy (though redundant features still cost you serving latency and memory).

Validating a new feature — the only test that counts

A feature earns its place in two stages. Offline ablation: train with and without it on the same data and compare a held-out metric (AUC, log-loss). If it doesn't move the offline number, it almost never survives. But offline lift is necessary, not sufficient — it can come from leakage. Online single-feature A/B: ship the model with the one feature added against the same model without it, and read the real-traffic lift (08 · Evaluation). A feature that wins offline but is flat or negative online is the classic signature of a feature that leaked in training or isn't computable the same way at serving. The two-stage gate is non-negotiable for any feature you intend to keep.

The cross-cutting sin: leakage

Every section above circled the same drain. Leakage is information in your training features that will not be available, in the same form, at the moment you must predict — so your offline metric is measuring a fantasy. It wears three faces:

A feature is only as good as it is computable at serving time, with the same code path
This is the load-bearing principle of the whole discipline. The most accurate offline feature is worthless — or harmful — if at serving time it is unavailable, stale, or computed by different logic. This is exactly why 18 · Deployment & serving pushes a single shared feature transformation (ideally one library, or a feature store that logs the served value and reuses it for training): when the same code computes the feature for both training logs and online inference, train-serve skew structurally cannot open up. Naive target encoding fails this test twice over — it leaks the label offline, and its per-row statistic isn't reproducible online — which is why it tops the list of features that look brilliant in a notebook and die in production.

Interactive · the target-encoding leakage demo

A high-cardinality categorical (think 200 merchant IDs, many seen only a handful of times) target-encoded against a binary label. Toggle naive vs out-of-fold (K-fold) encoding and slide the smoothing strength m. Watch the train-vs-validation AUC gap: naive encoding posts a gorgeous training AUC and collapses on validation — it memorized the label through the encoding. OOF + smoothing closes the gap to the feature's real signal. The numbers are computed live from a simulated dataset.

Target encoding: leaky vs out-of-fold
Naive encoding lets a row see its own label — rare categories get encoded to their own y, so train AUC soars and val AUC collapses. OOF removes the self-reference; smoothing shrinks noisy rare-category means toward the global rate. Watch the gap.
train AUC
validation AUC
train − val gap
verdict
Reading
Toggle the encoding and slide smoothing to see the leakage gap open and close.

Interview prompts you should be ready for

  1. "Deep models learn the crosses — isn't feature engineering obsolete?" (Half-true and a trap. The cross-network learns interactions among features you supply; it can't extract a signal you never logged, encode a categorical, scale a numeric for a non-tree model, or prevent leakage. Feature engineering moved from hand-written crosses into encoding, scaling, leakage control, and serving parity — and is still where most wins and most failures come from.)
  2. "Walk me through target encoding and how it leaks." (Replace a category with its mean label E[y|c]. The leak: a row's encoding is a statistic that includes its own label, so rare categories get encoded to their own y — train AUC soars, val collapses. Fix with out-of-fold encoding, so each row is encoded from folds that exclude it, plus smoothing toward the global mean, enc = (n·ȳ_c + m·ȳ)/(n+m), so thin categories fall back to the population rate.)
  3. "You have a product taxonomy with millions of leaf categories, most seen only a few times. Why does flat one-hot or flat target encoding fail, and what do you do instead?" (Flat one-hot makes a 10⁶-wide block where 99% of rows get <10 gradient updates, so cold-leaf vectors never leave random init; flat target encoding leaks (a leaf seen once encodes to its own label) and, even smoothed toward the global mean, collapses a rare leaf to the platform average — discarding the real "it's a phone" signal. Fix: encode the hierarchy so a sparse leaf falls back to its parent, not to global noise — parent-conditioned target encoding with per-level Bayesian smoothing enc = (n·leaf_mean + α·parent_mean)/(n+α), kept out-of-fold at every level; plus path encoding, Node2Vec on the taxonomy tree, or multi-granularity bucketing.)
  4. "In parent-conditioned target encoding, work the shrinkage for a leaf with n=3 vs n=300." (With parent_mean=4% and α=10: n=3 leaf with raw 66.7% → (3·0.667+10·0.04)/13 ≈ 18.5%, only 3/13≈23% weight on the noisy leaf, so it sits near the parent's 4% — it inherits "phone." n=300 leaf with raw 7% → (300·0.07+10·0.04)/310 ≈ 6.9%, 300/310≈97% weight, essentially its own signal. α is the half-trust count (n=α gives 50/50); same formula, n decides earned identity. At n=0, enc = parent_mean exactly, which is also how you transfer-init a brand-new leaf's embedding from its parent rather than from random noise.)
  5. "When does feature scaling matter and when is it a no-op?" (Essential for linear models, NNs, and distance/kernel methods — un-scaled features make GD zig-zag, distort L2 regularization, and let big-unit features dominate a distance. A no-op for trees: a split x < t is invariant to any monotone transform, so XGBoost users skip scaling. Saying "always standardize" reveals you don't know which model you're feeding.)
  6. "Why bucketize a continuous feature, and equal-width vs quantile?" (A flat model can't bend a curved monotone effect; pre-binning and giving each bucket its own weight/embedding hands it the curve for free. Equal-width is outlier-sensitive; equal-frequency/quantile puts equal counts per bin and is the robust default. Cost is within-bucket resolution; trees don't need it.)
  7. "How do you measure feature importance, and what's the catch?" (Filter = univariate MI/chi-square (blind to interactions); wrapper = RFE/forward-backward (expensive); embedded = L1/tree-gain (one fit, model-specific). Permutation importance is the model-agnostic default — shuffle a column, measure score drop — but under correlated features it under-credits both, because the model leans on the un-shuffled twin. Permute groups together or use SHAP.)
  8. "What's VIF and when do I care?" (VIF_j = 1/(1−R²_j) from regressing feature j on the rest; >5–10 flags collinearity. It wrecks linear-model coefficients — giant canceling weights make the interpretation fiction — but not tree or deep predictions, which just pick a redundant feature and predict fine. Care when you ship or explain a linear model; mostly ignore for accuracy on a deep ranker.)
  9. "A new feature lifts offline AUC but does nothing online. What happened?" (Classic leakage or train-serve skew. Offline it saw information unavailable at serving — a look-ahead window, a target-derived statistic — or it's computed by a different code path online (different missing-value default, stale window, scaler max). The cure is the principle: a feature is only as good as it's computable at serving time with the same code path — share one transformation library or a logging feature store.)
Takeaway
Deep models automated one thing — learning interactions among the features you hand them — and nothing upstream. Feature engineering is now the discipline of building a correct, serving-computable representation: encode categoricals by cardinality (one-hot small, embed/target-encode high, hash unbounded; never label-encode a nominal feature for a non-tree model); scale and de-skew numerics for linear/NN/distance models (and skip it for trees); select features knowing permutation importance under-credits correlated twins and VIF only bites linear coefficients; and treat leakage — target, look-ahead, train-serve skew — as the cardinal sin. Out-of-fold + smoothed target encoding, point-in-time joins, and one shared transformation code path are the three habits that separate a model that wins in the notebook from one that wins in production.