OSCR

Association-sensory spatiotemporal hierarchy and functional gradient-regularised recurrent neural network with implications for schizophrenia.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches
  1. [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. [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. [3] § Methods › Neural timescale estimation ↔ tau_experiments.ipynb, lines 161–190 · score 0.52 · negative log likelihood, NLL
  4. [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

  1. # %%
  2. import numpy as np
  3. import pymc as pm
  4. import arviz as az
  5. import matplotlib.pyplot as plt
  6. from scipy.optimize import minimize, curve_fit
  7. import scipy.stats as stats
  8. from scipy.stats import shapiro
  9. import seaborn as sns
  10. from tqdm.notebook import tqdm
  11. from mpl_toolkits.mplot3d import Axes3D
  12. import pandas as pd
  13. from statsmodels.tsa.stattools import acf
  14. # %%
  15. def simulate_ou_process(tau, D, delta_t, N, A0):
  16. A = np.zeros(N + 1)
  17. A[0] = A0
  18. exp_dt_tau = np.exp(-delta_t / tau)
  19. sqrt_variance = np.sqrt(D * tau * (1 - exp_dt_tau**2))
  20. for i in range(N):
  21. mean = A[i] * exp_dt_tau
  22. A[i + 1] = mean + sqrt_variance * np.random.randn()
  23. return A
  24. # %% [markdown]
  25. # ### Compute empirical ACF with covariance and estimate τ
  26. # %%
  27. def compute_empirical_acf(A, max_lag):
  28. N = len(A)
  29. A_mean = np.mean(A)
  30. A_centered = A - A_mean
  31. var_A = np.var(A_centered)
  32. autocorr = []
  33. for lag in range(1, max_lag + 1):
  34. # autocovariance function
  35. cov = np.mean(A_centered[:-lag] * A_centered[lag:])
  36. autocorr.append(cov / var_A)
  37. lags = np.arange(1, max_lag + 1)
  38. return lags, np.array(autocorr)
  39. def exponential_decay(lag, tau, delta_t):
  40. return np.exp(-lag * delta_t / tau)
  41. def estimate_tau_from_acf(lags, autocorr, delta_t):
  42. initial_guess = [1.0]
  43. try:
  44. params, covariance = curve_fit(
  45. lambda lag, tau: exponential_decay(lag, tau, delta_t), # fix delta_t
  46. lags,
  47. autocorr,
  48. p0=initial_guess,
  49. bounds=(1e-6, np.inf),
  50. method='trf',
  51. )
  52. tau_est = params[0]
  53. except:
  54. print('offset fit failed \n')
  55. tau_est = np.nan
  56. return tau_est
  57. # %% [markdown]
  58. # ###### Estimate τ with exp decay with offset
  59. # %%
  60. def exponential_decay_woffset(lag, A, tau, B, delta_t):
  61. return A * (np.exp(-lag * delta_t / tau) + B)
  62. def estimate_tau_from_acf_woffset(lags, autocorr, delta_t):
  63. initial_guess = [0,np.random.rand(1)[0]+0.01,0]
  64. model_func = lambda lag, A, tau, B: exponential_decay_woffset(lag, A, tau, B, delta_t)
  65. try:
  66. params, covariance = curve_fit(
  67. model_func,
  68. lags,
  69. autocorr,
  70. p0=initial_guess,
  71. bounds=([0, 0, -np.inf], [np.inf, np.inf, np.inf]),
  72. method='trf',
  73. )
  74. A_est, tau_est, B_est = params
  75. except:
  76. print('offset fit failed \n')
  77. tau_est = np.nan
  78. return tau_est
  79. # %%
  80. # True params
  81. tau_true = 15.33 # timescale
  82. D_true = .07 # diffusion coefficient
  83. delta_t = 1.72 # rime step
  84. N = int(10000/delta_t) # # of data points
  85. A0 = -0.013 # init value of the process
  86. num_simulations = 200 # # of simulations
  87. max_lag = 5000
  88. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  89. # direct Fit using ACF
  90. lags, autocorr = compute_empirical_acf(A, max_lag)
  91. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  92. tau_est_acf_woffset = estimate_tau_from_acf_woffset(lags, autocorr, delta_t)
  93. print(f'exp fit: {tau_est_acf}')
  94. print(f'exp fit woffset: {tau_est_acf_woffset}')
  95. # %%
  96. D = 0.07
  97. delta_t = 1.72
  98. N = int(300 / delta_t)
  99. A0 = -0.013
  100. max_lag = 100
  101. tau_high = 15.33
  102. tau_low = 2.0
  103. A_high = simulate_ou_process(tau_high, D, delta_t, N, A0)
  104. A_low = simulate_ou_process(tau_low, D, delta_t, N, A0)
  105. # Compute empirical ACFs
  106. lags, autocorr_high = compute_empirical_acf(A_high, max_lag)
  107. _, autocorr_low = compute_empirical_acf(A_low, max_lag)
  108. plt.figure(figsize=(10, 6))
  109. plt.plot(lags * delta_t, autocorr_high, label=f'High Timescale')
  110. plt.plot(lags * delta_t, autocorr_low, label=f'Low Timescale')
  111. plt.legend()
  112. plt.xlim([-1,50])
  113. plt.xticks(fontsize=8)
  114. plt.yticks(fontsize=8)
  115. plt.legend()
  116. plt.tight_layout()
  117. plt.show()
  118. # est tau from ACFs
  119. tau_est_high = estimate_tau_from_acf(lags, autocorr_high, delta_t)
  120. tau_est_low = estimate_tau_from_acf(lags, autocorr_low, delta_t)
  121. # # MLE
  122. # initial_guess = [1.0, 0.5]
  123. # tau_est_mle_high, _ = estimate_parameters_mle(A_high, delta_t, initial_guess)
  124. print(f"Estimated τ for high timescale process: {tau_est_high:.2f}")
  125. print(f"Estimated τ for low timescale process: {tau_est_low:.2f}")
  126. # %% [markdown]
  127. # ### MLE negative log-likelihood and τ, D estimation
  128. # %%
  129. def negative_log_likelihood(params, A, delta_t):
  130. tau, D = params
  131. if tau <= 0 or D <= 0:
  132. return np.inf
  133. N = len(A) - 1
  134. exp_dt_tau = np.exp(-delta_t / tau)
  135. variance = D * tau * (1 - exp_dt_tau**2)
  136. if variance <= 0:
  137. return np.inf
  138. residuals = A[1:] - A[:-1] * exp_dt_tau
  139. nll = 0.5 * N * np.log(2 * np.pi * variance) + np.sum(residuals**2) / (2 * variance)
  140. return nll
  141. def estimate_parameters_mle(A, delta_t, initial_guess):
  142. result = minimize(
  143. negative_log_likelihood,
  144. initial_guess,
  145. args=(A, delta_t),
  146. method='L-BFGS-B',
  147. bounds=((1e-6, None), (1e-6, None))
  148. )
  149. if result.success:
  150. tau_est, D_est = result.x
  151. return tau_est, D_est
  152. else:
  153. return np.nan, np.nan
  154. # %% [markdown]
  155. # ### Bayesian (w PyMC)
  156. # %%
  157. def estimate_parameters_bayesian(A, delta_t, mle_tau_estimate):
  158. with pm.Model() as ou_model:
  159. # Priors for tau and D
  160. # empirical bayes - use direct fit estimate as prior mean for tau
  161. # tau_prior_mean = mle_tau_estimate if not np.isnan(mle_tau_estimate) else 13.0
  162. # gaussian priors
  163. tau_prior_mean = 12. # prior mean for tau
  164. tau_prior_sd = 10. # prior standard deviation for tau
  165. D_prior_mean = 1.5 # prior mean for D
  166. D_prior_sd = 1 # prior standard deviation for D
  167. tau = pm.Normal('tau', mu=tau_prior_mean, sigma=tau_prior_sd)
  168. D = pm.Normal('D', mu=D_prior_mean, sigma=D_prior_sd)
  169. # gamma priors
  170. # tau = pm.Gamma('tau', alpha=2.0, beta=2.0 / tau_prior_mean)
  171. # D = pm.Gamma('D', alpha=2.0, beta=1.0)
  172. # compute variance and mean
  173. exp_dt_tau = pm.math.exp(-delta_t / tau)
  174. variance = D * tau * (1 - exp_dt_tau**2)
  175. mean = A[:-1] * exp_dt_tau
  176. # likelihood
  177. A_obs = pm.Normal('A_obs', mu=mean, sigma=pm.math.sqrt(variance), observed=A[1:])
  178. # sample from posterior
  179. try:
  180. trace = pm.sample(
  181. 1000, # Number of samples
  182. tune=500, # Number of tuning steps
  183. progressbar=False,
  184. target_accept=0.9
  185. )
  186. summary = az.summary(trace, var_names=['tau', 'D'], round_to=4)
  187. tau_est = summary.loc['tau', 'mean']
  188. D_est = summary.loc['D', 'mean']
  189. except:
  190. tau_est = np.nan
  191. D_est = np.nan
  192. return tau_est, D_est
  193. # %% [markdown]
  194. # ### Simulations
  195. # %%
  196. # True parameters
  197. delta_t = 1.72
  198. # N = int(310/delta_t) # Number of data points
  199. N = 210
  200. tau_true = 4.748 # True intrinsic timescale
  201. D_true = .06967 # True diffusion coefficient
  202. A0 = -0.013 # Initial value of the process
  203. # Estimation settings
  204. num_simulations = 200 # Number of simulations
  205. max_lag = 50 # Maximum lag for ACF
  206. # %%
  207. exp_dt_tau = np.exp(-delta_t / tau_true)
  208. variance = D_true * tau_true * (1 - exp_dt_tau**2)
  209. print(exp_dt_tau)
  210. print(variance)
  211. # %%
  212. sns.set_theme(style="darkgrid")
  213. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  214. plt.figure(figsize=(14, 6))
  215. plt.plot(A)
  216. # %%
  217. f, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), sharex=True)
  218. ax1.hist(A, bins=15, label='A');
  219. ax1.legend()
  220. ax2.hist(np.diff(A), bins=15, label='dA', color='r');
  221. ax2.legend()
  222. # %% [markdown]
  223. # #### compare direct exp fit, MLE, Bayes
  224. # %%
  225. tau_true = 15.33
  226. D_true = .07
  227. delta_t = 1.72
  228. N = int(310/delta_t)
  229. A0 = -0.013
  230. # est settings
  231. num_simulations = 200
  232. max_lag = 50
  233. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  234. # %%
  235. tau_estimates_acf = []
  236. tau_estimates_acf_woffset = []
  237. tau_estimates_mle = []
  238. tau_estimates_bayes = []
  239. D_estimates_mle = []
  240. D_estimates_bayes = []
  241. # %%
  242. for sim in tqdm(range(num_simulations)):
  243. # sim the OU process
  244. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  245. # dir fit using ACF
  246. lags, autocorr = compute_empirical_acf(A, max_lag)
  247. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  248. tau_estimates_acf.append(tau_est_acf)
  249. # # direct fit woffset
  250. # lags, autocorr = compute_empirical_acf(A, max_lag)
  251. # tau_est_acf_woffset = estimate_tau_from_acf_woffset(lags, autocorr, delta_t)
  252. # tau_estimates_acf_woffset.append(tau_est_acf_woffset)
  253. # MLE
  254. initial_guess = [1.0, 0.5]
  255. tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
  256. tau_estimates_mle.append(tau_est_mle)
  257. D_estimates_mle.append(D_est_mle)
  258. # Bayes
  259. tau_est_bayes, D_est_bayes = estimate_parameters_bayesian(A, delta_t, tau_est_mle)
  260. tau_estimates_bayes.append(tau_est_bayes)
  261. D_estimates_bayes.append(D_est_bayes)
  262. print(f"Simulation {sim + 1}/{num_simulations} completed.")
  263. # %%
  264. tau_estimates_acf = np.array(tau_estimates_acf)
  265. # tau_estimates_acf_woffset = np.array(tau_estimates_acf_woffset)
  266. tau_estimates_mle = np.array(tau_estimates_mle)
  267. tau_estimates_bayes = np.array(tau_estimates_bayes)
  268. D_estimates_mle = np.array(D_estimates_mle)
  269. D_estimates_bayes = np.array(D_estimates_bayes)
  270. # %%
  271. bias_acf = tau_estimates_acf - tau_true
  272. bias_mle = tau_estimates_mle - tau_true
  273. bias_bayes = tau_estimates_bayes - tau_true
  274. # %%
  275. all_data = np.concatenate((tau_estimates_acf,
  276. tau_estimates_mle, tau_estimates_bayes))
  277. all_data = all_data[~np.isnan(all_data)]
  278. bin_edges = np.linspace(np.min(all_data), np.max(all_data), num=50) # 10 bins creates 11 edges
  279. sns.set_theme(style="white", palette=None)
  280. plt.figure(figsize=(8, 5))
  281. plt.hist(tau_estimates_acf, bins=bin_edges, color='#4888A3', alpha=.5, edgecolor='none', label='Direct exponential fit')
  282. # plt.hist(tau_estimates_acf_woffset, bins=bin_edges, alpha=.4, edgecolor='none', label='Direct Exp Fit with Offset')
  283. plt.hist(tau_estimates_mle, bins=bin_edges, color='#D32F26', alpha=.6, edgecolor='none', label='MLE')
  284. plt.hist(tau_estimates_bayes, bins=bin_edges, color='#DDC332', alpha=.4, edgecolor='none', label='Bayes with gaussian priors')
  285. plt.axvline(tau_true, color='k', linestyle='dashed', linewidth=1, label='True τ')
  286. plt.xlim([0, 50])
  287. plt.xlabel('τ Estimates')
  288. plt.ylabel('Counts')
  289. plt.title('')
  290. plt.legend()
  291. plt.show()
  292. # %%
  293. print(np.var(tau_estimates_acf), np.nanvar(tau_estimates_mle), np.var(tau_estimates_bayes))
  294. # %%
  295. print(np.mean(tau_estimates_acf), np.nanmean(tau_estimates_mle), np.mean(tau_estimates_bayes))
  296. # %%
  297. all_data = np.concatenate((D_estimates_mle, D_estimates_bayes))
  298. all_data = all_data[~np.isnan(all_data)]
  299. bin_edges = np.linspace(np.min(all_data), np.max(all_data), num=30)
  300. plt.figure(figsize=(8, 5))
  301. plt.hist(D_estimates_mle, bins=bin_edges, color='#D32F26', edgecolor='none', alpha=0.6, label='MLE')
  302. plt.hist(D_estimates_bayes, bins=bin_edges, color='#DDC332', edgecolor='none', alpha=0.4, label='Bayes with gaussian priors')
  303. plt.axvline(D_true, color='k', linestyle='dashed', linewidth=1, label='True D')
  304. plt.xlabel('Estimated D')
  305. plt.ylabel('Counts')
  306. plt.legend()
  307. plt.show()
  308. # %% [markdown]
  309. # ##### effect of timeseries length
  310. # %%
  311. tau_true = 2.1030
  312. D_true = 0.4623
  313. delta_t = 1
  314. A0 = -0.013
  315. max_lag = 50
  316. N_values = np.arange(60, 1001, 5)
  317. num_simulations = 100
  318. results = {
  319. 'N': [],
  320. 'mean_tau_acf': [],
  321. 'var_tau_acf': [],
  322. 'mean_tau_mle': [],
  323. 'var_tau_mle': []
  324. }
  325. # %%
  326. for N in tqdm(N_values, desc="Processing N values"):
  327. tau_estimates_acf = []
  328. tau_estimates_mle = []
  329. for sim in range(num_simulations):
  330. # sim OU
  331. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  332. # dir fit exp to ACF
  333. max_lag = min(100, N//2)
  334. lags, autocorr = compute_empirical_acf(A, max_lag)
  335. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  336. tau_estimates_acf.append(tau_est_acf)
  337. # MLE
  338. initial_guess = [np.random.rand(1)[0]+0.01, np.random.rand(1)[0]+0.01]
  339. tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
  340. tau_estimates_mle.append(tau_est_mle)
  341. mean_tau_acf = np.nanmean(tau_estimates_acf)
  342. var_tau_acf = np.nanvar(tau_estimates_acf)
  343. mean_tau_mle = np.nanmean(tau_estimates_mle)
  344. var_tau_mle = np.nanvar(tau_estimates_mle)
  345. results['N'].append(N)
  346. results['mean_tau_acf'].append(mean_tau_acf)
  347. results['var_tau_acf'].append(var_tau_acf)
  348. results['mean_tau_mle'].append(mean_tau_mle)
  349. results['var_tau_mle'].append(var_tau_mle)
  350. # %%
  351. df_results = pd.DataFrame(results)
  352. plt.figure(figsize=(12, 6))
  353. # ACF
  354. plt.plot(df_results['N'], df_results['mean_tau_acf'], label='Exp fit', color='#4888A3', lw=1.5)
  355. plt.fill_between(df_results['N'],
  356. df_results['mean_tau_acf'] - np.sqrt(df_results['var_tau_acf']),
  357. df_results['mean_tau_acf'] + np.sqrt(df_results['var_tau_acf']),
  358. color='#4888A3', alpha=0.25)
  359. # MLE
  360. plt.plot(df_results['N'], df_results['mean_tau_mle'], label='MLE', color='#D32F26', lw=1.5)
  361. plt.fill_between(df_results['N'],
  362. df_results['mean_tau_mle'] - np.sqrt(df_results['var_tau_mle']),
  363. df_results['mean_tau_mle'] + np.sqrt(df_results['var_tau_mle']),
  364. color='#D32F26', alpha=0.25)
  365. plt.axhline(y=tau_true, color='green', lw=2,linestyle='--', label='True Tau')
  366. plt.xlabel('Time Length')
  367. plt.ylabel('τ Estimates')
  368. plt.legend()
  369. plt.grid(True)
  370. plt.show()
  371. # %% [markdown]
  372. # ##### effect of Δt
  373. # %%
  374. tau_true = 15.33
  375. D_true = .07
  376. A0 = -0.013
  377. N = 200
  378. max_lag = 50
  379. delta_t_values = np.arange(1, 5, .01)
  380. num_simulations = 100
  381. results = {
  382. 'delta_t': [],
  383. 'mean_tau_acf': [],
  384. 'var_tau_acf': [],
  385. 'mean_tau_mle': [],
  386. 'var_tau_mle': []
  387. }
  388. # %%
  389. for delta_t in tqdm(delta_t_values, desc="Processing delta_t values"):
  390. tau_estimates_acf = []
  391. tau_estimates_mle = []
  392. for sim in range(num_simulations):
  393. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  394. max_lag = 100
  395. lags, autocorr = compute_empirical_acf(A, max_lag)
  396. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  397. tau_estimates_acf.append(tau_est_acf)
  398. # MLE
  399. initial_guess = [np.random.rand(1)[0]+0.01, np.random.rand(1)[0]+0.01]
  400. tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
  401. tau_estimates_mle.append(tau_est_mle)
  402. mean_tau_acf = np.nanmean(tau_estimates_acf)
  403. var_tau_acf = np.nanvar(tau_estimates_acf)
  404. mean_tau_mle = np.nanmean(tau_estimates_mle)
  405. var_tau_mle = np.nanvar(tau_estimates_mle)
  406. results['delta_t'].append(delta_t)
  407. results['mean_tau_acf'].append(mean_tau_acf)
  408. results['var_tau_acf'].append(var_tau_acf)
  409. results['mean_tau_mle'].append(mean_tau_mle)
  410. results['var_tau_mle'].append(var_tau_mle)
  411. # %%
  412. df_results = pd.DataFrame(results)
  413. plt.figure(figsize=(12, 6))
  414. plt.plot(df_results['delta_t'], df_results['mean_tau_acf'], label='Exp fit', color='#4888A3', lw=1.5)
  415. plt.fill_between(df_results['delta_t'],
  416. df_results['mean_tau_acf'] - np.sqrt(df_results['var_tau_acf']),
  417. df_results['mean_tau_acf'] + np.sqrt(df_results['var_tau_acf']),
  418. color='#4888A3', alpha=0.25)
  419. plt.plot(df_results['delta_t'], df_results['mean_tau_mle'], label='MLE', color='#D32F26', lw=1.5)
  420. plt.fill_between(df_results['delta_t'],
  421. df_results['mean_tau_mle'] - np.sqrt(df_results['var_tau_mle']),
  422. df_results['mean_tau_mle'] + np.sqrt(df_results['var_tau_mle']),
  423. color='#D32F26', alpha=0.25)
  424. plt.axhline(y=tau_true, color='green', lw=2,linestyle='--', label='True τ')
  425. plt.xlabel('Δt')
  426. plt.ylabel('τ Estimates')
  427. plt.legend()
  428. plt.grid(True)
  429. plt.show()
  430. # %% [markdown]
  431. # ##### joint effects of N and delta t
  432. # %%
  433. np.random.seed(0)
  434. tau_true = 10.0
  435. D_true = .08
  436. A0 = -0.013
  437. max_lag = 50
  438. N_values = np.arange(100, 501, 50)
  439. delta_t_values = np.arange(1, 5, .5)
  440. num_simulations = 100
  441. mean_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
  442. mean_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
  443. var_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
  444. var_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
  445. mse_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
  446. mse_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
  447. # sim loop
  448. for i, N in enumerate(tqdm(N_values, desc="Processing N values")):
  449. for j, delta_t in enumerate(tqdm(delta_t_values, desc=f"N={N}", leave=False)):
  450. tau_estimates_acf = []
  451. tau_estimates_mle = []
  452. for sim in range(num_simulations):
  453. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  454. # dir ACF Fit
  455. max_lag_current = N // 2
  456. lags, autocorr = compute_empirical_acf(A, max_lag_current)
  457. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  458. tau_estimates_acf.append(tau_est_acf)
  459. # MLE
  460. initial_guess = [1.0, 0.5] # [tau_guess, D_guess]
  461. tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
  462. tau_estimates_mle.append(tau_est_mle)
  463. tau_estimates_acf = np.array(tau_estimates_acf)
  464. tau_estimates_mle = np.array(tau_estimates_mle)
  465. mean_tau_acf[i, j] = np.nanmean(tau_estimates_acf)
  466. mean_tau_mle[i, j] = np.nanmean(tau_estimates_mle)
  467. # compute var of Estimated Tau
  468. var_tau_acf[i, j] = np.nanvar(tau_estimates_acf)
  469. var_tau_mle[i, j] = np.nanvar(tau_estimates_mle)
  470. # compute MSE
  471. # mse_tau_acf[i, j] = np.nanmean((tau_estimates_acf - tau_true) ** 2)
  472. # mse_tau_mle[i, j] = np.nanmean((tau_estimates_mle - tau_true) ** 2)
  473. mse_tau_acf[i, j] = np.nanmean(abs(tau_estimates_acf - tau_true))
  474. mse_tau_mle[i, j] = np.nanmean(abs(tau_estimates_mle - tau_true))
  475. overall_mean_mse_acf = np.mean(mse_tau_acf)
  476. overall_mean_mse_mle = np.mean(mse_tau_mle)
  477. overall_mean_var_acf = np.mean(var_tau_acf)
  478. overall_mean_var_mle = np.mean(var_tau_mle)
  479. print("\nAggregated Results for Tau Estimation Methods:")
  480. print("------------------------------------------------")
  481. print(f"ACF Method:")
  482. print(f" Mean MSE: {overall_mean_mse_acf:.4f}")
  483. print(f" Mean Variance: {overall_mean_var_acf:.4f}\n")
  484. print(f"MLE Method:")
  485. print(f" Mean MSE: {overall_mean_mse_mle:.4f}")
  486. print(f" Mean Variance: {overall_mean_var_mle:.4f}\n")
  487. # %%
  488. print(mean_tau_acf.mean())
  489. print(mean_tau_mle.mean())
  490. # %%
  491. import matplotlib.colors as mcolors
  492. def hex_to_rgba(hex_color, alpha=1):
  493. return (*mcolors.hex2color(hex_color), alpha)
  494. zmin = min(np.nanmin(mean_tau_acf), np.nanmin(mean_tau_mle), tau_true)
  495. zmax = max(np.nanmax(mean_tau_acf), np.nanmax(mean_tau_mle), tau_true)
  496. zmin = 5
  497. zmax = 10
  498. # Meshgrid
  499. N_grid, delta_t_grid = np.meshgrid(N_values, delta_t_values, indexing='ij')
  500. # Dir fit
  501. fig = plt.figure(figsize=(14, 6))
  502. true_tau_surface = tau_true * np.ones_like(N_grid)
  503. #
  504. ax1 = fig.add_subplot(121, projection='3d')
  505. surf1 = ax1.plot_surface(N_grid, delta_t_grid, mean_tau_acf, cmap='cividis',
  506. edgecolor='none', linewidth=0,
  507. vmin=zmin, vmax=zmax
  508. )
  509. ax1.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24, linewidth=0)
  510. ax1.set_zlim(zmin, zmax)
  511. # fig.colorbar(surf1, ax=ax1, shrink=0.4, aspect=20)
  512. ax1.view_init(elev=25, azim=140)
  513. hexcol = "#f3f3f3"
  514. ax1.xaxis.set_pane_color(hex_to_rgba(hexcol))
  515. ax1.yaxis.set_pane_color(hex_to_rgba(hexcol))
  516. ax1.zaxis.set_pane_color(hex_to_rgba(hexcol))
  517. # ax1.set_xticks([200, 400])
  518. # ax1.set_yticks([2, 4])
  519. # ax1.set_zticks([3.5, 4.5])
  520. # MLE Method Plot
  521. ax2 = fig.add_subplot(122, projection='3d')
  522. surf2 = ax2.plot_surface(N_grid, delta_t_grid, mean_tau_mle, cmap='cividis',
  523. edgecolor='none',linewidth=0,
  524. vmin=zmin,
  525. vmax=zmax
  526. )
  527. ax2.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24)
  528. ax2.set_zlim(zmin, zmax)
  529. fig.colorbar(surf2, ax=ax2, shrink=0.4, aspect=20)
  530. ax2.view_init(elev=25, azim=140)
  531. ax2.xaxis.set_pane_color(hex_to_rgba(hexcol))
  532. ax2.yaxis.set_pane_color(hex_to_rgba(hexcol))
  533. ax2.zaxis.set_pane_color(hex_to_rgba(hexcol))
  534. # ax2.set_xticks([200, 400])
  535. # ax2.set_yticks([2, 4])
  536. # ax2.set_zticks([3.5, 4.5])
  537. plt.tight_layout()
  538. # plt.savefig("/Users/subat/Desktop/datan/bsnip/st1_28_oct/strnn/tf/network/space_smry/tau_estimation.png", dpi=400)
  539. plt.show()
  540. # %% [markdown]
  541. # ##### small tau
  542. # %%
  543. tau_true = 4.748
  544. D_true = .06967
  545. A0 = -0.013
  546. max_lag = 50
  547. N_values = np.arange(100, 1101, 100)
  548. delta_t_values = np.arange(1, 5, .05)
  549. num_simulations = 100
  550. mean_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
  551. mean_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
  552. #
  553. var_tau_acf = np.zeros((len(N_values), len(delta_t_values)))
  554. var_tau_mle = np.zeros((len(N_values), len(delta_t_values)))
  555. # %%
  556. for i, N in enumerate(tqdm(N_values, desc="Processing N values")):
  557. for j, delta_t in enumerate(tqdm(delta_t_values, desc=f"N={N}", leave=False)):
  558. tau_estimates_acf = []
  559. tau_estimates_mle = []
  560. for sim in range(num_simulations):
  561. A = simulate_ou_process(tau_true, D_true, delta_t, N, A0)
  562. max_lag = min(100, N//2)
  563. lags, autocorr = compute_empirical_acf(A, max_lag)
  564. tau_est_acf = estimate_tau_from_acf(lags, autocorr, delta_t)
  565. tau_estimates_acf.append(tau_est_acf)
  566. initial_guess = [1.0, 0.5]
  567. tau_est_mle, D_est_mle = estimate_parameters_mle(A, delta_t, initial_guess)
  568. tau_estimates_mle.append(tau_est_mle)
  569. mean_tau_acf[i, j] = np.nanmean(tau_estimates_acf)
  570. var_tau_acf[i, j] = np.nanvar(tau_estimates_acf)
  571. mean_tau_mle[i, j] = np.nanmean(tau_estimates_mle)
  572. var_tau_mle[i, j] = np.nanvar(tau_estimates_mle)
  573. # %%
  574. zmin = min(np.nanmin(mean_tau_acf), np.nanmin(mean_tau_mle), tau_true)
  575. zmax = max(np.nanmax(mean_tau_acf), np.nanmax(mean_tau_mle), tau_true)
  576. N_grid, delta_t_grid = np.meshgrid(N_values, delta_t_values, indexing='ij')
  577. fig = plt.figure(figsize=(14, 6))
  578. true_tau_surface = tau_true * np.ones_like(N_grid)
  579. ax1 = fig.add_subplot(121, projection='3d')
  580. surf1 = ax1.plot_surface(N_grid, delta_t_grid, mean_tau_acf, cmap='viridis',
  581. edgecolor='none',
  582. # vmin=zmin, vmax=zmax
  583. )
  584. ax1.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.24)
  585. ax1.set_title('Exponential fit')
  586. ax1.set_xlabel('Time Length')
  587. ax1.set_ylabel('Δt')
  588. ax1.set_zlabel('τ Estimates')
  589. ax1.set_zlim(zmin, zmax)
  590. fig.colorbar(surf1, ax=ax1, shrink=0.4, aspect=20)
  591. ax2 = fig.add_subplot(122, projection='3d')
  592. surf2 = ax2.plot_surface(N_grid, delta_t_grid, mean_tau_mle, cmap='plasma',
  593. edgecolor='none',
  594. # vmin=zmin,
  595. # vmax=zmax
  596. )
  597. ax2.plot_surface(N_grid, delta_t_grid, true_tau_surface, color='gray', alpha=0.4)
  598. ax2.set_title('OU MLE')
  599. ax2.set_xlabel('Time Length')
  600. ax2.set_ylabel('Δt')
  601. ax2.set_zlabel('τ Estimates')
  602. ax2.set_zlim(zmin, zmax)
  603. fig.colorbar(surf2, ax=ax2, shrink=0.4, aspect=20)
  604. plt.tight_layout()
  605. plt.show()

