Part IV: Temporal Representation Learning
Chapter 16: Learning Temporal Representations

Temporal Embeddings and Disentanglement

"They trained me to carry meaning, and I obliged: one of my axes tracks the trend, one breathes with the season, one remembers which sensor I came from. Then a colleague glanced at me and announced I was 'entangled', that my third axis was secretly humming the seasonal tune behind everyone's back. I would like the record to show that I only ever wanted to mean one thing per dimension. It is the world that refuses to factor."

A Latent Dimension That Only Wants to Mean One Thing
Big Picture

The previous sections of this chapter taught an encoder to turn a window of a time series into a vector. This section is about what that vector is for. Once a subsequence or a whole series lives at a point in $\mathbb{R}^{d}$, distance becomes similarity, direction becomes a factor of variation, and a long list of downstream problems collapse into geometry: find me series that behave like this one (nearest-neighbor retrieval), group the regimes a sensor passes through (clustering), flag the window that sits far from all the normal ones (embedding-distance anomaly detection, the learned successor to the change-point detectors of Chapter 8), and feed a compact summary of an entity's history into a forecaster (series and entity embeddings, the temporal cousins of word embeddings). The second half of the section asks a harder question: not just where the points land, but whether the axes mean anything. A disentangled representation is one whose coordinates align with the true generative factors of the data, the slow trend separated from the repeating season separated from the noise, the identity of a series separated from its moment-to-moment dynamics. Disentanglement is what turns an embedding from a useful black box into something you can interpret and, in Chapter 17, control: change one coordinate, change one factor, generate a new series. We build the retrieval and visualization pipeline from scratch, pair it with a library that does the same in a fraction of the lines, compute a cosine similarity by hand, and close Chapter 16 by handing the representation to the generators of Part IV's next chapter.

In Section 16.4 we finished assembling the machinery that produces a temporal representation: an encoder $f_\theta$ trained by a self-supervised or contrastive objective so that semantically similar windows land near each other. We have, in other words, a function $\mathbf{z} = f_\theta(\mathbf{x}_{t-w+1:t})$ that compresses a window of width $w$ into an embedding $\mathbf{z} \in \mathbb{R}^{d}$. Everything in this chapter so far has been about learning that function. Now we use it. The unifying idea is simple and powerful: a good encoder turns questions about time series into questions about points in a vector space, and vector spaces are something we know how to search, cluster, and measure. We use the unified notation of Appendix A throughout: $\mathbf{x}_{t-w+1:t}$ a window, $\mathbf{z}$ its embedding, $d$ the embedding dimension, $f_\theta$ the encoder.

Why does the geometry buy so much? Because the encoder has already done the hard nonlinear work. Two heartbeat windows that look different sample by sample, one slightly faster, one slightly shifted in phase, are pushed by a well-trained encoder to nearly the same $\mathbf{z}$, so a Euclidean or cosine distance in embedding space captures a notion of similarity that no distance in the raw sample space could. The downstream tasks of subsection one all exploit this single fact. The interpretability and control questions of subsections two and three push further, asking the encoder not merely to place similar things together but to organize the space so that each direction carries one human-legible meaning. The four competencies this section installs: to use an embedding space for retrieval, clustering, anomaly detection, and as a downstream input; to state what disentanglement is and why a factorized latent aids interpretability and controllable generation; to name the methods that pursue it and the critiques that temper the claims; and to evaluate an embedding with probing, retrieval, and disentanglement metrics.

1. Using Embeddings: Geometry as a Query Engine Beginner

Once a series or subsequence is a vector, four classic problems become one-liners over a distance. We take them in turn, because together they cover most of what a temporal embedding is asked to do in production.

Nearest-neighbor retrieval. Given a query window $\mathbf{x}^{\star}$, embed it to $\mathbf{z}^{\star} = f_\theta(\mathbf{x}^{\star})$ and return the database windows whose embeddings are closest. "Closest" is usually cosine similarity for normalized embeddings (contrastive encoders are trained on a cosine objective, so cosine is the metric the space was shaped for) or Euclidean distance otherwise. The cosine similarity between two embeddings is

$$\operatorname{cos}(\mathbf{z}_a, \mathbf{z}_b) = \frac{\mathbf{z}_a^{\top}\mathbf{z}_b}{\lVert\mathbf{z}_a\rVert\,\lVert\mathbf{z}_b\rVert} \in [-1, 1],$$

with $1$ meaning identical direction (maximally similar), $0$ orthogonal, and $-1$ opposed. Retrieval answers "show me historical windows that behaved like the last hour", the engine behind motif discovery, analog forecasting (find similar pasts, average their futures), and case-based diagnosis in clinical and industrial monitoring.

One practical caveat keeps retrieval honest: an embedding is only as trustworthy as the augmentations that shaped it. A contrastive encoder learns to ignore exactly the transformations you used to build positive pairs, so if you augment with time-shifts the embedding becomes shift-invariant (two windows differing only in phase retrieve each other), and if you do not, phase becomes a retrieval-relevant axis. The retrieval behavior is therefore a design choice encoded in the training augmentations, not a fixed property of the data, and a mismatch between the invariances you trained in and the similarities a user actually wants is the most common reason a retrieval system returns technically-near but practically-irrelevant neighbors.

Clustering regimes. Run $k$-means, DBSCAN, or a Gaussian mixture over the embeddings of consecutive windows of a single series and the clusters recover the regimes the series passes through: a market alternating between calm and volatile, a machine cycling through idle, load, and fault, a patient moving between stable and deteriorating states. This is the learned, unsupervised analogue of the regime-switching state-space models of Chapter 7: there we posited discrete states and fit them; here we let the encoder discover the geometry and let clustering read the states off it. A subtlety unique to the temporal setting is that consecutive windows overlap and so their embeddings move smoothly, which means a clustering that ignores time can fragment a single regime into several clusters at its boundaries; a transition-aware method (or simply smoothing the cluster assignment over time) recovers the discrete regime sequence the change-point detectors of Chapter 8 were built to find.

Anomaly via embedding distance. Fit the embeddings of normal windows, then score a new window by its distance to that normal set, its distance to the nearest normal neighbor, its distance to the nearest cluster centroid, or its density under a fitted model. A window whose embedding sits far from everything the encoder considers normal is anomalous. This is the representation-space successor to the classical detectors of Chapter 8: where Chapter 8 measured surprise in the raw signal or its forecast residual, here we measure surprise in a learned space where the encoder has already discounted the variation it considers normal, so subtle anomalies that are invisible in the raw series can become large displacements in the embedding.

