https://github.com/jdiedrichsen/pcm_toolbox
Raw File
Tip revision: 4e290a8b2c0d0820f868b7bcb60a3da7bb30e6ee authored by Jörn Diedrichsen on 26 April 2023, 01:59:24 UTC
Update pcm_estimateRegression.m
Tip revision: 4e290a8
pcm_groupLikelihood.m
function [negLogLike,dnldtheta,L,dLdtheta] = pcm_likelihoodGroup(theta,YY,M,Z,X,P,varargin);
% function [negLogLike,dnldtheta,L,dLdtheta] = pcm_groupLikelihood(theta,YY,M,Z,X,P,varargin);
% Returns negative log likelihood of the data from a number of subjects (YY) under the
% representational model specified by M.
% The representational model specifies some group-level parameters, i.e the
% structure of the second-moment matrix is supposed to be stable across all
% subjects.
% To link the group prediction to individual second-moment matrices, a
% number of individual-level nuisance parameters are introduced:
%   s:        Scaling of the data
%   sigma2:   Noise variability
% Additionally, if 'runEffect' is set, the effect of each imaging run is
%   also modelled as a random effect, giving the last individual-subject
%   parameter
% INPUT:
%      theta:   Vector of model parameters + subject-specific nuisance parameters
%               1...numGParams           : model parameters
%               numGParams+1... +numSubj : log(scaling parameters)
%               numGParams+numSubj+1...  : log(noise variance)
%               numGParams+numSubj*2+1...: log(run effect variance)
%                                          - used if runEffect is provided
%      YY:      cell array {numSubj}: Y*Y': Outer product of the data (NxN)
%      M:       Model for G (same for the whole group):
%               a. this can either be a fixed KxK matrix (fixed model) or a
%               b. Structure with fields
%                   M.type      = 'fixed','component','squareroot','nonlinear'
%                   M.numGparam = number of free parameters of G
%                   M.modelpred = handle to a fcn [G, dGdtheta] = fcn(theta);
%                   M.Gc        = Component matrices
%                   M.Ac        = Component matrices for matrix square root
%      Z:       cell array {numSubj}: Design matrix for random effects,
%                    variables of interest - often condition to trials (N) matrix
%      X:       cell array {numSubj}: Design matrix for fixed effect,
%                     accounted for through ReML.
%      P:       numSubj x 1 vector: Number of voxels
% VARARGIN:
%      'runEffect',B:  cell array {numSubj} of design matrices for the run effect,
%                   which is modelled as a individual subject-specific random effect.
%      'S',S:       Optional structured noise matrix - default is the identity matrix
%                   S should be a structure-array (number of subjects) with two fields
%                   S.S: noise structure matrix
%                   S.invS: Inverse of noise structure matrix
%      'verbose',0/1: Set verbose mode -
% OUTPUT:
%      negLogLike:  Negative Log likelihood of all subject's data
%                   We use the negative log liklihood to be able to plug the function into
%                   minimize or other optimisation routines.
%      dnldtheta:   Derivative of the negative log-likelihood
%      L:           Log likelihood (not inverted) for all the subject
%      dLdtheta:    Derivate of Log likelihood for each subject
%
%   Joern Diedrichsen, 6/2016, joern.diedrichsen@googlemail.com
%

S         = [];
verbose   = 0;
runEffect = [];
pcm_vararginoptions(varargin,{'S','verbose','runEffect'});

% Get G-matrix and derivative of G-matrix in respect to parameters 
if (isstruct(M))
    [G,dGdtheta] = pcm_calculateG(M,theta(1:M.numGparams)); 
else 
    G=M;
    M=[]; 
    M.numGparams=0; 
end; 

