Пример #1
0
def test_max_likelihood_all_methods():
    # Sanity check that all algorithms work on very simple model.
    all_methods = ['auto', 'bruteforce', 'tree_dp', 'path_dp', 'junction_tree']
    model = PairWiseFiniteModel(2, 2)
    model.add_interaction(0, 1, np.array([[0, 0], [0, 1]]))
    expected_result = np.array([1, 1])

    for method in all_methods:
        result = model.max_likelihood(algorithm=method)
        assert np.allclose(result, expected_result)
Пример #2
0
def test_sample_all_methods():
    # Sanity check that all algorithms work on very simple model.
    all_methods = ['auto', 'bruteforce', 'tree_dp', 'junction_tree']
    model = PairWiseFiniteModel(2, 2)
    model.set_field(np.array([[100, 0], [100, 0]]))
    num_samples = 10
    expected_result = np.zeros((num_samples, 2))

    for method in all_methods:
        result = model.sample(algorithm=method, num_samples=num_samples)
        assert result.shape == expected_result.shape
        assert np.allclose(result, expected_result)
Пример #3
0
def infer_message_passing(model: PairWiseFiniteModel,
                          max_iter=None) -> InferenceResult:
    """Inference with Message Passing.

    For acyclic graph returns exact partition function and marginal
        probabilities. For graph with loops may return good approximation to
        the true marginal probabilities, but partition function will be a
        useless number.
    This is an iterative algorithm which terminates when it converged or when
        `max_iter` iterations were made.

    :param model: Pairwise model for which to perform inference.
    :param max_iter: How many iterations without convergence should happen for
        algorithm to terminate. Defaults to maximal diameter of connected
        component.
    :return: InferenceResult object.

    Reference
        [1] Wainwright, Jordan. Graphical Models, Exponential Families, and
        Variational Inference. 2008. Section 2.5.1 (p. 26).
    """
    if max_iter is None:
        graph = networkx.Graph()
        graph.add_edges_from(model.get_edges_connected())
        max_iter = networkx.diameter(graph)

    # Build list of directed edges.
    edges = model.get_edges_connected()
    dir_edges = np.concatenate([edges, np.flip(edges, axis=1)])

    # Sort edges by end vertex. This ensures that edges ending with the same
    # vertex are sequential, which allows for efficient lookup.
    dir_edges.view('i4,i4').sort(order=['f1'], axis=0)

    # Compact representation of interactions.
    intrn = model.get_interactions_for_edges(dir_edges)

    # Main algorithm.
    lmu = _message_passing(dir_edges, intrn, model.field, max_iter)

    # Restore partition function for fixed values in nodes.
    log_marg_pf = np.array(model.field)
    for edge_id in range(len(dir_edges)):
        log_marg_pf[dir_edges[edge_id][1], :] += lmu[edge_id]

    log_pf = scipy.special.logsumexp(log_marg_pf, axis=-1)
    marg_prob = scipy.special.softmax(log_marg_pf, axis=-1)
    marg_prob /= np.sum(marg_prob, axis=-1).reshape(-1, 1)
    return InferenceResult(np.min(log_pf), marg_prob)
Пример #4
0
def test_get_dfs_result():
    model = PairWiseFiniteModel(4, 2)
    j = np.ones((2, 2))
    model.add_interaction(2, 3, j)
    model.add_interaction(2, 1, j)
    model.get_dfs_result()  # To test cache invalidation.
    model.add_interaction(1, 0, j)

    dfs_edges = model.get_dfs_result().dfs_edges

    assert np.allclose(dfs_edges, np.array([[0, 1], [1, 2], [2, 3]]))
Пример #5
0
def max_likelihood_tree_dp(model: PairWiseFiniteModel):
    """Max Likelihood for the pairwise model.

    Performs dynamic programming on tree.

    Applicable only if the interaction graph is a tree or a forest. Otherwise
    throws exception.

    :param model: Model for which to find most likely state.
    :return: Most likely state. np.array of ints.
    """
    assert not model.get_dfs_result().had_cycles, "Graph has cycles."

    field = model.field.astype(np.float64, copy=False)
    dfs_edges = model.get_dfs_result().dfs_edges
    ints = model.get_interactions_for_edges(dfs_edges)

    return _max_likelihood_internal(field, dfs_edges, ints)
Пример #6
0
def test_inference_all_methods():
    # Sanity check that all algorithms work on very simple model.
    all_methods = [
        'auto', 'bruteforce', 'mean_field', 'message_passing', 'tree_dp',
        'path_dp', 'junction_tree'
    ]
    model = PairWiseFiniteModel(2, 2)
    model.add_interaction(0, 1, np.array([[0, 0], [0, 1]]))
    m = np.array([2, 1 + np.exp(1)])
    expected_result = InferenceResult(np.log(3 + np.exp(1)),
                                      np.array([m, m]) / np.sum(m))

    for method in all_methods:
        result = model.infer(algorithm=method)
        assert_results_close(result,
                             expected_result,
                             log_pf_tol=1.0,
                             mp_mse_tol=0.1)
Пример #7
0
def test_build_from_factors():
    model = PairWiseFiniteModel(5, 2)
    x = model.get_symbolic_variables()

    # Field.
    model *= np.exp(5 * x[1])
    model *= np.exp(10 * x[2])
    model *= DiscreteFactor(model, [3], np.exp([7, 8]))

    # Interactions.
    model *= np.exp(2 * x[0] * x[1])
    model *= (1 + x[2] + x[3])
    model *= np.exp(10 * x[0] * x[1])  # Should accumulate.
    model *= DiscreteFactor(model, [0, 4], np.exp([[1, 2], [3, 4]]))

    assert np.allclose(model.field,
                       np.array([[0, 0], [0, 5], [0, 10], [7, 8], [0, 0]]))
    assert model.edges == [(0, 1), (2, 3), (0, 4)]
    assert np.allclose(model.get_interaction_matrix(0, 1), [[0, 0], [0, 12]])
    assert np.allclose(model.get_interaction_matrix(2, 3),
                       np.log([[1, 2], [2, 3]]))
    assert np.allclose(model.get_interaction_matrix(0, 4), [[1, 2], [3, 4]])
Пример #8
0
def test_build_from_interactions():
    model = PairWiseFiniteModel(10, 5)
    j1 = np.random.random(size=(5, 5))
    j2 = np.random.random(size=(5, 5))
    model.add_interaction(0, 1, j1)
    model.add_interaction(1, 0, j1)
    model.add_interaction(1, 2, j2)

    assert np.allclose(model.field, np.zeros((10, 5)))
    assert model.edges == [(0, 1), (1, 2)]
    assert np.allclose(model.get_interaction_matrix(0, 1), j1 + j1.T)
    assert np.allclose(model.get_interaction_matrix(2, 1), j2.T)
Пример #9
0
def prepare_path_dp(model: PairWiseFiniteModel) -> PwPathDecomposition:
    """Prepares model for path decomposition dynamic programming.

    Builds path decomposition and calculates matrices A describing interactions
    with layers and matrices B describing interactions between them.

    :param model: Pairwise model.
    :return: PwPathDecomposition object with path decomposition and calculated
      matrices A and B.
    """
    layers = path_decomposition(model.get_graph())
    sum_layers_size = sum([len(layer) for layer in layers])
    assert sum_layers_size == model.gr_size, "Graph is not connected."
    max_layer_size = max([len(layer) for layer in layers])
    assert model.al_size**max_layer_size <= 1e5, (
        "Algorithm won't handle this complexity.")

    a = [model.get_subgraph_factor_values(layer) for layer in layers]
    b = [
        get_b(model, layers[i], layers[i + 1]) for i in range(len(layers) - 1)
    ]

    return PwPathDecomposition(layers=layers, a=a, b=b)
Пример #10
0
def infer_tree_dp(model: PairWiseFiniteModel,
                  subtree_mp=False) -> InferenceResult:
    """Inference using DP on tree.

    Performs dynamic programming on tree.

    Applicable only if the interaction graph is a tree or a forest. Otherwise
    throws exception.

    :param model: Model for which to perform inference.
    :param subtree_mp: If true, will return marginal probabilities for
        subtrees, i.e. for each node will return probability of it having
        different values if we leave only it and its subtree.
    :return: InferenceResult object.
    """
    assert not model.get_dfs_result().had_cycles, "Graph has cycles."

    dfs_edges = model.get_dfs_result().dfs_edges
    dfs_j = model.get_interactions_for_edges(dfs_edges)

    lz = model.field.astype(dtype=np.float64, copy=True)  # log(z)
    lzc = np.zeros_like(lz)  # log(zc)
    # Log(z_r). z_r  is partition function for all tree except subtree of given
    # vertex, when value of given vertex is fixed.
    lzr = np.zeros((model.gr_size, model.al_size))

    _dfs1(lz, lzc, dfs_edges, dfs_j)
    log_pf = logsumexp(lz[0, :])

    if subtree_mp:
        return InferenceResult(log_pf, lz)

    _dfs2(lz, lzc, lzr, dfs_edges, dfs_j)

    marg_proba = np.exp(lz + lzr - log_pf)
    return InferenceResult(log_pf, marg_proba)
Пример #11
0
def test_from_model():
    model1 = GenericGraphModel(3)
    model1[0].domain = DiscreteDomain.binary()
    model1[2].domain = DiscreteDomain([0, 10])
    model1[1].domain = DiscreteDomain.range(3)
    x0, x1, x2 = model1.get_symbolic_variables()
    model1.add_factor(np.exp(x0 + x1))
    model1.add_factor(np.exp(x1 + x2))
    model2 = PairWiseFiniteModel.from_model(model1)

    assert np.allclose(model2.field, np.zeros((3, 3)))
    assert model2.edges == [(0, 1), (1, 2)]
    assert np.allclose(
        model2.get_all_interactions(),
        np.array([[[0, 1, 2], [1, 2, 3], [-np.inf, -np.inf, -np.inf]],
                  [[0, 10, -np.inf], [1, 11, -np.inf], [2, 12, -np.inf]]]))
Пример #12
0
def _compute_all_probs(model: PairWiseFiniteModel) -> np.ndarray:
    """For all possible states finds their probabilities (not normed)."""
    if model.al_size**model.gr_size > 2e7:
        raise TooMuchStatesError()
    return _compute_all_probs_internal(model.field, model.get_edges_array(),
                                       model.get_all_interactions())
Пример #13
0
def test_encode_state():
    model = PairWiseFiniteModel(5, 2)
    assert model.encode_state([1, 1, 0, 1, 0]) == 11

    model = PairWiseFiniteModel(5, 10)
    assert model.encode_state([5, 4, 3, 2, 1]) == 12345
Пример #14
0
def test_decode_state():
    model = PairWiseFiniteModel(5, 2)
    assert model.decode_state(11) == [1, 1, 0, 1, 0]

    model = PairWiseFiniteModel(5, 10)
    assert model.decode_state(12345) == [5, 4, 3, 2, 1]