.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "auto_examples/01_decoding_pipeline/03_expected_uncertainty.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_auto_examples_01_decoding_pipeline_03_expected_uncertainty.py: ============================================================ Expected decoding uncertainty via simulate + decode ============================================================ Fisher information gives a *local* lower bound on the variance of any unbiased estimator of a stimulus from a fitted encoding model. It's quick to compute and useful as a theoretical bound, but it ignores two things you usually care about in practice: * the actual prior / stimulus distribution you'll evaluate on, * the *bias* of the maximum-a-posteriori or posterior-mean estimator (which Fisher info, being a Cramér–Rao bound, says nothing about). A more direct alternative is to **simulate from the fitted model** and **re-decode** the simulated responses through the same model. The distribution of the posterior-mean estimate across many simulated trials at each stimulus value tells you: * the **bias** :math:`E[\hat{s}] - s`, and * the **expected uncertainty** :math:`\sqrt{\mathrm{Var}[\hat{s}]}`, both as functions of the stimulus. This is the procedure used in ``neural_priors/encoding_model/get_expected_uncertainty.py``; the example below ports it to public ``braincoder`` APIs on the bundled demo dataset. .. GENERATED FROM PYTHON SOURCE LINES 28-42 .. code-block:: Python import matplotlib.pyplot as plt import numpy as np import pandas as pd from braincoder.models import LogGaussianPRF from braincoder.optimize import ParameterFitter, ResidualFitter from braincoder.utils.data import load_pratcarrabin2025_npc from braincoder.utils.math import get_expected_value, get_sd_posterior from braincoder.utils.stats import ( fit_r2_mixture, get_rsq, ) .. GENERATED FROM PYTHON SOURCE LINES 43-46 Load the demo + select voxels ----------------------------------------------------------------- Same voxel-selection step as the first two examples — kept self-contained. .. GENERATED FROM PYTHON SOURCE LINES 46-67 .. code-block:: Python bundle = load_pratcarrabin2025_npc() r2 = bundle['r2'] paradigm_all = bundle['paradigm'] data_all = bundle['data'] r2_wb = bundle['r2_wholebrain'].get_fdata() mask_wb = bundle['brain_mask'].get_fdata().astype(bool) fit = fit_r2_mixture(r2_wb[mask_wb]) # Noise μ + 2σ on the logit scale — see example 1 for the rationale. threshold = 1.0 / (1.0 + np.exp(-(fit['noise_mu'] + 2 * fit['noise_sigma']))) keep = r2.index[r2 > threshold] print(f'Kept {len(keep)} voxels (within-sample R² ≥ {threshold:.3f})') # Restrict to wide-range trials and z-score within the selected voxel set. is_wide = paradigm_all['range'].values == 'wide' paradigm = paradigm_all.loc[is_wide].reset_index(drop=True) data = data_all.loc[is_wide, keep].reset_index(drop=True).astype(np.float32) data = (data - data.mean(axis=0)) / data.std(axis=0).replace(0, 1) print(f'Data: {data.shape} (trials × voxels)') .. rst-class:: sphx-glr-script-out .. code-block:: none Kept 155 voxels (within-sample R² ≥ 0.085) Data: (240, 155) (trials × voxels) .. GENERATED FROM PYTHON SOURCE LINES 68-73 Fit the encoding model on *all* trials ----------------------------------------------------------------- For an uncertainty estimate we want the model's best guess at the true tuning curves — no held-out trials. The expected-uncertainty calculation only needs ``parameters`` and the noise model. .. GENERATED FROM PYTHON SOURCE LINES 73-92 .. code-block:: Python model = LogGaussianPRF(parameterisation='mu_sd_natural') fitter = ParameterFitter(model, data, paradigm[['n']]) mu_grid = np.linspace(10, 40, 16, dtype=np.float32) sd_grid = np.array([5., 10., 20.], dtype=np.float32) amp_grid = np.array([0.5, 1.0, 2.0], dtype=np.float32) base_grid = np.array([0.0], dtype=np.float32) init = fitter.fit_grid(mu_grid, sd_grid, amp_grid, base_grid) pars = fitter.fit(max_n_iterations=600, init_pars=init, noise_model='gaussian', learning_rate=0.05, progressbar=False) predictions = model.predict(paradigm=paradigm[['n']], parameters=pars) predictions.index = data.index train_r2 = get_rsq(data, predictions) print(f'Train R² — median {np.nanmedian(train_r2):.3f}, ' f'p90 {np.nanpercentile(train_r2, 90):.3f}') .. rst-class:: sphx-glr-script-out .. code-block:: none Working with chunk size of 17921 0%| | 0/1 [00:00` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: 03_expected_uncertainty.py <03_expected_uncertainty.py>` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: 03_expected_uncertainty.zip <03_expected_uncertainty.zip>` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_