def draw_line_between(self, origin_id: str, destination_id: str): """ This method connects automatically the two rects given with an edge. Parameters ---------- origin_id : str The origin node for the edge destination_id : str The destination node for the edge """ origin_item = None destination_item = None # Find the rects corresponding to the given ids for rect, block in self.scene.blocks.items(): if block.block_id == origin_id: origin_item = rect if block.block_id == destination_id: destination_item = rect if origin_item is not None and destination_item is not None: if "Pr" in origin_id: self.scene.auto_add_line(origin_item, destination_item) return try: # Update the node input NodeOps.update_node_input( self.renderer.NN.nodes[destination_id], self.renderer.NN.nodes[origin_id].out_dim) # Update sequential nodes, if any if len(self.renderer.NN.edges[destination_id]) > 1: self.renderer.update_network_from( destination_id, origin_id) # The new line is drawn line = self.scene.auto_add_line(origin_item, destination_item) out_dim = self.renderer.NN.nodes[ self.scene.blocks[origin_item].block_id].out_dim line.update_dims(out_dim) self.scene.blocks[origin_item].out_dim = out_dim self.scene.blocks[destination_item].in_dim = out_dim self.scene.blocks[ destination_item].out_dim = self.renderer.NN.nodes[ self.scene.blocks[destination_item]. block_id].out_dim return except Exception as e: self.renderer.delete_edge_from(origin_id) dialog = MessageDialog( str(e) + "\nPlease check dimensions.", MessageType.ERROR) dialog.exec() return
def open_property(self): """ This method opens a SMT file containing the description of the network properties. The file is checked to be consistent with the network and then parsed in order to build the property objects. Returns ------- """ # Check project if not self.network.nodes: err = MessageDialog("No network loaded!", MessageType.ERROR) err.exec() return # Select file property_file_name = QFileDialog.getOpenFileName( None, "Open property file", "", PROPERTY_FORMATS) if property_file_name != ("", ""): self.input_handler = InputHandler() QApplication.setOverrideCursor(Qt.WaitCursor) self.properties = self.input_handler.read_properties( property_file_name[0]) QApplication.restoreOverrideCursor() self.opened_property.emit()
def read_properties(self, path: str) -> dict: """ This method reads the SMT property file and creates a property for each node. Parameters ---------- path : str The SMT-LIB file path. Returns ------- dict The dictionary of properties """ parser = SmtLibParser() try: script = parser.get_script_fname(path) except PysmtException: dialog = MessageDialog("Failed to parse SMT property.", MessageType.ERROR) dialog.exec() return dict() declarations = script.filter_by_command_name( ['declare-fun', 'declare-const']) assertions = script.filter_by_command_name('assert') var_set = [] var_list = [] properties = dict() for d in declarations: var_list.append(str(d.args[0]).replace('\'', '')) varname = str(d.args[0]).split('_')[0].replace( '\'', '') # Variable format is <v_name>_<idx> if varname not in var_set: var_set.append(varname) counter = 0 for a in assertions: line = str(a.args[0]).replace('\'', '') for v in var_set: if f" {v}" in line or f"({v}" in line: # Either '(v ...' or '... v)' if v not in properties.keys(): properties[v] = PropertyBlock(f"{counter}Pr", "Generic SMT") properties[v].smt_string = '' properties[v].variables = list( filter(lambda x: v in x, var_list)) counter += 1 conv = ExpressionTreeConverter() wrap = conv.build_from_infix(line).as_prefix() properties[v].smt_string += f"(assert {wrap})\n" break return properties
def verify_network(self): if not self.renderer.NN.nodes: dialog = MessageDialog("No network to verify.", MessageType.ERROR) dialog.exec() elif not self.project.properties: dialog = MessageDialog("No property to verify.", MessageType.ERROR) dialog.exec() else: window = VerificationWindow(self.renderer.NN, self.project.properties) window.exec()
def verify_network(self): """ This class is a Window for the training of the network. It features a file picker for choosing the dataset and a grid of parameters for tuning the procedure. """ if self.strategy is None: err_dialog = MessageDialog("No verification methodology selected.", MessageType.ERROR) err_dialog.exec() return # Save properties path = 'never2/' + self.__repr__().split(' ')[-1].replace('>', '') + '.smt2' utility.write_smt_property(path, self.properties, 'Real') input_name = list(self.properties.keys())[0] output_name = list(self.properties.keys())[-1] # Add logger text box log_textbox = LoggerTextBox(self) logger = logging.getLogger("pynever.strategies.verification") logger.addHandler(log_textbox) logger.setLevel(logging.INFO) self.layout.addWidget(log_textbox.widget) logger.info("***** NeVer 2 - VERIFICATION *****") # Load NeVerProperty from file parser = reading.SmtPropertyParser(verification.SMTLIBProperty(path), input_name, output_name) to_verify = parser.parse_property() # Property read, delete file os.remove(path) # Launch verification self.strategy.verify(self.nn, to_verify) self.verify_btn.setEnabled(False) self.cancel_btn.setText("Close")
def save(self, _as: bool = True): """ This method converts and saves the network in a file. It prompts the user before starting. Parameters ---------- _as : bool This attribute distinguishes between "save" and "save as". If _as is True the network will be saved in a new file, while if _as is False the network will overwrite the current one. """ # If the user picked "save as" option or there isn't a current file, # a dialog is opened to chose where to save the net if _as or self.file_name == ("", ""): self.file_name = QFileDialog.getSaveFileName( None, 'Save File', "", NETWORK_FORMATS_SAVE) if self.file_name != ("", ""): self.output_handler = OutputHandler() self.network.identifier = self.file_name[0].split('/')[-1].split( ".")[0] # A "wait cursor" appears locking the interface QApplication.setOverrideCursor(Qt.WaitCursor) self.output_handler.save(self.network, self.file_name) if self.properties: self.output_handler.save_properties(self.properties, self.file_name) QApplication.restoreOverrideCursor() # At the end of the loading, the main thread looks for eventual # Exception in the output_handler if self.output_handler.exception is not None: error_dialog = MessageDialog( "Error in network saving: \n" + str(self.output_handler.exception), MessageType.ERROR) error_dialog.exec()
def train_network(self): if not self.renderer.NN.nodes: dialog = MessageDialog("No network to train.", MessageType.ERROR) dialog.exec() else: window = TrainingWindow(self.renderer.NN) window.exec() if window.is_nn_trained: dialog = FuncDialog( "Training completed. Weights and biases updated.\nSave network?", self.project.save(False)) dialog.exec()
def open(self): """ This method opens a file for reading a network in one of the supported formats. The network is then converted by a thread, while a loading dialog is displayed. """ # Open network self.file_name = QFileDialog.getOpenFileName(None, "Open network", "", NETWORK_FORMATS_OPENING) # If a file has been selected: if self.file_name != ("", ""): self.input_handler = InputHandler() # A "wait cursor" appears QApplication.setOverrideCursor(Qt.WaitCursor) self.network = self.input_handler.read_network(self.file_name[0]) if isinstance(self.network, pynn.SequentialNetwork) and \ self.network.input_id == '': self.network.input_id = 'X' QApplication.restoreOverrideCursor() # At the end of the loading, the main thread looks for potential # exceptions in the input_handler if self.input_handler.conversion_exception is not None: # If there is some error in converting, it is returned # an error message error_dialog = MessageDialog( "Error in network reading: \n" + str(self.input_handler.conversion_exception), MessageType.ERROR) error_dialog.exec() else: self.opened_net.emit()
def parameters_action_validation(self) -> Optional[NodeBlock]: """ This method performs a check on the object on which the parameters action is called, in order to prevent unwanted operations. Returns ---------- NodeBlock The graphic wrapper of the NetworkNode selected, if present. """ if self.canvas.scene.selectedItems(): if type(self.canvas.scene.selectedItems()[0]) is QGraphicsRectItem: # Return block graphic object return self.canvas.scene.blocks[self.canvas.scene.selectedItems()[0]] elif type(self.canvas.scene.selectedItems()[0]) is GraphicLine: msg_dialog = MessageDialog("No parameters available for connections.", MessageType.ERROR) msg_dialog.exec() else: err_dialog = MessageDialog("No block selected.", MessageType.MESSAGE) err_dialog.exec()
def delete_selected(self): """ This method deletes the selected block or edge. """ if self.scene.mode != DrawingMode.IDLE: dialog = MessageDialog( "Cannot delete items in draw or insert mode.", MessageType.MESSAGE) dialog.exec() else: # Get selected item to delete item = self.scene.delete_selected() if item is not None: if type(item) == QGraphicsRectItem: if self.scene.blocks[ item].block_id in self.project.properties.keys(): self.project.properties.pop( self.scene.blocks[item].block_id) if self.scene.blocks[ item].block_id in self.renderer.NN.nodes.keys(): # Get the first node first_node = self.renderer.NN.get_first_node() # If the node is in the network, delete it new_tuple = self.renderer.delete_node( self.scene.blocks[item].block_id) # If there was a connection, preserve it if new_tuple is not None: self.draw_line_between(new_tuple[0], new_tuple[1]) # New first node new_first_node = self.renderer.NN.get_first_node() # If the first node is changed if first_node is not new_first_node: self.renderer.disconnected_network[new_first_node.identifier] \ .is_head = True # Delete block if not in connected network if self.scene.blocks[ item].block_id in self.renderer.disconnected_network: self.renderer.disconnected_network.pop( self.scene.blocks[item].block_id) # Remove item self.scene.removeItem(item) self.scene.blocks.pop(item) elif type(item) == GraphicLine: # Deleting an edge makes the network non sequential block_before = self.scene.blocks[item.origin] block_after = self.scene.blocks[item.destination] if isinstance(block_before, PropertyBlock): if block_after.block_id in self.project.properties.keys( ): del self.project.properties[block_after.block_id] self.scene.removeItem(block_before.rect) self.scene.blocks.pop(block_before.rect) else: block_after.is_head = True self.renderer.delete_edge_from(block_before.block_id) item.remove_self() # Delete other selected items if self.scene.selectedItems(): self.delete_selected()
def edit_node(self, block: NodeBlock): """ This method propagates the changes stored in the block.edits attribute. Parameters ---------- block : NodeBlock The block to update. """ edits = block.edits if edits is not None and block.block_id in self.renderer.disconnected_network.keys( ): edit_node_id = edits[0] edit_data = edits[1] new_in_dim = None if "in_dim" in edit_data.keys(): new_in_dim = edit_data["in_dim"] # If in_dim changes in FC, update in_features if block.node.name == 'Fully Connected' and new_in_dim is not None: edit_data["in_features"] = new_in_dim[-1] # If out_features changes in FC, update out_dim if block.node.name == 'Fully Connected' and 'out_features' in edit_data.keys( ): edit_data["out_dim"] = block.out_dim[:-1] + ( edit_data["out_features"], ) for block_par, info in block.node.param.items(): if "shape" in info: str_shape = tuple(map(str, info["shape"].split(', '))) new_shape = tuple() # Confront for dim in str_shape: new_dim = block.block_data[dim] if dim in edit_data: new_dim = edit_data[dim] if isinstance(new_dim, tuple): new_shape += new_dim else: new_shape += (new_dim, ) # Add new Tensor value to edit_data edit_data[block_par] = Tensor( shape=new_shape, buffer=np.random.normal(size=new_shape)) try: # Check if the network has changed self.renderer.edit_node(edit_node_id, edit_data) except Exception as e: dialog = MessageDialog( str(e) + "\nImpossible to propagate changes.", MessageType.ERROR) dialog.exec() # Update the graphic block self.renderer.disconnected_network[edit_node_id].update_labels() # Update dimensions in edges & nodes for line in self.scene.edges: if isinstance(self.scene.blocks[line.origin], NodeBlock): origin_id = self.scene.blocks[line.origin].block_id new_dim = self.renderer.NN.nodes[origin_id].out_dim line.update_dims(new_dim) self.scene.blocks[line.origin].out_dim = new_dim self.scene.blocks[line.destination].in_dim = new_dim # Empty changes buffer block.edits = None
def draw_line_between_selected(self): """ This method draws a line if an item was selected in draw_line mode. If the connection is legal, the network is updated, otherwise the edge is deleted. """ # The method draw_line returns a tuple of the two blocks connected conn_nodes = self.scene.add_line() if conn_nodes is not None: origin = self.scene.blocks[conn_nodes[0]] destination = self.scene.blocks[conn_nodes[1]] if isinstance(origin, NodeBlock) and isinstance( destination, PropertyBlock): conn_nodes[2].remove_self() dialog = MessageDialog("Illegal property connection.", MessageType.ERROR) dialog.exec() return if isinstance(origin, PropertyBlock) and isinstance( destination, NodeBlock): origin.variables = utility.create_variables_from( destination.block_id, destination.out_dim) origin.pre_condition = False origin.condition_label.setText("POST") # Properties dict is {node_id: property} if origin in self.project.properties.values(): # Find key of origin key = list(self.project.properties.keys())[list( self.project.properties.values()).index(origin)] del self.project.properties[key] self.project.properties[destination.block_id] = origin return try: # Add the new node to the network legal = self.renderer.add_edge(origin.block_id, destination.block_id) if legal: if self.renderer.NN.get_first_node( ).identifier == origin.block_id: origin.is_head = True # Draw dimensions in_dim = self.renderer.NN.nodes[ destination.block_id].in_dim if isinstance(self.renderer.NN.nodes[destination.block_id], nodes.FullyConnectedNode): out_dim = self.renderer.NN.nodes[ destination.block_id].out_features else: out_dim = self.renderer.NN.nodes[ destination.block_id].out_dim self.scene.blocks[conn_nodes[0]].out_dim = in_dim self.scene.blocks[conn_nodes[1]].out_dim = out_dim destination.in_dim = in_dim destination.is_head = False drawn_edge = conn_nodes[2] drawn_edge.update_dims(in_dim) else: # If the edge is illegal for a legal network, it is removed conn_nodes[2].remove_self() dialog = MessageDialog("Sequential network: illegal edge.", MessageType.ERROR) dialog.exec() except Exception as e: # If an error occurs, the edge is removed conn_nodes[2].remove_self() dialog = MessageDialog(str(e), MessageType.ERROR) dialog.exec()
def insert_node(self): """ This method inserts a block inside the network. The selected edge is split in two edges. """ result = self.scene.add_node() if result is not None: # Nodes prev_node = self.scene.blocks[result[0]] middle_node = self.scene.blocks[result[1]] # Edges old_line = result[2] l1 = result[3] l2 = result[4] try: # The logical network is modified legal = self.renderer.insert_node(prev_node.block_id, middle_node.block_id) except Exception as e: # In case of error, the new edges are deleted l1.remove_self() l2.remove_self() # a Message is displayed dialog = MessageDialog(str(e), MessageType.ERROR) dialog.exec() return try: if not legal: # If the modification is not possible, the new edges are # removed l1.remove_self() l2.remove_self() dialog = MessageDialog( "Sequential network : illegal operation.", MessageType.ERROR) dialog.exec() else: # Otherwise the old edge is removed old_line.remove_self() # inputs and dimensions labels are updated out_dim_1 = self.renderer.NN.nodes[ prev_node.block_id].out_dim out_dim_2 = self.renderer.NN.nodes[ middle_node.block_id].out_dim # Dimension labels are updated l1.update_dims(out_dim_1) l2.update_dims(out_dim_2) self.scene.blocks[result[0]].out_dim = out_dim_1 # Scene blocks are updated self.scene.blocks[l2.origin].in_dim = out_dim_1 self.scene.blocks[l2.destination].in_dim = out_dim_2 self.scene.blocks[l2.origin].is_head = False except Exception as e: next_node = self.scene.blocks[l2.destination] if prev_node.out_dim is not middle_node.in_dim: l1.set_valid(False) if middle_node.out_dim is not next_node.in_dim: l2.set_valid(False) dialog = MessageDialog( str(e) + "\nPlease check dimensions.", MessageType.ERROR) dialog.exec()
def train_network(self): """ This method reads the inout from the window widgets and launches the training procedure on the selected dataset. """ err_dialog = None if self.dataset_path == "": err_dialog = MessageDialog("No dataset selected.", MessageType.ERROR) elif self.widgets["Optimizer"].currentIndex() == -1: err_dialog = MessageDialog("No optimizer selected.", MessageType.ERROR) elif self.widgets["Scheduler"].currentIndex() == -1: err_dialog = MessageDialog("No scheduler selected.", MessageType.ERROR) elif self.widgets["Loss Function"].currentIndex() == -1: err_dialog = MessageDialog("No loss function selected.", MessageType.ERROR) elif self.widgets["Precision Metric"].currentIndex() == -1: err_dialog = MessageDialog("No metrics selected.", MessageType.ERROR) elif "value" not in self.params["Epochs"].keys(): err_dialog = MessageDialog("No epochs selected.", MessageType.ERROR) elif "value" not in self.params["Validation percentage"].keys(): err_dialog = MessageDialog("No validation percentage selected.", MessageType.ERROR) elif "value" not in self.params["Training batch size"].keys(): err_dialog = MessageDialog("No training batch size selected.", MessageType.ERROR) elif "value" not in self.params["Validation batch size"].keys(): err_dialog = MessageDialog("No validation batch size selected.", MessageType.ERROR) if err_dialog is not None: err_dialog.exec() return # Load dataset data = self.load_dataset() # Add logger text box log_textbox = LoggerTextBox(self) logger = logging.getLogger("pynever.strategies.training") logger.addHandler(log_textbox) logger.setLevel(logging.INFO) self.layout.addWidget(log_textbox.widget) logger.info("***** NeVer 2 - TRAINING *****") # Create optimizer dictionary of parameters opt_params = dict() for k, v in self.gui_params["Optimizer:Adam"].items(): opt_params[v["name"]] = v["value"] # Create scheduler dictionary of parameters sched_params = dict() for k, v in self.gui_params["Scheduler:ReduceLROnPlateau"].items(): sched_params[v["name"]] = v["value"] # Init loss function if self.loss_f == "Loss Function:Cross Entropy": loss = torch.nn.CrossEntropyLoss() if self.gui_params["Loss Function:Cross Entropy"]["Weight"][ "value"] != '': loss.weight = self.gui_params["Loss Function:Cross Entropy"][ "Weight"]["value"] loss.ignore_index = self.gui_params["Loss Function:Cross Entropy"][ "Ignore index"]["value"] loss.reduction = self.gui_params["Loss Function:Cross Entropy"][ "Reduction"]["value"] else: loss = fun.mse_loss loss.reduction = self.gui_params["Loss Function:MSE Loss"][ "Reduction"]["value"] # Init metrics if self.metric == "Precision Metric:Inaccuracy": metrics = PytorchMetrics.inaccuracy else: metrics = fun.mse_loss metrics.reduction = self.gui_params["Precision Metric:MSE Loss"][ "Reduction"]["value"] # Checkpoint loading checkpoints_path = self.params["Checkpoints root"].get( "value", '') + self.nn.identifier + '.pth.tar' if not os.path.isfile(checkpoints_path): checkpoints_path = None start_epoch = 0 if checkpoints_path is not None: checkpoint = torch.load(checkpoints_path) start_epoch = checkpoint["epoch"] if self.params["Epochs"]["value"] <= start_epoch: start_epoch = -1 logger.info( "Checkpoint already reached, no further training necessary" ) if start_epoch > -1: # Init train strategy train_strategy = PytorchTraining( opt.Adam, opt_params, loss, self.params["Epochs"]["value"], self.params["Validation percentage"]["value"], self.params["Training batch size"]["value"], self.params["Validation batch size"]["value"], opt.lr_scheduler.ReduceLROnPlateau, sched_params, metrics, cuda=self.params["Cuda"]["value"], train_patience=self.params["Train patience"].get( "value", None), checkpoints_root=self.params["Checkpoints root"].get( "value", ''), verbose_rate=self.params["Verbosity level"].get("value", None)) try: self.nn = train_strategy.train(self.nn, data) self.is_nn_trained = True # Delete checkpoint if the network isn't saved if self.nn.identifier == '': os.remove('.pth.tar') except Exception as e: self.nn = None dialog = MessageDialog("Training error:\n" + str(e), MessageType.ERROR) dialog.exec() self.close() self.train_btn.setEnabled(False) self.cancel_btn.setText("Close")
def save(self, _as: bool = True): """ This method saves the current network if the format is correct Parameters ---------- _as : bool, optional This attribute distinguishes between "save" and "save as". If _as is True the network will be saved in a new file, while if _as is False the network will overwrite the current one. (Default: True) """ if len(self.canvas.renderer.NN.nodes) == 0 or \ len(self.canvas.renderer.NN.edges) == 0: # Limit case: one disconnected node -> new network with one node if len(self.canvas.renderer.disconnected_network) == 1: for node in self.canvas.renderer.disconnected_network: try: self.canvas.renderer.add_node_to_nn(node) self.canvas.project.save(_as) self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) except Exception as e: error_dialog = MessageDialog(str(e), MessageType.ERROR) error_dialog.exec() # More than one disconnected nodes cannot be saved elif len(self.canvas.renderer.disconnected_network) > 1: not_sequential_dialog = MessageDialog("The network is not sequential, and " "cannot be saved.", MessageType.ERROR) not_sequential_dialog.exec() else: # Network is empty message = MessageDialog("The network is empty!", MessageType.MESSAGE) message.exec() elif self.canvas.renderer.is_nn_sequential(): # If there are logical nodes, the network is sequential every_node_connected = True # every node has to be in the nodes dictionary for node in self.canvas.renderer.disconnected_network: if node not in self.canvas.project.network.nodes: every_node_connected = False break if every_node_connected: self.canvas.project.save(_as) if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) else: # If there are disconnected nodes, a message is displayed to the # user to choose if saving only the connected network confirm_dialog = ConfirmDialog("Save network", "All the nodes outside the " "sequential network will lost.\n" "Do you wish to continue?") confirm_dialog.exec() if confirm_dialog.confirm: self.canvas.project.save(_as) if self.canvas.project.network is not None: self.setWindowTitle(self.SYSNAME + " - " + self.canvas.project.network.identifier) else: # If the network is not sequential, it cannot be saved. not_sequential_dialog = MessageDialog("The network is not sequential and " "cannot be saved.", MessageType.ERROR) not_sequential_dialog.exec()
def temp_window(): dialog = MessageDialog("Work in progress...", MessageType.MESSAGE) dialog.exec()