-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathpcm_fitModelGroup.m
216 lines (200 loc) · 9.4 KB
/
pcm_fitModelGroup.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
function [T,theta_hat,G_pred,theta0]=pcm_fitModelGroup(Y,M,partitionVec,conditionVec,varargin);
% function [T,theta_hat,G_pred]=pcm_fitModelCrossval(Y,M,partitionVec,conditionVec,varargin);
% Fits pattern component model(s) specified by M to data from a number of
% subjects.
% The model parameters are shared - the noise parameters are not.
% If provided with a G_hat and Sig_hat, it also automatically estimates close
% startingvalues.
%==========================================================================
% INPUT:
% Y: {#Subjects}.[#Conditions x #Voxels]
% Observed/estimated beta regressors from each subject.
% Preferably multivariate noise-normalized beta regressors.
%
% M: cell arrays {#Models} of model(s) to be fitted
% define multiple models if desired. Contains subfields:
% .type: Type of the model to be fitted
% 'fixed': Fixed structure without parameter
% (except scale and noise for each subject)
% 'component': G is a sum of linear components, specified by Gc
% 'feature': G=A*A', with A a linear sum of weighted components Ac
% 'nonlinear': Nonlinear model with own function
% to return G-matrix and derivatives
% 'noiseceiling': Uses the mean estimated
% G-matrix from crossvalidation
% 'freechol': Free model in Cholesky form
% .numGparams: Scalar that defines the number of parameters
% included in model.
% .theta0: Vector of starting values for theta. If not given,
% the function attempts to estimate these from a
% crossvalidated version Values usually estimated from
% observed second-moment matrix. Can estimate
% these parameters using 'pcm_modelpred_free_startingval'
% for more fields see the manual for model specification.
%
% partitionVec: {#Subjects} Cell array with partition assignment vector
% for each subject. Rows of partitionVec{subj} define
% partition assignment of rows of Y{subj}.
% Commonly these are the scanning run #s for beta
% regressors. If 1 vector is given, then partitions are
% assumed to be the same across subjects
%
% conditionVec: {#Subjects} Cell array with condition assignment vector
% for each subject. Rows of conditionVec{subj} define
% condition assignment of rows of Y{subj}. If 1 vector is given,
% then conditions are assumed to be the same across subjects
% If the (elements of) conditionVec are matrices, it is
% assumed to be the design matrix Z, allowing the
% specification individualized models.
%--------------------------------------------------------------------------
% OPTION:
% 'runEffect': How to deal with effects that may be specific to different
% imaging runs:
% 'random': Models variance of the run effect for each subject
% as a seperate random effects parameter.
% 'fixed': Consider run effect a fixed effect, will be removed
% implicitly using ReML (default)
% 'fitScale': Fit additional scaling parameter for each of the
% subjects? Defaults to true. This makes sense, as signal/ noise is not
% the same across subjects.
% However, when you want to test strongly against a null model that
% does not predict any difference between different conditions,
% then the scaling parameter makes the alternative model
% more flexible
% 'isCheckDeriv: Check the derivative accuracy of theta params. Done using
% 'pcm_checkderiv'. This function compares input to finite
% differences approximations. See function documentation.
%
% 'MaxIteration': Number of max minimization iterations. Default is 1000.
%
% 'verbose': Optional flag to show display message in the command
% line (e.g., elapsed time). Default is 1.
%
% 'S',S : Structure of the NxN noise covariance matrix -
% otherwise independence is assumed
%
% 'fitAlgorithm': Either 'NR' or 'minimize' - provides over-write for
% model specific algorithms
%--------------------------------------------------------------------------
% OUTPUT:
% T: Structure with following subfields:
% SN: Subject number
% likelihood: Group-fit likelihood (with subj included)
% noise: Noise Variance
% scale: Scale parameter for each subject (if fitScale set to 1)
% run: Run variance (if runEffect = 'random');
%
% theta_hat: Estimated parameters at the overall fitting (including
% noise, scale, and run parameters). A mx1 cell array
% Gpred: Predicted second moment matrix for the model from group
% fit for each model. A mx1 cell array
runEffect = 'fixed';
isCheckDeriv = 0;
MaxIteration = 1000;
verbose = 1;
fitScale = 1; % Fit an additional scaling parameter for each subject?
S = []; % Structure of noise matrix
fitAlgorithm = []; % Over-write on model specific fit Algorithm
theta0 = [];
scalePrior = 10;
pcm_vararginoptions(varargin,{'runEffect','isCheckDeriv','MaxIteration',...
'verbose','fitScale','S','fitAlgorithm','theta0','scalePrior'});
numSubj = numel(Y);
% Determine number of models
if (~iscell(M))
M={M};
end;
numModels = numel(M);
% Preallocate output structure
T.SN = [1:numSubj]';
T.iterations = zeros(numSubj,1);
T.time = zeros(numSubj,1);
% Determine optimal algorithm for each of the models
if (~isempty(fitAlgorithm))
for m=1:numModels
M{m}.fitAlgorithm = fitAlgorithm;
end;
end;
M = pcm_optimalAlgorithm(M);
% Set up all parameters for the upcoming fit
[Z,B,X,YY,S,N,P,G_hat,noise0,run0]=...
pcm_setUpFit(Y,partitionVec,conditionVec,'runEffect',runEffect,'S',S);
% -----------------------------------------------------
% Perform the overall group fit across all subjects
% -----------------------------------------------------
for m = 1:numModels
if (verbose)
if isfield(M{m},'name');
fprintf('Overall fitting model:%s\n',M{m}.name);
else
fprintf('Overall fitting model:%d\n',m);
end;
end;
tic;
% Get starting guess for theta if not provided
if (~isfield(M{m},'theta0'))
M{m}.theta0 = pcm_getStartingval(M{m},mean(G_hat,3));
end;
% Use normal linear regression to get scaling parameter for the
% subject
switch (M{m}.type)
case 'freedirect'
G0 = pcm_makePD(mean(G_hat,3));
M{m}.numGparams=0;
M{m}.Gc = G0;
otherwise
G0 = pcm_calculateG(M{m},M{m}.theta0);
end;
g0 = G0(:);
if (fitScale)
for s = 1:numSubj
g_hat = G_hat(:,:,s);
g_hat = g_hat(:);
scaling = (g0'*g_hat)/(g0'*g0);
if ((scaling<10e-5)||~isfinite(scaling)); scaling = 10e-5; end; % Enforce positive scaling
scale0(s,m) = log(scaling);
end;
end;
% Put together the vector of starting value
if (numel(theta0)<m)
theta0{m} = [M{m}.theta0;noise0];
if(fitScale)
theta0{m} = [theta0{m};scale0(:,m)];
end;
if (strcmp(runEffect,'random'))
theta0{m} = [theta0{m};run0];
end;
end;
% Set options
OPT.runEffect=B;
OPT.S = S;
OPT.fitScale = fitScale;
OPT.scalePrior = scalePrior;
% Now do the fitting
switch (M{m}.fitAlgorithm)
case 'minimize' % Use minimize to find maximum liklhood estimate runEffect',B{s});
fcn = @(x) pcm_likelihoodGroup(x,YY,M{m},Z,X,P,OPT);
[theta_hat{m},~,T.iterations(:,m)] = minimize(theta0{m}, fcn, MaxIteration);
case 'NR'
fcn = @(x) pcm_likelihoodGroup(x,YY,M{m},Z,X,P,OPT);
[theta_hat{m},~,T.iterations(:,m),T.reg(:,m)] = pcm_NR(theta0{m}, fcn);
otherwise
error('unknown fitting Algorith: %s',M{m}.fitAlgorithm);
end;
% retrieve parameters
T.noise(:,m) = exp(theta_hat{m}(M{m}.numGparams+1:M{m}.numGparams+numSubj));
if (fitScale)
T.scale(:,m) = exp(theta_hat{m}(M{m}.numGparams+numSubj+1:M{m}.numGparams+2*numSubj));
end;
if (strcmp(runEffect,'random'))
T.run(:,m) = exp(theta_hat{m}(M{m}.numGparams+(1+fitScale)*numSubj+1:M{m}.numGparams+(2+fitScale)*numSubj));
end;
G_pred{m} = pcm_calculateG(M{m},theta_hat{m}(1:M{m}.numGparams));
% Compute log-likelihood under the estimated parameters
[~,~,~,T.likelihood(:,m)] = fcn(theta_hat{m});
T.time(:,m) = toc;
% This is an optional check if the dervivate calculation is correct
if (isCheckDeriv)
d = pcm_checkderiv(fcn,theta_hat{m}-0.01,0.0000001);
end;
end;