def fwdback(init_state_distrib, transmat, obslik, **kwargs): ''' % FWDBACK Compute the posterior probs. in an HMM using the forwards backwards algo. % % [alpha, beta, gamma, loglik, xi, gamma2] = fwdback(init_state_distrib, transmat, obslik, ...) % % Notation: % Y(t) = observation, Q(t) = hidden state, M(t) = mixture variable (for MOG outputs) % A(t) = discrete input (action) (for POMDP models) % % INPUT: % init_state_distrib(i) = Pr(Q(1) = i) % transmat(i,j) = Pr(Q(t) = j | Q(t-1)=i) % or transmat{a}(i,j) = Pr(Q(t) = j | Q(t-1)=i, A(t-1)=a) if there are discrete inputs % obslik(i,t) = Pr(Y(t)| Q(t)=i) % (Compute obslik using eval_pdf_xxx on your data sequence first.) % % Optional parameters may be passed as 'param_name', param_value pairs. % Parameter names are shown below; default values in [] - if none, argument is mandatory. % % For HMMs with MOG outputs: if you want to compute gamma2, you must specify % 'obslik2' - obslik(i,j,t) = Pr(Y(t)| Q(t)=i,M(t)=j) [] % 'mixmat' - mixmat(i,j) = Pr(M(t) = j | Q(t)=i) [] % or mixmat{t}(m,q) if not stationary % % For HMMs with discrete inputs: % 'act' - act(t) = action performed at step t % % Optional arguments: % 'fwd_only' - if 1, only do a forwards pass and set beta=[], gamma2=[] [0] % 'scaled' - if 1, normalize alphas and betas to prevent underflow [1] % 'maximize' - if 1, use max-product instead of sum-product [0] % % OUTPUTS: % alpha(i,t) = p(Q(t)=i | y(1:t)) (or p(Q(t)=i, y(1:t)) if scaled=0) % beta(i,t) = p(y(t+1:T) | Q(t)=i)*p(y(t+1:T)|y(1:t)) (or p(y(t+1:T) | Q(t)=i) if scaled=0) % gamma(i,t) = p(Q(t)=i | y(1:T)) % loglik = log p(y(1:T)) % xi(i,j,t-1) = p(Q(t-1)=i, Q(t)=j | y(1:T)) - NO LONGER COMPUTED % xi_summed(i,j) = sum_{t=}^{T-1} xi(i,j,t) - changed made by Herbert Jaeger % gamma2(j,k,t) = p(Q(t)=j, M(t)=k | y(1:T)) (only for MOG outputs) % % If fwd_only = 1, these become % alpha(i,t) = p(Q(t)=i | y(1:t)) % beta = [] % gamma(i,t) = p(Q(t)=i | y(1:t)) % xi(i,j,t-1) = p(Q(t-1)=i, Q(t)=j | y(1:t)) % gamma2 = [] % % Note: we only compute xi if it is requested as a return argument, since it can be very large. % Similarly, we only compute gamma2 on request (and if using MOG outputs). % % Examples: % % [alpha, beta, gamma, loglik] = fwdback(pi, A, multinomial_prob(sequence, B)); % % [B, B2] = mixgauss_prob(data, mu, Sigma, mixmat); % [alpha, beta, gamma, loglik, xi, gamma2] = fwdback(pi, A, B, 'obslik2', B2, 'mixmat', mixmat); ''' obslik2 = kwargs.pop('obslik2', None) mixmat = kwargs.pop('mixmat', None) fwd_only = kwargs.pop('fwd_only', False) scaled = kwargs.pop('scaled', True) act = kwargs.pop('act', None) maximize = kwargs.pop('maximize', False) compute_xi = kwargs.pop('compute_xi', obslik2!=None) compute_gamma2 = kwargs.pop('compute_gamma2', obslik2!=None and mixmat!=None) init_state_distrib = np.asmatrix(init_state_distrib) obslik = np.asmatrix(obslik) Q, T = obslik.shape; if act==None: act = np.zeros((T,)) transmat = transmat[np.newaxis,:,:] scale = np.ones((T,)) # scale(t) = Pr(O(t) | O(1:t-1)) = 1/c(t) as defined by Rabiner (1989). # Hence prod_t scale(t) = Pr(O(1)) Pr(O(2)|O(1)) Pr(O(3) | O(1:2)) ... = Pr(O(1), ... ,O(T)) # or log P = sum_t log scale(t). # Rabiner suggests multiplying beta(t) by scale(t), but we can instead # normalise beta(t) - the constants will cancel when we compute gamma. loglik = 0 alpha = np.asmatrix(np.zeros((Q,T))) gamma = np.asmatrix(np.zeros((Q,T))) if compute_xi: xi_summed = np.zeros((Q,Q)); else: xi_summed = None ######## Forwards ######## t = 0 alpha[:,t] = np.multiply(init_state_distrib, obslik[:,t].T).T if scaled: #[alpha(:,t), scale(t)] = normaliseC(alpha(:,t)); alpha[:,t], scale[t] = normalise(alpha[:,t]) #assert(approxeq(sum(alpha(:,t)),1)) for t in range (1, T): #trans = transmat(:,:,act(t-1))'; trans = transmat[act[t-1]] if maximize: m = max_mult(trans.T, alpha[:,t-1]) #A = repmat(alpha(:,t-1), [1 Q]); #m = max(trans .* A, [], 1); else: m = np.dot(trans.T,alpha[:,t-1]) alpha[:,t] = np.multiply(m, obslik[:,t]) if scaled: #[alpha(:,t), scale(t)] = normaliseC(alpha(:,t)); alpha[:,t], scale[t] = normalise(alpha[:,t]) if compute_xi and fwd_only: # useful for online EM #xi(:,:,t-1) = normaliseC((alpha(:,t-1) * obslik(:,t)') .* trans); xi_summed = xi_summed + normalise(np.multiply(np.dot(alpha[:,t-1], obslik[:,t].T), trans))[0]; #assert(approxeq(sum(alpha(:,t)),1)) if scaled: if np.any(scale==0): loglik = -np.Inf; else: loglik = np.sum(np.log(scale), 0) else: loglik = np.log(np.sum(alpha[:,T], 0)) if fwd_only: gamma = alpha; beta = None; gamma2 = None; return alpha, beta, gamma, loglik, xi_summed, gamma2 ######## Backwards ######## beta = np.asmatrix(np.zeros((Q,T))) if compute_gamma2: if isinstance(mixmat, list): M = mixmat[0].shape[1] else: M = mixmat.shape[1] gamma2 = np.zeros((Q,M,T)) else: gamma2 = None beta[:,T-1] = np.ones((Q,1)) #%gamma(:,T) = normaliseC(alpha(:,T) .* beta(:,T)); gamma[:,T-1], _ = normalise(np.multiply(alpha[:,T-1], beta[:,T-1])) t=T-1 if compute_gamma2: denom = obslik[:,t] + (obslik[:,t]==0) # replace 0s with 1s before dividing if isinstance(mixmat, list): #in case mixmax is an anyarray gamma2[:,:,t] = np.divide(np.multiply(np.multiply(obslik2[:,:,t], mixmat[t]), np.tile(gamma[:,t], (1, M))), np.tile(denom, (1, M))); else: gamma2[:,:,t] = np.divide(np.multiply(np.multiply(obslik2[:,:,t], mixmat), np.tile(gamma[:,t], (1, M))), np.tile(denom, (1, M))) #TODO: tiling and asmatrix might be slow. mybe remove #gamma2(:,:,t) = normaliseC(obslik2(:,:,t) .* mixmat .* repmat(gamma(:,t), [1 M])); % wrong! for t in range(T-2, -1, -1): b = np.multiply(beta[:,t+1], obslik[:,t+1]) #trans = transmat(:,:,act(t)); trans = transmat[act[t]] if maximize: B = np.tile(b.T, (Q, 1)) beta[:,t] = np.max(np.multiply(trans, B), 1) else: beta[:,t] = np.dot(trans, b) if scaled: #beta(:,t) = normaliseC(beta(:,t)); beta[:,t], _ = normalise(beta[:,t]) #gamma(:,t) = normaliseC(alpha(:,t) .* beta(:,t)); gamma[:,t], _ = normalise(np.multiply(alpha[:,t], beta[:,t])) if compute_xi: #xi(:,:,t) = normaliseC((trans .* (alpha(:,t) * b'))); xi_summed = xi_summed + normalise(np.multiply(trans, np.dot(alpha[:,t],b.T)))[0] if compute_gamma2: denom = obslik[:,t] + (obslik[:,t]==0) # replace 0s with 1s before dividing if isinstance(mixmat, list): #in case mixmax is an anyarray gamma2[:,:,t] = np.divide(np.multiply(np.multiply(obslik2[:,:,t], mixmat[t]), np.tile(gamma[:,t], (1, M))), np.tile(denom, (1, M))) else: gamma2[:,:,t] = np.divide(np.multiply(np.multiply(obslik2[:,:,t], mixmat), np.tile(gamma[:,t], (1, M))), np.tile(denom, (1, M))) #gamma2(:,:,t) = normaliseC(obslik2(:,:,t) .* mixmat .* repmat(gamma(:,t), [1 M])); # We now explain the equation for gamma2 # Let zt=y(1:t-1,t+1:T) be all observations except y(t) # gamma2(Q,M,t) = P(Qt,Mt|yt,zt) = P(yt|Qt,Mt,zt) P(Qt,Mt|zt) / P(yt|zt) # = P(yt|Qt,Mt) P(Mt|Qt) P(Qt|zt) / P(yt|zt) # Now gamma(Q,t) = P(Qt|yt,zt) = P(yt|Qt) P(Qt|zt) / P(yt|zt) # hence # P(Qt,Mt|yt,zt) = P(yt|Qt,Mt) P(Mt|Qt) [P(Qt|yt,zt) P(yt|zt) / P(yt|Qt)] / P(yt|zt) # = P(yt|Qt,Mt) P(Mt|Qt) P(Qt|yt,zt) / P(yt|Qt) return alpha, beta, gamma, loglik, xi_summed, gamma2
def test(self): A = np.matrix([[2, 1], [0, 1]]) x = np.matrix([[1], [1]]) assert np.all(max_mult(A, x) == np.matrix([[2], [1]]))
def test(self): A = np.matrix([[2,1], [0,1]]) x = np.matrix([[1],[1]]) assert np.all(max_mult(A, x)==np.matrix([[2],[1]]))