def add_backprop_linear_layer(layer_no, squire, input_key, output_key): """ Encodes the backprop version of a linear layer """ network = squire.network model = squire.model output_vars = [] output_var_namer = utils.build_var_namer(output_key) fc_layer = network.fcs[layer_no] fc_weight = utils.as_numpy(fc_layer.weight) backprop_bounds = squire.get_ith_backward_box(layer_no) input_vars = squire.get_vars(input_key) for i in range(fc_layer.in_features): output_var = model.addVar(lb=backprop_bounds[i][0], ub=backprop_bounds[i][1], name=output_var_namer(i)) weight_col = fc_weight[:, i] model.addConstr(output_var == gb.LinExpr(weight_col, input_vars)) output_vars.append(output_var) model.update() squire.set_vars(output_key, output_vars)
def __init__(self, dimension, x): """ For now just set the dimension of the ambient space and the central point (which can be none)""" self.dimension = dimension if x is None: self.x = None else: self.x = utils.as_numpy(x).reshape(-1) # Things we'll set later self.box_low = None self.box_high = None self.l2_radius = None self.linf_radius = None # Original box constraints to be kept separate from those generated # by upper bounds. self.original_box_low = None self.original_box_high = None self.unmodified_bounds_low = None self.unmodified_bounds_high = None
def _setup_state(self, x, lp_norm, potential): """ Sets up the state to be used on a per-run basis Shared between min_dist_multiproc and decision_problem_multiproc Sets instance variables and does asserts """ assert lp_norm in ['l_2', 'l_inf'] self.lp_norm = lp_norm self.x = x self.x_np = utils.as_numpy(x) self.true_label = int(self.net(x).max(1)[1].item()) dist_selector = { 'l_2': Face.l2_dist_gurobi, 'l_inf': Face.linf_dist_gurobi } self.lp_dist = dist_selector[self.lp_norm] self.domain = Domain(x.numel(), x) if self.hyperbox_bounds is not None: self.domain.set_original_hyperbox_bound(*self.hyperbox_bounds) self._update_dead_constraints() assert potential in ['lp', 'lipschitz'] if self.net.layer_sizes[-1] > 2 and potential == 'lipschitz': raise NotImplementedError( "Lipschitz potential buggy w/ >2 classes!")
def _backprop_linear_layer(self, fc_layer, input_lows, input_highs): """ Subroutine to handle the backprop of a linear layer: i.e., given a function defined as y=Wx + b and some interval on the value of df/dy, want intervals for df/dx ARGS: layer_no: nn.Linear object - object we are backpropping through input_lows: np.Array - array for the lower bounds of the input gradient input_highs: np.Array - array for the upper bounds of the input gradient RETURNS: output_lows : np.Array - array for the lower bounds on the output gradients output_highs : np.Array - tensor for the high bounds on the output gradients """ weight_t = utils.as_numpy(fc_layer.weight.t()) midpoint = (input_lows + input_highs) / 2.0 radius = (input_highs - input_lows) / 2.0 new_midpoint = weight_t.dot(midpoint) new_radius = np.abs(weight_t).dot(radius) output_lows = new_midpoint - new_radius output_highs = new_midpoint + new_radius return output_lows, output_highs
def __init__(self, values): """ Values gets stored as its numpy array of type np.int8 where all values are -1, 0, 1 (0 <=> ? <=> {-1, +1}) """ self.values = utils.as_numpy(values).astype(np.int8) self.dimension = len(self.values)
def contains(self, point): """ Returns True if the provided point is in the hyperbox """ point = utils.as_numpy(point) assert len(point) == self.dimension return all([lo_i <= point_i <= hi_i for (point_i, (lo_i, hi_i)) in zip(point, self)])
def get_sign_configs(self, x): preacts = [ utils.as_numpy(_.squeeze()) for _ in self(x.view(-1), return_preacts=True)[:-1] ] return [_ > 0 for _ in preacts]
def Lin_Lip_verify_modded(network, network_name, images, labels, norm="2", warmup="True", method="ours", targettype="untargeted", lipsbnd='disable', LP=False, LPFULL=False, eps=0.2, lipsteps=30, steps=15): """ :param network: "PLNN network object" :param images: "images to verify in format [N, 1, n1, n2] (for MNIST n1=n2=28)" :param labels: "labels of images. list of ints" :param norm: {"i", "1", "2"} :param warmup: {True, False} warm up before the first iteration :param method: {"ours", "spectral", "naive"} "ours": our proposed bound, "spectral": spectral norm bounds, "naive": naive bound' :param targettype: {"untargeted", "top2"} "tops2: 2nd highest prediction label" :param lipsbnd: {"disable", "fast", "naive", "both"} compute Lipschitz bound, after using some method to compute neuron lower/upper bounds :param LP: {True, False} use LP to get bounds for final output :param LPFULL: {True, False} use FULL LP to get bounds for output :param eps: "initial guess for epsilon for verification" :param lipsteps: "number of steps to use in lipschitz bound' :param steps: "how many steps to binary search" :return: """ #TODO: eps? tf.reset_default_graph() gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=0.33, allow_growth=True) sess = tf.Session(config=tf.ConfigProto(gpu_options=gpu_options)) with tf.Session() as sess: sys.stdout.flush() random.seed(1215) np.random.seed(1215) tf.set_random_seed(1215) ################################################################################## # # # Network Model + Results Loading # # # ################################################################################## # ===================== # Convert Network # ===================== layer_sizes = network.layer_sizes num_classes = layer_sizes[-1] params = layer_sizes[1:-1] restore = [None] for fc in network.fcs: weight = as_numpy(fc.weight).T bias = as_numpy(fc.bias) restore.append([weight, bias]) restore.append(None) model = NLayerModel_comparison(params, num_classes=num_classes, restore=restore) # FL_network = NLayerModel_comparison(params, session=sess) numlayer = len(params) + 1 # restore = 'models/mnist_2layer_relu_20_best' # params = [nhidden] * (numlayer - 1) # model = NLayerModel(params, restore=modelfile) # ======================================= # Convert Image Shapes + Create targets # ======================================= images_lin_lip = as_numpy(images.reshape(-1, 28, 28, 1)) true_labels = [] top2_targets = [] preds = [] for image, label in zip(images, labels): label_array = np.zeros([num_classes]) label_array[label] = 1 true_labels.append(label_array) pred = model.predict(tf.constant( as_numpy(image))) array = pred.eval()[0] preds.append(array) target_index = np.argsort(pred.eval()[0])[-2] top2_target = np.zeros([num_classes]) top2_target[target_index] = 1 top2_targets.append(top2_target) true_labels = np.asarray(true_labels) true_ids = [num for num in range(0, np.shape(images)[0] + 1)] inputs = images_lin_lip if targettype == "untargeted": targets = true_labels targeted = False elif targettype == "top2": targets = np.asarray(top2_targets) targeted = True else: raise NotImplementedError # ===================================== sys.stdout.flush() random.seed(1215) np.random.seed(1215) tf.set_random_seed(1215) # the weights and bias are saved in lists: weights and bias # weights[i-1] gives the ith layer of weight and so on weights, biases = get_weights_list(model) preds = model.model.predict(inputs) Nsamp = 0 r_sum = 0.0 r_gx_sum = 0.0 # warmup if warmup: print("warming up...") sys.stdout.flush() if method == "spectral": robustness_gx = spectral_bound(weights, biases, 0, 1, inputs[0], preds[0], numlayer, norm, not targeted) else: compute_worst_bound(weights, biases, 0, 1, inputs[0], preds[0], numlayer, norm, 0.01, method, lipsbnd, LP, LPFULL, not targeted) print("starting robustness verification on {} images!".format(len(inputs))) sys.stdout.flush() sys.stderr.flush() total_time_start = time.time() min_dists = [] times = [] for i in range(len(inputs)): Nsamp += 1 p = norm # p = "1", "2", or "i" predict_label = np.argmax(true_labels[i]) target_label = np.argmax(targets[i]) start = time.time() # Spectral bound: no binary search needed if method == "spectral": robustness_gx = spectral_bound(weights, biases, predict_label, target_label, inputs[i], preds[i], numlayer, p, not targeted) # compute worst case bound # no need to pass in sess, model and data # just need to pass in the weights, true label, norm, x0, prediction of x0, number of layer and eps elif lipsbnd != "disable": # You can always use the "multi" version of Lipschitz bound to improve results (about 30%). robustness_gx = compute_worst_bound_multi(weights, biases, predict_label, target_label, inputs[i], preds[i], numlayer, p, eps, lipsteps, method, lipsbnd, not targeted) eps = eps # if initial eps is too small, then increase it if robustness_gx == eps: while robustness_gx == eps: eps = eps * 2 print("==============================") print("increase eps to {}".format(eps)) print("==============================") robustness_gx = compute_worst_bound_multi(weights, biases, predict_label, target_label, inputs[i], preds[i], numlayer, p, eps, lipsteps, method, lipsbnd, not targeted) # if initial eps is too large, then decrease it elif robustness_gx <= eps / 5: while robustness_gx <= eps / 5: eps = eps / 5 print("==============================") print("increase eps to {}".format(eps)) print("==============================") robustness_gx = compute_worst_bound_multi(weights, biases, predict_label, target_label, inputs[i], preds[i], numlayer, p, eps, lipsteps, method, lipsbnd, not targeted) else: gap_gx = 100 eps = eps eps_LB = -1 eps_UB = 1 counter = 0 is_pos = True is_neg = True # perform binary search eps_gx_UB = np.inf eps_gx_LB = 0.0 is_pos = True is_neg = True # eps = eps_gx_LB*2 eps = eps while eps_gx_UB - eps_gx_LB > 0.00001: gap_gx, _, _ = compute_worst_bound(weights, biases, predict_label, target_label, inputs[i], preds[i], numlayer, p, eps, method, "disable", LP, LPFULL, not targeted) print("[L2][binary search] step = {}, eps = {:.5f}, gap_gx = {:.2f}".format(counter, eps, gap_gx)) if gap_gx > 0: if gap_gx < 0.01: eps_gx_LB = eps break if is_pos: # so far always > 0, haven't found eps_UB eps_gx_LB = eps eps *= 10 else: eps_gx_LB = eps eps = (eps_gx_LB + eps_gx_UB) / 2 is_neg = False else: if is_neg: # so far always < 0, haven't found eps_LB eps_gx_UB = eps eps /= 2 else: eps_gx_UB = eps eps = (eps_gx_LB + eps_gx_UB) / 2 is_pos = False counter += 1 if counter >= steps: break robustness_gx = eps_gx_LB r_gx_sum += robustness_gx print( "[L1] seq = {}, id = {}, true_class = {}, target_class = {} robustness_gx = {:.5f}, avg_robustness_gx = {:.5f}," " time = {:.4f}, total_time = {:.4f}".format(i, true_ids[i], predict_label, target_label, robustness_gx, r_gx_sum / Nsamp, time.time() - start, time.time() - total_time_start)) times.append(time.time() - start) min_dists.append(robustness_gx) sys.stdout.flush() sys.stderr.flush() # ===================== # Save Output # ===================== output_dictionary = {'min_dists': min_dists, 'times': times} cwd = os.getcwd() import os.path as path norm_text = get_lp_text(norm) filename = cwd + "/Results/Lip_Lin_out_" + str(network_name[0:-4]) + '_' + norm_text + ".pkl" f = open(filename, 'wb') pickle.dump(output_dictionary, f) f.close() print('Saved Results @:') print(filename) print("[L0] avg robustness_gx = {:.5f}, numimage = {}, total_time = {:.4f}".format(r_gx_sum / Nsamp, Nsamp, time.time() - total_time_start)) sys.stdout.flush() sys.stderr.flush() sess.close() return min_dists, times
def l1_norm(cls, w): return np.linalg.norm(utils.as_numpy(w), 1)
def linf_norm(cls, w): # BE CAREFUL HERE -- IS THIS THE BEST WE CAN DO???? return np.linalg.norm(utils.as_numpy(w), np.inf) # IS THIS RIGHT???
def op_norm(cls, w): return np.linalg.norm(utils.as_numpy(w))
def display_images(self, include_diffs=True, include_pgd=False, figsize=(12, 12)): """ Shorthand method to display images found by GeoCert. Useful when doing things with GeoCert in jupyter notebooks ARGS: include_diffs : boolean - if True, we'll display the differences between the original and GeoCert image (diffs scaled up by 5x!) include_pgd : boolean - if True, we'll also display the image found by PGD (useful upper bound) RETURNS: None, but inline displays the images in the order [original | diff | geoCert | PGD] """ if self.best_ex is None: # No Geocert image => do nothing return # Build the display row of numpy elements original_np = utils.as_numpy(self.original.reshape( self.original_shape)) best_ex_np = utils.as_numpy(self.best_ex.reshape(self.original_shape)) display_row = [original_np, best_ex_np] label_row = ['original', 'geoCert'] if include_diffs: diff_np = np.clip(0.5 + (best_ex_np - original_np) * 5, 0.0, 1.0) display_row.insert(1, diff_np) label_row.insert(1, 'difference x5 (+0.5)') if include_pgd and self.adv_ex is not None: adv_ex_np = utils.as_numpy(self.adv_ex.reshape( self.original_shape)) display_row.append(adv_ex_np) label_row.append('PGD') # Make sure everything has three dimensions (CxHxW) # --- determine if grayscale or not grayscale = (original_np.squeeze().ndim == 2) if grayscale: num_channels = 1 imshow_kwargs = {'cmap': 'gray'} else: num_channels = 3 imshow_kwargs = {} # --- determine height/width h, w = original_np.squeeze().shape[-2:] for i in range(len(display_row)): display_row[i] = display_row[i].reshape((num_channels, h, w)) # Concatenate everything into a single row, and display # --- concatenate row together cat_row = np.concatenate(display_row, -1) if grayscale: cat_row = cat_row.squeeze() plt.figure(figsize=figsize, dpi=80, facecolor='w', edgecolor='k') plt.axis('off') plt.imshow(cat_row, **imshow_kwargs) # -- add labels underneath the images for label_idx, label in enumerate(label_row): x_offset = (0.33 + label_idx) * w plt.text(x_offset, h + 1, label) plt.show()
def linf_dist(self, x): """ Computes the l_infinity distance to point x using LP The linear program is as follows min_{t, v} t such that 1) A(x + v) <= b (<==>) Av <= b - Ax 2) -t <= v_i <= t (<==>) v_i - t <= 0 AND -v_i -t <= 0 3) (x + v) in Domain 5) t <= upper_bound 4) A_eq(x + v) = b_eq (<==>) so if A has shape (m,n) and domain constraints have shape (d, n) - (n + 1) variables - (m + 2n + d) inequality constraints - 1 equality constraint """ ###################################################################### # Setup things needed for linprog # ###################################################################### m, n = self.ub_A.shape zero_m_col = np.zeros((m, 1)) zero_n_col = np.zeros((n, 1)) x_row = utils.as_numpy(x).squeeze() x_col = x_row.reshape(n, 1) ###################################################################### # Build constraints row by row # ###################################################################### # VARIABLES ARE (v, t) a_constraints = [] b_constraints = [] # Constraint 1 has shape (m, n+1) constraint_1a = np.hstack((self.ub_A, zero_m_col)) constraint_1b = (self.ub_b - self.ub_A.dot(x_col).squeeze()).reshape(-1) assert constraint_1a.shape == (m, n + 1) assert constraint_1b.shape == (m, ) a_constraints.append(constraint_1a) b_constraints.append(constraint_1b) # Constraint 2 has shape (2n, n+1) constraint_2a_left = np.vstack((np.eye(n), -1 * np.eye(n))) constraint_2a = np.hstack((constraint_2a_left, -1 * np.ones( (2 * n, 1)))) constraint_2b = np.zeros(2 * n) assert constraint_2a.shape == (2 * n, n + 1) assert constraint_2b.shape == (2 * n, ) a_constraints.append(constraint_2a) b_constraints.append(constraint_2b) # Constraint 3 is added by the domain # If a full box, should have shape (2n, n + 1) d_a, d_b = self.domain.original_box_constraints() x_dx_low = x_row[self.domain.unmodified_bounds_low] x_dx_high = x_row[self.domain.unmodified_bounds_high] if d_a is not None: d_a_rows = d_a.shape[0] constraint_d_a = np.hstack((d_a, np.zeros((d_a_rows, 1)))) constraint_d_b = d_b + np.hstack((x_dx_low, -x_dx_high)) assert constraint_d_a.shape == (d_a_rows, n + 1) assert constraint_d_b.shape == (d_a_rows, ) a_constraints.append(constraint_d_a) b_constraints.append(constraint_d_b) # Constraint 4 is upper bound constraint if self.domain.linf_radius is not None: constraint_4a = np.zeros((1, n + 1)) constraint_4a[0][-1] = 1 constaint_4b = np.array(self.domain.linf_radius) a_constraints.append(constraint_4a) b_constraints.append(constaint_4b) # Constraint 5 is equality constraint, should have (1, n+1) a_eq = matrix(np.hstack((self.a_eq, np.zeros((1, 1))))) b_eq = matrix((self.b_eq - self.a_eq.dot(x_row)).astype(np.double)) # Objective should have length (n + 1) c = matrix(np.zeros(n + 1)) c[-1] = 1 ub_a = matrix(np.vstack(a_constraints)) ub_b = matrix(np.hstack(b_constraints)) start = time.time() cvxopt_out = solvers.lp(c, ub_a, ub_b, A=a_eq, b=b_eq, solver='mosek') end = time.time() # print("LP SOLVED IN %.03f" % (end -start)) if cvxopt_out['status'] == 'optimal': return cvxopt_out['primal objective'], \ (x_row + np.array(cvxopt_out['x'])[:-1].squeeze()) elif cvxopt_out['status'] in ['primal infeasible', 'unknown']: return None, None else: print("About to fail...") print("CVXOPT status", cvxopt_out['status']) raise Exception("LINF DIST FAILED?")
def build_mip_model(network, x, domain, pre_relu_bounds, true_label, problem_type, radius, lp_norm, timeout=None): """ ARGS: network : plnn.PLNN - network we wish to compute bounds on x : Tensor or numpy of the point we want to verify domain : domain.Domain - domain restricting the input domain pre_relu_bounds : list of np arrays of shape [#relu x 2] - holds the upper/lower bounds for each pre_relu (and the logits) true_label : int - what the model predicts for x problem_type: 'min_dist' or 'decision_problem' radius: float - l_inf ball that we are 'deciding' on for 'decision_problem' variant """ ########################################################################## # Step 1: setup things we'll need throughout # ########################################################################## num_pre_relu_layers = len(network.fcs) - 1 # - build model, add variables and box constraints model = gb.Model() # model.setParam('OutputFlag', False) # -- uncomment to suppress gurobi logs if timeout is not None: model.setParam('TimeLimit', timeout) model.setParam('Threads', 1) # Fair comparisions -- we only use 1 thread x_np = utils.as_numpy(x).reshape(-1) assert domain.box_low is not None assert domain.box_high is not None box_bounds = zip(domain.box_low, domain.box_high) x_namer = build_var_namer('x') x_vars = [model.addVar(lb=low, ub=high, name= x_namer(i)) for i, (low, high) in enumerate(box_bounds)] for (low, high), xvar in zip(box_bounds, x_vars): model.addConstr(xvar >= low) model.addConstr(xvar <= high) var_dict = {'x': x_vars} if lp_norm == 'l_2': diff_namer = build_var_namer('diff') diff_vars = [] for i in range(len(x_vars)): diff_var = model.addVar(lb=-gb.GRB.INFINITY, ub=gb.GRB.INFINITY, name=diff_namer(i)) diff_vars.append(diff_var) model.addConstr(diff_var == x_vars[i] - x_np[i]) l2_norm = gb.quicksum(diff_vars[i] * diff_vars[i] for i in range(len(diff_vars))) model.addConstr(l2_norm <= radius ** 2) # if l_2, and the radius is not None, add those constraints as well model.update() ########################################################################## # Step 2: Now add layers iteratively # ########################################################################## # all layers except the last final layer for i, fc_layer in enumerate(network.fcs[:-1]): # add linear layer if i == 0: input_name = 'x' else: input_name = 'fc_%s_post' % i pre_relu_name = 'fc_%s_pre' % (i + 1) post_relu_name = 'fc_%s_post' % (i + 1) relu_name = 'relu_%s' % (i + 1) add_linear_layer_mip(network, i, model, var_dict, input_name, pre_relu_name) add_relu_layer_mip(network, i, model, var_dict, pre_relu_name, pre_relu_bounds[i], post_relu_name, relu_name) # add the final fully connected layer output_var_name = 'logits' add_linear_layer_mip(network, len(network.fcs) - 1, model, var_dict, post_relu_name, output_var_name) ########################################################################## # Step 3: Add the 'adversarial' constraint and objective # ########################################################################## add_adversarial_constraint(model, var_dict[output_var_name], true_label, pre_relu_bounds[-1]) if lp_norm == 'l_inf': add_l_inf_obj(model, x_np, var_dict['x'], problem_type) else: add_l_2_obj(model, x_np, var_dict['x'], problem_type) model.update() return model