%Author: Ashish Kapoor MIT Media Lab, 10/20/02
%Please send corrections to: ash@media.mit.edu
function [pi_out, a_out, b_out, llk_array]=learn_hmm_param(pi_in, a_in, b_in, num_states, obs_data, EPSILON);
%Learn parameters of HMM until abs(newloglikelihood-oldloglikelihood) <= EPSILON%

pi_out = pi_in;
a_out = a_in;
b_out = b_in;

%Memory Allocation

K = size(obs_data,2);
alpha_hat = cell(K, 1);
alpha_hat_hat = cell(K, 1);
c=cell(K, 1);
beta_hat = cell(K, 1);
beta_hat_hat = cell(K, 1);

llk=0; old_llk=-999;
llk_array=[];

while abs(llk-old_llk) > EPSILON
    pi=pi_out;
    a=a_out;
    b=b_out;
    
    %**********************************************************************************
    %Compute alphas and betas corresponding to each observation

    for k=1:K
        %Initialization
        obs=obs_data{k};
        T = size(obs,1);
    
        alpha_hat{k} = zeros(T, num_states);
        alpha_hat_hat{k} = zeros(T, num_states);
        c{k} = zeros(T,1);
        beta_hat{k} = zeros(T, num_states);
        beta_hat_hat{k} = zeros(T, num_states);
    
        %Computation of alphas
        %Init alphas and c
        alpha_hat_hat{k}(1,:)=pi'.*b(:,obs(1)+1)';
        c{k}(1)=1/sum(alpha_hat_hat{k}(1,:));
        alpha_hat{k}(1,:)=c{k}(1)*alpha_hat_hat{k}(1,:);
        %Recursion for alphas
        for t=2:T
            alpha_hat_hat{k}(t,:)=(alpha_hat{k}(t-1,:)*a(:,:)).*b(:,obs(t)+1)';
            c{k}(t)=1/sum(alpha_hat_hat{k}(t,:));        
            alpha_hat{k}(t,:)=c{k}(t)*alpha_hat_hat{k}(t,:);    
        end
    
        %Computation of betas
        %Init betas
        beta_hat_hat{k}(T,:)=1;
        beta_hat{k}(T,:)=c{k}(T)*beta_hat_hat{k}(T,:);
        %Recursion for betas
        for t=1:T-1
            beta_hat_hat{k}(T-t,:)=a(:,:) * (b(:,obs(T-t+1)+1) .* beta_hat{k}(T-t+1,:)') ;
            beta_hat{k}(T-t,:)=c{k}(T-t)*beta_hat_hat{k}(T-t,:);      
        end
    end

    %**********************************************************************************
    %Update pi, a and b
   
    %pi stays the same
    pi_out=pi;

    %Update a and b
    for i=1:num_states
        num_a=zeros(1,size(a,2));
        num_b=zeros(1,size(b,2)); 
        denom_a=0;
        denom_b=0;
            
        for k=1:K
            obs=obs_data{k};
            T = size(obs,1);

            for t=1:T-1
                num_a = num_a + alpha_hat{k}(t,i)*a(i,:).*b(:,obs(t+1)+1)'.*beta_hat{k}(t+1,:);
                num_b(obs(t)+1) = num_b(obs(t)+1) + (alpha_hat{k}(t,i)*beta_hat{k}(t,i)/c{k}(t));
            end
            
            denom_a=denom_a + sum((alpha_hat{k}(1:T-1,i).*beta_hat{k}(1:T-1,i)./c{k}(1:T-1)));
            denom_b=denom_b + sum((alpha_hat{k}(1:T,i).*beta_hat{k}(1:T,i)./c{k}(1:T)));
            num_b(obs(T)+1) = num_b(obs(T)+1) + (alpha_hat{k}(T,i)*beta_hat{k}(T,i)/c{k}(T));
        end
    
        a_out(i,:)=num_a/denom_a;
        b_out(i,:)=num_b/denom_b;
    end
    
    %***********************************************************************************
    %Compute Convergence Criterea
    llk = 0;
    old_llk = 0;

    for k=1:K
        llk = llk + log_prob_obs_hmm(pi_out, a_out, b_out, num_states, obs_data{k});
        old_llk = old_llk + (-sum(log(c{k})));
    end
    
    %Plot loglikelihoods
    if size(llk_array)==0
        fprintf(1, 'Initial Loglikelihood = %f\n', old_llk);
        llk_array=[old_llk llk];
    else
        llk_array=[llk_array llk];
    end

    fprintf(1, 'Current Loglikelihood = %f\n', llk);
end
