fitnonlinearmodel.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. function results = fitnonlinearmodel(opt,chunksize,chunknum)
  2. % function results = fitnonlinearmodel(opt,chunksize,chunknum)
  3. %
  4. % <opt> is a struct with the following fields (or a .mat file with 'opt'):
  5. %
  6. % *** OUTPUT DIRECTORY ***
  7. % <outputdir> (optional) is the directory to save results to
  8. %
  9. % *** STIMULUS ***
  10. % <stimulus> is:
  11. % (1) a matrix with time points x components
  12. % (2) a cell vector of (1) indicating different runs
  13. % (3) a function that returns (1) or (2)
  14. %
  15. % *** DATA ***
  16. % <data> is:
  17. % (1) a matrix with time points x voxels
  18. % (2) a cell vector of (1) indicating different runs
  19. % (3) a function that returns (1) or (2)
  20. % (4) a function that accepts a vector of voxel indices and returns (1) or (2)
  21. % corresponding to those voxels. in this case, <vxs> must be supplied.
  22. % <vxs> (optional) is:
  23. % (1) a vector of all voxel indices that you wish to analyze. (If you use
  24. % the chunking mechanism (<chunksize>, <chunknum>), then a subset of these
  25. % voxels are analyzed in any given function call.) Note that we automatically
  26. % sort the voxel indices and ensure uniqueness.
  27. % (2) a .mat file with 'vxs' as (1)
  28. % this input matters only if <data> is of case (4).
  29. %
  30. % *** MODEL ***
  31. % <model> is:
  32. % {X Y Z W} where
  33. % X is the initial seed (1 x P).
  34. % Y are the bounds (2 x P). NaNs in the first row indicate parameters to fix.
  35. % Z is a function that accepts two arguments, parameters (1 x P) and
  36. % stimuli (N x C), and outputs predicted responses (N x 1).
  37. % W (optional) is a function that transforms stimuli into a new form prior
  38. % to model evaluation.
  39. % OR
  40. % {M1 M2 M3 ...} where M1 is of the form {X Y Z W} described above,
  41. % and the remaining Mi are of the form {F G H I} where
  42. % F is a function that takes fitted parameters (1 x P) from the previous model
  43. % and outputs an initial seed (1 x Pnew) for the current model
  44. % G are the bounds (2 x Pnew). NaNs in the first row indicate parameters to fix.
  45. % H is a function that takes fitted parameters (1 x P) from the previous model
  46. % and outputs a function that accepts two arguments, parameters (1 x Pnew) and
  47. % stimuli (N x C), and outputs predicted responses (N x 1).
  48. % I (optional) is a function that takes fitted parameters (1 x P) from the
  49. % previous model and outputs a function that transforms stimuli into a
  50. % new form prior to model evaluation.
  51. % OR
  52. % M where M is a function that takes stimuli (N x C) and responses (N x 1) and
  53. % outputs an estimate of the linear weights (1 x C). For example, simple
  54. % OLS regression is the case where M is @(X,y) (inv(X'*X)*X'*y)'.
  55. % This case is referred to as the linear-model case.
  56. %
  57. % *** SEED ***
  58. % <seed> (optional) is:
  59. % (1) the initial seed (1 x P)
  60. % (2) several initial seeds to try (Q x P) in order to find the one that
  61. % produces the least error
  62. % (3) a function that accepts a single voxel index and returns (1) or (2).
  63. % in this case, <vxs> must be supplied.
  64. % If supplied, <seed> overrides the contents of X in <model>.
  65. % In the linear-model case, <seed> is not applicable and should be [].
  66. %
  67. % *** OPTIMIZATION OPTIONS ***
  68. % <optimoptions> (optional) are optimization options in the form used by optimset.m.
  69. % Can also be a cell vector with option/value pairs, in which these are applied
  70. % after the default optimization options. The default options are:
  71. % optimset('Display','iter','FunValCheck','on', ...
  72. % 'MaxFunEvals',Inf,'MaxIter',Inf, ...
  73. % 'TolFun',1e-6,'TolX',1e-6, ...
  74. % 'OutputFcn',@(a,b,c) outputfcnsanitycheck(a,b,c,1e-6,10))
  75. % In particular, it may be useful to specify a specific optimization algorithm to use.
  76. % In the linear-model case, <optimoptions> is ignored.
  77. % <outputfcn> (optional) is a function suitable for use as an 'OutputFcn'. If you
  78. % supply <outputfcn>, it will take precedence over any 'OutputFcn' in <optimoptions>.
  79. % The reason for <outputfcn> is that the data points being fitted will be passed as a
  80. % fourth argument to <outputfcn> (if <outputfcn> accepts four arguments). This
  81. % enables some useful functionality such as being able to visualize the model and
  82. % the data during the optimization.
  83. % In the linear-model case, <outputfcn> is ignored.
  84. %
  85. % *** RESAMPLING SCHEMES ***
  86. % <wantresampleruns> (optional) is whether to resample at the level of runs (as opposed
  87. % to the level of individual data points). If only one run of data is supplied, the
  88. % default is 0 (resample data points). If more than one run of data is supplied, the
  89. % default is 1 (resample runs).
  90. % <resampling> (optional) is:
  91. % 0 means fit fully (no bootstrapping nor cross-validation)
  92. % B or {B SEED GROUP} indicates to perform B bootstraps, using SEED as the random
  93. % number seed, and GROUP as the grouping to use. GROUP should be a vector of
  94. % positive integers. For example, [1 1 1 2 2 2] means to draw six bootstrap
  95. % samples in total, with three bootstrap samples from the first three cases and
  96. % three bootstrap samples from the second three cases. If SEED is not provided,
  97. % the default is sum(100*clock). If GROUP is not provided, the default is ones(1,D)
  98. % where D is the total number of runs or data points.
  99. % V where V is a matrix of dimensions (cross-validation schemes) x (runs or data
  100. % points). Each row indicates a distinct cross-validation scheme, where 1 indicates
  101. % training, -1 indicates testing, and 0 indicates to not use. For example,
  102. % [1 1 -1 -1 0] specifies a scheme where the first two runs (or data points) are
  103. % used for training and the second two runs (or data points) are used for testing.
  104. % Default: 0.
  105. %
  106. % *** METRIC ***
  107. % <metric> (optional) determine how model performance is quantified. <metric> should
  108. % be a function that accepts two column vectors (the first is the model; the second
  109. % is the data) and outputs a number. Default: @calccorrelation.
  110. %
  111. % *** ADDITIONAL REGRESSORS ***
  112. % <maxpolydeg> (optional) is a non-negative integer with the maximum polynomial degree
  113. % to use for polynomial nuisance functions. The polynomial nuisance functions are
  114. % constructed on a per-run basis. <maxpolydeg> can be a vector to indicate different
  115. % degrees for different runs. A special case is NaN which means to omit polynomials.
  116. % Default: NaN.
  117. % <wantremovepoly> (optional) is whether to project the polynomials out from both the
  118. % model and the data before computing <metric>. Default: 1.
  119. % <extraregressors> (optional) is:
  120. % (1) a matrix with time points x regressors
  121. % (2) a cell vector of (1) indicating different runs
  122. % (3) a function that returns (1) or (2)
  123. % Note that a separate set of regressors must be supplied for each run. The number
  124. % of regressors does not have to be the same across runs.
  125. % <wantremoveextra> (optional) is whether to project the extraregressors out from
  126. % both the model and the data before computing <metric>. Default: 1.
  127. %
  128. % *** OUTPUT-RELATED ***
  129. % <dontsave> (optional) is a string or a cell vector of strings indicating outputs
  130. % to omit when returning. For example, you may want to omit 'testdata', 'modelpred',
  131. % 'modelfit', 'numiters', and 'resnorms' since they may use a lot of memory.
  132. % If [] or not supplied, then we use the default of {'modelfit' 'numiters' 'resnorms'}.
  133. % If {}, then we will return all outputs. Note: <dontsave> can also refer to
  134. % auxiliary variables that are saved to the .mat files when <outputdir> is used.
  135. % <dosave> (optional) is just like 'dontsave' except that the outputs specified here
  136. % are guaranteed to be returned. (<dosave> takes precedence over <dontsave>.)
  137. % Default is {}.
  138. %
  139. % <chunksize> (optional) is the number of voxels to process in a single function call.
  140. % The default is to process all voxels.
  141. % <chunknum> (optional) is the chunk number to process. Default: 1.
  142. %
  143. % This function, fitnonlinearmodel.m, is essentially a wrapper around MATLAB's
  144. % lsqcurvefit.m function for the purposes of fitting nonlinear (and linear) models
  145. % to data.
  146. %
  147. % This function provides the following key benefits:
  148. % - Deals with input and output issues (making it easy to process many individual
  149. % voxels and evaluate different models)
  150. % - Deals with resampling (cross-validation and bootstrapping)
  151. % - In the case of nonlinear models, makes it easy to evaluate multiple initial
  152. % seeds (to avoid local minima)
  153. % - In the case of nonlinear models, makes it easy to perform stepwise fitting of models
  154. %
  155. % Outputs:
  156. % - 'params' is resampling cases x parameters x voxels.
  157. % These are the estimated parameters from each resampling scheme for each voxel.
  158. % - 'trainperformance' is resampling cases x voxels.
  159. % This is the performance of the model on the training data under each resampling
  160. % scheme for each voxel.
  161. % - 'testperformance' is resampling cases x voxels.
  162. % This is the performance of the model on the testing data under each resampling
  163. % scheme for each voxel.
  164. % - 'aggregatedtestperformance' is 1 x voxels.
  165. % This is the performance of the model on the testing data, after aggregating
  166. % the data and model predictions across the resampling schemes.
  167. % - 'testdata' is time points x voxels.
  168. % This is the aggregated testing data across the resampling schemes.
  169. % - 'modelpred' is time points x voxels.
  170. % This is the aggregated model predictions across the resampling schemes.
  171. % - 'modelfit' is resampling cases x time points x voxels.
  172. % This is the model fit for each resampling scheme. Here, by "model fit"
  173. % we mean the fit for each of the original stimuli based on the parameters
  174. % estimated in a given resampling case; we do not mean the fit for each of the
  175. % stimuli involved in the fitting. (For example, if there are 100 stimuli and
  176. % we are performing cross-validation, there will nevertheless be 100 time points
  177. % in 'modelfit'.) Also, note that 'modelfit' is the raw fit; it is not adjusted
  178. % for <wantremovepoly> and <wantremoveextra>.
  179. % - 'numiters' is a cell vector of dimensions 1 x voxels. Each element is
  180. % is resampling cases x seeds x models. These are the numbers of iterations
  181. % used in the optimizations. Note that 'numiters' is [] in the linear-model case.
  182. % - 'resnorms' is a cell vector of dimensions 1 x voxels. Each element is
  183. % is resampling cases x seeds. These are the residual norms obtained
  184. % in the optimizations. This is useful for diagnosing multiple-seed issues.
  185. % Note that 'resnorms' is [] in the linear-model case.
  186. %
  187. % Notes:
  188. % - Since we use %06d.mat to name output files, you should use no more than 999,999 chunks.
  189. % - <chunksize> and <chunknum> can be strings (if so, they will be passed to str2double).
  190. % - <stimulus> can actually have multiple frames in the third dimension. This is handled
  191. % by making it such that the prediction for a given data point is calculated as the
  192. % average of the predicted responses for the individual stimulus frames associated with
  193. % that data point.
  194. % - In the case of nonlinear models, to control the scale of the computations, in the
  195. % optimization call we divide the data by its standard deviation and apply the exact
  196. % same scaling to the model. This has the effect of controlling the scale of the
  197. % residuals. This last-minute scaling should have no effect on the final parameter estimates.
  198. %
  199. % History:
  200. % - 2014/05/01 - change the main loop to parfor; some cosmetic tweaks;
  201. % now, if no parameters are to be optimized, just return the initial seed
  202. % - 2013/10/02 - implement the linear-model case
  203. % - 2013/09/07 - fix bug (if polynomials or extra regressors were used in multiple runs,
  204. % then they were not getting fit properly).
  205. % - 2013/09/07 - in fitnonlinearmodel_helper.m, convert to double in the call to lsqcurvefit;
  206. % and perform a speed-up (don't compute modelfit if unwanted)
  207. % - 2013/09/04 - add totalnumvxs variable
  208. % - 2013/09/03 - allow <dontsave> to refer to the auxiliary variables
  209. % - 2013/09/02 - add 'modelfit' and adjust default for 'dontsave'; add 'dosave'
  210. % - 2013/08/28 - new outputs 'resnorms' and 'numiters'; last-minute data scaling;
  211. % tweak default handling of 'dontsave'
  212. % - 2013/08/18 - Initial version.
  213. %
  214. % Example 1:
  215. %
  216. % % first, a simple example
  217. % x = randn(100,1);
  218. % y = 2*x + 3 + randn(100,1);
  219. % opt = struct( ...
  220. % 'stimulus',[x ones(100,1)], ...
  221. % 'data',y, ...
  222. % 'model',{{[1 1] [-Inf -Inf; Inf Inf] @(pp,dd) dd*pp'}});
  223. % results = fitnonlinearmodel(opt);
  224. %
  225. % % now, try 100 bootstraps
  226. % opt.resampling = 100;
  227. % opt.optimoptions = {'Display' 'off'}; % turn off reporting
  228. % results = fitnonlinearmodel(opt);
  229. %
  230. % % now, try leave-one-out cross-validation
  231. % opt.resampling = -(2*(eye(100) - 0.5));
  232. % results = fitnonlinearmodel(opt);
  233. %
  234. % Example 2:
  235. %
  236. % % try a more complicated example. we use 'outputfcn' to
  237. % % visualize the data and model during the optimization.
  238. % x = (1:.1:10)';
  239. % y = evalgaussian1d([5 1 4 0],x);
  240. % y = y + randn(size(y));
  241. % opt = struct( ...
  242. % 'stimulus',x, ...
  243. % 'data',y, ...
  244. % 'model',{{[1 2 1 0] [repmat(-Inf,[1 4]); repmat(Inf,[1 4])] ...
  245. % @(pp,dd) evalgaussian1d(pp,dd)}}, ...
  246. % 'outputfcn',@(a,b,c,d) pause2(.1) | outputfcnsanitycheck(a,b,c,1e-6,10) | outputfcnplot(a,b,c,1,d));
  247. % results = fitnonlinearmodel(opt);
  248. %
  249. % Example 3:
  250. %
  251. % % same as the first example in Example 1, but now we use the
  252. % % linear-model functionality
  253. % x = randn(100,1);
  254. % y = 2*x + 3 + randn(100,1);
  255. % opt = struct( ...
  256. % 'stimulus',[x ones(100,1)], ...
  257. % 'data',y, ...
  258. % 'model',@(X,y) (inv(X'*X)*X'*y)');
  259. % results = fitnonlinearmodel(opt);
  260. % internal notes:
  261. % - replaces fitprf.m, fitprfstatic.m, fitprfmulti.m, and fitprfstaticmulti.m
  262. % - some of the new features: opt struct format, fix projection matrix bug (must
  263. % compute projection matrix based on concatenated regressors), multiple initial
  264. % seeds are handled internally!, user must deal with model specifics like
  265. % the HRF and positive rectification, massive clean up of the logic (e.g.
  266. % runs and data points are treated as a single case), consolidation of
  267. % the different functions, drop support for data trials (not worth the
  268. % implementation cost), drop support for NaN stimulus frames, hide the
  269. % myriad optimization options from the input level, drop run-separated metrics,
  270. % drop the stimulus transformation speed-up (it was getting implemented in a
  271. % non-general way)
  272. % - regularization is its own thing? own code module?
  273. %%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
  274. fprintf('*** fitnonlinearmodel: started at %s. ***\n',datestr(now));
  275. stime = clock; % start time
  276. %%%%%%%%%%%%%%%%%%%%%%%%%%%% SETUP
  277. % deal with opt
  278. if ischar(opt)
  279. opt = loadmulti(opt,'opt');
  280. end
  281. % is <data> of case (4)?
  282. isvxscase = isa(opt.data,'function_handle') && nargin(opt.data) > 0;
  283. % deal with outputdir
  284. if ~isfield(opt,'outputdir') || isempty(opt.outputdir)
  285. opt.outputdir = [];
  286. end
  287. wantsave = ~isempty(opt.outputdir); % should we save results to disk?
  288. % deal with vxs
  289. if isfield(opt,'vxs')
  290. if ischar(opt.vxs)
  291. vxsfull = loadmulti(opt.vxs,'vxs');
  292. else
  293. vxsfull = opt.vxs;
  294. end
  295. vxsfull = sort(union([],flatten(vxsfull)));
  296. totalnumvxs = length(vxsfull);
  297. end
  298. % deal with chunksize and chunknum
  299. if ~exist('chunksize','var') || isempty(chunksize)
  300. chunksize = []; % deal with this later
  301. end
  302. if ~exist('chunknum','var') || isempty(chunknum)
  303. chunknum = 1;
  304. end
  305. if ischar(chunksize)
  306. chunksize = str2double(chunksize);
  307. end
  308. if ischar(chunknum)
  309. chunknum = str2double(chunknum);
  310. end
  311. % deal with data (including load the data)
  312. if isa(opt.data,'function_handle')
  313. fprintf('*** fitnonlinearmodel: loading data. ***\n');
  314. if nargin(opt.data) == 0
  315. data = feval(opt.data);
  316. if iscell(data)
  317. totalnumvxs = size(data{1},2);
  318. else
  319. totalnumvxs = size(data,2);
  320. end
  321. else % note that in this case, vxs should have been specified,
  322. % so totalnumvxs should have already been calculated above.
  323. if isempty(chunksize)
  324. chunksize = length(vxsfull);
  325. end
  326. vxs = chunking(vxsfull,chunksize,chunknum);
  327. data = feval(opt.data,vxs);
  328. end
  329. else
  330. data = opt.data;
  331. if iscell(data)
  332. totalnumvxs = size(data{1},2);
  333. else
  334. totalnumvxs = size(data,2);
  335. end
  336. end
  337. if ~iscell(data)
  338. data = {data};
  339. end
  340. % deal with chunksize
  341. if isempty(chunksize)
  342. chunksize = totalnumvxs; % default is all voxels
  343. end
  344. % if not isvxscase, then we may still need to do chunking
  345. if ~isvxscase
  346. vxs = chunking(1:totalnumvxs,chunksize,chunknum);
  347. if ~isequal(vxs,1:totalnumvxs)
  348. for p=1:length(data)
  349. data{p} = data{p}(:,vxs);
  350. end
  351. end
  352. end
  353. % calculate the number of voxels to analyze in this function call
  354. vnum = length(vxs);
  355. % finally, since we have dealt with chunksize and chunknum, we can do some reporting
  356. fprintf(['*** fitnonlinearmodel: outputdir = %s, chunksize = %d, chunknum = %d\n'], ...
  357. opt.outputdir,chunksize,chunknum);
  358. % deal with model
  359. if ~isa(opt.model,'function_handle') && ~iscell(opt.model{1})
  360. opt.model = {opt.model};
  361. end
  362. if ~isa(opt.model,'function_handle')
  363. for p=1:length(opt.model)
  364. if length(opt.model{p}) < 4 || isempty(opt.model{p}{4})
  365. if p==1
  366. opt.model{p}{4} = @identity;
  367. else
  368. opt.model{p}{4} = @(ss) @identity;
  369. end
  370. end
  371. end
  372. end
  373. % deal with seed
  374. if ~isfield(opt,'seed') || isempty(opt.seed)
  375. opt.seed = [];
  376. end
  377. % deal with optimization options
  378. if ~isfield(opt,'optimoptions') || isempty(opt.optimoptions)
  379. opt.optimoptions = {};
  380. end
  381. if iscell(opt.optimoptions)
  382. temp = optimset('Display','iter','FunValCheck','on', ...
  383. 'MaxFunEvals',Inf,'MaxIter',Inf, ...
  384. 'TolFun',1e-6,'TolX',1e-6, ...
  385. 'OutputFcn',@(a,b,c) outputfcnsanitycheck(a,b,c,1e-6,10));
  386. for p=1:length(opt.optimoptions)/2
  387. temp.(opt.optimoptions{(p-1)*2+1}) = opt.optimoptions{(p-1)*2+2};
  388. end
  389. opt.optimoptions = temp;
  390. clear temp;
  391. end
  392. if ~isfield(opt,'outputfcn') || isempty(opt.outputfcn)
  393. opt.outputfcn = [];
  394. end
  395. % deal with resampling schemes
  396. if ~isfield(opt,'wantresampleruns') || isempty(opt.wantresampleruns)
  397. if length(data) == 1
  398. opt.wantresampleruns = 0;
  399. else
  400. opt.wantresampleruns = 1;
  401. end
  402. end
  403. if opt.wantresampleruns
  404. numdataunits = length(data);
  405. else
  406. numdataunits = sum(cellfun(@(x) size(x,1),data));
  407. end
  408. if ~isfield(opt,'resampling') || isempty(opt.resampling)
  409. opt.resampling = 0;
  410. end
  411. if isequal(opt.resampling,0)
  412. resamplingmode = 'full';
  413. elseif ~iscell(opt.resampling) && numel(opt.resampling) > 1
  414. resamplingmode = 'xval';
  415. else
  416. resamplingmode = 'boot';
  417. end
  418. if isequal(resamplingmode,'boot')
  419. if ~iscell(opt.resampling)
  420. opt.resampling = {opt.resampling};
  421. end
  422. if length(opt.resampling) < 2 || isempty(opt.resampling{2})
  423. opt.resampling{2} = sum(100*clock);
  424. end
  425. if length(opt.resampling) < 3 || isempty(opt.resampling{3})
  426. opt.resampling{3} = ones(1,numdataunits);
  427. end
  428. end
  429. % deal with metric
  430. if ~isfield(opt,'metric') || isempty(opt.metric)
  431. opt.metric = @calccorrelation;
  432. end
  433. % deal with additional regressors
  434. if ~isfield(opt,'maxpolydeg') || isempty(opt.maxpolydeg)
  435. opt.maxpolydeg = NaN;
  436. end
  437. if length(opt.maxpolydeg) == 1
  438. opt.maxpolydeg = repmat(opt.maxpolydeg,[1 length(data)]);
  439. end
  440. if ~isfield(opt,'wantremovepoly') || isempty(opt.wantremovepoly)
  441. opt.wantremovepoly = 1;
  442. end
  443. if ~isfield(opt,'extraregressors') || isempty(opt.extraregressors)
  444. opt.extraregressors = [];
  445. end
  446. if ~isfield(opt,'wantremoveextra') || isempty(opt.wantremoveextra)
  447. opt.wantremoveextra = 1;
  448. end
  449. if ~isfield(opt,'dontsave') || (isempty(opt.dontsave) && ~iscell(opt.dontsave))
  450. opt.dontsave = {'modelfit' 'numiters' 'resnorms'};
  451. end
  452. if ~iscell(opt.dontsave)
  453. opt.dontsave = {opt.dontsave};
  454. end
  455. if ~isfield(opt,'dosave') || isempty(opt.dosave)
  456. opt.dosave = {};
  457. end
  458. if ~iscell(opt.dosave)
  459. opt.dosave = {opt.dosave};
  460. end
  461. % make outputdir if necessary
  462. if wantsave
  463. mkdirquiet(opt.outputdir);
  464. opt.outputdir = subscript(matchfiles(opt.outputdir),1,1);
  465. outputfile = sprintf([opt.outputdir '/%06d.mat'],chunknum);
  466. end
  467. % set random number seed
  468. if isequal(resamplingmode,'boot')
  469. setrandstate({opt.resampling{2}});
  470. end
  471. % calc
  472. numtime = cellfun(@(x) size(x,1),data);
  473. % save initial time
  474. if wantsave
  475. saveexcept(outputfile,[{'data'} setdiff(opt.dontsave,opt.dosave)]);
  476. end
  477. %%%%%%%%%%%%%%%%%%%%%%%%%%%% LOAD SOME ITEMS
  478. % deal with stimulus
  479. if isa(opt.stimulus,'function_handle')
  480. fprintf('*** fitnonlinearmodel: loading stimulus. ***\n');
  481. stimulus = feval(opt.stimulus);
  482. else
  483. stimulus = opt.stimulus;
  484. end
  485. if ~iscell(stimulus)
  486. stimulus = {stimulus};
  487. end
  488. stimulus = cellfun(@full,stimulus,'UniformOutput',0);
  489. % deal with extraregressors
  490. if isa(opt.extraregressors,'function_handle')
  491. fprintf('*** fitnonlinearmodel: loading extra regressors. ***\n');
  492. extraregressors = feval(opt.extraregressors);
  493. else
  494. extraregressors = opt.extraregressors;
  495. end
  496. if isempty(extraregressors)
  497. extraregressors = repmat({[]},[1 length(data)]);
  498. end
  499. if ~iscell(extraregressors)
  500. extraregressors = {extraregressors};
  501. end
  502. %%%%%%%%%%%%%%%%%%%%%%%%%%%% PRECOMPUTE SOME STUFF
  503. % construct polynomial regressors matrix
  504. polyregressors = {};
  505. for p=1:length(data)
  506. if isnan(opt.maxpolydeg(p))
  507. polyregressors{p} = zeros(numtime(p),0);
  508. else
  509. polyregressors{p} = constructpolynomialmatrix(numtime(p),0:opt.maxpolydeg(p));
  510. end
  511. end
  512. % construct total regressors matrix for fitting purposes
  513. % (i.e. both polynomials and extra regressors)
  514. % first, construct the run-wise regressors
  515. tmatrix = {};
  516. for p=1:length(data)
  517. tmatrix{p} = cat(2,polyregressors{p},extraregressors{p});
  518. end
  519. % then, separate them using blkdiag
  520. temp = blkdiag(tmatrix{:});
  521. cnt = 0;
  522. for p=1:length(data)
  523. tmatrix{p} = temp(cnt+(1:size(tmatrix{p},1)),:);
  524. cnt = cnt + size(tmatrix{p},1);
  525. end
  526. clear temp;
  527. % construct special regressors matrix for the purposes of the <metric>
  528. % first, construct the run-wise regressors
  529. smatrix = {};
  530. for p=1:length(data)
  531. temp = [];
  532. if opt.wantremovepoly
  533. temp = cat(2,temp,polyregressors{p});
  534. end
  535. if opt.wantremoveextra
  536. temp = cat(2,temp,extraregressors{p});
  537. end
  538. smatrix{p} = temp;
  539. end
  540. % then, separate them using blkdiag
  541. temp = blkdiag(smatrix{:});
  542. cnt = 0;
  543. for p=1:length(data)
  544. smatrix{p} = temp(cnt+(1:size(smatrix{p},1)),:);
  545. cnt = cnt + size(smatrix{p},1);
  546. end
  547. clear temp;
  548. % figure out trainfun and testfun for resampling
  549. switch resamplingmode
  550. case 'full'
  551. trainfun = {@(x) catcell(1,x)};
  552. testfun = {@(x) []};
  553. case 'xval'
  554. trainfun = {};
  555. testfun = {};
  556. for p=1:size(opt.resampling,1)
  557. trainix = find(opt.resampling(p,:) == 1);
  558. testix = find(opt.resampling(p,:) == -1);
  559. if opt.wantresampleruns
  560. trainfun{p} = @(x) catcell(1,x(trainix));
  561. testfun{p} = @(x) catcell(1,x(testix));
  562. else
  563. trainfun{p} = @(x) subscript(catcell(1,x),{trainix ':' ':' ':' ':' ':'}); % HACKY
  564. testfun{p} = @(x) subscript(catcell(1,x),{testix ':' ':' ':' ':' ':'});
  565. end
  566. end
  567. case 'boot'
  568. trainfun = {};
  569. testfun = {};
  570. for p=1:opt.resampling{1}
  571. trainix = [];
  572. for b=1:max(opt.resampling{3})
  573. temp = opt.resampling{3}==b;
  574. trainix = [trainix subscript(find(temp),ceil(rand(1,sum(temp))*sum(temp)))];
  575. end
  576. testix = [];
  577. if opt.wantresampleruns
  578. trainfun{p} = @(x) catcell(1,x(trainix));
  579. testfun{p} = @(x) catcell(1,x(testix));
  580. else
  581. trainfun{p} = @(x) subscript(catcell(1,x),{trainix ':' ':' ':' ':' ':'}); % HACKY
  582. testfun{p} = @(x) subscript(catcell(1,x),{testix ':' ':' ':' ':' ':'});
  583. end
  584. end
  585. end
  586. %%%%%%%%%%%%%%%%%%%%%%%%%%%% PERFORM THE FITTING
  587. % loop over voxels
  588. clear results0;
  589. parfor p=1:vnum
  590. % report
  591. fprintf('*** fitnonlinearmodel: processing voxel %d (%d of %d). ***\n',vxs(p),p,vnum);
  592. vtime = clock; % start time for current voxel
  593. % get data and hack it in
  594. opt2 = opt;
  595. opt2.data = cellfun(@(x) x(:,p),data,'UniformOutput',0);
  596. % get seed and hack it in
  597. if ~isempty(opt2.seed)
  598. assert(~isa(opt2.model,'function_handle')); % sanity check
  599. if isa(opt2.seed,'function_handle')
  600. seed0 = feval(opt2.seed,vxs(p));
  601. else
  602. seed0 = opt2.seed;
  603. end
  604. opt2.model{1}{1} = seed0;
  605. end
  606. % call helper function to do the actual work
  607. results0(p) = fitnonlinearmodel_helper(opt2,stimulus,tmatrix,smatrix,trainfun,testfun);
  608. % report
  609. fprintf('*** fitnonlinearmodel: voxel %d (%d of %d) took %.1f seconds. ***\n', ...
  610. vxs(p),p,vnum,etime(clock,vtime));
  611. end
  612. % consolidate results
  613. results = struct;
  614. results.params = cat(3,results0.params);
  615. results.testdata = cat(2,results0.testdata);
  616. results.modelpred = cat(2,results0.modelpred);
  617. results.modelfit = cat(3,results0.modelfit);
  618. results.trainperformance = cat(1,results0.trainperformance).';
  619. results.testperformance = cat(1,results0.testperformance).';
  620. results.aggregatedtestperformance = cat(2,results0.aggregatedtestperformance);
  621. results.numiters = cat(2,{results0.numiters});
  622. results.resnorms = cat(2,{results0.resnorms});
  623. % kill unwanted outputs
  624. for p=1:length(opt.dontsave)
  625. % if member of dosave, keep it!
  626. if ismember(opt.dontsave{p},opt.dosave)
  627. % if not, then kill it (if it exists)!
  628. else
  629. if isfield(results,opt.dontsave{p})
  630. results = rmfield(results,opt.dontsave{p});
  631. end
  632. end
  633. end
  634. % save results
  635. if wantsave
  636. save(outputfile,'-struct','results','-append');
  637. end
  638. %%%%%%%%%%%%%%%%%%%%%%%%%%%% REPORT
  639. fprintf('*** fitnonlinearmodel: ended at %s (%.1f minutes). ***\n', ...
  640. datestr(now),etime(clock,stime)/60);