% Loop over subjects
numSubj = length(YY);
for s=1:numSubj
    % Get parameter and sizes
    scaleParam = theta(M.numGparams+s);   % Subject Scaling Parameter
    noiseParam = theta(M.numGparams+numSubj+s);   % Subject Noise Parameter
    N  = size(Z{s},1);
    Gs = G*exp(scaleParam);         % Scale the subject G matrix up by individual scale parameter
    
    % If Run effect is to ne modelled as a random effect - add to G and
    % design matrix
    if (~isempty(runEffect{s}))
        numRuns = size(runEffect{s},2);
        runParam = theta(M.numGparams+numSubj*2+s);    % Subject run effect parameter
        Gs = pcm_blockdiag(Gs,eye(numRuns)*exp(runParam));  % Include run effect in G
        Z{s} = [Z{s} runEffect{s}];                 % Include run effect in design matrix
    else
        numRuns = 0;                                % No run effects modelled
    end;
    
    % Find the inverse of the G-matrix
    Gs = (Gs+Gs')/2;        % Symmetrize
    [u,lam_Gs] = eig(full(Gs));
    dS    = diag(lam_Gs);
    idx = dS>1e-5;
    Zu  = Z{s}*u(:,idx);
    
    % Give warning if G was not invertible
    if (verbose)
        if (any(dS<-1e-5))
            %error('negative eigenvalues in G');
            warning('negative eigenvalues in G: num=%d, min=%d',sum(dS<-1e-5),dS(dS<-1e-5)); % ay
            if isstruct(M)
                if isfield(M,'name') % ay
                    fprintf('\t Model: %s',M.name);
                else
                    fprintf('\t Model: %s',char(M.modelpred));
                end
            end
        end;
    end
    
    % Compute inv(V) over matrix inversion lemma - use particular noise
    % structure if given.
    if (~isempty(S));
        sS = S(s).S; % noise covariance
        iS = S(s).invS; % inverse noise covariance
        iV  = (iS-iS*Zu/(diag(1./dS(idx))*exp(noiseParam)+Zu'*iS*Zu)*Zu'*iS)./exp(noiseParam); % Matrix inversion lemma
    else
        iV  = (eye(N)-Zu/(diag(1./dS(idx))*exp(noiseParam)+Zu'*Zu)*Zu')./exp(noiseParam); % Matrix inversion lemma
    end;
    iV  = real(iV); % sometimes iV gets complex (ay)
    
    % Deal with the fixed effects, if they are present
    if (isempty(X) || isempty(X{s}))
        iVr   = iV;
    else
        iVX   = iV * X{s};
        iVr   = iV - iVX*((X{s}'*iVX)\iVX');
    end;
    
    % Precompute some matrices
    A     = iVr*Z{s};
    B     = YY{s}*A/P(s);
    % Get the derivatives for all the parameters
    for i = 1:M.numGparams
        C{i}  = (A*pcm_blockdiag(dGdtheta(:,:,i),zeros(numRuns)));
        dLdtheta(i,s) = -P(s)/2*(traceABtrans(C{i},Z{s})-traceABtrans(C{i},B))*exp(scaleParam);
    end
    
    % Get the derivatives for the scaling parameters
    indx             = M.numGparams+s;  % Which number parameter is it?
    C{indx}          = A*pcm_blockdiag(G,zeros(numRuns));
    dLdtheta(indx,s) = -P(s)/2*(traceABtrans(C{indx},Z{s})-traceABtrans(C{indx},B))*exp(scaleParam);
    
    % Get the derivatives for the Noise parameters
    indx             = M.numGparams+numSubj+s;  % Which number parameter is it?
    if (isempty(S))
        dLdtheta(indx,s)     = -P(s)/2*traceABtrans(iVr,(speye(N)-YY{s}*iVr/P(s)))*exp(noiseParam);
    else
        dLdtheta(indx,s)     = -P(s)/2*traceABtrans(iVr*S(s).S,(speye(N)-YY{s}*iVr/P(s)))*exp(noiseParam);
    end;
    
    % Get the derivatives for the block parameter
    if (~isempty(runEffect) && ~isempty(runEffect{s}))
        indx             = M.numGparams+numSubj*2+s;  % Which number parameter is it?
        C{indx}          = A*pcm_blockdiag(zeros(size(G,1)),eye(numRuns));
        dLdtheta(indx,s) = -P(s)/2*(traceABtrans(C{indx},Z{s})-traceABtrans(C{indx},B))*exp(runParam);
    end;
    
    % Calculate the overall Likelihood for this subject
    ldet  = -2* sum(log(diag(chol(iV))));        % Safe computation of the log determinant (V) Thanks to code from D. lu
    L(s)     = -P(s)/2*(ldet)-0.5*traceABtrans(iVr,YY{s});
    if (~isempty(X) && ~isempty(X{s})) % Correct for ReML estimates
        L(s) = L(s) - P(s)*sum(log(diag(chol(X{s}'*iV*X{s}))));  % - P/2 log(det(X'V^-1*X));
    end;
end

% Sum over participants
negLogLike = -sum(L);
dnldtheta   = -sum(dLdtheta,2);
back to top