in onnxconverter_common/topology.py [0:0]
def _resolve_duplicates(self):
'''
Merge variables connected by identity operator to reduce the number of redundant variables
'''
self._initialize_graph_status_for_traversing()
# Traverse the graph from roots to leaves
for operator in self.topological_operator_iterator():
if operator.type != 'identity':
continue
if any(variable.is_root for variable in operator.inputs) and \
any(variable.is_leaf for variable in operator.outputs):
continue
# Replace the output variable with the input variable everywhere
original = operator.inputs[0]
duplicate = operator.outputs[0]
for another_scope in self.scopes:
for another_operator in another_scope.operators.values():
for i in range(len(another_operator.inputs)):
if another_operator.inputs[i].onnx_name != duplicate.onnx_name:
continue
another_operator.inputs[i] = original
# When original variable's documentation string or denotation is empty but duplicate's is not, we
# copy that field to the original variable to avoid information loss.
if not original.type.doc_string and duplicate.type.doc_string:
original.type.doc_string = duplicate.type.doc_string
if isinstance(original.type, TensorType) and isinstance(duplicate.type, TensorType):
if not original.type.denotation and duplicate.type.denotation:
original.type.denotation = duplicate.type.denotation
if not original.type.channel_denotations:
original.type.channel_denotations = duplicate.type.channel_denotations
elif duplicate.type.channel_denotations:
# Merge the channel denotations if available in both the original and the duplicate
for i in range(len(original.type.channel_denotations)):
if original.type.channel_denotations[i]:
continue
original.type.channel_denotations[i] = duplicate.type.channel_denotations[i]
# Sometime, shapes of duplicates are different.
# We try to replace the original variable's unknown dimensions...
# ...as many as possible because we will get rid of the duplicate.
if len(original.type.shape) == len(duplicate.type.shape):
for i in range(len(original.type.shape)):
if original.type.shape[i] != 'None':
continue
original.type.shape[i] = duplicate.type.shape[i]
# Because we're iterating through the topology, we cannot delete any operator or variable. Otherwise,
# the traversing function may be broken. We will delete those abandoned ones later.
duplicate.is_abandoned = True
operator.is_abandoned = True
for scope in self.scopes:
# Find out who is going to be abandoned
abandoned_operator_names = set(onnx_name for onnx_name, operator in scope.operators.items()
if operator.is_abandoned)
abandoned_variable_names = set(onnx_name for onnx_name, variable in scope.variables.items()
if variable.is_abandoned)
# Remove abandoned operators
for name in abandoned_operator_names:
scope.delete_local_operator(name)
# Remove abandoned variables
for name in abandoned_variable_names:
scope.delete_local_variable(name)