Esempio n. 1
0
def test_disconnected():
    model = PairWiseFiniteModel(size=4, al_size=5)
    model.add_interaction(0, 1, np.random.random(size=(5, 5)))
    model.add_interaction(2, 3, np.random.random(size=(5, 5)))
    max_lh_gt = model.max_likelihood(algorithm='bruteforce')
    max_lh = max_lh_path_dp(model)
    assert np.allclose(max_lh, max_lh_gt)
Esempio n. 2
0
def test_pairs():
    n = 5
    j = np.array([[0, 0, 10], [0, 0, 0], [0, 0, 0]])
    model = PairWiseFiniteModel(2 * n, 3)
    for i in range(n):
        model.add_interaction(2 * i, 2 * i + 1, j)
    expected = np.array([0, 2] * n)

    result = model.max_likelihood(algorithm='tree_dp')

    assert np.allclose(expected, result)
Esempio n. 3
0
def test_fully_isolated():
    # Create model where all variables are independent with given
    # distributions. Then calculate empirical distributions for every
    # variable - they should be close to original distributions.
    gr_size, al_size, num_samples = 10, 5, 200
    probs = np.random.random(size=(gr_size, al_size))
    probs /= probs.sum(axis=1).reshape(-1, 1)
    model = PairWiseFiniteModel(gr_size, al_size)
    model.set_field(np.log(probs))
    samples = model.sample(num_samples=num_samples, algorithm='tree_dp')

    check_samples(samples=samples, true_marg_probs=probs, tol=2e-3)
Esempio n. 4
0
def sample_tree_dp(model: PairWiseFiniteModel, num_samples: int):
    """Draws iid samples with dynamic programming on tree."""
    assert not model.get_dfs_result().had_cycles, "Graph has cycles."

    log_z = infer_tree_dp(model, subtree_mp=True).marg_prob
    log_z = log_z.astype(np.float64, copy=False)
    assert log_z.shape == (model.gr_size, model.al_size)

    dfs_edges = model.get_dfs_result().dfs_edges
    ints = model.get_interactions_for_edges(dfs_edges)
    num_samples = numba.types.int32(num_samples)

    return _sample_internal(log_z, dfs_edges, ints, num_samples)
Esempio n. 5
0
def test_to_fg_factor():
    model = PairWiseFiniteModel(3, 2)
    x0, x1, x2 = model.get_symbolic_variables()
    model *= (x0 + x1)
    model *= (2 * x1 + x2)

    tmp_file = os.path.join(LibDaiInterop().tmp_path, 'tmp.fg')
    LibDaiInterop.write_fg_file(model, tmp_file)
    with open(tmp_file, 'r') as f:
        lines = [x.strip() for x in f.readlines()]
    assert lines == [
        '2', '', '2', '0 1', '2 2', '3',
        '1 1.0000000000', '2 1.0000000000', '3 2.0000000000',
        '', '2', '1 2', '2 2', '3',
        '1 2.0000000000', '2 1.0000000000', '3 3.0000000000', '']
Esempio n. 6
0
def test_infer_disconnected():
    pw_model = PairWiseFiniteModel(5, al_size=2)
    pw_model.set_field(np.random.random(size=(5, 2)))
    pw_model.add_interaction(2, 3, np.random.random(size=(2, 2)))
    true_pf = np.exp(pw_model.infer(algorithm='bruteforce').log_pf)
    nfg_model = inferlo.NormalFactorGraphModel.from_model(pw_model)

    pf = infer_edge_elimination(nfg_model)

    assert np.allclose(true_pf, pf)
Esempio n. 7
0
def test_disconnected():
    model = PairWiseFiniteModel(size=4, al_size=5)
    model.add_interaction(0, 1, np.random.random(size=(5, 5)))
    model.add_interaction(2, 3, np.random.random(size=(5, 5)))
    max_lh_gt = model.max_likelihood(algorithm='path_dp')
    max_lh_gt_value = np.log(model.evaluate(max_lh_gt))
    max_lh_lower_bound = lp_relaxation(model).lower_bound
    max_lh_upper_bound = lp_relaxation(model).upper_bound
    assert (max_lh_lower_bound <= max_lh_gt_value <= max_lh_upper_bound)
Esempio n. 8
0
def test_create_from_model():
    field = np.zeros((4, 2))
    edges = [[0, 1], [0, 2], [0, 3]]
    j1 = np.array([[0, 0], [0, 1]])
    interactions = [j1, j1, j1]
    model1 = PairWiseFiniteModel.create(field, edges, interactions)
    expected_edges = [[0, 3], [0, 4], [1, 5], [2, 6], [1, 3], [2, 3]]
    kron_delta = np.array([[[1, 0], [0, 0]], [[0, 0], [0, 1]]])
    unit_factor = np.array([1, 1])
    expected_factor_tables = [
        np.exp(j1)] * 3 + [kron_delta] + [
        unit_factor] * 3

    model2 = NormalFactorGraphModel.from_model(model1)

    assert model2.num_variables == 6
    assert len(model2.factors) == 7
    assert model2.edges == expected_edges
    for i in range(7):
        assert np.allclose(model2.factors[i].values, expected_factor_tables[i])
Esempio n. 9
0
def minimal_cycle(model: PairWiseFiniteModel) -> sherali_adams_result:
    """
    This is an implementation of Cycle relaxation. In fact,
    cycle relaxation is a simplified version of the third
    level of Sherali-Adams hierarchy. It may result in worse
    upper bounds but has much fewer constraints and solved
    faster.

    The idea behind this relaxation is to consider all cycles
    in the graph and to add cycle-to-edge marginalization
    constraints to the local consistency constraints.

    In some cases cycle relaxation coincides with the third
    level of Sherali-Adams. It may also be shown that instead
    of all cycles constraints, it is enough to consider only
    chordless cycles. In this code we consider the set of
    minimal cycles found by networkx.

    More on LP hierarchies may be found in D.Sontag's thesis
    "Approximate Inference in Graphical Models using LP
    relaxations".
    https://people.csail.mit.edu/dsontag/papers/sontag_phd_thesis.pdf
    """
    al_size = model.al_size
    var_size = model.gr_size
    edge_list = model.edges

    # check if graph is acyclic
    if model.is_graph_acyclic():
        print("Graph is acyclic!")
        print("Cycle relaxation is equivalent to the local LP relaxation.")

    # introduce cluster variables and constraints
    clusters = {}
    constraints = []

    # add local consistency constraints first
    for cluster_size in [1, 2]:
        variables = list(combinations(range(var_size), cluster_size))
        values = list(product(range(al_size), repeat=cluster_size))

        for cluster_ids in variables:
            cluster = {}
            for x in values:
                cluster[x] = cp.Variable(nonneg=True)
            clusters[cluster_ids] = cluster

            # add normalization constraint
            constraints += [sum(list(cluster.values())) == 1]

            # add marginalization constraints
            for cluster_subset_size in range(1, cluster_size):
                all_cluster_subsets = list(
                    combinations(list(cluster_ids), cluster_subset_size))
                subset_values = list(
                    product(range(al_size), repeat=cluster_subset_size))

                for subset in all_cluster_subsets:
                    subset_ids = list(subset)
                    for subset_x in subset_values:
                        marginal_sum = 0.0
                        for value, cp_variable in cluster.items():
                            consistency = [
                                value[cluster_ids.index(
                                    subset_ids[i])] == subset_x[i]
                                for i in range(cluster_subset_size)
                            ]
                            if sum(consistency) == len(subset):
                                marginal_sum += cp_variable

                        constraints += [
                            marginal_sum == clusters[subset][subset_x]
                        ]

    # add cycle consistency
    graph = model.get_graph()
    cycles = nx.cycle_basis(graph)

    for cycle in cycles:
        cycle.append(cycle[0])
        cycle_edges = []
        for i in range(len(cycle) - 1):
            edge = sorted([cycle[i], cycle[i + 1]])
            cycle_edges.append(tuple(edge))

        cycle_values = list(product(range(al_size), repeat=len(cycle)))
        cluster_ids = tuple(sorted(cycle))
        cluster = {}
        for x in cycle_values:
            cluster[x] = cp.Variable(nonneg=True)
        clusters[cluster_ids] = cluster

        # add normalization constraint
        constraints += [sum(list(cluster.values())) == 1]

        # add marginalization constraints
        for edge in cycle_edges:
            edge_values = list(product(range(al_size), repeat=2))
            first_node_position = cluster_ids.index(edge[0])
            second_node_position = cluster_ids.index(edge[1])
            for edge_value in edge_values:
                marginal_sum = 0.0
                edge_variable = clusters[edge][edge_value]
                for x in cycle_values:
                    if (x[first_node_position] == edge_value[0]) \
                            and (x[second_node_position] == edge_value[1]):
                        marginal_sum += clusters[cluster_ids][x]
                constraints += [marginal_sum == edge_variable]

    # define objective
    objective = 0

    # add field in every node
    for node in range(var_size):
        for letter in range(al_size):
            objective += model.field[node, letter] * \
                clusters[(node,)][(letter,)]

    # add pairwise interactions
    # a and b iterate over all values of the finite field
    for edge in edge_list:
        for a in range(model.al_size):
            for b in range(model.al_size):
                J = model.get_interaction_matrix(edge[0], edge[1])
                if (edge[0] <= edge[1]):
                    objective += J[a, b] * clusters[(edge[0], edge[1])][(a, b)]
                else:
                    objective += J[a, b] * clusters[(edge[1], edge[0])][(b, a)]

    prob = cp.Problem(cp.Maximize(objective), constraints)
    prob.solve(solver=cp.SCS, eps=1e-8)

    projected_variables = []
    for node in range(var_size):
        projected_variables.append(clusters[(node, )].values())

    return sherali_adams_result(upper_bound=prob.value,
                                projection=projected_variables)
Esempio n. 10
0
def map_lp(model: PairWiseFiniteModel) -> map_lp_result:
    """LP relaxation of MAP problem for pairwise model.

    This function implements linear programming (LP) relaxation
    of maximum a posteriori assignment problem (MAP) for
    pairwise graphical model with finite alphabet.

    The goal of MAP estimation is to find most probable
    assignment of original variables by maximizing probability
    density function. For the case of pairwise finite model it
    reduces to maximization of quadratic function over finite
    field.

    For every node, we introduce Q non-negative belief variables
    where Q is the size of the alphabet. Every such variable
    is our 'belief' that variable at node takes particular value.

    Analogously, for every edge we introduce Q*Q pairwise beliefs.

    For both node and edge beliefs we require normalization
    constraints: 1) for every node, the sum of beliefs equals one
    and 2) for every edge the sum of edge-beliefs equals one.

    We also add marginalization constraint: for every edge,
    summing edge beliefs over one of the nodes must equal
    to the node belief of the second node.

    Finally we get a linear program and its solution is an
    upper bound on the MAP value. We restore the lower bound
    on MAP value as the solution of the dual relaxation.

    More details may be found in "MAP Estimation,
    Linear Programming and BeliefPropagation with
    Convex Free Energies" by Yair Weiss, Chen Yanover and Talya
    Meltzer. https://arxiv.org/pdf/1206.5286.pdf

    :param model: Model for which to solve MAP problem.

    :return: Object with the following fields:
      ``upper_bound`` - upper bound on MAP value (solution of LP);
      ``lower_bound`` - lower bound on MAP value (dual solution);
      ``node_beliefs`` - optimal values of node beliefs;
      ``edge_beliefs`` - optimal values of edge beliefs;
      ``normalization_duals`` - optimal values of dual variables that
      correspond to normalization constraints;
      ``marginalization_duals`` - optimal values of dual variables that
      correspond to marginalization constraints.
    """

    edge_list = model.edges
    node_beliefs = cp.Variable((model.gr_size, model.al_size), nonneg=True)
    edge_beliefs = []
    for edge in edge_list:
        edge_beliefs.append(
            cp.Variable((model.al_size, model.al_size), nonneg=True))
    objective = 0
    constraints = []

    # add field in every node
    for node in range(model.gr_size):
        for letter in range(model.al_size):
            objective += model.field[node, letter] * node_beliefs[node, letter]

    # add pairwise interactions
    # a and b iterate over all values of the finite field
    for edge in edge_list:
        for a in range(model.al_size):
            for b in range(model.al_size):
                J = model.get_interaction_matrix(edge[0], edge[1])
                objective += J[a, b] * edge_beliefs[edge_list.index(edge)][a,
                                                                           b]

    # normalization constraints
    for edge in edge_list:
        constraints += [
            sum([
                edge_beliefs[edge_list.index(edge)][a, b]
                for a in range(model.al_size) for b in range(model.al_size)
            ]) == 1
        ]

    # marginalization constraints
    for edge in edge_list:
        for a in range(model.al_size):
            marginal_left = 0.0
            for b in range(model.al_size):
                marginal_left += edge_beliefs[edge_list.index(edge)][a, b]
            constraints += [marginal_left == node_beliefs[edge[0], a]]

        for a in range(model.al_size):
            marginal_right = 0.0
            for b in range(model.al_size):
                marginal_right += edge_beliefs[edge_list.index(edge)][b, a]
            constraints += [marginal_right == node_beliefs[edge[1], a]]

    prob = cp.Problem(cp.Maximize(objective), constraints)
    prob.solve(solver=cp.SCS, eps=1e-8)

    normal_dual_vars = [
        constraints[i].dual_value for i in range(len(edge_list))
    ]
    marginal_dual_vars = [
        constraints[i].dual_value
        for i in range(len(edge_list), len(constraints))
    ]
    dual_objective = sum(normal_dual_vars)

    return map_lp_result(upper_bound=prob.value,
                         lower_bound=dual_objective,
                         node_beliefs=node_beliefs.value,
                         edge_beliefs=[edge.value for edge in edge_beliefs],
                         normalization_duals=normal_dual_vars,
                         marginalization_duals=marginal_dual_vars)
Esempio n. 11
0
def test_fully_isolated():
    model = PairWiseFiniteModel(10, 2)
    model.set_field(np.random.random(size=(10, 2)))
    ground_truth = model.infer(algorithm='bruteforce')
    result = model.infer(algorithm='tree_dp')
    assert_results_close(result, ground_truth)
Esempio n. 12
0
def test_fully_isolated():
    model = PairWiseFiniteModel(10, 2)
    model.set_field(np.random.random(size=(10, 2)))
    ground_truth = model.max_likelihood(algorithm='bruteforce')
    result = model.max_likelihood(algorithm='tree_dp')
    assert np.allclose(ground_truth, result)
Esempio n. 13
0
def lp_relaxation(model: PairWiseFiniteModel) -> LPRelaxResult:
    """Max Likelihood for pairwise model by solving LP relaxation.

    1) Reformulates the original problem of maximizing
    ``sum F[i][X_i] + 0.5*sum J[i][j][X[i]][X[j]])``
    as a binary optimization problem by introducing new variables
    ``y_i``, ``a`` and ``z_i,j,a,b``::

        maximize (sum_i sum_a F[i][a] * y_i,a) +
                 (sum_i sum_j sum_a sum_b 0.5*sum J[i][j][a][b] * z_i,j,a,b))
        subject to y_i,a in {0, 1}
                z_i,j,a,b in {0, 1}
                (for all i) sum_a y_i,a = 1
                z_i,j,a,b <= y_i,a
                z_i,j,a,b <= y_j,b
                z_i,j,a,b >= y_i,a + y_j,b - 1

    2) Solves the LP relaxation by relaxing binary constraints
    to::

        y_i,a in [0, 1]
        z_i,j,a,b in [0, 1]

    Note that ``z`` will be reshaped to matrix (cvxpy does not support tensor
    variables).

    :param model: Model for which to find most likely state.
    :return: Solution of LP relaxation of the Max Likelihood problem.
    """
    edge_list = model.edges

    y = cp.Variable((model.gr_size, model.al_size), nonneg=True)
    z = []
    for e in edge_list:
        z.append(cp.Variable((model.al_size, model.al_size), nonneg=True))
    obj = 0
    cons = []

    # add field
    for i in range(model.gr_size):
        for a in range(model.al_size):
            obj += model.field[i, a] * y[i, a]

    # add pairwise
    # a and b iterate over all values of the finite field
    for e in edge_list:
        for a in range(model.al_size):
            for b in range(model.al_size):
                J = model.get_interaction_matrix(e[0], e[1])
                obj += J[a, b] * z[edge_list.index(e)][a, b]

    # add y constraints:
    for i in range(model.gr_size):
        cons += [sum(y[i, :]) == 1]
        for a in range(model.al_size):
            cons += [y[i, a] <= 1]

    # add z constraints
    for e in edge_list:
        for a in range(model.al_size):
            for b in range(model.al_size):
                cons += [z[edge_list.index(e)][a, b] <= 1]
                cons += [z[edge_list.index(e)][a, b] <= y[e[0], a]]
                cons += [z[edge_list.index(e)][a, b] <= y[e[1], b]]
                cons += [z[edge_list.index(e)][a, b] >=
                         y[e[0], a] + y[e[1], b] - 1]

    prob = cp.Problem(cp.Maximize(obj), cons)
    prob.solve(solver=cp.SCS)

    # in fact, rounding LP relaxation is sampling
    rounded = np.array(np.random.randint(low=0,
                                         high=model.al_size,
                                         size=model.gr_size))
    lower = np.log(model.evaluate(rounded))

    return LPRelaxResult(
        upper_bound=prob.value,
        lower_bound=lower,
        rounded_solution=rounded
    )