Example #1
0
def test_one_point():
    """Point-wise test case with only one constraint.

    This leads to weight bounds that are unbounded-on-one-side.
    """
    # Where the weight is too big.
    network = Network([
        FullyConnectedLayer(np.eye(2), np.zeros(shape=(2,))),
        ReluLayer(),
        FullyConnectedLayer(np.array([[1.0, 0.0], [1.0, 0.0]]),
                            np.array([0.0, 0.0])),
    ])
    layer_index = 0
    points = [[1.0, 0.5]]
    labels = [1]
    patcher = NetPatcher(network, layer_index, points, labels)
    patched = patcher.compute(steps=1)
    assert np.argmax(patched.compute(points)) == 1

    # Where the weight is too small.
    network = Network([
        FullyConnectedLayer(np.eye(2), np.array([0.0, 0.0])),
        ReluLayer(),
        FullyConnectedLayer(np.array([[1.0, 0.0], [1.0, 0.0]]), np.array([0.0, 2.0])),
    ])
    layer_index = 0
    points = [[1.0, 0.0]]
    labels = [0]
    patcher = NetPatcher(network, layer_index, points, labels)
    patched = patcher.compute(steps=1)
    assert np.argmax(patched.compute(points)) == 0
Example #2
0
def test_from_planes():
    """Test case to load key points from a set of labeled 2D polytopes.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([
        ReluLayer(),
        FullyConnectedLayer(np.eye(2), np.zeros(shape=(2,)))
    ])
    layer_index = 1
    planes = [
        np.array([[-1.0, -3.0], [-0.5, -3.0], [-0.5, 9.0], [-1.0, 9.0]]),
        np.array([[8.0, -2.0], [16.0, -2.0], [16.0, 6.0], [8.0, 6.0]]),
    ]
    labels = [1, 0]
    patcher = NetPatcher.from_planes(network, layer_index, planes, labels)
    assert patcher.network is network
    assert patcher.layer_index is layer_index
    assert len(patcher.inputs) == (4 + 2) + (4 + 2)
    true_key_points = list(planes[0])
    true_key_points += [np.array([-1.0, 0.0]), np.array([-0.5, 0.0])]
    true_key_points += list(planes[1])
    true_key_points += [np.array([8.0, 0.0]), np.array([16.0, 0.0])]
    true_labels = ([1] * 6) + ([0] * 6)
    for true_point, true_label in zip(true_key_points, true_labels):
        try:
            i = next(i for i, point in enumerate(patcher.inputs)
                     if np.allclose(point, true_point))
        except StopIteration:
            assert False
        assert true_label == patcher.labels[i]
Example #3
0
def test_compute_from_syrenn():
    """Tests the it works given an arbitrary network and planes.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([ReluLayer()])
    planes = [np.array([[1.0, 2.0], [3.0, 4.0], [1.0, 2.0]]),
              np.array([[3.0, 2.0], [7.0, 4.0], [5.0, 2.0]])]
    transformed = network.transform_planes(planes, True, True)

    classifier = PlanesClassifier.from_syrenn(transformed)
    assert classifier.partially_computed

    classified = classifier.compute()
    assert len(classified) == len(planes)
    regions, labels = classified[0]
    assert np.allclose(regions, planes[0])
    assert np.allclose(labels, [1])
    regions, labels = classified[1]
    assert np.allclose(regions, planes[1])
    assert np.allclose(labels, [0])

    # Ensure it doesn't re-compute things it already knows.
    assert classifier.compute() is classified
Example #4
0
def test_from_spec():
    """Test case to load key points from a spec function.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([
        ReluLayer(),
        FullyConnectedLayer(np.eye(2), np.zeros(shape=(2,)))
    ])
    layer_index = 1
    region_of_interest = np.array([
        [0.5, -3.0],
        [1.0, -3.0],
        [1.0, 9.0],
        [0.5, 9.0],
    ])
    spec_fn = lambda i: np.isclose(i[:, 0], 1.0).astype(np.float32)
    patcher = NetPatcher.from_spec_function(network, layer_index,
                                            region_of_interest, spec_fn)
    assert patcher.network is network
    assert patcher.layer_index is layer_index
    assert len(patcher.inputs) == (4 + 2)
    true_key_points = list(region_of_interest)
    true_key_points += [np.array([0.5, 0.0]), np.array([1.0, 0.0])]
    true_labels = [0, 1, 1, 0, 0, 1]
    for true_point, true_label in zip(true_key_points, true_labels):
        try:
            i = next(i for i, point in enumerate(patcher.inputs)
                     if np.allclose(point, true_point))
        except StopIteration:
            assert False
        assert true_label == patcher.labels[i]
Example #5
0
def test_compute_from_network():
    """Tests the it works given an arbitrary network and planes.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")
    network = Network([ReluLayer()])
    planes = [np.array([[1.0, 2.0], [3.0, 4.0], [1.0, 2.0]]),
              np.array([[3.0, 2.0], [7.0, 4.0], [5.0, 2.0]])]
    classifier = PlanesClassifier(network, planes, preimages=True)

    classifier.partial_compute()
    assert classifier.partially_computed
    transformed = network.transform_planes(planes, True, True)
    assert all(all(np.allclose(actual_polytope[0], truth_polytope[0]) and
                   np.allclose(actual_polytope[1], truth_polytope[1])
                   for actual_polytope, truth_polytope in zip(actual, truth))
               for actual, truth in zip(classifier.transformed_planes,
                                        transformed))

    classified = classifier.compute()
    assert len(classified) == len(planes)
    regions, labels = classified[0]
    assert np.allclose(regions, planes[0])
    assert np.allclose(labels, [1])
    regions, labels = classified[1]
    assert np.allclose(regions, planes[1])
    assert np.allclose(labels, [0])

    # Ensure it doesn't re-compute things it already knows.
    assert classifier.compute() is classified
Example #6
0
def test_compute_from_network():
    """Tests the it works given an arbitrary network and lines.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([ReluLayer()])
    lines = [(np.array([0.0, 1.0]), np.array([0.0, -1.0])),
             (np.array([2.0, 3.0]), np.array([4.0, 3.0]))]
    classifier = LinesClassifier(network, lines, preimages=True)

    classifier.partial_compute()
    exactlines = network.exactlines(lines, True, True)
    assert all(
        np.allclose(actual[0], truth[0]) and np.allclose(actual[1], truth[1])
        for actual, truth in zip(classifier.transformed_lines, exactlines))

    classified = classifier.compute()
    assert len(classified) == len(lines)
    regions, labels = classified[0]
    assert np.allclose(regions,
                       [[[0.0, 1.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, -1.0]]])
    assert np.allclose(labels, [1, 0])
    regions, labels = classified[1]
    assert np.allclose(regions,
                       [[[2.0, 3.0], [3.0, 3.0]], [[3.0, 3.0], [4.0, 3.0]]])
    assert np.allclose(labels, [1, 0])

    # Ensure it doesn't re-compute things it already knows.
    assert classifier.compute() is classified
Example #7
0
    def layer_jacobian(self, points, representatives):
        """Computes the Jacobian of the FULLY CONNECTED layer parameters.

        Returns (n_points, n_outputs, [weight_shape])

        Basically, we assume WLOG that the layer is the first layer then we get
        something like for each point:
        B_nB_{n-1}...B_1Ax

        For each point we can collapse B_n...B_1 into a matrix B of shape
        (n_outputs, n_A_outputs)
        then we compute the einsum with x to get
        (n_outputs, n_A_outputs, n_A_inputs)
        which is what we want.
        """
        pre_network = Network(self.network.layers[:self.layer_index])
        points = pre_network.compute(points)
        n_points, A_in_dims = points.shape

        representatives = pre_network.compute(representatives)
        representatives = self.network.layers[self.layer_index].compute(representatives)
        n_points, A_out_dims = representatives.shape

        post_network = Network(self.network.layers[(self.layer_index+1):])
        # (n_points, A_out, A_out)
        jacobian = np.repeat([np.eye(A_out_dims)], n_points, axis=0)
        # (A_out, n_points, A_out)
        jacobian = jacobian.transpose((1, 0, 2))
        for layer in post_network.layers:
            if isinstance(layer, LINEAR_LAYERS):
                if isinstance(layer, ConcatLayer):
                    assert not any(isinstance(input_layer, ConcatLayer)
                                   for input_layer in layer.input_layers)
                    assert all(isinstance(input_layer, LINEAR_LAYERS)
                               for input_layer in layer.input_layers)
                representatives = layer.compute(representatives)
                jacobian = jacobian.reshape((A_out_dims * n_points, -1))
                jacobian = layer.compute(jacobian, jacobian=True)
                jacobian = jacobian.reshape((A_out_dims, n_points, -1))
            elif isinstance(layer, ReluLayer):
                # (n_points, running_dims)
                zero_indices = (representatives <= 0)
                representatives[zero_indices] = 0.
                # (A_out, n_points, running_dims)
                jacobian[:, zero_indices] = 0.
            elif isinstance(layer, HardTanhLayer):
                big_indices = (representatives >= 1.)
                small_indices = (representatives <= -1.)
                np.clip(representatives, -1.0, 1.0, out=representatives)
                jacobian[:, big_indices] = 0.
                jacobian[:, small_indices] = 0.
            else:
                raise NotImplementedError
        # (A_out, n_points, n_classes) -> (n_points, n_classes, A_out)
        B = jacobian.transpose((1, 2, 0))
        # (n_points, n_classes, n_A_in, n_A_out)
        C = np.einsum("nco,ni->ncio", B, points)
        return C, B
Example #8
0
def test_compute_from_exactline_error():
    """Tests that it requires the plural exactline*s*(), not the singular.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([ReluLayer()])
    exactline = network.exactline([-1.0, 1.0], [1.0, 0.0], True, True)

    try:
        classifier = LinesClassifier.from_exactlines(exactline)
        assert False
    except TypeError as e:
        pass
