예제 #1
0
def test_net_load():
    """ Validate the loading of network such that it works on all 3k dataset samples. """
    ds = c.CollisionData.load(device)
    all_fpaths = list(Path(c.COLLISION_DIR).glob('*.rlv'))

    print('Evaluating accuracies for each saved network (supposed to be the same):')
    dom = DeeppolyDom()  # which dom doesn't matter
    for fpath in all_fpaths:
        net = c.CollisionMPNet.load(fpath, dom, device)
        outs = net(ds.inputs)
        predicted = outs.argmax(dim=-1)

        accuracy = len((predicted == ds.labels).nonzero(as_tuple=False)) / len(ds.labels)
        print(f'-- Accuracy for {fpath.absolute().name}: {accuracy}')
        assert accuracy >= 0.99

    # inspect unsafe ones from last run, since all networks are the same, it doesn't matter
    failed_bits = (predicted != ds.labels).nonzero(as_tuple=True)
    failed_ins = ds.inputs[failed_bits]
    failed_labels = ds.labels[failed_bits]
    failed_outs = outs[failed_bits]
    print(f'Failing on following inputs: {failed_ins}')
    print(f'should be label: {failed_labels}')
    print(f'with outpus: {failed_outs}')
    return
예제 #2
0
def test_acas_input_optimizable():
    dp = DeeppolyDom()
    vi = IntervalDom()

    _tc4(vi)
    _tc4(dp)
    return
예제 #3
0
def test_abstraction_soundness():
    """ Validate that networks and inputs are abstracted correctly using implemented abstract domains.
        However, it turns out that the usage of MaxPool1d makes it rather easy to trigger unsound violations.
        see more details in tests/test_maxpool_soundness.py.
    """
    all_fpaths = list(Path(c.COLLISION_DIR).glob('*.rlv'))
    dom = DeeppolyDom()
    net = c.CollisionMPNet.load(all_fpaths[0], dom,
                                device)  # all nets are the same, just use one

    unstable_cnts = 0
    print('Evaluating abstraction correctness for the saved network:')
    for fpath in all_fpaths:
        prop = c.CollisionProp.load(fpath, dom)
        lb, ub = prop.lbub(device)
        pts = sample_points(lb, ub, 100)
        out_conc = net(pts)

        e = dom.Ele.by_intvl(lb, ub)
        out_lb, out_ub = net(e).gamma()

        threshold = 1e-5  # allow some numerical error
        diff_lb = out_lb - out_conc
        diff_ub = out_conc - out_ub
        # print(diff_lb.max())
        # print(diff_ub.max())
        if diff_lb.max() >= threshold or diff_ub.max() >= threshold:
            unstable_cnts += 1
            print(
                f'Network {fpath.absolute().name} found unsound cases (due to numerical instability because of MaxPool?)'
            )
            # print(diff_lb.max(), diff_ub.max())
    print(f'-- Eventually, {unstable_cnts} networks seems to be unstable.')
    return
예제 #4
0
def test_net_impl_1():
    dp = DeeppolyDom()
    vi = IntervalDom()

    _tc1(vi)
    _tc1(dp)
    return
예제 #5
0
def test_load_and_save(ntimes: int = 10):
    dom = DeeppolyDom()
    prop = AcasProp.property2(dom)
    all_fpaths = prop.applicable_net_paths()

    for _ in range(ntimes):
        fpath = random.choice(all_fpaths)
        # print('Picked nnet file:', fpath)
        net1, mins1, maxs1 = AcasNet.load_nnet(fpath, dom, device)
        # print('Loaded:', net1)

        with tempfile.TemporaryDirectory() as tmpdir:
            tmp_path = Path(tmpdir, 'tmp.nnet')

            net1.save_nnet(tmp_path, mins1, maxs1)
            net2, mins2, maxs2 = AcasNet.load_nnet(tmp_path, dom, device)

            assert mins1 == mins2
            assert maxs1 == maxs2

            assert len(net1.all_linears) == len(net2.all_linears)
            for lin1, lin2 in zip(net1.all_linears, net2.all_linears):
                assert torch.allclose(lin1.weight.data, lin2.weight.data)
                assert torch.allclose(lin1.bias.data, lin2.bias.data)
    return
