Skip to content
intermediate

Test How Demonstrations Shape Later Online Reinforcement Learning

A policy cloned from demonstrations can look brilliant and still be useless. It scores well on the states it was shown, then stalls the moment the world…

Published 2026-10-02Updated 2026-10-0410 min read
Dynamic abstract 3D rendering with blue geometric shapes and digital effects.
Dynamic abstract 3D rendering with blue geometric shapes and digital effects. Photo by Google DeepMind on Pexels.

A policy cloned from demonstrations can look brilliant and still be useless. It scores well on the states it was shown, then stalls the moment the world drifts off the demonstrated path. The cheapest way to find out whether environment feedback fixes that — or whether the demonstrations quietly cap it — is to run the experiment yourself.

This tutorial builds a small sequential task, freezes a demonstrations-only baseline, trains a learner from scratch, and then trains a learner that starts from the same demonstrations while continuing to receive environment feedback. The decisive number is not "does the learner beat random." It is does the learner beat the frozen demonstration policy.

If you have already written a tabular Q-learning loop and you understand that demonstrations can initialize, guide, or constrain a policy, you have everything you need. We will not re-teach either one here.

What This Experiment Actually Tests

A three-row comparison shows the demonstrations-only policy starts from demonstrations and receives no online updates; the from-scratch learner starts with blank values and receives feedback; the demonstration-initialized learner starts from demonstration values and also receives feedback. All three point to the same held-out evaluation starts.
Comparing these three conditions separates a demonstration head start from improvement driven by online feedback.

One question: do demonstrations raise the ceiling, or do they become the ceiling?

To answer it, you compare three conditions against a fixed evaluation protocol:

ConditionWhat it getsWhat it can do
Demonstrations-only baselineThe demonstration setNothing — the policy is frozen
From-scratch learnerEnvironment feedback onlyAct, update, explore
Demonstration-initialized learnerThe same demonstrations plus environment feedbackStart from demonstrated values, then keep updating

The measured quantity is return per episode over training, plus a final evaluation on a held-out set of start states. That second measurement matters more than the first. A policy can look strong on the training distribution and fall apart off it.

State the hypothesis before you write a line of code: feedback should allow improvement beyond the demonstrations only if the demonstrations are suboptimal and the learner can still explore. If either condition fails, expect a flat curve — and expect that flatness to be informative rather than broken.

Prerequisites and Environment Assumptions

You need Python 3, NumPy, and Matplotlib. No deep-learning framework, no GPU, no external services, no credentials.

The task is a small deterministic gridworld with a sparse goal reward and a step penalty. It is small enough that a Q-table is exact and the whole run finishes in seconds.

Why tabular? Because it removes function-approximation noise from the comparison. If a neural network is unstable, you cannot tell whether a difference between conditions came from the demonstrations or from the optimizer. Here, any difference is attributable to the demonstrations.

Fix your seeds and fix your evaluation protocol. Same seeds, same number of evaluation episodes, same starting states for every condition. Without that discipline, you are comparing noise to noise.

Build the Task and the Demonstration Set

Start with the environment. A grid with a goal cell, a step penalty, and termination on reaching the goal is enough:

import numpy as np

GRID = 6
GOAL = (5, 5)
ACTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)]  # up, down, left, right

def step(state, action):
    r, c = state
    dr, dc = ACTIONS[action]
    nr, nc = min(max(r + dr, 0), GRID - 1), min(max(c + dc, 0), GRID - 1)
    next_state = (nr, nc)
    if next_state == GOAL:
        return next_state, 1.0, True
    return next_state, -0.01, False

Now generate demonstrations from a deliberately imperfect scripted policy. A shortest-path walker with a fixed error rate is ideal: good enough to be useful, bad enough that improvement is possible.

def scripted_action(state, rng, error_rate=0.2):
    if rng.random() < error_rate:
        return int(rng.integers(len(ACTIONS)))
    r, c = state
    gr, gc = GOAL
    if r < gr:
        return 1
    if c < gc:
        return 3
    return 0

def collect_demos(n_episodes, rng):
    demos = []
    for _ in range(n_episodes):
        state, traj = (0, 0), []
        for _ in range(100):
            action = scripted_action(state, rng)
            next_state, reward, done = step(state, action)
            traj.append((state, action))
            state = next_state
            if done:
                break
        demos.append(traj)
    return demos

Record demonstrations both as (state, action) pairs and as full trajectories. The pair list is what you will clone from; the trajectory structure is what you will need later when you reason about credit assignment.

Print the grid, the demonstration success rate, and the average return of the demonstration policy. Those three numbers are your first milestone.

Common mistake: collecting demonstrations that cover only the optimal corridor. If every demo hugs the same path, the covariate-shift failure stays invisible, and you will conclude demonstrations are harmless when you have simply never tested them off-path.

Knowledge check