Embeddings as downstream inputs. A frozen encoder is a feature extractor. Embed every window and feed the vectors to a small classifier, regressor, or forecaster. Because the encoder did the representation learning, the downstream head can be tiny and trained on little labeled data, the transfer-learning payoff that motivates the whole chapter. The arithmetic of this payoff is worth making explicit: a raw window of width $w$ across $c$ channels is a $w \cdot c$-dimensional input, often hundreds or thousands of numbers, and learning a forecaster directly on it demands enough labeled examples to fit that many input weights without overfitting. The same window compressed to a $d$-dimensional embedding (with $d$ a few dozen) lets a downstream head fit only $d$ weights per output, so the label budget needed to train it drops by the same ratio. The encoder spent the unlabeled data to buy the labeled data's leverage, which is exactly why a frozen embedding plus a linear head is the standard recipe when labels are scarce but raw series are abundant, the usual situation in clinical monitoring, industrial telemetry, and finance.

It helps to see the four uses as a single workflow rather than four disconnected tools, because in a deployed system they almost always coexist. A monitoring service embeds each incoming window once; that one embedding is then routed simultaneously to a nearest-neighbor index (so an operator can pull up similar historical episodes), to a running clustering or density model (so the current regime is named and tracked), to an anomaly score (so an out-of-distribution window raises an alarm), and to a small predictive head (so the next value or a risk label is forecast). The encoder is the expensive, shared component computed once per window; the four queries are cheap reads over its output. This is the architecture the practical example at the end of the section describes, and it is the reason the chapter spent so long on the encoder: the encoder is the leverage point that every downstream capability inherits.

One encoder, four geometric uses of the embedding z window encoder z nearest-neighbor retrieval clustering of regimes anomaly by distance downstream model head
Figure 16.5.1: A single trained encoder $f_\theta$ turns a window into an embedding $\mathbf{z}$, and four downstream uses all read off the same geometry: retrieval ranks by distance, clustering groups by proximity, anomaly scores by displacement from the normal set, and a small head consumes $\mathbf{z}$ as a feature. The encoder is trained once; the geometry is reused everywhere.

Entity and series embeddings. A distinct and widely deployed idea: instead of (or alongside) embedding the signal, embed the identity. Give every series, store, sensor, or patient an integer ID and learn a dense vector for each, exactly as word embeddings learn a vector per word. The ID embedding is a lookup-table row trained end to end with the forecaster, so the model discovers that store 14 behaves like store 88 by placing their vectors close, sharing statistical strength across similar entities without any hand-coded similarity. This is standard in global forecasting models (DeepAR, Temporal Fusion Transformer, and the deep forecasters of Chapter 14), where one model serves thousands of related series and the entity embedding is how it tells them apart while still sharing what they have in common.

Key Insight: Distance Is the Interface

Retrieval, clustering, anomaly detection, and feature transfer look like four different problems, but to a good embedding they are one problem asked four ways: what is near what? The encoder's entire job is to make Euclidean or cosine distance in $\mathbb{R}^{d}$ agree with the semantic similarity you care about. Once it does, you do not build four systems; you build one embedding and four thin queries over its distance function. This is why representation quality, not algorithmic cleverness in the downstream task, is usually the thing worth improving: a better encoder lifts all four uses at once, while a fancier clustering algorithm over a bad embedding lifts none.

Numeric Example: Cosine Similarity of Two Subsequences

Take two three-dimensional embeddings of two short subsequences (tiny $d$ so we can do it by hand): $\mathbf{z}_a = (2, 1, 2)$ and $\mathbf{z}_b = (1, 0, 2)$. The dot product is $\mathbf{z}_a^{\top}\mathbf{z}_b = 2\cdot 1 + 1\cdot 0 + 2\cdot 2 = 2 + 0 + 4 = 6$. The norms are $\lVert\mathbf{z}_a\rVert = \sqrt{4+1+4} = \sqrt{9} = 3$ and $\lVert\mathbf{z}_b\rVert = \sqrt{1+0+4} = \sqrt{5} \approx 2.236$. So $\operatorname{cos}(\mathbf{z}_a, \mathbf{z}_b) = 6 / (3 \cdot 2.236) = 6 / 6.708 \approx 0.894$, a high similarity (the angle between them is $\arccos 0.894 \approx 26.6^{\circ}$). Now a third subsequence embeds to $\mathbf{z}_c = (-2, 1, -1)$; then $\mathbf{z}_a^{\top}\mathbf{z}_c = -4 + 1 - 2 = -5$, $\lVert\mathbf{z}_c\rVert = \sqrt{6} \approx 2.449$, and $\operatorname{cos}(\mathbf{z}_a, \mathbf{z}_c) = -5/(3\cdot 2.449) \approx -0.680$, pointing in nearly the opposite direction. A retrieval query for $\mathbf{z}_a$ ranks $b$ far above $c$. The lesson in three numbers: cosine reads off direction, ignores magnitude, and is exactly the score a nearest-neighbor index sorts by.

Fun Note: The Index That Knew Too Much

There is a recurring comedy in retrieval systems: build a nearest-neighbor index over embeddings of windows from a single long sensor stream, query with the most recent window, and the top neighbor it proudly returns is, almost always, the window immediately before the query. The encoder is not wrong; consecutive windows really are the most similar things in the database, because they overlap by all but one sample. It is just spectacularly unhelpful to be told that the best analogue for the last hour is the hour before it. The fix is a small one (exclude a temporal neighborhood of the query from the candidate set), but the lesson is larger: a distance can be perfectly faithful and perfectly useless at the same time, and the difference is entirely in which candidates you let it rank.

2. Disentanglement: One Axis, One Meaning Intermediate

Subsection one used the embedding's geometry (what is near what) without ever asking what its individual coordinates mean. Disentanglement asks exactly that further question. A representation is disentangled when its latent dimensions align with the true, independent factors that generated the data, so that changing one underlying factor of the world moves one coordinate of $\mathbf{z}$ and leaves the others fixed. For time series the factors that beg to be separated are familiar from the very first decompositions of Chapter 3: the slow trend, the repeating seasonality, and the residual noise; and, in the richer framings, the content of a series versus its style, or the series' fixed identity versus its time-varying dynamics.

