def version_11(cls, node, **kwargs): default_dtype = mapping.NP_TYPE_TO_TENSOR_TYPE[np.dtype('float32')] dtype = data_type.onnx2tf(node.attrs.get("dtype", default_dtype)) ragged = tf.RaggedTensor.from_row_lengths(values=[], row_lengths=[]) sparse = tf.cast(ragged.to_sparse(), dtype) return [tf.RaggedTensor.from_sparse(sparse)]
def _common(cls, node, **kwargs): attr_value = node.attrs["value"] dtype = data_type.onnx2tf(attr_value.data_type) value = numpy_helper.to_array(attr_value) return [ cls.make_tensor_from_onnx_node(node, inputs=[value], attrs={"dtype": dtype}) ]
def _onnx_initializer_to_input_dict_items(cls, initializer): """ Convert ONNX graph initializer to input dict items. :param initializer: ONNX graph initializer, list of TensorProto. :return: List of input dict items. """ def tensor2list(onnx_tensor): # Use the onnx.numpy_helper because the data may be raw return numpy_helper.to_array(onnx_tensor).flatten().tolist() def validate_initializer_name(name): # Replace ":" with "_tf_" and append a unique suffix for # traceability return name.replace(":", "_tf_") + "_" + get_unique_suffix( ) if ":" in name else name return [(init.name, tf.constant(tensor2list(init), shape=init.dims, dtype=data_type.onnx2tf(init.data_type), name=validate_initializer_name(init.name))) for init in initializer]
def scan(cls, node, input_dict, strict): current_opset = [make_opsetid(cls.DOMAIN, cls.VERSION)] body = node.attrs["body"] # in version 8, node.inputs[0] is the sequence_lens node_inputs = node.inputs if cls.SINCE_VERSION != 8 else \ node.inputs[1:] # M num_scan_inputs = int(node.attrs["num_scan_inputs"]) # N = num_inputs - M num_state_vars = len(node_inputs) - num_scan_inputs # K = num_outputs - N num_scan_outputs = len(node.outputs) - num_state_vars """ Function to run subgraph used with tf.scan """ def run_subgraph(a, b): input_values = {} # set the input values for the subgraph # set the values for the state variables for i in range(num_state_vars): input_values[body.input[i].name] = a[i] # set the values for the scan inputs for i in range(num_scan_inputs): input_values[body.input[i + num_state_vars].name] = b[i] # get the tensor operations for the onnx graph tensor_dict = \ backend.onnx_graph_to_tensorflow_ops( graph_def=body, input_values=input_values, opset=current_opset, strict=strict) # return sequence of tensors for every subgraph output outputs = [tensor_dict[output.name] for output in body.output] return outputs scan_input_axes = node.attrs.get("scan_input_axes", [0] * num_scan_inputs) scan_input_directions = node.attrs.get("directions" if cls.SINCE_VERSION == 8 else "scan_input_directions", [0] * num_scan_inputs) scan_output_axes = node.attrs.get("scan_output_axes", [0] * num_scan_outputs) scan_output_directions = node.attrs.get("scan_output_directions", [0] * num_scan_outputs) # if version 8 read the sequnce_lens from the first input if cls.SINCE_VERSION == 8: sequence_lens = input_dict[node.inputs[0]] \ if node.inputs[0] != '' else None inputs = [input_dict[node_input] for node_input in node_inputs] scan_inputs = inputs[num_state_vars:] # loop over all the scan inputs and apply transpose depending # on input axes provided and also reverse the scan inputs if # reverse direction for scan is provided for i in range(num_scan_inputs): # if input axes are different than 0, use transpose to scan over # the provided axes if scan_input_axes[i] != 0: transpose_perm = cls._calc_transpose_perm_input( tf.rank(scan_inputs[i]), scan_input_axes[i]) scan_inputs[i] = tf.transpose(scan_inputs[i], transpose_perm) # check for reverse direction scans if scan_input_directions[i] == 1: # version 8 has a batch dimension axis = 0 if cls.SINCE_VERSION != 8 else 1 scan_inputs[i] = tf.reverse(scan_inputs[i], [axis]) state_vars_init = inputs[:num_state_vars] scan_outputs_init = [] # generate sequence of zero tensors for all scan outputs # with the correct shape and dtype for scan_output in body.output[num_state_vars:]: tensor_type = scan_output.type.tensor_type shape = [ d.dim_value if (d.dim_value > 0 and d.dim_param == "") else None for d in tensor_type.shape.dim ] dtype = data_type.onnx2tf(tensor_type.elem_type) scan_outputs_init.append(tf.zeros(shape, dtype=dtype)) # tf.scan initilizer is state_variables_init + scan_outputs_init initializer = state_vars_init + scan_outputs_init if cls.SINCE_VERSION == 8: # version == 8 # function to process the batches. it is used with tf.map_fn def run_batches(x): # state vars initial values per batch initial = x[0] # scan inputs per batch scan_inputs = x[1] # sequence length for the batch seq_len = x[2] # slice the input to the current sequence len scan_inputs = [scan_input[:seq_len, ...] for scan_input in scan_inputs] # run scan on the current batch out = tf.scan( run_subgraph, scan_inputs, initializer=initial + scan_outputs_init) # pad to the original shape with zeros paddings = [[0, tf.shape(x[1][0], out_type=seq_len.dtype)[0] - seq_len]] for i in range(len(out)): pads = tf.concat( [paddings, tf.zeros([(tf.rank(out[i]) - 1), 2], dtype=tf.int32)], axis=0) out[i] = tf.pad(out[i], pads) return out if sequence_lens is None: # if sequence_lens is None, fill it with the shape of # the input axis 1 sequence_lens = tf.fill([tf.shape(scan_inputs[0])[0]], tf.shape(scan_inputs[0], out_type=tf.int32)[1]) output_types = [ data_type.onnx2tf(output.type.tensor_type.elem_type) for output in body.output ] # run scan for every batch out = tf.map_fn( run_batches, (state_vars_init, scan_inputs, sequence_lens), dtype=output_types) state_vars_outputs = [] # extract the final values of the state variables for state_var in out[:num_state_vars]: state_vars_outputs.append( tf.map_fn(lambda x: x[0][x[1] - 1], (state_var, sequence_lens), state_var.dtype)) else: # version > 8 # run the scan out = tf.scan(run_subgraph, scan_inputs, initializer=initializer) # extract the final values of the state variables state_vars_outputs = [ state_var[tf.shape(state_var)[0] - 1] for state_var in out[:num_state_vars] ] scan_outputs = out[num_state_vars:] # post process the scan outputs depending on the directions and # axes provided. for i in range(num_scan_outputs): # check for reverse direction scan outputs if scan_output_directions[i] == 1: scan_outputs[i] = tf.reverse(scan_outputs[i], [0]) if scan_output_axes[i] != 0: transpose_perm = cls._calc_transpose_perm_output( tf.rank(scan_outputs[i]), scan_output_axes[i]) scan_outputs[i] = tf.transpose(scan_outputs[i], transpose_perm) return state_vars_outputs + scan_outputs
def _onnx_graph_to_tensorflow_rep(cls, graph_def, opset, strict): """ Convert ONNX graph to TensorflowRep. :param graph_def: ONNX GraphProto object. :param opset: ONNX OperatorSetIdProto list. :param strict: whether to enforce semantic equivalence between the original model and the converted tensorflow model. :return: TensorflowRep object. """ handlers = cls._get_handlers(opset) tf_rep_graph = tf.Graph() with tf_rep_graph.as_default(): # initializer: TensorProtos representing the values to initialize # a given tensor. # initialized: A list of names of the initialized tensors. if graph_def.initializer: input_dict_items = cls._onnx_initializer_to_input_dict_items( graph_def.initializer) initialized = {init.name for init in graph_def.initializer} else: input_dict_items = [] initialized = set() # creating placeholders for currently unknown inputs for value_info in graph_def.input: if value_info.name in initialized: continue shape = list(d.dim_value if ( d.dim_value > 0 and d.dim_param == "") else None for d in value_info.type.tensor_type.shape.dim) value_info_name = value_info.name.replace( ":", "_tf_") + "_" + get_unique_suffix( ) if ":" in value_info.name else value_info.name x = tf.compat.v1.placeholder(data_type.onnx2tf( value_info.type.tensor_type.elem_type), name=value_info_name, shape=shape) input_dict_items.append((value_info.name, x)) # tensor dict: this dictionary is a map from variable names # to the latest produced TF tensors of the given name. # This dictionary will get updated as we build the graph to # record the names of newly produced tensors. tensor_dict = dict(input_dict_items) # Since tensor dict may be updated, we need to keep a copy # of the original input dict where we track the earliest # defined tensors so we can have access to the placeholders # to feed in input tensors when we run the graph. input_dict = dict(input_dict_items) for node in graph_def.node: onnx_node = OnnxNode(node) output_ops = cls._onnx_node_to_tensorflow_op(onnx_node, tensor_dict, handlers, opset=opset, strict=strict) curr_node_output_map = dict(zip(onnx_node.outputs, output_ops)) tensor_dict.update(curr_node_output_map) tf_rep = TensorflowRep() tf_rep.graph = tf_rep_graph tf_rep.inputs = [ value_info.name for value_info in graph_def.input if value_info.name not in initialized ] tf_rep.outputs = [value_info.name for value_info in graph_def.output] tf_rep.tensor_dict = tensor_dict return tf_rep
"value": lambda x: MakeNdarray(x.tensor), "seed2": lambda x: float(x.i), "seed": lambda x: float(x.i), "keep_dims": lambda x: int(x.b), "squeeze_dims": lambda x: list(x.list.i), } __onnx_attr_translator = { "axis": lambda x: int(x), "axes": lambda x: [int(a) for a in x], "dtype": lambda x: data_type.onnx2tf(x), "keepdims": lambda x: bool(x), "to": lambda x: data_type.onnx2tf(x), } def translate_tf(key, val): return __tf_attr_translator.get(key, lambda x: x)(val) def translate_onnx(key, val): return __onnx_attr_translator.get(key, lambda x: x)(val) def get_tf_shape_as_list(tf_shape_dim): return list(map(lambda x: x.size, list(tf_shape_dim)))