Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia.
The 4 matches
- [1] § Methods › Recurrent neural network, working memory tasks, and regularisations ↔ tasks_memory.py, lines 12–137 · score 0.75 · post response, cost mask, sin, modalities, fixation, strength
- [2] § Results › Neural timescale estimation and spatial-temporal convergence ↔ ou_mle_tau.py, lines 2–20 · score 0.56 · Ornstein Uhlenbeck, maximum likelihood, MLE, OU
- [3] § Methods › Neural timescale estimation ↔ tau_experiments.ipynb, lines 161–190 · score 0.52 · negative log likelihood, NLL
- [4] § Methods › Neural timescale estimation ↔ tau_experiments.ipynb, lines 31–70 · score 0.50 · exponential decay, empirical ACF, autocorrelation, fit
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
Jupyter notebook · 775 lines · 24 KB · no license · 2 matches
- # %%
- import numpy as np
- import pymc as pm
- import arviz as az
- import matplotlib.pyplot as plt
- from scipy.optimize import minimize, curve_fit
- import scipy.stats as stats
- from scipy.stats import shapiro
- import seaborn as sns
- from tqdm.notebook import tqdm
- from mpl_toolkits.mplot3d import Axes3D
- import pandas as pd
- from statsmodels.tsa.stattools import acf
- # %%
- def simulate_ou_process(tau, D, delta_t, N, A0):
- A = np.zeros(N + 1)
- A[0] = A0
- exp_dt_tau = np.exp(-delta_t / tau)
- sqrt_variance = np.sqrt(D * tau * (1 - exp_dt_tau**2))
- for i in range(N):
- mean = A[i] * exp_dt_tau
- A[i + 1] = mean + sqrt_variance * np.random.randn()
- return A
- # %% [markdown]
- # ### Compute empirical ACF with covariance and estimate τ
- # %%
- def compute_empirical_acf(A, max_lag):
- N = len(A)
- A_mean = np.mean(A)
- A_centered = A - A_mean
- var_A = np.var(A_centered)
- autocorr = []
- for lag in range(1, max_lag + 1):
- # autocovariance function
- cov = np.mean(A_centered[:-lag] * A_centered[lag:])
- autocorr.append(cov / var_A)
- lags = np.arange(1, max_lag + 1)
- return lags, np.array(autocorr)
- def exponential_decay(lag, tau, delta_t):
- return np.exp(-lag * delta_t / tau)
- def estimate_tau_from_acf(lags, autocorr, delta_t):
- initial_guess = [1.0]
- try:
- params, covariance = curve_fit(
- lambda lag, tau: exponential_decay(lag, tau, delta_t), # fix delta_t
- lags,
- autocorr,
- p0=initial_guess,
- bounds=(1e-6, np.inf),
- method='trf',
- )
- tau_est = params[0]
- except:
- print('offset fit failed \n')
- tau_est = np.nan
- return tau_est
- # %% [markdown]
- # ###### Estimate τ with exp decay with offset
- # %%
- def exponential_decay_woffset(lag, A, tau, B, delta_t):
- return A * (np.exp(-lag * delta_t / tau) + B)
- def estimate_tau_from_acf_woffset(lags, autocorr, delta_t):
- initial_guess = [0,np.random.rand(1)[0]+0.01,0]
- model_func = lambda lag, A, tau, B: exponential_decay_woffset(lag, A, tau, B, delta_t)
- try:
- params, covariance = curve_fit(
- model_func,
- lags,
- autocorr,
- p0=initial_guess,
- bounds=([0, 0, -np.inf], [np.inf, np.inf, np.inf]),
- method='trf',
- )
- A_est, tau_est, B_est = params
- except:
- print('offset fit failed \n')
- tau_est = np.nan
- return tau_est
- # %%
- # True params
- tau_true = 15.33 # timescale
- D_true = .07 # diffusion coefficient
- delta_t = 1.72 # rime step
- N = int(10000/delta_t) # # of data points
- A0 = -0.013 # init value of the process
- num_simulations = 200 # # of simulations
- max_lag = 5000
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- # direct Fit using ACF
- lags, autocorr = compute_empirical_acf(A, max_lag)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_est_acf_woffset = estimate_tau_from_acf_woffset(lags, autocorr, delta_t)
- print(f'exp fit: {tau_est_acf}')
- print(f'exp fit woffset: {tau_est_acf_woffset}')
- # %%
- D = 0.07
- delta_t = 1.72
- N = int(300 / delta_t)
- A0 = -0.013
- max_lag = 100
- tau_high = 15.33
- tau_low = 2.0
- A_high = simulate_ou_process(tau_high, D, delta_t, N, A0)
- A_low = simulate_ou_process(tau_low, D, delta_t, N, A0)
- # Compute empirical ACFs
- lags, autocorr_high = compute_empirical_acf(A_high, max_lag)
- _, autocorr_low = compute_empirical_acf(A_low, max_lag)
- plt.figure(figsize=(10, 6))
- plt.plot(lags * delta_t, autocorr_high, label=f'High Timescale')
- plt.plot(lags * delta_t, autocorr_low, label=f'Low Timescale')
- plt.legend()
- plt.xlim([-1,50])
- plt.xticks(fontsize=8)
- plt.yticks(fontsize=8)
- plt.legend()
- plt.tight_layout()
- plt.show()
- # est tau from ACFs
- tau_est_high = estimate_tau_from_acf(lags, autocorr_high, delta_t)
- tau_est_low = estimate_tau_from_acf(lags, autocorr_low, delta_t)
- # # MLE
- # initial_guess = [1.0, 0.5]
- # tau_est_mle_high, _ = estimate_parameters_mle(A_high, delta_t, initial_guess)
- print(f"Estimated τ for high timescale process: {tau_est_high:.2f}")
- print(f"Estimated τ for low timescale process: {tau_est_low:.2f}")
- # %% [markdown]
- # ### MLE negative log-likelihood and τ, D estimation
- # %%
- def negative_log_likelihood(params, A, delta_t):
- tau, D = params
- if tau <= 0 or D <= 0:
- return np.inf
- N = len(A) - 1
- exp_dt_tau = np.exp(-delta_t / tau)
- variance = D * tau * (1 - exp_dt_tau**2)
- if variance <= 0:
- return np.inf
- residuals = A[1:] - A[:-1] * exp_dt_tau
- nll = 0.5 * N * np.log(2 * np.pi * variance) + np.sum(residuals**2) / (2 * variance)
- return nll
- def estimate_parameters_mle(A, delta_t, initial_guess):
- result = minimize(
- negative_log_likelihood,
- initial_guess,
- args=(A, delta_t),
- method='L-BFGS-B',
- bounds=((1e-6, None), (1e-6, None))
- )
- if result.success:
- tau_est, D_est = result.x
- return tau_est, D_est
- else:
- return np.nan, np.nan
- # %% [markdown]
- # ### Bayesian (w PyMC)
- # %%
- def estimate_parameters_bayesian(A, delta_t, mle_tau_estimate):
- with pm.Model() as ou_model:
- # Priors for tau and D
- # empirical bayes - use direct fit estimate as prior mean for tau
- # tau_prior_mean = mle_tau_estimate if not np.isnan(mle_tau_estimate) else 13.0
- # gaussian priors
- tau_prior_mean = 12. # prior mean for tau
- tau_prior_sd = 10. # prior standard deviation for tau
- D_prior_mean = 1.5 # prior mean for D
- D_prior_sd = 1 # prior standard deviation for D
- tau = pm.Normal('tau', mu=tau_prior_mean, sigma=tau_prior_sd)
- D = pm.Normal('D', mu=D_prior_mean, sigma=D_prior_sd)
- # gamma priors
- # tau = pm.Gamma('tau', alpha=2.0, beta=2.0 / tau_prior_mean)
- # D = pm.Gamma('D', alpha=2.0, beta=1.0)
- # compute variance and mean
- exp_dt_tau = pm.math.exp(-delta_t / tau)
- variance = D * tau * (1 - exp_dt_tau**2)
- mean = A[:-1] * exp_dt_tau
- # likelihood
- A_obs = pm.Normal('A_obs', mu=mean, sigma=pm.math.sqrt(variance), observed=A[1:])
- # sample from posterior
- try:
- trace = pm.sample(
- 1000, # Number of samples
- tune=500, # Number of tuning steps
- progressbar=False,
- target_accept=0.9
- )
- summary = az.summary(trace, var_names=['tau', 'D'], round_to=4)
- tau_est = summary.loc['tau', 'mean']
- D_est = summary.loc['D', 'mean']
- except:
- tau_est = np.nan
- D_est = np.nan
- return tau_est, D_est
- # %% [markdown]
- # ### Simulations
- # %%
- # True parameters
- delta_t = 1.72
- # N = int(310/delta_t) # Number of data points
- N = 210
- tau_true = 4.748 # True intrinsic timescale
- D_true = .06967 # True diffusion coefficient
- A0 = -0.013 # Initial value of the process
- # Estimation settings
- num_simulations = 200 # Number of simulations
- max_lag = 50 # Maximum lag for ACF
- # %%
- exp_dt_tau = np.exp(-delta_t / tau_true)
- variance = D_true * tau_true * (1 - exp_dt_tau**2)
- print(exp_dt_tau)
- print(variance)
- # %%
- sns.set_theme(style="darkgrid")
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- plt.figure(figsize=(14, 6))
- plt.plot(A)
- # %%
- f, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharex=True)
- ax1.hist(A, bins=15, label='A');
- ax1.legend()
- ax2.hist(np.diff(A), bins=15, label='dA', color='r');
- ax2.legend()
- # %% [markdown]
- # #### compare direct exp fit, MLE, Bayes
- # %%
- tau_true = 15.33
- D_true = .07
- delta_t = 1.72
- N = int(310/delta_t)
- A0 = -0.013
- # est settings
- num_simulations = 200
- max_lag = 50
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- # %%
- tau_estimates_acf = []
- tau_estimates_acf_woffset = []
- tau_estimates_mle = []
- tau_estimates_bayes = []
- D_estimates_mle = []
- D_estimates_bayes = []
- # %%
- for sim in tqdm(range(num_simulations)):
- # sim the OU process
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- # dir fit using ACF
- lags, autocorr = compute_empirical_acf(A, max_lag)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_estimates_acf.append(tau_est_acf)
- # # direct fit woffset
- # lags, autocorr = compute_empirical_acf(A, max_lag)
- # tau_est_acf_woffset = estimate_tau_from_acf_woffset(lags, autocorr, delta_t)
- # tau_estimates_acf_woffset.append(tau_est_acf_woffset)
- # MLE
- initial_guess = [1.0, 0.5]
- tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
- tau_estimates_mle.append(tau_est_mle)
- D_estimates_mle.append(D_est_mle)
- # Bayes
- tau_est_bayes, D_est_bayes = estimate_parameters_bayesian(A, delta_t, tau_est_mle)
- tau_estimates_bayes.append(tau_est_bayes)
- D_estimates_bayes.append(D_est_bayes)
- print(f"Simulation {sim + 1}/{num_simulations} completed.")
- # %%
- tau_estimates_acf = np.array(tau_estimates_acf)
- # tau_estimates_acf_woffset = np.array(tau_estimates_acf_woffset)
- tau_estimates_mle = np.array(tau_estimates_mle)
- tau_estimates_bayes = np.array(tau_estimates_bayes)
- D_estimates_mle = np.array(D_estimates_mle)
- D_estimates_bayes = np.array(D_estimates_bayes)
- # %%
- bias_acf = tau_estimates_acf - tau_true
- bias_mle = tau_estimates_mle - tau_true
- bias_bayes = tau_estimates_bayes - tau_true
- # %%
- all_data = np.concatenate((tau_estimates_acf,
- tau_estimates_mle, tau_estimates_bayes))
- all_data = all_data[~np.isnan(all_data)]
- bin_edges = np.linspace(np.min(all_data), np.max(all_data), num=50) # 10 bins creates 11 edges
- sns.set_theme(style="white", palette=None)
- plt.figure(figsize=(8, 5))
- plt.hist(tau_estimates_acf, bins=bin_edges, color='#4888A3', alpha=.5, edgecolor='none', label='Direct exponential fit')
- # plt.hist(tau_estimates_acf_woffset, bins=bin_edges, alpha=.4, edgecolor='none', label='Direct Exp Fit with Offset')
- plt.hist(tau_estimates_mle, bins=bin_edges, color='#D32F26', alpha=.6, edgecolor='none', label='MLE')
- plt.hist(tau_estimates_bayes, bins=bin_edges, color='#DDC332', alpha=.4, edgecolor='none', label='Bayes with gaussian priors')
- plt.axvline(tau_true, color='k', linestyle='dashed', linewidth=1, label='True τ')
- plt.xlim([0, 50])
- plt.xlabel('τ Estimates')
- plt.ylabel('Counts')
- plt.title('')
- plt.legend()
- plt.show()
- # %%
- print(np.var(tau_estimates_acf), np.nanvar(tau_estimates_mle), np.var(tau_estimates_bayes))
- # %%
- print(np.mean(tau_estimates_acf), np.nanmean(tau_estimates_mle), np.mean(tau_estimates_bayes))
- # %%
- all_data = np.concatenate((D_estimates_mle, D_estimates_bayes))
- all_data = all_data[~np.isnan(all_data)]
- bin_edges = np.linspace(np.min(all_data), np.max(all_data), num=30)
- plt.figure(figsize=(8, 5))
- plt.hist(D_estimates_mle, bins=bin_edges, color='#D32F26', edgecolor='none', alpha=0.6, label='MLE')
- plt.hist(D_estimates_bayes, bins=bin_edges, color='#DDC332', edgecolor='none', alpha=0.4, label='Bayes with gaussian priors')
- plt.axvline(D_true, color='k', linestyle='dashed', linewidth=1, label='True D')
- plt.xlabel('Estimated D')
- plt.ylabel('Counts')
- plt.legend()
- plt.show()
- # %% [markdown]
- # ##### effect of timeseries length
- # %%
- tau_true = 2.1030
- D_true = 0.4623
- delta_t = 1
- A0 = -0.013
- max_lag = 50
- N_values = np.arange(60, 1001, 5)
- num_simulations = 100
- results = {
- 'N': [],
- 'mean_tau_acf': [],
- 'var_tau_acf': [],
- 'mean_tau_mle': [],
- 'var_tau_mle': []
- }
- # %%
- for N in tqdm(N_values, desc="Processing N values"):
- tau_estimates_acf = []
- tau_estimates_mle = []
- for sim in range(num_simulations):
- # sim OU
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- # dir fit exp to ACF
- max_lag = min(100, N//2)
- lags, autocorr = compute_empirical_acf(A, max_lag)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_estimates_acf.append(tau_est_acf)
- # MLE
- initial_guess = [np.random.rand(1)[0]+0.01, np.random.rand(1)[0]+0.01]
- tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
- tau_estimates_mle.append(tau_est_mle)
- mean_tau_acf = np.nanmean(tau_estimates_acf)
- var_tau_acf = np.nanvar(tau_estimates_acf)
- mean_tau_mle = np.nanmean(tau_estimates_mle)
- var_tau_mle = np.nanvar(tau_estimates_mle)
- results['N'].append(N)
- results['mean_tau_acf'].append(mean_tau_acf)
- results['var_tau_acf'].append(var_tau_acf)
- results['mean_tau_mle'].append(mean_tau_mle)
- results['var_tau_mle'].append(var_tau_mle)
- # %%
- df_results = pd.DataFrame(results)
- plt.figure(figsize=(12, 6))
- # ACF
- plt.plot(df_results['N'], df_results['mean_tau_acf'], label='Exp fit', color='#4888A3', lw=1.5)
- plt.fill_between(df_results['N'],
- df_results['mean_tau_acf'] - np.sqrt(df_results['var_tau_acf']),
- df_results['mean_tau_acf'] + np.sqrt(df_results['var_tau_acf']),
- color='#4888A3', alpha=0.25)
- # MLE
- plt.plot(df_results['N'], df_results['mean_tau_mle'], label='MLE', color='#D32F26', lw=1.5)
- plt.fill_between(df_results['N'],
- df_results['mean_tau_mle'] - np.sqrt(df_results['var_tau_mle']),
- df_results['mean_tau_mle'] + np.sqrt(df_results['var_tau_mle']),
- color='#D32F26', alpha=0.25)
- plt.axhline(y=tau_true, color='green', lw=2,linestyle='--', label='True Tau')
- plt.xlabel('Time Length')
- plt.ylabel('τ Estimates')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %% [markdown]
- # ##### effect of Δt
- # %%
- tau_true = 15.33
- D_true = .07
- A0 = -0.013
- N = 200
- max_lag = 50
- delta_t_values = np.arange(1, 5, .01)
- num_simulations = 100
- results = {
- 'delta_t': [],
- 'mean_tau_acf': [],
- 'var_tau_acf': [],
- 'mean_tau_mle': [],
- 'var_tau_mle': []
- }
- # %%
- for delta_t in tqdm(delta_t_values, desc="Processing delta_t values"):
- tau_estimates_acf = []
- tau_estimates_mle = []
- for sim in range(num_simulations):
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- max_lag = 100
- lags, autocorr = compute_empirical_acf(A, max_lag)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_estimates_acf.append(tau_est_acf)
- # MLE
- initial_guess = [np.random.rand(1)[0]+0.01, np.random.rand(1)[0]+0.01]
- tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
- tau_estimates_mle.append(tau_est_mle)
- mean_tau_acf = np.nanmean(tau_estimates_acf)
- var_tau_acf = np.nanvar(tau_estimates_acf)
- mean_tau_mle = np.nanmean(tau_estimates_mle)
- var_tau_mle = np.nanvar(tau_estimates_mle)
- results['delta_t'].append(delta_t)
- results['mean_tau_acf'].append(mean_tau_acf)
- results['var_tau_acf'].append(var_tau_acf)
- results['mean_tau_mle'].append(mean_tau_mle)
- results['var_tau_mle'].append(var_tau_mle)
- # %%
- df_results = pd.DataFrame(results)
- plt.figure(figsize=(12, 6))
- plt.plot(df_results['delta_t'], df_results['mean_tau_acf'], label='Exp fit', color='#4888A3', lw=1.5)
- plt.fill_between(df_results['delta_t'],
- df_results['mean_tau_acf'] - np.sqrt(df_results['var_tau_acf']),
- df_results['mean_tau_acf'] + np.sqrt(df_results['var_tau_acf']),
- color='#4888A3', alpha=0.25)
- plt.plot(df_results['delta_t'], df_results['mean_tau_mle'], label='MLE', color='#D32F26', lw=1.5)
- plt.fill_between(df_results['delta_t'],
- df_results['mean_tau_mle'] - np.sqrt(df_results['var_tau_mle']),
- df_results['mean_tau_mle'] + np.sqrt(df_results['var_tau_mle']),
- color='#D32F26', alpha=0.25)
- plt.axhline(y=tau_true, color='green', lw=2,linestyle='--', label='True τ')
- plt.xlabel('Δt')
- plt.ylabel('τ Estimates')
- plt.legend()
- plt.grid(True)
- plt.show()
- # %% [markdown]
- # ##### joint effects of N and delta t
- # %%
- np.random.seed(0)
- tau_true = 10.0
- D_true = .08
- A0 = -0.013
- max_lag = 50
- N_values = np.arange(100, 501, 50)
- delta_t_values = np.arange(1, 5, .5)
- num_simulations = 100
- mean_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
- mean_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
- var_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
- var_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
- mse_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
- mse_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
- # sim loop
- for i, N in enumerate(tqdm(N_values, desc="Processing N values")):
- for j, delta_t in enumerate(tqdm(delta_t_values, desc=f"N={N}", leave=False)):
- tau_estimates_acf = []
- tau_estimates_mle = []
- for sim in range(num_simulations):
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- # dir ACF Fit
- max_lag_current = N // 2
- lags, autocorr = compute_empirical_acf(A, max_lag_current)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_estimates_acf.append(tau_est_acf)
- # MLE
- initial_guess = [1.0, 0.5] # [tau_guess, D_guess]
- tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
- tau_estimates_mle.append(tau_est_mle)
- tau_estimates_acf = np.array(tau_estimates_acf)
- tau_estimates_mle = np.array(tau_estimates_mle)
- mean_tau_acf[i, j] = np.nanmean(tau_estimates_acf)
- mean_tau_mle[i, j] = np.nanmean(tau_estimates_mle)
- # compute var of Estimated Tau
- var_tau_acf[i, j] = np.nanvar(tau_estimates_acf)
- var_tau_mle[i, j] = np.nanvar(tau_estimates_mle)
- # compute MSE
- # mse_tau_acf[i, j] = np.nanmean((tau_estimates_acf - tau_true) ** 2)
- # mse_tau_mle[i, j] = np.nanmean((tau_estimates_mle - tau_true) ** 2)
- mse_tau_acf[i, j] = np.nanmean(abs(tau_estimates_acf - tau_true))
- mse_tau_mle[i, j] = np.nanmean(abs(tau_estimates_mle - tau_true))
- overall_mean_mse_acf = np.mean(mse_tau_acf)
- overall_mean_mse_mle = np.mean(mse_tau_mle)
- overall_mean_var_acf = np.mean(var_tau_acf)
- overall_mean_var_mle = np.mean(var_tau_mle)
- print("\nAggregated Results for Tau Estimation Methods:")
- print("------------------------------------------------")
- print(f"ACF Method:")
- print(f" Mean MSE: {overall_mean_mse_acf:.4f}")
- print(f" Mean Variance: {overall_mean_var_acf:.4f}\n")
- print(f"MLE Method:")
- print(f" Mean MSE: {overall_mean_mse_mle:.4f}")
- print(f" Mean Variance: {overall_mean_var_mle:.4f}\n")
- # %%
- print(mean_tau_acf.mean())
- print(mean_tau_mle.mean())
- # %%
- import matplotlib.colors as mcolors
- def hex_to_rgba(hex_color, alpha=1):
- return (*mcolors.hex2color(hex_color), alpha)
- zmin = min(np.nanmin(mean_tau_acf), np.nanmin(mean_tau_mle), tau_true)
- zmax = max(np.nanmax(mean_tau_acf), np.nanmax(mean_tau_mle), tau_true)
- zmin = 5
- zmax = 10
- # Meshgrid
- N_grid, delta_t_grid = np.meshgrid(N_values, delta_t_values, indexing='ij')
- # Dir fit
- fig = plt.figure(figsize=(14, 6))
- true_tau_surface = tau_true * np.ones_like(N_grid)
- #
- ax1 = fig.add_subplot(121, projection='3d')
- surf1 = ax1.plot_surface(N_grid, delta_t_grid, mean_tau_acf, cmap='cividis',
- edgecolor='none', linewidth=0,
- vmin=zmin, vmax=zmax
- )
- ax1.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24, linewidth=0)
- ax1.set_zlim(zmin, zmax)
- # fig.colorbar(surf1, ax=ax1, shrink=0.4, aspect=20)
- ax1.view_init(elev=25, azim=140)
- hexcol = "#f3f3f3"
- ax1.xaxis.set_pane_color(hex_to_rgba(hexcol))
- ax1.yaxis.set_pane_color(hex_to_rgba(hexcol))
- ax1.zaxis.set_pane_color(hex_to_rgba(hexcol))
- # ax1.set_xticks([200, 400])
- # ax1.set_yticks([2, 4])
- # ax1.set_zticks([3.5, 4.5])
- # MLE Method Plot
- ax2 = fig.add_subplot(122, projection='3d')
- surf2 = ax2.plot_surface(N_grid, delta_t_grid, mean_tau_mle, cmap='cividis',
- edgecolor='none',linewidth=0,
- vmin=zmin,
- vmax=zmax
- )
- ax2.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24)
- ax2.set_zlim(zmin, zmax)
- fig.colorbar(surf2, ax=ax2, shrink=0.4, aspect=20)
- ax2.view_init(elev=25, azim=140)
- ax2.xaxis.set_pane_color(hex_to_rgba(hexcol))
- ax2.yaxis.set_pane_color(hex_to_rgba(hexcol))
- ax2.zaxis.set_pane_color(hex_to_rgba(hexcol))
- # ax2.set_xticks([200, 400])
- # ax2.set_yticks([2, 4])
- # ax2.set_zticks([3.5, 4.5])
- plt.tight_layout()
- # plt.savefig("/Users/subat/Desktop/datan/bsnip/st1_28_oct/strnn/tf/network/space_smry/tau_estimation.png", dpi=400)
- plt.show()
- # %% [markdown]
- # ##### small tau
- # %%
- tau_true = 4.748
- D_true = .06967
- A0 = -0.013
- max_lag = 50
- N_values = np.arange(100, 1101, 100)
- delta_t_values = np.arange(1, 5, .05)
- num_simulations = 100
- mean_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
- mean_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
- #
- var_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
- var_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
- # %%
- for i, N in enumerate(tqdm(N_values, desc="Processing N values")):
- for j, delta_t in enumerate(tqdm(delta_t_values, desc=f"N={N}", leave=False)):
- tau_estimates_acf = []
- tau_estimates_mle = []
- for sim in range(num_simulations):
- A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
- max_lag = min(100, N//2)
- lags, autocorr = compute_empirical_acf(A, max_lag)
- tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
- tau_estimates_acf.append(tau_est_acf)
- initial_guess = [1.0, 0.5]
- tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
- tau_estimates_mle.append(tau_est_mle)
- mean_tau_acf[i, j] = np.nanmean(tau_estimates_acf)
- var_tau_acf[i, j] = np.nanvar(tau_estimates_acf)
- mean_tau_mle[i, j] = np.nanmean(tau_estimates_mle)
- var_tau_mle[i, j] = np.nanvar(tau_estimates_mle)
- # %%
- zmin = min(np.nanmin(mean_tau_acf), np.nanmin(mean_tau_mle), tau_true)
- zmax = max(np.nanmax(mean_tau_acf), np.nanmax(mean_tau_mle), tau_true)
- N_grid, delta_t_grid = np.meshgrid(N_values, delta_t_values, indexing='ij')
- fig = plt.figure(figsize=(14, 6))
- true_tau_surface = tau_true * np.ones_like(N_grid)
- ax1 = fig.add_subplot(121, projection='3d')
- surf1 = ax1.plot_surface(N_grid, delta_t_grid, mean_tau_acf, cmap='viridis',
- edgecolor='none',
- # vmin=zmin, vmax=zmax
- )
- ax1.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24)
- ax1.set_title('Exponential fit')
- ax1.set_xlabel('Time Length')
- ax1.set_ylabel('Δt')
- ax1.set_zlabel('τ Estimates')
- ax1.set_zlim(zmin, zmax)
- fig.colorbar(surf1, ax=ax1, shrink=0.4, aspect=20)
- ax2 = fig.add_subplot(122, projection='3d')
- surf2 = ax2.plot_surface(N_grid, delta_t_grid, mean_tau_mle, cmap='plasma',
- edgecolor='none',
- # vmin=zmin,
- # vmax=zmax
- )
- ax2.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.4)
- ax2.set_title('OU MLE')
- ax2.set_xlabel('Time Length')
- ax2.set_ylabel('Δt')
- ax2.set_zlabel('τ Estimates')
- ax2.set_zlim(zmin, zmax)
- fig.colorbar(surf2, ax=ax2, shrink=0.4, aspect=20)
- plt.tight_layout()
- plt.show()
tau_experiments.ipynb at commit 6d83652, no license · at the source
Overview
- Department of Psychiatry, University of Cambridge,Cambridge, UK
- Centre for Human-Inspired Artificial Intelligence, University of Cambridge,Cambridge, UK
- Department of Engineering, University of Cambridge,Cambridge, UK
- Department of Computer Science and Technology, University of Cambridge,Cambridge, UK
Abstract
The human neocortex is functionally organised at its highest level along a continuous sensory-to-association (AS) hierarchy. This study investigates two questions—how this hierarchy is structurally altered in schizophrenia, and what these alterations imply for neural dynamics and cognitive computation. Using a large fMRI dataset (N = 355), we extracted individual AS gradients via spectral analysis of brain connectivity and quantified hierarchical organisation by the gradient range. Schizophrenia showed a compressed AS hierarchy, indicating reduced functional differentiation. Estimating neural timescale (autocorrelation decay constant) with the Ornstein-Uhlenbeck process, we observed that the most specialised, locally cohesive regions at the gradient extremes exhibit longer timescales, an empirical spatiotemporal mapping that is attenuated in schizophrenia. To probe the computational consequences of this compression, we used the gradients to regularise subject-specific recurrent neural networks (RNNs) trained on working memory tasks. Networks endowed with greater gradient range learned more efficiently, plateaued at lower task loss, and maintained stronger alignment to the prescribed AS hierarchical geometry. Fixed-point linearisation showed that high-range networks settled into more stable neural states during memory delay, evidenced by lower energy and smaller maximal Jacobian eigenvalues. This gradient-regularised RNN framework thereby links large-scale cortical architecture with fixed point stability, providing a computational hypothesis that AS gradient de-differentiation can destabilise neural computations in schizophrenia, convergently supported by empirical timescale flattening along AS gradient and model-based evidence of less stable fixed points.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.
SubatA20/as_spec_rnn
6d83652afab9121679262fb478dfe8f801e67dcc, 28 July 2025Availability: 1 check, the latest on 30 September 2026: the link answers
- 30 September 2026: the link answers
13 files
- ctRNN_torch.py, Python, 102 lines
- diff_grad.py, Python, 93 lines
- embed_weights.py, Python, 176 lines
- fp_stability.py, Python, 171 lines
- network_train.py, Python, 94 lines
- ou_mle_tau.py, Python, 101 lines, 1 match
- tasks_memory.py, Python, 558 lines, 1 match
- tau_experiments.ipynb, Jupyter, 775 lines, 2 matches
- train_IO_L1.py, Python, 328 lines
- utils.py, Python, 63 lines
- w_as_gen.py, Python, 139 lines
- weight_geometry.py, Python, 139 lines
- README.md, Text, 20 lines
Code availability
All codes used in empirical and theoretical studies are available on the following GitHub repository: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 12 scripts, each with its path and the digest of its content;
- 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability
Data used in the preparation of this manuscript were obtained from the National Institute of Mental Health (NIMH) Data Archive (NDA; https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 30 September 2026: the first record
Recorded: type, language, journal, volume, issue, pages, dates, 4 authors, 2 keywords, 12 MeSH terms, 3 funders, 92 references.
Cite
This paper
Abulikemu, S., Radmard, P., Mamalakis, M., & Suckling, J. (2026). Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia. NPJ systems biology and applications, 12(1), 100. https://
BibTeX
@article{abulikemu2026as
author = {Abulikemu, Subati and Radmard, Puria and Mamalakis, Michail and Suckling, John},
title = {{Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia}},
journal = {NPJ systems biology and applications},
year = {2026},
month = apr,
volume = {12},
number = {1},
pages = {100},
publisher = {Nature Publishing Group},
issn = {2056-7189},
doi = {10.1038/
url = {https://
pmid = {42056136},
pmcid = {PMC13338132}
}
RIS
TY - JOUR
AU - Abulikemu, Subati
AU - Radmard, Puria
AU - Mamalakis, Michail
AU - Suckling, John
TI - Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia
T2 - NPJ systems biology and applications
J2 - NPJ Syst Biol Appl
PY - 2026
DA - 2026/
VL - 12
IS - 1
SP - 100
SN - 2056-7189
PB - Nature Publishing Group
DO - 10.1038/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1038/
"type": "article-journal",
"title": "Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia",
"container-title": "NPJ systems biology and applications",
"author": [
{
"family": "Abulikemu",
"given": "Subati"
},
{
"family": "Radmard",
"given": "Puria"
},
{
"family": "Mamalakis",
"given": "Michail"
},
{
"family": "Suckling",
"given": "John"
}
],
"container-title-short":
"volume": "12",
"issue": "1",
"page": "100",
"DOI": "10.1038/
"PMID": "42056136",
"PMCID": "PMC13338132",
"ISSN": "2056-7189",
"publisher": "Nature Publishing Group",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
30
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1038/s41467-026-74466-2 [code]
- Neuromorphic hierarchical modular reservoirs.Journal: Nature communicationsIn common: statsmodels, seaborn, pandas, 3 other tools, computational, 12 references
- [2] doi:10.1162/imag.a.1248 [code]
- Estimating fMRI timescale maps.Journal: Imaging neuroscience (Cambridge, Mass.)In common: statsmodels, pandas, SciPy, 2 other tools, 9 references
- [3] doi:10.1038/s41467-026-75585-6 [code]
- Brain network dynamics reflect psychiatric illness status and transdiagnostic symptom profiles across health and disease.Journal: Nature communicationsIn common: h5py, seaborn, pandas, 3 other tools, schizophrenia / psychosis, 7 references
- [4] doi:10.1038/s41467-026-75959-w [code]
- Charting higher-order models of brain function beyond pairwise interactions.Journal: Nature communicationsIn common: BrainSpace, h5py, statsmodels, 5 other tools, 5 references
- [5] doi:10.7554/elife.103097 [code]
- Canonical neurodevelopmental trajectories of structural and functional manifolds.Journal: eLifeIn common: BrainSpace, h5py, statsmodels, 3 other tools, 5 references
- [6] doi:10.3390/biomimetics11080569 [code]
- Pretraining of Embodied Recurrent Networks Bridges the Gap Between Artificial and Cortical Neural Activities.Journal: Biomimetics (Basel, Switzerland)In common: PyTorch, SciPy, Matplotlib, 1 other tool, 7 references
- [7] doi:10.1016/j.isci.2026.116903 [code]
- Neurobiological and behavioral relevance of intrinsic functional connectome constraints on task-evoked neural activation.Journal: iScienceIn common: PyTorch, SciPy, Matplotlib, 1 other tool, 7 references
- [8] doi:10.1371/journal.pcbi.1014162 [code]
- Exploring neural manifolds across a wide range of intrinsic dimensions.Journal: PLoS computational biologyIn common: h5py, pandas, SciPy, 2 other tools, computational, 5 references
- [9] doi:10.1371/journal.pbio.3003831 [code]
- Disinhibitory signaling enables flexible coding of top-down information in cortical networks.Journal: PLoS biologyIn common: h5py, PyTorch, pandas, 3 other tools, 5 references
- [10] doi:10.21203/rs.3.rs-9326213/v1 [code]
- Multi-task fMRI outperforms resting-state fMRI for revealing task-invariant organization of the human brainJournal: Research Square (preprint)In common: h5py, PyTorch, seaborn, 4 other tools, 5 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 12 scripts, and 4 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:2ac789d906b4174a…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[, paste the snippet at the top, then “Commit changes…” and, to review it first, “Create a new branch and start a pull request”. You open the pull request; OSCR asks for no permission.
Request its removal
To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