예제 #6
0
def test_acas_net_optimizable():
    dp = DeeppolyDom()
    vi = IntervalDom()

    _tc3(vi)
    _tc3(dp)
    return
예제 #7
0
def test_reluval_cex(nitems: int = 5):
    """ Try to call ReluVal and collect its CEX. Validate that things are working. """
    dom = DeeppolyDom()
    reluval = ReluVal()

    prop = AcasProp.property2(dom)
    lb, ub = prop.lbub()
    for npath in prop.applicable_net_paths()[:nitems]:
        print('Using network from path', npath)
        net, bound_mins, bound_maxs = AcasNet.load_nnet(npath, dom)
        cexs = reluval.verify(lb, ub, net, task_name='2')
        print(cexs)

        # validation
        for i in range(len(cexs)):
            print('------ Validating cex', i)
            cex = cexs[i:i + 1]
            cex = net.normalize_inputs(cex, bound_mins, bound_maxs)
            print('CEX:', cex)
            with torch.no_grad():
                out = net(cex)
            print('Concrete Outs:', out)
            assert out.argmax(dim=-1).item() == 0

            absin = dom.Ele.by_intvl(cex, cex)
            with torch.no_grad():
                absout = net(absin)
            print('Distance:', prop.safe_dist(absout))
            print('------')
    return
예제 #8
0
    def setup_rest(self, args: argparse.Namespace):
        """ Override this method to set up those not easily specified via command line arguments. """
        # validation
        assert not (args.no_pts and args.no_abs), 'training what?'

        args.dom = {
            'deeppoly': DeeppolyDom(),
            'interval': IntervalDom()
        }[args.dom]

        if args.use_scheduler:
            # having a scheduler does improve the accuracy quite a bit
            args.scheduler_fn = lambda opti: ReduceLROnPlateau(
                opti, factor=0.8, patience=10)
        else:
            args.scheduler_fn = lambda opti: None
        return
예제 #9
0
def test_violation_example():
    """ Demonstrate via a handcoded example. """
    dom = DeeppolyDom()
    err_eps = 1e-4

    in_neurons = 1
    fc1_neurons = 3
    kernel_size, stride = 2, 1  # stride must be < kernel size, so as to allow overlapping
    fc2_neurons = (fc1_neurons -
                   kernel_size) / stride + 1  # formula: (W - F + 2P) / S + 1
    assert int(fc2_neurons) == fc2_neurons
    fc2_neurons = int(fc2_neurons)
    out_neurons = 1

    lb = torch.tensor([[0.1]])
    ub = torch.tensor([[0.12]])
    pt = torch.tensor([[0.1010]])

    fc1 = dom.Linear(in_neurons, fc1_neurons)
    fc1.weight.data = torch.tensor([[0.9624], [-0.6785], [0.9087]])
    fc1.bias.data = torch.tensor([0.3255, 0.7965, 0.6321])

    relu = dom.ReLU()
    mp = dom.MaxPool1d(kernel_size=kernel_size, stride=stride)

    fc2 = dom.Linear(fc2_neurons, out_neurons, bias=False)
    fc2.weight.data = torch.tensor([-0.6859, -0.4253])

    def forward(x):
        x = fc1(x)
        x = relu(x)

        x = x.unsqueeze(dim=1)
        x = mp(x)
        x = x.squeeze(dim=1)

        x = fc2(x)
        return x

    print('in pt:', pt.squeeze().item())
    print('in lb:', lb.squeeze().item())
    print('in ub:', ub.squeeze().item())

    e = dom.Ele.by_intvl(lb, ub)
    out = forward(pt)
    out_e = forward(e)
    out_lb, out_ub = out_e.gamma()

    assert not (out_lb <= out + err_eps).all()
    print('out pt:', out.squeeze().item())
    print('out lb:', out_lb.squeeze().item())
    print('out ub:', out_ub.squeeze().item())
    return
