VU-Cog-Sci / prfpy

prf fitting routines at Spinoza Centre for Neuroimaging
GNU General Public License v3.0
7 stars 4 forks source link

Connective fields #4

Closed N-HEDGER closed 3 years ago

N-HEDGER commented 3 years ago

Here I have written some initial code to start incorporating connective fields into prfpy. This is based on previous discussions with TK and MA. You can see test/develop/CF_progress for a demo.

Summary (also detailed in commit messages).

  1. As suggested, I have now separated out the pycortex-related stuff required for making the subsurfaces and distance matrices. This now sits in utils.py. There is a 'subsurface' class that can make the source subsurfaces for a pycortex subject. All of my pycortex-based visualisation stuff has also now gone.

  2. As suggested, the distance matrices are now padded, so that distances in the opposite hemisphere are coded as np.inf. This prevents us having to treat the hemispheres separately, as was the case in my previous effort.

  3. I created new stimulus and model classes, these are significantly more minimal than they were before and are structured similarly to the existing classes. The CFStimulus class only requires i) the data, ii) the vertex indices defining the subsurface and iii) the distance X distance matrices. Therefore, it is up to the user whether they want to use the 'subsurface' class in utils.py to provide this information or not. In principle, all the relevant information that is provided to the CFstimulus class could be created outside of pycortex - there is no requirement to use it.

  4. At the fitting stage, things start to diverge a little. As discussed before, we may not always want to do a full GLM-based fitting for the CF models - we may want to use a fast method based on the dot product of z-scored data as used in Tomas' paper. Therefore, I include a quick_grid_fit method that does all the fitting via np.tensordot. This returns an array called quick_gridsearch_params to differentiate them from the gridsearch_params produced by grid_fit.

  5. Currently, as far as I can tell, the current cross validation methods only work on the iterative search params - but if you want to use this 'quick' method, you would want to do the cross validation instead based on the gridsearch params. Hence, I have included a 'quick_xval' method.

  6. In returning the best fitting parameters, note that the vertex centres should be ints - and keeping them in an array with the other parameters seems to force them into a float. Therefore, I additionally return the vertex_centres as a vector of ints.

To do list

  1. Currently, I have not yet implemented a way in which the gridsearch params are fed into the iterative fitting stage. As we discussed before - we don't want to optimise the vertex centres - we only really want to optimise sigma. We should discuss how the current iterative fit methods allow us to ignore some parameters, or whether we would have to write a completely new method that ignores the vertex centres. One method of doing this would be to remove the vertex centres from the starting params, convert them into a dictionary and enter this as one of args to the iterative_fit method. Then when it comes to the error function (below), my understanding is it will use the args (vertex centre) to generate the model prediction, but it wont try and optimise args, only parameters
 np.nan_to_num(np.sum((data - objective_function(*list(parameters), **args))**2), nan=1)

One other alternative we discussed was to somehow use iterate over the distance matrix instead. At any rate, it is worth discussing the best way to do this.

  1. One problem could be that creating a new function for the 'quick' fitting may not fit stylistically. Instead, it might be better to have one function for grid fitting, that has an argument that determines whether whether quick or GLM based fitting is performed. i.e.
gf.grid_fit(sigmas,method='GLM')
gf.grid_fit(sigmas,method='quick'')
marcoaqil commented 3 years ago

Hi Nick! Really awesome stuff here, sorry for the late reply. One way to keep some parameters fixed in iterative fit while fitting the others is to use the Bounds argument of python optimizers. When an optimizer is given a bound for a parameter where lower and upper bounds are identical like (1,1), it will just keep that parameter fixed to 1. I do this in the context of some pRF models. In my head, that is stylistically better than removing some parameters from the array, but in practice the latter might work better. Whatever works works :)

Let know when you are done coding and testing this branch, and we should indeed start merging it into the master.

marcoaqil commented 3 years ago

Merged today! on the to do list, as from my comment above: Because it is something that is useful in a variety of situations, I will implement myself ASAP a minimal way to keep certain parameters fixed in iterative fitting to a specific value, and only fit others, without changing the structure of fitting params array (which could create problems later on) or using additional non-array structures (dictionaries). See the relevant issue that I opened