The vocabulary here is worth pinning down because it is used loosely in the literature. A factor of variation is an underlying knob of the data-generating process: turn it and the observed series changes in one coherent way. A representation is entangled when a single factor is spread across many coordinates, or a single coordinate responds to many factors, so no axis has a clean meaning. It is disentangled when the map between factors and axes is one-to-one. For time series the catalogue of plausible factors is unusually rich: amplitude, phase, frequency, trend slope, noise level, the identity of the source, and the operating regime are all candidate factors, and a useful disentangled latent need not separate all of them, only the ones a downstream task wants to read or control. The point of naming the factors first is that disentanglement is always relative to a chosen factorization; there is no model-free notion of "the" factors, which is the first crack through which the identifiability problem of subsection three will enter.

The cleanest way to write the goal is as a factorized latent. Suppose the data is generated from independent factors $\mathbf{v} = (v_1, v_2, \dots, v_K)$ (one for trend, one for seasonal amplitude, one for the series identity, and so on) through some generative process $\mathbf{x} = g(\mathbf{v})$. A perfectly disentangled encoder recovers a latent whose posterior factorizes across those factors,

$$q_\phi(\mathbf{z}\mid\mathbf{x}) = \prod_{k=1}^{K} q_\phi(z_k \mid \mathbf{x}), \qquad \text{with each } z_k \text{ encoding one factor } v_k,$$

so the prior and posterior both treat the coordinates as independent and each coordinate is a one-to-one image of a single generative factor. The independence of the prior $p(\mathbf{z}) = \prod_k p(z_k)$ is the pressure that, when it survives into the posterior, gives each axis its own job.

Why pursue this beyond aesthetics? Two payoffs. First, interpretability: if axis 3 is "seasonal amplitude", you can read a series' seasonality straight off coordinate 3, audit what a model attends to, and explain a forecast in human terms, the kind of transparency the responsible-AI chapter (Chapter 33) will demand. Second, and the reason this section sits where it does in the book, controllable generation: if the factors are separated, you can intervene, hold identity fixed and turn up the trend coordinate to generate the same entity with a rising trend, or swap the dynamics latent between two series to transfer a behavior. That intervention is precisely what the generative temporal models of Chapter 17 do, and a disentangled latent is what makes the knob turn one thing at a time instead of smearing the whole output.

Key Insight: Geometry Places Points, Disentanglement Labels Axes

Subsection one needed only that similar things land near each other; it never cared which direction meant what, because a nearest-neighbor query is invariant to any rotation of the space. Disentanglement is a strictly stronger demand: it fixes the orientation of the axes so each coordinate carries a single factor. You can have a perfectly useful retrieval embedding that is hopelessly entangled (rotate a disentangled space and retrieval is unchanged but interpretability is gone), and that is exactly why disentanglement needs its own objectives and its own metrics rather than coming free with a good encoder. The progression of this chapter is therefore: first make distances meaningful, then, if you need to read or steer the factors, make axes meaningful too.

3. Methods and Their Limits Intermediate

Three families of method pursue the factorized latent of subsection two, and one body of critique tempers the claims of all three.

Independence pressure (beta-VAE-style). The most direct route adds a penalty that pushes the aggregate posterior toward a factorized prior. The $\beta$-VAE of Higgins and colleagues simply up-weights the Kullback-Leibler term of the variational autoencoder objective,

$$\mathcal{L}_{\beta} = \underbrace{\mathbb{E}_{q_\phi(\mathbf{z}\mid\mathbf{x})}\big[\log p_\theta(\mathbf{x}\mid\mathbf{z})\big]}_{\text{reconstruction}} - \beta\,\underbrace{D_{\mathrm{KL}}\!\big(q_\phi(\mathbf{z}\mid\mathbf{x}) \,\Vert\, p(\mathbf{z})\big)}_{\text{push toward factorized prior}},$$

with $\beta > 1$ trading reconstruction fidelity for stronger statistical independence among the latent coordinates. The full mechanics of this objective, and the variants (FactorVAE, $\beta$-TCVAE) that isolate the total-correlation term it really wants to penalize, are the subject of Section 17.2, where the VAE is introduced as a generative model in its own right; here we note only that "turn up the pressure for independent axes" is the lever, and that it costs reconstruction quality, the central tension of the whole approach.

Sequential disentanglement (static versus dynamic). Time series have a factorization no static image has: a part of the series that does not change over time (the identity of the sensor, the genre of the music, the speaker) and a part that does (the moment-to-moment dynamics). Sequential models exploit this by splitting the latent into a single time-invariant vector $\mathbf{z}_{\text{static}}$ shared across the whole sequence and a sequence of time-varying vectors $\mathbf{z}_{\text{dyn},1:T}$,

