예제 #1
0
파일: generic_hmm.py 프로젝트: yarden/bhmm
    def update(self, Tij, Pi=None):
        r""" Updates the transition matrix and recomputes all derived quantities """
        # EMMA imports
        from msmtools import analysis as msmana

        # save a copy of the transition matrix
        self._Tij = np.array(Tij)
        assert msmana.is_transition_matrix(
            self._Tij), 'Given transition matrix is not a stochastic matrix'
        assert self._Tij.shape[
            0] == self._nstates, 'Given transition matrix has unexpected number of states '

        # initial / stationary distribution
        if (Pi is not None):
            assert np.all(
                Pi >= 0
            ), 'Given initial distribution contains negative elements.'
            Pi = np.array(Pi) / np.sum(
                Pi)  # ensure normalization and make a copy

        if (self._stationary):
            pT = msmana.stationary_distribution(self._Tij)
            if Pi is None:  # stationary and no stationary distribution fixed, so computing it from trans. mat.
                self._Pi = pT
            else:  # stationary but stationary distribution is fixed, so the transition matrix must be consistent
                assert np.allclose(Pi, pT), 'Stationary HMM requested, but given distribution is not the ' \
                                            'stationary distribution of the given transition matrix.'
                self._Pi = Pi
        else:
            if Pi is None:  # no initial distribution given, so use stationary distribution anyway
                self._Pi = msmana.stationary_distribution(self._Tij)
            else:
                self._Pi = Pi

        # reversible
        if self._reversible:
            assert msmana.is_reversible(
                Tij
            ), 'Reversible HMM requested, but given transition matrix is not reversible.'

        # try to do eigendecomposition by default, because it's very cheap for hidden transition matrices
        from scipy.linalg import LinAlgError
        try:
            if self._reversible:
                self._R, self._D, self._L = msmana.rdl_decomposition(
                    self._Tij, norm='reversible')
                # everything must be real-valued
                self._R = self._R.real
                self._D = self._D.real
                self._L = self._L.real
            else:
                self._R, self._D, self._L = msmana.rdl_decomposition(
                    self._Tij, norm='standard')
            self._eigenvalues = np.diag(self._D)
            self._spectral_decomp_available = True
        except LinAlgError:
            logger().warn('Eigendecomposition failed for transition matrix\n' +
                          str(self._Tij) +
                          '\nspectral properties will not be available')
            self._spectral_decomp_available = False
예제 #2
0
    def sample(self, nsamples, nburn=0, nthin=1, save_hidden_state_trajectory=False, call_back=None):
        """Sample from the BHMM posterior.

        Parameters
        ----------
        nsamples : int
            The number of samples to generate.
        nburn : int, optional, default=0
            The number of samples to discard to burn-in, following which `nsamples` will be generated.
        nthin : int, optional, default=1
            The number of Gibbs sampling updates used to generate each returned sample.
        save_hidden_state_trajectory : bool, optional, default=False
            If True, the hidden state trajectory for each sample will be saved as well.
        call_back : function, optional, default=None
            a call back function with no arguments, which if given is being called
            after each computed sample. This is useful for implementing progress bars.

        Returns
        -------
        models : list of bhmm.HMM
            The sampled HMM models from the Bayesian posterior.

        Examples
        --------

        >>> from bhmm import testsystems
        >>> [model, observations, states, sampled_model] = testsystems.generate_random_bhmm(ntrajectories=5, length=1000)
        >>> nburn = 5 # run the sampler a bit before recording samples
        >>> nsamples = 10 # generate 10 samples
        >>> nthin = 2 # discard one sample in between each recorded sample
        >>> samples = sampled_model.sample(nsamples, nburn=nburn, nthin=nthin)

        """

        # Run burn-in.
        for iteration in range(nburn):
            logger().info("Burn-in   %8d / %8d" % (iteration, nburn))
            self._update()

        # Collect data.
        models = list()
        for iteration in range(nsamples):
            logger().info("Iteration %8d / %8d" % (iteration, nsamples))
            # Run a number of Gibbs sampling updates to generate each sample.
            for thin in range(nthin):
                self._update()
            # Save a copy of the current model.
            model_copy = copy.deepcopy(self.model)
            # print "Sampled: \n",repr(model_copy)
            if not save_hidden_state_trajectory:
                model_copy.hidden_state_trajectory = None
            models.append(model_copy)
            if call_back is not None:
                call_back()

        # Return the list of models saved.
        return models
예제 #3
0
    def _generateInitialModel(self, output_model_type):
        """Initialize using an MLHMM.

        """
        logger().info("Generating initial model for BHMM using MLHMM...")
        from bhmm.estimators.maximum_likelihood import MaximumLikelihoodEstimator
        mlhmm = MaximumLikelihoodEstimator(self.observations, self.nstates, reversible=self.reversible, type=output_model_type)
        model = mlhmm.fit()
        return model
예제 #4
0
    def update(self, Tij, Pi=None):
        r""" Updates the transition matrix and recomputes all derived quantities """
        # EMMA imports
        from pyemma.msm import analysis as msmana

        # save a copy of the transition matrix
        self._Tij = np.array(Tij)
        assert msmana.is_transition_matrix(self._Tij), "Given transition matrix is not a stochastic matrix"
        assert self._Tij.shape[0] == self._nstates, "Given transition matrix has unexpected number of states "

        # initial / stationary distribution
        if Pi is not None:
            assert np.all(Pi >= 0), "Given initial distribution contains negative elements."
            Pi = np.array(Pi) / np.sum(Pi)  # ensure normalization and make a copy

        if self._stationary:
            pT = msmana.stationary_distribution(self._Tij)
            if Pi is None:  # stationary and no stationary distribution fixed, so computing it from trans. mat.
                self._Pi = pT
            else:  # stationary but stationary distribution is fixed, so the transition matrix must be consistent
                assert np.allclose(Pi, pT), (
                    "Stationary HMM requested, but given distribution is not the "
                    "stationary distribution of the given transition matrix."
                )
                self._Pi = Pi
        else:
            if Pi is None:  # no initial distribution given, so use stationary distribution anyway
                self._Pi = msmana.stationary_distribution(self._Tij)
            else:
                self._Pi = Pi

        # reversible
        if self._reversible:
            assert msmana.is_reversible(Tij), "Reversible HMM requested, but given transition matrix is not reversible."

        # try to do eigendecomposition by default, because it's very cheap for hidden transition matrices
        from scipy.linalg import LinAlgError

        try:
            if self._reversible:
                self._R, self._D, self._L = msmana.rdl_decomposition(self._Tij, norm="reversible")
                # everything must be real-valued
                self._R = self._R.real
                self._D = self._D.real
                self._L = self._L.real
            else:
                self._R, self._D, self._L = msmana.rdl_decomposition(self._Tij, norm="standard")
            self._eigenvalues = np.diag(self._D)
            self._spectral_decomp_available = True
        except LinAlgError:
            logger().warn(
                "Eigendecomposition failed for transition matrix\n"
                + str(self._Tij)
                + "\nspectral properties will not be available"
            )
            self._spectral_decomp_available = False
예제 #5
0
파일: gaussian.py 프로젝트: yarden/bhmm
    def _sample_output_model(self, observations):
        """
        Sample a new set of distribution parameters given a sample of observations from the given state.

        Both the internal parameters and the attached HMM model are updated.

        Parameters
        ----------
        observations :  [ numpy.array with shape (N_k,) ] with `nstates` elements
            observations[k] is a set of observations sampled from state `k`

        Examples
        --------

        Generate synthetic observations.

        >>> nstates = 3
        >>> nobs = 1000
        >>> output_model = GaussianOutputModel(nstates=nstates, means=[-1, 0, 1], sigmas=[0.5, 1, 2])
        >>> observations = [ output_model.generate_observations_from_state(state_index, nobs) for state_index in range(nstates) ]
        >>> weights = [ np.zeros([nobs,nstates], np.float32).T for _ in range(nstates) ]

        Update output parameters by sampling.

        >>> output_model._sample_output_model(observations)

        """
        for state_index in range(self.nstates):
            # Update state emission distribution parameters.

            observations_in_state = observations[state_index]
            # Determine number of samples in this state.
            nsamples_in_state = len(observations_in_state)

            # Skip update if no observations.
            if nsamples_in_state == 0:
                logger().warn('Warning: State %d has no observations.' %
                              state_index)
                continue

            # Sample new mu.
            self.means[state_index] = np.random.randn(
            ) * self.sigmas[state_index] / np.sqrt(
                nsamples_in_state) + np.mean(observations_in_state)

            # Sample new sigma.
            # This scheme uses the improper Jeffreys prior on sigma^2, P(mu, sigma^2) \propto 1/sigma
            chisquared = np.random.chisquare(nsamples_in_state - 1)
            sigmahat2 = np.mean(
                (observations_in_state - self.means[state_index])**2)
            self.sigmas[state_index] = np.sqrt(sigmahat2) / np.sqrt(
                chisquared / nsamples_in_state)

        return
예제 #6
0
    def _generateInitialModel(self, output_model_type):
        """Initialize using an MLHMM.

        """
        logger().info("Generating initial model for BHMM using MLHMM...")
        from bhmm.estimators.maximum_likelihood import MaximumLikelihoodEstimator
        mlhmm = MaximumLikelihoodEstimator(self.observations,
                                           self.nstates,
                                           reversible=self.reversible,
                                           output=output_model_type)
        model = mlhmm.fit()
        return model
예제 #7
0
    def _update(self):
        """Update the current model using one round of Gibbs sampling.

        """
        initial_time = time.time()

        self._updateHiddenStateTrajectories()
        self._updateEmissionProbabilities()
        self._updateTransitionMatrix()

        final_time = time.time()
        elapsed_time = final_time - initial_time
        logger().info("BHMM update iteration took %.3f s" % elapsed_time)
예제 #8
0
    def _update(self):
        """Update the current model using one round of Gibbs sampling.

        """
        initial_time = time.time()

        self._updateHiddenStateTrajectories()
        self._updateEmissionProbabilities()
        self._updateTransitionMatrix()

        final_time = time.time()
        elapsed_time = final_time - initial_time
        logger().info("BHMM update iteration took %.3f s" % elapsed_time)
예제 #9
0
파일: gaussian.py 프로젝트: yarden/bhmm
    def _sample_output_model(self, observations):
        """
        Sample a new set of distribution parameters given a sample of observations from the given state.

        Both the internal parameters and the attached HMM model are updated.

        Parameters
        ----------
        observations :  [ numpy.array with shape (N_k,) ] with `nstates` elements
            observations[k] is a set of observations sampled from state `k`

        Examples
        --------

        Generate synthetic observations.

        >>> nstates = 3
        >>> nobs = 1000
        >>> output_model = GaussianOutputModel(nstates=nstates, means=[-1, 0, 1], sigmas=[0.5, 1, 2])
        >>> observations = [ output_model.generate_observations_from_state(state_index, nobs) for state_index in range(nstates) ]
        >>> weights = [ np.zeros([nobs,nstates], np.float32).T for _ in range(nstates) ]

        Update output parameters by sampling.

        >>> output_model._sample_output_model(observations)

        """
        for state_index in range(self.nstates):
            # Update state emission distribution parameters.

            observations_in_state = observations[state_index]
            # Determine number of samples in this state.
            nsamples_in_state = len(observations_in_state)

            # Skip update if no observations.
            if nsamples_in_state == 0:
                logger().warn('Warning: State %d has no observations.' % state_index)
                continue

            # Sample new mu.
            self.means[state_index] = np.random.randn()*self.sigmas[state_index]/np.sqrt(nsamples_in_state) + np.mean(observations_in_state)

            # Sample new sigma.
            # This scheme uses the improper Jeffreys prior on sigma^2, P(mu, sigma^2) \propto 1/sigma
            chisquared = np.random.chisquare(nsamples_in_state-1)
            sigmahat2 = np.mean((observations_in_state - self.means[state_index])**2)
            self.sigmas[state_index] = np.sqrt(sigmahat2) / np.sqrt(chisquared / nsamples_in_state)

        return
예제 #10
0
    def _update_model(self, gammas, count_matrices, maxiter=10000000):
        """
        Maximization step: Updates the HMM model given the hidden state assignment and count matrices

        Parameters
        ----------
        gamma : [ ndarray(T,N, dtype=float) ]
            list of state probabilities for each trajectory
        count_matrix : [ ndarray(N,N, dtype=float) ]
            list of the Baum-Welch transition count matrices for each hidden
            state trajectory
        maxiter : int
            maximum number of iterations of the transition matrix estimation if
            an iterative method is used.

        """
        gamma0_sum = self._init_counts(gammas)
        C = self._transition_counts(count_matrices)
        logger().info("Initial count = \n" + str(gamma0_sum))
        logger().info("Count matrix = \n" + str(C))

        # compute new transition matrix
        from bhmm.estimators._tmatrix_disconnected import estimate_P, stationary_distribution
        T = estimate_P(C,
                       reversible=self._hmm.is_reversible,
                       fixed_statdist=self._fixed_stationary_distribution,
                       maxiter=maxiter,
                       maxerr=1e-12,
                       mincount_connectivity=1e-16)
        # print 'P:\n', T
        # estimate stationary or init distribution
        if self._stationary:
            if self._fixed_stationary_distribution is None:
                pi = stationary_distribution(T,
                                             C=C,
                                             mincount_connectivity=1e-16)
            else:
                pi = self._fixed_stationary_distribution
        else:
            if self._fixed_initial_distribution is None:
                pi = gamma0_sum / np.sum(gamma0_sum)
            else:
                pi = self._fixed_initial_distribution
        # print 'pi: ', pi, ' stationary = ', self._hmm.is_stationary

        # update model
        self._hmm.update(pi, T)

        logger().info("T: \n" + str(T))
        logger().info("pi: \n" + str(pi))

        # update output model
        self._hmm.output_model.estimate(self._observations, gammas)
예제 #11
0
    def _update_model(self, gammas, count_matrices, maxiter=10000000):
        """
        Maximization step: Updates the HMM model given the hidden state assignment and count matrices

        Parameters
        ----------
        gamma : [ ndarray(T,N, dtype=float) ]
            list of state probabilities for each trajectory
        count_matrix : [ ndarray(N,N, dtype=float) ]
            list of the Baum-Welch transition count matrices for each hidden
            state trajectory
        maxiter : int
            maximum number of iterations of the transition matrix estimation if
            an iterative method is used.

        """
        gamma0_sum = self._init_counts(gammas)
        C = self._transition_counts(count_matrices)
        logger().info("Initial count = \n"+str(gamma0_sum))
        logger().info("Count matrix = \n"+str(C))

        # compute new transition matrix
        from bhmm.estimators._tmatrix_disconnected import estimate_P, stationary_distribution
        T = estimate_P(C, reversible=self._hmm.is_reversible, fixed_statdist=self._fixed_stationary_distribution,
                       maxiter=maxiter, maxerr=1e-12, mincount_connectivity=1e-16)
        # print 'P:\n', T
        # estimate stationary or init distribution
        if self._stationary:
            if self._fixed_stationary_distribution is None:
                pi = stationary_distribution(T, C=C, mincount_connectivity=1e-16)
            else:
                pi = self._fixed_stationary_distribution
        else:
            if self._fixed_initial_distribution is None:
                pi = gamma0_sum / np.sum(gamma0_sum)
            else:
                pi = self._fixed_initial_distribution
        # print 'pi: ', pi, ' stationary = ', self._hmm.is_stationary

        # update model
        self._hmm.update(pi, T)

        logger().info("T: \n"+str(T))
        logger().info("pi: \n"+str(pi))

        # update output model
        self._hmm.output_model.estimate(self._observations, gammas)
예제 #12
0
    def _update_model(self, gammas, count_matrices):
        """
        Maximization step: Updates the HMM model given the hidden state assignment and count matrices

        Parameters
        ----------
        gamma : [ ndarray(T,N, dtype=float) ]
            list of state probabilities for each trajectory
        count_matrix : [ ndarray(N,N, dtype=float) ]
            list of the Baum-Welch transition count matrices for each hidden state trajectory

        """
        K = len(self._observations)
        N = self._nstates

        C = np.zeros((N, N))
        gamma0_sum = np.zeros((N))
        for k in range(K):
            # update state counts
            gamma0_sum += gammas[k][0]
            # update count matrix
            C += count_matrices[k]

        logger().info("Count matrix = \n" + str(C))

        # compute new transition matrix
        from bhmm.estimators._tmatrix_disconnected import estimate_P, stationary_distribution
        T = estimate_P(C,
                       reversible=self._hmm.is_reversible,
                       fixed_statdist=self._fixed_stationary_distribution)
        # stationary or init distribution
        if self._hmm.is_stationary:
            if self._fixed_stationary_distribution is None:
                pi = stationary_distribution(C, T)
            else:
                pi = self._fixed_stationary_distribution
        else:
            if self._fixed_initial_distribution is None:
                pi = gamma0_sum / np.sum(gamma0_sum)
            else:
                pi = self._fixed_initial_distribution

        # update model
        self._hmm.update(T, pi)

        logger().info("T: \n" + str(T))
        logger().info("pi: \n" + str(pi))

        # update output model
        # TODO: need to parallelize model fitting. Otherwise we can't gain much speed!
        self._hmm.output_model._estimate_output_model(self._observations,
                                                      gammas)
예제 #13
0
    def _update_model(self, gammas, count_matrices):
        """
        Maximization step: Updates the HMM model given the hidden state assignment and count matrices

        Parameters
        ----------
        gamma : [ ndarray(T,N, dtype=float) ]
            list of state probabilities for each trajectory
        count_matrix : [ ndarray(N,N, dtype=float) ]
            list of the Baum-Welch transition count matrices for each hidden state trajectory

        """
        K = len(self._observations)
        N = self._nstates

        C = np.zeros((N,N))
        gamma0_sum = np.zeros((N))
        for k in range(K):
            # update state counts
            gamma0_sum += gammas[k][0]
            # update count matrix
            # print 'C['+str(k)+'] = ',count_matrices[k]
            C += count_matrices[k]

        logger().info("Count matrix = \n"+str(C))

        # compute new transition matrix
        from bhmm.msm.tmatrix_disconnected import estimate_P,stationary_distribution
        T = estimate_P(C, reversible=self._hmm.is_reversible, fixed_statdist=self._fixed_stationary_distribution)
        # stationary or init distribution
        if self._hmm.is_stationary:
            if self._fixed_stationary_distribution is None:
                pi = stationary_distribution(C,T)
            else:
                pi = self._fixed_stationary_distribution
        else:
            if self._fixed_initial_distribution is None:
                pi = gamma0_sum / np.sum(gamma0_sum)
            else:
                pi = self._fixed_initial_distribution

        # update model
        self._hmm.update(T, pi)

        logger().info("T: \n"+str(T))
        logger().info("pi: \n"+str(pi))

        # update output model
        # TODO: need to parallelize model fitting. Otherwise we can't gain much speed!
        self._hmm.output_model._estimate_output_model(self._observations, gammas)
예제 #14
0
파일: discrete.py 프로젝트: yarden/bhmm
def initial_model_discrete(observations, nstates, lag=1, reversible=True):
    """Generate an initial model with discrete output densities

    Parameters
    ----------
    observations : list of ndarray((T_i), dtype=int)
        list of arrays of length T_i with observation data
    nstates : int
        The number of states.
    lag : int, optional, default=1
        The lag time to use for initializing the model.

    TODO
    ----
    * Why do we have a `lag` option?  Isn't the HMM model, by definition, lag=1 everywhere?  Why would this be useful instead of just having the user subsample the data?

    Examples
    --------

    Generate initial model for a discrete output model.

    >>> from bhmm import testsystems
    >>> [model, observations, states] = testsystems.generate_synthetic_observations(output_model_type='discrete')
    >>> initial_model = initial_model_discrete(observations, model.nstates)

    """
    # check input
    if not reversible:
        warnings.warn("nonreversible initialization of discrete HMM currently not supported. Using a reversible matrix for initialization.")
        reversible = True

    # estimate Markov model
    C_full = msmtools.estimation.count_matrix(observations, lag)
    lcc = msmtools.estimation.largest_connected_set(C_full)
    Clcc= msmtools.estimation.largest_connected_submatrix(C_full, lcc=lcc)
    T = msmtools.estimation.transition_matrix(Clcc, reversible=True).toarray()

    # pcca
    pcca = msmtools.analysis.dense.pcca.PCCA(T, nstates)

    # default output probability, in order to avoid zero columns
    nstates_full = C_full.shape[0]
    eps = 0.01 * (1.0/nstates_full)

    # Use PCCA distributions, but avoid 100% assignment to any state (prevents convergence)
    B_conn = np.maximum(pcca.output_probabilities, eps)

    # full state space output matrix
    B = eps * np.ones((nstates,nstates_full), dtype=np.float64)
    # expand B_conn to full state space
    B[:,lcc] = B_conn[:,:]
    # renormalize B to make it row-stochastic
    B /= B.sum(axis=1)[:,None]

    # coarse-grained transition matrix
    M = pcca.memberships
    W = np.linalg.inv(np.dot(M.T, M))
    A = np.dot(np.dot(M.T, T), M)
    P_coarse = np.dot(W, A)

    # symmetrize and renormalize to eliminate numerical errors
    X = np.dot(np.diag(pcca.coarse_grained_stationary_probability), P_coarse)
    X = 0.5 * (X + X.T)
    # if there are values < 0, set to eps
    X = np.maximum(X, eps)
    # turn into coarse-grained transition matrix
    A = X / X.sum(axis=1)[:, None]

    logger().info('Initial model: ')
    logger().info('transition matrix = \n'+str(A))
    logger().info('output matrix = \n'+str(B.T))

    # initialize HMM
    # --------------
    output_model = DiscreteOutputModel(B)
    model = HMM(A, output_model)
    return model
예제 #15
0
파일: discrete.py 프로젝트: ChayaSt/bhmm
def init_discrete_hmm_spectral(C_full, nstates, reversible=True, stationary=True, active_set=None, P=None,
                               eps_A=None, eps_B=None, separate=None):
    """Initializes discrete HMM using spectral clustering of observation counts

    Initializes HMM as described in [1]_. First estimates a Markov state model
    on the given observations, then uses PCCA+ to coarse-grain the transition
    matrix [2]_ which initializes the HMM transition matrix. The HMM output
    probabilities are given by Bayesian inversion from the PCCA+ memberships [1]_.

    The regularization parameters eps_A and eps_B are used
    to guarantee that the hidden transition matrix and output probability matrix
    have no zeros. HMM estimation algorithms such as the EM algorithm and the
    Bayesian sampling algorithm cannot recover from zero entries, i.e. once they
    are zero, they will stay zero.

    Parameters
    ----------
    C_full : ndarray(N, N)
        Transition count matrix on the full observable state space
    nstates : int
        The number of hidden states.
    reversible : bool
        Estimate reversible HMM transition matrix.
    stationary : bool
        p0 is the stationary distribution of P. In this case, will not
    active_set : ndarray(n, dtype=int) or None
        Index area. Will estimate kinetics only on the given subset of C
    P : ndarray(n, n)
        Transition matrix estimated from C (with option reversible). Use this
        option if P has already been estimated to avoid estimating it twice.
    eps_A : float or None
        Minimum transition probability. Default: 0.01 / nstates
    eps_B : float or None
        Minimum output probability. Default: 0.01 / nfull
    separate : None or iterable of int
        Force the given set of observed states to stay in a separate hidden state.
        The remaining nstates-1 states will be assigned by a metastable decomposition.

    Returns
    -------
    p0 : ndarray(n)
        Hidden state initial distribution
    A : ndarray(n, n)
        Hidden state transition matrix
    B : ndarray(n, N)
        Hidden-to-observable state output probabilities

    Raises
    ------
    ValueError
        If the given active set is illegal.
    NotImplementedError
        If the number of hidden states exceeds the number of observed states.

    Examples
    --------
    Generate initial model for a discrete output model.

    >>> import numpy as np
    >>> C = np.array([[0.5, 0.5, 0.0], [0.4, 0.5, 0.1], [0.0, 0.1, 0.9]])
    >>> initial_model = init_discrete_hmm_spectral(C, 2)

    References
    ----------
    .. [1] F. Noe, H. Wu, J.-H. Prinz and N. Plattner: Projected and hidden
        Markov models for calculating kinetics and  metastable states of
        complex molecules. J. Chem. Phys. 139, 184114 (2013)
    .. [2] S. Kube and M. Weber: A coarse graining method for the identification
        of transition rates between molecular conformations.
        J. Chem. Phys. 126, 024103 (2007)

    """
    # MICROSTATE COUNT MATRIX
    nfull = C_full.shape[0]

    # INPUTS
    if eps_A is None:  # default transition probability, in order to avoid zero columns
        eps_A = 0.01 / nstates
    if eps_B is None:  # default output probability, in order to avoid zero columns
        eps_B = 0.01 / nfull
    # Manage sets
    symsum = C_full.sum(axis=0) + C_full.sum(axis=1)
    nonempty = np.where(symsum > 0)[0]
    if active_set is None:
        active_set = nonempty
    else:
        if np.any(symsum[active_set] == 0):
            raise ValueError('Given active set has empty states')  # don't tolerate empty states
    if P is not None:
        if np.shape(P)[0] != active_set.size:  # needs to fit to active
            raise ValueError('Given initial transition matrix P has shape ' + str(np.shape(P))
                             + 'while active set has size ' + str(active_set.size))
    # when using separate states, only keep the nonempty ones (the others don't matter)
    if separate is None:
        active_nonseparate = active_set.copy()
        nmeta = nstates
    else:
        if np.max(separate) >= nfull:
            raise ValueError('Separate set has indexes that do not exist in full state space: '
                             + str(np.max(separate)))
        active_nonseparate = np.array(list(set(active_set) - set(separate)))
        nmeta = nstates - 1
    # check if we can proceed
    if active_nonseparate.size < nmeta:
        raise NotImplementedError('Trying to initialize ' + str(nmeta) + '-state HMM from smaller '
                                  + str(active_nonseparate.size) + '-state MSM.')

    # MICROSTATE TRANSITION MATRIX (MSM).
    C_active = C_full[np.ix_(active_set, active_set)]
    if P is None:  # This matrix may be disconnected and have transient states
        P_active = _tmatrix_disconnected.estimate_P(C_active, reversible=reversible, maxiter=10000)  # short iteration
    else:
        P_active = P

    # MICROSTATE EQUILIBRIUM DISTRIBUTION
    pi_active = _tmatrix_disconnected.stationary_distribution(P_active, C=C_active)
    pi_full = np.zeros(nfull)
    pi_full[active_set] = pi_active

    # NONSEPARATE TRANSITION MATRIX FOR PCCA+
    C_active_nonseparate = C_full[np.ix_(active_nonseparate, active_nonseparate)]
    if reversible and separate is None:  # in this case we already have a reversible estimate with the right size
        P_active_nonseparate = P_active
    else:  # not yet reversible. re-estimate
        P_active_nonseparate = _tmatrix_disconnected.estimate_P(C_active_nonseparate, reversible=True)

    # COARSE-GRAINING WITH PCCA+
    if active_nonseparate.size > nmeta:
        from msmtools.analysis.dense.pcca import PCCA
        pcca_obj = PCCA(P_active_nonseparate, nmeta)
        M_active_nonseparate = pcca_obj.memberships  # memberships
        B_active_nonseparate = pcca_obj.output_probabilities  # output probabilities
    else:  # equal size
        M_active_nonseparate = np.eye(nmeta)
        B_active_nonseparate = np.eye(nmeta)

    # ADD SEPARATE STATE IF NEEDED
    if separate is None:
        M_active = M_active_nonseparate
    else:
        M_full = np.zeros((nfull, nstates))
        M_full[active_nonseparate, :nmeta] = M_active_nonseparate
        M_full[separate, -1] = 1
        M_active = M_full[active_set]

    # COARSE-GRAINED TRANSITION MATRIX
    P_hmm = coarse_grain_transition_matrix(P_active, M_active)
    if reversible:
        P_hmm = _tmatrix_disconnected.enforce_reversible_on_closed(P_hmm)
    C_hmm = M_active.T.dot(C_active).dot(M_active)
    pi_hmm = _tmatrix_disconnected.stationary_distribution(P_hmm, C=C_hmm)  # need C_hmm in case if A is disconnected

    # COARSE-GRAINED OUTPUT DISTRIBUTION
    B_hmm = np.zeros((nstates, nfull))
    B_hmm[:nmeta, active_nonseparate] = B_active_nonseparate
    if separate is not None:  # add separate states
        B_hmm[-1, separate] = pi_full[separate]

    # REGULARIZE SOLUTION
    pi_hmm, P_hmm = regularize_hidden(pi_hmm, P_hmm, reversible=reversible, stationary=stationary, C=C_hmm, eps=eps_A)
    B_hmm = regularize_pobs(B_hmm, nonempty=nonempty, separate=separate, eps=eps_B)

    # print 'cg pi: ', pi_hmm
    # print 'cg A:\n ', P_hmm
    # print 'cg B:\n ', B_hmm

    logger().info('Initial model: ')
    logger().info('initial distribution = \n'+str(pi_hmm))
    logger().info('transition matrix = \n'+str(P_hmm))
    logger().info('output matrix = \n'+str(B_hmm.T))

    return pi_hmm, P_hmm, B_hmm
예제 #16
0
파일: gaussian.py 프로젝트: yarden/bhmm
def initial_model_gaussian1d(observations, nstates, reversible=True):
    """Generate an initial model with 1D-Gaussian output densities

    Parameters
    ----------
    observations : list of ndarray((T_i), dtype=float)
        list of arrays of length T_i with observation data
    nstates : int
        The number of states.

    Examples
    --------

    Generate initial model for a gaussian output model.

    >>> from bhmm import testsystems
    >>> [model, observations, states] = testsystems.generate_synthetic_observations(output_model_type='gaussian')
    >>> initial_model = initial_model_gaussian1d(observations, model.nstates)

    """
    ntrajectories = len(observations)

    # Concatenate all observations.
    collected_observations = np.array([], dtype=config.dtype)
    for o_t in observations:
        collected_observations = np.append(collected_observations, o_t)

    # Fit a Gaussian mixture model to obtain emission distributions and state stationary probabilities.
    from bhmm._external.sklearn import mixture
    gmm = mixture.GMM(n_components=nstates)
    gmm.fit(collected_observations[:,None])
    from bhmm import GaussianOutputModel
    output_model = GaussianOutputModel(nstates, means=gmm.means_[:,0], sigmas=np.sqrt(gmm.covars_[:,0]))

    logger().info("Gaussian output model:\n"+str(output_model))

    # Extract stationary distributions.
    Pi = np.zeros([nstates], np.float64)
    Pi[:] = gmm.weights_[:]

    logger().info("GMM weights: %s" % str(gmm.weights_))

    # Compute fractional state memberships.
    Nij = np.zeros([nstates, nstates], np.float64)
    for o_t in observations:
        # length of trajectory
        T = o_t.shape[0]
        # output probability
        pobs = output_model.p_obs(o_t)
        # normalize
        pobs /= pobs.sum(axis=1)[:,None]
        # Accumulate fractional transition counts from this trajectory.
        for t in range(T-1):
            Nij[:,:] = Nij[:,:] + np.outer(pobs[t,:], pobs[t+1,:])

        logger().info("Nij\n"+str(Nij))

    # Compute transition matrix maximum likelihood estimate.
    import msmtools.estimation as msmest
    Tij = msmest.transition_matrix(Nij, reversible=reversible)

    # Update model.
    model = HMM(Tij, output_model, reversible=reversible)

    return model
예제 #17
0
    def fit(self):
        """
        Maximum-likelihood estimation of the HMM using the Baum-Welch algorithm

        Returns
        -------
        model : HMM
            The maximum likelihood HMM model.

        """
        logger().info(
            "================================================================="
        )
        logger().info("Running Baum-Welch:")
        logger().info("  input observations: " + str(self.nobservations) +
                      " of lengths " + str(self.observation_lengths))
        logger().info("  initial HMM guess:" + str(self._hmm))

        initial_time = time.time()

        it = 0
        self._likelihoods = np.zeros(self.maxit)
        loglik = 0.0
        # flag if connectivity has changed (e.g. state lost) - in that case the likelihood
        # is discontinuous and can't be used as a convergence criterion in that iteration.
        tmatrix_nonzeros = self.hmm.transition_matrix.nonzero()
        converged = False

        while not converged and it < self.maxit:
            # self._fbtimings = np.zeros(5)
            t1 = time.time()
            loglik = 0.0
            for k in range(self._nobs):
                loglik += self._forward_backward(k)
            t2 = time.time()

            # convergence check
            if it > 0:
                dL = loglik - self._likelihoods[it - 1]
                # print 'dL ', dL, 'iter_P ', maxiter_P
                if dL < self._accuracy:
                    # print "CONVERGED! Likelihood change = ",(loglik - self.likelihoods[it-1])
                    converged = True

            # update model
            self._update_model(self._gammas, self._Cs, maxiter=self._maxit_P)
            t3 = time.time()

            # connectivity change check
            tmatrix_nonzeros_new = self.hmm.transition_matrix.nonzero()
            if not np.array_equal(tmatrix_nonzeros, tmatrix_nonzeros_new):
                converged = False  # unset converged
                tmatrix_nonzeros = tmatrix_nonzeros_new

            # print 't_fb: ', str(1000.0*(t2-t1)), 't_up: ', str(1000.0*(t3-t2)), 'L = ', loglik, 'dL = ', (loglik - self._likelihoods[it-1])
            # print '  fb timings (ms): pobs', (1000.0*self._fbtimings).astype(int)

            logger().info(str(it) + " ll = " + str(loglik))
            # print self.model.output_model
            # print "---------------------"

            # end of iteration
            self._likelihoods[it] = loglik
            it += 1

        # final update with high precision
        # self._update_model(self._gammas, self._Cs, maxiter=10000000)

        # truncate likelihood history
        self._likelihoods = self._likelihoods[:it]
        # set final likelihood
        self._hmm.likelihood = loglik
        # set final count matrix
        self.count_matrix = self._transition_counts(self._Cs)
        self.initial_count = self._init_counts(self._gammas)

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("maximum likelihood HMM:" + str(self._hmm))
        logger().info("Elapsed time for Baum-Welch solution: %.3f s" %
                      elapsed_time)
        logger().info("Computing Viterbi path:")

        initial_time = time.time()

        # Compute hidden state trajectories using the Viterbi algorithm.
        self._hmm.hidden_state_trajectories = self.compute_viterbi_paths()

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("Elapsed time for Viterbi path computation: %.3f s" %
                      elapsed_time)
        logger().info(
            "================================================================="
        )

        return self._hmm
예제 #18
0
    def __init__(self,
                 observations,
                 nstates,
                 initial_model=None,
                 type='gaussian',
                 reversible=True,
                 stationary=True,
                 p=None,
                 accuracy=1e-3,
                 maxit=1000):
        """Initialize a Bayesian hidden Markov model sampler.

        Parameters
        ----------
        observations : list of numpy arrays representing temporal data
            `observations[i]` is a 1d numpy array corresponding to the observed trajectory index `i`
        nstates : int
            The number of states in the model.
        initial_model : HMM, optional, default=None
            If specified, the given initial model will be used to initialize the BHMM.
            Otherwise, a heuristic scheme is used to generate an initial guess.
        type : str, optional, default=None
            Output model type from [None, 'gaussian', 'discrete'].
        reversible : bool, optional, default=True
            If True, a prior that enforces reversible transition matrices (detailed balance) is used;
            otherwise, a standard  non-reversible prior is used.
        stationary : bool, optional, default=True
            If True, the initial distribution of hidden states is self-consistently computed as the stationary
            distribution of the transition matrix. If False, it will be estimated from the starting states.
        p : ndarray (nstates), optional, default=None
            Initial or fixed stationary distribution. If given and stationary=True, transition matrices will be
            estimated with the constraint that they have p as their stationary distribution. If given and
            stationary=False, p is the fixed initial distribution of hidden states.
        accuracy : float
            convergence threshold for EM iteration. When two the likelihood does not increase by more than accuracy, the
            iteration is stopped successfully.
        maxit : int
            stopping criterion for EM iteration. When so many iterations are performanced without reaching the requested
            accuracy, the iteration is stopped without convergence (a warning is given)

        """
        # Store a copy of the observations.
        self._observations = copy.deepcopy(observations)
        self._nobs = len(observations)
        self._Ts = [len(o) for o in observations]
        self._maxT = np.max(self._Ts)

        # Store the number of states.
        self._nstates = nstates

        if initial_model is not None:
            # Use user-specified initial model, if provided.
            self._hmm = copy.deepcopy(initial_model)
            # consistency checks
            if self._hmm.is_stationary != stationary:
                logger().warn('Requested stationary=' + str(stationary) +
                              ' but initial model is stationary=' +
                              str(self._hmm.is_stationary) +
                              '. Using stationary=' +
                              str(self._hmm.is_stationary))
            if self._hmm.is_reversible != reversible:
                logger().warn('Requested reversible=' + str(reversible) +
                              ' but initial model is reversible=' +
                              str(self._hmm.is_reversible) +
                              '. Using reversible=' +
                              str(self._hmm.is_reversible))
            # setting parameters
            self._reversible = self._hmm.is_reversible
            self._stationary = self._hmm.is_stationary
        else:
            # Generate our own initial model.
            self._hmm = bhmm.init_hmm(observations, nstates, type=type)
            # setting parameters
            self._reversible = reversible
            self._stationary = stationary

        # stationary and initial distribution
        self._fixed_stationary_distribution = None
        self._fixed_initial_distribution = None
        if p is not None:
            if stationary:
                self._fixed_stationary_distribution = np.array(p)
            else:
                self._fixed_initial_distribution = np.array(p)

        # pre-construct hidden variables
        self._alpha = np.zeros((self._maxT, self._nstates),
                               config.dtype,
                               order='C')
        self._beta = np.zeros((self._maxT, self._nstates),
                              config.dtype,
                              order='C')
        self._pobs = np.zeros((self._maxT, self._nstates),
                              config.dtype,
                              order='C')
        self._gammas = [
            np.zeros((len(self._observations[i]), self._nstates),
                     config.dtype,
                     order='C') for i in range(self._nobs)
        ]
        self._Cs = [
            np.zeros((self._nstates, self._nstates), config.dtype, order='C')
            for _ in range(self._nobs)
        ]

        # convergence options
        self._accuracy = accuracy
        self._maxit = maxit
        self._likelihoods = None

        # Kernel for computing things
        hidden.set_implementation(config.kernel)
        self._hmm.output_model.set_implementation(config.kernel)
예제 #19
0
    def fit(self):
        """
        Maximum-likelihood estimation of the HMM using the Baum-Welch algorithm

        Returns
        -------
        model : HMM
            The maximum likelihood HMM model.

        """
        logger().info("=================================================================")
        logger().info("Running Baum-Welch:")
        logger().info("  input observations: "+str(self.nobservations)+" of lengths "+str(self.observation_lengths))
        logger().info("  initial HMM guess:"+str(self._hmm))

        initial_time = time.time()

        it = 0
        self._likelihoods = np.zeros(self.maxit)
        loglik = 0.0
        # flag if connectivity has changed (e.g. state lost) - in that case the likelihood
        # is discontinuous and can't be used as a convergence criterion in that iteration.
        tmatrix_nonzeros = self.hmm.transition_matrix.nonzero()
        converged = False

        while not converged and it < self.maxit:
            # self._fbtimings = np.zeros(5)
            t1 = time.time()
            loglik = 0.0
            for k in range(self._nobs):
                loglik += self._forward_backward(k)
            t2 = time.time()

            # convergence check
            if it > 0:
                dL = loglik - self._likelihoods[it-1]
                # print 'dL ', dL, 'iter_P ', maxiter_P
                if dL < self._accuracy:
                    # print "CONVERGED! Likelihood change = ",(loglik - self.likelihoods[it-1])
                    converged = True

            # update model
            self._update_model(self._gammas, self._Cs, maxiter=self._maxit_P)
            t3 = time.time()

            # connectivity change check
            tmatrix_nonzeros_new = self.hmm.transition_matrix.nonzero()
            if not np.array_equal(tmatrix_nonzeros, tmatrix_nonzeros_new):
                converged = False  # unset converged
                tmatrix_nonzeros = tmatrix_nonzeros_new

            # print 't_fb: ', str(1000.0*(t2-t1)), 't_up: ', str(1000.0*(t3-t2)), 'L = ', loglik, 'dL = ', (loglik - self._likelihoods[it-1])
            # print '  fb timings (ms): pobs', (1000.0*self._fbtimings).astype(int)

            logger().info(str(it) + " ll = " + str(loglik))
            # print self.model.output_model
            # print "---------------------"

            # end of iteration
            self._likelihoods[it] = loglik
            it += 1

        # final update with high precision
        # self._update_model(self._gammas, self._Cs, maxiter=10000000)

        # truncate likelihood history
        self._likelihoods = self._likelihoods[:it]
        # set final likelihood
        self._hmm.likelihood = loglik
        # set final count matrix
        self.count_matrix = self._transition_counts(self._Cs)
        self.initial_count = self._init_counts(self._gammas)

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("maximum likelihood HMM:"+str(self._hmm))
        logger().info("Elapsed time for Baum-Welch solution: %.3f s" % elapsed_time)
        logger().info("Computing Viterbi path:")

        initial_time = time.time()

        # Compute hidden state trajectories using the Viterbi algorithm.
        self._hmm.hidden_state_trajectories = self.compute_viterbi_paths()

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("Elapsed time for Viterbi path computation: %.3f s" % elapsed_time)
        logger().info("=================================================================")

        return self._hmm
예제 #20
0
def initial_model_discrete(observations, nstates, lag=1, reversible=True):
    """Generate an initial model with discrete output densities

    Parameters
    ----------
    observations : list of ndarray((T_i), dtype=int)
        list of arrays of length T_i with observation data
    nstates : int
        The number of states.
    lag : int, optional, default=1
        The lag time to use for initializing the model.

    TODO
    ----
    * Why do we have a `lag` option?  Isn't the HMM model, by definition, lag=1 everywhere?  Why would this be useful instead of just having the user subsample the data?

    Examples
    --------

    Generate initial model for a discrete output model.

    >>> from bhmm import testsystems
    >>> [model, observations, states] = testsystems.generate_synthetic_observations(output_model_type='discrete')
    >>> initial_model = initial_model_discrete(observations, model.nstates)

    """
    # check input
    if not reversible:
        warnings.warn("nonreversible initialization of discrete HMM currently not supported. Using a reversible matrix for initialization.")
        reversible = True

    # import emma inside function in order to avoid dependency loops
    from pyemma import msm

    # estimate Markov model
    MSM = msm.estimate_markov_model(observations, lag, reversible=True, connectivity='largest')

    # PCCA
    pcca = MSM.pcca(nstates)

    # HMM output matrix
    B_conn = MSM.metastable_distributions

    #print 'B_conn = \n',B_conn
    # full state space output matrix
    nstates_full = MSM.count_matrix_full.shape[0]
    eps = 0.01 * (1.0/nstates_full) # default output probability, in order to avoid zero columns
    B = eps * np.ones((nstates,nstates_full), dtype=np.float64)
    # expand B_conn to full state space
    B[:,MSM.active_set] = B_conn[:,:]
    # renormalize B to make it row-stochastic
    B /= B.sum(axis=1)[:,None]

    # coarse-grained transition matrix
    M = pcca.memberships
    W = np.linalg.inv(np.dot(M.T, M))
    A = np.dot(np.dot(M.T, MSM.transition_matrix), M)
    P_coarse = np.dot(W, A)

    # symmetrize and renormalize to eliminate numerical errors
    X = np.dot(np.diag(pcca.coarse_grained_stationary_probability), P_coarse)
    X = 0.5 * (X + X.T)
    # if there are values < 0, set to eps
    X = np.maximum(X, eps)
    # turn into coarse-grained transition matrix
    A = X / X.sum(axis=1)[:, None]

    logger().info('Initial model: ')
    logger().info('transition matrix = \n'+str(A))
    logger().info('output matrix = \n'+str(B.T))

    # initialize HMM
    # --------------
    output_model = DiscreteOutputModel(B)
    model = HMM(A, output_model)
    return model
예제 #21
0
    def fit(self):
        """
        Maximum-likelihood estimation of the HMM using the Baum-Welch algorithm

        Returns
        -------
        model : HMM
            The maximum likelihood HMM model.

        """
        logger().info("=================================================================")
        logger().info("Running Baum-Welch:")
        logger().info("  input observations: "+str(self.nobservations)+" of lengths "+str(self.observation_lengths))
        logger().info("  initial HMM guess:"+str(self._hmm))

        initial_time = time.time()

        it = 0
        self._likelihoods = np.zeros((self.maxit))
        loglik = 0.0
        converged = False

        while (not converged and it < self.maxit):
            loglik = 0.0
            for k in range(self._nobs):
                loglik += self._forward_backward(k)

            self._update_model(self._gammas, self._Cs)
            logger().info(str(it)+" ll = "+str(loglik))
            #print self.model.output_model
            #print "---------------------"

            self._likelihoods[it] = loglik

            if it > 0:
                if loglik - self._likelihoods[it-1] < self._accuracy:
                    #print "CONVERGED! Likelihood change = ",(loglik - self.likelihoods[it-1])
                    converged = True

            it += 1

        # truncate likelihood history
        self._likelihoods = self._likelihoods[:it]
        # set final likelihood
        self._hmm.likelihood = loglik

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("maximum likelihood HMM:"+str(self._hmm))
        logger().info("Elapsed time for Baum-Welch solution: %.3f s" % elapsed_time)
        logger().info("Computing Viterbi path:")

        initial_time = time.time()

        # Compute hidden state trajectories using the Viterbi algorithm.
        self._hmm.hidden_state_trajectories = self.compute_viterbi_paths()

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("Elapsed time for Viterbi path computation: %.3f s" % elapsed_time)
        logger().info("=================================================================")

        return self._hmm
예제 #22
0
    def fit(self):
        """
        Maximum-likelihood estimation of the HMM using the Baum-Welch algorithm

        Returns
        -------
        model : HMM
            The maximum likelihood HMM model.

        """
        logger().info(
            "================================================================="
        )
        logger().info("Running Baum-Welch:")
        logger().info("  input observations: " + str(self.nobservations) +
                      " of lengths " + str(self.observation_lengths))
        logger().info("  initial HMM guess:" + str(self._hmm))

        initial_time = time.time()

        it = 0
        self._likelihoods = np.zeros((self.maxit))
        loglik = 0.0
        converged = False

        while (not converged and it < self.maxit):
            loglik = 0.0
            for k in range(self._nobs):
                loglik += self._forward_backward(k)

            self._update_model(self._gammas, self._Cs)
            logger().info(str(it) + " ll = " + str(loglik))
            #print self.model.output_model
            #print "---------------------"

            self._likelihoods[it] = loglik

            if it > 0:
                if loglik - self._likelihoods[it - 1] < self._accuracy:
                    #print "CONVERGED! Likelihood change = ",(loglik - self.likelihoods[it-1])
                    converged = True

            it += 1

        # truncate likelihood history
        self._likelihoods = self._likelihoods[:it]
        # set final likelihood
        self._hmm.likelihood = loglik

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("maximum likelihood HMM:" + str(self._hmm))
        logger().info("Elapsed time for Baum-Welch solution: %.3f s" %
                      elapsed_time)
        logger().info("Computing Viterbi path:")

        initial_time = time.time()

        # Compute hidden state trajectories using the Viterbi algorithm.
        self._hmm.hidden_state_trajectories = self.compute_viterbi_paths()

        final_time = time.time()
        elapsed_time = final_time - initial_time

        logger().info("Elapsed time for Viterbi path computation: %.3f s" %
                      elapsed_time)
        logger().info(
            "================================================================="
        )

        return self._hmm
예제 #23
0
    def __init__(self, observations, nstates, initial_model=None, type='gaussian',
                 reversible=True, stationary=True, p=None, accuracy=1e-3, maxit=1000):
        """Initialize a Bayesian hidden Markov model sampler.

        Parameters
        ----------
        observations : list of numpy arrays representing temporal data
            `observations[i]` is a 1d numpy array corresponding to the observed trajectory index `i`
        nstates : int
            The number of states in the model.
        initial_model : HMM, optional, default=None
            If specified, the given initial model will be used to initialize the BHMM.
            Otherwise, a heuristic scheme is used to generate an initial guess.
        type : str, optional, default=None
            Output model type from [None, 'gaussian', 'discrete'].
        reversible : bool, optional, default=True
            If True, a prior that enforces reversible transition matrices (detailed balance) is used;
            otherwise, a standard  non-reversible prior is used.
        stationary : bool, optional, default=True
            If True, the initial distribution of hidden states is self-consistently computed as the stationary
            distribution of the transition matrix. If False, it will be estimated from the starting states.
        p : ndarray (nstates), optional, default=None
            Initial or fixed stationary distribution. If given and stationary=True, transition matrices will be
            estimated with the constraint that they have p as their stationary distribution. If given and
            stationary=False, p is the fixed initial distribution of hidden states.
        accuracy : float
            convergence threshold for EM iteration. When two the likelihood does not increase by more than accuracy, the
            iteration is stopped successfully.
        maxit : int
            stopping criterion for EM iteration. When so many iterations are performanced without reaching the requested
            accuracy, the iteration is stopped without convergence (a warning is given)

        """
        # Store a copy of the observations.
        self._observations = copy.deepcopy(observations)
        self._nobs = len(observations)
        self._Ts = [len(o) for o in observations]
        self._maxT = np.max(self._Ts)

        # Store the number of states.
        self._nstates = nstates


        if initial_model is not None:
            # Use user-specified initial model, if provided.
            self._hmm = copy.deepcopy(initial_model)
            # consistency checks
            if self._hmm.is_stationary != stationary:
                logger().warn('Requested stationary='+str(stationary)+' but initial model is stationary='+
                              str(self._hmm.is_stationary)+'. Using stationary='+str(self._hmm.is_stationary))
            if self._hmm.is_reversible != reversible:
                logger().warn('Requested reversible='+str(reversible)+' but initial model is reversible='+
                              str(self._hmm.is_reversible)+'. Using reversible='+str(self._hmm.is_reversible))
            # setting parameters
            self._reversible = self._hmm.is_reversible
            self._stationary = self._hmm.is_stationary
        else:
            # Generate our own initial model.
            self._hmm = bhmm.init_hmm(observations, nstates, type=type)
            # setting parameters
            self._reversible = reversible
            self._stationary = stationary

        # stationary and initial distribution
        self._fixed_stationary_distribution = None
        self._fixed_initial_distribution = None
        if p is not None:
            if stationary:
                self._fixed_stationary_distribution = np.array(p)
            else:
                self._fixed_initial_distribution = np.array(p)

        # pre-construct hidden variables
        self._alpha = np.zeros((self._maxT,self._nstates), config.dtype, order='C')
        self._beta = np.zeros((self._maxT,self._nstates), config.dtype, order='C')
        self._pobs = np.zeros((self._maxT,self._nstates), config.dtype, order='C')
        self._gammas = [np.zeros((len(self._observations[i]),self._nstates), config.dtype, order='C') for i in range(self._nobs)]
        self._Cs = [np.zeros((self._nstates,self._nstates), config.dtype, order='C') for i in range(self._nobs)]

        # convergence options
        self._accuracy = accuracy
        self._maxit = maxit
        self._likelihoods = None

        # Kernel for computing things
        hidden.set_implementation(config.kernel)
        self._hmm.output_model.set_implementation(config.kernel)
