Part X: Sensor Fusion, World Models, and Spatial AI
Chapter 50: Deep Multimodal Fusion and Missing-Modality Robustness

Modality-specific encoders

"You would not read a barcode and a sonnet with the same eye. Do not ask one network to."

A Specialized AI Agent

Prerequisites

This section assumes you can build the per-signal backbones it composes: 1D convolutions and temporal models from Chapter 13, and the tokenize-then-attend pattern from Chapter 15. It uses the sampling-rate and synchronization vocabulary from Chapter 3, and it sits directly on the early/mid/late fusion taxonomy of Chapter 48. Deep learning basics (backprop, normalization layers) are collected in Appendix B. How these encoder outputs are then combined is the work of the rest of this chapter, not this section.

The Big Picture

Before any deep fusion mechanism can mix modalities, each modality has to be turned into something mixable. A raw camera frame is a \(3\times H\times W\) grid; a lidar sweep is an unordered set of \(N\times 3\) points; an inertial burst is a \(6\times T\) time series at 200 Hz; a temperature log is one number a minute. These live in incompatible spaces with incompatible statistics, sampling rates, and geometry. A modality-specific encoder is the network that maps one such stream into a common latent representation, a fixed-width set of embedding vectors, so that fusion downstream sees only vectors of the same size and meaning. Get this stage right and fusion becomes a clean linear-algebra problem; get it wrong and no cross-attention or gating in later sections can rescue it.

One encoder per modality, and why not one for all

The design rule is blunt: give every modality its own encoder, tuned to that modality's structure, and make them all agree on only one thing, the width \(d\) of the vectors they emit. The what is a set of functions \(f_m: \mathcal{X}_m \to \mathbb{R}^{n_m \times d}\), one per modality \(m\), each turning its native input into \(n_m\) tokens of dimension \(d\). The why is inductive bias. A convolution assumes translation-equivariant local structure, which is true of images and spectrograms and false of a bag of lidar points. A permutation-invariant set encoder is right for that point cloud and wrong for a time series, where order is the signal. Forcing one architecture across modalities means every modality but one is being processed under an assumption it violates.

The how is to reuse the right backbone from earlier parts of the book. Images and depth maps go through a CNN or vision transformer; point clouds and lidar sweeps through a set or voxel network of the kind built in Chapter 42; inertial, audio, and biosignal streams through the 1D convolutional or transformer encoders of Chapter 13; a handful of scalar tags through a small MLP. The when to break the rule is worth stating precisely: share an encoder only when the streams are genuinely the same modality, three identical accelerometers on one body, a stereo pair of the same camera, where a shared \(f_m\) both saves parameters and enforces a consistent representation. Distinct physics means distinct encoders.

Key Insight

The encoder's job is not just to compress; it is to relocate. Two modalities that describe the same event, a spoken word and the lip motion that produced it, start in spaces where their vectors are uncomparable. A good pair of encoders places both near each other in \(\mathbb{R}^d\), so that a simple dot product or a shared attention layer can relate them. This is why the projection to a common width is not a formality: it is the coordinate system fusion will reason in. The encoders decide what "close" means before fusion ever measures it.

Making heterogeneous streams commensurable

Three concrete mismatches have to be resolved inside the encoders, or they leak into fusion as bugs. The first is dimensionality: each encoder ends in a projection to the shared width \(d\), typically a linear layer on a pooled or per-token feature, so a 2048-channel CNN feature and a 128-channel IMU feature both leave as \(d\)-vectors. The second is token count. An encoder can emit one summary vector per modality (\(n_m = 1\), cheap, discards structure) or a sequence of tokens (\(n_m > 1\), preserves where and when, which the cross-attention of Section 50.2 needs). The choice is an accuracy-versus-cost knob you set here.

The third and most treacherous is rate and phase. Modalities arrive at wildly different sampling rates and with real time offsets, exactly the synchronization problem of Chapter 3. Two disciplines keep this honest. Encode each stream over a shared time window rather than a shared sample count, so a 200 Hz IMU and a 30 Hz camera covering the same second stay aligned in meaning even though their token counts differ. And normalize per modality: an accelerometer in \(\text{m/s}^2\), a pixel in \([0,1]\), and a temperature in kelvin cannot share one normalization statistic, so each encoder owns its own input scaling. On the leakage-safe discipline of Part I, these normalization statistics are fit on training data only; a normalizer fit across the whole set silently leaks test-time distribution into every modality at once.

import torch, torch.nn as nn

D = 256   # shared embedding width every modality must emit

class IMUEncoder(nn.Module):        # 6-axis inertial, (B, 6, T) at high rate
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv1d(6, 64, 7, stride=2, padding=3), nn.GELU(),
            nn.Conv1d(64, 128, 5, stride=2, padding=2), nn.GELU(),
            nn.AdaptiveAvgPool1d(1))          # one summary token
        self.proj = nn.Linear(128, D)
    def forward(self, x):
        return self.proj(self.net(x).squeeze(-1)).unsqueeze(1)   # (B, 1, D)

class ImageEncoder(nn.Module):      # camera frame, (B, 3, H, W)
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(3, 32, 3, stride=2, padding=1), nn.GELU(),
            nn.Conv2d(32, 64, 3, stride=2, padding=1), nn.GELU(),
            nn.AdaptiveAvgPool2d(2))          # a 2x2 grid = 4 tokens
        self.proj = nn.Linear(64, D)
    def forward(self, x):
        f = self.net(x).flatten(2).transpose(1, 2)   # (B, 4, 64)
        return self.proj(f)                           # (B, 4, D)

