-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathpcm_fitModelIndividCrossval.m
322 lines (298 loc) · 13.3 KB
/
pcm_fitModelIndividCrossval.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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
function [T,DD,theta_hat,theta0]=pcm_fitModelIndividCrossval(Y,M,partitionVec,conditionVec,varargin);
% function [T,D,theta_hat]=pcm_fitModelIndividCrossval(Y,M,partitionVec,conditionVec,varargin);
% Fits pattern component model(s) specified in M to data from one (or more)
% subjects individually, using leave-one out crossvalidation within each
% subject.
%==========================================================================
% INPUT:
% Y: [#Conditions x #Voxels]
% Observed/estimated beta regressors from each subject.
% Preferably multivariate noise-normalized beta regressors.
% If it's a cell array, it is assuming multiple subjects, each
% cell containing the data from one subject
%
% M: {#Models} Cell array with structure that defines model(s). Each
% may contain the fields
% .type: Type of the model to be fitted
% 'fixed': Fixed structure without parameter (except scale for each subject)
% 'component': G is a sum of linear components
% 'feature': G=A*A', with A a linear sum of weighted feature components
% 'nonlinear': Nonlinear model with own function to return derivatives
% 'freechol': Free model in Cholesky form
% .numGparams: Scalar that defines the number of parameters
% included in model.
% .theta0: Vector of starting theta parameters to calculate predicted
% model G.
% for more fields see the manual for model specification.
%
% partitionVec: Partition assignment vector
% Could be a cell array of multiple vector if its for multiple
% subjects and the parition structure is different.
% Rows of partitionVec{subj} defin partition assignment of rows of Y{subj}.
% Commonly these are the scanning run #s for beta
% regressors.
% If a single vector is provided, it is assumed to me the
% same for all subjects
% The runs are assume to be labeled 1-numRuns
%
% conditionVec: Condition assignment vector
% for each subject. Rows of conditionVec{subj} define
% condition assignment of rows of Y{subj}.
% If a single vector is provided, it is assumed to me the
% same for all subjects
% If the (elements of) conditionVec are matrices, it is
% assumed to be the design matrix Z, allowing the
% specification individualized models.
%--------------------------------------------------------------------------
% OPTION:
% 'crossvalScheme': Crossvalidation scheme on the different partitions
% 'leaveOneOut': Leave one partition at a time out
% 'leaveTwoOut': Leave two consecutive partitions out
% 'oddeven': Split half by odd and even runs
% 'evaluation': List of evaluation criteria
% 'likelihood': Using log predictive probability (pseudo
% likelihood)
% 'R2': Crossvalidated R^2
% 'R' : Correlation between observed and predicted
% '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.
% 'MaxIteration': Number of max minimization iterations. Default is 1000.
%
% 'S',S : (Cell array of) NxN noise covariance matrices -
% otherwise independence is assumed
% 'fitAlgorithm': Either 'NR' or 'minimize' - provides over-write for
% model specific algorithms
% 'verbose': Optional flag to show display message in the command
% line. Default is 1. Setting to 2 gives more detailed
% feedback on pcm_NR
% 'theta0': Cell array of starting values (same format as theta{m})
%--------------------------------------------------------------------------
% OUTPUT:
% T: Summary crossvalidation results (1 per subject)
% SN: Subject number
% likelihood: crossvalidated likelihood
% noise: Noise parameter
% run: Run parameter (if run = 'random')
% iterations: Number of interations for model fit
% time: Elapsed time in sec
% D: Detailed crossvalidation results (1 per condition)
% fold: Crossvalidation fold
%
% theta_hat{model}: Estimated parameters (model + scaling/noise parameters)
% for individual subjects
runEffect = 'random';
isCheckDeriv = 0;
MaxIteration = 1000;
Iter = [];
verbose = 1;
S = [];
fitAlgorithm = [];
crossvalScheme = 'leaveOneOut';
evaluation = {'likelihood','R2','R'};
theta0 = {};
pcm_vararginoptions(varargin,{'crossvalScheme','fitAlgorithm','runEffect',...
'MaxIteration','verbose','S','evaluation','crossvalScheme','theta0'});
DD=[];
% Number of subejcts
if (~iscell(Y))
Y={Y};
end;
numSubj = numel(Y);
% Number of evaluation criteria
if (~iscell(evaluation))
evaluation={evaluation};
end;
numEval = numel(evaluation);
% Preallocate output structure
T.SN = [1:numSubj]';
% Get the number of models
if (~iscell(M))
M={M};
end;
numModels = numel(M);
% 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);
% Loop over subject and provide inidivdual fits
for s = 1:numSubj
% Set up crossvalidation scheme
if iscell(partitionVec)
pV=partitionVec{s};
else
pV=partitionVec;
end;
part = unique(pV)';
numPart = numel(part);
if ischar(crossvalScheme)
partI={};
switch (crossvalScheme)
case 'leaveOneOut'
for i=1:numPart
partI{i}=part(i);
end;
case 'leaveTwoOut'
for i=1:floor(numPart/2)
partI{i}=part((i-1)*2+[1:2]);
end;
case 'oddEven'
for i=1:2
partI{i}=part(mod(part+i,2)==0);
end;
end;
else
partI = crossvalScheme; % Direct specificiation
end;
numFolds = numel(partI);
% Now loop over models
for m = 1:length(M)
if (verbose)
if isfield(M{m},'name');
fprintf('Fitting Subj: %d model:%s\n',s,M{m}.name);
else
fprintf('Fitting Subj: %d model:%d\n',s,m);
end;
end;
tic;
% Set options
if (~isempty(S))
OPT.S = S;
end;
% Get starting guess for theta if not provided
if (numel(theta0)<m || size(theta0{m},2)<s)
if (isfield(M{m},'theta0'))
th0m = M{m}.theta0(1:M{m}.numGparams);
else
th0m = pcm_getStartingval(M{m},G_hat(:,:,s));
end;
if (strcmp(runEffect,'random'))
theta0{m}(:,s) = [th0m;noise0(s);run0(s)];
else
theta0{m}(:,s) = [th0m;noise0(s)];
end;
end;
% Set starting values
x0 = theta0{m}(:,s);
th = nan(size(x0,1),numFolds);
% Now perform the cross-validation across different partitions
for p=1:numFolds
trainIdx = ~ismember(pV,partI{p});
testIdx = ismember(pV,partI{p});
% Get the data and design matrices for training set
Ytrain=Y{s}(trainIdx,:);
Xtrain=reduce(X{s},trainIdx);
Ztrain=Z{s}(trainIdx,:);
OPT.runEffect = reduce(B{s},trainIdx);
% Get test data and design matrices for the test set
Ytest=Y{s}(testIdx,:);
Xtest=reduce(X{s},testIdx);
Ztest=Z{s}(testIdx,:);
Btest=reduce(B{s},testIdx);
Ntest = size(Ztest,1);
% Perform the initial fit to the training data
switch (M{m}.fitAlgorithm)
case 'minimize' % Use minimize to find maximum liklhood estimate runEffect',B{s});
fcn = @(x) pcm_likelihoodIndivid(x,Ytrain*Ytrain',M{m},Ztrain,Xtrain,P(s),OPT);
l=fcn(x0);
[th(:,p),fX,D.iterations(p,m)] = minimize(x0, fcn, MaxIteration);
D.likelihood_fit(p,m)=-fX(end);
case 'NR'
fcn = @(x) pcm_likelihoodIndivid(x,Ytrain*Ytrain',M{m},Ztrain,Xtrain,P(s),OPT);
[th(:,p),D.likelihood_fit(p,m),D.iterations(p,m)]= pcm_NR(x0,fcn,'verbose',verbose);
end;
x0 = th(:,p);
% Record the stats from fitting
D.SN(p,1) = s;
D.fold(p,1) = p;
D.noise(p,m) = exp(th(M{m}.numGparams+1,p));
if strcmp(runEffect,'random')
D.run(p,m) = exp(th(M{m}.numGparams+2,p));
end;
D.time(p,m) = toc;
% calculate prediction on
[estU,varU] = pcm_estimateU(M{m},th(:,p),Ytrain,Ztrain,Xtrain,'runEffect',OPT.runEffect);
Ypred = Ztest*estU;
Ypredx = Ypred-Xtest*pinv(Xtest)*Ypred;
Ytestx = Ytest-Xtest*pinv(Xtest)*Ytest;
for c = 1:numEval
switch (evaluation{c})
% Under developement
case 'likelihood' % Evaluate log probability under the predictive probability
R = Ytest - Ztest * estU; % Residuals after subtracting prediction
V = Ztest * varU * Ztest'+eye(Ntest)*D.noise(p,m);
if strcmp(runEffect,'random')
V = V + Btest*Btest'*D.run(p,m);
end
iV = inv(V);
if (~isempty(X))
iVX = iV * Xtest;
iVr = iV - iVX*((Xtest'*iVX)\iVX');
else
iVr = iV;
end
% Computation of (restricted) likelihood
ldet = -2* sum(log(diag(chol(iV)))); % Safe computation of the log determinant (V) Thanks to code from D. lu
l = -P(s)/2*(ldet)-0.5*traceABtrans(iVr,R*R');
if (~isempty(X)) % Correct for ReML estimates
l = l - P(s)*sum(log(diag(chol(Xtest'*iV*Xtest)))); % - P/2 log(det(X'V^-1*X));
end
D.likelihood(p,m) = l;
case 'R2' % Predictive R2
D.TSS(p,m)= sum(sum(Ytestx.*Ytestx));
D.RSS(p,m)= sum(sum((Ytestx-Ypredx).^2));
case {'R','Rpool'} % Predictive correlation
D.SS1(p,m) = sum(sum(Ytestx.*Ytestx));
D.SS2(p,m) = sum(sum(Ypredx.*Ypredx));
D.SSC(p,m) = sum(sum(Ypredx.*Ytestx));
end;
end;
% Use last iterations as a parameter starting value
% x0 = th(:,p);
end; % For each partition
theta_hat{m}(:,s)=mean(th,2);
end; % For each model
DD=addstruct(DD,D);
% Summarize results across partitions for each subject
T.noise(s,:)=mean(D.noise);
if strcmp(runEffect,'random')
T.run(s,:) = mean(D.run);
end;
T.time(s,:) = sum(D.time);
T.iterations(s,:) = sum(D.iterations);
for c = 1:numEval
switch (evaluation{c})
case 'likelihood'
T.(evaluation{c})(s,:) = sum(D.(evaluation{c}));
case 'R2'
TSS = sum(D.TSS);
RSS = sum(D.RSS);
T.R2(s,:) = 1-RSS./TSS;
case 'Rpool' % Pool sums-of-squares first, then calculate correlations:
% Warning: This measure is slightly negatively biased for
% noise data, but can perform more stable in model
% comparision
SSC = sum(D.SSC);
SS1 = sum(D.SS1);
SS2 = sum(D.SS2);
T.SS1(s,:)=SS1;
T.SS2(s,:)=SS2;
T.SSC(s,:)=SSC;
T.Rpool(s,:) = SSC./sqrt(SS1.*SS2);
case 'R' % Predictive correlation for each fold - then pool correlation
T.R(s,:) = mean(D.SSC./sqrt(D.SS1.*D.SS2));
end;
end;
end; % for each subject
function Xt=reduce(X,index);
Xt=X(index,:);
Xt=Xt(:,sum(abs(Xt))>0);