Example #1
0
def do_first_em_cycle(pars_init, obs_scheme, y, u=None, eps=1e-30, 
                           use_A_flag=True,use_B_flag=False,diag_R_flag=True):
    """ INPUT:
        pars:      collection of initial parameters for LDS
        obs_scheme: observation scheme for given data, stored in dictionary
                   with keys 'sub_pops', 'obs_time', 'obs_pops'
        y:         data array of observed variables
        u:         data array of input variables
        eps:       precision (stopping criterion) for deciding on convergence
                   of latent covariance estimates durgin the E-step                   
        This function serves to quickly get the results of one EM-cycle. It is
        mostly intended to generate results that can quickly be compared with
        other EM implementations or different parameter initialisation methods. 
    """

    y_dim = y.shape[0]
    u_dim = ssm_fit._get_u_dim(u)
    t_tot = y.shape[1]

    pars_init = dict(pars_init)
    ssm_fit.check_pars(pars=pars_init, 
                       y_dim=y_dim, 
                       u_dim=u_dim)         
    obs_scheme = dict(obs_scheme)
    ssm_fit.check_obs_scheme(obs_scheme=obs_scheme,
                             y_dim=y_dim,
                             t_tot=t_tot)                                   

    # do one E-step
    stats_init, ll_init, t_conv_ft, t_conv_sm    = \
      ssm_fit.lds_e_step(pars=pars_init,
                         y=y,
                         u=u, 
                         obs_scheme=obs_scheme,
                         eps=eps)        

    # do one M-step      
    pars_first = ssm_fit.lds_m_step(stats=stats_init,
                                    y=y, 
                                    u=u,
                                    obs_scheme=obs_scheme,
                                    use_A_flag=use_A_flag,
                                    use_B_flag=use_B_flag,
                                    diag_R_flag=diag_R_flag)

    # do another E-step
    stats_first, ll_first, t_conv_ft, t_conv_sm = \
      ssm_fit.lds_e_step(pars=pars_first,
                         y=y,
                         u=u, 
                         obs_scheme=obs_scheme,
                         eps=eps)        

    return stats_init, ll_init, pars_first, stats_first, ll_first
Example #2
0
def do_e_step(pars, y, u=None, obs_scheme=None, eps=1e-30):
    """ INPUT:
        pars  : (list of) LDS parameters
        y: data array of observed variables
        u: data array of input variables
        obs_scheme: observation scheme for given data, stored in dictionary
                   with keys 'sub_pops', 'obs_time', 'obs_pops'
        eps:       precision (stopping criterion) for deciding on convergence
                   of latent covariance estimates durgin the E-step 
        Wrapper function to quickly compute a single E-step with given data y
        and LDS parameters pars. This function mostly exists to deal with the
        difference between having no input u and not using it (i.e. parameter
        B = 0). 
    """

    if (u is None and 
        not (pars['B'] is None or pars['B'] == [] or
             (isinstance(pars['B'],np.ndarray) and pars['B'].size==0) or
             (isinstance(pars['B'],np.ndarray) and pars['B'].size>0 
              and np.max(abs(pars['B']))==0) or
             (isinstance(pars['B'],(float,numbers.Integral)) 
              and pars['B']==0))):
        print(('Warning: Parameter B is initialised, but input u = None. '
               'Algorithm will ignore input for the LDS, outcomes may be '
               'not as expected.'))

    y_dim = y.shape[0]
    if isinstance(u, np.ndarray):
        u_dim = u.shape[0]
    else:
        u_dim = 0

    if obs_scheme is None:
        t_tot = y.shape[1]
        print('creating default observation scheme: Full population observed')
        obs_scheme = {'sub_pops': [list(range(y_dim))], # creates default case
                     'obs_time': [t_tot],               # of fully observed
                     'obs_pops': [0]}                   # population

    ssm_fit.check_pars(pars=pars,
                       y_dim=y_dim, 
                       u_dim=u_dim)                     

    stats, lltr, t_conv_ft, t_conv_sm = \
        ssm_fit.lds_e_step(pars=pars,
                           y=y,
                           u=u, 
                           obs_scheme=obs_scheme,
                           eps=eps)        

    return stats, lltr, t_conv_ft, t_conv_sm
