Introduction
A pressure sensor has no eyes. It cannot see a face, a limb, or a silhouette. All it has is a grid of numbers, each one a force reading at a fixed point in space. From that alone, our bed already does three things: it tells apart lying positions, it locates the body's major joints, and, separately, it recognizes who is on it. This post is about the first two, posture and keypoints, and specifically about one question: do these models actually generalize to people they were never trained on, or do they just memorize the people we happened to collect data from?
Motivation
A model that only works on its training subjects is not a product, it is a demo. Our earlier work on Pressure ID solved a different problem: recognizing a small, fixed set of enrolled users across new sessions. That is a closed-set problem, the same shape as Face ID. Posture and keypoint estimation are open-set problems. Every new person who lies on the bed is someone the model has never seen during training, and it has to work anyway. That is the harder and more interesting claim, and it is the one this post is about.
Ideology
The easy way to validate a model is to split the dataset randomly, sample by sample. This is the wrong way to do it for us, and it is worth explaining exactly why, because the reason generalizes beyond this one project. A single sleep session produces thousands of correlated pressure frames: the same body, the same day's noise characteristics, repeated frame after frame. If a random split lets some frames from a session land in training and other frames from that same session land in validation, the model does not need to learn anything general about posture or anatomy to score well. It only needs to recognize the session it has already partially seen. The validation number in that setup is measuring memorization dressed up as generalization.
The honest test is to split by session and by subject, so that every session used for validation is completely absent from training, not just the frames but the entire session. We checked this directly rather than assuming it: at training time we verified that no session ID ever appeared in both the training and validation sets for any fold, for every single fold we report. Only numbers from folds that passed this check are reported here. This sounds like a small procedural detail, but it is really the difference between a result that means something and one that does not.
There is also a deliberate architectural reason we built this as a convolutional network rather than a simpler feature-based model, and it goes beyond raw accuracy on any one task. A CNN trained on the raw pressure map is forced to build an internal, learned representation, a hidden latent space, that captures the underlying structure of how a body loads a surface, rather than a representation hand-built around one specific label. That shared latent space matters for a specific reason: it gives us a way to test, later, whether a single generalized model, one shared backbone producing both posture and keypoints from the same learned representation, can match or beat the two separate, task-specific models reported in this post. Building this as a CNN now is what makes that comparison possible at all. This is the practical reason posture and keypoints in this post share the same underlying design philosophy even though they output very different things: the expensive part, learning what pressure distributions mean physically, is shared, and only the cheap part, mapping that understanding to a specific output, changes per task.
One more constraint we build into this pipeline that is easy to overlook: we do not let the model infer body measurements from pressure alone as pure guesswork. Height, weight, and limb length for each subject are measured directly and supplied as additional inputs alongside the pressure map. This matters because a pressure map is inherently ambiguous about scale, a tall, light person and a shorter, heavier person can produce contact patterns that are hard to tell apart from pressure alone. Providing real anthropometric measurements as an extra constraint removes that ambiguity before the model has to resolve it statistically, the same way giving a navigation system a known starting GPS coordinate removes an entire class of guesswork it would otherwise have to do from dead reckoning alone.
Experimentation
Posture
The posture model classifies the input into seven classes from the raw pressure map alone, using a compact convolutional network: six genuine body postures, plus a seventh class for when the bed is simply empty. Out-of-bed is not a body posture, it is a distinct physical state with a near-trivial pressure signature, and it is worth keeping that distinction in mind throughout this section rather than treating all seven classes as equally hard body-posture problems. Why does classification work at all from pressure alone, with no visual signal? Different postures redistribute body mass onto entirely different contact regions and produce distinct contact-area shapes: lying flat concentrates load along the spine and hips in a long, narrow band; lying on one side shifts nearly all mass onto a single hip and shoulder, producing a shorter, asymmetric blob; a seated-up posture pulls the high-pressure region toward the lower back and away from the legs entirely. The network is not learning "what a body looks like," it is learning the mapping from load distribution to posture class, which turns out to be a highly separable mapping once enough sessions are seen.
We validate with 5-fold session-disjoint StratifiedGroupKFold, grouped on session ID, across roughly 50 independent sleep sessions. The fold sizes below are not an even 80:20 split by session count, and that is intentional. Sessions are not uniform, some sessions barely touch certain postures while others sit in one posture for most of their length. Splitting by raw session count alone risks a fold where one class has hundreds of training samples but only a handful in validation, or the reverse, and a class with thin validation support produces an accuracy number for that class that is closer to noise than to a measurement. Stratifying by class forces every fold's validation set to carry a comparable, meaningful amount of every posture class, even if that means the session counts across folds come out uneven, 45 versus 6 in one fold, 38 versus 13 in another. The uneven session split is the price paid to keep the number that actually matters, per-class sample balance, trustworthy.
| Fold | Train sessions | Val sessions | Best val accuracy |
|---|---|---|---|
| 1 | 45 | 6 | 0.9136 |
| 2 | 39 | 12 | 0.8828 |
| 3 | 41 | 10 | 0.9028 |
| 4 | 41 | 10 | 0.9182 |
| 5 | 38 | 13 | 0.9279 |
| OOF ensemble | — | — | 0.9090 |
Fold 2 stands out as the weakest of the five, and the more useful way to read that is not "this fold performed worse" but "the subjects held out in this fold's validation set were not well represented by whatever variation existed in that fold's training set." With roughly 50 sessions total, any given fold's training split can simply miss certain body types or postural variations that happen to be concentrated in that fold's held-out sessions, and when that happens the model has genuinely never learned the variation it is being asked to generalize to, rather than failing to learn something it saw. This is a population coverage problem, not a capacity or training problem.
This spread across folds is also the honest way to talk about how confident we should be in the headline 90.9% number. The five fold accuracies (0.9136, 0.8828, 0.9028, 0.9182, 0.9279) have a standard deviation of about 0.017 around that mean, which gives a rough 95% confidence interval of approximately 88.8% to 93.0% for the true generalization accuracy of this model, given only five folds' worth of evidence. 90.9% is the best point estimate available, not a number with the precision its single-decimal presentation implies.
Keypoints
Keypoint regression has to solve a harder inverse problem than posture classification: instead of a single label, it must recover nine continuous 2D coordinates from a pressure map. Intuition for why this is even possible from pressure alone: a joint's location correlates with local curvature and pressure gradient, not just raw magnitude. A hip produces a broad, high, smoothly varying pressure peak because it sits over a stable, well-supported bone structure. An ankle produces a narrow, unstable, often partially occluded pressure signature because it is light, mobile, and prone to being blanket-covered or lifted slightly off the surface. This is exactly the pattern the results below show: the model is most confident exactly where the physical signal is most stable, and least confident exactly where it is most variable.
The model regresses the 2D location of nine anatomical landmarks, head, shoulders, hips, knees, and ankles, directly from the pressure map, using a width-48 ResNet-style backbone with GroupNorm and GELU activations, approximately 1.57M parameters. Performance is reported as pixel error in out-of-fold (OOF) predictions, meaning every validation prediction comes from a fold where that session was never part of training. We also report
the fraction of predicted joints falling within $k$ pixels of ground truth, a stricter and more interpretable measure than mean error alone. At 1.905 cm per pixel (the full pressure array maps a 7ft by 6ft bed surface), 10 pixels corresponds to 19.05 cm, roughly 190mm, so PCK@10px is asking: what fraction of predicted joints land within about 19cm of the true location, on a person the model has never trained on. That is a wide tolerance, close to forearm length, not a tight few-centimeter claim, and should be stated as such rather than implying sub-centimeter precision. Each fold trains until validation error stops improving; the epoch listed below is the checkpoint actually used, the point where that fold's model hit its lowest validation error before early stopping cut training off, not the total epochs run.
| Fold | Best val error (px) | Epoch of best checkpoint |
|---|---|---|
| 1 | 9.149 | 61 |
| 2 | 8.016 | 83 |
| 3 | 6.804 | 77 |
| 4 | 6.905 | 69 |
| 5 | 7.441 | 58 |
| OOF mean | 7.731 | — |
| Joint | OOF error (px) |
|---|---|
| Head | 7.04 |
| L. shoulder | 6.85 |
| R. shoulder | 6.84 |
| L. hip | 5.92 |
| R. hip | 6.20 |
| L. knee | 8.55 |
| R. knee | 8.59 |
| L. ankle | 9.89 |
| R. ankle | 9.71 |
Results
| Task | Metric | Result | Validation |
|---|---|---|---|
| Posture | OOF accuracy | 90.9% | 5-fold, session-disjoint |
| Keypoints | OOF mean error | 7.731 px (≈14.7 cm / 147 mm) | 5-fold, session-disjoint |
| Keypoints | PCK@10px | 0.800 | 5-fold, session-disjoint |
Two things are worth noting about the posture result. One class, corresponding to an empty bed, is nearly perfectly separable (precision and recall above 0.99), which raises the macro-average somewhat; the genuinely hard classes sit closer to 0.87 to 0.89 F1. This means the 90.9% headline number is a mild overstatement of difficulty on the classes that actually matter for the product, actual body-posture discrimination is closer to high-80s once the trivial empty-bed class is excluded, and that more honest number should be the one used internally for comparison against future model versions.
For keypoints, error is not uniform across the body, and the pattern is consistent across every fold, not just in the aggregate. Hips are the easiest landmark to localize (under 6.2 px), ankles the hardest (around 9.7 to 9.9 px), with knees sitting in between and shoulders and head clustering close to the hip numbers. This tracks physical intuition directly. Hips sit over the region of highest, most stable interface pressure, close to the body's center of mass, and barely move relative to the torso during a given posture. Ankles are lighter, sit at the far end of a long lever arm from the hip, move independently and frequently during sleep, and are more often partially occluded from clean pressure signal under a blanket's folds. The roughly 4 pixel gap between best and worst joint is not noise, it is the model correctly reflecting where the physical signal itself is and is not stable.
Limitations
Everything above is validated on a single device, our own bed's pressure array. We have not yet tested whether these models, or the underlying idea, transfer to a different device geometry, such as our chair product. That is a genuinely open question, not a claim being made here. Separately, and worth stating plainly: Pressure ID's 88% accuracy figure (against a 55% naive baseline) is a different kind of result from the two above. It reflects recognition of a small, fixed, enrolled set of users across new sessions, not generalization to a person never seen before. Readers should not conflate the two claims.
The 55% naive baseline here is simply the prior probability of the majority class in that dataset, the accuracy achievable by always guessing the more frequent enrolled user without looking at the pressure map at all.
There is a more fundamental limitation underneath all of the numbers in this post, and it deserves to be stated directly rather than buried in a footnote. Roughly 50 sessions from a limited pool of subjects is not the population. It does not cover the full range of body structures, weights, heights, and sleep behaviors that exist in the real world, and there is no honest way to know from this dataset alone how these numbers would hold up against that full range. Our working assumption, and it is an assumption, not a measured result, is that collecting genuinely population-scale data would show a drop from the accuracy and error numbers reported here, not an improvement, simply because more real-world variation gives the model more ways to be wrong that the current dataset never had the chance to expose. Every number in this post should be read as "the best we currently know, from the data we currently have," not as a ceiling or a guarantee.
Further Work
The natural next test is cross-device transfer: does a model trained on bed pressure data carry any useful structure to chair pressure data, or does each device need to relearn everything from zero. Beyond that, there are two concrete, well-understood levers left to pull on the numbers reported here, and it is worth being specific about both rather than leaving "more work needed" as a vague closing line.
The first is scale of data collection, plainly and simply having more subjects and more sessions. Fifty sessions is enough to prove the underlying idea works and to show that generalization across people is real, but it is not enough to claim it works evenly across every body type or every mattress interaction pattern that exists in the real population. The overfitting behavior visible in the posture training curves, train loss collapsing within a handful of epochs while validation loss climbs for the rest of training, is itself a symptom of a dataset this size relative to model capacity, not a flaw in the architecture. A model that overfits this quickly on this little data is exactly the kind of model that should be expected to improve substantially, both in raw accuracy and in the size of the overfitting gap itself, as the subject pool grows. This is the single highest-leverage, least glamorous improvement available: more people, more sessions, more diversity in body type.
The second lever is noise, and it comes from two distinct physical sources that should not be conflated. One is sensor-level noise: the piezoresistive pressure array itself has measurement noise and calibration drift over time, both of which sit underneath every downstream model as a noise floor no amount of training data can fully remove. The other is mattress-level noise: the foam stack between the body and the sensor is not a passive, linear medium, it has load-dependent stiffness and spreads and distorts the true contact pressure before the sensor ever measures it, which means part of what these models are learning to do is compensate for physical distortion introduced before the signal reaches them at all. Reducing noise at the sensor level, through better hardware and tighter calibration, and reducing distortion at the mattress level, through deconvolution rather than treating foam nonlinearity as unavoidable, both raise the ceiling on what any model, including this one, could ever achieve on this data. Improving the model further without addressing these two sources first would mean asking the model to keep compensating for a noisier, more distorted signal than it needs to, which is a less efficient use of effort than fixing the signal itself.
Beyond these two general levers, each task also has its own specific next step. For posture, the direct plan is to push accuracy up by deliberately collecting across much greater population variance than the current dataset covers, rather than collecting more sessions from a similar pool of subjects. For keypoints, the current model is limited to two-dimensional coordinates in the plane of the pressure array; the planned next step is extending this to full three-dimensional keypoint estimation, recovering height off the mattress surface in addition to position on it, which is a meaningfully harder regression problem but a necessary one for anything that needs real spatial understanding of the body rather than a flattened projection of it.