imu  = IMUEncoder()(torch.randn(8, 6, 200))     # (8, 1, 256)
cam  = ImageEncoder()(torch.randn(8, 3, 64, 64))# (8, 4, 256)
tokens = torch.cat([imu, cam], dim=1)           # (8, 5, 256) ready for fusion
print(imu.shape, cam.shape, tokens.shape)
Listing 50.1. Two modality-specific encoders that disagree on everything except output width. The IMU path is a 1D convnet emitting a single token; the image path is a 2D convnet emitting a small token grid. Both project to D=256, so their outputs concatenate into one (B, 5, 256) tensor that any fusion block in this chapter can consume. Note the different forward shapes: the shared contract is only the last dimension.

As Listing 50.1 shows, the interface between "sensing" and "fusion" is exactly this shape agreement. Everything specific to a sensor, its geometry, its rate, its physics, is spent inside its own encoder; everything after the concatenation is modality-blind. That clean seam is what lets you swap a better image backbone in without touching the fusion head.

In Practice: a fall-detection smartwatch

A wrist wearable fuses three streams to distinguish a genuine fall from a dropped watch or vigorous handwashing. A 100 Hz six-axis IMU carries the impact and posture change; a green-LED PPG at 25 Hz carries the pulse spike of a startle response; a low-rate barometer carries the roughly one-meter altitude drop of a body hitting the floor. Each gets its own encoder: a dilated 1D convnet for the IMU burst, a short temporal encoder for the PPG segment (built on the cardiovascular models of Chapter 30), and a two-layer MLP for the barometer window. They emit one 128-dim token each into a shared space. Because the encoders normalize independently, the barometer's slow millibar drift never has to share a scale with the IMU's \(\pm 16g\) spikes. The payoff surfaces in the next sections: when the PPG channel is lost to a loose strap, the fusion head still has two clean tokens to reason over, precisely because each modality was encoded on its own terms rather than crammed into a shared front end that assumed all three were present.

Pretrained backbones and where to freeze

Encoders rarely start from scratch. The single biggest lever on multimodal accuracy is initializing each \(f_m\) from a strong single-modality model, an ImageNet or CLIP vision backbone, an audio encoder, a self-supervised inertial model from Chapter 17, then training only a thin projection plus the fusion layers on top. The why is data economy: paired multimodal data is scarce and expensive, while unimodal pretraining data is abundant, so you want most of each encoder's competence to come from cheap single-modality learning. The when to freeze versus fine-tune is a small decision tree: freeze the backbone and train only the projection when paired data is tiny or the modality is well covered by its pretrained model; unfreeze the top blocks when your sensor's domain (a wrist accelerometer, a thermal camera) drifts far from the backbone's training distribution. Either way you keep the projection head trainable, because that is the layer that rotates each modality into the shared space fusion expects.

The Right Tool

Hand-building and pretraining a competitive vision backbone is thousands of lines and days of compute you do not need to spend. A pretrained encoder plus a projection is two lines with timm:

import timm, torch.nn as nn
backbone = timm.create_model("vit_small_patch16_224", pretrained=True, num_classes=0)
img_encoder = nn.Sequential(backbone, nn.Linear(backbone.num_features, 256))  # -> (B, 256)
Listing 50.2. A production-grade image encoder in two lines: timm supplies the pretrained ViT and its weights, and a single linear layer projects to the shared width. This replaces roughly 300 lines of model definition plus a multi-day pretraining run with one call; timm and transformers expose hundreds of such backbones for image, audio, and text under the same interface. What the library will not decide for you is the shared width and which blocks to freeze.

As Listing 50.2 shows, the library owns the encoder; you own the interface it plugs into. The judgment about token count, freezing, and per-modality normalization stays with the architect.

Research Frontier

The strict one-encoder-per-modality rule is being softened at the frontier. Perceiver IO (Jaegle et al., 2021) feeds raw bytes from many modalities into a single attention stack with a shared latent array, and ImageBind (Girdhar et al., 2023) trains six modalities, image, text, audio, depth, thermal, and IMU, into one embedding space by binding each to images, so that modalities never seen together still land nearby. Meta-Transformer (Zhang et al., 2023) pushes further, sharing a single frozen transformer across twelve modalities with only lightweight per-modality tokenizers in front. The trend is thinning the modality-specific part down to a small tokenizer and letting a shared backbone do the heavy lifting, which is exactly the sensor-language direction of Chapter 21. For most sensor stacks today, though, separate encoders with a shared width remain the reliable default.

Exercise

Take the three-stream smartwatch from the In-Practice box. Sketch the tensor shape at the output of each encoder if you choose (a) one summary token per modality versus (b) one token per 0.2 s window over a 2 s clip. For each design, state the total token count entering fusion and one consequence: memory cost, and whether a later cross-attention layer could localize when in the window the fall occurred. Then decide which barometer normalization statistics you would compute and on which split, and explain what leaks if you compute them on the full dataset.

Self-Check

1. Why is a permutation-invariant set encoder correct for a lidar sweep but wrong for an IMU stream, and what does each assume about its input?

2. Two encoders emit features of width 2048 and 128. What single layer makes them fusable, and what property of the shared space does it establish?

3. You have abundant unimodal pretraining data but only a few thousand paired examples. Which parts of each encoder do you freeze, which do you train, and why?

What's Next

In Section 50.2, we take the aligned token sequences these encoders produce and let the modalities actually talk to each other, using cross-attention and transformer fusion to route information between streams rather than merely concatenating them, so that the camera tokens can be sharpened by the radar tokens and back again.