Example #1
0
def solve_lp_primal_elided(verif_instance):
    """Compute optimal attack loss for MLPs against LP relaxation."""
    assert verif_instance.type == utils.VerifInstanceTypes.MLP_ELIDED
    params, bounds, obj, obj_const = (verif_instance.params,
                                      verif_instance.bounds,
                                      verif_instance.obj, verif_instance.const)
    layer_sizes = utils.mlp_layer_sizes(params)
    post_activations = [cp.Variable((1, layer_sizes[0]))]
    constraints = []

    for (i, param) in enumerate(params):
        W, b = param
        b = jnp.reshape(b, (1, b.size))
        post_activations.append(cp.Variable((1, b.size)))
        pre_act = post_activations[-2] @ W + b
        post_act = post_activations[-1]

        # Linear relaxation of ReLU constraints
        constraints += [post_act >= pre_act]
        constraints += [post_act >= 0]

        # Triangle relaxation
        l = np.minimum(0., bounds[i + 1].lb_pre)
        u = np.maximum(0., bounds[i + 1].ub_pre)
        constraints += [
            cp.multiply(u, pre_act) - cp.multiply(u, l) -
            cp.multiply(u - l, post_act) >= 0
        ]

    # Optionally, include IBP bounds to speed up MIP solving
    # Post activations are within bounds
    # i=0 case encodes input constraint
    for (i, post) in enumerate(post_activations[:1]):
        constraints += [post <= bounds[i].ub]
        constraints += [post >= bounds[i].lb]

    # Set objective over final post-activations
    obj_cp = cp.sum(cp.multiply(obj, post_activations[-1]))

    # Define and solve problem
    problem = cp.Problem(cp.Maximize(obj_cp), constraints)
    problem.solve(solver=cp.ECOS)

    # Report results
    info = {
        'problem': problem,
        'post': post_activations,
    }
    return obj_cp.value + obj_const, info
Example #2
0
def check_sdp_bounds_numpy(P, verif_instance, input_bounds=(0, 1)):
    """Check SDP solution for 1-hidden MLP satisfies constraints in numpy."""
    params, bounds, obj, const = (verif_instance.params, verif_instance.bounds,
                                  verif_instance.obj, verif_instance.const)
    layer_sizes = utils.mlp_layer_sizes(params)
    assert len(layer_sizes) == 2, 'Relu MLP with 1 hidden layer'
    assert len(params) == 1, 'Relu MLP with 1 hidden layer'
    assert P.shape == (1 + sum(layer_sizes), 1 + sum(layer_sizes))
    violations = {}
    # Matrix constraints
    violations['P = P.T'] = _violation(np.abs(P - P.T))
    violations['P[0][0] = 1'] = abs(P[0][0] - 1.0)
    eig_vals, _ = scipy.linalg.eigh(P)
    violations['P >= 0 (SDP)'] = _violation(-eig_vals)

    x = P[0, 1:1 + layer_sizes[0]]
    z = P[0, 1 + layer_sizes[0]:]
    xx = P[1:1 + layer_sizes[0], 1:1 + layer_sizes[0]]
    xz = P[1:1 + layer_sizes[0], 1 + layer_sizes[0]:]
    zz = P[1 + layer_sizes[0]:, 1 + layer_sizes[0]:]

    # Relu constraints
    w, b = params[0]
    violations['relu_0'] = _violation_leq(0, z)
    violations['relu_wx_b'] = _violation_leq(np.matmul(x, w) + b, z)
    violations['relu_eq'] = _violation(
        np.abs(np.diag(np.matmul(w.T, xz)) + b * z - np.diag(zz)))

    # Input bound constraints
    violations['input_lb'] = _violation_leq(input_bounds[0], x)
    violations['input_ub'] = _violation_leq(x, input_bounds[1])

    # Interval bound constraints
    for i in range(len(layer_sizes)):
        lb = bounds[i].lb[0]
        ub = bounds[i].ub[0]
        x_slice = slice(1 + sum(layer_sizes[:i]), 1 + sum(layer_sizes[:i + 1]))
        x = P[0, x_slice]
        xx = P[x_slice, x_slice]
        violations[f'lay{i}_bound'] = _violation_leq(np.diag(xx),
                                                     (lb + ub) * x - lb * ub)

    # Objective
    obj = const + np.sum(obj * z)
    return obj, violations
