├── .gitignore ├── M_dyn.m ├── M_obs.m ├── M_var.m ├── PG_C.m ├── Q_dyn.m ├── Q_obs.m ├── Q_var.m ├── README.md ├── bootstrap_ci.m ├── bootstrap_dyn.m ├── bootstrap_match.m ├── bootstrap_obs.m ├── bootstrap_var.m ├── eeg_eye_state.mat ├── example.m ├── examples ├── eeg_eye_example.asv ├── eeg_eye_example.m ├── eeg_eye_state.mat └── example.m ├── fast_dyn.m ├── fast_obs.m ├── fast_var.m ├── forecast.m ├── get_covariance.m ├── get_covariance_aux.m ├── get_crosscovariance.m ├── init_dyn.m ├── init_obs.m ├── init_var.m ├── kfs_dyn.m ├── kfs_obs.m ├── myinv.m ├── preproc_dyn.m ├── preproc_obs.m ├── reestimate_dyn.m ├── reestimate_obs.m ├── regfun.m ├── simulate_dyn.m ├── simulate_obs.m ├── simulate_var.m ├── skfs_dyn.m ├── skfs_obs.m ├── skfs_p1_dyn.m ├── skfs_p1_obs.m ├── skfs_p1r1_dyn.m ├── skfs_r1_dyn.m ├── skfs_var.m ├── switch_dyn.m ├── switch_obs.m ├── switch_var.m └── visualize_mts.m /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /M_var.m: -------------------------------------------------------------------------------- 1 | function outpars = M_var(pars,Ms,sum_MCP,sum_MP,sum_MPb,... 2 | sum_Ms2,y,control,equal,fixed,scale,skip) 3 | 4 | 5 | abstol = control.abstol; 6 | reltol = control.reltol; 7 | verbose = control.verbose; 8 | outpars = pars; 9 | [M,T] = size(Ms); 10 | r = size(pars.A,1); 11 | p = size(pars.A,2) / r; 12 | 13 | 14 | 15 | 16 | %=========================================================================% 17 | % Update A % 18 | %=========================================================================% 19 | 20 | 21 | % Case: no fixed coefficient constraints 22 | if isempty(fixed.A) 23 | if equal.A && equal.Q 24 | sum_Pb = sum(sum_MPb,3); 25 | sum_CP = sum(sum_MCP,3); 26 | Ahat = sum_CP / sum_Pb; 27 | if any(isnan(Ahat(:))|isinf(Ahat(:))) 28 | Ahat = sum_CP * pinv(sum_Pb); 29 | end 30 | Ahat = repmat(Ahat,[1,1,M]); 31 | elseif equal.A 32 | % If the A's are all equal but the Q's are not, there is no closed 33 | % form expression for the A and Q's that maximize the Q function. 34 | % In this case, fix the Q's and find the best associated A (ECM) 35 | lhs = zeros(p*r,p*r); 36 | rhs = zeros(r,p*r); 37 | for j=1:M 38 | Qinv_j = myinv(pars.Q(:,:,j)); 39 | lhs = lhs + kron(sum_MPb(:,:,j),Qinv_j); 40 | rhs = rhs + Qinv_j * sum_MCP(:,:,j); 41 | end 42 | rhs = rhs(:); 43 | Ahat = reshape(lhs\rhs,r,p*r); 44 | if any(isnan(Ahat(:))|isinf(Ahat(:))) 45 | Ahat = reshape(pinv(lhs)*rhs,r,p*r); 46 | end 47 | Ahat = repmat(Ahat,[1,1,M]); 48 | else 49 | Ahat = zeros(r,p*r,M); 50 | for j=1:M 51 | A_j = sum_MCP(:,:,j) / sum_MPb(:,:,j); 52 | if any(isnan(A_j(:)) | isinf(A_j(:))) 53 | A_j = sum_MCP(:,:,j) * pinv(sum_MPb(:,:,j)); 54 | end 55 | Ahat(:,:,j) = A_j; 56 | end 57 | end 58 | end 59 | 60 | % Case: fixed coefficient constraints on A --> Vectorize matrices and 61 | % solve associated problem after discarding rows associated with fixed 62 | % coefficients. Recall: there cannot be both fixed coefficient 63 | % constraints *and* equality constraints on A 64 | if ~skip.A && ~isempty(fixed.A) 65 | pr2 = p*r*r; 66 | Ahat = zeros(r,p*r,M); 67 | for j = 1:M 68 | % Linear indices of free coefficients in A(j) 69 | idx = (fixed.A(:,1) > (j-1)*pr2) & (fixed.A(:,1) <= j*pr2); 70 | fixed_Aj = fixed.A(idx,:); 71 | fixed_Aj(:,1) = fixed_Aj(:,1) - (j-1)*pr2; 72 | free = setdiff(1:pr2,fixed_Aj(:,1)); 73 | free = free(:); 74 | Qinv_j = myinv(pars.Q(:,:,j)); 75 | % Matrix problem min(X) trace(W(-2*B1*X' + X*B2*X')) 76 | % (under fixed coefficient constraints) becomes vector problem 77 | % min(x) x' kron(B2,W) x - 2 x' vec(W*B1) 78 | % with X = A(j), x = vec(A(j)), W = Q(j)^(-1), B1 = sum_MCP(j), 79 | % and B2 = sum_MPb(j) (remove fixed entries in x) 80 | mat = kron(sum_MPb(:,:,j),Qinv_j); 81 | vec = Qinv_j * sum_MCP(:,:,j); 82 | A_j = zeros(pr2,1); 83 | A_j(fixed_Aj(:,1)) = fixed_Aj(:,2); 84 | A_j(free) = mat(free,free)\vec(free); 85 | if any(isnan(A_j)|isinf(A_j)) 86 | A_j(free) = pinv(mat(free,free)) * vec(free); 87 | end 88 | Ahat(:,:,j) = reshape(A_j,r,p*r); 89 | end 90 | end 91 | 92 | % Check eigenvalues of estimate and regularize if needed 93 | if ~skip.A 94 | Abig = diag(ones((p-1)*r,1),-r); 95 | for j = 1:M 96 | % Check eigenvalues 97 | Abig(1:r,:,:) = Ahat(:,:,j); 98 | eigval = eig(Abig); 99 | if any(abs(eigval) > scale.A) 100 | if verbose 101 | warning(['Eigenvalues of A%d greater than %f.',... 102 | ' Regularizing.'],j,scale.A) 103 | end 104 | % Case: regularize with no fixed coefficients constraints 105 | c = .999 * scale.A / max(abs(eigval)); 106 | A_j = reshape(Ahat(:,:,j),[r,r,p]); 107 | for l = 1:p 108 | A_j(:,:,l) = c^l * A_j(:,:,l); 109 | end 110 | Ahat(:,:,j) = reshape(A_j,[r,p*r]); 111 | end 112 | if equal.A 113 | Ahat = repmat(Ahat(:,:,1),[1,1,M]); 114 | break 115 | end 116 | end 117 | 118 | % Check that parameter update actually increases Q-function 119 | % If not, keep previous parameter estimate 120 | Qvalold = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 121 | outpars.A = Ahat; 122 | Qval = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 123 | if Qval < Qvalold 124 | outpars.A = pars.A; 125 | end 126 | end 127 | 128 | 129 | 130 | %=========================================================================% 131 | % Update Q % 132 | %=========================================================================% 133 | 134 | 135 | % Unconstrained solution 136 | if ~skip.Q 137 | Qhat = zeros(r,r,M); 138 | sum_M = sum(Ms(:,p+1:T),2); 139 | for j=1:M 140 | if sum_M(j) == 0 141 | Qhat(:,:,j) = eye(r); 142 | continue 143 | end 144 | A_j = outpars.A(:,:,j); 145 | sum_MPj = sum_MP(:,:,j); 146 | sum_MCPj = sum_MCP(:,:,j); 147 | sum_MPbj = sum_MPb(:,:,j); 148 | Q_j = (sum_MPj - (sum_MCPj * A_j.') - ... 149 | (A_j * sum_MCPj.') + A_j * sum_MPbj * A_j.') / sum_M(j); 150 | Qhat(:,:,j) = 0.5 * (Q_j + Q_j'); 151 | end 152 | if equal.Q 153 | Qtmp = zeros(r); 154 | for j = 1:M 155 | Qtmp = Qtmp + (sum_M(j)/(T-p)) * Qhat(:,:,j); 156 | end 157 | Qhat = repmat(Qtmp,1,1,M); 158 | end 159 | 160 | % Enforce fixed coefficient constraints 161 | if ~isempty(fixed.Q) 162 | Qhat(fixed.Q(:,1)) = fixed.Q(:,2); 163 | end 164 | 165 | % Regularize estimate if needed 166 | for j = 1:M 167 | eigval = eig(Qhat(:,:,j)); 168 | if min(eigval) < max(abstol,max(eigval)*reltol) 169 | if verbose 170 | warning(['Q%d ill-conditioned and/or nearly singular.', ... 171 | ' Regularizing.'],j); 172 | end 173 | Qhat(:,:,j) = regfun(Qhat(:,:,j),abstol,reltol); 174 | end 175 | if equal.Q 176 | Qhat = repmat(Qhat(:,:,1),[1,1,M]); 177 | break 178 | end 179 | end 180 | 181 | % Apply fixed coefficient constraints 182 | if ~isempty(fixed.Q) 183 | Qhat(fixed.Q(:,1)) = fixed.Q(:,2); 184 | end 185 | 186 | % Check that estimate Qhat increases Q-function. If not, keep 187 | % estimate from previous iteration 188 | Qvalold = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 189 | outpars.Q = Qhat; 190 | Qval = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 191 | if Qval < Qvalold 192 | outpars.Q = pars.Q; 193 | end 194 | end 195 | 196 | 197 | 198 | %=========================================================================% 199 | % Update mu % 200 | %=========================================================================% 201 | 202 | 203 | if ~skip.mu 204 | if equal.mu && equal.Sigma 205 | muhat = repmat(mean(y(:,1:p),2),1,M); 206 | elseif equal.mu 207 | lhs = zeros(r,r); 208 | rhs = zeros(r,1); 209 | for j=1:M 210 | Sinv_j = myinv(pars.Sigma(:,:,j)); 211 | lhs = lhs + Sinv_j; 212 | rhs = rhs + Sinv_j * y(:,1:p) * Ms(j,1:p)'; 213 | end 214 | muhat = lhs\rhs; 215 | if any(isnan(muhat) | isinf(muhat)) 216 | muhat = pinv(lhs)*rhs; 217 | end 218 | muhat = repmat(muhat,1,M); 219 | else 220 | muhat = zeros(r,M); 221 | for j = 1:M 222 | sum_Mj = sum(Ms(j,1:p)); 223 | if sum_Mj > 0 224 | muhat(:,j) = (y(:,1:p) * Ms(j,1:p)') / sum_Mj; 225 | end 226 | end 227 | end 228 | 229 | % Apply fixed coefficient constraints 230 | if ~isempty(fixed.mu) 231 | muhat(fixed.mu(:,1)) = fixed.mu(:,2); 232 | end 233 | 234 | % Check that muhat increases Q-function. If not, keep estimate from 235 | % previous iteration 236 | Qvalold = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 237 | outpars.mu = muhat; 238 | Qval = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 239 | if Qval < Qvalold 240 | outpars.mu = pars.mu; 241 | end 242 | end 243 | 244 | 245 | 246 | %=========================================================================% 247 | % Update Sigma % 248 | %=========================================================================% 249 | 250 | 251 | if ~skip.Sigma 252 | Sigmahat = zeros(r,r,M); 253 | % sum(t=1:p) P(S(t)=j|y(1:T)) 254 | sum_M = sum(Ms(:,1:p),2); 255 | for j = 1:M 256 | if sum_M(j) == 0 257 | Sigmahat(:,:,j) = eye(r); 258 | continue 259 | end 260 | B_j = y(:,1:p) - repmat(outpars.mu(:,j),1,p); 261 | S_j = (B_j * diag(Ms(j,1:p)) * B_j') / sum_M(j); 262 | Sigmahat(:,:,j) = 0.5 * (S_j + S_j'); 263 | end 264 | if equal.Sigma 265 | Stmp = zeros(r); 266 | for j = 1:M 267 | Stmp = Stmp + (sum_M(j)/p) * Sigmahat(:,:,j); 268 | end 269 | Sigmahat = repmat(Stmp,1,1,M); 270 | end 271 | 272 | % The above estimates of Sigma(j) have rank p at most (VAR order). 273 | % If p < r (time series dimension), they are not invertible --> 274 | % set off-diagonal terms to zero 275 | if p < r 276 | for j = 1:M 277 | Sigmahat(:,:,j) = diag(diag(Sigmahat(:,:,j))); 278 | end 279 | end 280 | 281 | % Enforce fixed coefficient constraints 282 | if ~isempty(fixed.Sigma) 283 | Sigmahat(fixed.Sigma(:,1)) = fixed.Sigma(:,2); 284 | end 285 | 286 | % Regularize estimate if needed 287 | for j = 1:M 288 | eigval = eig(Sigmahat(:,:,j)); 289 | if min(eigval) < max(abstol,max(eigval)*reltol) 290 | % if verbose 291 | % warning(['Sigma%d ill-conditioned and/or nearly singular.', ... 292 | % ' Regularizing.'],j); 293 | % end 294 | Sigmahat(:,:,j) = regfun(Sigmahat(:,:,j),abstol,reltol); 295 | end 296 | if equal.Sigma 297 | Sigmahat = repmat(Sigmahat(:,:,1),[1,1,M]); 298 | break 299 | end 300 | end 301 | 302 | % Enforce fixed coefficient constraints 303 | if ~isempty(fixed.Sigma) 304 | Sigmahat(fixed.Sigma(:,1)) = fixed.Sigma(:,2); 305 | end 306 | 307 | % Check that Sigmahat increases Q-function. If not, keep 308 | % parameter estimate from previous iteration 309 | Qvalold = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 310 | outpars.Sigma = Sigmahat; 311 | Qval = Q_var(outpars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 312 | if Qval < Qvalold 313 | outpars.Sigma = pars.Sigma; 314 | end 315 | end 316 | 317 | 318 | 319 | %=========================================================================% 320 | % Update Pi % 321 | %=========================================================================% 322 | 323 | if ~skip.Pi 324 | outpars.Pi = Ms(:,1); 325 | end 326 | 327 | 328 | 329 | %=========================================================================% 330 | % Update Z % 331 | %=========================================================================% 332 | 333 | 334 | if ~skip.Z 335 | outpars.Z = sum_Ms2 ./ repmat(sum(sum_Ms2,2),1,M); 336 | if ~isempty(fixed.Z) 337 | outpars.Z(fixed.Z(:,1)) = fixed.Z(:,2); 338 | end 339 | end 340 | 341 | 342 | -------------------------------------------------------------------------------- /PG_C.m: -------------------------------------------------------------------------------- 1 | function [C,err] = PG_C(C0,sum_xy,sum_P,R,scale,fixed,maxit,tol) 2 | %-------------------------------------------------------------------------- 3 | % 4 | % ESTIMATION OF OBSERVATION MATRIX IN SWITCHING STATE-SPACE MODEL 5 | % UNDER FIXED COEFFICIENT AND/OR SCALE CONSTRAINTS 6 | % 7 | % GOAL 8 | % Minimize trace( R^(-1) (C sum_P C' - 2 sum_xy' C') ) under scale constraints 9 | % and fixed coefficient constraints on C by projected gradient method 10 | % 11 | % USAGE 12 | % [C,err] = PG_quad_scale(C0,sum_P,sum_xy,R,scale,fixed,maxit,tol) 13 | % 14 | % INPUTS 15 | % C0: Starting value for projected gradient method 16 | % sum_P: Matrix defining objective function (must be positive definite) 17 | % sum_xy: Matrix defining objective function 18 | % R: Matrix defining objective function (must be positive definite) 19 | % scale: the columns of C must have Euclidean norm equal to 'scale' 20 | % fixed: optional matrix with two columns. If specified, 1st column must 21 | % contain indices of fixed coefficients in C, 2nd column must contain 22 | % corresponding fixed values 23 | % maxit: maximum number of iterations in algorithm, default = 1e4 24 | % tol: tolerance for convergence (algorithm terminates if relative change 25 | % in objective function between two successive iterations is less 26 | % than tol). Default = 1e-6 27 | % 28 | % OUTPUT 29 | % C: best solution satisfying the problem constraints if any found, otherwise starting value 30 | % err: true if a solution satisfying the problem constraints has been found, false otherwise 31 | %-------------------------------------------------------------------------- 32 | 33 | 34 | %@@@@@ Initialization @@@@@% 35 | 36 | % Check number of input arguments 37 | narginchk(6,8); 38 | 39 | % Set control parameters to default values if not specified 40 | if nargin == 6 41 | maxit = 1e4; 42 | end 43 | if nargin <= 7 44 | tol = 1e-6; 45 | end 46 | 47 | % Logical flags for constraints 48 | fixed_cnstr = ~isempty(fixed); 49 | scale_cnstr = ~isempty(scale); 50 | 51 | % Tolerance for testing scale equality 52 | if scale_cnstr 53 | eps = 1e-8 * min(scale,1); 54 | end 55 | 56 | % Starting point 57 | C = C0; 58 | 59 | % Objective function value 60 | objective = zeros(maxit,1); 61 | Rinv = myinv(R); 62 | sum_yx = sum_xy'; 63 | c = trace(sum_P\(sum_yx'*R*sum_yx)); 64 | % Add constant c to objective function to make it always non-negative 65 | 66 | % Check whether problem constraints are met. If yes, calculate objective. 67 | % Otherwise, set objective to Inf. 68 | fixed_test = ~fixed_cnstr || all(C(fixed(:,1)) == fixed(:,2)); 69 | scale_test = ~scale_cnstr || all(abs(sqrt(sum(C.^2))-scale) <= eps); 70 | if ~(fixed_test && scale_test) 71 | objective_best = Inf; 72 | else 73 | objective_best = trace(R*(C*sum_P-2*sum_yx)*C') + c; 74 | end 75 | % Best solution to date 76 | Cbest = C; 77 | 78 | % Lipschitz constant of gradient of objective function (up to factor 2) 79 | beta = norm(sum_P,'fro') * norm(Rinv,'fro'); 80 | 81 | 82 | 83 | %@@@@@ MAIN LOOP @@@@@% 84 | 85 | for i = 1:maxit 86 | 87 | % Gradient step 88 | grad = Rinv * (C * sum_P - sum_yx); 89 | C = C - (1/beta) * grad; 90 | 91 | % (Alternating) projection step 92 | for j = 1:maxit 93 | % Apply fixed coefficient constraints 94 | if fixed_cnstr 95 | C(fixed(:,1)) = fixed(:,2); 96 | end 97 | % Check scale constraint, terminate projection step if true 98 | % and apply it otherwise 99 | if scale_cnstr 100 | nrmC = sqrt(sum(C.^2)); 101 | scale_test = all(abs(nrmC-scale) <= eps); 102 | if scale_test 103 | break 104 | else 105 | C = C ./ nrmC * scale; 106 | end 107 | end 108 | end 109 | 110 | % Calculate objective 111 | % Note: since projection step guarantees that scale constraint is met, 112 | % no need to check it again here 113 | fixed_test = ~fixed_cnstr || all(C(fixed(:,1)) == fixed(:,2)); 114 | if ~fixed_test 115 | objective(i) = Inf; 116 | else 117 | objective(i) = trace(R*(C*sum_P-2*sum_yx)*C') + c; 118 | end 119 | 120 | % Update best solution 121 | if objective(i) < objective_best 122 | Cbest = C; 123 | objective_best = objective(i); 124 | end 125 | 126 | % Monitor convergence 127 | if i >= 10 128 | test1 = abs(objective(i) - objective(i-1)) <= tol * objective(i-1); % convergence attained 129 | test2 = objective(i) == 0; % best solution (=unconstrained) attained 130 | if test1 || test2 131 | break 132 | end 133 | end 134 | end 135 | 136 | C = Cbest; 137 | err = isinf(objective_best); 138 | 139 | -------------------------------------------------------------------------------- /Q_dyn.m: -------------------------------------------------------------------------------- 1 | % Q-function of EM algorithm (Conditional expectation of complete-data 2 | % log-likelihood given observed data) for switching dynamics model 3 | 4 | % Initial state vector X(t)=(x(1),...,x(2-p)) ~ 5 | % N((mu_j,...,mu_j),diag(S_j,...,S_j)) conditional on S(1)=j 6 | 7 | function Qval = Q_dyn(pars,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 8 | sum_P,sum_xy,sum_yy) 9 | 10 | [M,T] = size(Ms); 11 | [N,r] = size(pars.C); 12 | p = size(pars.A,2) / size(pars.A,1); 13 | 14 | % C/R contribution 15 | [R_chol,err] = cholcov(pars.R); 16 | if err 17 | Qval = -Inf; 18 | return 19 | end 20 | Q_CR = -0.5*T*N*log(2*pi) - T * sum(log(diag(R_chol))) ... 21 | - 0.5*trace(pars.R\(sum_yy - 2 * pars.C * sum_xy + pars.C * sum_P * pars.C.')); 22 | 23 | % A/Q contribution 24 | Q_AQ = -0.5*(T-1)*r*log(2*pi); 25 | for j=1:M 26 | sum_Mj = sum(Ms(j,2:T)); 27 | if sum_Mj == 0 28 | continue 29 | end 30 | A_j = pars.A(:,:,j); 31 | Q_j = pars.Q(:,:,j); 32 | [Qj_chol,err] = cholcov(Q_j); 33 | if err 34 | Qval = -Inf; 35 | return 36 | end 37 | sum_MPj = sum_MP(:,:,j); 38 | sum_MPbj = sum_MPb(:,:,j); 39 | sum_MCPj = sum_MCP(:,:,j); 40 | Q_AQ = Q_AQ - sum_Mj * sum(log(diag(Qj_chol))) - ... 41 | 0.5 * trace(Q_j \ (sum_MPj - 2 * sum_MCPj * A_j.' ... 42 | + A_j * sum_MPbj * A_j.')); 43 | end 44 | 45 | % mu/Sigma contribution 46 | Q_muSigma = -0.5*p*r*log(2*pi); 47 | for j = 1:M 48 | if Ms(j,1) == 0 49 | continue 50 | end 51 | mu_j = repmat(pars.mu(:,j),p,1); 52 | Sigma_j = kron(eye(p),pars.Sigma(:,:,j)); 53 | [Sigmaj_chol,err] = cholcov(Sigma_j); 54 | if err 55 | Qval = -Inf; 56 | return 57 | end 58 | Mx0j = Mx0(:,j); 59 | Q_muSigma = Q_muSigma - Ms(j,1) * sum(log(diag(Sigmaj_chol))) ... 60 | - 0.5 * trace(Sigma_j \ ... 61 | (MP0(:,:,j) - 2 * (Mx0j * mu_j') + Ms(j,1) * (mu_j * mu_j'))); 62 | 63 | end 64 | 65 | % Pi/Z contribution 66 | logPi = log(pars.Pi); 67 | logPi(isinf(logPi)) = 0; 68 | Q_Pi = dot(Ms(:,1),logPi); 69 | logZ = log(pars.Z(:)); 70 | logZ(isinf(logZ)) = 0; 71 | Q_Z = dot(sum_Ms2(:),logZ); 72 | 73 | % Total 74 | Qval = Q_AQ + Q_CR + Q_muSigma + Q_Pi + Q_Z; 75 | end 76 | -------------------------------------------------------------------------------- /Q_obs.m: -------------------------------------------------------------------------------- 1 | function Qval = Q_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,... 2 | sum_P,sum_Pb,sum_yy,x0) 3 | 4 | % Q-function of EM algorithm (Conditional expectation of complete-data 5 | % log-likelihood given observed data) for switching observations model 6 | 7 | 8 | % Model and data dimensions 9 | [r,N,M] = size(sum_Mxy); 10 | p = size(sum_Pb,1) / size(sum_P,1); 11 | T = size(Ms,2); 12 | 13 | % Expand parameters mu and Sigma 14 | mu = repmat(pars.mu,[p,1]); 15 | Stmp = zeros(p*r,p*r,M); 16 | for j = 1:M 17 | Stmp(:,:,j) = kron(eye(p),pars.Sigma(:,:,j)); 18 | end 19 | Sigma = Stmp; 20 | 21 | % C/R contribution 22 | R = pars.R; 23 | Q_CR = -0.5*T*N*log(2*pi) - 0.5*T*log(det(R)) - 0.5 * trace(R\sum_yy); 24 | for j=1:M 25 | C_j = pars.C(:,:,j); 26 | sum_Mxyj = sum_Mxy(:,:,j); 27 | sum_MPj = sum_MP(:,:,j); 28 | Q_CR = Q_CR - 0.5 * trace(R \ ... 29 | (-2 * C_j * sum_Mxyj + (C_j * sum_MPj * C_j.'))); 30 | end 31 | 32 | % A/Q contribution 33 | Q_AQ = -0.5*(T-1)*M*p*r*log(2*pi); 34 | for j = 1:M 35 | A_j = pars.A(:,:,j); 36 | Q_j = pars.Q(:,:,j); 37 | sum_Pj = sum_P(:,:,j); 38 | sum_Pbj = sum_Pb(:,:,j); 39 | sum_CPj = sum_CP(:,:,j); 40 | Q_AQ = Q_AQ - 0.5 * (T-1) * log(det(Q_j)) ... 41 | - 0.5 * trace(Q_j \ ... 42 | (sum_Pj - 2 * sum_CPj * A_j.' + (A_j * sum_Pbj * A_j.'))); 43 | end 44 | 45 | % mu/Sigma contribution 46 | Q_muSigma = -0.5*M*p*r*log(2*pi); 47 | for j = 1:M 48 | mu_j = mu(:,j); 49 | Sigma_j = Sigma(:,:,j); 50 | P0_j = P0(:,:,j); 51 | Q_muSigma = Q_muSigma - 0.5 * log(det(Sigma_j)) ... 52 | - 0.5 * trace(Sigma_j \ ... 53 | (P0_j - 2 * x0(:,j) * mu_j.' + (mu_j * mu_j.'))); 54 | end 55 | 56 | % Pi/Z contribution 57 | logPi = log(pars.Pi); 58 | logPi(isinf(logPi)) = 0; 59 | Q_Pi = dot(Ms(:,1),logPi); 60 | logZ = log(pars.Z(:)); 61 | logZ(isinf(logZ)) = 0; 62 | Q_Z = dot(sum_Ms2(:),logZ); 63 | 64 | % Total 65 | Qval = Q_AQ + Q_CR + Q_muSigma + Q_Pi + Q_Z; 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /Q_var.m: -------------------------------------------------------------------------------- 1 | % Q-function of EM algorithm (Conditional expectation of complete-data 2 | % log-likelihood given observed data) for switching VAR model 3 | 4 | % Initial state vectors x(1),...,x(p) independent conditional on S(1:p) 5 | % and x(t)|S(t)=j ~ N(mu(j),Sigma(j)) 6 | 7 | function Qval = Q_var(pars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,x) 8 | 9 | M = numel(pars.Pi); 10 | [N,T] = size(x); 11 | p = size(pars.A,2) / N; 12 | 13 | % A/Q contribution 14 | Q_AQ = -0.5*(T-p)*N*log(2*pi); 15 | for j=1:M 16 | sum_Mj = sum(Ms(j,p+1:T)); 17 | A_j = pars.A(:,:,j); 18 | Q_j = pars.Q(:,:,j); 19 | try 20 | Qj_chol = chol(Q_j); 21 | catch 22 | Qval = Inf; 23 | return 24 | end 25 | sum_MPj = sum_MP(:,:,j); 26 | sum_MPbj = sum_MPb(:,:,j); 27 | sum_MCPj = sum_MCP(:,:,j); 28 | Q_AQ = Q_AQ - sum_Mj * sum(log(diag(Qj_chol))) - ... 29 | 0.5 * trace(Q_j \ (sum_MPj - 2 * sum_MCPj * A_j.' ... 30 | + A_j * sum_MPbj * A_j.')); 31 | end 32 | 33 | % mu/Sigma contribution 34 | Q_muSigma = -0.5*p*N*log(2*pi); 35 | for j = 1:M 36 | sum_Mj = sum(Ms(j,1:p)); 37 | if sum_Mj == 0 38 | continue 39 | end 40 | try 41 | Sigmaj_chol = chol(pars.Sigma(:,:,j)); 42 | catch 43 | Qval = Inf; 44 | return 45 | end 46 | xc = Sigmaj_chol \ ... 47 | ((x(:,1:p) - repmat(pars.mu(:,j),1,p)) * diag(sqrt(Ms(j,1:p)))); 48 | Q_muSigma = Q_muSigma - sum_Mj * sum(log(diag(Sigmaj_chol))) ... 49 | - 0.5 * sum(xc(:).^2); 50 | end 51 | 52 | % Pi/Z contribution 53 | logPi = log(pars.Pi); 54 | logPi(isinf(logPi)) = 0; 55 | Q_Pi = dot(Ms(:,1),logPi); 56 | logZ = log(pars.Z(:)); 57 | logZ(isinf(logZ)) = 0; 58 | Q_Z = dot(sum_Ms2(:),logZ); 59 | 60 | % Total 61 | Qval = Q_AQ + Q_muSigma + Q_Pi + Q_Z; 62 | 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # switch-ssm 2 | Markov-Switching State-Space Models 3 | 4 | This is a suite of Matlab functions for fitting Markov-switching state-space models (SSMs) to multivariate time series data by maximum likelihood. We consider three switching SSMs: switching dynamics, switching observations, and swiching vector autoregressive (VAR). The maximum likelihood estimator is calculated via an approximate EM algorithm. (Exact calculations are not tractable because of exponential number of possible regime histories, M^T with M the number of states/regimes for the Markov chain and T the length of the time series.) To keep calculations tractable, we use the filtering/smoothing algorithm of Kim (1994) in the E-step of the EM algorithm. 5 | 6 | ## Functions 7 | The user-level functions of the package are of the form `xxx_yyy`, where the prefix `xxx` indicates what the function does and the suffix `yyy` indicates which model the function applies to. 8 | The possible prefixes are: 9 | - `init`: find starting values for EM algorithm 10 | - `switch`: fit EM algorithm 11 | - `fast`: fit EM algorithm with fixed regime sequence 12 | - `reestimate`: estimate model parameters by least squares with fixed regime sequence 13 | - `bootstrap`: perform parametric bootstrap 14 | - `simulate`: simulate a realization of the model 15 | 16 | The possible suffixes are: 17 | - `dyn`: switching dynamics model 18 | - `obs`: switching observations model 19 | - `var`: swiching vector autoregressive model 20 | 21 | NEW: the function `bootstrap_ci` builds (pointwise) bootstrap confidence intervals for all model parameters and for the stationary covariance, correlation, and partial correlation in all three switching SSMs (dyn, obs, var). Basic, percentile, and normal bootstrap CIs are used. 22 | 23 | ## Authors 24 | **Author:** David Degras 25 | **Contributors:** Chee Ming Ting @CheeMingTing, Siti Balqis Samdin 26 | 27 | ## References 28 | - Degras, D., Ting, C.M., and Ombao, H.: Markov-Switching State-Space Models with Applications to Neuroimaging. _Computational Statistics and Data Analysis_ 174 (2022) 29 | - Kim, C.J.: Dynamic linear models with Markov-switching. _J. Econometrics_ 60(1-2), 1–22 (1994) 30 | - Kim, C.J., Nelson, C.R.: State-Space Models with Regime Switching: Classical and Gibbs-Sampling Approaches with Applications. _The MIT Press_ (1999) 31 | - Murphy, K.P.: Switching Kalman filters. Tech. rep., _University of California Berkeley_ (1998) 32 | -------------------------------------------------------------------------------- /bootstrap_ci.m: -------------------------------------------------------------------------------- 1 | function ci = bootstrap_ci(parsboot,pars,level,lagmax) 2 | 3 | %-------------------------------------------------------------------------% 4 | % 5 | % Title: Bootstrap confidence intervals for switching state-space models 6 | % 7 | % Purpose: BOOTSTRAP_CI takes as input bootstrap replicates of the 8 | % maximum likelihood estimator (MLE) and produces pointwise 9 | % bootstrap confidence intervals (CI) for all model parameters in 10 | % each regime as well as for the stationary autocorrelation, 11 | % covariance, and correlation of the observed time series in each 12 | % regime 13 | % 14 | % Usage: ci = bootstrap_ci(parsboot,pars,level,lagmax) 15 | % 16 | % Inputs: parboots - structure containing bootstrap replicates of the 17 | % MLE, typically, the result of a call to one of the 18 | % functions bootstrap_dyn, bootstrap_obs, or bootstrap_var 19 | % par - structure containing the MLE, typically, the result of a 20 | % call to switch_dyn, switch_obs, or switch_var 21 | % level - pointwise confidence level of CIs. Default = 0.95 22 | % lagmax - maximum lag for the autocorrelation 23 | % 24 | % Outputs: ci - structure with fields 'A', 'C', ... (all model parameters) 25 | % and 'ACF', 'COV', 'COR', 'PCOR' (stationary autocorrelation, 26 | % covariance, correlation, and partial correlation). Each 27 | % field has 3 subfields 'percentile', 'basic', and 'normal' 28 | % indicating the CI method. Each of these has in turn 2 29 | % subfields 'lo' and 'up' containing the lower and upper 30 | % confidence bounds for the target parameter. The output 31 | % structure also contains the input 'level'. 32 | % 33 | % Author: David Degras, University of Massachusetts Boston 34 | % 35 | %-------------------------------------------------------------------------% 36 | 37 | 38 | 39 | % Number of bootstraps 40 | B = size(parsboot.A,5); 41 | 42 | % Confidence level 43 | if ~exist('level','var') 44 | level = 0.95; 45 | else 46 | assert(level > 0 && level < 1) 47 | end 48 | q = norminv((1+level)/2); 49 | 50 | % Maximum lag for autocorrelation 51 | if ~exist('lagmax','var') 52 | lagmax = 50; 53 | end 54 | 55 | % Model dimensions and type 56 | [r,~,~,M] = size(pars.A); 57 | if ~isfield(pars,'C') || isempty(pars.C) 58 | model = 'var'; 59 | elseif ismatrix(pars.C) 60 | model = 'dyn'; 61 | else 62 | model = 'obs'; 63 | end 64 | if strcmp(model,'var') 65 | N = r; 66 | else 67 | N = size(pars.C,1); 68 | end 69 | 70 | % Output structure 71 | ci = struct('A',[], 'C',[], 'Q',[], 'R',[], 'mu',[], 'Sigma',[], ... 72 | 'Pi',[], 'Z',[], 'ACF',[], 'COV',[], 'COR',[], 'PCOR',[]); 73 | fname = fieldnames(ci); 74 | 75 | % Bootstrap CIs for model parameters 76 | for i = 1:8 77 | f = fname{i}; 78 | if ~isfield(pars,f) || isempty(pars.(f)) 79 | continue 80 | end 81 | ndim = ndims(parsboot.(f)); 82 | mean_boot = mean(parsboot.(f),ndim,'omitNaN'); 83 | sd_boot = std(parsboot.(f),1,ndim,'omitNaN'); 84 | loqt_boot = quantile(parsboot.(f),(1-level)/2,ndim); 85 | upqt_boot = quantile(parsboot.(f),(1+level)/2,ndim); 86 | ci.(f).percentile.lo = loqt_boot; 87 | ci.(f).percentile.up = upqt_boot; 88 | ci.(f).basic.lo = 2 * mean_boot - upqt_boot; 89 | ci.(f).basic.up = 2 * mean_boot - loqt_boot; 90 | ci.(f).normal.lo = 2 * pars.(f) - mean_boot - q * sd_boot; 91 | ci.(f).normal.up = 2 * pars.(f) - mean_boot + q * sd_boot; 92 | end 93 | 94 | % MLEs of stationary autocorrelation, covariance, and correlation 95 | stationary = get_covariance(pars,lagmax,0); 96 | ACF = stationary.ACF; 97 | COV = stationary.COV; 98 | COR = stationary.COR; 99 | PCOR = stationary.PCOR; 100 | 101 | % Bootstrap distribution of stationary autocorrelation, covariance, and 102 | % correlation 103 | ACFboot = NaN(N,lagmax+1,M,B); 104 | COVboot = NaN(N,N,M,B); 105 | CORboot = NaN(N,N,M,B); 106 | PCORboot = NaN(N,N,M,B); 107 | parsb = struct('A',[], 'C',[], 'Q',[], 'R',[]); 108 | for b = 1:B 109 | parsb.A = parsboot.A(:,:,:,:,b); 110 | parsb.Q = parsboot.Q(:,:,:,b); 111 | if strcmp(model,'dyn') 112 | parsb.C = parsboot.C(:,:,b); 113 | parsb.R = parsboot.R(:,:,b); 114 | elseif strcmp(model,'obs') 115 | parsb.C = parsboot.C(:,:,:,b); 116 | parsb.R = parsboot.R(:,:,b); 117 | end 118 | stationaryboot = get_covariance(parsb,lagmax,0); 119 | ACFboot(:,:,:,b) = stationaryboot.ACF; 120 | COVboot(:,:,:,b) = stationaryboot.COV; 121 | CORboot(:,:,:,b) = stationaryboot.COR; 122 | PCORboot(:,:,:,b) = stationaryboot.PCOR; 123 | end 124 | 125 | % Bootstrap CIs for stationary autocorrelation, covariance, and correlation 126 | for i = 9:12 127 | f = fname{i}; 128 | switch i 129 | case 9 130 | mle = ACF; boot = ACFboot; 131 | case 10 132 | mle = COV; boot = COVboot; 133 | case 11 134 | mle = COR; boot = CORboot; 135 | case 12 136 | mle = PCOR; boot = PCORboot; 137 | end 138 | mean_boot = mean(boot,4,'omitNaN'); 139 | sd_boot = std(boot,1,4,'omitNaN'); 140 | loqt_boot = quantile(boot,(1-level)/2,4); 141 | upqt_boot = quantile(boot,(1+level)/2,4); 142 | ci.(f).percentile.lo = loqt_boot; 143 | ci.(f).percentile.up = upqt_boot; 144 | ci.(f).basic.lo = 2 * mean_boot - upqt_boot; 145 | ci.(f).basic.up = 2 * mean_boot - loqt_boot; 146 | ci.(f).normal.lo = 2 * mle - mean_boot - q * sd_boot; 147 | ci.(f).normal.up = 2 * mle - mean_boot + q * sd_boot; 148 | end 149 | 150 | ci.level = level; 151 | 152 | -------------------------------------------------------------------------------- /bootstrap_dyn.m: -------------------------------------------------------------------------------- 1 | function [outpars,LL] = ... 2 | bootstrap_dyn(pars,T,B,opts,control,equal,fixed,scale,parallel,match) 3 | 4 | %-------------------------------------------------------------------------% 5 | % 6 | % Title: Parametric bootstrap in regime-switching state-space models 7 | % (switching dynamics) 8 | % 9 | % Purpose: This function performs parametric bootstrap of the switching 10 | % dynamics model 11 | % y(t) = C x(t) + w(t) 12 | % x(t) = A(1,S(t)) x(t-1) + ... + A(p,S(t)) x(t-p) + v(t) 13 | % where y indicates the observed measurement vector, x the 14 | % unbserved state vector, and S the current (unobserved) regime. 15 | % The terms v and w are noise vectors. All model parameters are 16 | % (re-)estimated by maximum likelihood. ML estimation is 17 | % implemented via the EM algorithm. 18 | % 19 | % Usage: [outpars,LL] = bootstrap_dyn(pars,T,B,control,equal,... 20 | % fixed,scale,parallel,match) 21 | % 22 | % Inputs: pars - structure of estimated model parameters with fields 'A', 23 | % 'C','Q','R','mu','Sigma','Pi', and 'Z'. Typically, this 24 | % structure is obtaining by calling init_dyn, switch_dyn, 25 | % fast_dyn, or reestimate_dyn 26 | % T - Time series length 27 | % B - Number of bootstrap replicates (default = 500) 28 | % control - optional struct variable with fields: 29 | % 'eps': tolerance for EM termination; default = 1e-8 30 | % 'ItrNo': number of EM iterations; default = 100 31 | % 'beta0': initial inverse temperature parameter for 32 | % deterministic annealing; default = 1 33 | % 'betarate': decay rate for temperature; default = 1 34 | % 'safe': if true, regularizes variance matrices in 35 | % switching Kalman filtering to be well-conditioned 36 | % before inversion. If false, no regularization (faster 37 | % but less safe) 38 | % 'abstol': absolute tolerance for eigenvalues before 39 | % inversion of covariance matrices (eigenvalues less 40 | % than abstol are set to this value) 41 | % 'reltol': relative tolerance for eigenvalues before 42 | % inversion of covariance matrices (eigenvalues less 43 | % than (reltol * largest eigenvalue) are set to this 44 | % value) 45 | % equal - optional structure with fields: 46 | % 'A': if true, estimates of transition matrices A(l,j) 47 | % are equal across regimes j=1,...,M. Default = false 48 | % 'C': if true, observation matrices C(j) are equal across 49 | % regimes (default = false) 50 | % 'Q': if true, estimates of innovation matrices Q(j) are 51 | % equal across regimes. Default = false 52 | % 'mu': if true, estimates of initial mean state vectors 53 | % mu(j) are equal across regimes. Default = true 54 | % 'Sigma': if true, estimates of initial variance matrices 55 | % Sigma(j) are equal across regimes. Default = true 56 | % fixed - optional struct variable with fields 'A','C','Q','R', 57 | % 'mu','Sigma'. If not empty, each field must be an array 58 | % of the same dimension as the parameter. Numeric values 59 | % in the array are interpreted as fixed coefficients 60 | % whereas NaN's represent free coefficients. For example, 61 | % a diagonal structure for 'R' would be specified as 62 | % fixed.R = diag(NaN(N,1)). 63 | % scale - optional structure with fields: 64 | % 'A': upper bound on norm of eigenvalues of A matrices. 65 | % Must be in (0,1) to guarantee stationarity of state 66 | % process. 67 | % 'C': value of the (Euclidean) column norms of the 68 | % matrices C(j). Must be positive. 69 | % match - parameter to use to match the bootstrap replicates to 70 | % the maximum likelihood estimate: 'A', 'AQ', 'COV', 71 | % 'COR'. Default = 'COV' 72 | % 73 | % 74 | % Outputs: outpars - struct with fields 75 | % 'A': Bootstrap distribution of A (size rxrxpxMxB) 76 | % 'C': Bootstrap distribution of C (size NxrxB) 77 | % 'Q': Bootstrap distribution of Q (size rxrxMxB) 78 | % 'R': Bootstrap distribution of R (size NxNxB) 79 | % 'mu': Bootstrap distribution of mu (size rxMxB) 80 | % 'Sigma': Bootstrap distribution of Sigma (size rxrxMxB) 81 | % 'Pi': Bootstrap distribution of Pi (size MxB) 82 | % 'Z': Bootstrap distribution of Z (size MxMxB) 83 | % LL - Bootstrap distribution of attained log-likelihood (1xB) 84 | % 85 | % Author: David Degras, david.degras@umb.edu 86 | % University of Massachusetts Boston 87 | % 88 | %-------------------------------------------------------------------------% 89 | 90 | 91 | % Check number of arguments 92 | narginchk(3,10); 93 | 94 | % Initialize missing arguments if needed 95 | if ~exist('B','var') || isempty(B) 96 | B = 500; 97 | end 98 | if ~exist('control','var') || ~isstruct(control) 99 | control = struct('verbose',false); 100 | end 101 | if isfield(control,'verbose') 102 | verbose = control.verbose; 103 | else 104 | verbose = false; 105 | end 106 | if ~exist('equal','var') 107 | equal = []; 108 | end 109 | if ~exist('fixed','var') 110 | fixed = []; 111 | end 112 | if ~exist('scale','var') 113 | scale = []; 114 | end 115 | if ~exist('parallel','var') || ~islogical(parallel) 116 | parallel = true; 117 | end 118 | if ~exist('opts','var') 119 | opts = []; 120 | end 121 | if ~exist('match','var') || isempty(match) 122 | match = 'COV'; 123 | end 124 | assert(ismember(match,{'A','AQ','COV','COR','no'})) 125 | 126 | % Model dimensions 127 | M = numel(pars.Pi); 128 | [N,r] = size(pars.C); 129 | p = size(pars.A,3); 130 | 131 | % Bootstrap estimates 132 | Aboot = NaN(r,r,p,M,B); 133 | Cboot = NaN(N,r,B); 134 | Qboot = NaN(r,r,M,B); 135 | Rboot = NaN(N,N,B); 136 | muboot = NaN(r,M,B); 137 | Sigmaboot = NaN(r,r,M,B); 138 | Piboot = NaN(M,B); 139 | Zboot = NaN(M,M,B); 140 | LLboot = NaN(B,1); 141 | warning('off'); 142 | 143 | % Set up parallel pool if needed 144 | if parallel 145 | pool = gcp('nocreate'); 146 | if isempty(pool) 147 | pool = gcp; 148 | end 149 | poolsize = pool.NumWorkers; 150 | control.verbose = false; 151 | else 152 | poolsize = 0; 153 | end 154 | 155 | % Initialize progress bar if required 156 | if verbose 157 | parfor_progress(B); 158 | end 159 | 160 | 161 | parfor (b=1:B, poolsize) 162 | 163 | % Resample data by parametric bootstrap 164 | % Try to ensure that each regime occurs at least once 165 | count = 0; Meff = 0; 166 | while count < 20 && Meff < M 167 | count = count + 1; 168 | [y,S] = simulate_dyn(pars,T); 169 | Meff = numel(unique(S)); 170 | end 171 | 172 | % Run EM algorithm 173 | try 174 | pars0 = init_dyn(y,M,p,r,opts,control,equal,fixed,scale); 175 | [~,~,~,~,~,~,parsboot,LL] = ... 176 | switch_dyn(y,M,p,r,pars0,control,equal,fixed,scale); 177 | catch 178 | continue 179 | end 180 | 181 | % Store results 182 | Aboot(:,:,:,:,b) = parsboot.A; 183 | Cboot(:,:,b) = parsboot.C; 184 | Qboot(:,:,:,b) = parsboot.Q; 185 | Rboot(:,:,b) = parsboot.R; 186 | muboot(:,:,b) = parsboot.mu; 187 | Sigmaboot(:,:,:,b) = parsboot.Sigma; 188 | Piboot(:,b) = parsboot.Pi; 189 | Zboot(:,:,b) = parsboot.Z; 190 | LLboot(b) = max(LL); 191 | 192 | % Display progress if required 193 | if verbose && parallel 194 | parfor_progress; 195 | end 196 | end 197 | 198 | outpars = struct('A',Aboot, 'C',Cboot, 'Q',Qboot, 'R',Rboot, 'mu',muboot, ... 199 | 'Sigma',Sigmaboot, 'Pi',Piboot, 'Z',Zboot); 200 | LL = LLboot; 201 | 202 | % Match bootstrap replicates to MLE 203 | if ~strcmp(match,'no') 204 | outpars = bootstrap_match(outpars,pars,match); 205 | end 206 | 207 | if verbose && parallel 208 | parfor_progress(0); 209 | end 210 | 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /bootstrap_match.m: -------------------------------------------------------------------------------- 1 | function outboot = bootstrap_match(parsboot,pars,target) 2 | 3 | %-------------------------------------------------------------------------% 4 | % 5 | % Title: Match bootstrap replicates to maximum likelihood estimate (MLE) 6 | % 7 | % Purpose: Permute the regime-specific components of each bootstrap 8 | % estimate so as to best match the components of the MLE 9 | % according to a certain target 10 | % 11 | % Usage: outboot = match_bootstrap(parsboot,pars,target) 12 | % 13 | % Inputs: parsboot - structure of bootstrapped parameter estimates, 14 | % typically the result of a call to bootstrap_dyn, 15 | % bootstrap_var, or bootstrap_obs 16 | % pars - structure of MLE, typically the result of a call to 17 | % switch_dyn, switch_var, or switch_obs 18 | % target - parameter(s) or functions thereof to base the matching 19 | % on: 'A', 'C', 'AQ' (both A and Q), 'COV', and 'COR'. The 20 | % last two are the stationary covariance and correlation of 21 | % the observed time series in each regime. 22 | % 23 | % 24 | % Outputs: outboot - structure of matched bootstrap replicates with the 25 | % same fields as 'parsboot': 26 | % 'A': Bootstrap distribution of A (size rxrxpxMxB) 27 | % 'C': Bootstrap distribution of C if available (size NxrxB) 28 | % 'Q': Bootstrap distribution of Q (size rxrxMxB) 29 | % 'R': Bootstrap distribution of R (size NxNxB) 30 | % 'mu': Bootstrap distribution of mu (size rxMxB) 31 | % 'Sigma': Bootstrap distribution of Sigma (size rxrxMxB) 32 | % 'Pi': Bootstrap distribution of Pi (size MxB) 33 | % 'Z': Bootstrap distribution of Z (size MxMxB) 34 | % 35 | % Author: David Degras, david.degras@umb.edu 36 | % University of Massachusetts Boston 37 | % 38 | %-------------------------------------------------------------------------% 39 | 40 | narginchk(2,3) 41 | 42 | % Initialize target if needed 43 | if nargin == 2 44 | target = 'COV'; 45 | end 46 | 47 | % Check validity of argument 'target' 48 | trgt_list = {'A','C','AQ','COV','COR'}; 49 | assert(ismember(target,trgt_list)); 50 | 51 | % Argument dimensions 52 | [~,~,~,M,B] = size(parsboot.A); 53 | N = []; 54 | if isfield(pars,'C') 55 | N = size(pars.C,1); 56 | end 57 | 58 | % Initialize output 59 | outboot = parsboot; 60 | 61 | % Trivial case 62 | if M == 1 63 | return 64 | end 65 | 66 | % Select and reshape target 67 | trgt_val = []; 68 | switch target 69 | case 'A' 70 | trgt_val = pars.A; 71 | case 'C' 72 | if ~isfield(pars,'C') 73 | error('Field ''C'' missing in argument ''pars''') 74 | end 75 | N = size(pars.C,1); 76 | if size(pars.C,3) ~= M 77 | error(['Number of matrices ''C'' (%d) incompatible ', ... 78 | 'with number of regimes (%d)'],size(pars.C,3),M) 79 | end 80 | trgt_val = zeros(N,N,M); 81 | for j = 1:M 82 | C_j = pars.C(:,:,j); 83 | trgt_val(:,:,j) = (C_j / (C_j' * C_j)) * C_j'; 84 | end 85 | case 'AQ' 86 | trgt_val = [reshape(pars.A,[],M) ; reshape(pars.Q,[],M)]; 87 | case 'COV' 88 | stationary = get_covariance(pars,0,0); 89 | trgt_val = stationary.COV; 90 | % [~,~,trgt_val] = get_covariance(pars,0,0); 91 | case 'COR' 92 | stationary = get_covariance(pars,0,0); 93 | trgt_val = stationary.COR; 94 | end 95 | trgt_val = reshape(trgt_val,[],M); 96 | 97 | % Determine type of model 98 | if ~isfield(pars,'C') || isempty(pars.C) 99 | model = 'var'; 100 | elseif ismatrix(pars.C) 101 | model = 'dyn'; 102 | else 103 | model = 'obs'; 104 | end 105 | 106 | % Match bootstrapped estimates to target 107 | P = perms(1:M); 108 | nperm = size(P,1); 109 | parb = struct('A',[], 'C',[], 'Q',[], 'R',[]); 110 | dist = zeros(1,nperm); 111 | for b = 1:B 112 | % Reshape bootstrap estimate 113 | boot_val = []; 114 | switch target 115 | case 'A' 116 | boot_val = parsboot.A(:,:,:,:,b); 117 | case 'C' 118 | boot_val = zeros(N,N,M); 119 | for j = 1:M 120 | C_j = parsboot.C(:,:,j,b); 121 | boot_val(:,:,j) = (C_j / (C_j' * C_j)) * C_j'; 122 | end 123 | case 'AQ' 124 | boot_val = [reshape(parsboot.A(:,:,:,:,b),[],M) ; ... 125 | reshape(parsboot.Q(:,:,:,b),[],M)]; 126 | case 'COV' 127 | parb.A = parsboot.A(:,:,:,:,b); 128 | parb.Q = parsboot.Q(:,:,:,b); 129 | if strcmp(model,'dyn') 130 | parb.C = parsboot.C(:,:,b); 131 | parb.R = parsboot.R(:,:,b); 132 | elseif strcmp(model,'obs') 133 | pars.C = parsboot.C(:,:,:,b); 134 | parb.R = parsboot.R(:,:,b); 135 | end 136 | stationary = get_covariance(parb,0,0); 137 | boot_val = stationary.COV; 138 | case 'COR' 139 | parb.A = parsboot.A(:,:,:,:,b); 140 | parb.Q = parsboot.Q(:,:,:,b); 141 | if strcmp(model,'dyn') 142 | parb.C = parsboot.C(:,:,b); 143 | parb.R = parsboot.R(:,:,b); 144 | elseif strcmp(model,'obs') 145 | pars.C = parsboot.C(:,:,:,b); 146 | parb.R = parsboot.R(:,:,b); 147 | end 148 | stationary = get_covariance(parb,0,0); 149 | boot_val = stationary.COR; 150 | end 151 | boot_val = reshape(boot_val,[],M); 152 | 153 | % Calculate distances between bootstrap estimates and targets 154 | for i = 1:nperm 155 | dist(i) = norm(trgt_val-boot_val(:,P(i,:)),1); 156 | end 157 | [~,i] = min(dist); 158 | if ~isequal(P(i,:),1:M) 159 | outboot.A(:,:,:,:,b) = parsboot.A(:,:,:,P(i,:),b); 160 | outboot.Q(:,:,:,b) = parsboot.Q(:,:,P(i,:),b); 161 | if strcmp(model,'obs') 162 | outboot.C(:,:,:,b) = parsboot.C(:,:,P(i,:),b); 163 | end 164 | outboot.mu(:,:,b) = parsboot.mu(:,P(i,:),b); 165 | outboot.Sigma(:,:,:,b) = parsboot.Sigma(:,:,P(i,:),b); 166 | outboot.Pi(:,b) = parsboot.Pi(P(i,:),b); 167 | outboot.Z(:,:,b) = parsboot.Z(P(i,:),P(i,:),b); 168 | end 169 | end 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /bootstrap_obs.m: -------------------------------------------------------------------------------- 1 | function [outpars,LL] = ... 2 | bootstrap_obs(pars,T,B,opts,control,equal,fixed,scale,parallel,match) 3 | 4 | %-------------------------------------------------------------------------% 5 | % 6 | % Title: Parametric bootstrap in regime-switching state-space models 7 | % (switching observations) 8 | % 9 | % Purpose: BOOTSTRAP_OBS performs parametric bootstrap of the maximum 10 | % likelihood estimator (MLE) in the switching observations 11 | % model 12 | % y(t) = C(S(t)) x(t,S(t)) + w(t) 13 | % x(t,j) = A(1,j) x(t-1,j) + ... + A(p,j) x(t-p,j) + v(t,j) 14 | % where y indicates the observed measurement vector, x the 15 | % unbserved state vector, and S the (unobserved) regime. 16 | % The terms v and w are noise vectors. 17 | % 18 | % Usage: [outpars,LL]... 19 | % = bootstrap_obs(pars,T,B,opts,control,equal,... 20 | % fixed,scale,parallel,match) 21 | % 22 | % Inputs: pars - structure of estimated model parameters with fields 'A', 23 | % 'C','Q','R','mu','Sigma','Pi', and 'Z'. Typically, this 24 | % structure is obtaining by calling init_obs, switch_obs, 25 | % fast_obs, or reestimate_obs 26 | % T - Time series length 27 | % B - Number of bootstrap replicates (default = 500) 28 | % control - optional struct variable with fields: 29 | % 'eps': tolerance for EM termination; default = 1e-8 30 | % 'ItrNo': number of EM iterations; default = 100 31 | % 'beta0': initial inverse temperature parameter for 32 | % deterministic annealing; default = 1 33 | % 'betarate': decay rate for temperature; default = 1 34 | % 'safe': if true, regularizes variance matrices in 35 | % switching Kalman filtering to be well-conditioned 36 | % before inversion. If false, no regularization (faster 37 | % but less safe) 38 | % 'abstol': absolute tolerance for eigenvalues before 39 | % inversion of covariance matrices (eigenvalues less 40 | % than abstol are set to this value) 41 | % 'reltol': relative tolerance for eigenvalues before 42 | % inversion of covariance matrices (eigenvalues less 43 | % than (reltol * largest eigenvalue) are set to this 44 | % value) 45 | % equal - optional structure with fields: 46 | % 'A': if true, estimates of transition matrices A(l,j) 47 | % are equal across regimes j=1,...,M. Default = false 48 | % 'C': if true, observation matrices C(j) are equal across 49 | % regimes (default = false) 50 | % 'Q': if true, estimates of innovation matrices Q(j) are 51 | % equal across regimes. Default = false 52 | % 'mu': if true, estimates of initial mean state vectors 53 | % mu(j) are equal across regimes. Default = true 54 | % 'Sigma': if true, estimates of initial variance matrices 55 | % Sigma(j) are equal across regimes. Default = true 56 | % fixed - optional struct variable with fields 'A','C','Q','R', 57 | % 'mu','Sigma'. If not empty, each field must be an array 58 | % of the same dimension as the parameter. Numeric values 59 | % in the array are interpreted as fixed coefficients 60 | % whereas NaN's represent free coefficients. For example, 61 | % a diagonal structure for 'R' would be specified as 62 | % fixed.R = diag(NaN(N,1)). 63 | % scale - optional structure with fields: 64 | % 'A': upper bound on norm of eigenvalues of A matrices. 65 | % Must be in (0,1) to guarantee stationarity of state 66 | % process. 67 | % 'C': value of the (Euclidean) column norms of the 68 | % matrices C(j). Must be positive. 69 | % match - parameter to use to match the bootstrap replicates to 70 | % the maximum likelihood estimate: 'A', 'C', 'AQ', 'COV', 71 | % 'COR' 72 | % 73 | % 74 | % Outputs: outpars - struct with fields 75 | % 'A': Bootstrap distribution of A (size rxrxpxMxB) 76 | % 'C': Bootstrap distribution of C (size NxrxMxB) 77 | % 'Q': Bootstrap distribution of Q (size rxrxMxB) 78 | % 'R': Bootstrap distribution of R (size NxNxB) 79 | % 'mu': Bootstrap distribution of mu (size rxMxB) 80 | % 'Sigma': Bootstrap distribution of Sigma (size rxrxMxB) 81 | % 'Pi': Bootstrap distribution of Pi (size MxB) 82 | % 'Z': Bootstrap distribution of Z (size MxMxB) 83 | % LL - Bootstrap distribution of attained log-likelihood (1xB) 84 | % 85 | % Author: David Degras, david.degras@umb.edu 86 | % University of Massachusetts Boston 87 | % 88 | %-------------------------------------------------------------------------% 89 | 90 | 91 | 92 | 93 | % Check number of arguments 94 | narginchk(3,10); 95 | 96 | % Initialize missing arguments if needed 97 | if ~exist('B','var') 98 | B = 500; 99 | end 100 | if ~exist('control','var') || ~isstruct(control) 101 | control = struct('verbose',false); 102 | end 103 | if isfield(control,'verbose') 104 | verbose = control.verbose; 105 | else 106 | verbose = false; 107 | end 108 | if ~exist('equal','var') 109 | equal = []; 110 | end 111 | if ~exist('fixed','var') 112 | fixed = []; 113 | end 114 | if ~exist('scale','var') 115 | scale = []; 116 | end 117 | if ~exist('parallel','var') || isempty(parallel) 118 | parallel = true; 119 | end 120 | if ~exist('opts','var') 121 | opts = []; 122 | end 123 | if ~exist('match','var') || isempty(match) 124 | match = 'COV'; 125 | end 126 | assert(ismember(match,{'A','AQ','C','COV','COR','no'})) 127 | 128 | % Model dimensions 129 | N = size(pars.C,1); 130 | [r,~,p,M] = size(pars.A); 131 | 132 | % Bootstrap estimates 133 | Aboot = NaN(r,r,p,M,B); 134 | Cboot = NaN(N,r,M,B); 135 | Qboot = NaN(r,r,M,B); 136 | Rboot = NaN(N,N,B); 137 | muboot = NaN(r,M,B); 138 | Sigmaboot = NaN(r,r,M,B); 139 | Piboot = NaN(M,B); 140 | Zboot = NaN(M,M,B); 141 | LLboot = NaN(B,1); 142 | warning('off') 143 | 144 | % Set up parallel pool if needed 145 | if parallel 146 | pool = gcp('nocreate'); 147 | if isempty(pool) 148 | pool = gcp; 149 | end 150 | poolsize = pool.NumWorkers; 151 | control.verbose = false; 152 | else 153 | poolsize = 0; 154 | end 155 | 156 | % Initialize progress bar if required 157 | if verbose && parallel 158 | parfor_progress(B); 159 | end 160 | 161 | % MAIN LOOP 162 | parfor (b=1:B, poolsize) 163 | 164 | % Resample data by parametric bootstrap 165 | % Try to ensure that each regime occurs at least once 166 | count = 0; Meff = 0; 167 | while count < 20 && Meff < M 168 | count = count + 1; 169 | [y,S] = simulate_obs(pars,T); 170 | Meff = numel(unique(S)); 171 | end 172 | 173 | % EM algorithm 174 | try 175 | pars0 = init_obs(y,M,p,r,opts,control,equal,fixed,scale); 176 | [~,~,~,~,~,~,parsboot,LL] = ... 177 | switch_obs(y,M,p,r,pars0,control,equal,fixed,scale); 178 | catch 179 | continue 180 | end 181 | Aboot(:,:,:,:,b) = parsboot.A; 182 | Cboot(:,:,:,b) = parsboot.C; 183 | Qboot(:,:,:,b) = parsboot.Q; 184 | Rboot(:,:,b) = parsboot.R; 185 | muboot(:,:,b) = parsboot.mu; 186 | Sigmaboot(:,:,:,b) = parsboot.Sigma; 187 | Piboot(:,b) = parsboot.Pi; 188 | Zboot(:,:,b) = parsboot.Z; 189 | LLboot(b) = max(LL); 190 | 191 | % Display progress if required 192 | if verbose && parallel 193 | parfor_progress; 194 | end 195 | end 196 | 197 | outpars = struct('A',Aboot, 'C',Cboot, 'Q',Qboot, 'R',Rboot, 'mu',muboot, ... 198 | 'Sigma',Sigmaboot, 'Pi',Piboot, 'Z',Zboot); 199 | LL = LLboot; 200 | 201 | % Match bootstrap replicates to MLE 202 | if ~strcmp(match,'no') 203 | outpars = bootstrap_match(outpars,pars,match); 204 | end 205 | 206 | if verbose && parallel 207 | parfor_progress(0); 208 | end 209 | 210 | 211 | -------------------------------------------------------------------------------- /bootstrap_var.m: -------------------------------------------------------------------------------- 1 | function [outpars,LL] = ... 2 | bootstrap_var(pars,T,B,opts,control,equal,fixed,scale,parallel,match) 3 | 4 | %-------------------------------------------------------------------------% 5 | % 6 | % Title: Parametric bootstrap in regime-switching state-space models 7 | % (switching vector autoregressive model) 8 | % 9 | % Purpose: BOOTSTRAP_VAR performs parametric bootstrap of the maximum 10 | % likelihood estimator (MLE) in the switching vector 11 | % autoregresssive (VAR) model 12 | % x(t) = A(1,S(t)) x(t-1) + ... + A(p,S(t)) x(t-p) + v(t,S(t)) 13 | % where x(t) is the observed measurement vector, S(t) is a latent 14 | % variable indicating the current (unobserved) regime, and 15 | % v(t,S(t)) is a noise term. 16 | % 17 | % Usage: [outpars,LL] = bootstrap_var(pars,T,B,opts,control,equal,... 18 | % fixed,scale,parallel,match) 19 | % 20 | % Inputs: pars - structure of estimated model parameters with fields 'A', 21 | % 'Q','mu','Sigma','Pi', and 'Z'. Typically, this 22 | % structure is obtaining by calling init_var, switch_var, 23 | % fast_var, or reestimate_var 24 | % T - Time series length 25 | % B - Number of bootstrap replicates (default = 500) 26 | % control - optional struct variable with fields: 27 | % eps tolerance for EM termination; default = 1e-8 28 | % 'init': starting point for EM algorithm 29 | % 'ItrNo': number of EM iterations; default = 100 30 | % 'beta0': initial inverse temperature parameter for 31 | % deterministic annealing; default = 1 32 | % 'betarate': decay rate for temperature; default = 1 33 | % 'safe': if true, regularizes variance matrices in 34 | % switching Kalman filtering to be well-conditioned 35 | % before inversion. If false, no regularization (faster 36 | % but less safe) 37 | % 'abstol': absolute tolerance for eigenvalues before 38 | % inversion of covariance matrices (eigenvalues less 39 | % than abstol are set to this value) 40 | % 'reltol': relative tolerance for eigenvalues before 41 | % inversion of covariance matrices (eigenvalues less 42 | % than (reltol * largest eigenvalue) are set to this 43 | % value) 44 | % equal - optional structure with fields: 45 | % 'A': if true, estimates of transition matrices A(l,j) 46 | % are equal across regimes j=1,...,M. Default = false 47 | % 'Q': if true, estimates of innovation matrices Q(j) are 48 | % equal across regimes. Default = false 49 | % 'mu': if true, estimates of initial mean state vectors 50 | % mu(j) are equal across regimes. Default = true 51 | % 'Sigma': if true, estimates of initial variance matrices 52 | % Sigma(j) are equal across regimes. Default = true 53 | % fixed - optional struct variable with fields 'A','Q','mu', 54 | % 'Sigma', 'Pi', 'Z'. If not empty, each field must be an 55 | % array of the same dimension as the parameter. Numeric 56 | % values in the array are interpreted as fixed coefficients 57 | % whereas NaN's represent free (unconstrained) coefficients. 58 | % scale - optional structure with field: 59 | % 'A': upper bound on norm of eigenvalues of A matrices. 60 | % Must be in (0,1) to guarantee stationarity of state 61 | % process. 62 | % match - parameter to use to match the bootstrap replicates to 63 | % the maximum likelihood estimate across regimes: 'A', 64 | % 'AQ', 'COV', 'COR', or 'no' for no matching (not recommended) 65 | % 66 | % 67 | % Outputs: outpars - struct with fields 68 | % 'A': Bootstrap distribution of A (size rxrxpxMxB) 69 | % 'Q': Bootstrap distribution of Q (size rxrxMxB) 70 | % 'mu': Bootstrap distribution of mu (size rxMxB) 71 | % 'Sigma': Bootstrap distribution of Sigma (size rxrxMxB) 72 | % 'Pi': Bootstrap distribution of Pi (size MxB) 73 | % 'Z': Bootstrap distribution of Z (size MxMxB) 74 | % LL - Bootstrap distribution of attained log-likelihood (1xB) 75 | % 76 | % 77 | % Author: David Degras, University of Massachusetts Boston 78 | % 79 | %-------------------------------------------------------------------------% 80 | 81 | 82 | % Check number of arguments 83 | narginchk(3,10); 84 | 85 | % Initialize missing arguments if needed 86 | if ~exist('B','var') 87 | B = 500; 88 | end 89 | if ~exist('control','var') || ~isstruct(control) 90 | control = struct('verbose',false); 91 | end 92 | if isfield(control,'verbose') 93 | verbose = control.verbose; 94 | else 95 | verbose = false; 96 | end 97 | if ~exist('equal','var') 98 | equal = []; 99 | end 100 | if ~exist('fixed','var') 101 | fixed = []; 102 | end 103 | if ~exist('scale','var') 104 | scale = []; 105 | end 106 | if ~exist('opts','var') 107 | opts = []; 108 | end 109 | if ~exist('parallel','var') || isempty(parallel) 110 | parallel = true; 111 | end 112 | if ~exist('match','var') || isempty(match) 113 | match = 'COV'; 114 | end 115 | assert(ismember(match,{'A','AQ','COV','COR','no'})) 116 | 117 | 118 | % Model dimensions 119 | [r,~,p,M] = size(pars.A); 120 | 121 | % Bootstrap estimates 122 | Aboot = zeros(r,r,p,M,B); 123 | Qboot = zeros(r,r,M,B); 124 | muboot = zeros(r,M,B); 125 | Sigmaboot = zeros(r,r,M,B); 126 | Piboot = zeros(M,B); 127 | Zboot = zeros(M,M,B); 128 | LLboot = zeros(1,B); 129 | warning('off'); 130 | 131 | % Set up parallel pool if needed 132 | if parallel 133 | pool = gcp('nocreate'); 134 | if isempty(pool) 135 | pool = gcp; 136 | end 137 | poolsize = pool.NumWorkers; 138 | control.verbose = false; 139 | else 140 | poolsize = 0; 141 | end 142 | 143 | % Initialize progress bar if required 144 | if verbose && parallel 145 | parfor_progress(B); 146 | end 147 | 148 | % Set starting value for EM algorithm if provided 149 | pars0 = []; 150 | if isfield(control,'init') 151 | pars0 = control.init; 152 | end 153 | 154 | 155 | parfor (b=1:B, poolsize) 156 | 157 | % Resample data by parametric bootstrap 158 | % Ensure that each regime occurs at least once 159 | count = 0; Meff = 0; 160 | y = []; 161 | while count < 20 && Meff < M 162 | count = count + 1; 163 | [y,S] = simulate_var(pars,T); 164 | Meff = numel(unique(S)); 165 | end 166 | 167 | % Run EM algorithm 168 | pars0b = pars0; 169 | if isempty(pars0b) 170 | try 171 | pars0b = init_var(y,M,p,opts,control,equal,fixed,scale); 172 | catch 173 | continue 174 | end 175 | end 176 | try 177 | [~,~,~,~,parsboot,LL] = ... 178 | switch_var(y,M,p,pars0b,control,equal,fixed,scale); 179 | catch 180 | continue 181 | end 182 | 183 | % Store results 184 | Aboot(:,:,:,:,b) = parsboot.A; 185 | Qboot(:,:,:,b) = parsboot.Q; 186 | muboot(:,:,b) = parsboot.mu; 187 | Sigmaboot(:,:,:,b) = parsboot.Sigma; 188 | Piboot(:,b) = parsboot.Pi; 189 | Zboot(:,:,b) = parsboot.Z; 190 | LLboot(b) = max(LL); 191 | 192 | % Display progress if required 193 | if verbose && parallel 194 | parfor_progress; 195 | end 196 | end 197 | 198 | outpars = struct('A',Aboot, 'Q',Qboot, 'mu',muboot, 'Sigma',Sigmaboot, ... 199 | 'Pi',Piboot, 'Z',Zboot); 200 | LL = LLboot; 201 | 202 | % Match bootstrap replicates to MLE 203 | if ~strcmp(match,'no') 204 | outpars = bootstrap_match(outpars,pars,match); 205 | end 206 | 207 | if verbose && parallel 208 | parfor_progress(0); 209 | end 210 | 211 | end 212 | 213 | -------------------------------------------------------------------------------- /eeg_eye_state.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddegras/switch-ssm/fb600733936049cc2186a7d776b0f5337e4ba724/eeg_eye_state.mat -------------------------------------------------------------------------------- /examples/eeg_eye_example.asv: -------------------------------------------------------------------------------- 1 | % Load data 2 | load('eeg_eye_state.mat') 3 | 4 | % Visualize EEG signals 5 | visualize_mts(eeg,channel) 6 | 7 | % Fit standard (non-switching) VAR model 8 | M = 1; p = 6; S = ones(1,T); scale = []; % struct('A',.98); 9 | pars = fast_var(eeg,M,p,S,[],[],[],scale); 10 | 11 | % Visualize transition and noise variance matrices A and Q 12 | tiledlayout(1,2) 13 | nexttile 14 | imagesc(reshape(pars.A,N,p*N)) 15 | colorbar; title("A"); yticks(1:N); yticklabels(channel) 16 | nexttile 17 | imagesc(pars.Q) 18 | colorbar; title("Q"); yticks(1:N); yticklabels(channel) 19 | 20 | % Subtract common stationary component from data 21 | e = eeg; 22 | e(:,1:p) = e(:,1:p) - pars.mu; 23 | for l = 1:p 24 | e(:,p+1:T) = e(:,p+1:T) - pars.A(:,:,l) * eeg(:,p+1-l:T-l); 25 | end 26 | 27 | % visualize_mts(e,channel,2) 28 | %% 29 | % Fit switching VAR to residuals 30 | M = 6; % number of regimes 31 | % Initialization parameters: length of segments, number of random starts 32 | % for K-means clustering 33 | opts = struct('len',500,'Replicates',50); 34 | % EM parameters: maximum number of iterations, initial inverse temperature 35 | % and change rate for DAEM 36 | control = struct('ItrNo',300,'beta0',.7,'betarate',1.02); 37 | pars2 = init_var(e,M,p,opts,control,[],[],scale); % EM initialization 38 | [~,Ms,~,Shat,pars2,LL] = switch_var(e,M,p,pars2,control,[],[],scale); % EM 39 | 40 | %% 41 | 42 | figure(1) 43 | tiledlayout(4,4); 44 | for j = 1:M 45 | nexttile 46 | imagesc(reshape(pars2.A(:,:,:,j),N,p*N)) 47 | yticklabels(channel) 48 | title(sprintf("A (Regime %d)",j)) 49 | colorbar 50 | end 51 | for j = 1:M 52 | nexttile 53 | imagesc(pars2.Q(:,:,j)) 54 | title(sprintf("Q (Regime %d)",j)) 55 | colorbar 56 | end 57 | 58 | 59 | tabulate(Shat) % brain functional connectivity regimes 60 | tabulate(state) % eye status: 0 = open 1 = closed 61 | twoway = crosstab(Shat,state) 62 | disp(crosstab(Shat,state)) 63 | 64 | figure(2) 65 | plot(Shat,'*') 66 | 67 | -------------------------------------------------------------------------------- /examples/eeg_eye_example.m: -------------------------------------------------------------------------------- 1 | % Load data 2 | load('eeg_eye_state.mat') 3 | 4 | % Visualize EEG signals 5 | visualize_mts(eeg,channel) 6 | 7 | % Fit standard (non-switching) VAR model 8 | M = 1; p = 6; S = ones(1,T); scale = []; % struct('A',.98); 9 | pars = fast_var(eeg,M,p,S,[],[],[],scale); 10 | 11 | % Visualize transition and noise variance matrices A and Q 12 | tiledlayout(1,2) 13 | nexttile 14 | imagesc(reshape(pars.A,N,p*N)) 15 | colorbar; title("A"); yticks(1:N); yticklabels(channel) 16 | nexttile 17 | imagesc(pars.Q) 18 | colorbar; title("Q"); yticks(1:N); yticklabels(channel) 19 | 20 | % Subtract common stationary component from data 21 | e = eeg; 22 | e(:,1:p) = e(:,1:p) - pars.mu; 23 | for l = 1:p 24 | e(:,p+1:T) = e(:,p+1:T) - pars.A(:,:,l) * eeg(:,p+1-l:T-l); 25 | end 26 | 27 | % visualize_mts(e,channel,2) 28 | %% 29 | % Fit switching VAR to residuals 30 | M = 6; % number of regimes 31 | % Initialization parameters: length of segments, number of random starts 32 | % for K-means clustering 33 | opts = struct('len',500,'Replicates',50); 34 | % EM parameters: maximum number of iterations, initial inverse temperature 35 | % and change rate for DAEM 36 | control = struct('ItrNo',300,'beta0',.7,'betarate',1.02); 37 | pars2 = init_var(e,M,p,opts,control,[],[],scale); % EM initialization 38 | [~,Ms,~,Shat,pars2,LL] = switch_var(e,M,p,pars2,control,[],[],scale); % EM 39 | 40 | %% 41 | 42 | figure(1) 43 | tiledlayout(4,4); 44 | for j = 1:M 45 | nexttile 46 | imagesc(reshape(pars2.A(:,:,:,j),N,p*N)) 47 | yticklabels(channel) 48 | title(sprintf("A (Regime %d)",j)) 49 | colorbar 50 | end 51 | for j = 1:M 52 | nexttile 53 | imagesc(pars2.Q(:,:,j)) 54 | title(sprintf("Q (Regime %d)",j)) 55 | colorbar 56 | end 57 | 58 | 59 | tabulate(Shat) % brain functional connectivity regimes 60 | tabulate(state) % eye status: 0 = open 1 = closed 61 | twoway = crosstab(Shat,state); 62 | disp(twoway); 63 | disp(twoway ./ sum(twoway,2)); % eye status and FC regime are largely independent 64 | 65 | figure(2) 66 | plot(Shat,'*') 67 | 68 | -------------------------------------------------------------------------------- /examples/eeg_eye_state.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ddegras/switch-ssm/fb600733936049cc2186a7d776b0f5337e4ba724/examples/eeg_eye_state.mat -------------------------------------------------------------------------------- /fast_dyn.m: -------------------------------------------------------------------------------- 1 | function [xf,xs,outpars,LL] = ... 2 | fast_dyn(y,M,p,r,S,pars,control,equal,fixed,scale) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % PARAMETER ESTIMATION AND INFERENCE IN STATE-SPACE MODEL 7 | % WITH SWITCHING DYNAMICS ASSUMING REGIMES KNOWN 8 | % 9 | % PURPOSE 10 | % FAST_DYN estimates model parameters and infers hidden state vectors 11 | % by the EM algorithm in state-space models with switching dynamics under 12 | % the assumption that the regime (i.e. switching) variables are known. 13 | % The function can be used to fit the model under a trajectory of regimes 14 | % that is highly likely. In the case of only one regime (no 15 | % switching), it can also be used to fit the standard linear 16 | % state-space model. 17 | % 18 | % USAGE 19 | % [xf,xs,outpars,LL] = fast_dyn(y,M,p,r,S,pars,control,equal,fixed,scale) 20 | % 21 | % INPUTS 22 | % y: time series data (dimension NxT) 23 | % M: number of regimes 24 | % p: order of VAR model for state vector x(t) 25 | % r: size of state vector x(t) 26 | % S: fixed sequence of regimes S(t), t=1:T 27 | % pars: optional structure with fields: 28 | % 'A': Initial estimate of VAR matrices A(l,j) in system equation 29 | % x(t,j) = sum(l=1:p) A(l,j) x(t-l,j) + v(t,j), j=1:M (dimension rxrxpxM) 30 | % 'C': Initial estimates of observation matrices C(j) in equation 31 | % y(t) = C(j) x(t,j) + w(t), j=1:M (dimension NxrxM) 32 | % 'Q': Initial estimate of state noise covariance Cov(v(t,j)) (dimension rxrxM) 33 | % 'R': Initial estimate of observation noise covariance Cov(w(t)) (dimension NxN) 34 | % 'mu': Initial estimate of mean state mu(j)=E(x(t,j)) for t=1:p (dimension rxM) 35 | % 'Sigma': Initial estimate of covariance Sigma(j)=Cov(x(t,j)) for t=1:p (dimension rxrxM) 36 | % control: optional structure with fields: 37 | % 'eps': tolerance for EM termination; defaults to 1e-8 38 | % 'ItrNo': number of EM iterations; defaults to 100 39 | % 'beta0': initial inverse temperature parameter for deterministic annealing; default 1 40 | % 'betarate': decay rate for temperature; default 1 41 | % 'safe': if true, regularizes variance matrices to be well-conditioned 42 | % before taking inverse. If false, no regularization (faster but less safe) 43 | % 'abstol': absolute tolerance for eigenvalues in matrix inversion (only effective if safe = true) 44 | % 'reltol': relative tolerance for eigenvalues in matrix inversion 45 | % = inverse condition number 46 | % equal: optional structure with fields: 47 | % 'A': if true, VAR transition matrices A(l,j) are equal across regimes j=1,...,M 48 | % 'Q': if true, VAR innovation matrices Q(j) are equal across regimes 49 | % 'mu': if true, initial mean state vectors mu(j) are equal across regimes 50 | % 'Sigma': if true, initial variance matrices Sigma(j) are equal across regimes 51 | % fixed: optional struct variable with fields 'A','C','Q','R','mu','Sigma'. 52 | % Each specified field must contain an array of the same dimensions 53 | % as the input it represents, e.g. fixed.A must have the same dimensions 54 | % as A. Fixed coefficients are given by numerical values i the array 55 | % and free coefficients are indicated by NaN's. 56 | % scale: optional struct variable with fields: 57 | % 'A': upper bound for modulus of eigenvalues of A matrices. Must be positive. 58 | % 'C': value of the (euclidean) column norms of the matrices C(j). Must be positive. 59 | % 60 | % OUTPUTS 61 | % xf: Filtered state vector 62 | % xs: Smoothed state vector 63 | % outpars: struct variable with fields 64 | % A: Estimated system (VAR) matrix 65 | % C: Estimated observation matrix 66 | % Q: Estimated state noise cov 67 | % R: Estimated observation noise cov 68 | % mu: Estimated initial mean of state vector 69 | % Sigma: Estimated initial variance of state vector 70 | % Pi: Estimated initial regime probabilities 71 | % Z: Estimated regime transition probabilities 72 | % LL : Log-likelihood 73 | % 74 | % AUTHOR 75 | % David Degras, david.degras@umb.edu 76 | % University of Massachusetts Boston 77 | % 78 | % CONTRIBUTORS 79 | % Ting Chee Ming, cmting@utm.my 80 | % Siti Balqis Samdin 81 | % Center for Biomedical Engineering, Universiti Teknologi Malaysia. 82 | % 83 | %-------------------------------------------------------------------------- 84 | 85 | 86 | 87 | 88 | 89 | %-------------------------------------------------------------------------% 90 | % Initialization % 91 | %-------------------------------------------------------------------------% 92 | 93 | 94 | narginchk(5,10) 95 | 96 | % Data dimensions 97 | [N,T] = size(y); 98 | 99 | % Data centering 100 | y = y - mean(y,2); 101 | 102 | % 'small' state vector: x(t), size r 103 | % 'big' state vector: X(t)=(x(t),...,x(t-p+1)), size p*r 104 | % We assume t the initial vectors x(1),...,x(p) are iid ~ N(mu,Sigma) 105 | % and mutually independent with S(1),...,S(p). 106 | 107 | % Check that time series has same length as regime sequence 108 | assert(size(y,2) == numel(S)); 109 | 110 | % Check that all regime values S(t) are in 1:M 111 | assert(all(ismember(S,1:M))); 112 | 113 | %@@@@@ Initialize optional arguments if not specified @@@@@% 114 | if ~exist('fixed','var') 115 | fixed = struct(); 116 | end 117 | if ~exist('equal','var') 118 | equal = struct(); 119 | end 120 | if ~exist('control','var') 121 | control = struct(); 122 | end 123 | if ~exist('scale','var') 124 | scale = struct(); 125 | end 126 | 127 | 128 | 129 | %@@@@@ Initialize estimators by OLS if not specified @@@@@% 130 | pars0 = struct('A',[], 'C',[], 'Q',[], 'R',[], 'mu',[], 'Sigma',[]); 131 | if exist('pars','var') && isstruct(pars) 132 | fname = fieldnames(pars0); 133 | for i = 1:6 134 | name = fname{i}; 135 | if isfield(pars,name) 136 | pars0.(name) = pars.(name); 137 | end 138 | end 139 | end 140 | pars = pars0; 141 | 142 | Pi = zeros(M,1); 143 | Pi(S(1)) = 1; 144 | pars.Pi = Pi; 145 | Z = crosstab(S(1:T-1),S(2:T)); 146 | Z = Z ./ sum(Z,2); 147 | Z(isnan(Z)) = 1/M; 148 | pars.Z = Z; 149 | 150 | if any(structfun(@isempty,pars)) 151 | pars = reestimate_dyn(y,M,p,r,S,control,equal,fixed,scale); 152 | end 153 | 154 | [pars,control,equal,fixed,scale,skip] = ... 155 | preproc_dyn(M,N,p,r,pars,control,equal,fixed,scale); 156 | 157 | abstol = control.abstol; 158 | reltol = control.reltol; 159 | eps = control.eps; 160 | ItrNo = control.ItrNo; 161 | verbose = control.verbose; 162 | safe = control.safe; 163 | 164 | 165 | 166 | 167 | %@@@@ Initialize other quantities @@@@@% 168 | 169 | LL = zeros(1,ItrNo); % Log-likelihood 170 | LLflag = 0; % counter for convergence of of log-likelihood 171 | sum_yy = y * y.'; % sum(t=1:T) y(t)*y(t)' 172 | Ms = zeros(M,T); 173 | for j = 1:M 174 | Ms(j,S == j) = 1; 175 | end 176 | sum_Ms2 = zeros(M); 177 | for i = 1:M 178 | for j = 1:M 179 | sum_Ms2(i,j) = sum(S(1:T-1) == i & S(2:T) == j); 180 | end 181 | end 182 | 183 | for i = 1:ItrNo 184 | 185 | 186 | 187 | %-------------------------------------------------------------------------% 188 | % E-step % 189 | %-------------------------------------------------------------------------% 190 | 191 | 192 | 193 | 194 | % Kalman filtering and smoothing 195 | [xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_P] = ... 196 | kfs_dyn(y,M,p,r,pars,S,safe,abstol,reltol); 197 | 198 | sum_xy = xs * y.'; % sum(t=1:T) E(x(t)|y(1:T)) y(t)' 199 | 200 | % Log-likelihood 201 | LL(i) = L; 202 | if verbose 203 | fprintf('Iteration-%d Log-likelihood = %g\n',i,LL(i)); 204 | end 205 | 206 | % Check if current solution is best to date 207 | if (i == 1 || LL(i) > LLbest) 208 | LLbest = LL(i); 209 | outpars = pars; 210 | xfbest = xf; 211 | xsbest = xs; 212 | end 213 | 214 | % Monitor convergence of log-likelihood 215 | if (i>1 && LL(i)-LL(i-1) < eps * abs(LL(i-1))) 216 | LLflag = LLflag + 1; 217 | else 218 | LLflag = 0; 219 | end 220 | % Terminate EM algorithm if no sufficient reduction in log-likelihood 221 | % for 5 iterations in a row 222 | if LLflag == 10 223 | break; 224 | end 225 | 226 | 227 | 228 | %-------------------------------------------------------------------------% 229 | % M-step % 230 | %-------------------------------------------------------------------------% 231 | 232 | 233 | 234 | 235 | pars = M_dyn(pars,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 236 | sum_P,sum_xy,sum_yy,control,equal,fixed,scale,skip); 237 | 238 | % Display Q-function if required 239 | if verbose 240 | Qval = Q_dyn(pars,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 241 | sum_P,sum_xy,sum_yy); 242 | fprintf('Q-function after M-step = %g\n',Qval); 243 | end 244 | 245 | 246 | end % END MAIN LOOP 247 | 248 | 249 | % Wrap-up 250 | outpars.A = reshape(outpars.A,[r,r,p,M]); 251 | outpars.Pi = Pi; 252 | outpars.Z = Z; 253 | LL = LL(1:i); 254 | xf = xfbest; 255 | xs = xsbest; 256 | 257 | end 258 | -------------------------------------------------------------------------------- /fast_obs.m: -------------------------------------------------------------------------------- 1 | function [xf,xs,outpars,LL] = fast_obs(y,M,p,r,S,pars,control,equal,fixed,scale) 2 | %-------------------------------------------------------------------------- 3 | % Title: Parameter estimation and inference in state-space models with 4 | % regime switching (switching observations) assuming regimes known 5 | % 6 | % Function: Infer hidden state vectors and regimes by switching Kalman 7 | % filtering/smoothing (aka Hamilton filtering or Kim filtering) 8 | % and estimate model parameters by maximum likelihood (EM algorithm). 9 | % 10 | % Usage: [Mf,Ms,Sf,Ss,xf,xs,outpars,LL] = ... 11 | % fast_obs(y,M,p,S,pars,control,equal,fixed,scale) 12 | % 13 | % Inputs: 14 | % y - Time series (dimension NxT) 15 | % M - number of regimes 16 | % p - order of VAR model for state vector 17 | % pars - optional structure with fields 18 | % A - Initial estimates of VAR matrices A(l,j) in system equation 19 | % x(t,j) = sum(l=1:p) A(l,j) x(t-l,j) + v(t,j), j=1:M (dimension rxrxpxM) 20 | % C - Initial estimates of observation matrices C(j) in equation 21 | % y(t) = C(j) x(t,j) + w(t), j=1:M (dimension NxrxM) 22 | % Q - Initial estimates of state noise covariance Cov(v(t,j)) (dimension rxrxM) 23 | % R - Pilot estimate of observation noise covariance Cov(w(t)) (dimension NxN) 24 | % mu - Pilot estimate of mean state mu(j)=E(x(t,j)) for t=1:p (dimension rxM) 25 | % Sigma - Pilot estimate of covariance Sigma(j)=Cov(x(t,j)) for t=1:p (dimension rxrxM) 26 | % S - regime sequence (length T) 27 | % control - optional struct variable with fields: 28 | % 'eps': tolerance for EM termination; defaults to 1e-8 29 | % 'ItrNo': number of EM iterations; defaults to 1000 30 | % 'beta0': initial inverse temperature parameter for deterministic annealing; default 1 31 | % 'betarate': decay rate for temperature; default 1 32 | % 'safe': if true, regularizes variance matrices to be well-conditioned 33 | % before taking inverse. If false, no regularization (faster but less safe) 34 | % 'abstol': absolute tolerance for eigenvalues in matrix inversion (only effective if safe = true) 35 | % 'reltol': relative tolerance for eigenvalues in matrix inversion 36 | % = inverse condition number (only effective if safe = true) 37 | % equal - optional struct variable with fields: 38 | % 'A': if true, VAR transition matrices A(l,j) are equal across regimes j=1,...,M 39 | % 'C': if true, observation matrices C(j) are equal across regimes 40 | % 'Q': if true, VAR innovation matrices Q(j) are equal across regimes 41 | % 'mu': if true, initial mean state vectors mu(j) are equal across regimes 42 | % 'Sigma': if true, initial variance matrices Sigma(j) are equal across regimes 43 | % fixed - optional struct variable with fields 'A','C','Q','R','mu','Sigma'. 44 | % If not empty, each field must contain a matrix with 2 columns, the first for 45 | % the location of fixed coefficients and the second for their values. 46 | % scale - optional struct variable with fields: 47 | % 'A': upper bound for norm of eigenvalues of A matrices. Must be in (0,1). 48 | % 'C': value of the (euclidean) column norms of the matrices C(j). Must be positive. 49 | % 50 | % Outputs: 51 | % Mf - State probability estimated by switching Kalman Filter 52 | % Ms - State probability estimated by switching Kalman Smoother 53 | % Sf - Estimated states (Kalman Filter) 54 | % Ss - Estimated states (Kalman Smoother) 55 | % xf - Filtered state vector 56 | % xs - Smoothed state vector 57 | % outpars - structure with fields 58 | % A - Estimated system matrix 59 | % C - Estimated observation matrix 60 | % Q - Estimated state noise cov 61 | % R - Estimated observation noise cov 62 | % mu - Estimated initial mean of state vector 63 | % Sigma - Estimated initial variance of state vector 64 | % LL - Log-likelihood 65 | 66 | % Variables: 67 | % T = length of signal 68 | % N = dimension of observation vector 69 | % r = dimension of state vector 70 | % M = number of regimes/states 71 | % 72 | % Author: David Degras 73 | % University of Massachusetts Boston 74 | % 75 | % Contributors: Ting Chee Ming, cmting@utm.my 76 | % Siti Balqis Samdin 77 | % Centre for Biomedical Engineering, Universiti Teknologi Malaysia. 78 | % 79 | % Version date: February 7, 2021 80 | %-------------------------------------------------------------------------- 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | %-------------------------------------------------------------------------% 89 | % Initialization % 90 | %-------------------------------------------------------------------------% 91 | 92 | 93 | narginchk(5,10); 94 | 95 | % Data dimensions 96 | [N,T] = size(y); 97 | 98 | % x(t,j): state vector for j-th process at time t (size r0) 99 | % x(t) = x(t,1),...,x(t,M): state vector for all processes at time t (size M*r0) 100 | % X(t,j) = x(t,j),...,x(t-p+1,j)): state vector for j-th process at times t,...,t-p+1 (size r=p*r0) 101 | % X(t) = x(t,1),...,x(t,M): state vector for all processes at times t,...,t-p+1 (size M*p*r0) 102 | % We assume t the initial vectors x(1),...,x(1-p+1) are iid ~ N(mu,Sigma) 103 | 104 | % Check that time series has same length as regime sequence 105 | assert(size(y,2) == numel(S)); 106 | 107 | % Check that all regime values S(t) are in 1:M 108 | assert(all(ismember(S,1:M))); 109 | 110 | % Data centering 111 | y = y - mean(y,2); 112 | 113 | %@@@@ Initialize optional arguments if not specified 114 | 115 | if ~exist('fixed','var') 116 | fixed = struct(); 117 | end 118 | if ~exist('equal','var') 119 | equal = struct(); 120 | end 121 | if ~exist('control','var') 122 | control = struct(); 123 | end 124 | if ~exist('scale','var') 125 | scale = struct(); 126 | end 127 | 128 | 129 | 130 | %@@@@ Initialize estimators by OLS if not specified @@@@% 131 | pars0 = struct('A',[], 'C',[], 'Q',[], 'R',[], 'mu',[], 'Sigma',[], ... 132 | 'Pi',[], 'Z',[]); 133 | if exist('pars','var') && isstruct(pars) 134 | fname = fieldnames(pars0); 135 | for i = 1:8 136 | name = fname{i}; 137 | if isfield(pars,name) 138 | pars0.(name) = pars.(name); 139 | end 140 | end 141 | end 142 | pars = pars0; 143 | if any(structfun(@isempty,pars)) 144 | pars = init_obs(y,M,p,r,[],control,equal,fixed,scale); 145 | end 146 | 147 | Pi = zeros(M,1); 148 | Pi(S(1)) = 1; 149 | pars.Pi = Pi; 150 | fixed.Pi = []; 151 | Z = crosstab(S(1:T-1),S(2:T)); 152 | Z = Z ./ sum(Z,2); 153 | Z(isnan(Z)) = 1/M; 154 | pars.Z = Z; 155 | fixed.Z = []; 156 | 157 | 158 | % Preprocess input arguments 159 | [pars,control,equal,fixed,scale,skip] = ... 160 | preproc_obs(M,N,p,r,pars,control,equal,fixed,scale); 161 | 162 | abstol = control.abstol; 163 | reltol = control.reltol; 164 | eps = control.eps; 165 | ItrNo = control.ItrNo; 166 | verbose = control.verbose; 167 | safe = control.safe; 168 | 169 | % Parameter sizes 170 | % A: r x pr x M, C: N x r x M, Q: r x r x M, R: N x N, 171 | % mu: r x M, Sigma: r x r x M 172 | % (Only size of A is changed) 173 | 174 | 175 | %@@@@ Initialize other quantities 176 | 177 | LL = zeros(1,ItrNo); % Log-likelihood 178 | LLbest = -Inf; 179 | LLflag = 0; % counter for convergence of of log-likelihood 180 | sum_yy = y * y.'; % sum(t=1:T) y(t)*y(t)' 181 | Ms = zeros(M,T); % P(S(t)=j), for use in Q-function 182 | % Ms(sub2ind([M,T],S,T)) = 1; 183 | sum_Ms2 = zeros(M); 184 | for j = 1:M 185 | Ms(j,S == j) = 1; 186 | for k = 1:M 187 | sum_Ms2(j,k) = sum(S(1:end-1) == j & S(2:end) == k); 188 | end 189 | end 190 | 191 | 192 | 193 | 194 | for i = 1:ItrNo 195 | 196 | 197 | 198 | %-------------------------------------------------------------------------% 199 | % Filtering and smoothing + E-step % 200 | %-------------------------------------------------------------------------% 201 | 202 | 203 | 204 | [xf,xs,x0,P0,L,sum_CP,sum_MP,sum_Mxy,sum_P,sum_Pb] = ... 205 | kfs_obs(y,M,p,r,S,pars,safe,abstol,reltol); 206 | 207 | % Log-likelihood 208 | LL(i) = L; 209 | if verbose 210 | fprintf('Iteration-%d Log-likelihood = %g\n',i,LL(i)); 211 | Qval = Q_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,... 212 | sum_P,sum_Pb,sum_yy,x0); 213 | fprintf('Iteration-%d Q-function = %g (before M-step)\n',i,Qval); 214 | end 215 | 216 | % Check if current solution is best to date 217 | if i == 1 || LL(i) > LLbest 218 | LLbest = LL(i); 219 | xfbest = xf; 220 | xsbest = xs; 221 | outpars = pars; 222 | end 223 | 224 | % Monitor convergence of log-likelihood 225 | if i>1 && (LL(i)-LL(i-1)) < (eps * abs(LL(i-1))) 226 | LLflag = LLflag + 1; 227 | else 228 | LLflag = 0; 229 | end 230 | 231 | % Terminate EM algorithm if no sufficient reduction in log-likelihood 232 | % for 5 successive iterations 233 | if LLflag == 5 234 | break; 235 | end 236 | 237 | 238 | 239 | 240 | %-------------------------------------------------------------------------% 241 | % M-step % 242 | %-------------------------------------------------------------------------% 243 | 244 | 245 | pars = M_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,... 246 | sum_P,sum_Pb,sum_yy,x0,control,equal,fixed,scale,skip); 247 | 248 | if verbose 249 | Qval = Q_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,... 250 | sum_P,sum_Pb,sum_yy,x0); 251 | fprintf('Iteration-%d Q-function = %g (after M-step)\n',i,Qval); 252 | end 253 | 254 | 255 | end % END MAIN LOOP 256 | 257 | 258 | 259 | %-------------------------------------------------------------------------% 260 | % Output % 261 | %-------------------------------------------------------------------------% 262 | 263 | % Return best estimates (i.e. with highest log-likelihood) 264 | % after reshaping them in compact form 265 | outpars.A = reshape(outpars.A,r,r,p,M); 266 | outpars.Pi = Pi; 267 | outpars.Z = Z; 268 | xf = xfbest; 269 | xs = xsbest; 270 | LL = LL(1:i); 271 | 272 | end 273 | 274 | -------------------------------------------------------------------------------- /forecast.m: -------------------------------------------------------------------------------- 1 | function yhat = forecast(pars,S,x,model) 2 | 3 | model = string(model); 4 | assert(ismember(model,["dyn","obs"])) 5 | [M,T] =size(S); 6 | N = size(pars.C,1); 7 | p = size(pars.A,3); 8 | r = size(x,1); 9 | 10 | A = pars.A; 11 | C = pars.C; 12 | Z = pars.Z; 13 | T = numel(S); 14 | 15 | yhat = zeros(N,T); 16 | 17 | switch model 18 | case "obs" 19 | for t = p+1:T 20 | for j = 1:M 21 | xx = zeros(r,1); 22 | for k = 1:p 23 | xx = xx + A(:,:,k,j) * x(:,j,t-k); 24 | end 25 | yhat(:,t) = yhat(:,t) + Z(S(t-1),j) * C(:,:,j) * xx; 26 | end 27 | end 28 | case "dyn" 29 | for t = p+1:T 30 | for j = 1:M 31 | xx = zeros(r,1); 32 | for k = 1:p 33 | xx = xx + Z(S(t-1),j) * A(:,:,k,j) * x(:,t-k); 34 | end 35 | end 36 | yhat(:,t) = C * xx; 37 | end 38 | end 39 | 40 | yhat(:,1:p) = NaN(N,p); 41 | 42 | -------------------------------------------------------------------------------- /get_covariance.m: -------------------------------------------------------------------------------- 1 | function stationary = get_covariance(pars,lagmax,nfreq) 2 | 3 | %-------------------------------------------------------------------------% 4 | % 5 | % Purpose: GET_COVARIANCE calculates the stationary autocorrelation function, 6 | % coherence function, and covariance matrix, correlation matrix, 7 | % and partial correlation matrix for each regime of a switching 8 | % state-space model 9 | % 10 | % Usage: stationary = get_covariance(pars,lagmax,nfreq) 11 | % 12 | % Inputs: pars - structure with fields 13 | % A - transition matrices ([r,r,p,M]) 14 | % C - observation matrices ([N,r], [N,r,M], or []) 15 | % Q - state noise covariance matrices ([N,N,M]) 16 | % R - observation noise covariance matrix ([N,N] or []) 17 | % lagmax - maximum lag for autocorrelation function (optional, 18 | % default = 2000) 19 | % nfreq - number of frequencies at which to calculate coherence 20 | % (optional, default = 100) 21 | % 22 | % Outputs: stationary - structure with fields 23 | % ACF - autocorrelation function ([N,lagmax,M]) 24 | % COH - coherence function ([N,N,nfreq,M]) 25 | % COV - covariance matrix ([N,N,M]) 26 | % COR - correlation matrix ([N,N,M]) 27 | % PCOR - partial correlation matrix ([N,N,M]) 28 | % VAR - variance ([N,M]) 29 | % 30 | %-------------------------------------------------------------------------% 31 | 32 | narginchk(1,3); 33 | 34 | assert(isstruct(pars)) 35 | 36 | A = pars.A; 37 | Q = pars.Q; 38 | if isfield(pars,'C') 39 | C = pars.C; 40 | else 41 | C = []; 42 | end 43 | if isfield(pars,'R') 44 | R = pars.R; 45 | else 46 | R = []; 47 | end 48 | if ~exist('lagmax','var') || isempty(lagmax) 49 | lagmax = 2000; 50 | end 51 | if ~exist('nfreq','var') || isempty(nfreq) 52 | nfreq = 100; 53 | end 54 | 55 | % Model dimensions 56 | [r,~,p,M] = size(A); 57 | 58 | % Check arguments C and R 59 | if ~isempty(C) || ~isempty(R) 60 | assert(~isempty(C) && ~isempty(R)); 61 | N = size(C,1); 62 | if ismatrix(C) 63 | C = repmat(C,1,1,M); 64 | end 65 | else 66 | N = r; 67 | end 68 | 69 | 70 | 71 | % Initialize stationary measures 72 | mask = logical(eye(N)); 73 | ACF = NaN(N,lagmax+1,M); % auto-correlation diag(cor(x(t),x(t-l)|S(t)=j) 74 | ACF(:,1,:) = 1; 75 | idx_acf = repmat(mask,1,1,lagmax+1); 76 | if nfreq == 0 77 | COH = []; 78 | else 79 | COH = NaN(N,N,nfreq,M); % coherence 80 | idx_coh = repmat(mask,1,1,nfreq); 81 | end 82 | COV = NaN(N,N,M); % covariance Cov(x(t)|S(t)=j) 83 | COR = NaN(N,N,M); % correlation Cor(x(t)|S(t)=j) 84 | PCOR = NaN(N,N,M); % partial correlation 85 | VAR = zeros(N,M); 86 | Abig = zeros(p*r); % container for A 87 | invAbig = zeros(p*r); 88 | if p > 1 89 | Abig(r+1:end,1:end-r) = eye((p-1)*r); 90 | invAbig(1:end-r,r+1:end) = eye((p-1)*r); 91 | end 92 | Qbig = zeros(p*r); % container for Q 93 | 94 | % Calculations 95 | for j = 1:M 96 | A_j = A(:,:,:,j); 97 | Abig(1:r,:) = reshape(A_j,r,p*r); 98 | Qbig(1:r,1:r) = Q(:,:,j); 99 | if all(A_j(:) == 0) 100 | Vbig = Qbig; 101 | else 102 | eigA = abs(eig(Abig)); 103 | if any(eigA >= 1) 104 | continue 105 | elseif min(eigA) <= 1e-8 * max(1,max(eigA)) % case: Abig numerically singular 106 | Vbig = get_covariance_aux(A_j,Q(:,:,j)); % Cov(X(t)|S(t)=j) 107 | else % case: Abig full rank 108 | invAbig((p-1)*r+1:p*r,1:r) = inv(A_j(:,:,p)); 109 | if p > 1 110 | invAbig((p-1)*r+1:p*r,r+1:p*r) = ... 111 | -A_j(:,:,p)\Abig(1:r,1:(p-1)*r); 112 | end 113 | Vbig = sylvester(invAbig,-Abig',invAbig*Qbig); 114 | end 115 | end 116 | 117 | % Covariance and variance 118 | Vbig = 0.5 * (Vbig + Vbig.'); 119 | if isempty(C) 120 | COV(:,:,j) = Vbig(1:r,1:r); 121 | else 122 | COV(:,:,j) = (C(:,:,j) * Vbig(1:r,1:r) * C(:,:,j).') + R; 123 | end 124 | COV(:,:,j) = 0.5 * (COV(:,:,j) + COV(:,:,j).'); 125 | VAR(:,j) = diag(COV(:,:,j)); 126 | 127 | % Correlation and partial correlation 128 | try 129 | COR(:,:,j) = corrcov(COV(:,:,j)); 130 | iCORj = myinv(COR(:,:,j)); 131 | PCORj = - corrcov(iCORj + iCORj'); 132 | catch 133 | SDj = sqrt(VAR(:,j)); 134 | SDj(SDj == 0) = 1; 135 | COR(:,:,j) = diag(1./SDj) * COV(:,:,j) * diag(1./SDj); 136 | iCORj = myinv(COR(:,:,j)); 137 | SDj = sqrt(diag(iCORj)); 138 | SDj(SDj == 0) = 1; 139 | PCORj = - diag(1./SDj) * iCORj * diag(1./SDj); 140 | end 141 | PCORj(mask) = 1; 142 | PCOR(:,:,j) = PCORj; 143 | 144 | if lagmax > 0 145 | CCV_tmp = Vbig; % Cov(X(t),X(t-l)|S(t)=j) 146 | CCV = zeros(r,r,lagmax+1); % Cov(x(t),x(t-l)|S(t)=j) 147 | CCV(:,:,1) = Vbig(1:r,1:r); 148 | A_up = Abig(1:r,:); 149 | for l = 1:lagmax 150 | B = A_up * CCV_tmp; 151 | CCV_tmp(r+1:end,:) = CCV_tmp(1:end-r,:); 152 | CCV_tmp(1:r,:) = B; 153 | CCV(:,:,l+1) = CCV_tmp(1:r,1:r); 154 | end 155 | if ~isempty(C) % Cov(y(t),y(t-l)|S(t)=j) 156 | CCV = C(:,:,j) * reshape(CCV,r,r*(lagmax+1)); 157 | CCV = reshape(CCV,N,r,lagmax+1); 158 | CCV = permute(CCV,[1,3,2]); 159 | CCV = reshape(CCV,N*(lagmax+1),r) * C(:,:,j).'; 160 | CCV = reshape(CCV,N,lagmax+1,N); 161 | CCV = permute(CCV,[1,3,2]); 162 | CCV(:,:,1) = COV(:,:,j); 163 | end 164 | % Autocorrelation 165 | ACF(:,:,j) = reshape(CCV(idx_acf),N,lagmax+1); %#ok<*AGROW> 166 | ACF(:,:,j) = ACF(:,:,j) ./ diag(COV(:,:,j)); 167 | end 168 | 169 | if nfreq == 0 170 | continue 171 | end 172 | % Contribution from non-negative lags to cross-spectral density (CSD) 173 | % Angular frequency (stop at normalized Nyquist frequency = pi rad/sample) 174 | w = linspace(0, pi, nfreq+1); 175 | w = w(1:end-1); 176 | lags = 0:lagmax; 177 | cplxsin = exp(-1j * lags' * w); % complex sinusoidal wave 178 | CSD = reshape(CCV,N^2,lagmax+1) * cplxsin; 179 | % Contribution from negative lags 180 | % R_xy(-h) = cov(x(t),y(t+h)) = cov(y(t),x(t-h)) = R_yx(h) (h>0) 181 | CCV = permute(CCV(:,:,2:end),[2,1,3]); 182 | lags = -(1:lagmax); 183 | cplxsin = exp(-1j * lags' * w); 184 | CSD = CSD + reshape(CCV,N^2,lagmax) * cplxsin; 185 | CSD = reshape(CSD,N,N,nfreq); 186 | 187 | % Coherence 188 | % Extract diagonal terms for normalization 189 | SD = reshape(real(CSD(idx_coh)),N,nfreq); 190 | SD(SD < 0) = NaN; 191 | if any(isnan(SD(:))) 192 | warning(['Some of the calculated power spectral densities have ',... 193 | 'negative values. Consider increasing ''lagmax'' for ',... 194 | 'more accurate estimation.']) 195 | end 196 | CSD = abs(CSD).^2; 197 | for f = 1:nfreq 198 | nrm = 1./SD(:,f); 199 | CSD(:,:,f) = nrm .* CSD(:,:,f) .* (nrm.'); 200 | end 201 | COH(:,:,:,j) = CSD; 202 | 203 | end 204 | 205 | if M == 1 206 | ACF = squeeze(ACF); 207 | COH = squeeze(COH); 208 | COV = squeeze(COV); 209 | COR = squeeze(COR); 210 | PCOR = squeeze(PCOR); 211 | end 212 | 213 | 214 | 215 | stationary = struct('ACF',ACF,'COH',COH,'COV',COV,'COR',COR,... 216 | 'PCOR',PCOR,'VAR',VAR); 217 | 218 | 219 | -------------------------------------------------------------------------------- /get_covariance_aux.m: -------------------------------------------------------------------------------- 1 | function V = get_covariance_aux(A,Q) 2 | 3 | 4 | % Model dimensions 5 | [r,~,p] = size(A); 6 | 7 | % Define pr x pr matrix mapping pr x pr block-matrix V = (Gamma(k-l)) 8 | % (0 <= k,l < p) with Gamma(k) = Cov(x(t),x(t-k)) to corresponding indices 9 | % in gamma = (vech(Gamma(0)), vec(Gamma(1)), ... , vec(Gamma(p)) 10 | % The idea is to map the entries of V, which are duplicated multiple times, 11 | % to their unique values in gamma 12 | count = 0; 13 | for l = 0:p-1 14 | if l == 0 15 | mask = logical(tril(ones(r))); 16 | Gamma_idx = zeros(r); 17 | Gamma_idx(mask) = 1:r*(r+1)/2; 18 | Gamma_idx = Gamma_idx + tril(Gamma_idx,-1)'; 19 | Vtmp = kron(eye(p),Gamma_idx); 20 | count = count + r*(r+1)/2; 21 | else 22 | idx = count+1:count+r^2; 23 | Gamma_idx = reshape(idx(:),r,r); 24 | Vtmp = Vtmp + kron(diag(ones(p-l,1),l),Gamma_idx) + ... 25 | kron(diag(ones(p-l,1),-l),Gamma_idx'); 26 | count = count + r^2; 27 | end 28 | end 29 | Gamma_idx = Vtmp; 30 | 31 | % Solve associated linear equations to get gamma 32 | nvars = p*r^2-r*(r-1)/2; 33 | Abig = diag(ones((p-1)*r,1),-r); 34 | Abig(1:r,:) = reshape(A,[r,p*r]); 35 | LHS = eye((p*r)^2) - kron(Abig,Abig); 36 | keep = zeros(1,nvars); 37 | for i = 1:nvars 38 | idx = find(Gamma_idx == i); 39 | keep(i) = idx(1); 40 | LHS(:,idx(1)) = sum(LHS(:,idx),2); 41 | end 42 | LHS = LHS(:,keep); 43 | RHS = zeros(p*r); 44 | RHS(1:r,1:r) = Q; 45 | RHS = RHS(:); 46 | gamma = LHS\RHS; 47 | 48 | % Rearrange results for Cov(x(t),x(t-l)) 49 | V = reshape(gamma(Gamma_idx),p*r,p*r); 50 | -------------------------------------------------------------------------------- /get_crosscovariance.m: -------------------------------------------------------------------------------- 1 | function [ACF,CCV] = get_crosscovariance(pars,lagmax) 2 | 3 | %-------------------------------------------------------------------------% 4 | % 5 | % Purpose: calculate stationary cross-covariance function in switching state-space model 6 | % y(t) = C(S(t)) x(t) + w(t) 7 | % x(t) = A(1,S(t)) x(t-1) + ... + A(p,S(t)) x(t-p) + v(t) 8 | % 9 | % Usage: [ACF,CCV] = get_crosscovariance(pars,lagmax) 10 | % 11 | % Inputs: pars - struct with fields 12 | % A - state transition matrices ([r,r,p,M]) 13 | % C - observation matrices ([N,r], [N,r,M], or []) 14 | % Q - state noise covariance matrices ([N,N,M]) 15 | % R - observation noise covariance matrix ([N,N] or []) 16 | % lagmax - maximum lag for autocorrelation function (optional, 17 | % default = 5*p) 18 | % 19 | % Outputs: ACF - autocorrelation functions ([N,lagmax+1]) 20 | % CCV - cross-covariance functions ([N,N,lagmax+1,M]) 21 | % 22 | %-------------------------------------------------------------------------% 23 | 24 | narginchk(1,2); 25 | 26 | % Model parameters 27 | A = pars.A; 28 | if isfield(pars,'C') 29 | C = pars.C; 30 | else 31 | C = []; 32 | end 33 | Q = pars.Q; 34 | if isfield(pars,'R') 35 | R = pars.R; 36 | else 37 | R = []; 38 | end 39 | 40 | % Model dimensions 41 | [r,~,p,M] = size(A); 42 | 43 | if ~exist('lagmax','var') || isempty(lagmax) 44 | lagmax = 5*p; 45 | end 46 | 47 | lagmax = round(lagmax); 48 | assert(lagmax >= 0) 49 | 50 | % Check arguments C and R 51 | if ~isempty(C) || ~isempty(R) 52 | assert(~isempty(C) && ~isempty(R)) 53 | assert(ismatrix(R) && size(R,1) == size(R,2)) 54 | assert(size(C,1) == size(R,1)) 55 | N = size(C,1); 56 | if ismatrix(C) 57 | C = repmat(C,1,1,M); 58 | end 59 | else 60 | N = r; 61 | end 62 | 63 | 64 | 65 | % Initialize various quantities 66 | ACF = NaN(N,lagmax+1,M); % auto-correlation diag(cor(x(t),x(t-l)|S(t)=j) 67 | CCV = NaN(N,N,lagmax+1,M); % cross-correlation (cov(x(t),x(t-l)|S(t)=j) 68 | Abig = zeros(p*r); % container for A 69 | if p > 1 70 | Abig(r+1:end,1:end-r) = eye((p-1)*r); 71 | end 72 | Qbig = zeros(p*r); % container for Q 73 | idx_acf = (repmat(eye(N),1,1,lagmax+1) == 1); 74 | 75 | % Calculations 76 | for j = 1:M 77 | Aj = A(:,:,:,j); 78 | Abig(1:r,:) = reshape(Aj,r,p*r); 79 | Qbig(1:r,1:r) = Q(:,:,j); 80 | if all(Aj(:) == 0) 81 | Vbig = Qbig; 82 | else 83 | eigA = abs(eig(Abig)); 84 | if any(eigA >= 1) 85 | continue 86 | elseif min(eigA) <= 1e-10 * max(eigA) % case: Abig numerically singular 87 | Vbig = get_covariance_aux(Aj,Q(:,:,j)); % Cov(X(t)|S(t)=j) 88 | else % case: Abig full rank 89 | B = (Abig.' * Abig)\(Abig.'); 90 | Vbig = sylvester(B,-Abig.',B*Qbig); 91 | end 92 | end 93 | Vbig = 0.5 * (Vbig + Vbig.'); 94 | 95 | if isempty(C) 96 | COV = Vbig(1:r,1:r); 97 | else 98 | COV = (C(:,:,j) * Vbig(1:r,1:r) * C(:,:,j).') + R; 99 | end 100 | COV = 0.5 * (COV + COV'); 101 | 102 | if lagmax == 0 103 | CCV(:,:,:,j) = COV; 104 | ACF(:,:,j) = ones(N,1); 105 | continue 106 | end 107 | 108 | CCV_tmp = Vbig; % Cov(X(t),X(t-l)|S(t)=j) 109 | CCVj = zeros(r,r,lagmax+1); % Cov(x(t),x(t-l)|S(t)=j) 110 | CCVj(:,:,1) = Vbig(1:r,1:r); 111 | A_up = Abig(1:r,:); 112 | for l = 1:lagmax 113 | B = A_up * CCV_tmp; 114 | CCV_tmp(r+1:end,:) = CCV_tmp(1:end-r,:); 115 | CCV_tmp(1:r,:) = B; 116 | CCVj(:,:,l+1) = CCV_tmp(1:r,1:r); 117 | end 118 | if ~isempty(C) % Cov(y(t),y(t-l)|S(t)=j) 119 | CCVj = C(:,:,j) * reshape(CCVj,r,r*(lagmax+1)); 120 | CCVj = reshape(CCVj,N,r,lagmax+1); 121 | CCVj = permute(CCVj,[1,3,2]); 122 | CCVj = reshape(CCVj,N*(lagmax+1),r) * C(:,:,j).'; 123 | CCVj = reshape(CCVj,N,lagmax+1,N); 124 | CCVj = permute(CCVj,[1,3,2]); 125 | CCVj(:,:,1) = COV; 126 | end 127 | CCV(:,:,:,j) = CCVj; 128 | 129 | % Autocorrelation 130 | ACF(:,:,j) = reshape(CCVj(idx_acf),N,lagmax+1); 131 | ACF(:,:,j) = ACF(:,:,j) ./ diag(COV); 132 | end 133 | 134 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /init_obs.m: -------------------------------------------------------------------------------- 1 | function [pars,Shat] = init_obs(y,M,p,r,opts,control,equal,fixed,scale) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % INITIALIZATION OF EM ALGORITHM FOR STATE-SPACE MODEL 6 | % WITH MARKOV-SWITCHING OBSERVATIONS 7 | % 8 | % PURPOSE 9 | % This function calculates initial parameter estimates for the main 10 | % EM fitting function 'switch_obs'. The underlying model is 11 | % y(t) = C(S(t)) * x(t,S(t)) + w(t), t=1:T 12 | % x(t,j) = sum(l=1:p) A(l,j) * x(t-l,j) + v(t,j), j=1:M 13 | % S(t) = switching variable (Markov chain) taking values in {1:M} 14 | % 15 | % USAGE 16 | % [pars,Shat] = init_obs(y,M,p,r,opts,control,equal,fixed,scale) 17 | % 18 | % INPUTS: 19 | % y - data (size NxT with time in cols, variables in rows) 20 | % M - number of regimes for Markov chain S(t), t=1:T 21 | % p - order of vector autoregressive (VAR) model for state vectors x(t,j) 22 | % r - dimension of state vectors x(t,j), j=1:M 23 | % opts - optional structure with fields: 24 | % 'segmentation': possible values 'fixed' for fixed segments and 25 | % 'binary' for binary segmentation 26 | % 'reestimation': if 'true', parameters A & Q are re-estimated 27 | % after clustering; if 'false', A & Q are estimated by the 28 | % cluster centroids. 29 | % 'delta': minimal distance between two consecutive change points 30 | % in binary segmentation 31 | % 'tol': minimum relative decrease in loss function for a point to be 32 | % a valid change point (for binary segmentation). See function 33 | % find_single_cp for more details. 34 | % 'Replicates': number of replicates in k-means (default=10) 35 | % 'UseParallel': use parallel computing for k-means? (default=false) 36 | % 37 | % OUTPUTS: 38 | % pars - structure variable containing model parameter estimates 39 | % A - estimate of transition matrices A(l,j) (size rxrxpxM) 40 | % C - estimate of observation matrices C(j), j=1:M (size NxrxM) 41 | % Q - estimate of state noise variance matrices Q(j) = V(v(t,j)) 42 | % (size rxrxM) 43 | % R - estimate of observation noise variance matrix R = V(w(t)) 44 | % (size NxN) 45 | % mu - estimate of initial mean of state vector mu(j) = E(x(1,j)) 46 | % (size rxM) 47 | % Sigma - estimate of initial variance of state vector Sigma(j) = V(x(1,j)) 48 | % (size rxrxM) 49 | % Pi - estimate of initial probabilities Pi(j) = P(S(1)=j) (size Mx1) 50 | % Z - estimate of transition probabilities between regimes Z(i,j) = 51 | % P(S(t)=j|S(t-1)=i) (size MxM) 52 | % Shat - estimate of regimes S(t) (size Tx1) 53 | % 54 | % Author: David Degras, david.degras@umb.edu 55 | % University of Massachusetts Boston 56 | % 57 | % Date: January 18, 2021 58 | % 59 | % Reference: "Exploring dynamic functional connectivity of the brain with 60 | % switching state-space models". D. Degras, C.-M. Ting, and 61 | % H. Ombao (2018) 62 | %-------------------------------------------------------------------------- 63 | 64 | 65 | 66 | 67 | %-------------------------------------------------------------------------% 68 | % Preprocessing % 69 | %-------------------------------------------------------------------------% 70 | 71 | % Check number of arguments 72 | narginchk(4,9) 73 | 74 | % Data dimensions 75 | [N,~] = size(y); 76 | 77 | % Initialize optional arguments if needed 78 | if ~exist('opts','var') 79 | opts = []; 80 | end 81 | if ~exist('control','var') 82 | control = []; 83 | end 84 | if ~exist('fixed','var') 85 | fixed = []; 86 | end 87 | if ~exist('equal','var') 88 | equal = []; 89 | end 90 | if ~exist('scale','var') 91 | scale = []; 92 | end 93 | 94 | 95 | 96 | %-------------------------------------------------------------------------% 97 | % Initial parameter estimation for model with switching dynamics % 98 | %-------------------------------------------------------------------------% 99 | 100 | % 101 | % This step is used to segment the time series. It also checks the optional 102 | % arguments 'control', 'fixed', 'equal', and 'scale' 103 | 104 | fixed_tmp = fixed; 105 | test1 = isstruct(fixed) && isfield(fixed,'C'); 106 | test2 = isstruct(equal) && isfield(equal,'C') && equal.C; 107 | if test1 108 | if test2 || M == 1 109 | fixed_tmp.C = fixed.C(:,:,1); 110 | else 111 | fixed_tmp = rmfield(fixed,'C'); 112 | end 113 | end 114 | 115 | [pars,Shat] = init_dyn(y,M,p,r,opts,control,equal,fixed_tmp,scale); 116 | 117 | % Trivial case: M=1 (no switching). In this case the switching dynamics and 118 | % switching observations models are identical (a standard linear state-space 119 | % model) 120 | 121 | if M == 1 122 | return 123 | end 124 | 125 | 126 | 127 | %-------------------------------------------------------------------------% 128 | % Re-estimation % 129 | %-------------------------------------------------------------------------% 130 | 131 | 132 | pars = reestimate_obs(y,M,p,r,Shat,control,equal,fixed,scale); 133 | 134 | % Test compatibility between initial estimates and specified constraints 135 | test = preproc_obs(M,N,p,r,pars,control,equal,fixed,scale); %#ok 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /init_var.m: -------------------------------------------------------------------------------- 1 | 2 | function [pars,Shat] = init_var(y,M,p,opts,control,equal,fixed,scale) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % INITIALIZATION OF EM ALGORITHM 7 | % FOR SWITCHING VECTOR AUTOREGRESSIVE MODEL 8 | % 9 | % PURPOSE 10 | % This function calculates initial parameter estimates for the main 11 | % EM fitting function 'switch_var' 12 | % 13 | % USAGE 14 | % [pars,Shat] = init_var(y,M,p,opts,control,equal,fixed,scale) 15 | % 16 | % INPUTS 17 | % y: data (time = cols, variables = rows) 18 | % M: number of regimes for Markov chain 19 | % p: order of VAR state process 20 | % opts: optional structure with fields: 21 | % 'segmentation': with possible values 'fixed' (fixed segments) and 22 | % 'binary' (binary segmentation). Default = 'fixed' 23 | % 'len': segment length. Only for fixed segmentation. 24 | % 'delta': minimal distance between two consecutive change points. 25 | % Only for binary segmentation. 26 | % 'tol': minimum relative decrease in loss function for a point to be 27 | % acceptable as change point. Only for binary segmentation. See 28 | % function find_single_cp for more details. 29 | % 'Replicates': number of replicates in k-means (default=10) 30 | % 'UseParallel': use parallel computing for k-means? (default=false) 31 | % control: optional structure with fields 32 | % 'abstol': absolute tolerance for eigenvalues when regularizing 33 | % estimates of covariance matrices Q, R, and Sigma. Eigenvalues 34 | % less than the lower bound abstol are replaced by it 35 | % 'reltol': relative tolerance for eigenvalues when regularizing 36 | % estimates of covariance matrices Q, R, and Sigma. Eigenvalues 37 | % less than the lower bound (max eigenvalue * reltol) are replaced 38 | % by it 39 | % 40 | % OUTPUTS 41 | % Ahat: estimate of transition matrices (rxrxpxM) 42 | % Qhat: estimate of state noise covariance matrices (rxrxM) 43 | % muhat: estimate of initial mean of state vector (rx1) 44 | % Sigmahat: estimate of initial covariance of state vector (rxr) 45 | % Pihat: estimate of probabilities of initial state of Markov chain (Mx1) 46 | % Zhat: estimate of transition probabilities (MxM) 47 | % Shat: estimate of Markov chain states S(t) (Tx1) 48 | % 49 | %-------------------------------------------------------------------------- 50 | 51 | 52 | 53 | 54 | 55 | % Check number of inputs 56 | narginchk(3,8); 57 | 58 | % Data dimensions 59 | N = size(y,1); 60 | 61 | % Initialize optional arguments if needed 62 | if ~exist('opts','var') 63 | opts = []; 64 | end 65 | if ~exist('control','var') 66 | control = []; 67 | end 68 | if ~exist('equal','var') 69 | equal = []; 70 | end 71 | if ~exist('scale','var') 72 | scale = []; 73 | end 74 | if ~exist('fixed','var') 75 | fixed = struct(); 76 | end 77 | 78 | % Pass arguments to init_dyn 79 | fixed.R = 1e-10 * eye(N); 80 | fixed.C = eye(N); 81 | [pars,Shat] = init_dyn(y,M,p,N,opts,control,equal,fixed,scale); 82 | pars = rmfield(pars,'C'); 83 | pars = rmfield(pars,'R'); 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /kfs_dyn.m: -------------------------------------------------------------------------------- 1 | function [xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_P] = ... 2 | kfs_dyn(y,M,p,r,pars,S,safe,abstol,reltol) 3 | 4 | 5 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; 6 | Sigma = pars.Sigma; 7 | 8 | % Model dimensions 9 | [N,T] = size(y); 10 | % Size of 'small' state vector x(t): r 11 | % Size of 'big' state vector X(t) = (x(t),...,x(t-p+1)): p * r 12 | 13 | % To remove warnings when inverting singular matrices 14 | warning('off','MATLAB:singularMatrix'); 15 | warning('off','MATLAB:illConditionedMatrix'); 16 | 17 | % Declaring Kalman filter variables 18 | xp = zeros(r,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) 19 | Vp = zeros(r,r,T); % V(x(t)|y(1:t-1),S(t-1)=i) 20 | xf = zeros(p*r,T); % E(x(t)|y(1:t)) 21 | Vf = zeros(p*r,p*r,T); % V(x(t)|y(1:t),S(t-1)=i,S(t)=j) 22 | 23 | % Declaring Kalman smoothing variables 24 | xs = zeros(r,T); % E(x(t)|y(1:T)) 25 | % Vst = zeros(p*r,p*r); % V(X(t)|y(1:T)) 26 | % CVst = zeros(r,p*r); % Cov(x(t+1),X(t)|y(1:T)) 27 | 28 | % Other outputs 29 | sum_MCP = zeros(r,p*r,M); % sum(t=2:T,S(t)=j) E(x(t)X(t-1)'|y(1:T)) 30 | sum_MP = zeros(r,r,M); % sum(t=2:T,S(t)=j) E(x(t)x(t)'|y(1:T)) 31 | sum_MPb = zeros(p*r,p*r,M); % sum(t=2:T,S(t)=j) E(X(t-1)X(t-1)'|y(1:T)) 32 | % sum_P = zeros(r,r) % sum(t=1:T) E(x(t)x(t)'|y(1:T)) 33 | MP0 = zeros(p*r,p*r,M); % P(S(1)=j|y(1:T)) * E(X(1)X(1)'|y(1:T)) 34 | Mx0 = zeros(p*r,M); % P(S(1)=j|y(1:T)) * E(X(1)|y(1:T)) 35 | 36 | % Log-likelihood 37 | L = zeros(1,T); 38 | % Constant for likelihood calculation 39 | cst = - N/2 * log(2*pi); 40 | 41 | % Expand matrices 42 | Abig = repmat(diag(ones((p-1)*r,1),-r),[1,1,M]); 43 | Abig(1:r,:,:) = A; 44 | Cbig = zeros(N,p*r); 45 | Cbig(:,1:r) = C; 46 | Qbig = zeros(p*r,p*r,M); 47 | Qbig(1:r,1:r,:) = Q; 48 | 49 | 50 | %-------------------------------------------------------------------------% 51 | % Switching Kalman Filter % 52 | %-------------------------------------------------------------------------% 53 | 54 | 55 | 56 | % MAIN LOOP 57 | for t=1:T 58 | 59 | St = S(t); 60 | 61 | % Prediction of x(t) 62 | if t == 1 63 | xpt = repmat(mu(:,St),p,1); 64 | Vpt = kron(eye(p),Sigma(:,:,St)); 65 | else 66 | xpt = Abig(:,:,St) * xf(:,t-1); 67 | Vpt = Abig(:,:,St) * Vf(:,:,t-1) * Abig(:,:,St).' + Qbig(:,:,St); 68 | end 69 | % Store predictions 70 | xp(:,t) = xpt(1:r); 71 | Vp(:,:,t) = Vpt(1:r,1:r); 72 | 73 | % Prediction error for y(t) 74 | e = y(:,t) - C * xpt(1:r); 75 | Ve = C * Vpt(1:r,1:r) * C.' + R; % Variance of prediction error 76 | % Ve = 0.5 * (Ve+Ve.'); 77 | % Check that variance matrix is positive definite and well-conditioned 78 | if safe 79 | Ve = regfun(Ve,abstol,reltol); 80 | end 81 | 82 | % % Filtering update 83 | % CVp = C * Vpt; 84 | % K = (CVp.') / Ve; % Kalman gain matrix 85 | % xf(:,t) = xpt + K * e; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 86 | % Vf(:,:,t) = Vpt - K * CVp; % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 87 | % if t == T 88 | % % Cov(x(t),x(t-1)|S(t-1)=i,S(t)=j,y(1:t)) 89 | % CVf = (I - K*C) * A(:,:,j) * Vf(:,:,t-1); 90 | % end 91 | 92 | 93 | [Lchol,err] = chol(Ve,'lower'); 94 | if ~err % case: Ve definite positive 95 | LinvCVp = (Lchol\Cbig) * Vpt; 96 | Linve = Lchol\e; 97 | % Log-Likelihood 98 | L(t) = cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2); 99 | % Filtering update 100 | xf(:,t) = xpt + LinvCVp.' * Linve; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 101 | Vf(:,:,t) = Vpt - (LinvCVp.' * LinvCVp); % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 102 | else 103 | L(t) = -Inf; 104 | xf(:,t) = xpt; 105 | Vf(:,:,t) = Vpt; 106 | end 107 | 108 | end % end t loop 109 | 110 | idx = isinf(L); 111 | if any(idx) 112 | L(idx) = min(L(~idx)) + log(eps); 113 | end 114 | L = sum(L); 115 | 116 | 117 | 118 | 119 | 120 | 121 | %-------------------------------------------------------------------------% 122 | % Switching Kalman Smoother % 123 | %-------------------------------------------------------------------------% 124 | 125 | 126 | 127 | % Initialize smoother at time T 128 | xs(:,T) = xf(1:r,T); 129 | Vst = Vf(:,:,T); 130 | St = S(T); 131 | sum_MP(:,:,St) = (Vst(1:r,1:r) + (xs(:,T) * xs(:,T).')); 132 | 133 | for t = T-1:-1:1 134 | 135 | % Store relevant vectors/matrices from previous iteration 136 | Vstp1 = Vst(1:r,1:r); % V(x(t+1)|S(t+1),y(1:T)) 137 | 138 | % Shorthand 139 | St = S(t); 140 | Stp1 = S(t+1); 141 | 142 | % Smoothed mean and variance of x(t), smoothed cross-covariance of 143 | % x(t+1) & X(t) given S(t)=j and S(t+1)=k 144 | % Kalman smoother gain 145 | % J(t) = V(X(t)|S(t)=j,y(1:t)) * A_k' * V(x(t+1)|S(t)=j,y(1:t))^{-1} 146 | J = Vf(:,:,t) * A(:,:,Stp1).' / Vp(:,:,t+1); 147 | if any(isnan(J(:))) || any(isinf(J(:))) 148 | J = Vf(:,:,t) * A(:,:,Stp1).' * pinv(Vp(:,:,t+1)); 149 | end 150 | % E(X(t)|y(1:T)) 151 | xst = xf(:,t) + J * (xs(:,t+1) - xp(:,t+1)); 152 | xs(:,t) = xst(1:r); 153 | % V(X(t)|y(1:T)) 154 | Vst = Vf(:,:,t) + J * (Vstp1 - Vp(:,:,t+1)) * J.'; 155 | % Cov(x(t+1),X(t)|y(1:T)) = V(x(t+1)|y(1:T)) * J(t)' 156 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 157 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 158 | CVst = Vstp1 * J.'; 159 | 160 | % Required quantities for M step 161 | % E(X(t)X(t)'|y(1:T)) 162 | P = Vst + (xst * xst.'); 163 | % E(x(t+1)X(t)'|y(1:T)) 164 | CP = CVst(1:r,:) + xs(1:r,t+1) * xst.'; 165 | if t > 1 166 | sum_MP(:,:,St) = sum_MP(:,:,St) + P(1:r,1:r); 167 | end 168 | sum_MPb(:,:,Stp1) = sum_MPb(:,:,Stp1) + P; 169 | sum_MCP(:,:,Stp1) = sum_MCP(:,:,Stp1) + CP; 170 | 171 | end % end t loop 172 | 173 | Mx0(:,S(1)) = xst; 174 | MP0(:,:,S(1)) = P; 175 | sum_P = sum(sum_MP,3) + P(1:r,1:r); 176 | xf = xf(1:r,:); 177 | 178 | 179 | -------------------------------------------------------------------------------- /kfs_obs.m: -------------------------------------------------------------------------------- 1 | function [xf,xs,x0,P0,Loglik,sum_CP,sum_MP,sum_Mxy,sum_P,sum_Pb] = ... 2 | kfs_obs(y,M,p,r,S,pars,safe,abstol,reltol) 3 | 4 | 5 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; Sigma = pars.Sigma; 6 | 7 | % Model dimensions 8 | [N,T] = size(y); 9 | 10 | % Notations for state vector 11 | % x(t,j): length r (time t, regime j) 12 | % x(t) = (x(t,1),...,x(t,M)): length M*r (time t, all regimes) 13 | % X(t,j) = (x(t,j),...,x(t-p+1,j)): length p*r (all lags, regime j) 14 | % X(t) = (X(t,1),...,X(t,M)): length M*p*r (all lags, all regimes) 15 | 16 | % Mask for predictive covariance matrix 17 | tmp = zeros(p*r); tmp(1:r,1:r) = 1; 18 | mask_Vp = logical(kron(eye(M),tmp)); 19 | 20 | % Mask for filtering covariance matrix 21 | mask_Vf = logical(kron(eye(M),ones(p*r))); 22 | 23 | % Reshape parameter estimates 24 | Atmp = kron(eye(M),diag(ones((p-1)*r,1),-r)); 25 | tmp = zeros(p*r); tmp(1:r,:) = 1; 26 | mask_A = (kron(eye(M),tmp) == 1); 27 | Atmp(mask_A) = A(:); 28 | Asmall = zeros(M*r,M*p*r); % used for smoothing, size Mr x Mpr 29 | mask_A = (kron(eye(M),ones(r,p*r)) == 1); 30 | Asmall(mask_A) = A(:); 31 | Ctmp = zeros(N,M*p*r,M); 32 | for j = 1:M 33 | idx = (j-1)*p*r+1:(j-1)*p*r+r; 34 | Ctmp(:,idx,j) = C(:,:,j); 35 | end 36 | Qtmp = zeros(M*p*r); 37 | Qtmp(mask_Vp) = Q(:); 38 | mutmp = repmat(mu,p,1); 39 | Sigmatmp = zeros(M*p*r); 40 | tmp = repmat(reshape(Sigma,r*r,M),p,1); 41 | mask_Sigma = (kron(eye(M*p),ones(r)) == 1); 42 | Sigmatmp(mask_Sigma) = tmp; 43 | A = Atmp; % size Mpr x Mpr (used for filtering) 44 | C = Ctmp; % size N x Mpr x M 45 | Q = Qtmp; % size Mpr x Mpr 46 | mu = mutmp(:); % size Mpr x 1 47 | Sigma = Sigmatmp; % size Mpr x Mpr 48 | 49 | 50 | 51 | % Remove warnings when inverting singular matrices 52 | warning('off','MATLAB:singularMatrix'); 53 | warning('off','MATLAB:nearlySingularMatrix'); 54 | warning('off','MATLAB:illConditionedMatrix'); 55 | 56 | % Declare Kalman filter variables 57 | xp = zeros(M*r,T); % E(x(t)|y(1:t-1)) 58 | Vp = zeros(M*r^2,T); % V(x(t,j)|y(1:t-1)), j=1:M 59 | Loglik = zeros(T,1); % P(y(t)|y(1:t-1)) 60 | xf = zeros(M*p*r,T); % E(X(t)|y(1:t)) 61 | Vf = zeros(M*(p*r)^2,T); % V(X(t,j)|y(1:t)), j=1:M 62 | 63 | % Declare Kalman smoothing variables 64 | xs = zeros(M*r,T); % E(x(t)|y(1:T)) 65 | 66 | 67 | 68 | 69 | %-------------------------------------------------------------------------% 70 | % Switching Kalman Filter % 71 | %-------------------------------------------------------------------------% 72 | 73 | 74 | 75 | % Indices of x(t) (size Mr) in X(t) (size Mpr) 76 | indt = zeros(p*r,M); 77 | indt(1:r,:) = 1; 78 | indt = find(indt); 79 | 80 | % Constant for likelihood calculation 81 | cst = -N/2 * log(2*pi); 82 | 83 | 84 | % FILTERING LOOP 85 | for t = 1:T 86 | 87 | % Prediction of x(t) 88 | if t == 1 89 | xpt = mu; 90 | Vpt = Sigma; 91 | else 92 | xpt = A * xf(:,t-1); 93 | Vpt = A * Vft * A.' + Q; % here Vft = V(X(t-1)|y(1:t-1)) 94 | % Vpt = 0.5 * (Vpt + Vpt.'); 95 | end 96 | xp(:,t) = xpt(indt); 97 | Vp(:,t) = Vpt(mask_Vp); 98 | 99 | % Prediction error for y(t) 100 | Ct = C(:,:,S(t)); 101 | e = y(:,t) - Ct * xpt; 102 | CVp = Ct * Vpt; % Ct * V(x(t)|y(1:t-1)) 103 | Ve = CVp * Ct.' + R; % Variance of prediction error 104 | 105 | % Check that variance matrix is positive definite and well-conditioned 106 | if safe 107 | Ve = regfun(Ve,abstol,reltol); 108 | end 109 | 110 | % Choleski decomposition 111 | [Lchol,err] = chol(Ve,'lower'); 112 | if ~err % case: Ve definite positive 113 | LinvCVp = Lchol\CVp; 114 | Linve = Lchol\e; 115 | % Log-Likelihood 116 | Loglik(t) = cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2); 117 | % Filtering update 118 | xf(:,t) = xpt + LinvCVp.' * Linve; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 119 | Vft = Vpt - (LinvCVp.' * LinvCVp); % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 120 | Vf(:,t) = Vft(mask_Vf); 121 | else 122 | Loglik(t) = -Inf; 123 | xf(:,t) = xpt; 124 | Vf(:,t) = Vpt(mask_Vf); 125 | end 126 | 127 | end 128 | 129 | % Replace infinite or undefined values of log-likelihood by very small 130 | % value 131 | is_singular_Ve = isnan(Loglik) | isinf(Loglik); 132 | if any(is_singular_Ve) 133 | Loglik(is_singular_Ve) = min(Loglik(~is_singular_Ve)) + log(eps); 134 | end 135 | 136 | Loglik = sum(Loglik); 137 | 138 | 139 | 140 | 141 | %-------------------------------------------------------------------------% 142 | % Switching Kalman Smoother % 143 | %-------------------------------------------------------------------------% 144 | 145 | 146 | 147 | % Mask for r x r diagonal blocks in Mr x Mr matrix 148 | mask_Mr = (kron(eye(M),ones(r)) == 1); 149 | 150 | % Initialize smoother at time T 151 | xst = xf(:,T); 152 | xs(:,T) = xst(indt); 153 | Vft = zeros(M*p*r); 154 | Vst = zeros(M*p*r); % diag(V(X(T,j)|y(1:T))), j=1:M 155 | Vst(mask_Vf) = Vf(:,T); 156 | Vptp1 = zeros(M*r); 157 | PT = Vst + (xst * xst.'); 158 | sum_CP = zeros(M*r,M*p*r); % sum(t=2:T) E(x(t)X(t-1)'|y(1:T)) 159 | sum_MP = zeros(M*p*r,M*p*r,M); % sum(t:S(t)=j) E(X(t)X(t)'|y(1:T)), j=1:M 160 | sum_MP(:,:,S(T)) = PT; 161 | 162 | 163 | % To calculate smoothed means, covariances, and cross-covariances for all 164 | % components X(t,j), j=1:M, simultaneously, relevant predicted and filtered 165 | % covariance matrices are expressed in *block-diagonal* form 166 | 167 | for t=T-1:-1:1 168 | 169 | % Store smoothed mean & variance from previous iteration 170 | xstp1 = xst; % E(X(t+1)|y(1:T)) 171 | Vstp1 = Vst; % diag(V(X(t+1,j)|y(1:T))), j=1:M 172 | 173 | % Predicted and filtered mean and variance (for faster access) 174 | xptp1 = xp(:,t+1); 175 | Vptp1(mask_Mr) = Vp(:,t+1); % diag(V(x(t+1,j)|y(1:t))), j=1:M 176 | xft = xf(:,t); 177 | Vft(mask_Vf) = Vf(:,t); % diag(V(X(t,j)|y(1:t))), j=1:M 178 | 179 | % Kalman smoother gain J(t) 180 | % J(t) = V(X(t)|y(1:t)) * A' * V(x(t+1)|y(1:t))^{-1} 181 | J = Vft * (Asmall.') / Vptp1; 182 | if any(isnan(J(:))) || any(isinf(J(:))) 183 | J = Vft * Asmall.' * pinv(Vptp1); 184 | end 185 | 186 | % Smoothed mean vector E(X(t)|y(1:T)) 187 | xst = xft + J * (xstp1(indt) - xptp1); 188 | xs(:,t) = xst(indt); 189 | 190 | % Smoothed covariance matrix diag(V(X(t,j)|y(1:T))), j=1:M 191 | Vst = Vft + J * (Vstp1(indt,indt) - Vptp1) * J.'; 192 | 193 | % Smoothed cross-covariance matrix diag(Cov(x(t+1,j),X(t,j)|y(1:T))), j=1:M 194 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 195 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 196 | % Cov(x(t+1),X(t)|y(1:T)) = V(x(t+1)|y(1:T)) * J(t)' 197 | CVst = Vstp1(indt,indt) * J.'; 198 | 199 | % Required quantities for M step 200 | P = Vst + (xst * xst.'); 201 | sum_MP(:,:,S(t)) = sum_MP(:,:,S(t)) + P; 202 | CP = CVst + (xstp1(indt) * xst.'); 203 | sum_CP = sum_CP + CP; 204 | 205 | end % end t loop 206 | 207 | 208 | 209 | %-------------------------------------------------------------------------% 210 | % Process outputs % 211 | %-------------------------------------------------------------------------% 212 | 213 | 214 | % E(x(t,j)|y(1:t)), t=1:T,j=1:M 215 | xf = reshape(xf,[p*r,M,T]); 216 | xf = xf(1:r,:,:); 217 | 218 | % E(x(t,j)|y(1:T)), t=1:T,j=1:M 219 | xs = reshape(xs,[r,M,T]); 220 | 221 | % E(X(1,j)|y(1:T)), j=1:M 222 | x0 = reshape(xst,[p*r,M]); 223 | 224 | % E(X(1,j)X(1,j)'|y(1:T)), j=1:M 225 | P0 = reshape(P(mask_Vf),[p*r,p*r,M]); 226 | 227 | % sum(t=2:T) E(x(t,j)X(t-1,j)'|y(1:T)), j=1:M 228 | mask_CP = (kron(eye(M),ones(r,p*r)) == 1); 229 | sum_CP = reshape(sum_CP(mask_CP),[r,p*r,M]); 230 | 231 | % sum(t:S(t=j)) E(x(t,j)x(t,j)'|y(1:T)), j=1:M 232 | sum_MPcopy = sum_MP; 233 | sum_MP = zeros(r,r,M); 234 | for j = 1:M 235 | idx = (j-1)*p*r+1:(j-1)*p*r+r; 236 | sum_MP(:,:,j) = sum_MPcopy(idx,idx,j); 237 | end 238 | 239 | % sum(t:S(t)=j) E(x(t,j)|y(1:T))y(t)', j=1:M 240 | sum_Mxy = zeros(r,N,M); 241 | for j = 1:M 242 | idx = find(S == j); 243 | Tj = numel(idx); 244 | sum_Mxy(:,:,j) = reshape(xs(:,j,idx),r,Tj) * y(:,idx).'; 245 | end 246 | 247 | % sum(t=2:T) E(x(t,j)x(t,j)'|y(1:T)), j=1:M 248 | sum_P = sum(sum_MPcopy,3) - P; 249 | sum_P = reshape(sum_P(mask_Vp),r,r,M); 250 | 251 | % sum(t=1:T-1) E(X(t,j)X(t,j)'|y(1:T)), j=1:M 252 | sum_Pb = sum(sum_MPcopy,3) - PT; 253 | sum_Pb = reshape(sum_Pb(mask_Vf),[p*r,p*r,M]); 254 | 255 | 256 | end % END FUNCTION 257 | 258 | -------------------------------------------------------------------------------- /myinv.m: -------------------------------------------------------------------------------- 1 | function xinv = myinv(x) 2 | try 3 | xinv = inv(x); 4 | catch 5 | xinv = pinv(x); 6 | end 7 | 8 | if any(isnan(xinv(:))) 9 | xinv = pinv(x); 10 | end 11 | -------------------------------------------------------------------------------- /regfun.m: -------------------------------------------------------------------------------- 1 | % OLD VERSION: 2 | function out = regfun(x,abstol,reltol) 3 | [V,d] = eig(x,'vector'); 4 | lb = max(abstol,reltol*max(d)); 5 | good = (d >= lb); 6 | if all(good) 7 | out = x; 8 | else 9 | d(~good) = lb; 10 | out = V * diag(d) * V.'; 11 | if ~ issymmetric(out) 12 | out = (out+out.')/2; 13 | end 14 | end 15 | 16 | % NEW VERSION: add a multiple of the identity matrix to improve 17 | % conditioning 18 | 19 | % Input: 20 | % x: symmetric square matrix 21 | % abstol: smallest eigenvalue of regularized matrix 22 | % inverse of maximum condition number of regularized matrix 23 | 24 | % function out = regfun(x,abstol,reltol) 25 | % [v,d] = eig(x,'vector'); 26 | % out = x; 27 | % if any(d < abstol) 28 | % d(d < abstol) = abstol; 29 | % out = v * diag(d) * v.'; 30 | % if ~issymmetric(out) 31 | % out = 0.5 * (out+out.'); 32 | % end 33 | % end 34 | % dmax = max(d); 35 | % dmin = min(d); 36 | % lam = max(abstol-dmin,(reltol*dmax-dmin)/(1-reltol)); 37 | % if lam > 0 38 | % out = out + diag(repelem(lam,numel(d))); 39 | % end 40 | % end 41 | -------------------------------------------------------------------------------- /simulate_dyn.m: -------------------------------------------------------------------------------- 1 | function [y,S,x] = simulate_dyn(pars,T) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % SIMULATE DATA ACCORDING TO STATE-SPACE MODEL 6 | % WITH MARKOV-SWITCHING DYNAMICS 7 | % 8 | % 9 | % PURPOSE 10 | % Simulate processes x(t), y(t), and S(t) (t=1:T) in model 11 | % y(t) = C x(t) + w(t) 12 | % x(t) = A(1,S(t)) x(t-1) + ... + A(p,S(t)) x(t-p) + v(t,S(t)) 13 | % where w(1),...,w(T) iid ~ N(0,R), v(1),...,v(T) are independent 14 | % conditionally on S(1),...,S(T), v(t)|S(t)=j ~ N(0,Q(j)) for j=1:M, 15 | % and S(t),t=1:T, is a Markov chain with M states (initial probabilities 16 | % Pi(j) and transition probability matrix Z(i,j)). 17 | % 18 | % USAGE 19 | % [y,S,x] = simulate_dyn(pars,T) 20 | % 21 | % INPUTS 22 | % pars: structure containing model parameters in fields 'A', 'C', 'Q', 'R', 23 | % 'mu', 'Sigma', 'Pi', 'Z' with respective dimensions (r,r,p,M), 24 | % (N,r), (r,r,M), (N,N), (r,M), (r,r,M), (M,1), (M,M) 25 | % T: time series length 26 | % 27 | % OUTPUTS 28 | % x: state vectors, dim = (r,T) 29 | % y: observation vectors, dim = (N,T) 30 | % S: regimes, dim = (1,T) 31 | % 32 | %-------------------------------------------------------------------------- 33 | 34 | 35 | % Model parameters and dimensions 36 | A = pars.A; 37 | C = pars.C; 38 | Q = pars.Q; 39 | R = pars.R; 40 | mu = pars.mu; 41 | Sigma = pars.Sigma; 42 | Pi = pars.Pi; 43 | Z = pars.Z; 44 | [r,~,p,M] = size(A); 45 | N = size(C,1); 46 | A = reshape(A,r,p*r,M); 47 | 48 | % Generate regime sequence S(t) 49 | S = zeros(1,T); 50 | cumPi = [0;cumsum(Pi(1:M-1))]; 51 | cumZ = [zeros(M,1), cumsum(Z(:,1:M-1),2)]; 52 | cp = []; 53 | % Make sure that there is at least one change point 54 | % (This part can be commented out) 55 | while isempty(cp) 56 | u = rand(1,T); 57 | S(1) = sum(u(1) > cumPi); 58 | for t = 2:T 59 | S(t) = sum(u(t) > cumZ(S(t-1),:)); 60 | end 61 | cp = find(diff(S) ~= 0) + 1; 62 | end 63 | 64 | % Generate state vectors x(t) 65 | v = zeros(r,T); 66 | for j = 1:M 67 | Sj = (S == j); 68 | v(:,Sj) = mvnrnd(zeros(1,r),Q(:,:,j),sum(Sj))'; 69 | end 70 | x = zeros(r,T+p-1); % x(t), t=2-p:T 71 | x(:,1:p) = mvnrnd(mu(:,S(1))',Sigma(:,:,S(1)),p)'; 72 | for t = 1:T-1 73 | x(:,t+p) = A(:,:,S(t+1)) * ... 74 | reshape(x(:,t+p-1:-1:t),[],1) + v(:,t+1); 75 | end 76 | x = x(:,p:end); % discard (p-1) first values 77 | 78 | % Generate observations y(t) 79 | w = mvnrnd(zeros(1,N),R,T)'; 80 | y = C * x + w; 81 | 82 | -------------------------------------------------------------------------------- /simulate_obs.m: -------------------------------------------------------------------------------- 1 | function [y,S,x] = simulate_obs(pars,T) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % SIMULATE DATA ACCORDING TO STATE-SPACE MODEL 6 | % WITH MARKOV-SWITCHING OBSERVATIONS 7 | % 8 | % 9 | % PURPOSE 10 | % Simulate processes x(t), y(t), and S(t) (t=1:T) in model 11 | % 12 | % y(t) = C(S(t)) x(t,S(t)) + w(t) 13 | % x(t,j) = A(1,j) x(t-1,j) + ... + A(p,j) x(t-p,j) + v(t,j) 14 | % 15 | % where w(1),...,w(T) iid ~ N(0,R), v(1,j),...,v(T,j) are iid ~ N(0,Q(j)) 16 | % for j=1:M, and S(t),t=1:T, is a Markov chain with M states (initial 17 | % probabilities Pi(j) and transition probability matrix Z(i,j)). 18 | % 19 | % USAGE 20 | % [y,S,x] = simulate_obs(pars,T) 21 | % 22 | % INPUTS 23 | % pars: struct with fields 'A', 'C', 'Q', 'R', 'mu', 'Sigma', 'Pi', 'Z' 24 | % of respective dimensions (r,r,p,M), (N,r), (r,r,M), (N,N), (r,M), 25 | % (r,r,M), (M,1), (M,M) 26 | % T: time series length 27 | % 28 | % OUTPUTS 29 | % x: state vectors, dim = (r,T) 30 | % y: observation vectors, dim = (N,T) 31 | % S: regimes, dim = (1,T) 32 | % 33 | %-------------------------------------------------------------------------- 34 | 35 | 36 | % Model parameters and dimensions 37 | A = pars.A; 38 | C = pars.C; 39 | Q = pars.Q; 40 | R = pars.R; 41 | mu = pars.mu; 42 | Sigma = pars.Sigma; 43 | Pi = pars.Pi; 44 | Z = pars.Z; 45 | [r,~,p,M] = size(A); 46 | N = size(C,1); 47 | 48 | % Generate regime sequence S(t) 49 | if M == 1 50 | S = ones(1,T); 51 | else 52 | S = zeros(1,T); 53 | cumPi = [0;cumsum(Pi(1:M-1))]; 54 | cumZ = [zeros(M,1), cumsum(Z(:,1:M-1),2)]; 55 | cp = []; 56 | % Make sure that there is at least one change point 57 | % (This part can be commented out) 58 | while isempty(cp) 59 | u = rand(1,T); 60 | S(1) = sum(u(1) > cumPi); 61 | for t = 2:T 62 | S(t) = sum(u(t) > cumZ(S(t-1),:)); 63 | end 64 | cp = find(diff(S) ~= 0) + 1; 65 | end 66 | end 67 | 68 | % Generate state vectors x(t) and observations y(t) 69 | y = zeros(N,T); 70 | for j = 1:M 71 | x = zeros(r,T+p-1); % x(t), t=2-p:T 72 | cholSigma = chol(Sigma(:,:,j),'lower'); 73 | x(:,1:p) = mu(:,j) + cholSigma * randn(r,p); 74 | % x(:,1:p) = mvnrnd(mu(:,j)',Sigma(:,:,j),p)'; 75 | A_j = reshape(A(:,:,:,j),r,p*r); 76 | cholQ = chol(Q(:,:,j),'lower'); 77 | v = cholQ * rand(r,T); 78 | % mvnrnd(zeros(1,r),Q(:,:,j),T)'; 79 | for t = 1:T-1 80 | x(:,t+p) = A_j * reshape(x(:,t+p-1:-1:t),[],1) + v(:,t+1); 81 | end 82 | x = x(:,p:end); % discard (p-1) first values 83 | idx = (S == j); 84 | y(:,idx) = C(:,:,j) * x(:,idx); 85 | end 86 | cholR = chol(R,'lower'); 87 | y = y + cholR * randn(N,T); 88 | % y = y + mvnrnd(zeros(1,N),R,T)'; 89 | 90 | -------------------------------------------------------------------------------- /simulate_var.m: -------------------------------------------------------------------------------- 1 | function [y,S] = simulate_var(pars,T) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % SIMULATE REALIZATION OF SWITCHING VECTOR AUTOREGRESSIVE MODEL 6 | % 7 | % 8 | % PURPOSE 9 | % Simulate processes y(t) and S(t) (t=1:T) in model 10 | % y(t) = A(1,S(t)) y(t-1) + ... + A(p,S(t)) y(t-p) + w(t,S(t)) 11 | % where w(1),...,w(T) iid ~ N(0,Q(j)), v(1),...,v(T) are independent 12 | % conditionally on S(1),...,S(T) and S(t),t=1:T, is a Markov chain with M 13 | % states (initial probabilities Pi(j) and transition probability matrix 14 | % Z(i,j)). 15 | % 16 | % USAGE 17 | % [y,S] = simulate_dyn(pars,T) 18 | % 19 | % INPUTS 20 | % pars: structure containing model parameters 'A', 'Q', 'mu', 'Sigma', 21 | % 'Pi', 'Z' with respective dimensions (r,r,p,M), (r,r,M), (r,M), 22 | % (r,r,M), (M,1), (M,M) 23 | % T: time series length 24 | % 25 | % OUTPUTS 26 | % y: observation vectors, dim = (N,T) 27 | % S: regimes, dim = (1,T) 28 | % 29 | %-------------------------------------------------------------------------- 30 | 31 | 32 | % Model parameters and dimensions 33 | A = pars.A; 34 | Q = pars.Q; 35 | mu = pars.mu; 36 | Sigma = pars.Sigma; 37 | Pi = pars.Pi; 38 | Z = pars.Z; 39 | [N,~,p,M] = size(A); 40 | A = reshape(A,N,p*N,M); 41 | 42 | % Generate regime sequence S(t) 43 | if M == 1 44 | S = ones(1,T); 45 | else 46 | S = zeros(1,T); 47 | cumPi = [0;cumsum(Pi(1:M-1))]; 48 | cumZ = [zeros(M,1), cumsum(Z(:,1:M-1),2)]; 49 | cp = []; 50 | % Make sure that there is at least one change point 51 | % (This part can be commented out) 52 | while isempty(cp) 53 | u = rand(1,T); 54 | S(1:p) = sum(u(1) > cumPi); 55 | for t = p+1:T 56 | S(t) = sum(u(t) > cumZ(S(t-1),:)); 57 | end 58 | cp = find(diff(S) ~= 0) + 1; 59 | end 60 | end 61 | 62 | % Generate time series y(t) 63 | w = zeros(N,T); 64 | for j = 1:M 65 | Sj = (S == j); 66 | w(:,Sj) = mvnrnd(zeros(1,N),Q(:,:,j),sum(Sj))'; 67 | end 68 | y = zeros(N,T); 69 | for t = 1:p 70 | y(:,t) = mvnrnd(mu(:,S(t))',Sigma(:,:,S(t)))'; 71 | end 72 | for t = p+1:T 73 | y(:,t) = A(:,:,S(t)) * reshape(y(:,t-1:-1:t-p),[],1) + w(:,t); 74 | end 75 | 76 | 77 | -------------------------------------------------------------------------------- /skfs_dyn.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 2 | skfs_dyn(y,M,p,r,pars,beta,safe,abstol,reltol) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % SWITCHING KALMAN FILTER AND SMOOTHER 7 | % IN STATE-STATE MODEL WITH SWITCHING DYNAMICS 8 | % 9 | % PURPOSE 10 | % This is not meant to be directly called by the user. It is called by 11 | % the function 'switch_dyn' to complete the E step of the EM algorithm 12 | % 13 | % USAGE 14 | % [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 15 | % skfs_dyn(y,M,p,r,pars,beta,safe,abstol,reltol) 16 | % 17 | % REFERENCES 18 | % C. J. Kim (1994) "Dynamic Linear Models with Markov-Switching", Journal of 19 | % Econometrics 60, 1-22. 20 | % K. P. Murphy (1998) "Switching Kalman Filters", Technical Report 21 | % 22 | %-------------------------------------------------------------------------- 23 | 24 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; 25 | Sigma = pars.Sigma; Pi = pars.Pi; Z = pars.Z; 26 | 27 | % Model dimensions 28 | [N,T] = size(y); 29 | % Size of 'small' state vector x(t): r 30 | % Size of 'big' state vector X(t) = (x(t),...,x(t-p+1)): p * r 31 | 32 | % Remove warnings when inverting singular matrices 33 | warning('off','MATLAB:singularMatrix'); 34 | warning('off','MATLAB:nearlySingularMatrix'); 35 | warning('off','MATLAB:illConditionedMatrix'); 36 | 37 | % Declare Kalman filter variables 38 | % xp = zeros(p*r,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) 39 | % Vp = zeros(p*r,p*r,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i) 40 | xp = zeros(r,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 41 | Vp = zeros(r,r,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 42 | xf = zeros(r,T); % E(x(t)|y(1:t)) 43 | xf1 = zeros(p*r,M,T); % E(X(t)|y(1:t),S(t)=j) 44 | xf2 = zeros(p*r,M,M); % E(X(t)|y(1:t),S(t-1)=i,S(t)=j) 45 | Vf1 = zeros(p*r,p*r,M,T); % V(X(t)|y(1:t),S(t)=j) 46 | Vf2 = zeros(p*r,p*r,M,M); % V(X(t)|y(1:t),S(t-1)=i,S(t)=j) 47 | % CVf2 = zeros(r,r,M,M); % Cov(x(t),x(t-1)|y(1:t),S(t-1)=i,S(t)=j) 48 | Lp = zeros(M,M); % P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 49 | Mf = zeros(M,T); % P(S(t)=j|y(1:t)) 50 | % Mf2 = zeros(M,M); % P(S(t-1)=i,S(t)=j|y(1:t)) 51 | 52 | % Declare Kalman smoothing variables 53 | xs = zeros(r,T); % E(x(t)|y(1:T)) 54 | xs2 = zeros(p*r,M,M); % E(X(t)|y(1:T),S(t)=j,S(t+1)=k) 55 | Vs2 = zeros(p*r,p*r,M,M); % V(X(t)|y(1:T),S(t)=j,S(t+1)=k) 56 | CVs1 = zeros(r,p*r,M); % Cov(x(t+1),X(t)|y(1:T),S(t+1)=k) 57 | CVs2 = zeros(r,p*r,M,M); % Cov(x(t+1),X(t)|y(1:t),S(t)=j,S(t+1)=k) 58 | Ms = zeros(M,T); % P(S(t)=j|y(1:T)) 59 | 60 | % Other outputs 61 | sum_Ms2 = zeros(M,M); % sum(t=2:T) P(S(t-1)=i,S(t)=j|y(1:T)) 62 | sum_MCP = zeros(r,p*r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)X(t-1)'|S(t)=j,y(1:T)) 63 | sum_MP = zeros(r,r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 64 | sum_MPb = zeros(p*r,p*r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(X(t-1)X(t-1)'|S(t)=j,y(1:T)) 65 | % sum_P = zeros(r,r) % sum(t=1:T) E(x(t)x(t)'|S(t)=j,y(1:T)) 66 | MP0 = zeros(p*r,p*r,M); % P(S(1)=j|y(1:T)) * E(X(1)X(1)'|S(t)=j,y(1:T)) 67 | Mx0 = zeros(p*r,M); % P(S(1)=j|y(1:T)) * E(X(1)|S(t)=j,y(1:T)) 68 | 69 | 70 | 71 | % Auxiliary quantities 72 | cst = - N / 2 * log(2*pi); 73 | 74 | % Expand matrices 75 | Abig = repmat(diag(ones((p-1)*r,1),-r),[1,1,M]); 76 | Abig(1:r,:,:) = A; 77 | % Cbig = zeros(N,p*r); 78 | % Cbig(:,1:r) = C; 79 | Qbig = zeros(p*r,p*r,M); 80 | Qbig(1:r,1:r,:) = Q; 81 | 82 | 83 | %-------------------------------------------------------------------------% 84 | % Switching Kalman Filter % 85 | %-------------------------------------------------------------------------% 86 | 87 | 88 | % Initialize filter 89 | Acc = zeros(M,1); 90 | for j=1:M 91 | Sigma_j = kron(eye(p),Sigma(:,:,j)); 92 | e = y(:,1) - C * mu(:,j); 93 | Ve = C * Sigma_j(1:r,1:r) * C.' + R; 94 | if safe 95 | Ve = regfun(Ve,abstol,reltol); 96 | end 97 | Lchol = chol(Ve,'lower'); 98 | LinvCVp = (Lchol\C) * Sigma_j(1:r,:); 99 | Linve = Lchol\e; 100 | Acc(j) = Pi(j) * exp(cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2)); 101 | xf1(:,j,1) = repmat(mu(:,j),p,1) + LinvCVp.' * Linve; 102 | Vf1(:,:,j,1) = Sigma_j - (LinvCVp.' * LinvCVp); 103 | end 104 | 105 | if all(Acc == 0) 106 | Acc = eps * ones(M,1); 107 | end 108 | Mf(:,1) = Acc / sum(Acc); % P(S(1)=j|y(1)) 109 | xf(:,1) = xf1(1:r,:,1) * Mf(:,1); % E(x(1)|y(1)) 110 | L = log(sum(Acc)); % log(P(y(1))) 111 | 112 | Vhat = zeros(p*r,p*r,M); 113 | 114 | % MAIN LOOP 115 | for t=2:T 116 | 117 | for i=1:M 118 | 119 | for j=1:M 120 | 121 | % Prediction of x(t) 122 | xp_ij = Abig(:,:,j) * xf1(:,i,t-1); 123 | Vp_ij = Abig(:,:,j) * Vf1(:,:,i,t-1) * Abig(:,:,j).' + Qbig(:,:,j); 124 | 125 | % Store predictions 126 | xp(:,i,j,t) = xp_ij(1:r); 127 | Vp(:,:,i,j,t) = Vp_ij(1:r,1:r); 128 | 129 | % Prediction error for y(t) 130 | e = y(:,t) - C * xp_ij(1:r); 131 | CVp = C * Vp_ij(1:r,:); 132 | Ve = CVp(:,1:r) * C.' + R; % Variance of prediction error 133 | % Ve = 0.5 * (Ve+Ve.'); 134 | % Check that variance matrix is positive definite and well-conditioned 135 | if safe 136 | Ve = regfun(Ve,abstol,reltol); 137 | end 138 | 139 | % Choleski decomposition 140 | [Lchol,err] = chol(Ve,'lower'); 141 | 142 | if ~err % case: Ve definite positive 143 | LinvCVp = Lchol\CVp; 144 | Linve = Lchol\e; 145 | % Predictive Likelihood L(i,j,t) = P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 146 | Lp(i,j) = exp(cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2)); 147 | % Filtering update 148 | xf2(:,i,j) = xp_ij + LinvCVp.' * Linve; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 149 | Vf2(:,:,i,j) = Vp_ij - (LinvCVp.' * LinvCVp); % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 150 | else 151 | Lp(i,j) = 0; 152 | xf2(:,i,j) = xp_ij; 153 | Vf2(:,:,i,j) = Vp_ij; 154 | end 155 | 156 | end % end j loop 157 | 158 | end % end i loop 159 | 160 | % P(S(t-1)=i,S(t)=j|y(1:t)) (up to multiplicative constant) 161 | Mf2 = Lp .* Z .* Mf(:,t-1); % P(y(t),S(t-1)=i,S(t)=j|y(1:t-1)) 162 | if all(Mf2(:) == 0) 163 | Mf2 = eps * ones(M,M); 164 | end 165 | 166 | % Update log-likelihood 167 | % P(y(t)|y(1:t-1)) = sum(i,j) P(y(t)|S(t-1)=i,S(t)=j,y(1:t-1)) * 168 | % P(S(t)=j|S(t-1)=i) * P(S(t-1)=i|y(t-1)) 169 | L = L + log(sum(Mf2(:))); 170 | 171 | % Filtered occupancy probability of state j at time t 172 | Mf2 = Mf2 / sum(Mf2(:)); % P(S(t-1)=i,S(t)=j|y(1:t)) 173 | Mf(:,t) = sum(Mf2).'; % P(S(t)=j|y(1:t)) 174 | 175 | % Weights of state components 176 | W = Mf2 ./ (Mf(:,t).'); 177 | W(isnan(W)) = 0; 178 | 179 | % Collapse M^2 distributions (X(t)|S(t-1:t),y(1:t)) to M (X(t)|S(t),y(1:t)) 180 | for j = 1:M 181 | xhat = xf2(:,:,j) * W(:,j); 182 | for i = 1:M 183 | m = xf2(:,i,j) - xhat; 184 | Vhat(:,:,i) = W(i,j) * (Vf2(:,:,i,j) + (m*m.')); 185 | end 186 | % Filtered density of x(t) given state j 187 | xf1(:,j,t) = xhat; % E(X(t)|S(t)=j,y(1:t)) (Eq. 11) 188 | Vf1(:,:,j,t) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:t)) (Eq. 12) 189 | end 190 | 191 | % Collapse M distributions (X(t)|S(t),y(1:t)) to 1 (X(t)|y(1:t)) 192 | xf(:,t) = xf1(1:r,:,t) * Mf(:,t); % E(X(t)|y(1:t)) 193 | 194 | end % end t loop 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | %-------------------------------------------------------------------------% 203 | % Switching Kalman Smoother % 204 | %-------------------------------------------------------------------------% 205 | 206 | 207 | % Initialize smoother at time T 208 | Ms(:,T) = Mf(:,T); 209 | xs(:,T) = xf(:,T); 210 | xsb = zeros(p*r,M); 211 | xs1 = xf1(:,:,T); 212 | Vs1 = Vf1(:,:,:,T); 213 | MCP = zeros(r,p*r,M); 214 | MP = zeros(r,r,M); 215 | MPb = zeros(p*r,p*r,M); 216 | for j = 1:M 217 | sum_MP(:,:,j) = Ms(j,T) * (Vs1(1:r,1:r,j) + (xs1(1:r,j) * xs1(1:r,j).')); 218 | end 219 | Vsb = zeros(p*r,p*r,M); 220 | CVhat = zeros(r,p*r,M); 221 | 222 | for t = T-1:-1:1 223 | 224 | % Store relevant vectors/matrices from previous iteration 225 | xs1tp1 = xs1(1:r,:); % E(x(t+1)|S(t+1),y(1:T)) 226 | Vs1tp1 = Vs1(1:r,1:r,:); % V(x(t+1)|S(t+1),y(1:T)) 227 | 228 | % Predicted and filtered mean and variance (for faster access) 229 | xptp1 = xp(:,:,:,t+1); 230 | Vptp1 = Vp(:,:,:,:,t+1); 231 | xf1t = xf1(:,:,t); 232 | Vf1t = Vf1(:,:,:,t); 233 | 234 | % Smoothed mean and variance of x(t), smoothed cross-covariance of 235 | % x(t+1) & X(t) given S(t)=j and S(t+1)=k 236 | for j = 1:M 237 | for k = 1:M 238 | % Kalman smoother gain 239 | % J(t) = V(X(t)|S(t)=j,y(1:t)) * A_k' * V(x(t+1)|S(t)=j,y(1:t))^{-1} 240 | J = Vf1t(:,:,j) * A(:,:,k).' / Vptp1(:,:,j,k); 241 | if any(isnan(J(:))) || any(isinf(J(:))) 242 | J = Vf1t(:,:,j) * A(:,:,k).' * pinv(Vptp1(:,:,j,k)); 243 | end 244 | % E(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 245 | xs2(:,j,k) = xf1t(:,j) + J * (xs1tp1(:,k) - xptp1(:,j,k)); 246 | % V(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 247 | Vs2(:,:,j,k) = Vf1t(:,:,j) + J * (Vs1tp1(:,:,k) - Vptp1(:,:,j,k)) * J.'; 248 | % Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) = V(x(t+1)|S(t+1)=k,y(1:T)) * J(t)' 249 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 250 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 251 | CVs2(:,:,j,k) = Vs1tp1(:,:,k) * J.'; 252 | end 253 | end 254 | 255 | % Smoothed probability distribution of S(t) 256 | U = diag(Mf(:,t)) * Z; % P(S(t)=j|S(t+1)=k,y(1:T)) 257 | U = U ./ sum(U); % scaling 258 | U(isnan(U)) = 0; 259 | 260 | Ms2 = U * diag(Ms(:,t+1)); % P(S(t)=j,S(t+1)=k|y(1:T)) 261 | if all(Ms2(:) == 0) 262 | Ms2 = (1/M^2) * ones(M); 263 | end 264 | if beta < 1 265 | Ms2 = Ms2.^beta; % DAEM 266 | end 267 | Ms2 = Ms2 / sum(Ms2(:)); % for numerical accuracy 268 | sum_Ms2 = sum_Ms2 + Ms2; 269 | Ms(:,t) = sum(Ms2,2); % P(S(t)=j|y(1:T)) 270 | W = Ms2 ./ Ms(:,t); % P(S(t+1)=k|S(t)=j,y(1:T)) 271 | W(isnan(W)) = 0; 272 | 273 | % Collapse M^2 distributions to M 274 | xs2p = permute(xs2,[1,3,2]); 275 | for j = 1:M 276 | xhat = xs2p(:,:,j) * W(j,:).'; 277 | % xs1(:,j) = squeeze(xs2(:,j,:)) * W(j,:)'; @@@@@@@@ 278 | % xs1(:,j) = W(j,:) * squeeze(xs2(:,j,:)); % @@@@@@@@@@@@ 279 | for k = 1:M 280 | m = xs2(:,j,k) - xhat; 281 | Vhat(:,:,k) = W(j,k) * (Vs2(:,:,j,k) + (m*m.')); 282 | end 283 | xs1(:,j) = xhat; % E(X(t)|S(t)=j,y(1:T)) 284 | Vs1(:,:,j) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:T)) 285 | end 286 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) 287 | % B/c of approximation E(x(t+1)|S(t)=j,S(t+1)=k,y(1:T)) ~= E(x(t+1)|S(t+1)=k,y(1:T)), 288 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) ~= sum(j=1:M) Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) * U(j,k) 289 | % with U(j,k) = P(S(t)=j|S(t+1)=k,y(1:T)) 290 | for k = 1:M 291 | for j = 1:M 292 | CVhat(:,:,j) = U(j,k) * CVs2(:,:,j,k); 293 | end 294 | CVs1(:,:,k) = sum(CVhat,3); 295 | end 296 | % V(X(t)|S(t+1)=k,y(1:T)) 297 | for k = 1:M 298 | xsb(:,k) = xs2(:,:,k) * U(:,k); % E(X(t)|S(t+1)=k,y(1:T)) 299 | for j = 1:M 300 | m = xs2(:,j,k) - xsb(:,k); 301 | Vhat(:,:,j) = U(j,k) * (Vs2(:,:,j,k) + (m*m.')); 302 | end 303 | Vsb(:,:,k) = sum(Vhat,3); 304 | end 305 | 306 | % Collapse M distributions to 1 307 | xs(:,t) = xs1(1:r,:) * Ms(:,t); % E(X(t)|y(1:T)) 308 | 309 | % Required quantities for M step 310 | for j=1:M 311 | % P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 312 | MP(:,:,j) = Ms(j,t) * (Vs1(1:r,1:r,j) + (xs1(1:r,j) * xs1(1:r,j).')); 313 | % P(S(t+1)=j|y(1:T)) * E(X(t)X(t)'|S(t+1)=j,y(1:T)) 314 | MPb(:,:,j) = Ms(j,t+1) * (Vsb(:,:,j) + (xsb(:,j) * xsb(:,j).')); 315 | % P(S(t)=j|y(1:T)) * E(x(t+1)X(t)'|S(t+1)=j,y(1:T)) 316 | MCP(:,:,j) = Ms(j,t+1) * (CVs1(1:r,:,j) + xs1tp1(1:r,j) * xsb(:,j).'); 317 | end 318 | if t > 1 319 | sum_MP = sum_MP + MP; 320 | end 321 | sum_MPb = sum_MPb + MPb; 322 | sum_MCP = sum_MCP + MCP; 323 | 324 | end % end t loop 325 | 326 | for j = 1:M 327 | Mx0(:,j) = Ms(j,1) * xs1(:,j); 328 | MP0(:,:,j) = Ms(j,1) * (Vs1(:,:,j) + (xs1(:,j) * xs1(:,j)')); 329 | end 330 | sum_P = sum(sum_MP,3) + sum(MP,3); 331 | 332 | 333 | -------------------------------------------------------------------------------- /skfs_p1_dyn.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 2 | skfs_p1_dyn(y,M,~,r,pars,beta,safe,abstol,reltol) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % SWITCHING KALMAN FILTER AND SMOOTHER 7 | % IN STATE-STATE MODEL WITH SWITCHING DYNAMICS 8 | % 9 | % PURPOSE 10 | % This is not meant to be directly called by the user. It is called by 11 | % the function 'switch_dyn' to complete the E step of the EM algorithm 12 | % 13 | % USAGE 14 | % [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 15 | % skfs_p1_dyn(y,M,~,r,A,C,Q,R,mu,Sigma,Pi,Z,beta,safe,abstol,reltol) 16 | % 17 | % REFERENCES 18 | % C. J. Kim (1994) "Dynamic Linear Models with Markov-Switching", Journal of 19 | % Econometrics 60, 1-22. 20 | % K. P. Murphy (1998) "Switching Kalman Filters", Technical Report 21 | % 22 | %-------------------------------------------------------------------------- 23 | 24 | % Model dimensions 25 | [N,T] = size(y); 26 | p = size(pars.A,2) / size(pars.A,1); 27 | % Size of 'small' state vector x(t): r 28 | % Size of 'big' state vector X(t) = (x(t),...,x(t-p+1)): p * r 29 | 30 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; 31 | Sigma = pars.Sigma; Pi = pars.Pi; Z = pars.Z; 32 | 33 | 34 | % Remove warnings when inverting singular matrices 35 | warning('off','MATLAB:singularMatrix'); 36 | warning('off','MATLAB:nearlySingularMatrix'); 37 | warning('off','MATLAB:illConditionedMatrix'); 38 | 39 | % Declare Kalman filter variables 40 | % xp = zeros(p*r,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) 41 | % Vp = zeros(p*r,p*r,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i) 42 | xp = zeros(r,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 43 | Vp = zeros(r,r,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 44 | xf = zeros(r,T); % E(x(t)|y(1:t)) 45 | xf1 = zeros(r,M,T); % E(X(t)|y(1:t),S(t)=j) 46 | xf2 = zeros(r,M,M); % E(X(t)|y(1:t),S(t-1)=i,S(t)=j) 47 | Vf1 = zeros(r,r,M,T); % V(X(t)|y(1:t),S(t)=j) 48 | Vf2 = zeros(r,r,M,M); % V(X(t)|y(1:t),S(t-1)=i,S(t)=j) 49 | % CVf2 = zeros(r,r,M,M); % Cov(x(t),x(t-1)|y(1:t),S(t-1)=i,S(t)=j) 50 | Lp = zeros(M,M); % P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 51 | Mf = zeros(M,T); % P(S(t)=j|y(1:t)) 52 | % Mf2 = zeros(M,M); % P(S(t-1)=i,S(t)=j|y(1:t)) 53 | 54 | % Declare Kalman smoothing variables 55 | xs = zeros(r,T); % E(x(t)|y(1:T)) 56 | xs2 = zeros(r,M,M); % E(X(t)|y(1:T),S(t)=j,S(t+1)=k) 57 | Vs2 = zeros(r,r,M,M); % V(X(t)|y(1:T),S(t)=j,S(t+1)=k) 58 | CVs1 = zeros(r,r,M); % Cov(x(t+1),X(t)|y(1:T),S(t+1)=k) 59 | CVs2 = zeros(r,r,M,M); % Cov(x(t+1),X(t)|y(1:t),S(t)=j,S(t+1)=k) 60 | Ms = zeros(M,T); % P(S(t)=j|y(1:T)) 61 | 62 | % Other outputs 63 | sum_Ms2 = zeros(M,M); % sum(t=2:T) P(S(t-1)=i,S(t)=j|y(1:T)) 64 | sum_MCP = zeros(r,r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)X(t-1)'|S(t)=j,y(1:T)) 65 | sum_MP = zeros(r,r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 66 | sum_MPb = zeros(r,r,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(X(t-1)X(t-1)'|S(t)=j,y(1:T)) 67 | % sum_P = zeros(r,r) % sum(t=1:T) E(x(t)x(t)'|S(t)=j,y(1:T)) 68 | MP0 = zeros(r,r,M); % P(S(1)=j|y(1:T)) * E(X(1)X(1)'|S(t)=j,y(1:T)) 69 | Mx0 = zeros(r,M); % P(S(1)=j|y(1:T)) * E(X(1)|S(t)=j,y(1:T)) 70 | 71 | 72 | 73 | % Auxiliary quantities 74 | cst = - N / 2 * log(2*pi); 75 | 76 | 77 | 78 | 79 | %-------------------------------------------------------------------------% 80 | % Switching Kalman Filter % 81 | %-------------------------------------------------------------------------% 82 | 83 | 84 | % Initialize filter 85 | Acc = zeros(M,1); 86 | for j=1:M 87 | e = y(:,1) - C * mu(:,j); 88 | Sigma_j = Sigma(:,:,j); 89 | Ve = C * Sigma_j * C.' + R; 90 | if safe 91 | Ve = regfun(Ve,abstol,reltol); 92 | end 93 | Lchol = chol(Ve,'lower'); 94 | LinvCVp = (Lchol\C) * Sigma_j; 95 | Linve = Lchol\e; 96 | Acc(j) = Pi(j) * exp(cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2)); 97 | xf1(:,j,1) = mu(:,j) + LinvCVp.' * Linve; 98 | Vf1(:,:,j,1) = Sigma_j - (LinvCVp.' * LinvCVp); 99 | end 100 | 101 | if all(Acc == 0) 102 | Acc = eps * ones(M,1); 103 | end 104 | Mf(:,1) = Acc / sum(Acc); % P(S(1)=j|y(1)) 105 | xf(:,1) = xf1(:,:,1) * Mf(:,1); % E(x(1)|y(1)) 106 | L = log(sum(Acc)); % log(P(y(1))) 107 | 108 | Vhat = zeros(p*r,p*r,M); 109 | 110 | % MAIN LOOP 111 | for t=2:T 112 | 113 | for i=1:M 114 | 115 | for j=1:M 116 | 117 | % Prediction of x(t) 118 | xp_ij = A(:,:,j) * xf1(:,i,t-1); 119 | Vp_ij = A(:,:,j) * Vf1(:,:,i,t-1) * A(:,:,j).' + Q(:,:,j); 120 | 121 | % Store predictions 122 | xp(:,i,j,t) = xp_ij; 123 | Vp(:,:,i,j,t) = Vp_ij; 124 | 125 | % Prediction error for y(t) 126 | e = y(:,t) - C * xp_ij; 127 | CVp = C * Vp_ij; 128 | Ve = CVp * C.' + R; % Variance of prediction error 129 | % Ve = 0.5 * (Ve+Ve.'); 130 | % Check that variance matrix is positive definite and well-conditioned 131 | if safe 132 | Ve = regfun(Ve,abstol,reltol); 133 | end 134 | 135 | % Choleski decomposition 136 | [Lchol,err] = chol(Ve,'lower'); 137 | 138 | if ~err % case: Ve definite positive 139 | LinvCVp = Lchol\CVp; 140 | Linve = Lchol\e; 141 | % Predictive Likelihood L(i,j,t) = P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 142 | Lp(i,j) = exp(cst - sum(log(diag(Lchol))) - 0.5 * sum(Linve.^2)); 143 | % Filtering update 144 | xf2(:,i,j) = xp_ij + LinvCVp.' * Linve; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 145 | Vf2(:,:,i,j) = Vp_ij - (LinvCVp.' * LinvCVp); % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 146 | else 147 | Lp(i,j) = 0; 148 | xf2(:,i,j) = xp_ij; 149 | Vf2(:,:,i,j) = Vp_ij; 150 | end 151 | 152 | end % end j loop 153 | 154 | end % end i loop 155 | 156 | % P(S(t-1)=i,S(t)=j|y(1:t)) (up to multiplicative constant) 157 | Mf2 = Lp .* Z .* Mf(:,t-1); % P(y(t),S(t-1)=i,S(t)=j|y(1:t-1)) 158 | if all(Mf2(:) == 0) 159 | Mf2 = eps * ones(M,M); 160 | end 161 | 162 | % Update log-likelihood 163 | % P(y(t)|y(1:t-1)) = sum(i,j) P(y(t)|S(t-1)=i,S(t)=j,y(1:t-1)) * 164 | % P(S(t)=j|S(t-1)=i) * P(S(t-1)=i|y(t-1)) 165 | L = L + log(sum(Mf2(:))); 166 | 167 | % Filtered occupancy probability of state j at time t 168 | Mf2 = Mf2 / sum(Mf2(:)); % P(S(t-1)=i,S(t)=j|y(1:t)) 169 | Mf(:,t) = sum(Mf2).'; % P(S(t)=j|y(1:t)) 170 | 171 | % Weights of state components 172 | W = Mf2 ./ (Mf(:,t).'); 173 | W(isnan(W)) = 0; 174 | 175 | % Collapse M^2 distributions (X(t)|S(t-1:t),y(1:t)) to M (X(t)|S(t),y(1:t)) 176 | for j = 1:M 177 | xhat = xf2(:,:,j) * W(:,j); 178 | for i = 1:M 179 | m = xf2(:,i,j) - xhat; 180 | Vhat(:,:,i) = W(i,j) * (Vf2(:,:,i,j) + (m*m.')); 181 | end 182 | % Filtered density of x(t) given state j 183 | xf1(:,j,t) = xhat; % E(X(t)|S(t)=j,y(1:t)) (Eq. 11) 184 | Vf1(:,:,j,t) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:t)) (Eq. 12) 185 | end 186 | 187 | % Collapse M distributions (X(t)|S(t),y(1:t)) to 1 (X(t)|y(1:t)) 188 | xf(:,t) = xf1(:,:,t) * Mf(:,t); % E(X(t)|y(1:t)) 189 | 190 | end % end t loop 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | %-------------------------------------------------------------------------% 199 | % Switching Kalman Smoother % 200 | %-------------------------------------------------------------------------% 201 | 202 | 203 | % Initialize smoother at time T 204 | Ms(:,T) = Mf(:,T); 205 | xs(:,T) = xf(:,T); 206 | xsb = zeros(r,M); 207 | xs1 = xf1(:,:,T); 208 | Vs1 = Vf1(:,:,:,T); 209 | MCP = zeros(r,r,M); 210 | MP = zeros(r,r,M); 211 | MPb = zeros(r,r,M); 212 | for j = 1:M 213 | sum_MP(:,:,j) = Ms(j,T) * (Vs1(:,:,j) + (xs1(:,j) * xs1(:,j).')); 214 | end 215 | Vsb = zeros(p*r,p*r,M); 216 | CVhat = zeros(r,p*r,M); 217 | 218 | for t = T-1:-1:1 219 | 220 | % Store relevant vectors/matrices from previous iteration 221 | xs1tp1 = xs1; % E(x(t+1)|S(t+1),y(1:T)) 222 | Vs1tp1 = Vs1; % V(x(t+1)|S(t+1),y(1:T)) 223 | 224 | % Predicted and filtered mean and variance (for faster access) 225 | xptp1 = xp(:,:,:,t+1); 226 | Vptp1 = Vp(:,:,:,:,t+1); 227 | xf1t = xf1(:,:,t); 228 | Vf1t = Vf1(:,:,:,t); 229 | 230 | % Smoothed mean and variance of x(t), smoothed cross-covariance of 231 | % x(t+1) & X(t) given S(t)=j and S(t+1)=k 232 | for j = 1:M 233 | for k = 1:M 234 | % Kalman smoother gain 235 | % J(t) = V(X(t)|S(t)=j,y(1:t)) * A_k' * V(x(t+1)|S(t)=j,y(1:t))^{-1} 236 | J = Vf1t(:,:,j) * A(:,:,k).' / Vptp1(:,:,j,k); 237 | if any(isnan(J(:))) || any(isinf(J(:))) 238 | J = Vf1t(:,:,j) * A(:,:,k).' * pinv(Vptp1(:,:,j,k)); 239 | end 240 | % E(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 241 | xs2(:,j,k) = xf1t(:,j) + J * (xs1tp1(:,k) - xptp1(:,j,k)); 242 | % V(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 243 | Vs2(:,:,j,k) = Vf1t(:,:,j) + J * (Vs1tp1(:,:,k) - Vptp1(:,:,j,k)) * J.'; 244 | % Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) = V(x(t+1)|S(t+1)=k,y(1:T)) * J(t)' 245 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 246 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 247 | CVs2(:,:,j,k) = Vs1tp1(:,:,k) * J.'; 248 | end 249 | end 250 | 251 | % Smoothed probability distribution of S(t) 252 | U = diag(Mf(:,t)) * Z; % P(S(t)=j|S(t+1)=k,y(1:T)) 253 | U = U ./ sum(U); % scaling 254 | U(isnan(U)) = 0; 255 | 256 | Ms2 = U * diag(Ms(:,t+1)); % P(S(t)=j,S(t+1)=k|y(1:T)) 257 | if all(Ms2(:) == 0) 258 | Ms2 = (1/M^2) * ones(M); 259 | end 260 | if beta < 1 261 | Ms2 = Ms2.^beta; % DAEM 262 | end 263 | Ms2 = Ms2 / sum(Ms2(:)); % for numerical accuracy 264 | sum_Ms2 = sum_Ms2 + Ms2; 265 | Ms(:,t) = sum(Ms2,2); % P(S(t)=j|y(1:T)) 266 | W = Ms2 ./ Ms(:,t); % P(S(t+1)=k|S(t)=j,y(1:T)) 267 | W(isnan(W)) = 0; 268 | 269 | % Collapse M^2 distributions to M 270 | xs2p = permute(xs2,[1,3,2]); 271 | for j = 1:M 272 | xhat = xs2p(:,:,j) * W(j,:).'; 273 | % xs1(:,j) = squeeze(xs2(:,j,:)) * W(j,:)'; @@@@@@@@ 274 | % xs1(:,j) = W(j,:) * squeeze(xs2(:,j,:)); % @@@@@@@@@@@@ 275 | for k = 1:M 276 | m = xs2(:,j,k) - xhat; 277 | Vhat(:,:,k) = W(j,k) * (Vs2(:,:,j,k) + (m*m.')); 278 | end 279 | xs1(:,j) = xhat; % E(X(t)|S(t)=j,y(1:T)) 280 | Vs1(:,:,j) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:T)) 281 | end 282 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) 283 | % B/c of approximation E(x(t+1)|S(t)=j,S(t+1)=k,y(1:T)) ~= E(x(t+1)|S(t+1)=k,y(1:T)), 284 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) ~= sum(j=1:M) Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) * U(j,k) 285 | % with U(j,k) = P(S(t)=j|S(t+1)=k,y(1:T)) 286 | for k = 1:M 287 | for j = 1:M 288 | CVhat(:,:,j) = U(j,k) * CVs2(:,:,j,k); 289 | end 290 | CVs1(:,:,k) = sum(CVhat,3); 291 | end 292 | % V(X(t)|S(t+1)=k,y(1:T)) 293 | for k = 1:M 294 | xsb(:,k) = xs2(:,:,k) * U(:,k); % E(X(t)|S(t+1)=k,y(1:T)) 295 | for j = 1:M 296 | m = xs2(:,j,k) - xsb(:,k); 297 | Vhat(:,:,j) = U(j,k) * (Vs2(:,:,j,k) + (m*m.')); 298 | end 299 | Vsb(:,:,k) = sum(Vhat,3); 300 | end 301 | 302 | % Collapse M distributions to 1 303 | xs(:,t) = xs1 * Ms(:,t); % E(X(t)|y(1:T)) 304 | 305 | % Required quantities for M step 306 | for j=1:M 307 | % P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 308 | MP(:,:,j) = Ms(j,t) * (Vs1(:,:,j) + (xs1(:,j) * xs1(:,j).')); 309 | % P(S(t+1)=j|y(1:T)) * E(X(t)X(t)'|S(t+1)=j,y(1:T)) 310 | MPb(:,:,j) = Ms(j,t+1) * (Vsb(:,:,j) + (xsb(:,j) * xsb(:,j).')); 311 | % P(S(t)=j|y(1:T)) * E(x(t+1)X(t)'|S(t+1)=j,y(1:T)) 312 | MCP(:,:,j) = Ms(j,t+1) * (CVs1(:,:,j) + xs1tp1(:,j) * xsb(:,j).'); 313 | end 314 | if t > 1 315 | sum_MP = sum_MP + MP; 316 | end 317 | sum_MPb = sum_MPb + MPb; 318 | sum_MCP = sum_MCP + MCP; 319 | 320 | end % end t loop 321 | 322 | for j = 1:M 323 | Mx0(:,j) = Ms(j,1) * xs1(:,j); 324 | MP0(:,:,j) = Ms(j,1) * (Vs1(:,:,j) + (xs1(:,j) * xs1(:,j)')); 325 | end 326 | sum_P = sum(sum_MP,3) + sum(MP,3); 327 | 328 | 329 | -------------------------------------------------------------------------------- /skfs_p1r1_dyn.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 2 | skfs_p1r1_dyn(y,M,~,~,pars,beta,safe,abstol,reltol) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % SWITCHING KALMAN FILTER AND SMOOTHER 7 | % IN STATE-STATE MODEL WITH SWITCHING DYNAMICS 8 | % 9 | % PURPOSE 10 | % This is not meant to be directly called by the user. It is called by 11 | % functions 'switch_dyn' and 'fast_dyn' to complete the E step of the EM 12 | % algorithm. ******* SPECIAL CASE p = r = 1 ******* 13 | % 14 | % USAGE 15 | % [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 16 | % skfs_p1r1_dyn(y,M,~,~,A,C,Q,R,mu,Sigma,Pi,Z,beta,safe,abstol,reltol) 17 | % 18 | % REFERENCES 19 | % C. J. Kim (1994) "Dynamic Linear Models with Markov-Switching", Journal of 20 | % Econometrics 60, 1-22. 21 | % K. P. Murphy (1998) "Switching Kalman Filters", Technical Report 22 | % 23 | %-------------------------------------------------------------------------- 24 | 25 | % Model dimensions 26 | [N,T] = size(y); 27 | % Size of 'small' state vector x(t): r 28 | % Size of 'big' state vector X(t) = (x(t),...,x(t-p+1)): p * r 29 | 30 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; 31 | Sigma = pars.Sigma; Pi = pars.Pi; Z = pars.Z; 32 | 33 | 34 | % Remove warnings when inverting singular matrices 35 | warning('off','MATLAB:singularMatrix'); 36 | warning('off','MATLAB:nearlySingularMatrix'); 37 | warning('off','MATLAB:illConditionedMatrix'); 38 | 39 | % Declare Kalman filter variables 40 | % xp = zeros(p*r,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) 41 | % Vp = zeros(p*r,p*r,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i) 42 | xp = zeros(M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 43 | Vp = zeros(M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 44 | % xf = zeros(1,T); % E(X(t)|y(1:t)) 45 | xf1 = zeros(M,T); % E(X(t)|y(1:t),S(t)=j) 46 | xf2 = zeros(M,M); % E(X(t)|y(1:t),S(t-1)=i,S(t)=j) 47 | Vf1 = zeros(M,T); % V(X(t)|y(1:t),S(t)=j) 48 | % Vf2 = zeros(M,M); % V(X(t)|y(1:t),S(t-1)=i,S(t)=j) 49 | % CVf2 = zeros(r,r,M,M); % Cov(x(t),x(t-1)|y(1:t),S(t-1)=i,S(t)=j) 50 | Lp = zeros(M,M); % P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 51 | Mf = zeros(M,T); % P(S(t)=j|y(1:t)) 52 | % Mf2 = zeros(M,M); % P(S(t-1)=i,S(t)=j|y(1:t)) 53 | 54 | % Declare Kalman smoothing variables 55 | xs = zeros(1,T); % E(X(t)|y(1:T)) 56 | Ms = zeros(M,T); % P(S(t)=j|y(1:T)) 57 | 58 | % Other outputs 59 | sum_Ms2 = zeros(M,M); % sum(t=2:T) P(S(t-1)=i,S(t)=j|y(1:T)) 60 | sum_MCP = zeros(M,1); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)X(t-1)'|S(t)=j,y(1:T)) 61 | % sum_MP = zeros(1,1,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 62 | sum_MPb = zeros(M,1); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(X(t-1)X(t-1)'|S(t)=j,y(1:T)) 63 | % sum_P = zeros(r,r) % sum(t=1:T) E(x(t)x(t)'|S(t)=j,y(1:T)) 64 | % MP0 = zeros(M,1); % P(S(1)=j|y(1:T)) * E(X(1)X(1)'|S(t)=j,y(1:T)) 65 | % Mx0 = zeros(M,1); % P(S(1)=j|y(1:T)) * E(X(1)|S(t)=j,y(1:T)) 66 | 67 | % Reshape parameters 68 | A = squeeze(A)'; % size 1xM 69 | Q = squeeze(Q)'; % size 1xM 70 | 71 | % Auxiliary quantities 72 | CCt = C * C'; 73 | RinvC = R\C; 74 | CtRinvC = (C')*RinvC; 75 | cst = - N / 2 * log(2*pi); 76 | 77 | 78 | 79 | %-------------------------------------------------------------------------% 80 | % Switching Kalman Filter % 81 | %-------------------------------------------------------------------------% 82 | 83 | 84 | % Initialize filter 85 | Acc = zeros(M,1); 86 | for j=1:M 87 | S_j = Sigma(j); 88 | e = y(:,1) - C * mu(:,j); 89 | Ve = S_j * CCt + R; 90 | Ve = 0.5 * (Ve+Ve.'); 91 | if safe 92 | Ve = regfun(Ve,abstol,reltol); 93 | end 94 | xf1(j,1) = mu(j) + S_j * C.' * (Ve\e); 95 | Vf1(j,1) = S_j - S_j^2 * C.' * (Ve\C) ; 96 | Acc(j) = Pi(j) * mvnpdf(e.',[],Ve); % P(y(1),S(1)=j) 97 | end 98 | 99 | if all(Acc == 0) 100 | Acc = eps * ones(M,1); 101 | end 102 | Mf(:,1) = Acc / sum(Acc); % P(S(1)=j|y(1)) 103 | % xf(1) = dot(xf1(:,1),Acc); % E(x(1)|y(1)) 104 | L = log(sum(Acc)); % log(P(y(1))) 105 | 106 | 107 | % MAIN LOOP 108 | for t=2:T 109 | 110 | % Prediction of x(t) 111 | xpt = xf1(:,t-1) * A; 112 | Vpt = Vf1(:,t-1) * (A.^2) + Q; 113 | 114 | % Store predictions 115 | xp(:,:,t) = xpt; 116 | Vp(:,:,t) = Vpt; 117 | 118 | % Filtered variance 119 | Vf2 = 1 ./ (CtRinvC + 1./Vpt); 120 | 121 | for i=1:M 122 | 123 | for j=1:M 124 | 125 | % Prediction error for y(t) 126 | e = y(:,t) - C * xpt(i,j); 127 | Ve = Vpt(i,j) * CCt + R; % Variance of prediction error 128 | % Ve = 0.5 * (Ve+Ve.'); 129 | % Check that variance matrix is positive definite and well-conditioned 130 | if safe 131 | Ve = regfun(Ve,abstol,reltol); 132 | end 133 | 134 | % Filtered mean 135 | % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 136 | % xf2(i,j) = xpt(i,j) + Vpt(i,j) * (C' * (Ve\e)); % slow 137 | xf2(i,j) = xpt(i,j) + ... 138 | (Vpt(i,j)/(1+Vpt(i,j)*CtRinvC)) * (RinvC' * e); % fast 139 | 140 | % Predictive Likelihood L(i,j,t) = P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 141 | % Choleski decomposition 142 | [Lchol,err] = chol(Ve,'lower'); 143 | if ~err % case: Ve definite positive 144 | Lp(i,j) = exp(cst - sum(log(diag(Lchol))) - 0.5 * norm(Lchol\e)^2); 145 | else % case: Ve not definite positive 146 | Lp(i,j) = 0; 147 | end 148 | 149 | end % end j loop 150 | 151 | end % end i loop 152 | 153 | % P(S(t-1)=i,S(t)=j|y(1:t)) (up to multiplicative constant) 154 | % Calculated: P(y(t),S(t-1)=i,S(t)=j|y(1:t-1)) 155 | Mf2 = Lp .* Z .* Mf(:,t-1); 156 | 157 | if all(Mf2(:) == 0) 158 | Mf2 = eps * ones(M,M); 159 | end 160 | 161 | % Update log-likelihood 162 | % P(y(t)|y(1:t-1)) = sum(i,j) P(y(t)|S(t-1)=i,S(t)=j,y(1:t-1)) * 163 | % P(S(t)=j|S(t-1)=i) * P(S(t-1)=i|y(t-1)) 164 | L = L + log(sum(Mf2(:))); 165 | 166 | % Filtered occupancy probability of state j at time t 167 | Mf2 = Mf2 / sum(Mf2(:)); % P(S(t-1)=i,S(t)=j|y(1:t)) 168 | Mf(:,t) = sum(Mf2).'; % P(S(t)=j|y(1:t)) 169 | 170 | % Weights of state components 171 | W = Mf2 ./ (Mf(:,t)'); 172 | W(isnan(W)) = 0; 173 | 174 | % Collapse M^2 distributions (X(t)|S(t-1:t),y(1:t)) to M (X(t)|S(t),y(1:t)) 175 | xhat = sum(xf2 .* W); 176 | xf1(:,t) = xhat(:); % E(X(t)|S(t)=j,y(1:t)) j=1:M 177 | % xhat = repmat(xhat,M,1); 178 | Vhat = sum(W .* (Vf2 + (xf2 - xhat).^2)); 179 | Vf1(:,t) = Vhat(:); % V(X(t)|S(t)=j,y(1:t)), j=1:M 180 | 181 | 182 | end % end t loop 183 | 184 | 185 | % Collapse M distributions (X(t)|S(t),y(1:t)) to 1 (X(t)|y(1:t)) 186 | xf = sum(xf1 .* Mf,1); % E(X(t)|y(1:t)) 187 | 188 | 189 | 190 | 191 | 192 | %-------------------------------------------------------------------------% 193 | % Switching Kalman Smoother % 194 | %-------------------------------------------------------------------------% 195 | 196 | 197 | % Initialize smoother at time T 198 | Ms(:,T) = Mf(:,T); 199 | xs(T) = xf(T); 200 | xs1 = xf1(:,T); 201 | Vs1 = Vf1(:,T); 202 | sum_MP = Ms(:,T) .* (Vs1 + xs1.^2); 203 | 204 | for t = T-1:-1:1 205 | 206 | % Store relevant vectors/matrices from previous iteration 207 | xs1tp1 = xs1; % E(X(t+1)|S(t+1),y(1:T)) 208 | Vs1tp1 = Vs1; % V(X(t+1)|S(t+1),y(1:T)) 209 | 210 | % Predicted and filtered mean and variance (for faster access) 211 | xptp1 = xp(:,:,t+1); 212 | Vptp1 = Vp(:,:,t+1); 213 | xf1t = xf1(:,t); 214 | Vf1t = Vf1(:,t); 215 | 216 | % Smoothed mean and variance of x(t), smoothed cross-covariance of 217 | % x(t+1) & X(t) given S(t)=j and S(t+1)=k 218 | % Kalman smoother gain 219 | % J(t) = V(X(t)|S(t)=j,y(1:t)) * A_k' * V(x(t+1)|S(t)=j,S(t+1)=k,y(1:t))^{-1} 220 | J = (Vf1t * A) ./ Vptp1; 221 | % E(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 222 | xs2 = xf1t + J .* (xs1tp1' - xptp1); 223 | % V(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 224 | Vs2 = Vf1t + J.^2 .* (Vs1tp1' - Vptp1); 225 | % Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) = V(x(t+1)|S(t+1)=k,y(1:T)) * J(t)' 226 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 227 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 228 | CVs2 = J .* Vs1tp1'; 229 | 230 | % Smoothed probability distribution of S(t) 231 | U = diag(Mf(:,t)) * Z; % P(S(t)=j|S(t+1)=k,y(1:T)) 232 | U = U ./ sum(U); % scaling 233 | U(isnan(U)) = 0; 234 | 235 | Ms2 = U * diag(Ms(:,t+1)); % P(S(t)=j,S(t+1)=k|y(1:T)) 236 | if all(Ms2(:) == 0) 237 | Ms2 = (1/M^2) * ones(M); 238 | end 239 | if beta < 1 240 | Ms2 = Ms2.^beta; % DAEM 241 | end 242 | Ms2 = Ms2 / sum(Ms2(:)); % for numerical accuracy 243 | sum_Ms2 = sum_Ms2 + Ms2; 244 | Ms(:,t) = sum(Ms2,2); % P(S(t)=j|y(1:T)) 245 | W = Ms2 ./ Ms(:,t); % P(S(t+1)=k|S(t)=j,y(1:T)) 246 | W(isnan(W)) = 0; 247 | 248 | % Collapse M^2 distributions to M 249 | % E(X(t)|S(t)=j,y(1:T)), j=1:M 250 | xs1 = sum(W .* xs2,2); 251 | % V(X(t)|S(t)=j,y(1:T)), j=1:M 252 | Vs1 = sum(W .* (Vs2 + (xs2 - xs1).^2),2); 253 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) 254 | % B/c of approximation E(x(t+1)|S(t)=j,S(t+1)=k,y(1:T)) ~= E(x(t+1)|S(t+1)=k,y(1:T)), 255 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) ~= sum(j=1:M) Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) * U(j,k) 256 | % with U(j,k) = P(S(t)=j|S(t+1)=k,y(1:T)) 257 | CVs1 = sum(U .* CVs2)'; 258 | 259 | xsb = sum(U .* xs2); 260 | Vsb = sum(U .* (Vs2 + (xs2 - xsb).^2)); 261 | xsb = xsb(:); % E(X(t)|S(t+1)=k,y(1:T)), k=1:M 262 | Vsb = Vsb(:); % V(X(t)|S(t+1)=k,y(1:T)), k=1:M 263 | 264 | % Collapse M distributions to 1 265 | xs(t) = xs1.' * Ms(:,t); % E(X(t)|y(1:T)) 266 | 267 | % Required quantities for M step 268 | % P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)), j=1:M 269 | MP = Ms(:,t) .* (Vs1 + xs1.^2); 270 | % P(S(t+1)=j|y(1:T)) * E(X(t)X(t)'|S(t+1)=j,y(1:T)), j=1:M 271 | MPb = Ms(:,t+1) .* (Vsb + xsb.^2); 272 | % P(S(t)=j|y(1:T)) * E(x(t+1)X(t)'|S(t+1)=j,y(1:T)), j=1:M 273 | MCP = Ms(:,t+1) .* (CVs1 + xs1tp1 .* xsb); 274 | 275 | if t > 1 276 | sum_MP = sum_MP + MP; 277 | end 278 | sum_MPb = sum_MPb + MPb; 279 | sum_MCP = sum_MCP + MCP; 280 | 281 | end % end t loop 282 | 283 | 284 | 285 | Mx0 = (Ms(:,1) .* xs1)'; 286 | MP0 = reshape(Ms(:,1) .* (Vs1 + xs1.^2),1,1,M); 287 | 288 | sum_P = sum(sum_MP+MP); 289 | sum_MCP = reshape(sum_MCP,1,1,M); 290 | sum_MP = reshape(sum_MP,1,1,M); 291 | sum_MPb = reshape(sum_MPb,1,1,M); 292 | 293 | 294 | -------------------------------------------------------------------------------- /skfs_r1_dyn.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 2 | skfs_r1_dyn(y,M,p,r,pars,beta,safe,abstol,reltol) 3 | 4 | %-------------------------------------------------------------------------- 5 | % 6 | % SWITCHING KALMAN FILTER AND SMOOTHER 7 | % IN STATE-STATE MODEL WITH SWITCHING DYNAMICS 8 | % 9 | % PURPOSE 10 | % This is not meant to be directly called by the user. It is called by 11 | % functions 'switch_dyn' and 'fast_dyn' to complete the E step of the EM 12 | % algorithm. SPECIAL CASE r = 1 13 | % 14 | % USAGE 15 | % [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 16 | % skfs_r1_dyn(y,M,p,r,A,C,Q,R,mu,Sigma,Pi,Z,beta,safe,abstol,reltol) 17 | % 18 | % REFERENCES 19 | % C. J. Kim (1994) "Dynamic Linear Models with Markov-Switching", Journal of 20 | % Econometrics 60, 1-22. 21 | % K. P. Murphy (1998) "Switching Kalman Filters", Technical Report 22 | % 23 | %-------------------------------------------------------------------------- 24 | 25 | % Model dimensions 26 | [N,T] = size(y); 27 | % Size of 'small' state vector x(t): r 28 | % Size of 'big' state vector X(t) = (x(t),...,x(t-p+1)): p * r 29 | 30 | A = pars.A; C = pars.C; Q = pars.Q; R = pars.R; mu = pars.mu; 31 | Sigma = pars.Sigma; Pi = pars.Pi; Z = pars.Z; 32 | 33 | 34 | % Remove warnings when inverting singular matrices 35 | warning('off','MATLAB:singularMatrix'); 36 | warning('off','MATLAB:nearlySingularMatrix'); 37 | warning('off','MATLAB:illConditionedMatrix'); 38 | 39 | % Declare Kalman filter variables 40 | % xp = zeros(p,M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) 41 | % Vp = zeros(p,p,M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i) 42 | xp = zeros(M,M,T); % E(x(t)|y(1:t-1),S(t-1)=i,S(t)=j) @@@@ reduce memory footprint 43 | Vp = zeros(M,M,T); % V(x(t)|y(1:t-1),S(t-1)=i) @@@@ reduce memory footprint 44 | % xf = zeros(p,T); % E(X(t)|y(1:t)) 45 | xf1 = zeros(p,M,T); % E(X(t)|y(1:t),S(t)=j) 46 | xf2 = zeros(p,M,M); % E(X(t)|y(1:t),S(t-1)=i,S(t)=j) 47 | Vf1 = zeros(p,p,M,T); % V(X(t)|y(1:t),S(t)=j) 48 | Vf2 = zeros(p,p,M,M); % V(X(t)|y(1:t),S(t-1)=i,S(t)=j) 49 | % CVf2 = zeros(r,r,M,M); % Cov(x(t),x(t-1)|y(1:t),S(t-1)=i,S(t)=j) 50 | Lp = zeros(M,M); % P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 51 | Mf = zeros(M,T); % P(S(t)=j|y(1:t)) 52 | % Mf2 = zeros(M,M); % P(S(t-1)=i,S(t)=j|y(1:t)) 53 | 54 | % Declare Kalman smoothing variables 55 | xs = zeros(1,T); % E(X(t)|y(1:T)) 56 | xs2 = zeros(p,M,M); % E(X(t)|y(1:T),S(t)=j,S(t+1)=k) 57 | Vs2 = zeros(p,p,M,M); % V(X(t)|y(1:T),S(t)=j,S(t+1)=k) 58 | CVs1 = zeros(1,p,M); % Cov(x(t+1),X(t)|y(1:T),S(t+1)=k) 59 | CVs2 = zeros(1,p,M,M); % Cov(x(t+1),X(t)|y(1:t),S(t)=j,S(t+1)=k) 60 | Ms = zeros(M,T); % P(S(t)=j|y(1:T)) 61 | 62 | % Other outputs 63 | sum_Ms2 = zeros(M,M); % sum(t=2:T) P(S(t-1)=i,S(t)=j|y(1:T)) 64 | sum_MCP = zeros(1,p,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)X(t-1)'|S(t)=j,y(1:T)) 65 | sum_MP = zeros(1,1,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 66 | sum_MPb = zeros(p,p,M); % sum(t=2:T) P(S(t)=j|y(1:T)) * E(X(t-1)X(t-1)'|S(t)=j,y(1:T)) 67 | % sum_P = zeros(r,r) % sum(t=1:T) E(x(t)x(t)'|S(t)=j,y(1:T)) 68 | MP0 = zeros(p,p,M); % P(S(1)=j|y(1:T)) * E(X(1)X(1)'|S(t)=j,y(1:T)) 69 | Mx0 = zeros(p,M); % P(S(1)=j|y(1:T)) * E(X(1)|S(t)=j,y(1:T)) 70 | 71 | 72 | 73 | 74 | % Expand matrices 75 | Abig = repmat(diag(ones((p-1)*r,1),-r),[1,1,M]); 76 | Abig(1,:,:) = A; 77 | Cbig = zeros(N,p); 78 | Cbig(:,1) = C; 79 | Qbig = zeros(p,p,M); 80 | Qbig(1,1,:) = Q(:); 81 | 82 | % Auxiliary quantities 83 | CCt = C * C'; 84 | % RinvC = R\Cbig; 85 | % CtRinvC = dot(C,RinvC(:,1)); 86 | RinvC = R\C; % @@@@@@@@@ 87 | CtRinvC = dot(C,RinvC); 88 | cst = - N / 2 * log(2*pi); 89 | 90 | mu = mu(:); 91 | Sigma = Sigma(:); 92 | 93 | 94 | %-------------------------------------------------------------------------% 95 | % Switching Kalman Filter % 96 | %-------------------------------------------------------------------------% 97 | 98 | 99 | % Initialize filter 100 | Acc = zeros(M,1); 101 | for j=1:M 102 | S_j = Sigma(j) * eye(p); 103 | e = y(:,1) - C * mu(j); 104 | Ve = S_j(1) * CCt + R; 105 | if safe 106 | Ve = regfun(Ve,abstol,reltol); 107 | end 108 | xf1(:,j,1) = mu(j) + S_j * Cbig.' * (Ve\e); 109 | Vf1(:,:,j,1) = S_j - S_j * Cbig.' * (Ve\Cbig) * S_j; 110 | Acc(j) = Pi(j) * mvnpdf(e.',[],Ve); 111 | end 112 | 113 | if all(Acc == 0) 114 | Acc = eps * ones(M,1); 115 | end 116 | Mf(:,1) = Acc / sum(Acc); % P(S(1)=j|y(1)) 117 | L = log(sum(Acc)); % log(P(y(1))) 118 | 119 | Vhat = zeros(p,p,M); 120 | 121 | % MAIN LOOP 122 | for t=2:T 123 | 124 | for i=1:M 125 | 126 | for j=1:M 127 | 128 | % Prediction of x(t) 129 | xp_ij = Abig(:,:,j) * xf1(:,i,t-1); 130 | Vp_ij = Abig(:,:,j) * Vf1(:,:,i,t-1) * Abig(:,:,j).' + Qbig(:,:,j); 131 | 132 | % Store predictions 133 | xp(i,j,t) = xp_ij(1); 134 | Vp(i,j,t) = Vp_ij(1); 135 | 136 | % Prediction error for y(t) 137 | e = y(:,t) - C * xp_ij(1); 138 | Ve = Vp_ij(1) * CCt + R; % Variance of prediction error 139 | % Check that variance matrix is positive definite and well-conditioned 140 | if safe 141 | Ve = regfun(Ve,abstol,reltol); 142 | end 143 | 144 | % Filtering update 145 | % CVp = C * Vp_ij; 146 | % K = (CVp.') / Ve; % Kalman gain matrix 147 | % xf2(:,i,j) = xp_ij + K * e; % E(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 148 | xf2(:,i,j) = xp_ij + ... 149 | (dot(RinvC,e)/(1+Vp_ij(1)*CtRinvC)) * Vp_ij(:,1); 150 | % Vf2(:,:,i,j) = Vp_ij - K * CVp; % V(X(t)|S(t-1)=i,S(t)=j,y(1:t)) 151 | Vf2(:,:,i,j) = Vp_ij - ... 152 | (CtRinvC/(1+Vp_ij(1)*CtRinvC)) * Vp_ij(:,1) * Vp_ij(1,:); 153 | % Vf2(:,:,i,j) = Vp_ij - ... 154 | % ((RinvC.'*C)/(1+Vp_ij(1)*CtRinvC)) * Vp_ij(:,1) * Vp_ij(1,:); 155 | % if t == T 156 | % % Cov(x(t),x(t-1)|S(t-1)=i,S(t)=j,y(1:t)) 157 | % CVf2(:,:,i,j) = (I - K*C) * A(:,:,j) * Vf1(:,:,i,t-1); 158 | % end 159 | 160 | % Predictive Likelihood L(i,j,t) = P(y(t)|y(1:t-1),S(t)=j,S(t-1)=i) 161 | % Choleski decomposition 162 | [Lchol,err] = chol(Ve,'lower'); 163 | if ~err % case: Ve definite positive 164 | Lp(i,j) = exp(cst - sum(log(diag(Lchol))) - 0.5 * norm(Lchol\e)^2); 165 | else 166 | Lp(i,j) = 0; 167 | end 168 | % Lp(i,j) = mvnpdf(e.',[],Ve); 169 | 170 | end % end j loop 171 | 172 | end % end i loop 173 | 174 | % P(S(t-1)=i,S(t)=j|y(1:t)) (up to multiplicative constant) 175 | Mf2 = Lp .* Z .* Mf(:,t-1); % P(y(t),S(t-1)=i,S(t)=j|y(1:t-1)) 176 | if all(Mf2(:) == 0) 177 | Mf2 = eps * ones(M,M); 178 | end 179 | 180 | % Update log-likelihood 181 | % P(y(t)|y(1:t-1)) = sum(i,j) P(y(t)|S(t-1)=i,S(t)=j,y(1:t-1)) * 182 | % P(S(t)=j|S(t-1)=i) * P(S(t-1)=i|y(t-1)) 183 | L = L + log(sum(Mf2(:))); 184 | 185 | % Filtered occupancy probability of state j at time t 186 | Mf2 = Mf2 / sum(Mf2(:)); % P(S(t-1)=i,S(t)=j|y(1:t)) 187 | Mf(:,t) = sum(Mf2).'; % P(S(t)=j|y(1:t)) 188 | 189 | % Weights of state components 190 | W = Mf2 ./ Mf(:,t)'; 191 | W(isnan(W)) = 0; 192 | 193 | % Collapse M^2 distributions (X(t)|S(t-1:t),y(1:t)) to M (X(t)|S(t),y(1:t)) 194 | for j = 1:M 195 | xhat = xf2(:,:,j) * W(:,j); 196 | for i = 1:M 197 | m = xf2(:,i,j) - xhat; 198 | Vhat(:,:,i) = W(i,j) * (Vf2(:,:,i,j) + (m*m.')); 199 | end 200 | % Filtered density of x(t) given state j 201 | xf1(:,j,t) = xhat; % E(X(t)|S(t)=j,y(1:t)) (Eq. 11) 202 | Vf1(:,:,j,t) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:t)) (Eq. 12) 203 | end 204 | 205 | end % end t loop 206 | 207 | 208 | % Collapse M distributions (X(t)|S(t),y(1:t)) to 1 (X(t)|y(1:t)) 209 | xf = sum(squeeze(xf1(1,:,:)) .* Mf); % E(X(t)|y(1:t)) 210 | 211 | 212 | 213 | 214 | 215 | %-------------------------------------------------------------------------% 216 | % Switching Kalman Smoother % 217 | %-------------------------------------------------------------------------% 218 | 219 | 220 | % Initialize smoother at time T 221 | Ms(:,T) = Mf(:,T); 222 | xs(:,T) = xf(:,T); 223 | xsb = zeros(p,M); 224 | xs1 = xf1(:,:,T); 225 | Vs1 = Vf1(:,:,:,T); 226 | Asmall = reshape(A,p,M); 227 | MCP = zeros(1,p,M); 228 | MP = zeros(1,1,M); 229 | MPb = zeros(p,p,M); 230 | for j = 1:M 231 | sum_MP(j) = Ms(j,T) * (Vs1(1,1,j) + xs1(1,j)^2); 232 | end 233 | Vsb = zeros(p,p,M); 234 | CVhat = zeros(r,p,M); 235 | 236 | for t = T-1:-1:1 237 | 238 | % Store relevant vectors/matrices from previous iteration 239 | xs1tp1 = xs1(1,:); % E(X(t+1)|S(t+1),y(1:T)) 240 | Vs1tp1 = Vs1(1,1,:); % V(X(t+1)|S(t+1),y(1:T)) 241 | 242 | % Predicted and filtered mean and variance (for faster access) 243 | xptp1 = xp(:,:,t+1); 244 | Vptp1 = Vp(:,:,t+1); 245 | xf1t = xf1(:,:,t); 246 | Vf1t = Vf1(:,:,:,t); 247 | 248 | % Smoothed mean and variance of x(t), smoothed cross-covariance of 249 | % x(t+1) & X(t) given S(t)=j and S(t+1)=k 250 | for j = 1:M 251 | for k = 1:M 252 | % Kalman smoother gain 253 | % J(t) = V(X(t)|S(t)=j,y(1:t)) * A_k' * V(x(t+1)|S(t)=j,y(1:t))^{-1} 254 | J = Vf1t(:,:,j) * Asmall(:,k) / Vptp1(j,k); 255 | % E(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 256 | xs2(:,j,k) = xf1t(:,j) + J * (xs1tp1(k) - xptp1(j,k)); 257 | % V(X(t)|S(t)=j,S(t+1)=k,y(1:T)) 258 | Vs2(:,:,j,k) = Vf1t(:,:,j) + J * (Vs1tp1(k) - Vptp1(j,k)) * J.'; 259 | % Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) = V(x(t+1)|S(t+1)=k,y(1:T)) * J(t)' 260 | % Equation (20) of "Derivation of Kalman filtering and smoothing equations" 261 | % by B. M. Yu, K. V. Shenoy, M. Sahani. Technical report, 2004. 262 | CVs2(:,:,j,k) = Vs1tp1(k) * J.'; 263 | end 264 | end 265 | 266 | % Smoothed probability distribution of S(t) 267 | U = diag(Mf(:,t)) * Z; % P(S(t)=j|S(t+1)=k,y(1:T)) 268 | U = U ./ sum(U); % scaling 269 | U(isnan(U)) = 0; 270 | 271 | Ms2 = U * diag(Ms(:,t+1)); % P(S(t)=j,S(t+1)=k|y(1:T)) 272 | if all(Ms2(:) == 0) 273 | Ms2 = (1/M^2) * ones(M); 274 | end 275 | if beta < 1 276 | Ms2 = Ms2.^beta; % DAEM 277 | end 278 | Ms2 = Ms2 / sum(Ms2(:)); % for numerical accuracy 279 | sum_Ms2 = sum_Ms2 + Ms2; 280 | Ms(:,t) = sum(Ms2,2); % P(S(t)=j|y(1:T)) 281 | W = Ms2 ./ Ms(:,t); % P(S(t+1)=k|S(t)=j,y(1:T)) 282 | W(isnan(W)) = 0; 283 | 284 | % Collapse M^2 distributions to M 285 | for j = 1:M 286 | xhat = squeeze(xs2(:,j,:)) * W(j,:)'; % E(X(t)|S(t)=j,y(1:T)) @@@@@@@@ 287 | % xs1(:,j) = W(j,:) * squeeze(xs2(:,j,:)); % @@@@@@@@@@@@ 288 | for k = 1:M 289 | m = xs2(:,j,k) - xhat; 290 | Vhat(:,:,k) = W(j,k) * (Vs2(:,:,j,k) + (m*m.')); 291 | end 292 | xs1(:,j) = xhat; 293 | Vs1(:,:,j) = sum(Vhat,3); % V(X(t)|S(t)=j,y(1:T)) 294 | end 295 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) 296 | % B/c of approximation E(x(t+1)|S(t)=j,S(t+1)=k,y(1:T)) ~= E(x(t+1)|S(t+1)=k,y(1:T)), 297 | % Cov(x(t+1),X(t)|S(t+1)=k,y(1:T)) ~= sum(j=1:M) Cov(x(t+1),X(t)|S(t)=j,S(t+1)=k,y(1:T)) * U(j,k) 298 | % with U(j,k) = P(S(t)=j|S(t+1)=k,y(1:T)) 299 | for k = 1:M 300 | for j = 1:M 301 | CVhat(:,:,j) = U(j,k) * CVs2(:,:,j,k); 302 | end 303 | CVs1(:,:,k) = sum(CVhat,3); 304 | end 305 | % V(X(t)|S(t+1)=k,y(1:T)) 306 | for k = 1:M 307 | xsb(:,k) = xs2(:,:,k) * U(:,k); % E(X(t)|S(t+1)=k,y(1:T)) 308 | for j = 1:M 309 | m = xs2(:,j,k) - xsb(:,k); 310 | Vhat(:,:,j) = U(j,k) * (Vs2(:,:,j,k) + (m*m.')); 311 | end 312 | Vsb(:,:,k) = sum(Vhat,3); 313 | end 314 | 315 | % Collapse M distributions to 1 316 | xs(t) = xs1(1,:) * Ms(:,t); % E(X(t)|y(1:T)) 317 | 318 | % Required quantities for M step 319 | for j=1:M 320 | % P(S(t)=j|y(1:T)) * E(x(t)x(t)'|S(t)=j,y(1:T)) 321 | MP(j) = Ms(j,t) * (Vs1(1,1,j) + xs1(1,j).^2); 322 | % P(S(t+1)=j|y(1:T)) * E(X(t)X(t)'|S(t+1)=j,y(1:T)) 323 | MPb(:,:,j) = Ms(j,t+1) * (Vsb(:,:,j) + (xsb(:,j) * xsb(:,j).')); 324 | % P(S(t)=j|y(1:T)) * E(x(t+1)X(t)'|S(t+1)=j,y(1:T)) 325 | MCP(:,:,j) = Ms(j,t+1) * (CVs1(1,:,j) + xs1tp1(1,j) * xsb(:,j).'); 326 | end 327 | if t > 1 328 | sum_MP = sum_MP + MP; 329 | end 330 | sum_MPb = sum_MPb + MPb; 331 | sum_MCP = sum_MCP + MCP; 332 | 333 | end % end t loop 334 | 335 | for j = 1:M 336 | Mx0(:,j) = Ms(j,1) * xs1(:,j); 337 | MP0(:,:,j) = Ms(j,1) * (Vs1(:,:,j) + (xs1(:,j) * xs1(:,j)')); 338 | end 339 | 340 | sum_P = sum(sum_MP(:)) + sum(MP(:)); 341 | 342 | 343 | 344 | -------------------------------------------------------------------------------- /skfs_var.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,L,sum_Ms2] = skfs_var(x,M,p,pars,beta) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % SWITCHING KALMAN FILTER AND SMOOTHER 6 | % IN SWITCHING VAR MODEL 7 | % 8 | % PURPOSE 9 | % This is not meant to be directly called by the user. It is called by 10 | % functions 'switch_var' to complete the E step of the EM algorithm. 11 | % Model: conditional on S(t)=j, 12 | % x(t) ~ N(mu(j),Sigma(j)) if t <= p 13 | % x(t) = sum(l=1:p) A(l,j) x(t-l) + v(t) if t > p 14 | % with x(1),...,x(p) independent given S(1),...,S(p) 15 | % 16 | % USAGE 17 | % [Mf,Ms,L,sum_Ms2] = skfs_var(x,M,p,pars,beta) 18 | % 19 | % REFERENCES 20 | % K. P. Murphy (1998) "Switching Kalman Filters", Technical Report 21 | % 22 | %-------------------------------------------------------------------------- 23 | 24 | % Model dimensions 25 | [r,T] = size(x); 26 | 27 | A = reshape(pars.A,r,r,p,M); Q = pars.Q; mu = pars.mu; 28 | Sigma = pars.Sigma; Pi = pars.Pi; Z = pars.Z; 29 | Zt = Z'; 30 | 31 | % Remove warnings when inverting singular matrices 32 | warning('off','MATLAB:singularMatrix'); 33 | warning('off','MATLAB:illConditionedMatrix'); 34 | 35 | % Filtered and smoothed regime probabilities 36 | Mf = zeros(M,T); % P(S(t)=j|x(1:t)) 37 | Ms = zeros(M,T); % P(S(t)=j|x(1:T)) 38 | sum_Ms2 = zeros(M,M); % sum(t=2:T) P(S(t-1)=i,S(t)=j|y(1:T)) 39 | if M == 1 40 | Mf = ones(1,T); 41 | Ms = ones(1,T); 42 | sum_Ms2 = T-1; 43 | end 44 | 45 | % P(x(t)|S(t)=j) 46 | Lp = zeros(M,T); 47 | 48 | % Log-likelihood log(P(x(t)|x(1:t-1))) 49 | L = zeros(1,T); 50 | 51 | % Cholesky decomposition of Q(j) 52 | cholQ = zeros(r,r,M); 53 | for j = 1:M 54 | cholQ(:,:,j) = chol(Q(:,:,j),'lower'); 55 | end 56 | 57 | % Constants involving determinant of Q(j) 58 | logSqrtDetQ = zeros(M,1); 59 | for j = 1:M 60 | logSqrtDetQ(j) = sum(log(diag(cholQ(:,:,j)))) + (r/2) * log((2*pi)); 61 | end 62 | 63 | 64 | %-------------------------------------------------------------------------% 65 | % Switching Kalman Filter % 66 | %-------------------------------------------------------------------------% 67 | 68 | 69 | 70 | % Initialize filter 71 | for t = 1:p 72 | for j = 1:M 73 | Lp(j,t) = mvnpdf(x(:,t)',mu(:,j)',Sigma(:,:,j)); 74 | end 75 | if t == 1 76 | Acc = Pi(:) .* Lp(:,t); 77 | else 78 | Acc = (Zt * Mf(:,t-1)) .* Lp(:,t); 79 | end 80 | if any(isnan(Acc)) 81 | Acc(isnan(Acc)) = 0; 82 | end 83 | L(t) = log(sum(Acc)); 84 | 85 | if all(Acc == 0) 86 | Acc = ones(M,1); 87 | elseif any(isinf(Acc)) 88 | idx = isinf(Acc); 89 | Acc(idx) = 1; 90 | Acc(~idx) = 0; 91 | end 92 | Mf(:,t) = Acc / sum(Acc); 93 | end 94 | 95 | 96 | % Calculate predictive probabilities Lp(j,t) = P(x(t)|S(t)=j,x(1:t-1)) 97 | for j = 1:M 98 | xp = zeros(r,T-p); 99 | for k = 1:p 100 | xp = xp + A(:,:,k,j) * x(:,p+1-k:end-k); 101 | end 102 | e = cholQ(:,:,j)\(x(:,p+1:end) - xp); 103 | Lp(j,p+1:T) = exp(-0.5 * sum(e.^2) - logSqrtDetQ(j)); 104 | end 105 | clear e xp 106 | 107 | 108 | % Calculate log-likelihood and filtered probabilities 109 | for t=p+1:T 110 | Acc = Lp(:,t) .* (Zt * Mf(:,t-1)); 111 | if any(isnan(Acc)) 112 | Acc(isnan(Acc)) = 0; 113 | end 114 | L(t) = log(sum(Acc)); 115 | 116 | if all(Acc == 0) 117 | Acc = ones(M,1); 118 | elseif any(isinf(Acc)) 119 | idx = isinf(Acc); 120 | Acc(idx) = 1; 121 | Acc(~idx) = 0; 122 | end 123 | Mf(:,t) = Acc / sum(Acc); 124 | end 125 | clear Lp 126 | 127 | 128 | % Handle infinite values in log-likelihood 129 | test = isinf(L); 130 | if any(test) 131 | if all(test) 132 | L = -Inf; 133 | else 134 | L(test) = min(L(~test)); 135 | end 136 | end 137 | 138 | % Add log-likelihoods 139 | L = sum(L); 140 | 141 | if M == 1 142 | return 143 | end 144 | 145 | 146 | %-------------------------------------------------------------------------% 147 | % Switching Kalman Smoother % 148 | %-------------------------------------------------------------------------% 149 | 150 | 151 | 152 | % Initialize smoother at time T 153 | Ms(:,T) = Mf(:,T); 154 | 155 | for t = T-1:-1:1 156 | % P(S(t)=j|S(t+1)=k,y(1:T)) 157 | U = Mf(:,t) .* Z; %@@@@ uses implicit expansion 158 | U = U ./ sum(U); %@@@@ uses implicit expansion 159 | U(isnan(U)) = 0; 160 | 161 | % P(S(t)=j,S(t+1)=k|y(1:T)) 162 | Ms2 = U .* (Ms(:,t+1)'); %@@@@ uses implicit expansion 163 | if all(Ms2(:) == 0) 164 | Ms2 = (1/M^2) * ones(M); 165 | end 166 | if beta < 1 167 | Ms2 = Ms2.^beta; % DAEM 168 | end 169 | Ms2 = Ms2 / sum(Ms2(:)); % for numerical accuracy 170 | sum_Ms2 = sum_Ms2 + Ms2; 171 | 172 | % P(S(t)=j|y(1:T)) 173 | Ms(:,t) = sum(Ms2,2); 174 | end 175 | 176 | 177 | -------------------------------------------------------------------------------- /switch_dyn.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,Sf,Ss,xf,xs,outpars,LL] = ... 2 | switch_dyn(y,M,p,r,pars,control,equal,fixed,scale) 3 | 4 | %-------------------------------------------------------------------------- 5 | % Title: Parameter estimation and inference in state-space models with 6 | % regime switching (switching dynamics) 7 | % 8 | % Purpose: Infer hidden state vectors and regimes by switching Kalman 9 | % filtering/smoothing (aka Hamilton filtering or Kim filtering) 10 | % and estimate model parameters by maximum likelihood (EM algorithm). 11 | % 12 | % Usage: [Mf,Ms,Sf,Ss,xf,xs,outpars,LL] = ... 13 | % switch_dyn(y,M,p,r,pars,control,equal,fixed,scale) 14 | % 15 | % Inputs: y - Time series (size NxT with N=#variables and T=#time points) 16 | % M - number of possible regimes for switching variable S(t) 17 | % p - order of VAR model for state vector x(t) 18 | % r - size of state vector x(t) 19 | % pars - optional 'struct' of starting values for EM. Fields: 20 | % A - Initial estimate of VAR matrices A(l,j) in system equation 21 | % x(t) = sum(l=1:p) A(l,j) x(t-l) + v(t) conditional on S(t)=j, 22 | % j=1:M (size rxrxpxM) 23 | % C - Initial estimate of observation matrix C in equation 24 | % y(t) = C x(t) + w(t) (size Nxr) 25 | % Q - Initial estimate of system noise covariance Q(j)=Cov(v(t)|S(t)=j), 26 | % j=1:M (size rxrxM) 27 | % R - Initial estimate of observation noise covariance R=Cov(w(t)) 28 | % (size NxN) 29 | % mu - Initial estimate of mean state mu(j)=E(x(1)|S(1)=j), j=1:M 30 | % (size rxM) 31 | % Sigma - Initial estimate of state covariance Sigma(j)=Cov(x(1,j)), 32 | % j=1:M (size rxrxM) 33 | % Pi - Initial estimate of probability Pi(j)=P(S(1)=j), j=1:M (size Mx1) 34 | % Z - Initial estimate of transition probabilities Z(i,j) = 35 | % P(S(t)=j|S(t-1)=i), i,j=1:M (size MxM) 36 | % control - optional 'struct' of algorithm parameters. Fields: 37 | % 'eps': tolerance for EM termination; default = 1e-8 38 | % 'ItrNo': number of EM iterations; default = 1000 39 | % 'beta0': initial inverse temperature parameter for 40 | % deterministic annealing; default = 1 41 | % 'betarate': decay rate for temperature; default = 1 42 | % 'safe': if true, regularizes variance matrices in 43 | % switching Kalman filtering to be well-conditioned 44 | % before inversion. If false, no regularization (faster 45 | % but less safe) 46 | % 'abstol': absolute tolerance for eigenvalues before 47 | % inversion of covariance matrices (eigenvalues less 48 | % than abstol are set to this value) 49 | % 'reltol': relative tolerance for eigenvalues before 50 | % inversion of covariance matrices (eigenvalues less 51 | % than (reltol * largest eigenvalue) are set to this 52 | % value) 53 | % equal - optional 'struct' with fields: 54 | % 'A': if true, estimates of transition matrices A(l,j) 55 | % are equal across regimes j=1,...,M. Default = false 56 | % 'Q': if true, estimates of innovation matrices Q(j) are 57 | % equal across regimes. Default = false 58 | % 'mu': if true, estimates of initial mean state vectors 59 | % mu(j) are equal across regimes. Default = true 60 | % 'Sigma': if true, estimates of initial variance matrices 61 | % Sigma(j) are equal across regimes. Default = true 62 | % fixed - optional 'struct' with fields 'A','C','Q','R', 63 | % 'mu','Sigma'. If not empty, each field must be an array 64 | % of the same dimension as the parameter. Numeric values 65 | % in the array are interpreted as fixed coefficients whereas 66 | % NaN's represent free coefficients. For example, a 67 | % diagonal structure for 'R' would be specified as 68 | % fixed.R = diag(NaN(N,1)). 69 | % scale - optional 'struct' with fields: 70 | % 'A': upper bound on norm of eigenvalues of A matrices. 71 | % Must be in (0,1) to guarantee stationarity of state process. 72 | % 'C': value of the (euclidean) column norms of the matrices C(j). 73 | % Must be positive. 74 | % 75 | % Outputs: Mf - State probability estimated by switching Kalman Filter (size MxT) 76 | % Ms - State probability estimated by switching Kalman Smoother (size MxT) 77 | % Sf - Estimated states (Kalman Filter) 78 | % Ss - Estimated states (Kalman Smoother) 79 | % xf - Filtered state vector (size rxT) 80 | % xs - Smoothed state vector (size MxT) 81 | % outpars - 'struct' of estimated model parameters 82 | % A - Estimated system matrix (size rxrxpxM) 83 | % C - Estimated observation matrix (size Nxr) 84 | % Q - Estimated state noise covariance (size rxrxM) 85 | % R - Estimated observation noise covariance (size NxN) 86 | % mu - Estimated initial mean of state vector (size rxM) 87 | % Sigma - Estimated initial variance of state vector (size rxrxM) 88 | % Pi - Estimated initial state probabilities (Mx1) 89 | % Z - Estimated state transition probabilities (MxM) 90 | % LL - Sequence of log-likelihood values 91 | % 92 | % Author: David Degras, david.degras@umb.edu 93 | % University of Massachusetts Boston 94 | % 95 | % Contributors: Ting Chee Ming, cmting@utm.my 96 | % Siti Balqis Samdin 97 | % Centre for Biomedical Engineering, Universiti Teknologi Malaysia. 98 | % 99 | % Version: February 16, 2021 100 | %-------------------------------------------------------------------------- 101 | 102 | 103 | 104 | 105 | % We assume t conditional on S(1)=j, the initial state vectors x(2-p),...,x(1) 106 | % are iid ~ N(mu(j),Sigma(j)) 107 | 108 | 109 | 110 | %-------------------------------------------------------------------------% 111 | % Initialization % 112 | %-------------------------------------------------------------------------% 113 | 114 | 115 | narginchk(4,9); 116 | 117 | warning('off','MATLAB:singularMatrix'); 118 | warning('off','MATLAB:nearlySingularMatrix'); 119 | warning('off','MATLAB:illConditionedMatrix'); 120 | 121 | % Data dimensions 122 | N = size(y,1); 123 | 124 | % Centering 125 | y = y - mean(y,2); 126 | 127 | % 'small' state vector x(t), size = r 128 | % 'big' state vector X(t) = (x(t),...,x(t-p+1)), size = p*r 129 | 130 | % Initialize optional arguments if not specified 131 | if ~exist('fixed','var') 132 | fixed = []; 133 | end 134 | if ~exist('equal','var') 135 | equal = []; 136 | end 137 | if ~exist('control','var') 138 | control = []; 139 | end 140 | if ~exist('scale','var') 141 | scale = []; 142 | end 143 | 144 | 145 | %@@@@ Initialize estimators @@@@% 146 | pars0 = struct('A',[], 'C',[], 'Q',[], 'R',[], 'mu',[], 'Sigma',[], ... 147 | 'Pi',[], 'Z',[]); 148 | if exist('pars','var') && isstruct(pars) 149 | fname = fieldnames(pars0); 150 | for i = 1:8 151 | name = fname{i}; 152 | if isfield(pars,name) 153 | pars0.(name) = pars.(name); 154 | end 155 | end 156 | end 157 | pars = pars0; 158 | 159 | if any(structfun(@isempty,pars)) 160 | pars = init_dyn(y,M,p,r,[],control,equal,fixed,scale); 161 | end 162 | 163 | [pars,control,equal,fixed,scale,skip] = ... 164 | preproc_dyn(M,N,p,r,pars,control,equal,fixed,scale); 165 | 166 | abstol = control.abstol; 167 | reltol = control.reltol; 168 | betarate = control.betarate; 169 | eps = control.eps; 170 | ItrNo = control.ItrNo; 171 | verbose = control.verbose; 172 | safe = control.safe; 173 | 174 | 175 | %@@@@ Initialize other quantities 176 | LL = zeros(1,ItrNo); % Log-likelihood 177 | LLbest = -Inf; % best attained value of log-likelihood 178 | LLflag = 0; % counter for convergence of of log-likelihood 179 | sum_yy = y * y.'; % sum(t=1:T) y(t)*y(t)' 180 | beta = control.beta0; % initial temperature for deterministic annealing 181 | 182 | 183 | 184 | % Function for switching Kalman filtering and smoothing 185 | if p == 1 && r == 1 186 | skfs_fun = @skfs_p1r1_dyn; 187 | elseif p == 1 188 | skfs_fun = @skfs_p1_dyn; 189 | elseif r == 1 190 | skfs_fun = @skfs_r1_dyn; 191 | else 192 | skfs_fun = @skfs_dyn; 193 | end 194 | 195 | 196 | for i = 1:ItrNo 197 | 198 | 199 | 200 | %-------------------------------------------------------------------------% 201 | % E-step % 202 | %-------------------------------------------------------------------------% 203 | 204 | 205 | 206 | 207 | % Kim/Hamilton filtering and smoothing 208 | [Mf,Ms,xf,xs,L,MP0,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,sum_P] = ... 209 | skfs_fun(y,M,p,r,pars,beta,safe,abstol,reltol); 210 | 211 | % Log-likelihood 212 | LL(i) = L; 213 | 214 | % Needed for Q-function 215 | sum_xy = xs(1:r,:) * y.'; 216 | 217 | % Evaluate Q-function before M-step and display LL & Q if required 218 | if verbose 219 | fprintf('Iteration-%d Log-likelihood = %g\n',i,LL(i)); 220 | Qval = Q_dyn(pars,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 221 | sum_P,sum_xy,sum_yy); 222 | fprintf('Q-function before M-step = %g\n',Qval); 223 | end 224 | 225 | % Check if current solution is best to date 226 | if (i == 1 || LL(i) > LLbest) 227 | LLbest = LL(i); 228 | outpars = pars; 229 | Mfbest = Mf; Msbest = Ms; 230 | xfbest = xf; xsbest = xs; 231 | end 232 | 233 | % Monitor progress of log-likelihood 234 | if (i>1 && LL(i)-LL(i-1) < eps * abs(LL(i-1))) 235 | LLflag = LLflag + 1; 236 | else 237 | LLflag = 0; 238 | end 239 | % Terminate EM algorithm if no sufficient increase in log-likelihood 240 | % for 10 iterations in a row 241 | if LLflag == 5 242 | break; 243 | end 244 | 245 | % Update inverse temperature parameter (DAEM) 246 | beta = min(beta * betarate, 1); 247 | 248 | 249 | 250 | %-------------------------------------------------------------------------% 251 | % M-step % 252 | %-------------------------------------------------------------------------% 253 | 254 | 255 | 256 | parsold = pars; 257 | pars = M_dyn(parsold,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 258 | sum_P,sum_xy,sum_yy,control,equal,fixed,scale,skip); 259 | 260 | % Display Q-function if required 261 | if verbose 262 | Qval = Q_dyn(pars,MP0,Ms,Mx0,sum_MCP,sum_MP,sum_MPb,sum_Ms2,... 263 | sum_P,sum_xy,sum_yy); 264 | fprintf('Q-function after M-step = %g\n',Qval); 265 | end 266 | 267 | 268 | end % END MAIN LOOP 269 | 270 | 271 | 272 | %-------------------------------------------------------------------------% 273 | % Output % 274 | %-------------------------------------------------------------------------% 275 | 276 | % Return best estimates (i.e. with highest log-likelihood) 277 | % after reshaping them in compact form 278 | 279 | outpars.A = reshape(outpars.A,[r,r,p,M]); 280 | Mf = Mfbest; Ms = Msbest; 281 | [~,Sf] = max(Mf); [~,Ss] = max(Ms); 282 | xf = xfbest; xs = xsbest; 283 | LL = LL(1:i); 284 | 285 | end 286 | 287 | -------------------------------------------------------------------------------- /switch_obs.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,Sf,Ss,xf,xs,outpars,LL] = ... 2 | switch_obs(y,M,p,r,pars,control,equal,fixed,scale) 3 | 4 | %-------------------------------------------------------------------------- 5 | % Title: Parameter estimation and inference in state-space models with regime switching 6 | % (switching observations model) 7 | % 8 | % Function: Infer hidden state vectors and regmes by switching Kalman filtering/smoothing 9 | % (aka Hamilton filtering or Kim filtering) and estimate model parameters by 10 | % maximum likelihood (EM algorithm). 11 | % 12 | % Usage: [Mf,Ms,Sf,Ss,xf,xs,outpars,LL] = ... 13 | % switch_obs(y,M,p,pars,control,equal,fixed,scale) 14 | % 15 | % Inputs: y - Time series (size NxT) 16 | % M - number of regimes 17 | % p - order of VAR model for state vector 18 | % pars - optional 'struct' of EM starting values 19 | % A - Initial estimates of VAR matrices A(l,j) in system equation 20 | % x(t,j) = sum(l=1:p) A(l,j) x(t-l,j) + v(t,j), j=1:M (size rxrxpxM) 21 | % C - Initial estimates of observation matrices C(j) in equation 22 | % y(t) = C(j) x(t,j) + w(t), j=1:M (size NxrxM) 23 | % Q - Initial estimates of state noise covariance Cov(v(t,j)) (size rxrxM) 24 | % R - Pilot estimate of observation noise covariance Cov(w(t)) (size NxN) 25 | % mu - Pilot estimate of mean state mu(j)=E(x(t,j)) for t=1:p (size rxM) 26 | % Sigma - Pilot estimate of covariance Sigma(j)=Cov(x(t,j)) for t=1:p (size rxrxM) 27 | % Pi - Initial state probability (size Mx1) 28 | % Z - Pilot Markov transition probability matrix (size MxM) 29 | % control - optional struct variable with fields: 30 | % 'eps': tolerance for EM termination; defaults to 1e-8 31 | % 'ItrNo': number of EM iterations; dfaults to 100 32 | % 'beta0': initial inverse temperature parameter for deterministic annealing; default 1 33 | % 'betarate': decay rate for temperature; default 1 34 | % 'safe': if true, regularizes variance matrices to be well-conditioned 35 | % before taking inverse. If false, no regularization (faster but less safe) 36 | % 'abstol': absolute tolerance for eigenvalues in matrix inversion (only effective if safe = true) 37 | % 'reltol': relative tolerance for eigenvalues in matrix inversion 38 | % = inverse condition number (only effective if safe = true) 39 | % equal - optional struct variable with fields: 40 | % 'A': if true, VAR transition matrices A(l,j) are equal across regimes j=1,...,M 41 | % 'C': if true, observation matrices C(j) are equal across regimes 42 | % 'Q': if true, VAR innovation matrices Q(j) are equal across regimes 43 | % 'mu': if true, initial mean state vectors mu(j) are equal across regimes 44 | % 'Sigma': if true, initial variance matrices Sigma(j) are equal across regimes 45 | % fixed - optional struct variable with fields 'A','C','Q','R','mu','Sigma'. 46 | % If not empty, each field must contain a matrix with 2 columns, the first for 47 | % the location of fixed coefficients and the second for their values. 48 | % scale - optional struct variable with fields: 49 | % 'A': upper bound for norm of eigenvalues of A matrices. Must be in (0,1). 50 | % 'C': value of the (euclidean) column norms of the matrices C(j). Must be positive. 51 | % 52 | % Outputs: Mf - State probability estimated by switching Kalman Filter (size MxT) 53 | % Ms - State probability estimated by switching Kalman Smoother (size MxT) 54 | % Sf - Estimated states (Kalman Filter) 55 | % Ss - Estimated states (Kalman Smoother) 56 | % xf - Filtered state vector (size rxT) 57 | % xs - Smoothed state vector (size MxT) 58 | % outpars - 'struct' 59 | % A - Estimated system matrix (size rxrxpxM) 60 | % C - Estimated observation matrix (size Nxr) 61 | % Q - Estimated state noise covariance (size rxrxM) 62 | % R - Estimated observation noise covariance (size NxN) 63 | % mu - Estimated initial mean of state vector (size rxM) 64 | % Sigma - Estimated initial variance of state vector (size rxrxM) 65 | % LL - Sequence of log-likelihood values 66 | % 67 | % Author: David Degras, david.degras@umb.edu 68 | % University of Massachusetts Boston 69 | % 70 | % Contributors: Ting Chee Ming, cmting@utm.my 71 | % Siti Balqis Samdin 72 | % Centre for Biomedical Engineering, Universiti Teknologi Malaysia. 73 | % 74 | % Version: January 8, 2021 75 | %-------------------------------------------------------------------------- 76 | 77 | 78 | 79 | 80 | 81 | %-------------------------------------------------------------------------% 82 | % Initialization % 83 | %-------------------------------------------------------------------------% 84 | 85 | narginchk(4,9); 86 | 87 | % Data dimensions 88 | [N,T] = size(y); 89 | 90 | % Center data 91 | y = y - mean(y,2); 92 | 93 | % x(t,j): state vector for j-th process at time t (size r) 94 | % x(t) = x(t,1),...,x(t,M): state vector for all processes at time t (size M*r) 95 | % X(t,j) = x(t,j),...,x(t-p+1,j)): state vector for process j at times t,...,t-p+1 (size p*r) 96 | % X(t) = X(t,1),...,X(t,M): state vector for all processes at times t,...,t-p+1 (size M*p*r) 97 | % Assumption: initial vectors x(1),...,x(1-p+1) are iid ~ N(mu,Sigma) 98 | 99 | 100 | %@@@@@ Initialize optional arguments if not specified 101 | 102 | if ~exist('fixed','var') 103 | fixed = struct(); 104 | end 105 | if ~exist('equal','var') 106 | equal = struct(); 107 | end 108 | if ~exist('control','var') 109 | control = struct(); 110 | end 111 | if ~exist('scale','var') 112 | scale = struct(); 113 | end 114 | 115 | 116 | 117 | % Trivial case M = 1 118 | if M == 1 119 | if ~exist('pars','var') 120 | pars = []; 121 | end 122 | S = ones(1,T); Mf = S; Ms = S; Sf = S; Ss = S; 123 | [xf,xs,outpars,LL] = fast_obs(y,M,p,r,S,pars,control,equal,fixed,scale); 124 | return 125 | end 126 | 127 | 128 | 129 | %@@@@ Initialize estimators @@@@% 130 | pars0 = struct('A',[], 'C',[], 'Q',[], 'R',[], 'mu',[], 'Sigma',[], ... 131 | 'Pi',[], 'Z',[]); 132 | if exist('pars','var') && isstruct(pars) 133 | fname = fieldnames(pars0); 134 | for i = 1:8 135 | name = fname{i}; 136 | if isfield(pars,name) 137 | pars0.(name) = pars.(name); 138 | end 139 | end 140 | end 141 | 142 | if any(structfun(@isempty,pars0)) 143 | pars = init_obs(y,M,p,r,pars0,control,equal,pars0,scale); 144 | end 145 | 146 | [pars,control,equal,fixed,scale,skip] = ... 147 | preproc_obs(M,N,p,r,pars,control,equal,fixed,scale); 148 | 149 | abstol = control.abstol; 150 | reltol = control.reltol; 151 | betarate = control.betarate; 152 | eps = control.eps; 153 | ItrNo = control.ItrNo; 154 | verbose = control.verbose; 155 | safe = control.safe; 156 | 157 | 158 | 159 | % Initial parameters 'A','C',... are expanded from r-space (x(t)) to 160 | % (p*r)-space (X(t)). The new parameters 'Ahat','Chat',... have dimensions: 161 | % Ahat: (p*r)x(p*r)xM, Chat: Nx(p*r)xM, Qhat: (p*r)x(p*r)xM, Rhat: NxN, 162 | % muhat: (p*r)xM, Sigmahat: (p*r)x(p*r)xM. These parameters respect the 163 | % fixed coefficients and equality constraints (either default values or 164 | % user-specified ones). 165 | 166 | % The structure 'fixed' has fields 'fixed.A',... (one field per model 167 | % parameter). Each field is a matrix with two columns containing the 168 | % locations and values of fixed coefficients in the corresponding 169 | % parameter. 170 | 171 | % The structure 'equal' has fields 'equal.A',... representing equality 172 | % constraints on parameters across regimes j=1,...,M. By default, equality 173 | % constraints are: A: false, C: false, Q: false, (R: not applicable), mu: 174 | % true, Sigma: true. In other words, only the initial parameters mu and 175 | % Sigma are assumed to be common across regimes. If provided, user values 176 | % will override default values. 177 | 178 | % Various control parameters ('eps','ItrNo',...) are set either to their 179 | % default values or to user-specified values through argument 'control'. 180 | 181 | 182 | 183 | %@@@@@ Initialize other quantities @@@@@% 184 | 185 | LL = zeros(1,ItrNo); % Log-likelihood 186 | LLbest = -Inf; % best attained log-likelihood 187 | LLflag = 0; % counter for monitoring progress of log-likelihood 188 | sum_yy = y * y.'; % sum(t=1:T) y(t)*y(t)' 189 | beta = control.beta0; % initial temperature for deterministic annealing 190 | 191 | 192 | 193 | % Function for switching Kalman filtering and smoothing 194 | if p == 1 195 | skfs_fun = @skfs_p1_obs; 196 | else 197 | skfs_fun = @skfs_obs; 198 | end 199 | 200 | 201 | 202 | 203 | for i=1:ItrNo 204 | 205 | 206 | 207 | %-------------------------------------------------------------------------% 208 | % E-step % 209 | %-------------------------------------------------------------------------% 210 | 211 | 212 | 213 | % Kim/Hamilton filtering and smoothing 214 | [Mf,Ms,xf,xs,x0,P0,L,sum_CP,sum_MP,sum_Ms2,sum_Mxy,sum_P,sum_Pb] = ... 215 | skfs_fun(y,M,p,pars,beta,safe,abstol,reltol); 216 | 217 | % Log-likelihood 218 | LL(i) = L; 219 | if verbose 220 | fprintf('Iteration-%d Log-likelihood = %g\n',i,LL(i)); 221 | Qval = Q_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,sum_P,... 222 | sum_Pb,sum_yy,x0); 223 | fprintf('Q-function before M-step = %g\n',Qval); 224 | end 225 | 226 | % Check if current solution is best to date 227 | if i == 1 || LL(i) > LLbest 228 | LLbest = LL(i); 229 | outpars = pars; 230 | Mfbest = Mf; 231 | Msbest = Ms; 232 | xfbest = xf; 233 | xsbest = xs; 234 | end 235 | 236 | % Monitor progress of log-likelihood 237 | if i>1 && LL(i)-LL(i-1) < eps * abs(LL(i-1)) 238 | LLflag = LLflag + 1; 239 | else 240 | LLflag = 0; 241 | end 242 | % Terminate EM algorithm if no sufficient reduction in log-likelihood 243 | % for 10 successive iterations 244 | if LLflag == 5 245 | break; 246 | end 247 | 248 | % Update inverse temperature parameter (DAEM) 249 | beta = min(beta * betarate, 1); 250 | 251 | 252 | 253 | 254 | %-------------------------------------------------------------------------% 255 | % M-step % 256 | %-------------------------------------------------------------------------% 257 | 258 | 259 | 260 | pars = M_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,sum_P,... 261 | sum_Pb,sum_yy,x0,control,equal,fixed,scale,skip); 262 | 263 | % Evaluate and display Q-function value if required 264 | if verbose 265 | Qval = Q_obs(pars,Ms,P0,sum_CP,sum_MP,sum_Ms2,sum_Mxy,sum_P,... 266 | sum_Pb,sum_yy,x0); 267 | fprintf('Q-function after M-step = %g\n',Qval); 268 | end 269 | 270 | 271 | end % END MAIN LOOP 272 | 273 | 274 | 275 | %-------------------------------------------------------------------------% 276 | % Output % 277 | %-------------------------------------------------------------------------% 278 | 279 | % Return best estimates (i.e. with highest log-likelihood) 280 | % after reshaping them to original size 281 | outpars.A = reshape(outpars.A,r,r,p,M); 282 | Mf = Mfbest; 283 | Ms = Msbest; 284 | [~,Sf] = max(Mf); 285 | [~,Ss] = max(Ms); 286 | xf = xfbest; 287 | xs = xsbest; 288 | 289 | LL = LL(1:i); 290 | 291 | end 292 | 293 | -------------------------------------------------------------------------------- /switch_var.m: -------------------------------------------------------------------------------- 1 | function [Mf,Ms,Sf,Ss,outpars,LL] = ... 2 | switch_var(y,M,p,pars,control,equal,fixed,scale) 3 | 4 | %-------------------------------------------------------------------------- 5 | % Title: Parameter estimation and inference in state-space models with 6 | % regime switching (switching vector autoregressive) 7 | % 8 | % Purpose: Infer hidden state vectors and regimes by switching Kalman 9 | % filtering/smoothing (aka Hamilton filtering or Kim filtering) 10 | % and estimate model parameters by maximum likelihood (EM algorithm). 11 | % 12 | % Usage: [Mf,Ms,Sf,Ss,outpars,LL] = ... 13 | % switch_var(y,M,p,pars,control,equal,fixed,scale) 14 | % 15 | % Inputs: y - Time series (size NxT) 16 | % M - number of regimes (values) for switching variable S(t) 17 | % p - order of VAR model for y(t) 18 | % pars - structure with fields 19 | % A - Initial estimate of VAR matrices A(l,j) in model 20 | % y(t) = sum(l=1:p) A(l,j) y(t-l) + v(t) conditional on 21 | % S(t)=j, j=1:M (size rxrxpxM) 22 | % Q - Initial estimate of system noise covariance Q(j) = 23 | % V(v(t)|S(t)=j), j=1:M (size rxrxM) 24 | % mu - Initial estimate of mean state mu(j)=E(y(1)|S(1)=j), 25 | % j=1:M (size rxM) 26 | % Sigma - Initial estimate of state covariance Sigma(j) = 27 | % V(y(1)|S(1)=j), j=1:M (size rxrxM) 28 | % Pi - Initial estimate of probability Pi(j)=P(S(1)=j), j=1:M (size Mx1) 29 | % Z - Initial estimate of transition probabilities Z(i,j) = 30 | % P(S(t)=j|S(t-1)=i), i,j=1:M (size MxM) 31 | % control - optional struct variable with fields: 32 | % eps - tolerance for EM termination; default = 1e-8 33 | % ItrNo - number of EM iterations; default = 100 34 | % beta0 - initial inverse temperature parameter for 35 | % deterministic annealing; must be in [0,1] 36 | % default = 1 (regular EM) 37 | % betarate - decay rate for temperature; must be >= 1; default = 1 38 | % safe - if true, regularizes variance matrices in 39 | % switching Kalman filtering to be well-conditioned 40 | % before inversion. If false, no regularization (faster 41 | % but less safe) 42 | % abstol - absolute tolerance for eigenvalues before 43 | % inversion of covariance matrices (eigenvalues less 44 | % than abstol are set to this value) 45 | % reltol - relative tolerance for eigenvalues before 46 | % inversion of covariance matrices (eigenvalues less 47 | % than (reltol * largest eigenvalue) are set to this 48 | % value) 49 | % equal - optional struct variable with fields: 50 | % A - if true, estimates of transition matrices A(l,j) 51 | % are equal across regimes j=1,...,M. Default = false 52 | % Q - if true, estimates of innovation matrices Q(j) are 53 | % equal across regimes. Default = false 54 | % mu - if true, estimates of initial mean state vectors 55 | % mu(j) are equal across regimes. Default = true 56 | % Sigma - if true, estimates of initial variance matrices 57 | % Sigma(j) are equal across regimes. Default = true 58 | % fixed - optional struct variable with fields 'A','C','Q','R', 59 | % 'mu','Sigma'. If not empty, each field must be an array 60 | % of the same dimension as the parameter. Numeric values 61 | % in the array are interpreted as fixed coefficients whereas 62 | % NaN's represent free coefficients. For example, a 63 | % diagonal structure for 'R' would be specified as 64 | % fixed.R = diag(NaN(N,1)). 65 | % scale - optional struct variable with field: 66 | % A - upper bound on norm of eigenvalues of A matrices. 67 | % Must be in (0,1) to guarantee stationarity of state process. 68 | % 69 | % Outputs: Mf - State probability estimated by switching Kalman Filter (size MxT) 70 | % Ms - State probability estimated by switching Kalman Smoother (size MxT) 71 | % Sf - Estimated states (Kalman Filter) 72 | % Ss - Estimated states (Kalman Smoother) 73 | % outpars - structure of parameter estimates with fields: 74 | % A - Estimated system matrix (size rxrxpxM) 75 | % Q - Estimated state noise covariance (size rxrxM) 76 | % mu - Estimated initial mean of state vector (size rxM) 77 | % Sigma - Estimated initial variance of state vector (size rxrxM) 78 | % LL - Sequence of log-likelihood values 79 | % 80 | % Author: David Degras (University of Massachusetts Boston) 81 | % 82 | % Contributors: Chee Ming Ting (Monash University Malaysia) 83 | % Siti Balqis Samdin (Xiamen University Malaysia) 84 | % 85 | %-------------------------------------------------------------------------- 86 | 87 | 88 | 89 | 90 | 91 | %-------------------------------------------------------------------------% 92 | % Initialization % 93 | %-------------------------------------------------------------------------% 94 | 95 | 96 | narginchk(3,8); 97 | 98 | 99 | warning('off','MATLAB:singularMatrix'); 100 | warning('off','MATLAB:nearlySingularMatrix'); 101 | warning('off','MATLAB:illConditionedMatrix'); 102 | 103 | 104 | % Centering 105 | y = y - mean(y,2); 106 | 107 | % Data dimensions 108 | [N,T] = size(y); 109 | r = N; 110 | 111 | % Initialize optional arguments if not specified 112 | fixed0 = struct('C',eye(r),'R',1e-20 * eye(r)); 113 | if exist('fixed','var') && isstruct(fixed) 114 | fixed.C = fixed0.C; 115 | fixed.R = fixed0.R; 116 | else 117 | fixed = fixed0; 118 | end 119 | if ~exist('equal','var') 120 | equal = []; 121 | end 122 | if ~exist('control','var') 123 | control = []; 124 | end 125 | if ~exist('scale','var') 126 | scale = []; 127 | end 128 | 129 | % Special case M = 1 130 | if M == 1 131 | Mf = ones(1,T); Ms = Mf; Sf = Mf; Ss = Mf; 132 | [outpars,LL] = fast_var(y,M,p,Ss,control,equal,fixed,scale); 133 | outpars.Pi = 1; outpars.Z = 1; 134 | return 135 | end 136 | 137 | 138 | 139 | %@@@@ Initialize estimators @@@@% 140 | pars0 = struct('A',[], 'C',fixed.C, 'Q',[], 'R',fixed.R, 'mu',[], ... 141 | 'Sigma',[], 'Pi',[], 'Z',[]); 142 | if exist('pars','var') && isstruct(pars) 143 | name = fieldnames(pars0); 144 | for i = 1:numel(name) 145 | if isfield(pars,name{i}) 146 | pars0.(name{i}) = pars.(name{i}); 147 | end 148 | end 149 | end 150 | 151 | if any(structfun(@isempty,pars0)) 152 | pars0 = init_dyn(y,M,p,r,[],control,equal,pars0,scale); 153 | end 154 | 155 | [pars,control,equal,fixed,scale,skip] = ... 156 | preproc_dyn(M,N,p,r,pars0,control,equal,fixed,scale); 157 | pars = rmfield(pars,{'C','R'}); 158 | 159 | % The structure 'fixed' has fields 'fixed.A',... (one field per model 160 | % parameter). Each field is a matrix with two columns containing the 161 | % locations and values of fixed coefficients in the corresponding 162 | % parameter. 163 | 164 | % The structure 'skip' specified whether a parameter (A,C,Q...) is entirely 165 | % fixed, in which case its update in the M-step can be skipped altogether 166 | 167 | % We assume that conditional on S(1:p), the initial vectors y(1),...,y(p) 168 | % are independent and that conditional on S(t)=j (t=1:p), y(t) ~ N(mu(j),Sigma(j)) 169 | 170 | 171 | 172 | %@@@@ Initialize other quantities 173 | 174 | ItrNo = control.ItrNo; 175 | LL = zeros(ItrNo,1); % Log-likelihood 176 | LLbest = -Inf; % best attained value of log-likelihood 177 | LLflag = 0; % counter for convergence of of log-likelihood 178 | beta = control.beta0; % initial temperature for deterministic annealing 179 | betarate = control.betarate; 180 | eps = control.eps; 181 | verbose = control.verbose; 182 | 183 | 184 | 185 | % Quantities required for Q-function and for M-step 186 | X = zeros(p*r,T-p); 187 | for k = 1:p 188 | X((k-1)*r+1:k*r,:) = y(:,p+1-k:T-k); 189 | end 190 | sum_MP = zeros(r,r,M); 191 | sum_MPb = zeros(p*r,p*r,M); 192 | sum_MCP = zeros(r,p*r,M); 193 | 194 | 195 | 196 | for i=1:ItrNo 197 | 198 | 199 | 200 | %-------------------------------------------------------------------------% 201 | % E-step % 202 | %-------------------------------------------------------------------------% 203 | 204 | 205 | 206 | % Kim/Hamilton filtering and smoothing 207 | [Mf,Ms,L,sum_Ms2] = skfs_var(y,M,p,pars,beta); 208 | 209 | % Log-likelihood 210 | LL(i) = L; 211 | 212 | for j = 1:M 213 | yj = y(:,p+1:T) .* sqrt(Ms(j,p+1:T)); 214 | sum_MP(:,:,j) = (yj * yj.'); 215 | Xj = X .* sqrt(Ms(j,p+1:T)); 216 | sum_MPb(:,:,j) = (Xj * Xj.'); 217 | sum_MCP(:,:,j) = yj * Xj.'; 218 | end 219 | clear Xj yj 220 | 221 | % Evaluate Q-function before M-step and display LL & Q if required 222 | if verbose 223 | fprintf('Iteration-%d Log-likelihood = %g\n',i,LL(i)); 224 | Qval = Q_var(pars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 225 | fprintf('Q-function before M-step = %g\n',Qval); 226 | end 227 | 228 | % Check if current solution is best to date 229 | if (LL(i) > LLbest) 230 | LLbest = LL(i); 231 | outpars = pars; 232 | Mfbest = Mf; 233 | Msbest = Ms; 234 | end 235 | 236 | % Monitor progress of log-likelihood 237 | if (i>1 && LL(i)-LL(i-1) < eps * abs(LL(i-1))) 238 | LLflag = LLflag + 1; 239 | else 240 | LLflag = 0; 241 | end 242 | % Terminate EM algorithm if no sufficient increase in log-likelihood 243 | % for 10 iterations in a row 244 | if LLflag == 5 245 | break; 246 | end 247 | 248 | % Update inverse temperature parameter (DAEM) 249 | beta = beta * betarate; 250 | if beta > 1 251 | beta = 1; 252 | end 253 | 254 | 255 | 256 | %-------------------------------------------------------------------------% 257 | % M-step % 258 | %-------------------------------------------------------------------------% 259 | 260 | 261 | pars = M_var(pars,Ms,sum_MCP,sum_MP,sum_MPb,... 262 | sum_Ms2,y,control,equal,fixed,scale,skip); 263 | 264 | 265 | % Evaluate and display Q-function if required 266 | if verbose 267 | Qval = Q_var(pars,Ms,sum_MCP,sum_MP,sum_MPb,sum_Ms2,y); 268 | fprintf('Q-function after M-step = %g\n',Qval); 269 | end 270 | 271 | 272 | end % END MAIN LOOP 273 | 274 | 275 | 276 | %-------------------------------------------------------------------------% 277 | % Output % 278 | %-------------------------------------------------------------------------% 279 | 280 | % Return best estimates (i.e. with highest log-likelihood) 281 | 282 | 283 | outpars.A = reshape(outpars.A,[N,N,p,M]); 284 | Mf = Mfbest; 285 | Ms = Msbest; 286 | [~,Sf] = max(Mf); 287 | [~,Ss] = max(Ms); 288 | LL = LL(1:i); 289 | 290 | 291 | 292 | 293 | -------------------------------------------------------------------------------- /visualize_mts.m: -------------------------------------------------------------------------------- 1 | function visualize_mts(x,labels,spacing,inrows) 2 | 3 | %-------------------------------------------------------------------------- 4 | % 5 | % VISUALIZE MULTIPLE TIME SERIES 6 | % 7 | % PURPOSE 8 | % The function VISUALIZE_MTS serves to visualize multiple time series on a 9 | % single plot. FTime series are displayed with a vertical space that can be 10 | % adjusted by the user. 11 | % 12 | % USAGE 13 | % visualize_mts(x,labels,spacing,inrows) 14 | % 15 | % INPUTS 16 | % x: multivariate time series 17 | % labels: string or cell array of labels (default = 1,2,...) 18 | % spacing: space between successive time series (default = largest median 19 | % absolute deviation of the time series multiplied by 3) 20 | % inrows: set to 'true' if time series are in rows, 'false' otherwise 21 | % (default = 'true') 22 | % 23 | %-------------------------------------------------------------------------- 24 | 25 | 26 | if ~exist('inrows','var') 27 | inrows = true; 28 | end 29 | 30 | if inrows 31 | x = x.'; 32 | end 33 | 34 | [T,N] = size(x); 35 | 36 | if ~exist('labels','var') || isempty(labels) 37 | labels = 1:N; 38 | end 39 | 40 | if ~exist('spacing','var') || isempty(spacing) 41 | spacing = 3 * max(mad(x,1,2)); 42 | end 43 | 44 | 45 | for i = 1:N 46 | x(:,i) = x(:,i) - mean(x(:,i)) + (N-i) * spacing; 47 | end 48 | 49 | plot(1:T,x,'Color','k'); 50 | yticks(linspace(0,(N-1)*spacing,N)); 51 | yticklabels(labels(N:-1:1)); 52 | xlabel('Time'); 53 | ylim([min(x(:)),max(x(:))]); 54 | 55 | 56 | 57 | 58 | 59 | 60 | --------------------------------------------------------------------------------