Example 1¶
This is an example that reproduces Figure 3 (up to iteration 50) from Genkin, M., Engel, T.A., Nat Mach Intell 2, 674–683 (2020). It also calculates validated negative loglikelihood at each iteration and plots it next to the training negative loglikelihood.
[1]:
import neuralflow
import numpy as np
import matplotlib.pyplot as plt, matplotlib.gridspec as gridspec
Step 1: Generate synthetic data¶
Specify the ground-truth model for data generation, see the implementation of the
EnergyModel
class for available options. Here we use the spectral elements method (SEM) for the eigenvector-eigenvalue problem withNe=256
elements andNp=8
points per element. We retainNv=64
eigenvectors and eigenvalues of the \(\mathcal{H}\) operator for the likelihood and gradients calculations. We use double-well model (as in main text FIG. 3, 4), noise magnitudeD0=10
, 1 neural response with the firing rate functionf(x) = 100*(x+1)
hz. We represent the results by plotting model potential across gradient descent iterations, and optimize the driving force \(F(x)\). Fitting results are saved in the form of equilibrium probability distribution \(p_{\rm eq}(x)\).Specify data generation parameters. Here we generate two trials of duration
100
seconds with the temporal resolution for latent trajectoriesdeltaT = 0.0001
.Perform data generation, and split the generated data into training and validation datasets
[2]:
EnergyModelParams={'pde_solve_param':{'method':{'name': 'SEM', 'gridsize': {'Np': 8, 'Ne': 256}}},
'Nv': 64,
'peq_model':{"model": "double_well", "params": {"xmin": 0.6, "xmax": 0.0, "depth": 2}},
'D0': 10,
'num_neuron':1,
'firing_model':[{"model": "rectified_linear", "params": {"r_slope": 100, "x_thresh": -1}}],
'verbose':True
}
data_gen_params={'deltaT':0.0001, 'time_epoch': [(0,100)]*2}
#Initialise ground-truth em class
em_gt=neuralflow.EnergyModel(**EnergyModelParams)
#Save the ground-truth model for visulalization
peq_gt=np.copy(em_gt.peq_)
#Generate data
data, time_bins, diff_traj, _=em_gt.generate_data(**data_gen_params)
#Split the data into training and validation set
dataTR=data[[0]]
dataCV=data[[1]]
Step 2: Perform model optimization¶
Create another instance of
EnergyModel
that will be used for model fitting. This instance is the same as the ground-truth but with different equilibrium probability distributionpeq
. In this case,peq
serves as the initial guess.Define fitting parameters. Here we use Gradient descent optimizer (
GD
), limit the optimization to 50 iteration, set the learning rate to0.005
,loglik_tol
to zero (so that optimization will not terminate due to the lack of relative loglikelihood improvement),etaf
to zero (so that there is no cost function regularizer), and specify validation data.Perform fitting
[3]:
%%capture
EnergyModelParams={'pde_solve_param':{'method':{'name': 'SEM', 'gridsize': {'Np': 8, 'Ne': 256}}},
'Nv': 64,
'peq_model':{"model": "cos_square", "params": {}},
'D0': 10,
'num_neuron':1,
'firing_model':[{"model": "rectified_linear", "params": {"r_slope": 100, "x_thresh": -1}}],
'verbose':True
}
em_fitting=neuralflow.EnergyModel(**EnergyModelParams)
optimizer='GD'
fitting_params={}
fitting_params['data']={'dataTR':dataTR,'dataCV': dataCV}
fitting_params['optimization']={'max_iteration': 50, 'gamma': {'F': 0.005}, 'loglik_tol':0.0, 'etaf': 0}
em_fitting.fit(optimizer,fitting_params)
Step 3: Visualize the results¶
[4]:
lls=em_fitting.iterations_GD_['logliks']
lls_CV=em_fitting.iterations_GD_['logliksCV']
#Shift training and validated loglikelihoods such that they both start from 0 for visualisation purposes
lls= (lls-lls[0])
lls_CV=(lls_CV-lls_CV[0])
fig=plt.figure(figsize=(20,7))
gridspec.GridSpec(2,4)
Iterations=[1,6,13,50]
colors=[[0.0, 0.0, 1.0],
[0.2, 0.4, 0.8],
[0.4, 0.2, 0.6],
[0.6, 0.2, 0.4]]
# Plot negative loglikelihood vs. iteration number
plt.subplot2grid((2,4), (0,0), colspan=2, rowspan=2)
plt.plot(np.arange(1,lls.size+1),lls,color='black',linewidth=3,label='training')
plt.plot(np.arange(1,lls_CV.size+1),lls_CV,color='red',linewidth=3, label='validation')
plt.xlabel('Iteration #', fontsize=18)
plt.ylabel(r'$-\log\mathcal{L}$', fontsize=18)
plt.legend()
#Point at iteration with minCV
minCV, ysize=np.argmin(lls_CV)+1, -np.min(lls_CV)/5
plt.arrow(minCV,lls_CV[minCV-1]+ysize,0,-ysize*0.9,width=0.25,length_includes_head=True,head_width=1.5,head_length=10, color='red')
#Plot potentials. Potential is calculated from peq by taking negative log: Phi = - log(peq).
for i,Iter in enumerate(Iterations):
plt.subplot2grid((2,4), (i//2,2+i%2))
plt.plot(em_fitting.x_d_,np.minimum(-np.log(em_fitting.iterations_GD_['peqs'][...,Iterations[i]-1]),6),color=colors[i],linewidth=3)
plt.plot(em_fitting.x_d_,np.minimum(-np.log(peq_gt),6),color='grey',linewidth=2)
plt.title('Iteration {}'.format(Iterations[i]))
The code above should produce the following image: