Ejemplo n.º 1
0
def transition_model(state):
    # given a hidden state, return the Distribution for the next hidden state
    x, y, action = state
    next_states  = Distribution()

    # we can always stay where we are
    if action == 'stay':
        next_states[(x, y, 'stay')] = .2
    else:
        next_states[(x, y, 'stay')] = .1

    if y > 0: # we can go up
        if action == 'stay':
            next_states[(x, y-1, 'up')] = .2
        if action == 'up':
            next_states[(x, y-1, 'up')] = .9
    if y < GRID_HEIGHT - 1: # we can go down
        if action == 'stay':
            next_states[(x, y+1, 'down')] = .2
        if action == 'down':
            next_states[(x, y+1, 'down')] = .9
    if x > 0: # we can go left
        if action == 'stay':
            next_states[(x-1, y, 'left')] = .2
        if action == 'left':
            next_states[(x-1, y, 'left')] = .9
    if x < GRID_WIDTH - 1: # we can go right
        if action == 'stay':
            next_states[(x+1, y, 'right')] = .2
        if action == 'right':
            next_states[(x+1, y, 'right')] = .9

    next_states.renormalize()
    return next_states
Ejemplo n.º 2
0
def transition_model(state):
    # given a hidden state, return the Distribution for the next hidden state
    x, y, action = state
    next_states = Distribution()

    # we can always stay where we are
    if action == 'stay':
        next_states[(x, y, 'stay')] = .2
    else:
        next_states[(x, y, 'stay')] = .1

    if y > 0:  # we can go up
        if action == 'stay':
            next_states[(x, y - 1, 'up')] = .2
        if action == 'up':
            next_states[(x, y - 1, 'up')] = .9
    if y < GRID_HEIGHT - 1:  # we can go down
        if action == 'stay':
            next_states[(x, y + 1, 'down')] = .2
        if action == 'down':
            next_states[(x, y + 1, 'down')] = .9
    if x > 0:  # we can go left
        if action == 'stay':
            next_states[(x - 1, y, 'left')] = .2
        if action == 'left':
            next_states[(x - 1, y, 'left')] = .9
    if x < GRID_WIDTH - 1:  # we can go right
        if action == 'stay':
            next_states[(x + 1, y, 'right')] = .2
        if action == 'right':
            next_states[(x + 1, y, 'right')] = .9

    next_states.renormalize()
    return next_states
Ejemplo n.º 3
0
def uniform_transition_model(state):
    next_state_distribution = Distribution()
    valid_next_states = get_valid_next_states(state)
    for next_state in valid_next_states:
        next_state_distribution[next_state] += 1
    next_state_distribution.renormalize()

    return next_state_distribution
Ejemplo n.º 4
0
def uniform_transition_model(state):
    next_state_distribution = Distribution()
    valid_next_states = get_valid_next_states(state)
    for next_state in valid_next_states:
        next_state_distribution[next_state] += 1
    next_state_distribution.renormalize()

    return next_state_distribution
Ejemplo n.º 5
0
 def compute_marginal(message, node_potential):
     marginal = Distribution()
     for hidden_state in all_possible_hidden_states:
         if hidden_state in message and \
            hidden_state in node_potential:
             value = message[hidden_state] * node_potential[hidden_state]
             if value > 0:  # only store entries with nonzero prob.
                 marginal[hidden_state] = value
     marginal.renormalize()
     return marginal
Ejemplo n.º 6
0
 def compute_marginal(message, node_potential):
     marginal = Distribution()
     for hidden_state in all_possible_hidden_states:
         if hidden_state in message and \
            hidden_state in node_potential:
             value = message[hidden_state] * node_potential[hidden_state]
             if value > 0: # only store entries with nonzero prob.
                 marginal[hidden_state] = value
     marginal.renormalize()
     return marginal
Ejemplo n.º 7
0
def spread_observation_model(state, radius=1):
    # given a hidden state, return the Distribution for its observation
    x, y, action    = state
    observed_states = Distribution()

    for x_new in range(x - radius, x + radius + 1):
        for y_new in range(y - radius, y + radius + 1):
            if x_new >= 0 and x_new <= GRID_WIDTH - 1 and \
               y_new >= 0 and y_new <= GRID_HEIGHT - 1:
                observed_states[(x_new, y_new)] = 1.

    observed_states.renormalize()
    return observed_states
Ejemplo n.º 8
0
def spread_observation_model(state, radius=1):
    # given a hidden state, return the Distribution for its observation
    x, y, action = state
    observed_states = Distribution()

    for x_new in range(x - radius, x + radius + 1):
        for y_new in range(y - radius, y + radius + 1):
            if x_new >= 0 and x_new <= GRID_WIDTH - 1 and \
               y_new >= 0 and y_new <= GRID_HEIGHT - 1:
                observed_states[(x_new, y_new)] = 1.

    observed_states.renormalize()
    return observed_states
Ejemplo n.º 9
0
def discretized_gaussian_observation_model(state, sigma=1):
    # given a hidden state, return the Distribution for its observation
    x, y, action    = state
    observed_states = Distribution()

    # x_new, y_new = np.meshgrid(range(GRID_WIDTH), range(GRID_HEIGHT))
    # values = np.exp( -( (x_new - x)**2 + (y_new -y)**2 )/(2.*sigma) )

    for x_new in range(GRID_WIDTH):
        for y_new in range(GRID_HEIGHT):
            observed_states[(x_new, y_new)] = \
                np.exp(-( (x_new - x)**2 + (y_new - y)**2 )/(2.*sigma))

    observed_states.renormalize()
    return observed_states
Ejemplo n.º 10
0
def discretized_gaussian_observation_model(state, sigma=1):
    # given a hidden state, return the Distribution for its observation
    x, y, action = state
    observed_states = Distribution()

    # x_new, y_new = np.meshgrid(range(GRID_WIDTH), range(GRID_HEIGHT))
    # values = np.exp( -( (x_new - x)**2 + (y_new -y)**2 )/(2.*sigma) )

    for x_new in range(GRID_WIDTH):
        for y_new in range(GRID_HEIGHT):
            observed_states[(x_new, y_new)] = \
                np.exp(-( (x_new - x)**2 + (y_new - y)**2 )/(2.*sigma))

    observed_states.renormalize()
    return observed_states
Ejemplo n.º 11
0
def forward_backward(all_possible_hidden_states, all_possible_observed_states,
                     prior_distribution, transition_model, observation_model,
                     observations):
    """
    Inputs
    ------
    all_possible_hidden_states: a list of possible hidden states

    all_possible_observed_states: a list of possible observed states

    prior_distribution: a distribution over states

    transition_model: a function that takes a hidden state and returns a
        Distribution for the next state

    observation_model: a function that takes a hidden state and returns a
        Distribution for the observation from that hidden state

    observations: a list of observations, one per hidden state
        (a missing observation is encoded as None)

    Output
    ------
    A list of marginal distributions at each time step; each distribution
    should be encoded as a Distribution (see the Distribution class in
    robot.py and see how it is used in both robot.py and the function
    generate_data() above, and the i-th Distribution should correspond to time
    step i
    """

    num_time_steps = len(observations)

    #-------------------------------------------------------------------------
    # Fold observations into singleton potentials
    #
    phis = []  # phis[n] is the singleton potential for node n
    for n in range(num_time_steps):
        potential = Distribution()
        observed_state = observations[n]
        if n == 0:
            for hidden_state in prior_distribution:
                value = prior_distribution[hidden_state]
                if observed_state is not None:
                    value *= observation_model(hidden_state)[observed_state]
                if value > 0:  # only store entries with nonzero prob.
                    potential[hidden_state] = value
        else:
            for hidden_state in all_possible_hidden_states:
                if observed_state is None:
                    # singleton potential should be identically 1
                    potential[hidden_state] = 1.
                else:
                    value = observation_model(hidden_state)[observed_state]
                    if value > 0:  # only store entries with nonzero prob.
                        potential[hidden_state] = value
        assert len(potential.keys()) > 0 , \
                "Invalid observation at time %d. Maybe you \
                forgot the --use-spread-output argument?"                                                         %n
        phis.append(potential)

    # we need not recompute edge potentials since they're given by the
    # transition model: phi(x_i, x_j) = transition_model[x_i](x_j),
    # where j = i+1

    #-------------------------------------------------------------------------
    # Forward pass
    #
    forward_messages = []

    # compute message from non-existent node -1 to node 0
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.
    message.renormalize()
    forward_messages.append(message)

    for n in range(num_time_steps - 1):
        # compute message from node n to node n+1
        message = Distribution()

        ## the commented block below is easier to understand but is slow;
        ## a faster version is below that switches the order of the for loops
        ## and reduces the number of states that we iterate over

        #for next_hidden_state in all_possible_hidden_states:
        #    value = 0.
        #    # only loop over hidden states with nonzero singleton potential!
        #    for hidden_state in phis[n]:
        #        value += phis[n][hidden_state] * \
        #                 transition_model(hidden_state)[next_hidden_state] * \
        #                 forward_messages[-1][hidden_state]
        #    if value > 0: # only store entries with nonzero prob.
        #        message[next_hidden_state] = value

        ## faster version of the commented block above
        # 1. only loop over hidden states with nonzero singleton potential!
        for hidden_state in phis[n]:
            # 2. only loop over possible next hidden states given current
            #    hidden state
            for next_hidden_state in transition_model(hidden_state):
                factor = phis[n][hidden_state] * \
                         transition_model(hidden_state)[next_hidden_state] * \
                         forward_messages[-1][hidden_state]
                if factor > 0:  # only store entries with nonzero prob.
                    if next_hidden_state in message:
                        message[next_hidden_state] += factor
                    else:
                        message[next_hidden_state] = factor

        message.renormalize()
        forward_messages.append(message)

    #-------------------------------------------------------------------------
    # Pre-processing to speed up the backward pass: cache for each hidden
    # state what the possible previous hidden states are
    #
    possible_prev_hidden_states = {}
    for hidden_state in all_possible_hidden_states:
        for next_hidden_state in transition_model(hidden_state):
            if next_hidden_state in possible_prev_hidden_states:
                possible_prev_hidden_states[next_hidden_state].add( \
                    hidden_state)
            else:
                possible_prev_hidden_states[next_hidden_state] = \
                    set([hidden_state])

    #-------------------------------------------------------------------------
    # Backward pass
    #
    backward_messages = []

    # compute message from non-existent node <num_time_steps> to node
    # <num_time_steps>-1
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.
    message.renormalize()
    backward_messages.append(message)

    for n in range(num_time_steps - 2, -1, -1):
        # compute message from node n+1 to n
        message = Distribution()

        ## again, I've commented out a block that's easier to understand but
        ## slow; the faster version is below

        #for hidden_state in all_possible_hidden_states:
        #    value = 0.
        #    for next_hidden_state in transition_model(hidden_state):
        #        value += phis[n+1][next_hidden_state] * \
        #                 transition_model(hidden_state)[next_hidden_state] * \
        #                 backward_messages[0][next_hidden_state]
        #    if value > 0: # only store entries with nonzero prob.
        #        message[hidden_state] = value

        ## faster version
        # 1. only loop over next hidden states with nonzero potential!
        for next_hidden_state in phis[n + 1]:
            # 2. only loop over possible previous hidden states
            for hidden_state in possible_prev_hidden_states[next_hidden_state]:
                factor = phis[n+1][next_hidden_state] * \
                         transition_model(hidden_state)[next_hidden_state] * \
                         backward_messages[0][next_hidden_state]
                if factor > 0:  # only store entries with nonzero prob.
                    if hidden_state in message:
                        message[hidden_state] += factor
                    else:
                        message[hidden_state] = factor

        message.renormalize()
        backward_messages.insert(0, message)

    #-------------------------------------------------------------------------
    # Compute marginals
    #
    marginals = []
    for n in range(num_time_steps):
        marginal = Distribution()
        for hidden_state in all_possible_hidden_states:
            if hidden_state in forward_messages[n] and \
               hidden_state in backward_messages[n] and \
               hidden_state in phis[n]:
                value = forward_messages[n][hidden_state] * \
                        backward_messages[n][hidden_state] * \
                        phis[n][hidden_state]
                if value > 0:  # only store entries with nonzero prob.
                    marginal[hidden_state] = value
        marginal.renormalize()
        marginals.append(marginal)

    # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    ### YOUR CODE HERE: Estimate marginals & pairwise marginals
    pairwise_marginals = [None] * (num_time_steps - 1)

    # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return (marginals, pairwise_marginals)
Ejemplo n.º 12
0
def forward(all_possible_hidden_states, prior_distribution, transition_model,
            observation_model, observations):
    """
    Inputs
    ------
    all_possible_hidden_states: a list of possible hidden states

    prior_distribution: a distribution over states

    transition_model: a function that takes a hidden state and returns a
        Distribution for the next state

    observation_model: a function that takes a hidden state and returns a
        Distribution for the observation from that hidden state

    observations: a list of observations, one per hidden state
        (a missing observation is encoded as None)

    Output
    ------
    This function is a Python generator! It calculates outputs "on demand";
    the i-th output should be the marginal distribution for time step i.
    """
    num_time_steps = len(observations)

    #-------------------------------------------------------------------------
    # Forward pass
    #
    def compute_marginal(message, node_potential):
        marginal = Distribution()
        for hidden_state in all_possible_hidden_states:
            if hidden_state in message and \
               hidden_state in node_potential:
                value = message[hidden_state] * node_potential[hidden_state]
                if value > 0:  # only store entries with nonzero prob.
                    marginal[hidden_state] = value
        marginal.renormalize()
        return marginal

    # compute message from non-existent node -1 to node 0
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.
    message.renormalize()

    # compute node potential for time step 0
    node_potential = Distribution()
    observed_state = observations[0]
    for hidden_state in prior_distribution:
        value = prior_distribution[hidden_state]
        if observed_state is not None:
            value *= observation_model(hidden_state)[observed_state]
        if value > 0:
            node_potential[hidden_state] = value

    yield compute_marginal(message, node_potential)
    prev_message = message
    prev_node_potential = node_potential

    for n in range(1, num_time_steps):
        message = Distribution()

        node_potential = Distribution()
        observed_state = observations[n]

        # compute message from node n-1 to n and fill in node potential for
        # time step n
        for hidden_state in all_possible_hidden_states:
            # only loop over possible next hidden states given current
            # hidden state
            for next_hidden_state in transition_model(hidden_state):
                factor = prev_node_potential[hidden_state] * \
                         transition_model(hidden_state)[next_hidden_state] * \
                         prev_message[hidden_state]
                if factor > 0:  # only store entries with nonzero prob.
                    if next_hidden_state in message:
                        message[next_hidden_state] += factor
                    else:
                        message[next_hidden_state] = factor

            if observed_state is not None:
                value = observation_model(hidden_state)[observed_state]
                if value > 0:
                    node_potential[hidden_state] = value
            else:
                node_potential[hidden_state] = 1.

        message.renormalize()
        yield compute_marginal(message, node_potential)
        prev_message = message
        prev_node_potential = node_potential
Ejemplo n.º 13
0
def forward(all_possible_hidden_states,
            prior_distribution,
            transition_model,
            observation_model,
            observations):
    """
    Inputs
    ------
    all_possible_hidden_states: a list of possible hidden states

    prior_distribution: a distribution over states

    transition_model: a function that takes a hidden state and returns a
        Distribution for the next state

    observation_model: a function that takes a hidden state and returns a
        Distribution for the observation from that hidden state

    observations: a list of observations, one per hidden state
        (a missing observation is encoded as None)

    Output
    ------
    This function is a Python generator! It calculates outputs "on demand";
    the i-th output should be the marginal distribution for time step i.
    """
    num_time_steps = len(observations)

    #-------------------------------------------------------------------------
    # Forward pass
    #
    def compute_marginal(message, node_potential):
        marginal = Distribution()
        for hidden_state in all_possible_hidden_states:
            if hidden_state in message and \
               hidden_state in node_potential:
                value = message[hidden_state] * node_potential[hidden_state]
                if value > 0: # only store entries with nonzero prob.
                    marginal[hidden_state] = value
        marginal.renormalize()
        return marginal

    # compute message from non-existent node -1 to node 0
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.
    message.renormalize()

    # compute node potential for time step 0
    node_potential = Distribution()
    observed_state = observations[0]
    for hidden_state in prior_distribution:
        value = prior_distribution[hidden_state]
        if observed_state is not None:
            value *= observation_model(hidden_state)[observed_state]
        if value > 0:
            node_potential[hidden_state] = value

    yield compute_marginal(message, node_potential)
    prev_message        = message
    prev_node_potential = node_potential

    for n in range(1, num_time_steps):
        message = Distribution()

        node_potential = Distribution()
        observed_state = observations[n]

        # compute message from node n-1 to n and fill in node potential for
        # time step n
        for hidden_state in all_possible_hidden_states:
            # only loop over possible next hidden states given current
            # hidden state
            for next_hidden_state in transition_model(hidden_state):
                factor = prev_node_potential[hidden_state] * \
                         transition_model(hidden_state)[next_hidden_state] * \
                         prev_message[hidden_state]
                if factor > 0: # only store entries with nonzero prob.
                    if next_hidden_state in message:
                        message[next_hidden_state] += factor
                    else:
                        message[next_hidden_state] = factor

            if observed_state is not None:
                value = observation_model(hidden_state)[observed_state]
                if value > 0:
                    node_potential[hidden_state] = value
            else:
                node_potential[hidden_state] = 1.

        message.renormalize()
        yield compute_marginal(message, node_potential)
        prev_message        = message
        prev_node_potential = node_potential
Ejemplo n.º 14
0
def forward_backward(
    all_possible_hidden_states,
    all_possible_observed_states,
    prior_distribution,
    transition_model,
    observation_model,
    observations,
):
    """
    Inputs
    ------
    all_possible_hidden_states: a list of possible hidden states

    all_possible_observed_states: a list of possible observed states

    prior_distribution: a distribution over states

    transition_model: a function that takes a hidden state and returns a
        Distribution for the next state

    observation_model: a function that takes a hidden state and returns a
        Distribution for the observation from that hidden state

    observations: a list of observations, one per hidden state
        (a missing observation is encoded as None)

    Output
    ------
    A list of marginal distributions at each time step; each distribution
    should be encoded as a Distribution (see the Distribution class in
    robot.py and see how it is used in both robot.py and the function
    generate_data() above, and the i-th Distribution should correspond to time
    step i
    """

    num_time_steps = len(observations)

    # -------------------------------------------------------------------------
    # Fold observations into singleton potentials
    #
    phis = []  # phis[n] is the singleton potential for node n
    for n in range(num_time_steps):
        potential = Distribution()
        observed_state = observations[n]
        if n == 0:
            for hidden_state in prior_distribution:
                value = prior_distribution[hidden_state]
                if observed_state is not None:
                    value *= observation_model(hidden_state)[observed_state]
                if value > 0:  # only store entries with nonzero prob.
                    potential[hidden_state] = value
        else:
            for hidden_state in all_possible_hidden_states:
                if observed_state is None:
                    # singleton potential should be identically 1
                    potential[hidden_state] = 1.0
                else:
                    value = observation_model(hidden_state)[observed_state]
                    if value > 0:  # only store entries with nonzero prob.
                        potential[hidden_state] = value
        assert len(potential.keys()) > 0, (
            "Invalid observation at time %d. Maybe you \
                forgot the --use-spread-output argument?"
            % n
        )
        phis.append(potential)

    # we need not recompute edge potentials since they're given by the
    # transition model: phi(x_i, x_j) = transition_model[x_i](x_j),
    # where j = i+1

    # -------------------------------------------------------------------------
    # Forward pass
    #
    forward_messages = []

    # compute message from non-existent node -1 to node 0
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.0
    message.renormalize()
    forward_messages.append(message)

    for n in range(num_time_steps - 1):
        # compute message from node n to node n+1
        message = Distribution()

        ## the commented block below is easier to understand but is slow;
        ## a faster version is below that switches the order of the for loops
        ## and reduces the number of states that we iterate over

        # for next_hidden_state in all_possible_hidden_states:
        #    value = 0.
        #    # only loop over hidden states with nonzero singleton potential!
        #    for hidden_state in phis[n]:
        #        value += phis[n][hidden_state] * \
        #                 transition_model(hidden_state)[next_hidden_state] * \
        #                 forward_messages[-1][hidden_state]
        #    if value > 0: # only store entries with nonzero prob.
        #        message[next_hidden_state] = value

        ## faster version of the commented block above
        # 1. only loop over hidden states with nonzero singleton potential!
        for hidden_state in phis[n]:
            # 2. only loop over possible next hidden states given current
            #    hidden state
            for next_hidden_state in transition_model(hidden_state):
                factor = (
                    phis[n][hidden_state]
                    * transition_model(hidden_state)[next_hidden_state]
                    * forward_messages[-1][hidden_state]
                )
                if factor > 0:  # only store entries with nonzero prob.
                    if next_hidden_state in message:
                        message[next_hidden_state] += factor
                    else:
                        message[next_hidden_state] = factor

        message.renormalize()
        forward_messages.append(message)

    # -------------------------------------------------------------------------
    # Pre-processing to speed up the backward pass: cache for each hidden
    # state what the possible previous hidden states are
    #
    possible_prev_hidden_states = {}
    for hidden_state in all_possible_hidden_states:
        for next_hidden_state in transition_model(hidden_state):
            if next_hidden_state in possible_prev_hidden_states:
                possible_prev_hidden_states[next_hidden_state].add(hidden_state)
            else:
                possible_prev_hidden_states[next_hidden_state] = set([hidden_state])

    # -------------------------------------------------------------------------
    # Backward pass
    #
    backward_messages = []

    # compute message from non-existent node <num_time_steps> to node
    # <num_time_steps>-1
    message = Distribution()
    for hidden_state in all_possible_hidden_states:
        message[hidden_state] = 1.0
    message.renormalize()
    backward_messages.append(message)

    for n in range(num_time_steps - 2, -1, -1):
        # compute message from node n+1 to n
        message = Distribution()

        ## again, I've commented out a block that's easier to understand but
        ## slow; the faster version is below

        # for hidden_state in all_possible_hidden_states:
        #    value = 0.
        #    for next_hidden_state in transition_model(hidden_state):
        #        value += phis[n+1][next_hidden_state] * \
        #                 transition_model(hidden_state)[next_hidden_state] * \
        #                 backward_messages[0][next_hidden_state]
        #    if value > 0: # only store entries with nonzero prob.
        #        message[hidden_state] = value

        ## faster version
        # 1. only loop over next hidden states with nonzero potential!
        for next_hidden_state in phis[n + 1]:
            # 2. only loop over possible previous hidden states
            for hidden_state in possible_prev_hidden_states[next_hidden_state]:
                factor = (
                    phis[n + 1][next_hidden_state]
                    * transition_model(hidden_state)[next_hidden_state]
                    * backward_messages[0][next_hidden_state]
                )
                if factor > 0:  # only store entries with nonzero prob.
                    if hidden_state in message:
                        message[hidden_state] += factor
                    else:
                        message[hidden_state] = factor

        message.renormalize()
        backward_messages.insert(0, message)

    # -------------------------------------------------------------------------
    # Compute marginals
    #
    marginals = []
    for n in range(num_time_steps):
        marginal = Distribution()
        for hidden_state in all_possible_hidden_states:
            if hidden_state in forward_messages[n] and hidden_state in backward_messages[n] and hidden_state in phis[n]:
                value = forward_messages[n][hidden_state] * backward_messages[n][hidden_state] * phis[n][hidden_state]
                if value > 0:  # only store entries with nonzero prob.
                    marginal[hidden_state] = value
        marginal.renormalize()
        marginals.append(marginal)

    # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
    ### YOUR CODE HERE: Estimate marginals & pairwise marginals
    pairwise_marginals = [None] * (num_time_steps - 1)

    # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    return (marginals, pairwise_marginals)