Example #3
0
def solve_mip_mlp_elided(verif_instance):
    """Compute optimal attack loss for MLPs, via exactly solving MIP."""
    assert MIP_SOLVERS, 'No MIP solvers installed with cvxpy.'
    assert verif_instance.type == utils.VerifInstanceTypes.MLP_ELIDED
    params, bounds, obj, obj_const = (verif_instance.params,
                                      verif_instance.bounds,
                                      verif_instance.obj, verif_instance.const)
    layer_sizes = utils.mlp_layer_sizes(params)
    on_state = []
    post_activations = [cp.Variable((1, layer_sizes[0]))]
    pre_activations = []
    constraints = []

    for (i, param) in enumerate(params):
        W, b = param
        b = jnp.reshape(b, (1, b.size))
        on_state.append(cp.Variable((1, b.size), boolean=True))
        pre_activations.append(cp.Variable((1, b.size)))
        post_activations.append(cp.Variable((1, b.size)))

        # Linear relaxation of ReLU constraints
        constraints += [pre_activations[-1] == post_activations[-2] @ W + b]
        constraints += [post_activations[-1] >= pre_activations[-1]]
        constraints += [post_activations[-1] >= 0]

        # If ReLU is off, post activation is non-positive. Otherwise <= ub
        constraints += [
            post_activations[-1] <= cp.multiply(on_state[-1], bounds[i + 1].ub)
        ]

        # If ReLU is off, pre-activation is non-positive. Otherwise <= ub_pre
        constraints += [
            pre_activations[-1] <= cp.multiply(on_state[-1],
                                               bounds[i + 1].ub_pre)
        ]

        # If ReLU is on, post-activation == pre-activation
        # Define <= here, >= constraint added above.
        constraints += [
            post_activations[-1] - pre_activations[-1] <= cp.multiply(
                1 - on_state[-1], bounds[i + 1].ub - bounds[i + 1].lb_pre)
        ]

    # Optionally, include IBP bounds to speed up MIP solving
    # Post activations are within bounds
    # i=0 case encodes input constraint
    for (i, post) in enumerate(post_activations):
        constraints += [post <= bounds[i].ub]
        constraints += [post >= bounds[i].lb]

    # # Pre activations are within bounds
    for (i, pre) in enumerate(pre_activations):
        constraints += [pre <= bounds[i + 1].ub_pre]
        constraints += [pre >= bounds[i + 1].lb_pre]

    # Set objective over final post-activations
    obj_cp = cp.sum(cp.multiply(obj, post_activations[-1]))

    # Define and solve problem
    problem = cp.Problem(cp.Maximize(obj_cp), constraints)
    # NB: Originally, we used cp.ECOS_BB here, but cvxpy 1.1 drops support,
    # so we just use the first available MIP solver (which is dependent on user
    # installation).
    problem.solve(solver=MIP_SOLVERS[0])

    # Report results
    info = {
        'problem': problem,
        'post': post_activations,
        'pre': pre_activations,
    }
    return obj_cp.value + obj_const, info
