State, Physics, Causality, and Memory Evaluation › State Probing and Representation Quality

Michael BrenndoerferAugust 2, 202616 min read
World Models HandbookAll chapters
Part 11 of 12Evaluation and Understanding
Chapter 63State, Physics, Causality, and Memory Evaluation › State Probing and Representation Quality
Reading section 2 of 8 · State Probing and Representation Quality · 16 minSection 2 of 8 · 16 min

Book contents

Section 2 of 8 · 16 min read

State, Physics, Causality, and Memory Evaluation › State Probing and Representation QualityOne section · Sections in this chapter2 / 8 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

A probe is a small model fitted on top of a frozen representation. You take the encoder, stop its gradients, run it over a dataset, and then ask: can a simple readout predict quantity qq from the representation ztz_t? Saying that the representation "contains" qq can conflate recoverability by a chosen readout, unrestricted information content, and downstream use. We need to ask how the information is arranged and which procedure can read it out.

Frozen representation and probe

A frozen representation is a fixed map zt=enc(o≤t)z_t = \mathrm{enc}(o_{\le t}) whose parameters do not change during evaluation. A probe is a separately fitted function gθg_\theta trained to predict a target qtq_t from ztz_t. The probe's held-out score measures how accessible qtq_t is to that probe family, not whether the encoder "knows" qtq_t in any deeper sense.

The three questions are:

  1. Linear accessibility. How well does a fitted linear readout recover qtq_t from ztz_t on held-out data? Success demonstrates accessibility to that fitted readout on the tested distribution. Failure can reflect regularization, sample size, fitting, or distribution shift; it does not prove that no linear readout could succeed. A linear probe still learns a task-specific mapping.
  2. Information decodability. Can some function in a richer probe family recover qtq_t? A flexible probe can learn substantial task structure from its frozen inputs, so high performance depends jointly on the encoded information, the readout's capacity, and its fitting procedure. Capacity cannot supply target information absent from those inputs.
  3. Downstream functional use. Does the rest of the system use qtq_t to make decisions? This is the only one of the three that connects to behaviour, and it cannot be established by a probe at all. A probe is a diagnostic instrument attached to the side of the model; it does not tell you what the model does with the information when it acts.

Comparing readouts can reveal which fitted family works better on the test distribution. Better nonlinear scores do not establish impossibility of linear recovery, and failures under both families do not prove absent information. Good decoding also does not establish causal necessity: the downstream system may ignore the recovered variable. Testing functional use requires additional experiments, such as controlled representation interventions and closed-loop comparisons.

Probe splits, controls, and preprocessing

Check these three procedural details before interpreting a probe score. Splits and preprocessing determine what information is held out; controls clarify how the selected probe behaves on an alternative task. Mistakes can bias an evaluation or limit the conclusions it supports.

The probe itself is a fitted map gθg_\theta from the frozen representation ztz_t to a target qtq_t. It is a simple readout, not a full model. In the linear case, gθ(zt)=Wzt+bg_\theta(z_t) = W z_t + b; in the nonlinear case, the probe first maps ztz_t through a fixed feature function and then applies a ridge readout. The data are the held-out trajectories, and all probe scores are computed against targets that were not used to fit either the encoder or the probe, and trajectories are held out as complete sequences rather than individual frames so that frames from one rollout never appear in two splits. The key design choice is that the encoder is frozen: the probe cannot reshape the representation to make its job easier, so any information the probe recovers must already be present in ztz_t.

Split at the trajectory level, not the frame level. Consecutive frames of one trajectory are strongly correlated: neighbouring timesteps share nearly the same position. Random frame splits can put near-duplicates of test frames in the training set and inflate the probe's held-out score through memorisation. Splitting whole trajectories removes this within-trajectory leak. In our toy we hold out complete rollouts. Choose a split unit that respects the dependencies in the data; shared scenes or subjects may require a larger grouping than one trajectory.

Fit preprocessing on training data only. Standardisation statistics, whitening matrices, and PCA bases must be estimated on the training split and then applied unchanged to validation and test. Fitting them on the full dataset uses held-out information. Its effect on a score depends on the data and fitting procedure; neither inflation nor a flat train/test gap is guaranteed.

