PILCO, PETS, Ensembles, and MBPO › Worked example: fixed-controller branch evaluation under model bias

Michael BrenndoerferJuly 13, 20266 min read
World Models HandbookAll chapters
Part 8 of 12Decision-Centric Research Lineages
Chapter 43PILCO, PETS, Ensembles, and MBPO › Worked example: fixed-controller branch evaluation under model bias
Reading section 7 of 10 · Worked example: fixed-controller branch evaluation under model bias · 6 minSection 7 of 10 · 6 min

Book contents

Section 7 of 10 · 6 min read

PILCO, PETS, Ensembles, and MBPO › Worked example: fixed-controller branch evaluation under model biasOne section · Sections in this chapter7 / 10 ↓
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

We will use a one-dimensional system to isolate multi-step prediction error. The true dynamics and biased model receive the same initial states, action sequences, and process-noise draws. These common random numbers remove sampling differences from the model-versus-truth comparison; the remaining discrepancy comes from the action-gain bias.

In[5]:
Code
import numpy as np

A_true, B_true = 0.9, 0.5
A_hat, B_hat = 0.9, 0.6
horizon = 12
n_trials = 200

rng_rmse = np.random.default_rng(0)
initial_states = rng_rmse.normal(size=n_trials)
actions = rng_rmse.normal(size=(n_trials, horizon))
process_noise = 0.05 * rng_rmse.normal(size=(n_trials, horizon))

s_true = initial_states.copy()
s_biased = initial_states.copy()
s_exact = initial_states.copy()
biased_sq_error = np.zeros(horizon)
exact_sq_error = np.zeros(horizon)

for t in range(horizon):
    eps_t = process_noise[:, t]
    a_t = actions[:, t]
    s_true = A_true * s_true + B_true * a_t + eps_t
    s_biased = A_hat * s_biased + B_hat * a_t + eps_t
    s_exact = A_true * s_exact + B_true * a_t + eps_t
    biased_sq_error[t] = np.mean((s_biased - s_true) ** 2)
    exact_sq_error[t] = np.mean((s_exact - s_true) ** 2)

rmse = np.sqrt(biased_sq_error)
rmse_exact = np.sqrt(exact_sq_error)

The exact-model control line is zero because it receives the same disturbances as the true system. This is a diagnostic use of common random numbers, not a claim that a predictive distribution has zero aleatoric variance.

Out[6]:
Console
step  1: biased-model RMSE = 0.1043
step  2: biased-model RMSE = 0.1308
step  3: biased-model RMSE = 0.1524
step  4: biased-model RMSE = 0.1720
step  5: biased-model RMSE = 0.1906
step  6: biased-model RMSE = 0.1969
step  7: biased-model RMSE = 0.2177
step  8: biased-model RMSE = 0.2192
step  9: biased-model RMSE = 0.2189
step 10: biased-model RMSE = 0.2201
step 11: biased-model RMSE = 0.2241
step 12: biased-model RMSE = 0.2226

Action-gain bias: B_hat - B_true = 0.10
Final-step RMSE (12 steps): 0.2226
Out[7]:
Visualization
Two RMSE curves versus rollout step: the biased-model curve grows overall with small fluctuations, while the exact-model common-random-number baseline remains at zero.
Toy linear-system simulation with common random numbers. The biased model and true system share initial states, actions, and process-noise draws, so the biased-model RMSE isolates the 0.1 action-gain error. The exact-model comparison remains at zero; the biased curve grows overall with finite-sample fluctuations and approaches a stable-system plateau.

The biased curve need not increase at every sampled step. In this stable system, the error obeys a stable recurrence driven by the random term (Bhat−Btrue)at(B_{\text{hat}}-B_{\text{true}})a_t, so its population variance approaches a finite limit. Different systems can amplify, cancel, oscillate, or saturate model error. Dimensionality alone does not determine error propagation: stability, coupling, error direction, and controller sensitivity matter. The task objective can change how consequential an error is and, when it changes action selection, which trajectories are encountered.

Coherent fixed-controller branch evaluation

The next toy is deliberately narrower than policy training. No policy is learned, no replay mixture is optimized, and this is not a literal MBPO implementation. We evaluate a grid of fixed proportional controllers, a=−Ksa=-Ks, under the true and biased simulators. Each branch starts from the same bank of states, runs coherently for kk consecutive steps, and receives the same process-noise draws in both simulators and for every candidate gain.

In[8]:
Code
rng_branches = np.random.default_rng(43)
n_branches = 4096
max_branch = 12
branch_lengths = np.arange(1, max_branch + 1)
Ks = np.linspace(0.0, 2.0, 41)

branch_starts = rng_branches.normal(size=n_branches)
branch_noise = 0.05 * rng_branches.normal(size=(max_branch, n_branches))


def cumulative_branch_scores(A, B):
    """Score fixed feedback gains on coherent branches with shared randomness."""
    scores = np.empty((max_branch, len(Ks)))
    for j, K in enumerate(Ks):
        s = branch_starts.copy()
        cumulative_cost = np.zeros(n_branches)
        for t in range(max_branch):
            a = -K * s
            s = A * s + B * a + branch_noise[t]
            cumulative_cost += s**2 + 0.05 * a**2
            scores[t, j] = -np.mean(cumulative_cost)
    return scores


true_branch_scores = cumulative_branch_scores(A_true, B_true)
model_branch_scores = cumulative_branch_scores(A_hat, B_hat)
score_rmse = np.sqrt(
    np.mean((model_branch_scores - true_branch_scores) ** 2, axis=1)
)

true_best_gain = Ks[np.argmax(true_branch_scores, axis=1)]
model_best_gain = Ks[np.argmax(model_branch_scores, axis=1)]
Out[9]:
Console
k= 1: true-best K=1.50, model-best K=1.30, score RMSE=0.0516
k= 3: true-best K=1.55, model-best K=1.35, score RMSE=0.0915
k=12: true-best K=1.55, model-best K=1.35, score RMSE=0.1234
Out[10]:
Visualization
Line chart showing score RMSE rising from about 0.052 at branch length one to 0.123 at length twelve, with progressively smaller increments.
Seeded toy evaluation of fixed proportional controllers under true and biased linear dynamics. Each point is the RMSE, across candidate gains, between cumulative true-system and model scores for coherent branches of length k. All candidates and both simulators use the same 4,096 branch starts and process-noise draws; no policy is trained.

Every model branch advances from its own current model state for exactly kk steps; there is no temporally lagged "real" state and no periodic reset inside a branch. Common random numbers make comparisons insensitive to candidate-evaluation order and reduce Monte Carlo noise in model-versus-truth differences. In this seeded toy, the plotted score discrepancy grows over the displayed horizons and then begins to level as the stable closed-loop dynamics contract.

The printed gains are evaluation results for fixed controllers, not evidence about policy training. The example supports only the local conclusion that longer coherent branches expose cumulative controller scores to more consequences of this particular model bias. It does not establish that three steps are negligible, that a short branch always selects a controller closer to the true optimum, or that changing one bias parameter moves an optimal kk monotonically.

For integer branch lengths, a candidate kk should be compared with neighboring values and validated on the downstream task. Equality of marginal benefit and marginal cost is only an intuition for a differentiable continuous relaxation; boundary optima and non-monotone finite differences are possible.