예제 #10
0
def test_andprop_conjoin():
    """ Validate (manually..) that the AndProp is correct. """
    dom = DeeppolyDom()

    def _go(id):
        props = id.applicable_props(dom)
        ap = AndProp(props)

        print('-- For network', id)
        for p in props:
            print('-- Has', p.name)
            lb, ub = p.lbub()
            print('   LB:', lb)
            print('   UB:', ub)

        lb, ub = ap.lbub()
        print('-- All conjoined,', ap.name)
        print('   LB:', lb)
        print('   UB:', ub)
        print('   Labels:', ap.labels)
        print('Cnt:', len(lb))
        for i in range(len(lb)):
            print('  ', i, 'th piece, width:', ub[i] - lb[i],
                  f'area: {total_area(lb[[i]], ub[[i]]) :E}')
        print()
        return

    ''' <1, 1> is tricky, as it has many props;
        <1, 9> is special, as it is different from many others;
        Many others have prop1, prop2, prop3, prop4 would generate 3 pieces, in which prop1 and prop2 merged.
    '''
    # _go(AcasNetID(1, 1))
    # _go(AcasNetID(1, 9))
    # exit(0)

    for id in AcasNetID.all_ids():
        _go(id)

    print('XL: Go manually check the outputs..')
    return
예제 #11
0
파일: acas.py 프로젝트: shivamhanda/ART
            safe_ratio = len(
                safe_bits.nonzero(as_tuple=False)) / float(sample_size)
            print('Iter', i, ': Safe ratio for point outputs:', safe_ratio)
            if safe_ratio != 1.0:
                spurious_bits = ~safe_bits
                spurious_dist = safe_dist[spurious_bits]
                print(
                    '\t',
                    f'spurious dists: [ {spurious_dist.min()} ~ {spurious_dist.max()} ]'
                )
                print('\t', 'spurious dists:', spurious_dist)
                pass
        return

    for p in AcasProp.all_props(dom):
        print(f'===== For {p.name} =====')
        debug_unsafe_point(p)
        print('\n')
    return


if __name__ == '__main__':
    from diffabs import DeeppolyDom
    dom = DeeppolyDom()
    device = torch.device(
        'cuda') if torch.cuda.is_available() else torch.device('cpu')

    inspect_net_props(dom)
    # inspect_prop_pts(dom)
    pass
예제 #12
0
def test_sample_violation():
    """ It suffices to generate such violations by sampling.

        Having both MaxPool1d and FC2 is necessary to reproduce the bug. FC1 must have bias to easily reproduce the
        bug while FC2 may have no bias. Eps = 1e-4 is maximal magnitude to reproduce the bug because the weights and
        input bounds are initialized small.
    """
    dom = DeeppolyDom()
    err_eps = 1e-4

    in_neurons = 1
    fc1_neurons = 3
    kernel_size, stride = 2, 1
    out_neurons = 1

    lb = torch.tensor([[0.1]])
    ub = torch.tensor([[0.12]])

    # fixed
    fc2_neurons = (fc1_neurons - kernel_size) / stride + 1
    assert int(fc2_neurons) == fc2_neurons
    fc2_neurons = int(fc2_neurons)  # if using MaxPool1d
    # fc2_neurons = fc1_neurons  # if not using MaxPool1d

    fc1 = dom.Linear(in_neurons, fc1_neurons, bias=True)
    relu = dom.ReLU()
    mp = dom.MaxPool1d(kernel_size=kernel_size, stride=stride)
    fc2 = dom.Linear(fc2_neurons, out_neurons, bias=False)

    def forward(x):
        x = fc1(x)
        x = relu(x)

        x = x.unsqueeze(dim=1)
        x = mp(x)
        x = x.squeeze(dim=1)

        x = fc2(x)
        return x

    def reset_params():
        fc1.reset_parameters()
        fc2.reset_parameters()
        return

    k = 0
    while True:
        k += 1
        reset_params()

        pts = sample_points(lb, ub, 10000)
        e = dom.Ele.by_intvl(lb, ub)

        out_conc = forward(pts)
        out_lb, out_ub = forward(e).gamma()

        if (out_lb <= out_conc + err_eps).all():
            continue

        print(f'After {k} resets')
        print('LB <= conc?', (out_lb <= out_conc + err_eps).all())
        print('LB <= conc? detail', out_lb <= out_conc + err_eps)

        bits = out_conc + err_eps <= out_lb
        bits = bits.any(dim=1)  # any dimension violation is sufficient
        idxs = bits.nonzero().squeeze(dim=1)

        idx = idxs[0]  # just pick the 1st one to debug
        viol_in = pts[[idx]]
        viol_out = out_conc[[idx]]
        print('conc in:', viol_in.squeeze().item())
        print('out lb:', out_lb.squeeze().item())
        print('out ub:', out_ub.squeeze().item())
        print('conc out:', viol_out.squeeze().item())

        torch.save([fc1, fc2, viol_in], 'error_ctx.pt')
        break
    return