Example #4
0
def solve_sdp_mlp_elided(verif_instance,
                         solver_name='SCS',
                         verbose=True,
                         check_feasibility=False,
                         feasibility_margin=0.0):
    """Compute exact SDP relaxation verified bound, following Raghunathan 18.

  Args:
    verif_instance: VerifInstance namedtuple
    solver_name: string, SDP solver, either 'SCS' or 'CVXOPT'
    verbose: bool, controls verbose output from SDP solver
    check_feasibility: bool, if True, try to find any verified certificate,
      rather than tightest possible lower bound
    feasibility_margin: float, when `check_feasibility=True`, verify that
      adversary cannot decrease objective below `feasibility_margin`.

  Returns:
    obj_value: either a float, the bound on objective (check_feasibility=False),
      or a bool, whether verification succeeded (check_feasibility=True)
    info: dict of other info, e.g. solver status, values found by solver
  """
    assert verif_instance.type == utils.VerifInstanceTypes.MLP_ELIDED
    params, input_bounds, bounds, obj, obj_const = (
        verif_instance.params, verif_instance.input_bounds,
        verif_instance.bounds, verif_instance.obj, verif_instance.const)
    layer_sizes = utils.mlp_layer_sizes(params)
    assert len(bounds) == len(layer_sizes) + 1
    # Matrix P, where P = vv' before SDP relaxation, and v = [1, x_0, ..., x_L]
    P = cp.Variable((1 + sum(layer_sizes), 1 + sum(layer_sizes)))

    # Matrix constraints
    constraints = [
        P == P.T,
        P >> 0,
        P[0][0] == 1.,
    ]

    cumsum_sizes = [0] + list(np.cumsum(layer_sizes))

    def _slice(i):
        """Helper method for `p_slice`."""
        if i == -1:
            return 0
        else:
            return slice(1 + cumsum_sizes[i], 1 + cumsum_sizes[i + 1])

    def p_slice(i, j):
        """Symbolic indexing into matrix P.

    Args:
      i: an integer, either -1, or in [0, num_layers).
      j: an integer, either -1, or in [0, num_layers).

    Returns:
      slice object, used to index into P. In the QCQP, if P = vv', where
        v = [1, x_0, ..., x_L], then p_slice(i, j) gives submatrix corresponding
        to P[x_i x_j']. When j = -1, this returns P[x_i].
    """
        return P[_slice(i), _slice(j)]

    diag = cp.atoms.affine.diag.diag

    # Input/IBP constraints
    # TODO: Check if these are actually necessary
    if input_bounds is not None:
        constraints += [p_slice(0, -1) >= input_bounds[0]]
        constraints += [p_slice(0, -1) <= input_bounds[1]]

    for i in range(len(layer_sizes)):
        lb = bounds[i].lb[0]
        ub = bounds[i].ub[0]
        assert lb.shape == ub.shape == (layer_sizes[i], )
        assert diag(p_slice(i, i)).shape == (layer_sizes[i], )
        assert p_slice(i, -1).shape == (layer_sizes[i], )
        constraints += [
            diag(p_slice(i, i)) <=
            cp.multiply(lb + ub, p_slice(i, -1)) - cp.multiply(lb, ub)
        ]

    # Relu / weight constraints
    for i, param in enumerate(params):
        W, b = param
        constraints += [p_slice(i + 1, -1) >= 0]
        constraints += [p_slice(i + 1, -1) >= p_slice(i, -1) @ W + b]
        # Encode constraint P[zz'] = WP[xz']. Since our networks use z=xW+b rather
        # than z=Wx+b, we use W'x = xW (which holds when x is a vector)
        constraints += [
            diag(p_slice(i + 1, i + 1)) == diag(W.T @ p_slice(i, i + 1)) +
            cp.multiply(b, p_slice(i + 1, -1))
        ]

    # Set objective over final post-activations
    final_idx = len(layer_sizes) - 1
    x_final = P[_slice(final_idx), _slice(-1)]
    obj_cp = cp.sum(cp.multiply(obj[0], x_final))

    # Define and solve problem
    if check_feasibility:
        constraints += [obj_cp + obj_const >= feasibility_margin]
        problem = cp.Problem(cp.Maximize(cp.Constant(0.)), constraints)
    else:
        problem = cp.Problem(cp.Maximize(obj_cp), constraints)
    solver = getattr(cp, solver_name)
    problem.solve(solver=solver, verbose=verbose)

    # Report results
    info = {
        'problem': problem,
        'P': P,
        'constraints': constraints,
    }
    print('status', problem.status)
    if check_feasibility:
        # If solver shows problem is infeasible for adversary, this is a certificate
        obj_value = problem.status == 'infeasible'
    else:
        obj_value = obj_cp.value + obj_const if obj_cp.value is not None else -99999
    return obj_value, info