Use a control task with matched probe capacity. Hewitt and Liang (2019) developed type-level randomized control tasks for linguistic probes. Our shuffled regression targets are a simpler toy control, not a reproduction of their protocol. Report real-target and control results together, with the randomization and split specified. A control gives context about the chosen fitting procedure; it does not bound all structured-task learning or certify information content.

The toy representation

To keep this concrete, let us build a frozen representation by hand and be explicit about what it is. We will use a phase encoding of position only. In the formula and NumPy code, xtx_t and yty_t denote the numerical values of observed coordinates in metres: the dimensionless phase is 2π2\pi times the coordinate divided by a one-metre period.

zt=[ cos⁡(2πxt),  sin⁡(2πxt),  cos⁡(2πyt),  sin⁡(2πyt) ]∈R4.z_t = \big[\,\cos(2\pi x_t),\; \sin(2\pi x_t),\; \cos(2\pi y_t),\; \sin(2\pi y_t)\,\big] \in \mathbb{R}^4 .

where:

  • xtx_t: the observed horizontal position at time tt, in metres.
  • yty_t: the observed vertical position at time tt, in metres.
  • cos⁡(2πxt),sin⁡(2πxt)\cos(2\pi x_t), \sin(2\pi x_t): the phase encoding of the horizontal coordinate as a point on the unit circle; together they determine its numerical value modulo one. Exact inversion over a full period is nonlinear; this does not preclude a useful approximate linear readout over a restricted distribution.
  • cos⁡(2πyt),sin⁡(2πyt)\cos(2\pi y_t), \sin(2\pi y_t): the phase encoding of the vertical coordinate as a point on the unit circle, with the same periodic-recovery property.

This is a handcrafted encoder, not a learned one. Exact recovery of a noiseless position within a specified one-period interval uses nonlinear phase inversion; positions differing by an integer share the same encoding, and noisy observations near the interval boundary complicate recovery. The encoder does not directly take velocity, but that does not make velocity statistically independent of its output. Our sampled trajectories can correlate position and velocity. The experiment tests these readouts on a named distribution, not an absolute present-versus-absent information theorem.

The toy world and the dataset

Our world is a point mass in the unit square with perfectly reflecting walls. Let us fix the conventions before writing any metric. Fixing conventions up front is not a formality; it is what makes the numbers in the tables below interpretable and comparable across sections.

  • State: st=(xt,yt,vtx,vty)s_t = (x_t, y_t, v^x_t, v^y_t), metres and metres per second.
  • Action: ata_t, metres per second squared, applied as an acceleration.
  • Time step: Δt=0.1\Delta t = 0.1 s, so one trajectory of 30 steps is 3 seconds.
  • Observation: ot=(xt,yt)+ϵto_t = (x_t, y_t) + \epsilon_t with independent coordinate noise ϵt∼N(0,(0.004 m)2I2)\epsilon_t \sim \mathcal{N}(0, (0.004\ \text{m})^2 I_2).
  • Occluder: the band x∈[0.5−w,0.5+w]x \in [0.5 - w, 0.5 + w] for y∈[0.05,0.95]y \in [0.05, 0.95], with ww the occluder half-width.
  • Data-generating policy: at∼Uniform(−0.3,0.3)2a_t \sim \mathrm{Uniform}(-0.3, 0.3)^2 m/s², independent across components and timesteps.
  • Split unit: one complete trajectory. We use 60 for training, 20 for validation, 20 for test.
In[3]:
Code
import numpy as np

DT = 0.1  # s
V_MAX = 0.6  # m/s
A_MAX = 0.3  # m/s^2
N_STEPS = 30  # 30 steps = 3.0 s
SIGMA_OBS = 0.004  # m
OCC_Y = (0.05, 0.95)
OCC_HALF = 0.10  # half-width of the occluder band in x