Example #9
0
    def linearize_around(self, inputs, network, layer_index):
        """Linearizes a network before/after a certain layer.

        We return a 3-tuple, (pre_lins, mid_inputs, post_lins) satisfying:

        1) For all x in the same linear region as x_i:
            Network(x) = PostLin_i(Layer_l(PreLin_i(x)))
        2) For all x_i:
            mid_inputs_i = PreLin_i(x_i)

        pre_lins and post_lins are lists of FullyConnectedLayers, one per
        input, and mid_inputs is a Numpy array.
        """
        pre_network = Network(network.layers[:layer_index])
        pre_linearized = self.linearize_network(inputs, pre_network)

        mid_inputs = pre_network.compute(inputs)
        pre_post_inputs = network.layers[layer_index].compute(mid_inputs)
        post_network = Network(network.layers[(layer_index + 1):])
        post_linearized = self.linearize_network(pre_post_inputs, post_network)
        return pre_linearized, mid_inputs, post_linearized
Example #10
0
def test_compute_from_network():
    """Tests the it works given an arbitrary network and lines.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")
    network = Network([ReluLayer()])
    lines = [(np.array([0.0, 1.0]), np.array([0.0, -1.0])),
             (np.array([2.0, 3.0]), np.array([4.0, 3.0]))]

    helper = IntegratedGradients(network, lines)
    helper.partial_compute()
    assert len(helper.exactlines) == len(lines)
    assert np.allclose(helper.exactlines[0], [0.0, 0.5, 1.0])
    assert np.allclose(helper.exactlines[1], [0.0, 1.0])
    assert helper.n_samples == [2, 1]

    attributions_0 = helper.compute_attributions(0)
    assert len(attributions_0) == len(lines)
    # The second component doesn't affect the 0-label at all, and the first
    # component is 0 everywhere, so we have int_0^0 0.0dx = 0.0
    assert np.allclose(attributions_0[0], [0.0, 0.0])
    # Gradient of the 0-label is (1.0, 0.0) everywhere since its in the first
    # orthant, and the partition has a size of (2.0, 0.0), so the IG is (2.0,
    # 0.0).
    assert np.allclose(attributions_0[1], [2.0, 0.0])

    attributions_1 = helper.compute_attributions(1)
    assert len(attributions_1) == len(lines)
    # The gradient in the first partition is (0.0, 1.0) with a size of (0.0,
    # -1.0) -> contribution of (0.0, -1.0). In the second partition, (0.0,
    # 0.0)*(0.0, -1.0) = (0.0, 0.0).
    assert np.allclose(attributions_1[0], [0.0, -1.0])
    # Gradient is (0, 1) and the size is (2, 0) so IG is (0, 0).
    assert np.allclose(attributions_1[1], [0.0, 0.0])

    attributions_1_re = helper.compute_attributions(1)
    # Ensure it doesn't re-compute the attributions.
    assert attributions_1 is attributions_1_re
Example #11
0
def test_compute_from_exactlines():
    """Tests the it works given pre-transformed lines.
    """
    if not Network.has_connection():
        pytest.skip("No server connected.")

    network = Network([ReluLayer()])
    lines = [(np.array([0.0, 1.0]), np.array([0.0, -1.0])),
             (np.array([2.0, 3.0]), np.array([4.0, 3.0]))]
    exactlines = network.exactlines(lines, True, True)

    classifier = LinesClassifier.from_exactlines(exactlines)
    classified = classifier.compute()

    assert len(classified) == len(lines)
    regions, labels = classified[0]
    assert np.allclose(regions,
                       [[[0.0, 1.0], [0.0, 0.0]], [[0.0, 0.0], [0.0, -1.0]]])
    assert np.allclose(labels, [1, 0])
    regions, labels = classified[1]
    assert np.allclose(regions,
                       [[[2.0, 3.0], [3.0, 3.0]], [[3.0, 3.0], [4.0, 3.0]]])
    assert np.allclose(labels, [1, 0])
Example #12
0
def test_already_good():
    """Point-wise test case where all constraints are already met.

    We want to make sure that it doesn't change any weights unnecessarily.
    """
    network = Network([
        FullyConnectedLayer(np.eye(2), np.zeros(shape=(2,))),
        ReluLayer(),
    ])
    layer_index = 0
    points = [[1.0, 0.5], [2.0, -0.5], [4.0, 5.0]]
    labels = [0, 0, 1]
    patcher = NetPatcher(network, layer_index, points, labels)
    patched = patcher.compute(steps=1)
    patched_layer = patched.value_layers[0]
    assert np.allclose(patched_layer.weights.numpy(), np.eye(2))
    assert np.allclose(patched_layer.biases.numpy(), np.zeros(shape=(2,)))
Example #13
0
def test_optimal():
    """Point-wise test case that the greedy algorithm can solve in 1 step.

    All it needs to do is triple the second component.
    """
    network = Network([
        FullyConnectedLayer(np.eye(2), np.zeros(shape=(2, ))),
        ReluLayer(),
    ])
    layer_index = 0
    points = [[1.0, 0.5], [2.0, -0.5], [5.0, 4.0]]
    labels = [1, 0, 1]
    patcher = ProvableRepair(network, layer_index, points, labels)
    patched = patcher.compute()
    assert patched.differ_index == 0
    assert np.count_nonzero(
        np.argmax(patched.compute(points), axis=1) == labels) == 3
Example #14
0
    def compute(self):
        network = Network.deserialize(self.network.serialize())

        if self.layer is not None:
            for param in self.get_parameters(network):
                param.requires_grad = False

        parameters = self.get_parameters(network, self.layer)
        for param in parameters:
            param.requires_grad = True

        if self.norm_objective:
            original_parameters = [param.detach().clone() for param in parameters]
            for param in original_parameters:
                # Do not train these, they're just for reference.
                param.requires_grad = False

        start = timer()
        optimizer = torch.optim.SGD(parameters, lr=self.lr, momentum=self.momentum)
        indices = list(range(len(self.inputs)))
        random.seed(24)
        self.epoched_out = None
        holdout_n_correct = self.holdout_n_correct(network)
        for epoch in range(self.epochs):
            # NOTE: In the paper, we checked this _after_ the inner loop. It
            # should only make a difference in the case where the network
            # already met the specification, so should make no difference to
            # the results.
            if self.auto_stop and self.is_done(network):
                self.maybe_print("100% training accuracy!")
                self.epoched_out = False
                break
            random.shuffle(indices)
            losses = []
            for batch_start in range(0, len(self.inputs), self.batch_size):
                batch = slice(batch_start, batch_start + self.batch_size)
                inputs = torch.tensor([self.inputs[i] for i in indices[batch]])
                labels = torch.tensor([self.labels[i] for i in indices[batch]])
                # representatives = [self.representatives[i] for i in indices[batch]]

                optimizer.zero_grad()
                output = network.compute(inputs)
                loss = torch.nn.functional.cross_entropy(output, labels)
                if self.norm_objective:
                    for curr_param, og_param in zip(parameters, original_parameters):
                        delta = (curr_param - og_param).flatten()
                        loss += torch.linalg.norm(delta, ord=2)
                        loss += torch.linalg.norm(delta, ord=float("inf"))
                loss.backward()
                losses.append(loss)
                optimizer.step()
            self.maybe_print("Average Loss:", torch.mean(torch.tensor(losses)))
            if self.holdout_set is not None:
                new_holdout_n_correct = self.holdout_n_correct(network)
                self.maybe_print("New holdout n correct:", new_holdout_n_correct, "/", len(self.holdout_set))
                if new_holdout_n_correct < holdout_n_correct:
                    self.maybe_print("Holdout accuracy dropped, ending!")
                    break
                holdout_n_correct = new_holdout_n_correct
        else:
            self.epoched_out = True
        for param in parameters:
            param.requires_grad = False
        self.timing = dict({
            "total": timer() - start,
        })
        return network