Example #3
0
def run(x_dim, 
        y_dim, 
        u_dim, 
        t_tot, 
        obs_scheme=None, 

        max_iter=10, 
        epsilon=np.log(1.001),
        eps_cov=0,        
        plot_flag=False,
        trace_pars_flag=False,
        trace_stats_flag=False,
        diag_R_flag=True,
        use_A_flag=True,
        use_B_flag=False,

        pars_true=None,                          
        gen_A_true='diagonal', 
        lts_true=None,
        gen_B_true='random', 
        gen_Q_true='identity', 
        gen_mu0_true='random', 
        gen_V0_true='identity', 
        gen_C_true='random', 
        gen_d_true='scaled', 
        gen_R_true='fraction',

        pars_init=None,
        gen_A_init='diagonal', 
        lts_init=None,
        gen_B_init='random',  
        gen_Q_init='identity', 
        gen_mu0_init='random', 
        gen_V0_init='identity', 
        gen_C_init='random', 
        gen_d_init='mean', 
        gen_R_init='fractionObserved',

        u=None, input_type='pwconst',const_input_length=1,
        y=None, 
        x=None,
        interm_store_flag = False,
        save_file='LDS_data.mat'):

    """ INPUT:
        x_dim : dimensionality of latent states x
        y_dim : dimensionality of observed states y
        u_dim : dimensionality of input states u
        t_tot : trial length (in number of time points)
        obs_scheme: observation scheme for given data, stored in dictionary
                   with keys 'sub_pops', 'obs_time', 'obs_pops'
        max_iter: maximum number of allowed EM steps
        epsilon:    precision (stopping criterion) for deciding on convergence
                    of overall EM algorithm
        eps_cov: precision (stopping criterion) for deciding on convergence
                    of latent covariance estimates durgin the E-step        
        plot_flag   : boolean specifying if to visualise fitting progress`
        trace_pars_flag:  boolean, specifying if entire parameter updates 
                           history or only the current state is kept track of 
        trace_stats_flag: boolean, specifying if entire history of inferred  
                           latents or only the current state is kept track of 
        diag_R_flag      : boolean specifying if R is represented as diagonal
        use_A_flag  : boolean specifying whether to fit the LDS with parameter A
        use_B_flag  : boolean specifying whether to fit the LDS with parameter B

        pars_true : None, or list/np.ndarray/dict containing no, some or all
                   of the desired ground-truth parameters. Will identify any
                   parameters not handed over and will fill in the rest
                   according to selected strings below.
        gen_A_true   : string specifying methods of parameter generation
        lts_true    : ndarray with one entry per latent time scale (i.e. x_dim)
        gen_B_true   : string specifying methods of parameter generation
        gen_Q_true   :  ""
        gen_mu0_true :  "" 
        gen_C_true   :  ""
        gen_V0_true   :  "" 
        gen_d_true   :  ""
        gen_R_true   : (see below for details)
        pars_init : None, or list/np.ndarray/dict containing no, some or all
                   of the desired parameter initialisations. Will identify any
                   parameters not handed over and will fill in the rest
                   according to selected strings below.
        gen_A_init   : string specifying methods of parameter initialisation
        lts_init    : ndarray with one entry per latent time scale (i.e. x_dim)
        gen_B_init   : string specifying methods of parameter initialisation
        gen_Q_init   :  ""
        gen_mu0_init :  "" 
        gen_V0_init  :  ""
        gen_C_init   :  "" 
        gen_d_init   :  ""
        gen_R_init   : (see below for details)
        x: data array of latent variables
        y: data array of observed variables
        u: data array of input variables
        interm_store_flag : boolean, specifying whether or not to 
                                     store the intermediate results after 
                                     each EM cycle to the same folder as
                                     given by input variable save_file
        save_file : (path to folder and) name of file for storing results.  
        Generates parameters of an LDS, potentially by looking at given data.
        Can be used for for generating ground-truth parameters for generating
        data from an artificial experiment using the LDS, or for finding 
        parameter initialisations for fitting an LDS to data. Usage is slightly
        different in the two cases (see below). By nature of the wide range of
        applicability of the LDS model, this function contains many options
        (implemented as strings differing different cases, and arrays giving
         user-specified values such as timescale ranges), and is to be extended
        even further in the future.

    """
    if not isinstance(use_B_flag,bool):
        raise Exception('use_B_flag has to be a boolean. However, it is', use_B_flag)

    if not isinstance(use_A_flag,bool):
        raise Exception('use_A_flag has to be a boolean. However, it is', use_A_flag)

    if not isinstance(diag_R_flag,bool):
        raise Exception('diag_R_flag has to be a boolean. However, it is ',
                         diag_R_flag)

    if not isinstance(interm_store_flag,bool):
        raise Exception(('interm_store_flag has to be a boolean' 
                         'However, it is '), interm_store_flag)
       
    obs_scheme = ssm_fit.check_obs_scheme(obs_scheme=obs_scheme,
                                          y_dim=y_dim,
                                          t_tot=t_tot)

    if y is None:
        if lts_true is None:
            lts_true = np.linspace(0.9,0.98,x_dim)
        pars_true, pars_options_true = gen_pars(
                          x_dim=x_dim, 
                          y_dim=y_dim, 
                          u_dim=u_dim, 
                          pars_in=pars_true,
                          obs_scheme=obs_scheme,
                          gen_A=gen_A_true, 
                          lts=lts_true,
                          gen_B=gen_B_true, 
                          gen_Q=gen_Q_true, 
                          gen_mu0=gen_mu0_true, 
                          gen_V0=gen_V0_true, 
                          gen_C=gen_C_true,
                          gen_d=gen_d_true, 
                          gen_R=gen_R_true)
        n_tr = 1 # fix to always just one repetition for now        

        # generate data from model
        print('generating data from model with ground-truth parameters')
        x,y,u = sim_data(pars=pars_true,
                         t_tot=t_tot,
                         n_tr=n_tr,
                         obs_scheme=obs_scheme,
                         u=u,
                         input_type=input_type,
                         const_input_length=const_input_length)

        Pi   = sp.linalg.solve_discrete_lyapunov(pars_true['A'], 
                                                 pars_true['Q'])
        Pi_t = np.dot(pars_true['A'].transpose(), Pi)

        stats_true, lltr, t_conv_ft, t_conv_sm = \
                        do_e_step(pars=pars_true, 
                                  y=y, 
                                  u=u, 
                                  obs_scheme=obs_scheme, 
                                  eps=eps_cov)

    else:  # i.e. if data provided
        pars_true = {}
        pars_true['A'] = 0
        pars_true['B'] = 0
        pars_true['Q'] = 0
        pars_true['mu0'] = 0
        pars_true['V0'] = 0
        pars_true['C'] = 0
        pars_true['d'] = 0
        pars_true['R'] = 0
        Pi   = 0
        Pi_t = 0
        ext_true = 0
        extxt_true = 0
        extxtm1_true = 0
        lltr = 0


    # get initial parameters
    if not use_A_flag: # overwrites any other parameter choices for A! Set A = 0 
        if isinstance(pars_init, dict) and ('A' in pars_init):
            pars_init['A'] = np.zeros((x_dim,x_dim))
        elif (isinstance(pars_init,(list,np.ndarray)) and 
              not pars_init[0] is None):
            iniPars[0] = np.zeros((x_dim,x_dim))
        elif not gen_A_init == 'zero': 
            print(('Warning: set flag use_A_flag=False, but did not set gen_A_init '
                   'to zero. Will overwrite gen_A_init to zero now.'))
            gen_A_init = 'zero'
    if not use_B_flag: # overwrites any other parameter choices for B! Set B = 0
        if isinstance(pars_init, dict) and ('B' in pars_init):
            pars_init['B'] = np.zeros((x_dim,u_dim))
        elif (isinstance(pars_init,(list,np.ndarray)) and 
              not pars_init[1] is None):
            iniPars[1] = np.zeros((x_dim,u_dim))
        elif not gen_B_init == 'zero': 
            print(('Warning: set flag ifBseA=False, but did not set gen_B_init '
                   'to zero. Will overwrite gen_B_init to zero now.'))
            gen_B_init = 'zero'

    if lts_init is None:
        lts_init = np.random.uniform(size=[x_dim])

    pars_init, pars_options_init = gen_pars(
                      x_dim=x_dim, 
                      y_dim=y_dim, 
                      u_dim=u_dim, 
                      pars_in=pars_init, 
                      obs_scheme=obs_scheme,
                      gen_A=gen_A_init, 
                      lts=lts_init,
                      gen_B=gen_B_init, 
                      gen_Q=gen_Q_init, 
                      gen_mu0=gen_mu0_init, 
                      gen_V0=gen_V0_init, 
                      gen_C=gen_C_init,
                      gen_d=gen_d_init, 
                      gen_R=gen_R_init,
                      x=x, y=y, u=u)

    # check initial goodness of fit for initial parameters
    stats_init,ll_init,pars_first,stats_first,ll_first = do_first_em_cycle(
                                                          pars_init=pars_init, 
                                                          obs_scheme=obs_scheme, 
                                                          y=y, 
                                                          u=u, 
                                                          eps=eps_cov,
                                                          use_A_flag=use_A_flag,
                                                          use_B_flag=use_B_flag,
                                                          diag_R_flag=diag_R_flag)
    if interm_store_flag:
        save_file_interm = save_file
    else: 
        save_file_interm = None


    fit_lds = setup_fit_lds(y=y, 
                            u=y, 
                            max_iter=max_iter,
                            epsilon=epsilon, 
                            eps_cov=eps_cov,
                            plot_flag=plot_flag, 
                            trace_pars_flag=trace_pars_flag, 
                            trace_stats_flag=trace_stats_flag, 
                            diag_R_flag=diag_R_flag,
                            use_A_flag=use_A_flag, 
                            use_B_flag=use_B_flag)

    # fit the model to data          
    print('fitting model to data')
    t = time.time()
    pars_hat,ll = fit_lds(x_dim=x_dim,
                          pars=pars_init, 
                          obs_scheme=obs_scheme,
                          save_file=save_file_interm)

    elapsed_time = time.time() - t
    print('elapsed time for fitting is')
    print(elapsed_time)


    stats_hat = ssm_fit._setup_stats(y, x_dim, u_dim)
    if use_B_flag:
        stats_hat, ll_hat, t_conv_ft, t_conv_sm = \
         ssm_fit.lds_e_step(pars=pars_hat,
                            y=y,
                            u=u, 
                            obs_scheme=obs_scheme,
                            eps=eps_cov)

    else:
        stats_hat, ll_hat, t_conv_ft, t_conv_sm = \
         ssm_fit.lds_e_step(pars=pars_hat,
                            y=y,
                            u=None, 
                            obs_scheme=obs_scheme,
                            eps=eps_cov)            

    Pi_h   = sp.linalg.solve_discrete_lyapunov(pars_hat['A'], 
                                               pars_hat['Q'])
    Pi_t_h  = np.dot(pars_hat['A'].transpose(), Pi_h)

    # save results for visualisation (with Matlab code)
    if u is None:
        u = 0
        B_h = 0
        B_hs = [0]
    if B_h is None:
        B_h = 0
        B_hs = [0]
    save_file_m = {'x': x, 'y': y, 'u' : u, 'll' : ll, 
                      'T' : t_tot, 'Trial':n_tr, 'elapsedTime' : elapsed_time,
                      'inputType' : input_type,
                      'constInputLngth' : const_input_length,
                      'ifUseB':use_B_flag, 'ifUseA':use_A_flag, 
                      'epsilon':epsilon,
                      'ifPlotProgress':plot_flag,
                      'ifTraceParamHist':trace_pars_flag,
                      'ifTraceStatsHist':trace_stats_flag,
                      'ifRDiagonal':diag_R_flag,
                      'ifUseB':use_B_flag,
                      'covConvEps':eps_cov,        
                      'truePars':pars_true,
                      'initPars':pars_init,
                      'firstPars':pars_first,
                      'estPars': pars_hat,
                      'stats_0': stats_init,
                      'stats_1': stats_first,
                      'stats_h': stats_hat,
                      'stats_true': stats_true,
                      'Pi':Pi,'Pi_h':Pi_h,'Pi_t':Pi_t,'Pi_t_h': Pi_t_h,
                      'obsScheme' : obs_scheme}

    savemat(save_file,save_file_m) # does the actual saving

    return y,x,u,pars_hat,pars_init,pars_true