def simulate(
    rng,
    n_traj,
    n_steps=N_STEPS,
    driven=True,
    damping=1.0,
    restitution=1.0,
    v_scale=1.0,
):
    """State s_t = (x, y, vx, vy) in metres and metres per second.
    Returns positions (n, T+1, 2), velocities (n, T+1, 2),
    actions (n, T, 2), and noisy observations (n, T+1, 2).
    """
    pos = np.empty((n_traj, n_steps + 1, 2))
    vel = np.empty((n_traj, n_steps + 1, 2))
    act = np.zeros((n_traj, n_steps, 2))

    pos[:, 0, 0] = rng.uniform(0.10, 0.30, n_traj)
    pos[:, 0, 1] = rng.uniform(0.30, 0.70, n_traj)
    vel[:, 0, 0] = v_scale * rng.uniform(0.15, 0.35, n_traj)
    vel[:, 0, 1] = v_scale * rng.uniform(-0.15, 0.15, n_traj)

    for t in range(n_steps):
        if driven:
            act[:, t] = rng.uniform(-A_MAX, A_MAX, size=(n_traj, 2))
        v = damping * vel[:, t] + act[:, t] * DT
        v = np.clip(v, -V_MAX, V_MAX)
        p = pos[:, t] + v * DT
        for axis in (0, 1):
            lo = p[:, axis] < 0.0
            hi = p[:, axis] > 1.0
            p[lo, axis] = -p[lo, axis]
            p[hi, axis] = 2.0 - p[hi, axis]
            hit = lo | hi
            v[hit, axis] = -restitution * v[hit, axis]
        p = np.clip(p, 0.0, 1.0)
        pos[:, t + 1] = p
        vel[:, t + 1] = v

    obs = pos + rng.normal(0.0, SIGMA_OBS, size=pos.shape)
    return pos, vel, act, obs


def visibility(pos, occ_half=OCC_HALF):
    """1.0 where the ball is visible, 0.0 where it is behind the occluder."""
    hidden = (
        (np.abs(pos[..., 0] - 0.5) <= occ_half)
        & (pos[..., 1] >= OCC_Y[0])
        & (pos[..., 1] <= OCC_Y[1])
    )
    return (~hidden).astype(float)


def encode(pos):
    """Frozen handcrafted representation: phase encoding of position only."""
    return np.stack(
        [
            np.cos(2 * np.pi * pos[..., 0]),
            np.sin(2 * np.pi * pos[..., 0]),
            np.cos(2 * np.pi * pos[..., 1]),
            np.sin(2 * np.pi * pos[..., 1]),
        ],
        axis=-1,
    )

Now we generate the training, validation, and test trajectories. The split is made on the trajectory axis, so no single trajectory contributes frames to two different splits. This is the discipline introduced above, applied here: the unit of the split is one whole trajectory, not one frame.

In[4]:
Code
rng = np.random.default_rng(11)
N_TRAIN, N_VAL, N_TEST = 60, 20, 20
## NOTE: several diagnostics below are sensitive to the seed; keep the seed fixed.
## Probes, hidden-horizon errors and freeze-drift diagnostics share this main split.
## Width sweeps and physical-constraint tests use separately generated fixtures.

pos_all, vel_all, act_all, obs_all = simulate(
    rng, N_TRAIN + N_VAL + N_TEST, driven=True
)
mask_all = visibility(pos_all)
## act_all already has shape (n, T, 2)

train_sl = slice(0, N_TRAIN)
val_sl = slice(N_TRAIN, N_TRAIN + N_VAL)
test_sl = slice(N_TRAIN + N_VAL, None)
Out[5]:
Console
trajectories: train=60  val=20  test=20
frames per trajectory: 31
maximum absolute velocity component: 0.553 m/s (per-component clamp 0.6)
fraction of hidden frames: 0.278
unit of the split: one complete trajectory

The hidden fraction tells us how much of the data sits behind the occluder. At 0.278 it is not small, and occlusion is not rare in this dataset. The reason occlusion matters out of proportion to its frequency is that a rare failure to maintain a coherent hidden state can dominate the downstream consequences, even if the model is otherwise accurate on 90% of the data.

Fitting the probes

We now build the probe dataset. Because the encoder is defined on observed positions, we keep only timesteps where the ball is visible, and we standardise the representation using training statistics only. Standardising with statistics computed on the full dataset is the leakage trap described above, so we are careful to compute the mean and standard deviation from the training split and apply the same transformation to validation and test.

In[6]:
Code
def visible_arrays(sl, pos, vel, obs, mask):
    Z = encode(obs[sl])
    keep = mask[sl].astype(bool)
    return Z[keep], pos[sl][keep], vel[sl][keep]