예제 #24
0
파일: discrete.py 프로젝트: yarden/bhmm
def initial_model_discrete(observations, nstates, lag=1, reversible=True):
    """Generate an initial model with discrete output densities

    Parameters
    ----------
    observations : list of ndarray((T_i), dtype=int)
        list of arrays of length T_i with observation data
    nstates : int
        The number of states.
    lag : int, optional, default=1
        The lag time to use for initializing the model.

    TODO
    ----
    * Why do we have a `lag` option?  Isn't the HMM model, by definition, lag=1 everywhere?  Why would this be useful instead of just having the user subsample the data?

    Examples
    --------

    Generate initial model for a discrete output model.

    >>> from bhmm import testsystems
    >>> [model, observations, states] = testsystems.generate_synthetic_observations(output_model_type='discrete')
    >>> initial_model = initial_model_discrete(observations, model.nstates)

    """
    # check input
    if not reversible:
        warnings.warn(
            "nonreversible initialization of discrete HMM currently not supported. Using a reversible matrix for initialization."
        )
        reversible = True

    # estimate Markov model
    C_full = msmtools.estimation.count_matrix(observations, lag)
    lcc = msmtools.estimation.largest_connected_set(C_full)
    Clcc = msmtools.estimation.largest_connected_submatrix(C_full, lcc=lcc)
    T = msmtools.estimation.transition_matrix(Clcc, reversible=True).toarray()

    # pcca
    pcca = msmtools.analysis.dense.pcca.PCCA(T, nstates)

    # default output probability, in order to avoid zero columns
    nstates_full = C_full.shape[0]
    eps = 0.01 * (1.0 / nstates_full)

    # Use PCCA distributions, but avoid 100% assignment to any state (prevents convergence)
    B_conn = np.maximum(pcca.output_probabilities, eps)

    # full state space output matrix
    B = eps * np.ones((nstates, nstates_full), dtype=np.float64)
    # expand B_conn to full state space
    B[:, lcc] = B_conn[:, :]
    # renormalize B to make it row-stochastic
    B /= B.sum(axis=1)[:, None]

    # coarse-grained transition matrix
    M = pcca.memberships
    W = np.linalg.inv(np.dot(M.T, M))
    A = np.dot(np.dot(M.T, T), M)
    P_coarse = np.dot(W, A)

    # symmetrize and renormalize to eliminate numerical errors
    X = np.dot(np.diag(pcca.coarse_grained_stationary_probability), P_coarse)
    X = 0.5 * (X + X.T)
    # if there are values < 0, set to eps
    X = np.maximum(X, eps)
    # turn into coarse-grained transition matrix
    A = X / X.sum(axis=1)[:, None]

    logger().info('Initial model: ')
    logger().info('transition matrix = \n' + str(A))
    logger().info('output matrix = \n' + str(B.T))

    # initialize HMM
    # --------------
    output_model = DiscreteOutputModel(B)
    model = HMM(A, output_model)
    return model
예제 #25
0
def initial_model_gaussian1d(observations, nstates, reversible=True):
    """Generate an initial model with 1D-Gaussian output densities

    Parameters
    ----------
    observations : list of ndarray((T_i), dtype=float)
        list of arrays of length T_i with observation data
    nstates : int
        The number of states.

    Examples
    --------

    Generate initial model for a gaussian output model.

    >>> from bhmm import testsystems
    >>> [model, observations, states] = testsystems.generate_synthetic_observations(output_model_type='gaussian')
    >>> initial_model = initial_model_gaussian1d(observations, model.nstates)

    """
    ntrajectories = len(observations)

    # Concatenate all observations.
    collected_observations = np.array([], dtype=config.dtype)
    for o_t in observations:
        collected_observations = np.append(collected_observations, o_t)

    # Fit a Gaussian mixture model to obtain emission distributions and state stationary probabilities.
    from bhmm._external.sklearn import mixture
    gmm = mixture.GMM(n_components=nstates)
    gmm.fit(collected_observations[:, None])
    from bhmm import GaussianOutputModel
    output_model = GaussianOutputModel(nstates,
                                       means=gmm.means_[:, 0],
                                       sigmas=np.sqrt(gmm.covars_[:, 0]))

    logger().info("Gaussian output model:\n" + str(output_model))

    # Extract stationary distributions.
    Pi = np.zeros([nstates], np.float64)
    Pi[:] = gmm.weights_[:]

    logger().info("GMM weights: %s" % str(gmm.weights_))

    # Compute fractional state memberships.
    Nij = np.zeros([nstates, nstates], np.float64)
    for o_t in observations:
        # length of trajectory
        T = o_t.shape[0]
        # output probability
        pobs = output_model.p_obs(o_t)
        # normalize
        pobs /= pobs.sum(axis=1)[:, None]
        # Accumulate fractional transition counts from this trajectory.
        for t in range(T - 1):
            Nij[:, :] = Nij[:, :] + np.outer(pobs[t, :], pobs[t + 1, :])

        logger().info("Nij\n" + str(Nij))

    # Compute transition matrix maximum likelihood estimate.
    import msmtools.estimation as msmest
    Tij = msmest.transition_matrix(Nij, reversible=reversible)

    # Update model.
    model = HMM(Tij, output_model, reversible=reversible)

    return model
