|
| 1 | +Sensitivity Analysis |
| 2 | +==================== |
| 3 | + |
| 4 | +The sensitivity-analysis toolbox answers a single question about a policy: |
| 5 | +*which environment conditions drive success?* Given the per-episode results of an |
| 6 | +evaluation sweep — where factors such as lighting, object mass, or table material were |
| 7 | +varied — it fits a posterior over those factors conditioned on the outcome (e.g. success |
| 8 | +rate) and renders one figure summarising which factor values are associated with success. |
| 9 | + |
| 10 | +Two distinct ideas are at work. *Joint* means all factors are modelled together rather than |
| 11 | +one at a time, which is what captures interactions and confounds (see the next section). |
| 12 | +*Posterior* means the result is conditioned on the outcome: starting from the prior — the |
| 13 | +factor values the sweep actually drew, uniform over the declared ranges — it reweights them |
| 14 | +by how often each led to the chosen outcome. So the figure answers *given success, which |
| 15 | +factor values were in play?*, not merely *how were the factors distributed in the sweep?* |
| 16 | + |
| 17 | +Why a joint posterior, not a success rate per factor? |
| 18 | +----------------------------------------------------- |
| 19 | + |
| 20 | +The simplest analysis would chart a success rate for each factor independently. That hides |
| 21 | +the two things that matter most in a multi-factor sweep: |
| 22 | + |
| 23 | +- **Factors interact.** How much light a policy needs can depend on the object — a matte |
| 24 | + object may succeed at low light while a shiny one needs far more. A per-factor |
| 25 | + "success vs light" curve averages over objects and reports one blurry gate that is wrong |
| 26 | + for both. The joint posterior keeps the interaction, so you can condition on a specific |
| 27 | + object and see its gate. |
| 28 | +- **Factors confound each other.** If bright-light episodes also happened to use an easy |
| 29 | + object, a per-factor light chart cannot tell which one drove success. Modelling all |
| 30 | + factors together attributes the effect to the factor that actually carries it. |
| 31 | + |
| 32 | +The per-factor rate is a projection of the joint posterior — derivable from it, but not the |
| 33 | +other way around. The toolbox therefore always fits the joint — via simulation-based |
| 34 | +inference (MNPE or NPE) — and reads the per-factor marginals from it. |
| 35 | + |
| 36 | +How it works |
| 37 | +------------ |
| 38 | + |
| 39 | +The toolbox is a thin analysis layer over `sbi <https://sbi.readthedocs.io>`_'s |
| 40 | +neural posterior estimators. The flow is: |
| 41 | + |
| 42 | +1. **Per-episode input.** The analysis reads an ``episode_summary.jsonl`` — one row per |
| 43 | + episode, holding that episode's factor values and outcomes. |
| 44 | +2. **Schema.** A ``factors.yaml`` declares the *factors* — which ``arena_env_args`` columns |
| 45 | + were varied and whether each is continuous or categorical, plus the continuous ranges |
| 46 | + that were swept (so the analyzer's prior matches the simulation). It does **not** list |
| 47 | + outcomes — *which* outcome to condition on is chosen at analysis time, not saved here. |
| 48 | +3. **Inference.** ``SensitivityAnalyzer`` loads the pair, trains an estimator on the full |
| 49 | + ``(theta, x)`` jointly — sbi's terms for the factor values (``theta``) and the per-episode |
| 50 | + outcomes (``x``) — and samples the joint posterior conditioned on a chosen observation |
| 51 | + (by default, success). |
| 52 | +4. **Report.** A probability density curve for each continuous factor and a probability bar |
| 53 | + chart for each categorical factor. |
| 54 | + |
| 55 | +.. todo:: |
| 56 | + |
| 57 | + The eval-runner writer (``episode_writer``) that emits ``episode_summary.jsonl`` during |
| 58 | + evaluation is not part of this version — it lands in a follow-up. For now, run the analysis |
| 59 | + on synthetic data (see below) or on a JSONL produced externally. |
| 60 | + |
| 61 | +Inputs |
| 62 | +------ |
| 63 | + |
| 64 | +**factors.yaml** declares only the factors that were varied (and the continuous ranges that |
| 65 | +were swept). Outcomes are not declared here — they're selected at analysis time (see below): |
| 66 | + |
| 67 | +.. code-block:: yaml |
| 68 | +
|
| 69 | + factors: |
| 70 | + light_intensity: |
| 71 | + type: continuous |
| 72 | + range: [[0.0, 5000.0]] # the swept range; inferred from the data's min/max if omitted |
| 73 | + table_material: |
| 74 | + type: categorical |
| 75 | + choices: [oak, walnut, bamboo] |
| 76 | +
|
| 77 | +**episode_summary.jsonl** holds one JSON object per episode. It carries every measured |
| 78 | +outcome; the analysis picks which one(s) to condition on: |
| 79 | + |
| 80 | +.. code-block:: json |
| 81 | +
|
| 82 | + {"job_name": "pi0_sweep", "episode_idx": 0, |
| 83 | + "arena_env_args": {"light_intensity": 3200.0, "table_material": "oak"}, |
| 84 | + "outcomes": {"success": 1}} |
| 85 | +
|
| 86 | +Choice of estimator |
| 87 | +------------------- |
| 88 | + |
| 89 | +``SensitivityAnalyzer`` picks the estimator from the schema automatically: |
| 90 | + |
| 91 | +.. list-table:: |
| 92 | + :header-rows: 1 |
| 93 | + :widths: 25 25 50 |
| 94 | + |
| 95 | + * - Schema |
| 96 | + - Estimator |
| 97 | + - Notes |
| 98 | + * - Any categorical factor |
| 99 | + - MNPE |
| 100 | + - Mixed density estimator; handles continuous + categorical factors together. |
| 101 | + * - All continuous factors |
| 102 | + - NPE |
| 103 | + - Restricts to a Gaussian on a single factor, so a meaningful continuous-only |
| 104 | + analysis needs at least two continuous factors. |
| 105 | + |
| 106 | +Continuous factors are normalised to ``[0, 1]`` before fitting and de-normalised when |
| 107 | +sampling, so factors on very different scales (e.g. light in the thousands, an offset in |
| 108 | +the hundredths) train on equal footing. Outcomes are binary (0/1); the default query |
| 109 | +conditions on success (1). |
| 110 | + |
| 111 | +Running a report |
| 112 | +---------------- |
| 113 | + |
| 114 | +Point the report generator at a ``(factors.yaml, episode_summary.jsonl)`` pair. The output |
| 115 | +format follows the file extension (``.png``, ``.pdf``, …); reports are written under |
| 116 | +``eval/`` by default. |
| 117 | + |
| 118 | +.. code-block:: bash |
| 119 | +
|
| 120 | + python -m isaaclab_arena.analysis.sensitivity.generate_report \ |
| 121 | + --factors_yaml factors.yaml \ |
| 122 | + --episode_summary episode_summary.jsonl \ |
| 123 | + --outcome success \ |
| 124 | + --output eval/sensitivity_report.png |
| 125 | +
|
| 126 | +``--outcome`` selects which per-episode outcome(s) to condition on (keys in the rows' |
| 127 | +``outcomes`` block); it defaults to ``success``. Pass ``--observation`` to set the value |
| 128 | +per outcome — since outcomes are binary, use ``1`` for success or ``0`` for failure; it |
| 129 | +defaults to ``1`` (success). |
| 130 | + |
| 131 | +Trying it on synthetic data |
| 132 | +--------------------------- |
| 133 | + |
| 134 | +A synthetic simulator with a *known* ground truth lets you run the whole pipeline without |
| 135 | +Isaac Sim — useful for seeing the output shape and for validating the toolbox |
| 136 | +(the recovered posterior should reflect the planted relationship): |
| 137 | + |
| 138 | +.. code-block:: bash |
| 139 | +
|
| 140 | + # mixed: three continuous + two categorical factors (MNPE) |
| 141 | + python -m isaaclab_arena.tests.sensitivity_synthetic --kind mixed --output eval/demo.png |
| 142 | +
|
| 143 | +``--kind`` also accepts ``continuous`` (continuous-only factors, which exercises the NPE path). |
| 144 | + |
| 145 | +Reading the output |
| 146 | +------------------ |
| 147 | + |
| 148 | +.. todo:: |
| 149 | + |
| 150 | + Add a sample report figure here and walk through reading it. |
| 151 | + |
| 152 | +Each panel is the posterior over one factor *conditioned on success*. Intuitively it answers |
| 153 | +"given the policy succeeded, which values of this factor were responsible?" More precisely, |
| 154 | +among the successful episodes it shows the probability density that the factor took each |
| 155 | +value. For a continuous factor, mass concentrated at one end of its range means success |
| 156 | +favoured that end — e.g. a curve rising toward bright light means successful episodes were |
| 157 | +almost all bright ones, i.e. the policy needs bright light to succeed. |
| 158 | +For a categorical factor, the tallest bar is the value most associated with success. |
| 159 | + |
| 160 | +Current scope |
| 161 | +------------- |
| 162 | + |
| 163 | +- Outcomes are treated as **binary** (0/1). Conditioning defaults to success; a continuous |
| 164 | + outcome is rejected with a clear error rather than silently averaged. |
| 165 | +- Continuous **vector** factors (``dim > 1``) are reserved for a future extension. The likely |
| 166 | + approach is to record scalar reductions (e.g. a norm or distance-to-reference) alongside the |
| 167 | + raw vector, so a pose or RGB factor becomes one or more analysable scalar columns. |
| 168 | +- The estimators run on CPU and do not require Isaac Sim, so a report can be generated |
| 169 | + anywhere the evaluation JSONL is available. |
| 170 | +- The analysis assumes the ``episode_summary.jsonl`` is a single coherent slice — one |
| 171 | + policy, task, and embodiment. **TODO:** add a filter (in the spirit of robolab's |
| 172 | + ``--filter-policy`` / ``--filter-task``) to select that slice from a larger JSONL, |
| 173 | + rather than relying on the caller to pre-filter it. |
0 commit comments