Z_tr, P_tr, V_tr = visible_arrays(train_sl, pos_all, vel_all, obs_all, mask_all)
Z_va, P_va, V_va = visible_arrays(val_sl, pos_all, vel_all, obs_all, mask_all)
Z_te, P_te, V_te = visible_arrays(test_sl, pos_all, vel_all, obs_all, mask_all)

mu, sd = Z_tr.mean(axis=0), Z_tr.std(axis=0) + 1e-9
Zt, Zv, Zs = (Z_tr - mu) / sd, (Z_va - mu) / sd, (Z_te - mu) / sd

## Targets: position (x, y) and velocity (vx, vy)
Y_tr = np.hstack([P_tr, V_tr])
Y_te = np.hstack([P_te, V_te])

print(f"train frames={Zt.shape[0]}  val={Zv.shape[0]}  test={Zs.shape[0]}")
In[7]:
Code
def ridge_fit(X, Y, alpha=1e-3):
    return np.linalg.solve(X.T @ X + alpha * np.eye(X.shape[1]), X.T @ Y)


def r2_columns(Y, Yhat):
    ss_res = np.sum((Y - Yhat) ** 2, axis=0)
    ss_tot = np.sum((Y - Y.mean(axis=0)) ** 2, axis=0)
    if np.any(ss_tot <= 0):
        raise ValueError("R-squared requires nonconstant test targets")
    return 1.0 - ss_res / ss_tot


def with_bias(X):
    return np.hstack([X, np.ones((X.shape[0], 1))])


## Undefined constant-target R-squared is rejected rather than silently assigned.
try:
    r2_columns(np.ones((3, 1)), np.ones((3, 1)))
except ValueError:
    pass
else:
    raise AssertionError("Constant-target R-squared must be rejected")