예제 #13
0
def test_reluplex(logs_dir: str = './reluplex_logs/'):
    """ Use property 2 logs from Reluplex for thoroughly examination.
        Need to run Reluplex's script 2 first and prepare the logs in proper location.
    :param logs_dir: directory of logs
    """
    if not Path(logs_dir).is_dir():
        print(f'{logs_dir} is not valid path for all logs.')
        return

    dom = DeeppolyDom()

    def validate_normalize(dnn: AcasNet, cex: _CEX, mins, maxs):
        """ Validate that the normalize() function works the same as NNET's normalizeInput/Output(). """
        ni = torch.tensor([cex.inputs])
        ni = dnn.normalize_inputs(ni, mins, maxs)
        ni = ni[0].detach().numpy()
        target = cex.inputs_normed
        err = _errors(ni, target)

        print('My PyTorch normalizing:', ni)
        print('NNET normalizing:', target)
        print('Error:', err)
        return err

    def validate_dnn(dnn, cex):
        """ Validate that the DNN outputs the same result as NNET does. """
        oi = torch.tensor([cex.inputs])
        oo = dnn(oi)
        oo = oo[0].detach().numpy()
        target = cex.nnet_outputs_normed
        err = _errors(oo, target)

        print('PyTorch :', oo)
        print('NNET C++:', target)
        print('Error:', err)
        return err

    def validate_cex(c, log_path):
        id = log_path[-7:-4]  # e.g. 2_1 for property2_stats_2_1.txt
        net_path = './acas_nets/ACASXU_run2a_%s_batch_2000.nnet' % id
        print(net_path)
        dnn, mins, maxs = AcasNet.load_nnet(net_path, dom)

        err1 = validate_normalize(dnn, c, mins, maxs)
        print('---')
        err2 = validate_dnn(dnn, c)
        print()
        return err1, err2

    reluplex = Reluplex()
    log_files = [
        fn for fn in os.listdir(logs_dir) if not fn.endswith('_summary.txt')
    ]
    all_cexs = []
    err1s = []
    err2s = []
    for log_name in log_files:
        with open(Path(logs_dir, log_name), 'r') as f:
            log_data = f.read()

        cexs = reluplex.extract(log_data)
        all_cexs.extend(cexs)

        for c in cexs:
            err1, err2 = validate_cex(c, log_name)
            err1s.append(err1)
            err2s.append(err2)
        pass

    print('Errors for normalization:')
    for err in err1s:
        print(err)
    print('Avg:', sum(err1s) / len(err1s))

    print('Errors for forward propagation:')
    for err in err2s:
        print(err)
    print('Avg:', sum(err2s) / len(err2s))

    print('Example:')
    print(all_cexs[0])
    return