Check your understanding

Answer this question before you continue.

Why does the experiment preserve demonstrations both as state-action pairs and as full trajectories?
Single Choice

Focus: Explain why the experiment keeps both state-action demonstration pairs and trajectory structure.

Baseline 1: Freeze the Demonstrations

Fit a simple state-to-action policy from the demonstration pairs. A lookup table with a fallback for unseen states is enough:

def fit_policy(demos):
    counts = {}
    for traj in demos:
        for state, action in traj:
            counts.setdefault(state, np.zeros(len(ACTIONS)))
            counts[state][action] += 1
    return {s: int(np.argmax(v)) for s, v in counts.items()}

def frozen_action(policy, state):
    return policy.get(state, 0)  # fallback: always "up"

Evaluate it on the same protocol you will use for the learner. Report mean return and success rate.

Then watch it break. On states the demonstrations never visited, the fallback action produces a visible stall or a loop — the agent walks into a wall, or oscillates between two cells, forever. That is covariate shift made physical.

Write this number down before you train anything else. It is the comparison baseline the learner must beat.

A high demonstration score is not evidence of a good policy. It is evidence of good coverage. Those are different claims, and conflating them is how people ship imitation policies that fail in production.

Knowledge check

Check your understanding

Answer this question before you continue.

The frozen policy reaches a state that never appeared in its demonstrations. What action does the provided fallback choose?
Scenario Interpretation

Focus: Predict the frozen policy's behavior when it encounters a state absent from the demonstrations.

Baseline 2: Learn From Scratch

Run a standard tabular Q-learning loop with no demonstrations at all. Same seeds, same evaluation protocol.

def train_q(episodes, rng, lr=0.1, gamma=0.95,
            eps_start=1.0, eps_end=0.05, eps_decay=0.995,
            init_Q=None):
    Q = np.zeros((GRID, GRID, len(ACTIONS))) if init_Q is None else init_Q.copy()
    returns = []
    eps = eps_start
    for _ in range(episodes):
        state, total = (0, 0), 0.0
        for _ in range(100):
            if rng.random() < eps:
                action = int(rng.integers(len(ACTIONS)))
            else:
                action = int(np.argmax(Q[state[0], state[1]]))
            next_state, reward, done = step(state, action)
            best_next = 0.0 if done else np.max(Q[next_state[0], next_state[1]])
            Q[state[0], state[1], action] += lr * (
                reward + gamma * best_next - Q[state[0], state[1], action]
            )
            state, total = next_state, total + reward
            if done:
                break
        returns.append(total)
        eps = max(eps_end, eps * eps_decay)
    return Q, returns

Expect slow early progress. With a sparse goal reward and a step penalty, random exploration rarely reaches the goal, so the value signal arrives late and thin.

This curve answers a different question than the demonstration baseline: how much of the learner's speed comes from the demonstrations versus from the task itself?

Common mistake: comparing the demonstration-initialized learner only against the frozen baseline and concluding demonstrations helped — when the from-scratch learner would have reached the same return anyway, just later. Without this second baseline, you cannot separate "demonstrations accelerated learning" from "the task was easy."

Knowledge check

Check your understanding

Answer this question before you continue.

Why does the article expect slow early progress from the learner trained without demonstrations?
Misconception Check

Focus: Relate sparse goal feedback to the expected early behavior of the from-scratch learner.

Add Demonstrations to the Learner

Now the actual experimental condition. Initialize the Q-table from the demonstrations — set demonstrated action values above the alternatives:

Q_init = np.zeros((GRID, GRID, len(ACTIONS)))
for traj in demos:
    for state, action in traj:
        Q_init[state[0], state[1], action] = 1.0

Q_demo, returns_demo = train_q(EPISODES, rng, init_Q=Q_init)

That is initialization only: the demonstrated values set the starting point, and after that the standard update loop is free to overwrite them. This is the mechanism the headline three-condition experiment isolates. Guidance — a bonus that keeps pulling the policy back toward demonstrated actions during training — is a separate mechanism, and mixing it in here would make the curve impossible to attribute. We will test it as a follow-up instead.

Knowledge check

Check your understanding

Answer this question before you continue.

In the article's main demonstration-initialized condition, what happens to the demonstrated action values after training begins?
Comparison Reasoning

Focus: Distinguish demonstration-based Q-value initialization from ongoing demonstration guidance.

Evaluate on Held-Out Start States

Training return tells you how the learner behaves on the distribution it trained on. It does not tell you whether the policy generalizes. Define a fixed held-out set of start states and evaluate every condition on the same starts:

EVAL_STARTS = [(0, 0), (0, 3), (2, 2), (3, 0), (4, 4)]