tau_experiments.ipynb at commit 6d83652, no license · at the source

Overview

Authors: Subati Abulikemu1,2, Puria Radmard3, Michail Mamalakis1,2,4, John Suckling1,2
  1. Department of Psychiatry, University of Cambridge,Cambridge, UK
  2. Centre for Human-Inspired Artificial Intelligence, University of Cambridge,Cambridge, UK
  3. Department of Engineering, University of Cambridge,Cambridge, UK
  4. Department of Computer Science and Technology, University of Cambridge,Cambridge, UK
Institutions: University of Cambridge (United Kingdom)
Journal: NPJ systems biology and applications, volume 12, issue 1, article 100
Dates: received 9 October 2025; accepted 15 April 2026; published online 30 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41540-026-00727-x · PMID 42056136 · PMCID PMC13338132 · OpenAlex W7158966799
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), schizophrenia / psychosis (population), computational (subfield)
Methods: Spectral & time-frequency, Preprocessing, Connectivity, Machine learning, fMRI & imaging
Keywords: Computational biology and bioinformatics, Neuroscience
MeSH: Schizophrenia*, Adult, Brain, Brain Mapping, Female, Humans, Magnetic Resonance Imaging, Male, Memory, Short-Term, Neocortex, Nerve Net, Recurrent Neural Networks (* major topic)
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Cambridge Trust; Caius Engineering Trust Studentship; Horizon Europe research and innovation program
Citations: cited by 1 paper (Europe PMC); 96 references in the paper

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

