Example #1
0
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
Example #2
0
 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]]))
Example #3
0
 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]]))