def evaluate(Q, starts, rng, episodes_per_start=20):
    scores = []
    for start in starts:
        for _ in range(episodes_per_start):
            state, total = start, 0.0
            for _ in range(100):
                action = int(np.argmax(Q[state[0], state[1]]))
                state, reward, done = step(state, action)
                total += reward
                if done:
                    break
            scores.append(total)
    return float(np.mean(scores))

For the frozen policy, replace the argmax line with frozen_action(policy, state). For the from-scratch learner, pass its Q. For the demonstration-initialized learner, pass Q_demo.

Now plot all three curves on one axis:

import matplotlib.pyplot as plt

plt.plot(returns_frozen, label="demonstrations only")
plt.plot(returns_scratch, label="from scratch")
plt.plot(returns_demo, label="demo-initialized")
plt.xlabel("episode")
plt.ylabel("return")
plt.legend()
plt.show()

Expected output: the frozen baseline is a flat horizontal line. The from-scratch curve climbs slowly. The demo-initialized curve starts high and either keeps climbing or flattens below the frozen line. Both outcomes are results.

Read the Curves: Did Feedback Beat the Demonstrations?

The interesting outcome is a learner that starts near the demonstration return and then exceeds it. That is feedback doing work the demonstrations could not.

The disappointing outcome is a learner that starts high and stays flat or decays. Diagnose the cause before you blame the method:

  • Too little exploration — epsilon decayed too fast, so the learner never found states the demonstrations missed.
  • Demonstrations that already saturate the task — there is simply no headroom left.
  • Initial values too confident for the update rule to correct within the episode budget.

Report the held-out evaluation separately from training return. A policy can look strong on the training distribution and fail off it, and that gap is often the most important number in the whole run.

Decision rule: if the learner never exceeds the frozen baseline, the demonstrations are acting as a ceiling, not a springboard.

Failure Modes and Debugging Signals

SymptomLikely causeOne change to try
Flat curve at the demonstration levelExploration collapsedRaise the epsilon floor; slow the decay
Curve drops after initializationInitial values too confident for the update rule to correct, or learning rate too high for pre-filled valuesLower the learning rate; reduce the initial value magnitude
Strong training return, weak held-out returnPolicy memorized demonstration states and never generalizedEvaluate on the held-out starts; add exploration
Noisy, non-monotonic curveToo few episodes or a single seedAverage over several seeds before drawing conclusions

Each symptom maps to one concrete change. Make it, re-run, and compare against the original three curves on the same axes.

One Modification Worth Running

Change one variable at a time. Pick one:

  • Demonstration quality — raise the scripted policy's error rate.
  • Demonstration quantity — collect fewer trajectories.
  • Guidance — add a decaying bonus for demonstrated actions and compare it against initialization-only.

Predict the outcome before you run it. My expectation: noisier demonstrations should lower the starting return but may raise the final return, because feedback has more room to correct them. If that prediction holds, you have learned something about the mechanism, not just about the code.

Compare the modified run against the original three curves. This is where you find out whether demonstrations are a starting point or a constraint in your setup, not just in this article's.

What the Experiment Earns You

Demonstrations buy you a starting point. Environment feedback is what buys you a ceiling above it. If the learner never passes the frozen baseline, the demonstrations are constraining rather than accelerating — and now you have the curve to prove it.

The natural next step is to vary demonstration quality and quantity systematically, then read the results as evidence about your own pipeline rather than as a leaderboard. The question is never "are demonstrations good?" It is "good for what, and at what cost to the learner's freedom to improve?"

Knowledge check

Final check

Finish the article by checking the ideas you just learned.

A learner has strong training return but weak return on the fixed held-out starts. What is the most appropriate interpretation?
Question 1 of 2Scenario Interpretation

Focus: Interpret a gap between training performance and held-out evaluation as evidence about generalization.

The demonstration-initialized learner reaches a given return sooner than the frozen baseline. Which comparison is also needed to tell whether demonstrations accelerated learning rather than the task simply being easy?
Question 2 of 2Comparison Reasoning

Focus: Use the from-scratch baseline to distinguish demonstration acceleration from task difficulty.

References

  1. [PDF] Reinforcement Learning with Demonstrations using Successor ...proceedings.mlr.press
Practical resource

Build a stronger RL mental model

Use structured resources to connect equations, algorithms, and observable agent behavior.

Browse resources
Related sites

Continue across related AI foundations

Use LearnPyFast for Python foundations and LearnLLMFast for practical language-model and agent application concepts.

Python tutorialstutorial

LearnPyFast

Beginner-friendly Python tutorials, examples, and learning paths for practical programming foundations.

PythonProgrammingBeginners
Visit LearnPyFast
LLM tutorialstutorial

LearnLLMFast

Practical LLM tutorials for builders who want to understand prompting, workflows, agents, and AI applications.

LLMAIBuilders
Visit LearnLLMFast

Keep learning

Related reinforcement learning tutorials

Continue with nearby RL concepts, algorithms, and experiments that build on the same decision process.