License: none: the authors keep all their rights
State: the link answers, verified on 30 September 2026
Evidence: files inventoried
Commit: 6d83652afab9121679262fb478dfe8f801e67dcc, 28 July 2025
Languages: Python (11), Jupyter (1)
Size: 13 files, 12 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, 1 notebook
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (12 files), PyTorch (7 files), SciPy (6 files), h5py (3 files), BrainSpace (2 files), Matplotlib (2 files), seaborn (2 files), ArviZ (1 file), pandas (1 file), PyMC (1 file), statsmodels (1 file)
Availability: 1 check, the latest on 30 September 2026: the link answers
  • 30 September 2026: the link answers
13 files

Code availability

All codes used in empirical and theoretical studies are available on the following GitHub repository: https://github.com/SubatA20/as_spec_rnn.

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://nda.nih.gov/). NDA is a collaborative informatics system created by the National Institutes of Health to provide a national resource to support and accelerate research in mental health. Dataset identifiers: NDAR ID: 2274. This manuscript reflects the views of the authors and may not reflect the opinions or views of the NIH or of the Submitters submitting original data to NDA.

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://doi.org/10.1038/s41540-026-00727-x

BibTeX

@article{abulikemu2026association,
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/s41540-026-00727-x},
url = {https://doi.org/10.1038/s41540-026-00727-x},
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/04/30
VL - 12
IS - 1
SP - 100
SN - 2056-7189
PB - Nature Publishing Group
DO - 10.1038/s41540-026-00727-x
UR - https://doi.org/10.1038/s41540-026-00727-x
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41540-026-00727-x",
"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": "NPJ Syst Biol Appl",
"volume": "12",
"issue": "1",
"page": "100",
"DOI": "10.1038/s41540-026-00727-x",
"PMID": "42056136",
"PMCID": "PMC13338132",
"ISSN": "2056-7189",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41540-026-00727-x",
"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 communications
In 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 communications
In 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 communications
In 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: eLife
In 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: iScience
In 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 biology
In 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 biology
In 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 brain
Journal: 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.

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.