We fit four probe procedures on the same frozen features. The linear and nonlinear readouts test different forms of accessibility, the shuffled-target control reveals how the selected fitting procedure behaves on that control task, and the intercept-only baseline provides a trivial reference. The control score is not a capacity floor.

  • an intercept-only baseline, which predicts a fitted training-target constant and can score below zero on held-out R2R^2 when training and test means differ;
  • a linear probe, a ridge readout directly on zz;
  • a nonlinear probe, a ridge readout on fixed random Fourier features ϕ(z)=cos⁡(Wz+b)\phi(z) = \cos(Wz + b). With Gaussian frequencies and uniform phases, the normalized inner product (2/D)ϕ(z)⊤ϕ(z′)(2/D)\phi(z)^\top\phi(z') approximates a Gaussian kernel. We use the unnormalized features with the stated ridge penalty, so normalization would also change the effective regularization;
  • a control probe, the same nonlinear feature map, but trained on randomly permuted targets.
In[8]:
Code
## Intercept-only baseline
W_mean = ridge_fit(np.ones((Zt.shape[0], 1)), Y_tr)

## Linear probe
W_lin = ridge_fit(with_bias(Zt), Y_tr)

## Nonlinear probe: random Fourier features over the frozen representation
D_FEAT = 800
W_rff = rng.normal(0.0, 3.0, size=(D_FEAT, Zt.shape[1]))
b_rff = rng.uniform(0.0, 2 * np.pi, size=D_FEAT)


def phi(Z):
    return np.cos(Z @ W_rff.T + b_rff)


W_nl = ridge_fit(with_bias(phi(Zt)), Y_tr)

## Control probe: identical pipeline, randomly permuted training targets
perm = rng.permutation(Y_tr.shape[0])
W_ctrl = ridge_fit(with_bias(phi(Zt)), Y_tr[perm])

r2_mean = r2_columns(Y_te, np.ones((Zs.shape[0], 1)) @ W_mean)
r2_lin = r2_columns(Y_te, with_bias(Zs) @ W_lin)
r2_nl = r2_columns(Y_te, with_bias(phi(Zs)) @ W_nl)
r2_ctrl = r2_columns(Y_te, with_bias(phi(Zs)) @ W_ctrl)

probe_rows = [
    ("x position", r2_mean[0], r2_lin[0], r2_nl[0], r2_ctrl[0]),
    ("y position", r2_mean[1], r2_lin[1], r2_nl[1], r2_ctrl[1]),
    ("vx velocity", r2_mean[2], r2_lin[2], r2_nl[2], r2_ctrl[2]),
    ("vy velocity", r2_mean[3], r2_lin[3], r2_nl[3], r2_ctrl[3]),
]
Out[9]:
Console
target        mean-only   linear  nonlinear  control
x position       -0.034    0.826      0.963 -137.543
y position       -0.284    0.499     -2.135  -32.485
vx velocity      -0.001    0.012   -101.539 -109.621
vy velocity      -0.053    0.285   -170.657 -210.255

All scores are held-out R^2 on whole held-out trajectories.
The control column tests this probe family on shuffled training targets.

Read the table column by column. Each column has a specific interpretation, and the value of the table comes from comparing them.

  • The mean-only column uses an intercept-only ridge fit, a training-target constant slightly shrunk toward zero by the penalty. It can score below zero because test R2R^2 compares against the test-target mean, which need not equal that fitted constant.
  • The linear column measures accessibility to this linear readout. In this finite sample, xx and yy position achieve about 0.83 and 0.50, despite the phase encoding. Linear access need not vanish merely because the encoding is nonlinear.
  • The nonlinear column uses a high-capacity random-feature readout. It improves xx position but fails badly on yy and velocity. Its very negative scores indicate poor held-out prediction, not a lack of information established by the test.
  • The control column fits shuffled training targets. Its strongly negative scores show that this chosen probe can generalise poorly on the control task; they are not a universal capacity floor or a bound on what another probe could recover.

The four targets do not support a simple present-versus-absent classification: the results are mixed across probe families and state components. The handcrafted encoder explicitly takes only the current noisy position, yet correlations in the sampled trajectories can let a readout predict some velocity. The nonlinear readout's failure also shows why adding capacity does not guarantee a better diagnostic. Report these outcomes rather than replacing them with the intended story. The experiment illustrates probe sensitivity to fitting and data distribution; it does not prove that velocity information is absent or that position is inaccessible linearly.

Out[10]:
Visualization
Grouped bar chart of held-out R-squared for four probe families across four targets.
Held-out R-squared for four probe procedures and four state targets. The nonlinear readout improves x position but gives very negative scores on other targets; shuffled-target controls also generalise poorly. The symmetric-log vertical scale retains these failures rather than clipping them. These finite-probe results do not establish absent information.

Three things a probe can never tell you

Interpret the probe scores with three limits in mind: readout capacity, failed recovery, and downstream use.

A flexible probe may be doing substantial task learning from the information available in its frozen inputs, not creating target information missing from them. A nonlinear readout's gain can depend on both the encoded features and the readout's capacity and fitting procedure; the score does not apportion these contributions. Our control records this procedure's held-out performance after target shuffling. It bounds neither all learning on random targets nor learning on structured targets. Report the probe architecture, feature dimension, and regularisation strength so that the procedure can be reproduced.

Low linear performance does not show that information is absent. Failure of both chosen families still cannot rule out successful recovery by another readout or training procedure. A control task provides context, not an information-theoretic absence test. What you have shown is how these specific probe procedures performed on this split.

Good decoding does not establish causal necessity. If a fixed readout recovered velocity exactly almost surely throughout the target population, then vt=g(zt)v_t=g(z_t) on that population. Zero error on a finite test set does not establish that condition. Neither result shows that the transition model or policy uses the recovered velocity. Controlled representation interventions can test that use, but an intervention inside a model is not automatically an intervention in the world. Chapter 66 returns to this distinction in interpretability and world-model debugging.

There is a further trap in our toy: the encoder is handcrafted, so this experiment characterises a diagnostic procedure, not a trained world model. Its inputs are known by construction, but statistical relationships between position and velocity still depend on the trajectory distribution. A real encoder requires its own splits, controls and interpretation.

Probing is not control evaluation

A probe measures what a separate offline readout can recover from a frozen representation. A controller measures what the live system does with that representation under feedback. These are different quantities, and the second is the subject of Chapter 64. Predicting velocity statistically is not proof that a downstream planner uses a reliable velocity estimate.