def normalize_outputs(node: Node): if node.out_port(0).disconnected(): output = Result( node.graph, { 'name': node.name + '/Result_port_0/', 'keep_output_port': node.has_and_set('remove_values_output') }).create_node() node.out_port(0).get_connection().set_destination( output.in_port(0)) # we check port existing to support MaxPool_1 with only 1 output port and MaxPool_8 with 2 output ports if node.has_port('out', 1) and node.out_port(1).disconnected(): output = Result( node.graph, { 'name': node.name + '/Result_port_1/', 'keep_output_port': node.has_and_set('remove_values_output') }).create_node() node.out_port(1).get_connection().set_destination( output.in_port(0))
def merge_nodes(graph: Graph, nodes_to_merge_names: list, inputs_desc: list = None, outputs_desc: list = None): """ Merges nodes specified in the set 'nodes_to_merge_names' into one mega-node, creating new edges between mega-node and inputs/outputs nodes of the mega-node. The added edges contain name of input/output nodes which will be used for generation of placeholders and will be saved to the IR xml so IE plug-in know how to map input/output data for the layer. Also the function adds protobufs of the nodes of the sub-graph and 'Const' ops consumed by nodes in the sub-graph to the node's attribute 'pbs'. :param graph: the graph object to operate on. :param nodes_to_merge_names: list of nodes names that should be merged into a single node. :param inputs_desc: optional list describing input nodes order. :param outputs_desc: optional list describing output nodes order. """ if not is_connected_component(graph, nodes_to_merge_names): log.warning( "The following nodes do not form connected sub-graph: {}".format( nodes_to_merge_names)) # graph.dump_graph_for_graphviz(nodes_to_dump=nodes_to_merge_names) new_node_name = graph.unique_id("TFSubgraphCall_") log.info("Create new node with name '{}' for nodes '{}'".format( new_node_name, ', '.join(nodes_to_merge_names))) graph.add_node(new_node_name) new_node_attrs = graph.node[new_node_name] new_node_attrs['name'] = new_node_name set_tf_custom_call_node_attrs(new_node_attrs) new_node = Node(graph, new_node_name) added_input_tensors_names = set( ) # set of tensors that are were added as input to the sub-graph added_new_node_output_tensors = dict( ) # key - tensor name, value - out port for node_name in nodes_to_merge_names: node = Node(graph, node_name) add_node_pb_if_not_yet_added(node, new_node) # TODO: any improvements? for in_node_name, edge_attrs in Node(graph, node_name).get_inputs(): in_node = Node(graph, in_node_name) # internal edges between nodes of the sub-graph if in_node_name in nodes_to_merge_names: add_node_pb_if_not_yet_added(in_node, new_node) continue # edge outside of sub-graph into sub-graph if in_node_name not in nodes_to_merge_names: # we cannot use the 'in_node_name' as a protobuf operation name here # because the 'in_node_name' could be a sub-graph matched before. input_tensor_name = node.pb.input[edge_attrs['in']] if input_tensor_name not in added_input_tensors_names: if not new_node.has_port('in', edge_attrs['in']): new_node.add_input_port(edge_attrs['in']) graph.add_edge( in_node_name, new_node_name, **merge_edge_props( { 'in': find_input_port(new_node, inputs_desc, node_name, edge_attrs['in']), 'out': edge_attrs['out'], 'internal_input_node_name': input_tensor_name, 'original_dst_node_name': node_name, 'original_dst_port': edge_attrs['in'], 'in_attrs': [ 'in', 'internal_input_node_name', 'original_dst_node_name', 'original_dst_port', 'placeholder_name' ], 'out_attrs': ['out'] }, edge_attrs)) log.debug( "Creating edge from outside of sub-graph to inside sub-graph: {} -> {}" .format(in_node_name, new_node_name)) added_input_tensors_names.add(input_tensor_name) # edge from inside sub-graph to outside sub-graph for out_node_name, edge_attrs in Node(graph, node_name).get_outputs(): if out_node_name not in nodes_to_merge_names: log.debug( "Creating edge from inside of sub-graph to outside sub-graph: {} -> {}" .format(new_node_name, out_node_name)) out_name = internal_output_name_for_node( node_name, edge_attrs['out']) if out_name not in added_new_node_output_tensors.keys(): added_new_node_output_tensors[out_name] = find_output_port( new_node, outputs_desc, node_name, edge_attrs['out']) if not new_node.has_port( 'out', added_new_node_output_tensors[out_name]): new_node.add_output_port( added_new_node_output_tensors[out_name]) graph.add_edge( new_node_name, out_node_name, **merge_edge_props( { 'in': edge_attrs['in'], 'out': added_new_node_output_tensors[out_name], 'internal_output_node_name': out_name, 'in_attrs': ['in', 'internal_input_node_name'], 'out_attrs': ['out', 'internal_output_node_name'] }, edge_attrs)) new_node['output_tensors_names'] = [ val for val in {v: k for k, v in added_new_node_output_tensors.items()}.values() ] # add nodes using the same order as in initial GraphDef so we can dump them to IR in "correct" order new_node['nodes_order'] = [ node for node in graph.graph['initial_nodes_order'] if node in new_node['pbs'].keys() ] for n in nodes_to_merge_names: if graph.has_node( n): # check if not deleted by another (similar) pattern graph.remove_node(n) return Node(graph, new_node_name)
def infer(node: Node): num_of_inputs = len(node.in_ports()) opset = node.get_opset() max_num_of_inputs = 6 if opset == 'opset5' else 5 input_msg_fmt = 'NonMaxSuppression node {} from {} must have from 2 to {} inputs' node_name = node.soft_get('name', node.id) inputs_msg = input_msg_fmt.format(node_name, opset, max_num_of_inputs) assert 2 <= num_of_inputs <= max_num_of_inputs, inputs_msg boxes_shape = node.in_port(0).data.get_shape() assert boxes_shape is not None, 'The shape of tensor with boxes is not defined' scores_shape = node.in_port(1).data.get_shape() assert scores_shape is not None, 'The shape of tensor with scores is not defined' assert len(boxes_shape ) == 3, 'Length of tensors with boxes must be equal to 3' assert len(scores_shape ) == 3, 'Length of tensors with scores must be equal to 3' # According to the specification of the operation NonMaxSuppression, # the input 'max_output_boxes_per_class' (port 2) is optional, with default value 0. if num_of_inputs >= 3: max_output_boxes_per_class = node.in_port(2).data.get_value() else: max_output_boxes_per_class = 0 if not max_output_boxes_per_class: log.info( 'Set default "max_output_boxes_per_class" for node {} to number of boxes' .format(node.name)) max_output_boxes_per_class = boxes_shape[1] # convert the np.array value to a scalar to avoid issue with ragged numpy array generation in the shape # calculation formulas below if isinstance(max_output_boxes_per_class, np.ndarray): max_output_boxes_per_class = max_output_boxes_per_class.item() num_classes = scores_shape[1] num_input_boxes = boxes_shape[1] assert scores_shape[2] is dynamic_dimension or scores_shape[2] == num_input_boxes or scores_shape[2] is None \ or num_input_boxes is None, 'Number of boxes mismatch for operation {}'.format(node_name) if node.get_opset() in ['opset4', 'opset5']: max_number_of_boxes = min( num_input_boxes, max_output_boxes_per_class) * boxes_shape[0] * num_classes else: max_number_of_boxes = min( num_input_boxes, boxes_shape[0] * max_output_boxes_per_class * num_classes) node.out_port(0).data.set_shape(shape_array([max_number_of_boxes, 3])) if opset == 'opset5': node.out_port(0).data.set_shape( shape_array([dynamic_dimension_value, 3])) num_of_outputs = len([ port for port in node.out_ports().values() if not port.disconnected() ]) if num_of_outputs >= 2 and node.has_port('out', 1): node.out_port(1).data.set_shape( shape_array([dynamic_dimension_value, 3])) if num_of_outputs >= 3 and node.has_port('out', 2): node.out_port(2).data.set_shape(shape_array([1]))