def combine_batchnorm1d_net( network: networks.SequentialNetwork) -> networks.SequentialNetwork: """ Utilities function to combine all the FullyConnectedNodes followed by BatchNorm1DNodes in corresponding FullyConnectedNodes. Parameters ---------- network : SequentialNetwork Sequential Network of interest of which we want to combine the nodes. Return ---------- SequentialNetwork Corresponding Sequential Network with the combined nodes. """ py_net = cv.PyTorchConverter().from_neural_network(network) modules = [m for m in py_net.pytorch_network.modules()] modules = modules[1:] num_modules = len(modules) current_index = 0 new_modules = [] while current_index + 1 < num_modules: current_node = modules[current_index] next_node = modules[current_index + 1] if isinstance(current_node, ptl.Linear) and isinstance( next_node, ptl.BatchNorm1d): combined_node = combine_batchnorm1d(current_node, next_node) new_modules.append(combined_node) current_index = current_index + 1 elif isinstance(current_node, ptl.Linear): new_modules.append(copy.deepcopy(current_node)) elif isinstance(current_node, ptl.ReLU): new_modules.append(copy.deepcopy(current_node)) else: raise Exception( "Combine Batchnorm supports only ReLU, Linear and BatchNorm1D layers." ) current_index = current_index + 1 if not isinstance(modules[current_index], ptl.BatchNorm1d): new_modules.append(copy.deepcopy(modules[current_index])) temp_pynet = ptl.Sequential(py_net.pytorch_network.identifier, py_net.pytorch_network.input_id, new_modules) combined_pynet = cv.PyTorchNetwork(py_net.identifier, temp_pynet) combined_network = cv.PyTorchConverter().to_neural_network(combined_pynet) return combined_network
def input_search_lbl( net: networks.SequentialNetwork, ref_output: Tensor, starset_list: List[abst.StarSet], max_iter: int, threshold: float = 1e-5, optimizer_con: type = torch.optim.SGD, opt_params: dict = None, scheduler_con: type = torch.optim.lr_scheduler.ReduceLROnPlateau, scheduler_params: dict = None, max_iter_no_change: int = None): logger = logging.getLogger(logger_name) logger.info( f"LAYER-BY-LAYER SEARCH of Input Point corresponding to Output: {ref_output}." ) current_node = net.get_first_node() node_list = [] while current_node is not None: node_list.append(current_node) current_node = net.get_next_node(current_node) node_list.reverse() starset_list.reverse() temp_ref_output = ref_output for i in range(len(node_list)): temp_net = networks.SequentialNetwork("temp", "temp") temp_net.add_node(copy.deepcopy(node_list[i])) temp_start_input = list(starset_list[i].stars)[0].get_samples(1)[0] temp_start_input = temp_start_input.squeeze() temp_ref_output = temp_ref_output.squeeze() logger.info( f"ANALYZING LAYER: {node_list[i].identifier}. Starting Input = {temp_start_input}, " f"Reference Output = {temp_ref_output}") temp_correct, temp_current_input, temp_current_output = input_search( temp_net, temp_ref_output, temp_start_input, max_iter, threshold, optimizer_con, opt_params, scheduler_con, scheduler_params, max_iter_no_change) logger.info( f"ENDED LAYER SEARCH. FOUND = {temp_correct}, INPUT = {temp_current_input}, " f"OUTPUT = {temp_current_output}") if not temp_correct: logger.info(f"Search Failed at layer: {node_list[i].identifier}") return False, None, None temp_ref_output = temp_current_input py_net = cv.PyTorchConverter().from_neural_network(net).pytorch_network py_current_input = torch.from_numpy(temp_current_input) py_current_output = py_net(py_current_input) return temp_correct, py_current_input.detach().numpy( ), py_current_output.detach().numpy()
def search_cloud(net: networks.NeuralNetwork, ref_output: Tensor, start_input: Tensor, num_samples: int, scale: Tensor): py_net = cv.PyTorchConverter().from_neural_network(net).pytorch_network py_ref_output = torch.from_numpy(ref_output).squeeze() py_current_input = torch.from_numpy(start_input).squeeze() py_current_output = py_net(py_current_input) best_dist = torch.dist(py_ref_output, py_current_output, p=2) current_input = start_input best_sample = start_input for i in range(num_samples): sample = np.random.normal(loc=current_input, scale=scale, size=current_input.shape) py_sample = torch.from_numpy(sample).squeeze() py_output = py_net(py_sample) temp_dist = torch.dist(py_ref_output, py_output, p=2) if temp_dist.item() < best_dist.item(): best_sample = sample best_dist = temp_dist current_input = best_sample current_output = py_net( torch.from_numpy(current_input).squeeze()).detach().numpy() current_output = np.expand_dims(current_output, axis=1) current_dist = best_dist.item() return current_input, current_output, current_dist
def train(self, network: networks.NeuralNetwork, dataset: datasets.Dataset) -> networks.NeuralNetwork: """ Train the neural network of interest using the training strategy SGD Training. Parameters ---------- network : NeuralNetwork The neural network to train. dataset : Dataset The dataset to use for the training of the network. Returns ---------- NeuralNetwork The Neural Network resulting from the application of the training strategy to the original network. """ pytorch_converter = cv.PyTorchConverter() py_net = pytorch_converter.from_neural_network(network) py_net = self.__training(py_net, dataset) network.alt_rep_cache.clear() network.alt_rep_cache.append(py_net) network.up_to_date = False return network
def test(self, network: networks.NeuralNetwork, dataset: datasets.Dataset) -> float: pytorch_converter = cv.PyTorchConverter() py_net = pytorch_converter.from_neural_network(network) measure = self.__testing(py_net, dataset) return measure
def compute_saliency(net: networks.NeuralNetwork, ref_input: Tensor): class BackHook: def __init__(self, module: torch.nn.Module, backward=True): if backward: self.hook = module.register_backward_hook(self.hook_fn) else: self.hook = module.register_forward_hook(self.hook_fn) self.m_input = None self.m_output = None def hook_fn(self, module, m_input, m_output): self.m_input = m_input self.m_output = m_output def close(self): self.hook.remove() py_net = cv.PyTorchConverter().from_neural_network(net).pytorch_network # We register the hooks on the modules of the networks backward_hooks = [BackHook(layer) for layer in py_net.modules()] forward_hooks = [BackHook(layer, False) for layer in py_net.modules()] ref_input = torch.from_numpy(ref_input) ref_input.requires_grad = True out = py_net(ref_input) i = 0 print("FORWARD HOOKS") for m in py_net.modules(): hook = forward_hooks[i] print(m) print("INPUT") print(hook.m_input) print("OUTPUT") print(hook.m_output) i = i + 1 for k in range(len(out)): print(f"Variable {k} of output") out = py_net(ref_input) out[k].backward(retain_graph=True) print("INPUT GRAD:" + f"{ref_input.grad}") i = 0 for m in py_net.modules(): hook = backward_hooks[i] print(m) print("INPUT") print(hook.m_input[0]) print("OUTPUT") print(hook.m_output[0]) i = i + 1 print(out) ref_input[0] = ref_input[0] + 10 out = py_net(ref_input) print(out)
def train(self, network: networks.NeuralNetwork, dataset: datasets.Dataset) -> networks.NeuralNetwork: pytorch_converter = cv.PyTorchConverter() py_net = pytorch_converter.from_neural_network(network) py_net = self.__training(py_net, dataset) network.alt_rep_cache.clear() network.alt_rep_cache.append(py_net) network.up_to_date = False return network
def net_update(network: networks.NeuralNetwork) -> networks.NeuralNetwork: if not network.up_to_date: for alt_rep in network.alt_rep_cache: if alt_rep.up_to_date: if isinstance(alt_rep, cv.ONNXNetwork): return cv.ONNXConverter().to_neural_network(alt_rep) elif isinstance(alt_rep, cv.PyTorchNetwork): return cv.PyTorchConverter().to_neural_network(alt_rep) else: raise NotImplementedError else: return network
def prune(self, network: networks.NeuralNetwork, dataset: datasets.Dataset) -> networks.NeuralNetwork: """ Prune the neural network of interest using the pruning strategy Weight Pruning. Parameters ---------- network : NeuralNetwork The neural network to prune. dataset : Dataset The dataset to use for the pre-training and fine-tuning procedure. Returns ---------- NeuralNetwork The Neural Network resulting from the application of the pruning strategy to the original network. """ if self.training_strategy is not None and self.pre_training: fine_tuning = self.training_strategy.fine_tuning self.training_strategy.fine_tuning = False network = self.training_strategy.train(network, dataset) self.training_strategy.fine_tuning = fine_tuning pytorch_converter = cv.PyTorchConverter() py_net = pytorch_converter.from_neural_network(network) py_net = self.__pruning(py_net) network.alt_rep_cache.clear() network.alt_rep_cache.append(py_net) network.up_to_date = False if self.training_strategy is not None and self.training_strategy.fine_tuning: old_l1_decay = self.training_strategy.l1_decay old_batchnorm_decay = self.training_strategy.batchnorm_decay self.training_strategy.l1_decay = 0 self.training_strategy.batchnorm_decay = 0 network = self.training_strategy.train(network, dataset) self.training_strategy.l1_decay = old_l1_decay self.training_strategy.batchnorm_decay = old_batchnorm_decay return network
in_pred_bias.append([-norm_input_lb[m]]) in_pred_bias.append([norm_input_ub[m]]) in_pred_bias = np.array(in_pred_bias) in_pred_mat = np.array(in_pred_mat) out_pred_mat = np.array(unsafe_mats[k]) out_pred_bias = np.array(unsafe_vecs[k]) prop = ver.NeVerProperty(in_pred_mat, in_pred_bias, [out_pred_mat], [out_pred_bias]) # prop = ver.NeVerProperty(in_pred_mat, in_pred_bias, unsafe_mats, unsafe_vecs) input_prefix = network.input_id output_prefix = network.get_last_node().identifier ver.temp_never2smt(prop, input_prefix, output_prefix, f"smt_property/SMT_{p_id[k]}.smt2") p2 = reading.SmtPropertyParser( ver.SMTLIBProperty(f"smt_property/SMT_{p_id[k]}.smt2"), input_prefix, output_prefix).parse_property() print(p2) prop_set = True onnx_net = conv.ONNXConverter().from_neural_network(network) pytorch_net = conv.PyTorchConverter().from_neural_network(network) torch.save(pytorch_net.pytorch_network, f"acas_pytorch/{network.identifier}.pt") onnx.save(onnx_net.onnx_network, f"acas_onnx/{network.identifier}.onnx")
def combine_batchnorm1d_net( network: networks.SequentialNetwork) -> networks.SequentialNetwork: """ Utilities function to combine all the FullyConnectedNodes followed by BatchNorm1DNodes in corresponding FullyConnectedNodes. Parameters ---------- network : SequentialNetwork Sequential Network of interest of which we want to combine the nodes. Return ---------- SequentialNetwork Corresponding Sequential Network with the combined nodes. """ if not network.up_to_date: for alt_rep in network.alt_rep_cache: if alt_rep.up_to_date: if isinstance(alt_rep, cv.PyTorchNetwork): pytorch_cv = cv.PyTorchConverter() network = pytorch_cv.to_neural_network(alt_rep) elif isinstance(alt_rep, cv.ONNXNetwork): onnx_cv = cv.ONNXConverter network = onnx_cv.to_neural_network(alt_rep) else: raise NotImplementedError break combined_network = networks.SequentialNetwork(network.identifier + '_combined') current_node = network.get_first_node() node_index = 1 while network.get_next_node( current_node) is not None and current_node is not None: next_node = network.get_next_node(current_node) if isinstance(current_node, nodes.FullyConnectedNode) and isinstance( next_node, nodes.BatchNorm1DNode): combined_node = combine_batchnorm1d(current_node, next_node) combined_node.identifier = f"Combined_Linear_{node_index}" combined_network.add_node(combined_node) next_node = network.get_next_node(next_node) elif isinstance(current_node, nodes.FullyConnectedNode): identifier = f"Linear_{node_index}" new_node = nodes.FullyConnectedNode(identifier, current_node.in_features, current_node.out_features, current_node.weight, current_node.bias) combined_network.add_node(new_node) elif isinstance(current_node, nodes.ReLUNode): identifier = f"ReLU_{node_index}" new_node = nodes.ReLUNode(identifier, current_node.num_features) combined_network.add_node(new_node) else: raise NotImplementedError node_index += 1 current_node = next_node if isinstance(current_node, nodes.FullyConnectedNode): identifier = f"Linear_{node_index}" new_node = nodes.FullyConnectedNode(identifier, current_node.in_features, current_node.out_features, current_node.weight, current_node.bias) combined_network.add_node(new_node) elif isinstance(current_node, nodes.ReLUNode): identifier = f"ReLU_{node_index}" new_node = nodes.ReLUNode(identifier, current_node.num_features) combined_network.add_node(new_node) else: raise NotImplementedError return combined_network
baseline_net = trainer_baseline.train(baseline_net, dataset) sparse_net = copy.deepcopy(small_net) sparse_net = trainer_ns.train(sparse_net, dataset) trainer_ns.fine_tuning = True wp_pruner = pruning.WeightPruning(weight_sparsity_rate, trainer_wp, pre_training=True) ns_pruner = pruning.NetworkSlimming(neuron_sparsity_rate, trainer_ns, pre_training=False) wp_pruned_net = copy.deepcopy(small_net) wp_pruned_net = wp_pruner.prune(wp_pruned_net, dataset) ns_pruned_net = copy.deepcopy(sparse_net) ns_pruned_net = ns_pruner.prune(ns_pruned_net, dataset) baseline_accuracy, baseline_loss = util.testing(conversion.PyTorchConverter().from_neural_network(baseline_net), dataset, test_batch_size, cuda=cuda) sparse_accuracy, sparse_loss = util.testing(conversion.PyTorchConverter().from_neural_network(sparse_net), dataset, test_batch_size, cuda=cuda) ns_accuracy, ns_loss = util.testing(conversion.PyTorchConverter().from_neural_network(ns_pruned_net), dataset, test_batch_size, cuda=cuda) wp_accuracy, wp_loss = util.testing(conversion.PyTorchConverter().from_neural_network(wp_pruned_net), dataset, test_batch_size, cuda=cuda) # Batch norm fusion for the networks of interest (needed for verification and abstraction). com_baseline = util.combine_batchnorm1d_net(baseline_net) com_sparse_net = util.combine_batchnorm1d_net(sparse_net) com_wp_pruned_net = util.combine_batchnorm1d_net(wp_pruned_net) com_ns_pruned_net = util.combine_batchnorm1d_net(ns_pruned_net)
print("DROPOUT NODE TEST") start_network = network.SequentialNetwork("NET_TEST", "X") start_network.add_node(nodes.DropoutNode("Dropout_1", (3, 4, 5, 6))) alt_network = converter.from_neural_network(start_network) end_network = converter.to_neural_network(alt_network) assert isinstance(end_network, network.SequentialNetwork) start_node = start_network.get_first_node() end_node = end_network.get_first_node() assert isinstance(start_node, nodes.DropoutNode) assert isinstance(end_node, nodes.DropoutNode) assert start_node.p == end_node.p assert start_node.identifier == end_node.identifier converters = [conversion.ONNXConverter(), conversion.PyTorchConverter()] for conv in converters: print(f"Test for {conv.__class__.__name__}") relu_node_test(conv) sigmoid_node_test(conv) fully_connected_node_test(conv, True) fully_connected_node_test(conv, False) batchnorm_node_test(conv) conv_node_test(conv, True) conv_node_test(conv, False) averagepool_node_test(conv) maxpool_node_test(conv) lrn_node_test(conv) softmax_node_test(conv) unsqueeze_node_test(conv) reshape_node_test(conv)
def analyze(self, network: networks.NeuralNetwork, sample: Tensor) -> List: """ Analyze the Neural Network with respect to the input Tensor of interest. It returns a list of list containing the relevance value for the hidden layers ReLU neurons. Parameters ---------- network : NeuralNetwork The network to analyze to extract the relevances of the ReLU neurons. sample : Tensor The data sample which is used to compute the relevances. Returns ------- List List containing the relevances for each neuron of each ReLU hidden layer. """ pyt_net = conv.PyTorchConverter().from_neural_network( network).pytorch_network pyt_net.float() pyt_sample = torch.Tensor(sample).squeeze() pyt_net.eval() activations = [] layers = [] current_activations = pyt_sample activations.append(current_activations) last_module = 0 for m in pyt_net.modules(): last_module = last_module + 1 m_index = 0 for m in pyt_net.modules(): if isinstance(m, pyt_layers.Linear): current_activations = m(current_activations) if self.lrp_rule is None: layers.append(m) else: layers.append(self.lrp_rule(m)) elif isinstance(m, pyt_layers.ReLU) and m_index < last_module - 1: current_activations = m(current_activations) activations.append(current_activations) m_index += 1 activations.reverse() layers.reverse() # We begin with setting the relevances to the values of the output relevances = [pyt_net(pyt_sample)] current_relevance = relevances[0] for i in range(len(activations)): current_relevance = self.__rel_prop_mat(activations[i], layers[i], current_relevance) relevances.append(current_relevance) for i in range(len(relevances)): relevances[i] = relevances[i].detach().numpy() relevances.reverse() return relevances
def input_search( net: networks.NeuralNetwork, ref_output: Tensor, start_input: Tensor, max_iter: int, threshold: float = 1e-5, optimizer_con: type = torch.optim.SGD, opt_params: dict = None, scheduler_con: type = torch.optim.lr_scheduler.ReduceLROnPlateau, scheduler_params: dict = None, max_iter_no_change: int = None): logger = logging.getLogger(logger_name) logger.info( f"SEARCH of Input Point corresponding to Output: {ref_output}. MAX_ITER = {max_iter}\n" ) if opt_params is None: opt_params = dict(lr=0.1, momentum=0.5) if max_iter_no_change is None: max_iter_no_change = int(max_iter / 10) py_net = cv.PyTorchConverter().from_neural_network(net).pytorch_network py_ref_output = torch.from_numpy(ref_output) py_start_input = torch.from_numpy(start_input) current_input = py_start_input current_input.requires_grad = True optim = optimizer_con([current_input], **opt_params) if scheduler_params is None: scheduler = scheduler_con(optim) else: scheduler = scheduler_con(optim, **scheduler_params) dist = torch.Tensor([threshold + 10]) iteration = 0 iter_no_change = 0 last_dist = dist while dist > threshold and iteration < max_iter: current_output = py_net(current_input) current_output = torch.unsqueeze(current_output, 0) dist = funct.pairwise_distance(py_ref_output, current_output, p=2) if abs(dist.item() - last_dist.item()) < 0.000001: iter_no_change += 1 if iter_no_change > max_iter_no_change: logger.debug("Early Stopping") break optim.zero_grad() dist.backward() optim.step() scheduler.step(dist) logger.debug(f"Iter {iteration}, PW_Dist = {dist.item()}") logger.debug(f"Current Input: {current_input.data}") logger.debug(f"Current Output: {current_output.data}") logger.debug(f"Current Reference Output: {py_ref_output.data}\n") last_dist = dist iteration = iteration + 1 correct = False if dist <= threshold: correct = True return correct, current_input.detach().numpy(), current_output.detach( ).numpy()