Fix github issue #43824 https://github.com/tensorflow/tensorflow/issues/43824 by adding any observed layer names (e.g. when loading models) to the name generation avoid list when generating op layers.
Note: we can't make this change for all layers because that would change layer names in cases where users are loading model backbones for transfer learning. That would have a high risk of breaking users. Generated op layers are lower risk because they don't contain variables and usually aren't referred to by name, plus users can't work around name collisions as easily for generated layers (as they can't explicitly set the layer name if they have a problem). PiperOrigin-RevId: 336166917 Change-Id: I8b7e68ad9df62142d8b29c02c947923f6aaff3a1
This commit is contained in:
parent
3eed7f3438
commit
62d414ba03
@ -107,6 +107,17 @@ _CURRENT_SCRATCH_GRAPH = threading.local()
|
||||
_SESSION = threading.local()
|
||||
|
||||
|
||||
# A global dictionary mapping graph objects to an index of counters used
|
||||
# for various layer/optimizer names in each graph.
|
||||
# Allows to give unique autogenerated names to layers, in a graph-specific way.
|
||||
PER_GRAPH_OBJECT_NAME_UIDS = weakref.WeakKeyDictionary()
|
||||
|
||||
|
||||
# A global set tracking what object names have been seen so far.
|
||||
# Optionally used as an avoid-list when generaing names
|
||||
OBSERVED_NAMES = set()
|
||||
|
||||
|
||||
# _DUMMY_EAGER_GRAPH.key is used as a key in _GRAPH_LEARNING_PHASES.
|
||||
# We keep a separate reference to it to make sure it does not get removed from
|
||||
# _GRAPH_LEARNING_PHASES.
|
||||
@ -210,12 +221,6 @@ def cast_to_floatx(x):
|
||||
return np.asarray(x, dtype=floatx())
|
||||
|
||||
|
||||
# A global dictionary mapping graph objects to an index of counters used
|
||||
# for various layer/optimizer names in each graph.
|
||||
# Allows to give unique autogenerated names to layers, in a graph-specific way.
|
||||
PER_GRAPH_OBJECT_NAME_UIDS = weakref.WeakKeyDictionary()
|
||||
|
||||
|
||||
@keras_export('keras.backend.get_uid')
|
||||
def get_uid(prefix=''):
|
||||
"""Associates a string prefix with an integer counter in a TensorFlow graph.
|
||||
@ -248,6 +253,7 @@ def reset_uids():
|
||||
"""
|
||||
|
||||
PER_GRAPH_OBJECT_NAME_UIDS.clear()
|
||||
OBSERVED_NAMES.clear()
|
||||
|
||||
|
||||
@keras_export('keras.backend.clear_session')
|
||||
@ -999,11 +1005,17 @@ def track_variable(v):
|
||||
_GRAPH_VARIABLES[graph].add(v)
|
||||
|
||||
|
||||
def observe_object_name(name):
|
||||
"""Observe a name and make sure it won't be used by `unique_object_name`."""
|
||||
OBSERVED_NAMES.add(name)
|
||||
|
||||
|
||||
def unique_object_name(name,
|
||||
name_uid_map=None,
|
||||
avoid_names=None,
|
||||
namespace='',
|
||||
zero_based=False):
|
||||
zero_based=False,
|
||||
avoid_observed_names=False):
|
||||
"""Makes a object name (or arbitrary string) unique within a TensorFlow graph.
|
||||
|
||||
Arguments:
|
||||
@ -1011,12 +1023,15 @@ def unique_object_name(name,
|
||||
name_uid_map: An optional defaultdict(int) to use when creating unique
|
||||
names. If None (default), uses a per-Graph dictionary.
|
||||
avoid_names: An optional set or dict with names which should not be used. If
|
||||
None (default) does not avoid any names.
|
||||
None (default), don't avoid any names unless `avoid_observed_names` is
|
||||
True.
|
||||
namespace: Gets a name which is unique within the (graph, namespace). Layers
|
||||
which are not Networks use a blank namespace and so get graph-global
|
||||
names.
|
||||
zero_based: If True, name sequences start with no suffix (e.g. "dense",
|
||||
"dense_1"). If False, naming is one-based ("dense_1", "dense_2").
|
||||
avoid_observed_names: If True, avoid any names that have been observed by
|
||||
`backend.observe_object_name`.
|
||||
|
||||
Returns:
|
||||
Unique string name.
|
||||
@ -1031,7 +1046,10 @@ def unique_object_name(name,
|
||||
if name_uid_map is None:
|
||||
name_uid_map = get_default_graph_uid_map()
|
||||
if avoid_names is None:
|
||||
avoid_names = set()
|
||||
if avoid_observed_names:
|
||||
avoid_names = OBSERVED_NAMES
|
||||
else:
|
||||
avoid_names = set()
|
||||
proposed_name = None
|
||||
while proposed_name is None or proposed_name in avoid_names:
|
||||
name_key = (namespace, name)
|
||||
|
@ -2438,6 +2438,7 @@ class Layer(module.Module, version_utils.LayerVersionSelector):
|
||||
generic_utils.to_snake_case(self.__class__.__name__),
|
||||
zero_based=zero_based)
|
||||
else:
|
||||
backend.observe_object_name(name)
|
||||
self._name = name
|
||||
|
||||
def _get_existing_metric(self, name=None):
|
||||
|
@ -469,6 +469,32 @@ class BaseLayerTest(keras_parameterized.TestCase):
|
||||
]
|
||||
self.assertAllEqual(actual_names, expected_names)
|
||||
|
||||
@combinations.generate(combinations.combine(mode=['graph', 'eager']))
|
||||
def test_layer_names_after_loading(self):
|
||||
if context.executing_eagerly():
|
||||
backend.clear_session()
|
||||
with testing_utils.use_keras_tensors_scope(True):
|
||||
# Mimic loading a model that already contained add layers with
|
||||
# name = 'add_1' and 'tf.__operators__.add'
|
||||
layers.Add(name='add_1')
|
||||
layers.Add(name='tf.__operators__.add')
|
||||
|
||||
inputs = input_layer.Input(shape=[2])
|
||||
add1 = inputs + inputs
|
||||
add2 = layers.Add()([inputs, inputs])
|
||||
add3 = inputs + inputs
|
||||
add4 = layers.Add()([inputs, inputs])
|
||||
model = training_lib.Model(
|
||||
inputs=[inputs], outputs=[add1, add2, add3, add4])
|
||||
actual_names = [l.name for l in model.layers]
|
||||
# The generated op layer names should have avoided layer names seen in
|
||||
# the loaded model. (This avoiance should not apply to non-op-layers)
|
||||
expected_names = [
|
||||
'input_1', 'tf.__operators__.add_1',
|
||||
'add', 'tf.__operators__.add_2', 'add_1'
|
||||
]
|
||||
self.assertAllEqual(actual_names, expected_names)
|
||||
|
||||
def test_add_trainable_weight_on_frozen_layer(self):
|
||||
|
||||
class TestLayer(base_layer.Layer):
|
||||
|
@ -1304,8 +1304,16 @@ class TFOpLambda(Layer):
|
||||
api_name='keras',
|
||||
add_prefix_to_v1_names=True))
|
||||
if 'name' not in kwargs:
|
||||
# Generate a name.
|
||||
# TFOpLambda layers avoid already-observed names,
|
||||
# because users cannot easily control the generated names.
|
||||
# Without this avoidance, users would be more likely to run
|
||||
# into unavoidable duplicate layer name collisions.
|
||||
# (For standard layers users could just set `name` when creating the
|
||||
# layer to work around a collision, but they can't do that for
|
||||
# auto-generated layers)
|
||||
kwargs['name'] = K.unique_object_name(
|
||||
'tf.' + self.symbol, zero_based=True)
|
||||
'tf.' + self.symbol, zero_based=True, avoid_observed_names=True)
|
||||
kwargs['autocast'] = False
|
||||
|
||||
# Decorate the function to produce this layer's call method
|
||||
|
Loading…
Reference in New Issue
Block a user