ECML PKDD 2026 LFSM Wokshop research repository implementing a modular hybrid framework combining deep reinforcement learning (RL), evolutionary algorithms (EA), and uncertainty-guided surrogate optimization on continuous control tasks.
The central contribution is SC-ERL — a novel algorithm that gates genetic algorithm fitness evaluations using a learned critic as a surrogate. Instead of running every candidate policy through slow environment rollouts, the surrogate estimates fitness at near-zero cost. Epistemic uncertainty determines when the surrogate is trusted versus when a real rollout is triggered.
Baselines included: DDPG, TD3, PPO, SAC, CrossQ (all via Stable-Baselines3 / PyTorch), and canonical ERL (configured with distilled crossover).
ue_sc_erl/
├── entry_point.py # Hydra experiment launcher & auto device selection
├── pyproject.toml # Python 3.12 dependencies (uv)
├── Taskfile.yml # CLI task orchestrator
├── configs/
│ ├── config.yaml # Global defaults (seed, device, wandb, env)
│ └── algorithm/ # Per-algorithm Hydra configs
│ ├── ddpg.yaml, ppo.yaml, td3.yaml, erl.yaml
│ ├── sc_erl.yaml # Surrogate parameters (beta, dropout_p, omega, k)
│ ├── erl/<env>.yaml # Environment-specific ERL overrides
│ └── sc_erl/<env>.yaml # Environment-specific SC-ERL overrides
├── src/
│ ├── algorithms/
│ │ ├── DDPG/, TD3/, PPO/ # Thin Stable-Baselines3 wrappers (PyTorch)
│ │ ├── SAC/ # SAC via Stable-Baselines3 (PyTorch)
│ │ ├── CrossQ/ # CrossQ via sb3-contrib — batch-norm critic (PyTorch)
│ │ ├── ERL/ # Canonical ERL (TD3 + GA with shared replay buffer)
│ │ └── SC_ERL/ # Novel uncertainty-gated surrogate-assisted ERL
│ ├── common/
│ │ ├── surrogate_controller.py # Epistemic uncertainty gating & Q-value normalization
│ │ ├── sb3_callback.py # Shared eval + WandB logging callback (all SB3 baselines)
│ │ ├── utils.py # Huber loss, soft-updates, parameter flattening
│ │ ├── reply_buffer.py # Experience replay (Transition & Buffer)
│ │ └── wandb_logger.py # WandB telemetry interface
│ └── modules/
│ ├── deep_modules.py # Actor, Critic, EvidentialCritic
│ ├── ensemble_module.py # Multi-critic ensemble with prediction std
│ ├── evolution_module.py # Elite preservation, selection, sparse mutation
│ └── mc_dropout_module.py # MC Dropout runner for epistemic variance
└── plots_and_tests/
├── generate_results.py # Full reporting pipeline (plots + stats + LaTeX)
└── download_results.py # Download metrics from WandB
Requires Python 3.12 and uv.
MuJoCo v5 / DMC and MyoSuite pin incompatible mujoco/gymnasium versions, so MyoSuite lives in
its own venv directory (.venv-myosuite) rather than .venv — task run / task run-myo handle
syncing the right one automatically:
uv sync --extra mujoco-envs # MuJoCo v5 + DMC → .venv
UV_PROJECT_ENVIRONMENT=.venv-myosuite uv sync --extra myosuite # MyoSuite → .venv-myosuiteOr with pip (pick the matching dependency set from pyproject.toml's [project.optional-dependencies]):
python -m venv .venv
source .venv/bin/activate
pip install -e ".[mujoco-envs]"task run ALGO=sc_erl CLI_ARGS="env.id=HalfCheetah-v5 surrogate.mode=dropout"Supported ALGO values: sc_erl, erl, td3, ddpg, ppo, sac, crossq.
SC-ERL surrogate.mode options: dropout, ensemble, evidential, random.
sc_erl and erl always update their RL actor/critic via TD3 (twin critics, delayed policy updates, target-action smoothing) — this was the best-performing gradient update in experiments, so there is no backbone selection.
task run ALGO=sc_erl CLI_ARGS="env.id=dm_control/dog-stand-v0 surrogate.mode=ensemble"MyoSuite pins an older mujoco/gymnasium than the MuJoCo v5/DMC stack, so it runs from a
separate venv (.venv-myosuite) — task run-myo syncs and activates it automatically:
task run-myo ALGO=sc_erl ENV=myoHandReachRandom-v0MuJoCo — 50 tasks per env (10 algos × 5 seeds):
TARGET_ENV=HalfCheetah-v5 sbatch --array=0-49 slurm_run_array.shDMC dog — 20 tasks per env (4 SC-ERL modes × 5 seeds). Same script, backend auto-detected from the dm_control/ prefix:
TARGET_ENV=dm_control/dog-stand-v0 sbatch --array=0-19 slurm_run_array.sh
TARGET_ENV=dm_control/dog-walk-v0 sbatch --array=0-19 slurm_run_array.sh
TARGET_ENV=dm_control/dog-trot-v0 sbatch --array=0-19 slurm_run_array.sh
TARGET_ENV=dm_control/dog-run-v0 sbatch --array=0-19 slurm_run_array.sh
TARGET_ENV=dm_control/dog-fetch-v0 sbatch --array=0-19 slurm_run_array.shMyoSuite — 50 tasks per env (10 algos × 5 seeds), backend auto-detected from the myo prefix.
Runs from a separate venv (.venv-myosuite) — build it once per cluster checkout:
UV_PROJECT_ENVIRONMENT=.venv-myosuite uv sync --extra myosuite
TARGET_ENV=myoElbowPose1D6MRandom-v0 sbatch --array=0-49 slurm_run_array.sh
TARGET_ENV=myoHandReachRandom-v0 sbatch --array=0-49 slurm_run_array.sh
TARGET_ENV=myoHandPenTwirlRandom-v0 sbatch --array=0-49 slurm_run_array.sh
TARGET_ENV=myoHandObjHoldRandom-v0 sbatch --array=0-49 slurm_run_array.sh
TARGET_ENV=myoLegWalk-v0 sbatch --array=0-49 slurm_run_array.shtask reportRuns plots_and_tests/generate_results.py — compiles WandB metrics, statistical tests, and PDF report.
Because each environment is compared across many algorithms, the per-environment significance tables run a family of up to |proposed| × |baselines| pairwise tests (3 SC-ERL variants × 7 baselines = 21). Each pair uses a Welch t-test when both groups pass a Shapiro–Wilk normality check, otherwise the Mann–Whitney U test.
To control the family-wise error rate under this many comparisons, all raw p-values within one environment's table are corrected with the Holm–Bonferroni step-down procedure (holm_bonferroni in generate_results.py). Holm–Bonferroni is uniformly more powerful than plain Bonferroni while still bounding the probability of any false positive at α = 0.05. The tables report both the raw p and the adjusted p_adj, and significance stars (*/**/***) are decided on p_adj. Cross-environment comparisons are handled separately by the Friedman omnibus test + Nemenyi critical-difference diagram.
task cleanSC-ERL evaluates whether each candidate policy needs a real rollout or can be scored cheaply via the critic surrogate. surrogate.gate_mode selects how the deterministic part of that decision is made:
topk(default) — real-evaluate thesurrogate.rhofraction of the population with the highest predicted uncertaintyσ_Q(πᵢ), plus a random ε-coin flip (ε=0.10) on the rest so the surrogate error can still be measured on an unbiased sample.rhois adapted everysurrogate.e_hat_windowgenerations toward a target normalized surrogate errorsurrogate.e_star.relative(ablation) — real-evaluate ifσ_Q(πᵢ)exceeds a MAD-based threshold (surrogate.mad_k), or the ε-coin flip fires.
Otherwise → surrogate fitness via Lower Confidence Bound: f_LCB = μ_Q − β·σ_Q, squashed via surrogate.fitness_norm (tanh, default, or clip).
| Mode | Mechanism |
|---|---|
dropout |
T MC Dropout forward passes; empirical variance across passes |
ensemble |
N independent critic heads; std across predictions |
evidential |
Single forward pass; analytic NIG epistemic variance β/(v(α−1)) |
random |
Probabilistic coin-flip baseline (no uncertainty estimation) |
surrogate.mode=ensemble inverts rank across task classes — it beats random on DMC dog locomotion (Nemenyi rank 3.40 vs 5.60) but loses to it on classic MuJoCo (5.40 vs 4.00). We pre-registered two candidate explanations and tested both on a matched budget (dog-walk vs Swimmer-v5, 3 seeds, 500k steps, checkpointed at 20/60/100% of training) before looking at results — both were refuted, not marginally:
| criterion | threshold to confirm | threshold to refute | result |
|---|---|---|---|
| action-sensitivity ratio (dog / Swimmer) | > 3× | < 2× | 0.85× |
sigma_cv (dog vs Swimmer) |
dog > 5%, Swimmer < 2% | comparable | 2.00% vs 2.18% |
Two further observations from the same runs, independent of the gate itself:
sigma_ratio_ood < 1in every environment, every seed, every training phase. The ensemble assigns lower disagreement to out-of-distribution actions (uniform random) than to the actor's own actions — the opposite of what an epistemic estimator should do. This isn't a property of one environment or one gate config; it looks like a property of critic-disagreement-on-(s,a) as an uncertainty signal in general, relevant to any disagreement-based method in RL, not just this gate.gate_auc_d(behavioral-distance-augmented gate score, swept overλ ∈ {0, 0.25, 0.5, 1, 2}) never beat plain uncertainty-based gating on AUC — distance carries no signal the ensemble doesn't already have. Removed from the codebase (_gate_score,surrogate.lam); the analysis script (scripts/analyze_ensemble_checkpoints.py) still reportscorr_sigma_dif this needs revisiting.
Reproduction script and pre-registered pass/fail criteria: scripts/analyze_ensemble_checkpoints.py. Up to three attempts at a mechanistic explanation were budgeted; the first was unambiguous enough (not a marginal miss on either criterion) that the remaining two weren't spent. The mechanism is treated as an open problem for now — the paper reports the ranking gap without a causal explanation.
Key parameters in configs/algorithm/sc_erl.yaml:
| Parameter | Description |
|---|---|
rl.policy_noise / rl.noise_clip / rl.policy_delay |
TD3 target-smoothing noise, clip, and actor update delay |
surrogate.mode |
Uncertainty method: dropout, ensemble, evidential, random |
surrogate.beta |
LCB penalty weight (higher → more real rollouts) |
surrogate.omega |
Real-vs-surrogate coin-flip threshold, random mode only |
surrogate.gate_mode |
Gating strategy: topk (rank-based, default) or relative (MAD-threshold ablation) |
surrogate.rho / rho_min / rho_max / rho_eta |
topk real-eval budget fraction and its adaptation bounds/rate |
surrogate.e_star / e_hat_window |
Target normalized surrogate error and the generation window rho is adapted over |
surrogate.fitness_norm |
Surrogate fitness squashing: tanh (default) or clip |
surrogate.k |
Replay buffer slice size for surrogate evaluation |
surrogate.dropout_p |
Dropout probability for MC Dropout mode |
surrogate.mc_samples |
Number of MC forward passes (T) |
surrogate.k_ensembles |
Number of critic heads for ensemble mode |
evolution.mutation_std |
Gaussian mutation standard deviation |
evolution.mutation_prob |
Fraction of parameters mutated per individual |
evolution.elite_ratio |
Fraction of top individuals preserved each generation |
evolution.rl_injection_interval |
Steps between RL actor → GA population injections |
Global config (configs/config.yaml): seed, device (auto/cuda/mps/cpu), n_steps, wandb.*.
Thin wrappers around Stable-Baselines3's DDPG, TD3, and PPO. All three share the same
EvalAndLogCallback (src/common/sb3_callback.py) for periodic evaluation and WandB logging.
task run ALGO=ddpg CLI_ARGS="env.id=HalfCheetah-v5"
task run ALGO=td3 CLI_ARGS="env.id=HalfCheetah-v5"
task run ALGO=ppo CLI_ARGS="env.id=HalfCheetah-v5"Key config knobs: rl.learning_rate, rl.tau (DDPG/TD3), rl.exploration_noise_std (DDPG/TD3),
rl.policy_noise/rl.noise_clip/rl.policy_delay (TD3), rl.gae_lambda/rl.clip_param/rl.ppo_epochs (PPO).
A thin wrapper around Stable-Baselines3 SAC. Uses the same WandB callback and eval loop as the rest of the framework. No extra setup beyond uv sync.
task run ALGO=sac CLI_ARGS="env.id=HalfCheetah-v5"Key config knobs (configs/algorithm/sac.yaml): rl.learning_rate, rl.ent_coef (auto or float), warmup.warmup_steps.
A thin wrapper around sb3-contrib CrossQ — a batch-normalised critic algorithm that is highly sample-efficient. Runs on PyTorch, uses the same device as all other algorithms.
task run ALGO=crossq CLI_ARGS="env.id=HalfCheetah-v5"Key config knobs (configs/algorithm/crossq.yaml): rl.learning_rate, rl.gradient_steps, rl.policy_delay.
HalfCheetah-v5, Hopper-v5, Walker2d-v5, Ant-v5, Swimmer-v5.
Five DMC dog locomotion tasks are supported through shimmy, which registers dm_control environments into the Gymnasium registry:
| Task | Env ID |
|---|---|
| Stand | dm_control/dog-stand-v0 |
| Walk | dm_control/dog-walk-v0 |
| Trot | dm_control/dog-trot-v0 |
| Run | dm_control/dog-run-v0 |
| Fetch | dm_control/dog-fetch-v0 |
The environment backend is selected via env.backend:
auto(default) — detectsdm_control/,fancy/,metaworld/, ormyoprefixes automatically.mujoco— force MuJoCo/Gymnasium without shimmy import.fancy_gym— force shimmy import regardless of env ID.myosuite— force MyoSuite import regardless of env ID.
Environment-specific configs under configs/algorithm/sc_erl/ are auto-loaded and already set backend: fancy_gym for all dog tasks.
Five MyoSuite tasks are supported. MyoSuite
pins mujoco<3.7/gymnasium<1.3, incompatible with the mujoco>=3.10/gymnasium>=1.3 stack used
for MuJoCo v5/DMC — it lives in its own venv directory, .venv-myosuite (task run-myo syncs and
uses it automatically). See Setup.
| Task | Env ID |
|---|---|
| Elbow pose (1D, 6 muscles) | myoElbowPose1D6MRandom-v0 |
| Hand reach | myoHandReachRandom-v0 |
| Hand pen twirl | myoHandPenTwirlRandom-v0 |
| Hand object hold | myoHandObjHoldRandom-v0 |
| Leg walk | myoLegWalk-v0 |
task run-myo ALGO=sc_erl ENV=myoHandReachRandom-v0Environment-specific configs under configs/algorithm/sc_erl/ (e.g. sc_erl_myoHandReachRandom-v0.yaml) set env.id/env.backend; no surrogate/evolution hyperparameters have been tuned for these envs yet, so they inherit the global sc_erl.yaml defaults.