Your Model Is Not Underfit. Your Features Are Impersonal.

Data science teams respond to plateaued model performance by trying bigger architectures, more hyperparameter search, and more training data. The constraint is usually none of those. It is that every feature describes a segment rather than a person.

Published 2026-07-10 · 6 min read

Your Model Is Not Underfit. Your Features Are Impersonal.

The Plateau Every Data Science Team Recognizes

The churn model works. Not well, but it works. Precision hovers somewhere around fifty percent, which means half the customers flagged as at-risk were never going to leave, and the customer success team has quietly stopped treating the alerts as urgent.

The standard response is escalation. Try gradient boosting instead of logistic regression. Then try a neural network. Then add more training data. Then tune harder. Each round produces a marginal improvement — a point or two of AUC — and then the curve flattens again.

At some point someone suggests the problem might be the features. This is usually correct, and it is usually interpreted too narrowly. The team goes looking for more features rather than differently shaped ones, and the plateau holds.

The constraint is not the number of features. It is that nearly all of them describe segments rather than individuals.

What Aggregate Features Actually Encode

Look at a typical churn feature set.

Days since last login. Sessions in the trailing thirty days. Feature adoption count. Support tickets filed. Plan tier. Account age. Monthly spend. Team seat count.

Every one of these is a population-comparable metric. Each answers "where does this account sit relative to other accounts?" And each carries an implicit and usually false assumption: that a given value means the same thing for every user.

Consider "logged in twice in the last thirty days." For a user whose established rhythm is daily engagement, this is a severe deterioration signal — an eighty percent drop from their own norm, and probably the strongest churn predictor available. For a user who has logged in twice a month for two years and renews reliably every cycle, it is a perfectly healthy reading.

The feature value is identical. The meaning is opposite.

A model trained on this feature has to learn a single coefficient covering both cases. It cannot resolve the contradiction, so it converges on something in the middle — a weak signal that is wrong in both directions. Add a hundred more features shaped the same way and the model gets a hundred more muddled signals. That is the plateau, and no architecture change fixes it, because the information required to disambiguate was never in the feature set.

The Deviation Reframe

The fix is conceptually simple and operationally demanding: express features as deviation from the individual's own baseline rather than as absolute values compared across a population.

Instead of "sessions in last 30 days," compute "sessions in last 30 days relative to this user's trailing 90-day norm." Instead of "average session duration," compute "session duration trend against this user's own established pattern." Instead of "support tickets filed," compute "ticket rate change relative to this account's baseline."

This transformation changes what the model can learn. A deviation feature means the same thing for every user, because it has already been normalized against that user's own history. Negative eighty percent engagement is a meaningful signal whether the baseline was daily or monthly. The model no longer has to average across incompatible populations, so the coefficient it learns is sharp instead of muddy.

Teams that make only this change — same algorithm, same training data, same everything else — frequently see the largest single jump in model performance of the entire project.

Three Clocks, Not One

Baseline-relative features are necessary but not sufficient. The second thing that separates a good behavioral feature set from a mediocre one is respecting that different signals move at different speeds.

Trait-level signals are stable dispositions: how thoroughly someone reads before acting, tolerance for complexity, exploratory versus directed navigation style. These shift over months. Computing them on a seven-day window produces noise, not signal.

Preference-level signals drift over weeks: channel responsiveness, time-of-day patterns, format preferences, feature affinity.

State-level signals move within a single session: stress, urgency, confusion, decision readiness. Computing these on a ninety-day window destroys them entirely — the very thing that makes state informative is that it is current.

Most feature engineering picks one window, usually thirty days, and applies it to everything. That window is too short to measure traits reliably and far too long to preserve state. The result is a feature set where the slow signals are noisy and the fast signals have been averaged out of existence.

Fixing this means maintaining three windows and letting each signal live in the one that matches its natural rhythm. That is a meaningful engineering burden, and it is one of the main reasons teams do not do it.

Signals Sitting Unused in the Event Stream

Beyond reshaping existing features, there is a category of signal most teams never extract at all. It is already in the event stream, and it is discarded because it does not fit the aggregate framing.

Timing between actions. Not session duration, but the intervals within a session. The gap between opening a screen and acting on it. Hesitation before confirmation. These are direct measurements of decision difficulty, and they are almost never featurized.

Navigation sequence. Not which screens were visited, but in what order and with how much backtracking. Revisiting a pricing page three times in one session is different from viewing it once, and both are recorded as one pageview event by most aggregation logic.

Abandonment position. Where in a flow someone stopped. Abandoning at step one and abandoning at the final confirmation are opposite signals compressed into the same "did not complete" flag.

Response latency to outbound contact. How long between a notification and engagement with it, measured against that user's own norm. A user who normally responds in five minutes taking two days is a state change worth modeling.

Rhythm variance. Not average interval between sessions, but the stability of that interval. Increasing irregularity often precedes disengagement well before average frequency drops.

Each of these requires per-user statefulness — you cannot compute them with a group-by over an events table. That is precisely why they are missing from most feature sets, and precisely why they carry information the existing features do not.

Why Teams Build This Once and Regret It

The obvious next step is to build a behavioral feature pipeline internally. Some teams do, and the ones who have will recognize the following.

Maintaining per-user state across three time horizons is a stateful streaming problem, not a batch aggregation problem. Backfilling changes to baseline definitions means recomputing history. Keeping state-level features fresh enough to matter requires a low-latency path that a nightly job cannot provide. Serving these features to a model in production means solving training-serving skew for features that are defined relative to a moving per-user reference point.

None of this is impossible. All of it is a dedicated infrastructure project running parallel to the modeling work the team was actually hired to do. The usual outcome is a partial implementation: baseline-relative features for a few key metrics, a single time window, batch computation, and state signals quietly dropped because keeping them fresh was too hard.

That partial implementation still helps. It also leaves most of the available lift on the table.

The Production Result

In the Fortics deployment, Fluence processed 3.4 million behavioral profiles built entirely from interaction patterns — timing, navigation, hesitation, channel response, session rhythm. No survey data, no demographic inference, no message content.

Machine learning model accuracy improved 3.5x over the existing methods. Churn fell 40%. Conversion rose 2.3x.

The 3.5x figure is the one that matters for a data science audience, and the mechanism behind it is unglamorous. The models did not change architecture. They received per-user behavioral features — baseline-relative, computed across three time horizons, including state-level signals that were fresh at inference time — instead of population-comparable aggregates.

The algorithm was never the constraint. The features were describing segments, and the model was doing exactly what it should with segment-level information: producing segment-level predictions.

Fluence exposes this as infrastructure rather than as a project. A \GET /context/{user_id}\ call returns model-ready behavioral context — traits, preferences, and current state — that can feed a feature pipeline or go directly into an LLM's context window. It reads the event stream a platform already produces, which is why integration typically takes under 10 hours rather than a quarter of pipeline work. It analyzes behavioral patterns and never content, making it LGPD and GDPR compliant by architecture.

Before reaching for a bigger model, check whether any feature in the current set knows the difference between a user who changed and a user who was always like this. If none of them do, the architecture was never the problem.

👉 See what per-user behavioral features look like in a single API call →