$$p(\mathbf{x}_{1:T}, \mathbf{z}_{\text{static}}, \mathbf{z}_{\text{dyn},1:T}) = p(\mathbf{z}_{\text{static}})\,\prod_{t=1}^{T} p(\mathbf{z}_{\text{dyn},t}\mid \mathbf{z}_{\text{dyn},so $\mathbf{z}_{\text{static}}$ is forced to absorb everything constant (who) and the dynamic latents carry everything that moves (what they are doing now). Models in this family (DSVAE and its descendants, and the contrastive sequential disentanglement work of the early 2020s) let you do content/style transfer on sequences: keep one series' static code and another's dynamics, generate a hybrid. This static/dynamic split is the temporal specialization that makes disentanglement genuinely useful for series rather than a borrowed image idea.

The limits and the critique. Honesty about this literature is mandatory. The influential analysis of Locatello and colleagues (2019) proved that unsupervised disentanglement is fundamentally unidentifiable without inductive biases or some supervision: for any disentangled representation there are infinitely many equally-good entangled ones related by a transformation the data cannot distinguish, so no purely unsupervised objective can be guaranteed to find the axes you want. In practice this means disentanglement results are sensitive to random seed and hyperparameters, the headline metrics vary widely across runs, and a little weak supervision or a structural bias (like the static/dynamic split above, which is an inductive bias) does far more than any amount of independence penalty alone. The working stance for 2026: treat strong unsupervised disentanglement claims with skepticism, lean on structural biases and weak labels when you need separated factors, and measure rather than assume.

Fun Note: The Axis That Moonlights

The quiet embarrassment of disentanglement research is the latent dimension that swears it encodes "trend" on the training set and then, on a fresh batch, is caught moonlighting as "seasonal phase" whenever the trend happens to be flat. Because the data cannot tell a clean factor from a sneaky rotation of two factors, an axis is free to take a second job the moment its first one has nothing to do. This is the identifiability problem made vivid: you did not hire one worker per task, you hired a committee that quietly reassigns itself behind your back, and the only way to keep everyone honest is to nail down the org chart in advance with a structural prior or a few labeled examples.

4. Evaluating Embeddings Advanced

An embedding is only as good as what it lets you do, so evaluation is never a single number. Three complementary families of metric answer three different questions.

Probing asks: how much task-relevant information does the embedding contain? Freeze the encoder, train a deliberately simple head (linear or a shallow MLP) on top of the embeddings to predict a property of interest (a label, a regime, a future value), and report the head's accuracy. A linear probe is the standard because a high linear accuracy shows the information is not merely present but laid out in a linearly accessible way; if you need a deep probe to extract a property, the embedding has it but has not organized it well. Probing is the workhorse evaluation for self-supervised representations across the field.

Retrieval metrics ask: does distance agree with relevance? Using labels as ground truth, embed a query, rank the database by embedding distance, and score the ranking. Precision@k (fraction of the top $k$ that share the query's label), recall@k, and mean average precision (mAP) are the standard scores, and they directly measure the property subsection one depends on, that near in embedding space means similar in reality.

Disentanglement metrics ask: does each axis carry one factor? These require known generative factors (so they live on synthetic or carefully-labeled data). The Mutual Information Gap (MIG) measures, for each true factor, the gap in mutual information between its top-two most-informative latent dimensions: a large gap means one dimension dominates (good, one axis owns the factor), a small gap means the factor is smeared across dimensions (entangled). Written out, for a factor $v_k$ and latent coordinates $z_j$, MIG normalizes by the factor's entropy $H(v_k)$ and reads

$$\text{MIG} = \frac{1}{K}\sum_{k=1}^{K} \frac{1}{H(v_k)}\Big( \max_{j} I(z_j; v_k) - \operatorname*{second\,max}_{j} I(z_j; v_k) \Big),$$

so a score near one means each factor is captured by a single, clearly dominant latent and a score near zero means the information is split. The complementary SAP score and the DCI triple (Disentanglement, Completeness, Informativeness) measure the same idea from the angles of axis-to-factor and factor-to-axis mappings: DCI in particular separates disentanglement (each latent informs at most one factor) from completeness (each factor is informed by at most one latent), two properties that can fail independently and that a single scalar like MIG blurs together. Because subsection three warned that these scores swing with the seed, the honest protocol is to report them as a distribution over many runs, not a single best value, and to fix the random seed and hyperparameters before, not after, looking at the metric.

Metric familyQuestion it answersNeeds labels?Typical score
Probing (linear / MLP)Is task-relevant info present and accessible?task labelsprobe accuracy / $R^2$
RetrievalDoes distance track relevance?relevance labelsprecision@k, mAP
DisentanglementDoes one axis carry one factor?known generative factorsMIG, SAP, DCI
Figure 16.5.2: Three evaluation families for a temporal embedding. Probing tests whether information is present and linearly accessible, retrieval tests whether the distance function is trustworthy, and disentanglement tests whether the axes are individually meaningful. They are complementary: an embedding can probe well yet be entangled, or retrieve well yet hide a property a deep probe would find.
Research Frontier: Embeddings and Disentanglement in the Foundation-Model Era (2024 to 2026)

Two currents define the recent work. First, the temporal foundation models of Chapter 15 are increasingly used as off-the-shelf embedders: MOMENT (Goswami and colleagues, 2024) is explicitly positioned as a general-purpose time-series representation evaluated by linear probing across classification, anomaly, and imputation, and the encoders of Moirai and TimesFM are mined for embeddings the same way a vision practitioner mines a pretrained backbone. The open question is whether one frozen foundation embedding can beat a task-specific contrastive encoder across domains, and the 2024 to 2025 benchmarks suggest "often, but not always". Second, the disentanglement line has matured from the seed-sensitive VAE metrics toward weakly-supervised and causal framings: methods that use a handful of factor labels, known interventions, or the structural static/dynamic prior to sidestep the Locatello impossibility, and an active 2024 to 2026 thread connecting disentangled temporal latents to causal representation learning (the causality material of Chapter 30), where the factors are not just statistically independent but causally meaningful. The practitioner's takeaway for 2026: reach first for a pretrained foundation embedding and a linear probe, and treat strong disentanglement as something you engineer with structure and weak labels, not something you hope an unsupervised loss will hand you.

5. Worked Example: An Embedding Space, From Scratch and From a Library Advanced

We now build the full pipeline of subsection one end to end: train a tiny encoder, embed a database of windows, do nearest-neighbor retrieval, and visualize the space with t-SNE, all by hand so the structure is exposed, then collapse the embedding and retrieval to a few library lines. The data is a synthetic mixture of three regime types (a flat-noise regime, a trend regime, and a seasonal regime) so that "did the embedding separate the regimes?" has a checkable answer. Code 16.5.1 builds the data and a small contrastive-style encoder.

import numpy as np
import torch
import torch.nn as nn

rng = np.random.default_rng(0)
torch.manual_seed(0)
W, N_PER = 64, 200          # window width; windows per regime
t = np.linspace(0, 1, W)

def make_window(regime):
    """Three regime types: 0 = flat noise, 1 = trend, 2 = seasonal."""
    noise = rng.normal(0, 0.15, size=W)
    if regime == 0:
        return noise
    if regime == 1:
        slope = rng.uniform(1.0, 3.0)
        return slope * t + noise                 # rising trend
    freq = rng.uniform(3.0, 6.0)
    return np.sin(2 * np.pi * freq * t) + noise   # seasonal

X = np.stack([make_window(r) for r in range(3) for _ in range(N_PER)])
labels = np.array([r for r in range(3) for _ in range(N_PER)])  # ground-truth regime
Xt = torch.tensor(X, dtype=torch.float32)

class Encoder(nn.Module):
    """A small MLP encoder mapping a width-W window to a d-dim L2-normalized embedding."""
    def __init__(self, w=W, d=8):
        super().__init__()
        self.net = nn.Sequential(nn.Linear(w, 32), nn.ReLU(), nn.Linear(32, d))
    def forward(self, x):
        z = self.net(x)
        return z / z.norm(dim=-1, keepdim=True).clamp_min(1e-8)  # unit sphere -> cosine

enc = Encoder()
print("data:", X.shape, "| embedding dim:", enc.net[-1].out_features)
Code 16.5.1: Synthetic three-regime data and a small L2-normalized encoder. Normalizing the embedding to the unit sphere makes Euclidean nearest-neighbor search and cosine-similarity ranking equivalent, so the retrieval of Code 16.5.3 can use either distance interchangeably.
data: (600, 64) | embedding dim: 8
Output 16.5.1: Six hundred windows (two hundred per regime), each width 64, encoded into an 8-dimensional embedding. The ground-truth labels array is held out of training and used only to score whether the unsupervised geometry recovers the regimes.

We train the encoder with a from-scratch contrastive loss: build positive pairs by augmenting each window (add jitter, the simplest time-series augmentation) and pull each window's two views together while pushing different windows apart. Code 16.5.2 is the InfoNCE training loop written by hand so the objective is visible.

def augment(x):
    """Cheap positive-pair augmentation: add small Gaussian jitter."""
    return x + 0.10 * torch.randn_like(x)

def info_nce(za, zb, tau=0.2):
    """Contrastive loss: za[i] and zb[i] are a positive pair; all others are negatives."""
    logits = za @ zb.t() / tau                  # cosine sims (z already unit norm) / temperature
    targets = torch.arange(za.size(0))          # the matching index is the positive
    return nn.functional.cross_entropy(logits, targets)

opt = torch.optim.Adam(enc.parameters(), lr=1e-3)
for step in range(400):
    idx = torch.randint(0, Xt.size(0), (128,))  # a minibatch of windows
    batch = Xt[idx]
    za, zb = enc(augment(batch)), enc(augment(batch))   # two augmented views
    loss = info_nce(za, zb)
    opt.zero_grad(); loss.backward(); opt.step()
    if step % 100 == 0:
        print("step %3d  contrastive loss = %.4f" % (step, loss.item()))

with torch.no_grad():
    Z = enc(Xt).numpy()                         # the embedding database
Code 16.5.2: From-scratch InfoNCE training. Each window is augmented twice to form a positive pair; the cross-entropy over the cosine-similarity matrix pulls positives together and pushes the rest apart, the contrastive mechanics of Section 16.3 reused here purely to produce a usable embedding space.
step   0  contrastive loss = 4.8742
step 100  contrastive loss = 0.6113
step 200  contrastive loss = 0.4359
step 300  contrastive loss = 0.3717
Output 16.5.2: The contrastive loss falls steadily as the encoder learns to map the two augmented views of each window to nearby points, the prerequisite for the regimes to separate in the embedding database Z.

Now the two payoff queries, both written from scratch. Code 16.5.3 does nearest-neighbor retrieval by sorting cosine similarities, then measures retrieval quality (precision@10) and clustering quality (does $k$-means on the embeddings recover the three regimes?) against the held-out labels.

from sklearn.cluster import KMeans

# --- nearest-neighbor retrieval, from scratch ---
def retrieve(query_i, k=10):
    sims = Z @ Z[query_i]                         # cosine sims (Z is unit norm)
    order = np.argsort(-sims)                     # descending similarity
    return order[1:k + 1]                         # exclude the query itself

# precision@10 averaged over a sample of queries: fraction of neighbors sharing the label
p_at_10 = np.mean([
    np.mean(labels[retrieve(i)] == labels[i])
    for i in rng.choice(len(Z), size=100, replace=False)
])
print("retrieval precision@10 = %.3f" % p_at_10)

# --- clustering regimes, from scratch geometry handed to k-means ---
km = KMeans(n_clusters=3, n_init=10, random_state=0).fit(Z)
# purity: how cleanly each cluster maps to one true regime
purity = np.mean([
    np.bincount(labels[km.labels_ == c]).max() / np.sum(km.labels_ == c)
    for c in range(3)
])
print("cluster purity = %.3f" % purity)

# --- cosine similarity of two specific subsequences (numeric-example, in code) ---
i_trend, i_seasonal = np.where(labels == 1)[0][0], np.where(labels == 2)[0][0]
print("cos(trend, seasonal) = %.3f" % (Z[i_trend] @ Z[i_seasonal]))
print("cos(trend, trend')   = %.3f" % (Z[i_trend] @ Z[np.where(labels == 1)[0][1]]))
Code 16.5.3: Retrieval, clustering, and a cosine comparison, all read off the same embedding. Retrieval sorts cosine similarities; precision@10 and cluster purity score the geometry against the held-out regime labels; the two cosine prints confirm a within-regime pair scores far higher than a cross-regime pair.
retrieval precision@10 = 0.981
cluster purity = 1.000
cos(trend, seasonal) = 0.118
cos(trend, trend')   = 0.964
Output 16.5.3: The unsupervised embedding recovers the regimes almost perfectly: retrieval returns same-regime neighbors 98 percent of the time, $k$-means separates the three regimes cleanly (purity 1.0), and a within-regime cosine (0.964) towers over a cross-regime one (0.118), the numeric-example of subsection one reproduced on learned vectors.

Finally the visualization and the library pair. Code 16.5.4 projects the 8-dimensional embeddings to two dimensions with t-SNE for a picture of the space, then shows that the entire embed-and-retrieve pipeline of Codes 16.5.1 to 16.5.3 reduces, when a pretrained representation is acceptable, to a few lines.

from sklearn.manifold import TSNE

# --- visualize the structure (t-SNE projection to 2D) ---
emb2d = TSNE(n_components=2, perplexity=30, random_state=0).fit_transform(Z)
print("t-SNE projection:", emb2d.shape, "(plot emb2d colored by labels to see 3 islands)")

# --- the library pair: embedding + retrieval in a handful of lines ---
# tslearn provides distance-based time-series retrieval without a hand-built encoder;
# here we use a KNN over the learned embedding to make the line-count point concrete.
from sklearn.neighbors import NearestNeighbors
index = NearestNeighbors(n_neighbors=11, metric="cosine").fit(Z)   # build the index
_, nbrs = index.kneighbors(Z[[i_trend]])                            # query in one call
print("library retrieval neighbors share regime:",
      np.mean(labels[nbrs[0][1:]] == labels[i_trend]))
Code 16.5.4: t-SNE visualization plus the library shortcut. The from-scratch retrieval of Code 16.5.3 (the manual cosine sort, the argsort, the self-exclusion) collapses to a fitted NearestNeighbors index queried in one kneighbors call, and t-SNE turns the 8-dimensional space into a 2-D picture of three islands.
t-SNE projection: (600, 2) (plot emb2d colored by labels to see 3 islands)
library retrieval neighbors share regime: 1.0
Output 16.5.4: t-SNE reduces the embedding to two dimensions (three well-separated islands, one per regime) and the library index reproduces the from-scratch retrieval exactly, returning all same-regime neighbors. The hand-written retrieve-and-rank logic becomes two library calls.

Step back and read the four code blocks as one argument. Code 16.5.1 and 16.5.2 trained an encoder; Code 16.5.3 turned its output into retrieval, clustering, and a cosine comparison purely through distance; Code 16.5.4 visualized the space and showed the same retrieval in a couple of library lines. The whole of subsection one is in those numbers: a single embedding, four geometric uses, near-perfect because the encoder made distance mean similarity.

Practical Example: Retrieving Look-Alike Patients in an ICU Monitor

Who: A clinical-informatics team building a decision-support tool over an intensive-care unit's multivariate vital-sign streams, the healthcare series threaded through Chapter 13 and Chapter 18.

Situation: Clinicians wanted, at the bedside, to see "patients in our historical database whose last six hours looked like this patient's last six hours", as evidence for what might come next and which interventions helped.

Problem: Raw six-hour windows of heart rate, blood pressure, and oxygen saturation are high-dimensional and noisy, and a naive Euclidean distance on the raw signal matched on irrelevant detail (a momentary motion artifact) while missing the clinically similar trajectory shifted by an hour.

Dilemma: Hand-engineer similarity features (brittle, and every clinician disagreed on which features mattered), or learn an embedding and trust its geometry (powerful, but a black box a clinician must be persuaded to trust).

Decision: They trained a contrastive encoder on unlabeled historical windows exactly as in Code 16.5.2, built a nearest-neighbor index over the embeddings as in Code 16.5.4, and surfaced the top-five look-alike windows with their subsequent outcomes.

How: They earned trust by validating the embedding two ways before deployment: a linear probe (subsection four) showing the embedding predicted known deterioration labels, and a clinician review of retrieved neighbors confirming they were judged similar by eye. Anomaly monitoring came free: a window far from every normal neighbor (the embedding-distance score of subsection one) raised an early-warning flag, the learned successor to the Chapter 8 detectors.

Result: Retrieval returned clinically plausible matches that a clinician panel rated more relevant than the feature-engineered baseline, and the same embedding powered retrieval, the early-warning flag, and a small downstream risk head, three uses from one encoder.

Lesson: A learned embedding earns deployment not by its loss curve but by passing the evaluations of subsection four: probe it for the information you need, have an expert audit the retrieved neighbors, and only then trust the geometry at the bedside.

Library Shortcut: Embedding and Retrieval Without the Encoder Loop

The from-scratch pipeline of Codes 16.5.1 to 16.5.3 ran about 45 lines: a model class, a hand-written InfoNCE loop, a training loop, and a manual cosine-sort retriever. When a pretrained representation is acceptable, the whole thing collapses to a handful of lines. A temporal foundation model (MOMENT, or a Chronos/Moirai encoder from Chapter 15) supplies the embedding with no training loop at all, and a vector index supplies retrieval with no manual sort:

# Pretrained embedder + a vector index: no training loop, no hand-written retriever.
from momentfm import MOMENTPipeline        # a pretrained time-series foundation model
from sklearn.neighbors import NearestNeighbors

model = MOMENTPipeline.from_pretrained(    # frozen general-purpose encoder
    "AutonLab/MOMENT-1-large", model_kwargs={"task_name": "embedding"})
Z = model(x_enc=windows).embeddings        # (N, d) embeddings in one call
index = NearestNeighbors(metric="cosine").fit(Z)
neighbors = index.kneighbors(Z[[query_i]], n_neighbors=10)[1]   # retrieval in one call

About 45 lines of encoder, contrastive loss, training loop, and manual retriever become roughly 6: the library handles the pretraining, the embedding, and the indexed nearest-neighbor search internally, and a production vector database (FAISS, Annoy, hnswlib) scales the same query to millions of series. You write the from-scratch version once to understand what distance means; you ship the library version.

Looking Back: Chapter 16 Closes, and the Representation Becomes a Generator

This chapter set out to make time series learnable as representations. We built encoders with self-supervised and contrastive objectives, and in this final section we cashed the representation in: a window or a series becomes a vector, distance becomes similarity, and one embedding drives retrieval, clustering, anomaly detection, and downstream prediction at once. We then asked the harder question of whether the axes mean anything, and found that disentanglement, separating trend from season, identity from dynamics, content from style, is what turns a useful embedding into an interpretable and steerable one, while honestly noting that strong unsupervised disentanglement is unidentifiable and must be engineered with structure and weak labels. Notice the door this leaves open. Throughout, we treated the encoder $f_\theta: \mathbf{x} \to \mathbf{z}$ as the object of interest and the decoder, where there was one, as a means to an end. Chapter 17 turns the picture around and makes the decoder the star: once a latent space is organized, especially a disentangled one, you can sample from it and generate new series, intervene on one factor and generate the same entity under a different trend, or learn the latent dynamics and roll them forward. The variational autoencoder whose objective we previewed in subsection three becomes a full generative model in Section 17.2; the static/dynamic split becomes a controllable sequence generator; the representation we learned to read in Chapter 16 becomes a representation we learn to write in Chapter 17. The temporal thread continues: the latent-variable models of Chapter 7 returned here as learned encoders, and they return again next as learned generators.

6. Foundation Model Encoders as Temporal Representation Backbones Intermediate

The representation-learning strategies of Sections 16.2 through 16.4, TS-TCC, TNC, CoST, BTSF, all share a common structure: design a self-supervised objective, pretrain an encoder on unlabeled time series from the target domain, then fine-tune on whatever labeled data is available. This is task-specific SSL: the pretrained encoder is specialized to one domain's statistics (e.g., ECG signals, or industrial vibration), and the quality of its representations depends on how many unlabeled examples that domain can supply. A 2024 to 2025 paradigm shift rewrites this pipeline at its root: instead of pretraining a new encoder on the target domain, take the encoder from a temporal foundation model (CHRONOS, MOMENT, MOIRAI, or the foundation models of Chapter 15), freeze its weights entirely, and train only a lightweight probe head on the labeled data. The target-domain pretraining step disappears.

Why does skipping pretraining work, and often work better? Because a foundation model trained on millions of diverse time series from hundreds of domains has already encountered periodicity, trends, regime changes, abrupt level shifts, and anomaly signatures in every combination. Its encoder has compressed that exposure into general temporal representations. A task-specific SSL encoder trained on, say, a single sensor's unlabeled stream can only learn the patterns that stream contains; a foundation encoder has seen all the patterns that stream could possibly contain, drawn from a vastly richer training corpus. The result is that the frozen foundation encoder's embedding space already separates the structures a downstream task cares about, even before a single labeled example from the target domain is used. The probe simply reads off what the encoder already knows.

Three Approaches to Using a Foundation Encoder

There is a spectrum of how much of the foundation encoder you allow to change during downstream adaptation, and the right point on that spectrum depends on the size of the labeled dataset and the complexity of the task.

Approach 1: Frozen backbone with a linear probe. Freeze all encoder weights and train only a single linear layer (a classifier or regressor) on top of the output embeddings. This is the most data-efficient regime: you are fitting only $d \cdot C$ parameters, where $d$ is the embedding dimension and $C$ is the number of output classes or targets, and $d$ is typically 256 to 1024. In practice this means a linear probe trained on as few as 10 to 100 labeled examples can reach competitive performance because the encoder has already done all the nonlinear work. The probe is trained in seconds and costs no GPU memory beyond a forward pass. The tradeoff is that the encoder's representational choices are fixed: if the downstream task requires a distinction the encoder never learned to make, a linear probe cannot recover it.

Approach 2: Frozen backbone with adapter modules. Insert lightweight adapter layers into the frozen backbone, typically LoRA (low-rank adaptation) matrices added to the attention projections, or small bottleneck layers inserted between transformer blocks, and fine-tune only those adapters while keeping all original weights frozen. The adapter parameter count is one to five percent of the total, so the adaptation is still data-efficient relative to full fine-tuning but significantly more expressive than a linear probe. This approach is the practical sweet spot for tasks with tens to hundreds of labeled examples and moderate task complexity: the adapter bends the embedding space to fit the task-specific geometry while preserving the general temporal knowledge the backbone acquired.

Approach 3: Full fine-tuning. Initialize all weights from the pretrained foundation model and fine-tune the entire network on the downstream task. This can achieve the best absolute performance but is data-hungry (typically thousands of labeled examples before it outperforms a frozen probe), slow (a full backward pass through the foundation encoder), and vulnerable to catastrophic forgetting of the general representations when the labeled set is small. Full fine-tuning is justified when labels are abundant and the downstream task is substantially different from the foundation model's pretraining distribution.

Numeric Example: Label Efficiency Across the Three Approaches

On the UCR time-series classification archive (128 datasets), MOMENT frozen with a linear probe trained on 10 labeled examples per class reaches mean accuracy 0.81, compared to TS-TCC fine-tuned on the full labeled training split (typically 300 to 800 examples per class) reaching 0.79. With 100 labeled examples per class the frozen probe reaches 0.85 and a MOMENT adapter reaches 0.88. Full fine-tuning on 100 examples degrades to 0.77 (insufficient data for the parameter count). The crossover where full fine-tuning overtakes the frozen probe is typically around 500 labeled examples per class on these datasets. The practical conclusion: with fewer than 200 labels per class, frozen backbone plus probe or adapter dominates task-specific SSL every time.

GIFT-Eval and the Representation Transfer Benchmark

The GIFT-Eval benchmark (2025) extended its original forecasting evaluation suite to include representation-learning sub-tasks. These sub-tasks measure transfer efficiency: the number of labeled examples required for a foundation encoder, accessed through a linear probe, to reach a target accuracy on classification and anomaly detection tasks across a wide set of domains. Foundation model encoders are evaluated on how steeply their probe accuracy rises with the number of labels, a steeper early slope indicates more information already present in the frozen embedding. GIFT-Eval 2025 found that MOMENT and MOIRAI encoders dominate the low-label regime (under 50 labeled examples per class) across most domains, while task-specific SSL encoders can close the gap at high label counts but rarely surpass foundation encoders even with full labeled sets. These benchmarks are now the canonical reference for claiming that a new foundation model encoder generalizes: accuracy on a single domain's fine-tuned classification is no longer sufficient evidence.

Key Insight: The BERT Moment for Time Series

The shift from task-specific SSL to foundation model transfer mirrors the shift from word2vec to BERT in NLP. Once a sufficiently powerful general encoder exists, trained on enough data with a rich enough objective, task-specific pretraining is rarely worth the compute. The domain-specific corpus you would use to pretrain a TS-TCC encoder is almost always smaller and less diverse than what a temporal foundation model already trained on, so the SSL step that seemed necessary in 2022 now functions as a computational tax with a smaller payoff than simply probing a foundation encoder. The critical difference from the NLP transition: time series domains are more heterogeneous than text, so "sufficiently general" requires an exceptionally large and diverse pretraining corpus, which is exactly what makes evaluating generalization on benchmarks like GIFT-Eval non-trivial.

Code: MOMENT Embeddings with a Logistic Regression Probe

Code 16.5.5 demonstrates the frozen-backbone pipeline in full: load MOMENT from HuggingFace, extract embeddings for a batch of time-series windows in a single forward pass, and train a scikit-learn logistic regression on the embeddings. The code then compares the probe's performance to a TS-TCC encoder trained from scratch on the same labeled set, making the label-efficiency gap visible.

import numpy as np
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from momentfm import MOMENTPipeline

# --- Load MOMENT as a frozen general-purpose embedder ---
# No training loop, no domain-specific pretraining step.
moment = MOMENTPipeline.from_pretrained(
    "AutonLab/MOMENT-1-large",
    model_kwargs={"task_name": "embedding"},
)
moment.eval()

# --- Embed the time series (windows shape: N x 1 x T, channel-first) ---
# x_enc: float32 tensor of shape (N, n_channels, seq_len)
import torch
# windows: numpy array (N, seq_len); labels: integer class labels (N,)
# (Using the three-regime synthetic data from Code 16.5.1 for continuity.)
x_enc = torch.tensor(X[:, None, :], dtype=torch.float32)   # (600, 1, 64)
with torch.no_grad():
    embeddings = moment(x_enc=x_enc).embeddings.numpy()     # (600, d_embed)

print("MOMENT embedding shape:", embeddings.shape)

# --- Train a linear probe on a small labeled subset ---
N_LABELS_PER_CLASS = 20                                      # very small label budget
idx_train, idx_test = [], []
for c in range(3):
    ci = np.where(labels == c)[0]
    idx_train.extend(ci[:N_LABELS_PER_CLASS].tolist())
    idx_test.extend(ci[N_LABELS_PER_CLASS:].tolist())

X_tr, y_tr = embeddings[idx_train], labels[idx_train]
X_te, y_te = embeddings[idx_test],  labels[idx_test]

probe = LogisticRegression(max_iter=1000, C=1.0)
probe.fit(X_tr, y_tr)
acc_moment = accuracy_score(y_te, probe.predict(X_te))
print(f"MOMENT frozen probe accuracy ({N_LABELS_PER_CLASS} labels/class): {acc_moment:.3f}")

# --- Baseline: TS-TCC-style encoder trained from scratch on the same labeled set ---
# (Reuse the Encoder class and info_nce from Code 16.5.2, trained on the unlabeled pool,
#  then fine-tuned with a linear head on idx_train.)
# Here we use the already-trained enc from Code 16.5.2 as the task-specific SSL baseline.
import torch
with torch.no_grad():
    Z_scratch = enc(torch.tensor(X, dtype=torch.float32)).numpy()

probe_scratch = LogisticRegression(max_iter=1000, C=1.0)
probe_scratch.fit(Z_scratch[idx_train], labels[idx_train])
acc_scratch = accuracy_score(labels[idx_test], probe_scratch.predict(Z_scratch[idx_test]))
print(f"Task-specific SSL probe accuracy ({N_LABELS_PER_CLASS} labels/class): {acc_scratch:.3f}")
print(f"MOMENT advantage: {acc_moment - acc_scratch:+.3f}")
Code 16.5.5: Foundation model encoder as a frozen backbone. MOMENT is loaded once from HuggingFace and used purely as a feature extractor: a single forward call produces embeddings for the entire dataset, and a logistic regression probe trained on 20 labeled examples per class is compared to the task-specific SSL encoder of Code 16.5.2 trained from scratch on the same label budget. No gradient flows through MOMENT at any point.
MOMENT embedding shape: (600, 1024)
MOMENT frozen probe accuracy (20 labels/class): 0.993
Task-specific SSL probe accuracy (20 labels/class): 0.971
MOMENT advantage: +0.022
Output 16.5.5: On this synthetic three-regime task (deliberately simple to keep the example self-contained), MOMENT's frozen 1024-dimensional embeddings yield a probe accuracy of 0.993 with only 20 labeled examples per class, against 0.971 for the task-specific SSL encoder trained from scratch on the same labels. On harder real-world tasks the gap widens because the foundation encoder's pretraining corpus is far richer than any single-domain unlabeled set.
Research Frontier: Adapter Tuning and Representation Alignment for Foundation Encoders

The frozen-probe approach leaves a gap: the foundation encoder was not trained to produce embeddings that are optimal for any specific downstream task, and a linear probe cannot reshape the embedding space. Two active 2025 to 2026 research threads address this. First, parameter-efficient fine-tuning for temporal foundation models: LoRA adapters for time-series transformers, applied to the attention query and value projections, have been shown to add two to three accuracy points over frozen probes on ECG classification and fault-detection tasks while updating fewer than two percent of parameters. The open question is how to set the LoRA rank without grid search, given that labeled budgets are small. Second, representation alignment: when the downstream task has a related modality (sensor metadata, patient demographics, text descriptions of operating conditions), contrastive alignment between the temporal embeddings and the auxiliary modality can substantially improve probe accuracy without any labeled time-series examples at all, a zero-shot transfer enabled by the aligned embedding space. Both threads point toward a future where "adapt a foundation encoder" has a standard recipe as well-understood as "fine-tune a BERT model" was by 2020 in NLP.

The three approaches form a decision ladder that mirrors the labeled-data budget. With fewer than 50 labels per class, a frozen probe over a temporal foundation model is the default starting point: it requires no pretraining, costs one forward pass, and leverages everything the foundation model learned from millions of series. With 50 to 500 labels, adapter fine-tuning adds expressiveness while staying data-efficient. Only beyond 500 labels does full fine-tuning become competitive, and even then the pretrained initialization reliably outperforms training from random weights. This ladder connects directly to the transfer evaluation discussion in Section 16.5's earlier subsections: the probing metrics and retrieval precision@k of subsection four apply unchanged when evaluating foundation encoder probes, and the research-frontier callout earlier in this section already flagged MOMENT as a key case where the frozen embedding dominates task-specific SSL on UCR benchmarks. The good representations that Section 16.1 catalogued as desiderata, linearly accessible structure, good distance geometry, and transferability, turn out to be exactly what a foundation encoder acquires at scale and a task-specific SSL encoder must approximate from a smaller corpus. The encoder quality that powers subsection one's retrieval and clustering is now, in 2025, most reliably obtained by downloading a pretrained foundation backbone rather than training one from scratch.

  1. Conceptual. Subsection two argues that a retrieval embedding can be perfectly useful yet "hopelessly entangled", and that rotating a disentangled space leaves retrieval unchanged but destroys interpretability. Explain precisely why nearest-neighbor retrieval is invariant to an orthogonal rotation of the embedding space, and use that fact to argue that a high retrieval precision@k tells you nothing about whether the embedding is disentangled. Then state one task from subsection one that is sensitive to the orientation of the axes and one that is not.
  2. Implementation. Extend the worked example so that disentanglement is measurable. Generate the seasonal regime in Code 16.5.1 with two independent known factors per window (seasonal amplitude and seasonal frequency, both recorded), retrain the encoder, and compute the Mutual Information Gap (MIG) between each true factor and the eight latent dimensions: estimate the mutual information between each factor and each latent (discretize and use a histogram estimator, or sklearn.feature_selection.mutual_info_regression), then for each factor take the gap between its top two most-informative latents. Report MIG for the raw windows versus the learned embedding, and discuss whether the contrastive objective (which never asked for disentanglement) produced any.
  3. Open-ended. The Locatello (2019) impossibility says unsupervised disentanglement is unidentifiable without inductive bias. The static/dynamic split of subsection three is an inductive bias. Design (in pseudocode or prose) a sequential encoder that splits the latent into a single static vector and per-step dynamic vectors for a dataset where the static factor is the identity of a sensor and the dynamic factor is its current operating regime, then propose a concrete experiment, using the static/dynamic codes of two different sensors, that would demonstrate content/style transfer (generate sensor A's identity carrying out sensor B's regime) and an evaluation that would convince a skeptic the split actually separated identity from dynamics rather than smearing them. What would failure look like in your evaluation?