예제 #26
0
파일: discrete.py 프로젝트: ongbe/bhmm
def init_discrete_hmm_spectral(C_full,
                               nstates,
                               reversible=True,
                               stationary=True,
                               active_set=None,
                               P=None,
                               eps_A=None,
                               eps_B=None,
                               separate=None):
    """Initializes discrete HMM using spectral clustering of observation counts

    Initializes HMM as described in [1]_. First estimates a Markov state model
    on the given observations, then uses PCCA+ to coarse-grain the transition
    matrix [2]_ which initializes the HMM transition matrix. The HMM output
    probabilities are given by Bayesian inversion from the PCCA+ memberships [1]_.

    The regularization parameters eps_A and eps_B are used
    to guarantee that the hidden transition matrix and output probability matrix
    have no zeros. HMM estimation algorithms such as the EM algorithm and the
    Bayesian sampling algorithm cannot recover from zero entries, i.e. once they
    are zero, they will stay zero.

    Parameters
    ----------
    C_full : ndarray(N, N)
        Transition count matrix on the full observable state space
    nstates : int
        The number of hidden states.
    reversible : bool
        Estimate reversible HMM transition matrix.
    stationary : bool
        p0 is the stationary distribution of P. In this case, will not
    active_set : ndarray(n, dtype=int) or None
        Index area. Will estimate kinetics only on the given subset of C
    P : ndarray(n, n)
        Transition matrix estimated from C (with option reversible). Use this
        option if P has already been estimated to avoid estimating it twice.
    eps_A : float or None
        Minimum transition probability. Default: 0.01 / nstates
    eps_B : float or None
        Minimum output probability. Default: 0.01 / nfull
    separate : None or iterable of int
        Force the given set of observed states to stay in a separate hidden state.
        The remaining nstates-1 states will be assigned by a metastable decomposition.

    Returns
    -------
    p0 : ndarray(n)
        Hidden state initial distribution
    A : ndarray(n, n)
        Hidden state transition matrix
    B : ndarray(n, N)
        Hidden-to-observable state output probabilities

    Raises
    ------
    ValueError
        If the given active set is illegal.
    NotImplementedError
        If the number of hidden states exceeds the number of observed states.

    Examples
    --------
    Generate initial model for a discrete output model.

    >>> import numpy as np
    >>> C = np.array([[0.5, 0.5, 0.0], [0.4, 0.5, 0.1], [0.0, 0.1, 0.9]])
    >>> initial_model = init_discrete_hmm_spectral(C, 2)

    References
    ----------
    .. [1] F. Noe, H. Wu, J.-H. Prinz and N. Plattner: Projected and hidden
        Markov models for calculating kinetics and  metastable states of
        complex molecules. J. Chem. Phys. 139, 184114 (2013)
    .. [2] S. Kube and M. Weber: A coarse graining method for the identification
        of transition rates between molecular conformations.
        J. Chem. Phys. 126, 024103 (2007)

    """
    # MICROSTATE COUNT MATRIX
    nfull = C_full.shape[0]

    # INPUTS
    if eps_A is None:  # default transition probability, in order to avoid zero columns
        eps_A = 0.01 / nstates
    if eps_B is None:  # default output probability, in order to avoid zero columns
        eps_B = 0.01 / nfull
    # Manage sets
    symsum = C_full.sum(axis=0) + C_full.sum(axis=1)
    nonempty = np.where(symsum > 0)[0]
    if active_set is None:
        active_set = nonempty
    else:
        if np.any(symsum[active_set] == 0):
            raise ValueError('Given active set has empty states'
                             )  # don't tolerate empty states
    if P is not None:
        if np.shape(P)[0] != active_set.size:  # needs to fit to active
            raise ValueError('Given initial transition matrix P has shape ' +
                             str(np.shape(P)) + 'while active set has size ' +
                             str(active_set.size))
    # when using separate states, only keep the nonempty ones (the others don't matter)
    if separate is None:
        active_nonseparate = active_set.copy()
        nmeta = nstates
    else:
        if np.max(separate) >= nfull:
            raise ValueError(
                'Separate set has indexes that do not exist in full state space: '
                + str(np.max(separate)))
        active_nonseparate = np.array(list(set(active_set) - set(separate)))
        nmeta = nstates - 1
    # check if we can proceed
    if active_nonseparate.size < nmeta:
        raise NotImplementedError('Trying to initialize ' + str(nmeta) +
                                  '-state HMM from smaller ' +
                                  str(active_nonseparate.size) + '-state MSM.')

    # MICROSTATE TRANSITION MATRIX (MSM).
    C_active = C_full[np.ix_(active_set, active_set)]
    if P is None:  # This matrix may be disconnected and have transient states
        P_active = _tmatrix_disconnected.estimate_P(
            C_active, reversible=reversible, maxiter=10000)  # short iteration
    else:
        P_active = P

    # MICROSTATE EQUILIBRIUM DISTRIBUTION
    pi_active = _tmatrix_disconnected.stationary_distribution(P_active,
                                                              C=C_active)
    pi_full = np.zeros(nfull)
    pi_full[active_set] = pi_active

    # NONSEPARATE TRANSITION MATRIX FOR PCCA+
    C_active_nonseparate = C_full[np.ix_(active_nonseparate,
                                         active_nonseparate)]
    if reversible and separate is None:  # in this case we already have a reversible estimate with the right size
        P_active_nonseparate = P_active
    else:  # not yet reversible. re-estimate
        P_active_nonseparate = _tmatrix_disconnected.estimate_P(
            C_active_nonseparate, reversible=True)

    # COARSE-GRAINING WITH PCCA+
    if active_nonseparate.size > nmeta:
        from msmtools.analysis.dense.pcca import PCCA
        pcca_obj = PCCA(P_active_nonseparate, nmeta)
        M_active_nonseparate = pcca_obj.memberships  # memberships
        B_active_nonseparate = pcca_obj.output_probabilities  # output probabilities
    else:  # equal size
        M_active_nonseparate = np.eye(nmeta)
        B_active_nonseparate = np.eye(nmeta)

    # ADD SEPARATE STATE IF NEEDED
    if separate is None:
        M_active = M_active_nonseparate
    else:
        M_full = np.zeros((nfull, nstates))
        M_full[active_nonseparate, :nmeta] = M_active_nonseparate
        M_full[separate, -1] = 1
        M_active = M_full[active_set]

    # COARSE-GRAINED TRANSITION MATRIX
    P_hmm = coarse_grain_transition_matrix(P_active, M_active)
    if reversible:
        P_hmm = _tmatrix_disconnected.enforce_reversible_on_closed(P_hmm)
    C_hmm = M_active.T.dot(C_active).dot(M_active)
    pi_hmm = _tmatrix_disconnected.stationary_distribution(
        P_hmm, C=C_hmm)  # need C_hmm in case if A is disconnected

    # COARSE-GRAINED OUTPUT DISTRIBUTION
    B_hmm = np.zeros((nstates, nfull))
    B_hmm[:nmeta, active_nonseparate] = B_active_nonseparate
    if separate is not None:  # add separate states
        B_hmm[-1, separate] = pi_full[separate]

    # REGULARIZE SOLUTION
    pi_hmm, P_hmm = regularize_hidden(pi_hmm,
                                      P_hmm,
                                      reversible=reversible,
                                      stationary=stationary,
                                      C=C_hmm,
                                      eps=eps_A)
    B_hmm = regularize_pobs(B_hmm,
                            nonempty=nonempty,
                            separate=separate,
                            eps=eps_B)

    # print 'cg pi: ', pi_hmm
    # print 'cg A:\n ', P_hmm
    # print 'cg B:\n ', B_hmm

    logger().info('Initial model: ')
    logger().info('initial distribution = \n' + str(pi_hmm))
    logger().info('transition matrix = \n' + str(P_hmm))
    logger().info('output matrix = \n' + str(B_hmm.T))

    return pi_hmm, P_hmm, B_hmm
예제 #27
0
    def sample(self,
               nsamples,
               nburn=0,
               nthin=1,
               save_hidden_state_trajectory=False,
               call_back=None):
        """Sample from the BHMM posterior.

        Parameters
        ----------
        nsamples : int
            The number of samples to generate.
        nburn : int, optional, default=0
            The number of samples to discard to burn-in, following which `nsamples` will be generated.
        nthin : int, optional, default=1
            The number of Gibbs sampling updates used to generate each returned sample.
        save_hidden_state_trajectory : bool, optional, default=False
            If True, the hidden state trajectory for each sample will be saved as well.
        call_back : function, optional, default=None
            a call back function with no arguments, which if given is being called
            after each computed sample. This is useful for implementing progress bars.

        Returns
        -------
        models : list of bhmm.HMM
            The sampled HMM models from the Bayesian posterior.

        Examples
        --------

        >>> from bhmm import testsystems
        >>> [model, observations, states, sampled_model] = testsystems.generate_random_bhmm(ntrajectories=5, length=1000)
        >>> nburn = 5 # run the sampler a bit before recording samples
        >>> nsamples = 10 # generate 10 samples
        >>> nthin = 2 # discard one sample in between each recorded sample
        >>> samples = sampled_model.sample(nsamples, nburn=nburn, nthin=nthin)

        """

        # Run burn-in.
        for iteration in range(nburn):
            logger().info("Burn-in   %8d / %8d" % (iteration, nburn))
            self._update()

        # Collect data.
        models = list()
        for iteration in range(nsamples):
            logger().info("Iteration %8d / %8d" % (iteration, nsamples))
            # Run a number of Gibbs sampling updates to generate each sample.
            for thin in range(nthin):
                self._update()
            # Save a copy of the current model.
            model_copy = copy.deepcopy(self.model)
            # print "Sampled: \n",repr(model_copy)
            if not save_hidden_state_trajectory:
                model_copy.hidden_state_trajectory = None
            models.append(model_copy)
            if call_back is not None:
                call_back()

        # Return the list of models saved.
        return models