def _create_data_if_necessary(self): if self.node.graph.stage == 'front': raise Error("_create_data_if_necessary method is not applicable for front Graph phase!") if self.type == 'in': raise Error("_create_data_if_necessary method is not applicable for 'in' Port type!") if self.idx not in self.node.out_nodes(control_flow=self.control_flow): from mo.ops.op import Op Op.create_data_node(self.node.graph, self.node, out_port=self.idx) self.node['need_shape_inference'] = True return self.node.out_node(self.idx, control_flow=self.control_flow)
def apply_mean_value(graph: Graph, input_node: Node, node_mean_scale_values: dict): if 'mean' in node_mean_scale_values and node_mean_scale_values[ 'mean'] is not None: if all([x == 0 for x in node_mean_scale_values['mean']]): return out_node = input_node.out_node() if not input_node.has_valid('shape'): raise Error("Node {} has not valid shape attribute".format( input_node.id)) input_shape = input_node.shape # Create Add node graph.remove_edge(input_node.id, out_node.id) value = np.array(node_mean_scale_values['mean']) * (-1) add_node = Add(graph, dict(name="Add_")) add_data = Op.create_input_data_node(graph, "data_add_", np.array(value)) Op.expand_node_shape(add_data, (len(input_shape) - 2 if graph.graph['layout'] == 'NCHW' else 0)) add_input = Op.create_data_node(graph, input_node, {'shape': out_node.shape}) add_node.create_node_with_data(inputs=[add_input, add_data], data_nodes=out_node)
def apply_scale(graph: Graph, input_node: Node, node_mean_scale_values: dict): if 'scale' in node_mean_scale_values and node_mean_scale_values[ 'scale'] is not None: if all([x == 1 for x in node_mean_scale_values['scale']]): return out_node = input_node.out_node() if not input_node.has_valid('shape'): raise Error("Node {} has not valid shape attribute".format( input_node.id)) input_shape = input_node.shape # Create Mul node value = 1 / np.array(node_mean_scale_values['scale']) graph.remove_edge(input_node.id, out_node.id) mul_node = Mul(graph, dict(name="Mul_")) mul_data = Op.create_input_data_node(graph, "data_mul_", np.array(value)) Op.expand_node_shape(mul_data, (len(input_shape) - 2 if graph.graph['layout'] == 'NCHW' else 0)) mul_input = Op.create_data_node(graph, input_node, {'shape': out_node.shape}) mul_node.create_node_with_data(inputs=[mul_input, mul_data], data_nodes=out_node)
def insert_node_with_data_after(self, out, new_op_class: callable, op_after_params: dict = None): """ Inserts operation node with op_after_params and data node after current operation :param out: output data node of current node :param new_op_class: class of operation that will be inserted after current operation node :param op_after_params: parameters to be added to operation that will be inserted after current operation Before calling: [...] -> Cur_Op -> Cur_Data -> [...] After calling: [...] -> Cur_Op -> Cur_Data -> New_Op_aft -> New_Data_aft(==out) -> [...] [op_after_params] """ # we import it here because Op imports Node and unique_id from this file from mo.ops.op import Op graph = self.graph node = Node(graph, self.node) cls_name = new_op_class.op op_after_params = {} if op_after_params is None else op_after_params new_op_after = new_op_class(graph, op_after_params) graph.remove_edge(node.id, out.id) new_out = Op.create_data_node(graph, node) node.infer(node) new_op_after.create_node_with_data( [new_out], {'name': node.name + cls_name + '/After'}, data_nodes=out)
def _scale_input_action_mul(graph: nx.MultiDiGraph, match: dict, scale: float): assert (len(match['placeholder'].out_nodes())) tinput = match['placeholder'] if not tinput.has_valid('shape'): raise Error("Node {} has not valid shape attribute".format(tinput.id)) input_shape = tinput.shape toutput = match['data'] # Create Mul node value = np.array([1 / scale]) # Disconnect input with data node graph.remove_edge(tinput.id, toutput.id) # Create Mul node mul_node = Mul(graph, dict(name="Mul1_")) mul_data = Op.create_input_data_node(graph, "data_mul_scale_", np.array(value)) Op.expand_node_shape( mul_data, len(input_shape) - 2 if graph.graph['layout'] == 'NCHW' else 0) mul_input = Op.create_data_node(graph, tinput, {'shape': toutput.shape}) mul_node.create_node_with_data(inputs=[mul_input, mul_data], data_nodes=toutput)
def insert_abs_max(self, model_graph, node, type_stat, node_name, **kwargs): axis_const = self.find_axis(node, kwargs.get('granularity')) if isinstance(axis_const, str): return (True, node.name) abs_node = Abs(node.graph, { "name": f'abs_{node_name}' }).create_node_with_data([node.out_node(0)]).in_node(0) max_op = create_op_node_with_second_input( node.graph, ReduceMax, int64_array(axis_const), dict(name=f'{type_stat}_{node_name}')) if node.graph != model_graph: Op.create_data_node(max_op.graph, max_op, {'shape': [1]}) max_op['fullname'] = reset_node_fullname(node.fullname, max_op.name) abs_node.out_port(0).connect(max_op.in_port(0)) return self.insert_result(model_graph, node, max_op, type_stat)
def insert_reduce(self, model_graph, insert_op, node, granularity, type_stat, node_name, axis=1): axis_const = self.find_axis(node, granularity, axis) if isinstance(axis_const, str): return (True, node.name) reduce_op = create_op_node_with_second_input( node.graph, insert_op, int64_array(axis_const), dict(name=f'{type_stat}_{node_name}')) reduce_op['fullname'] = reset_node_fullname(node.fullname, reduce_op.name) if node.graph != model_graph: Op.create_data_node(reduce_op.graph, reduce_op, {'shape': [1]}) node.out_port(0).connect(reduce_op.in_port(0)) return self.insert_result(model_graph, node, reduce_op, type_stat)
def replace_pattern(self, graph: Graph, match: dict): if match['axis'].value is None or match['input'].shape is None: return dims = len(match['input'].shape) ones = np.ones(dims, dtype=np.int64) axis = np.array(match['axis'].value) axis = axis if axis.ndim != 0 else np.array([axis], dtype=np.int64) mean = graph.node[match['mean'].node] mean['stride'] = np.array(ones) # TODO: need to check axis with real layout spatial_dims = np.array(axis) mean['spatial_dims'] = spatial_dims mean['pad'] = np.zeros((dims, 2), np.int64) mean['pad_spatial_shape'] = np.array(mean['pad'][spatial_dims]) window = np.array(ones) window[spatial_dims] = match['input'].shape[spatial_dims] mean['window'] = window mean['TF_op'] = mean['op'] mean['op'] = 'AvgPool' mean['pool_method'] = 'avg' mean['rounding_type'] = 'ceil' mean['exclude_pad'] = 'true' mean['kernel_spatial'] = window[spatial_dims] graph.remove_edge(match['axis'].node, match['mean'].node) mean['permute_attrs'] = PermuteAttrs().update_attrs(attrs=[( 'pad', 'input:0'), ('stride', 'input:0'), ('window', 'input:0'), ('spatial_dims', 'input:0')]) if match['mean'].keep_dims == False: output = match['mean'].out_node() pool_node = match['mean'] # Keep dims for AvgPool shape = np.array(output.shape) for idx in spatial_dims: shape = np.insert(shape, idx, 1) graph.remove_edge(pool_node.id, output.id) # Create new data for pool with all dims pool_data = Op.create_data_node(graph, pool_node, {'shape': np.array(shape)}) # Create and connect reshape node reshape_op = Reshape(graph, {'dim': np.array(output.shape)}) reshape_node = reshape_op.create_node( [pool_data], dict(name='Reshape_', permute_attrs=PermuteAttrs().update_attrs( attrs=[('dim', 'output:0')]))) graph.create_edge(reshape_node, output)
def replace_pattern(self, graph: Graph, match: dict): """ This pass normalize FC layer Example: (2,16,512)-->FC->(2,16,101) => (2,16,512)-->Reshape-->(32,512)-->FC-->(32,101)-->Reshape-->(2,16,101) """ fc = match['fc'] fc_weights = fc.in_node(1) fc_output = match['fc_output'] fc_input = fc.in_node() input_shape = fc.in_node().shape if len(input_shape) <= 2 or np.prod( fc_input.shape[1:]) == fc_weights.shape[ fc_weights.input_channel_dim]: return # Insert Reshape to normalize input for FC layer that should be in [N,C] layout first_reshape_shape = np.array( [np.prod(input_shape[0:-1]), input_shape[-1]], dtype=np.int64) second_reshape_shape = np.array([*input_shape[0:-1], fc['out-size']], dtype=np.int64) fc_out_shape = np.array([np.prod(input_shape[0:-1]), fc['out-size']], dtype=np.int64) first_reshape = Reshape(graph, {'dim': np.array(first_reshape_shape)}) second_reshape = Reshape(graph, {'dim': np.array(second_reshape_shape)}) input_edge_attrs = graph.get_edge_data(fc_input.id, fc.id)[0] output_edge_attrs = graph.get_edge_data(fc.id, fc_output.id)[0] graph.remove_edge(fc_input.id, fc.id) graph.remove_edge(fc.id, fc_output.id) # Insert Reshapes before and after FullyConnected layer reshape_data = first_reshape.create_node_with_data(inputs=[fc_input]) graph.add_edge(reshape_data.id, fc.id, **input_edge_attrs) new_fc_output = Op.create_data_node(graph, fc, {'shape': fc_out_shape}, edge_attrs=output_edge_attrs) second_reshape.create_node_with_data(inputs=[new_fc_output], data_nodes=fc_output)
def insert_node_with_data_after(self, out, new_op_class: callable, op_after_params: dict = None, additional_inputs: list = None): """ Inserts operation node with op_after_params and data node after current operation :param out: output data node of current node :param new_op_class: class of operation that will be inserted after current operation node :param op_after_params: parameters to be added to operation that will be inserted after current operation :param additional_inputs: other parameters for a new operation node in addition to one that is created at the 'out' placed; new nodes are added after 0-th input TODO Allow indexing for input parameters as well as for 'out' data node to explicitly specify ports that are connected to. Before calling: [...] -> Cur_Op -> Cur_Data -> [...] After calling: [...] -> Cur_Op -> Cur_Data -> New_Op_aft -> New_Data_aft(==out) -> [...] [op_after_params] """ # we import it here because Op imports Node and unique_id from this file from mo.ops.op import Op graph = self.graph node = Node(graph, self.node) cls_name = new_op_class.op op_after_params = {} if op_after_params is None else op_after_params new_op_after = new_op_class(graph, op_after_params) graph.remove_edge(node.id, out.id) new_out = Op.create_data_node(graph, node) node.infer(node) # form a list of input nodes for a new op node combining new_out and additional_inputs inputs = [new_out] + (additional_inputs if additional_inputs else []) new_op_after.create_node_with_data( inputs, {'name': node.name + cls_name + '/After'}, data_nodes=out)
def find_and_replace_pattern(self, graph: Graph): for node in list(graph.nodes()): node = Node(graph, node) # Check that node layout mismatch with graph layout # For example: NHWC and NCHW or NCDHW and NDHWC if node.kind == 'op' and node.has_valid( 'layout') and node.layout != indices_mapping[len( node.layout)][graph.graph['layout']]: input = node.in_node() output = node.out_node() # Calculate permutation for further Transpose operations if graph.graph['layout'] == 'NCHW': # if Node has NCHW and graph has NHWC layout permutation = PermuteAttrs.get_nhwc_to_nchw_permutation( len(node.layout)) else: # if Node has NHWC and graph has NCHW layout permutation = PermuteAttrs.get_nchw_to_nhwc_permutation( len(node.layout)) # Schematic representation of transformation below # # \ NCHW NCHW # NHWC -- \ | permutation permutation | # data-->Convolution(example)-->data -- / | | NCHW | | # / data->Transpose->data->Convolution->data->Transpose->data # 1. Insert input Transpose # This Transpose will permute input from original input layout to operation layout edge_attrs = graph.get_edge_data(input.id, node.id)[0] graph.remove_edge(input.id, node.id) input_order_const = Const(graph, { 'value': permutation.perm }).create_node_with_data() input_permute_op = Transpose( graph, dict(name=node.name + '/Transpose_')) input_permute_data_node = input_permute_op.create_node_with_data( [input, input_order_const]) graph.add_edge(input_permute_data_node.id, node.id, **edge_attrs) # 2. Insert output Transpose # This Transpose will permute output from operation layout to original input layout edge_attrs = graph.get_edge_data(node.id, output.id)[0] graph.remove_edge(node.id, output.id) input_data_node = Op.create_data_node( graph, node, {'shape': output.shape[permutation.perm]}, edge_attrs) output_order_const = Const(graph, { 'value': permutation.inv }).create_node_with_data() output_permute_op = Transpose( graph, dict(name=node.name + '/Transpose_')).create_node_with_data( [input_data_node, output_order_const], data_nodes=output) # 3. Add permutations for Node # Here we use permutation mechanism where data nodes takes permutation attribute. # And then we call permute_attrs method that permutes node attributes according to permutations on # data nodes. node.in_node()['permutation'] = permutation node.out_node()['permutation'] = permutation node.permute_attrs.permute_attrs(node) node.in_node()['permutation'] = None node.out_node()['permutation'] = None