aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Allen Lavoie <allenl@google.com>2017-11-28 15:27:57 -0800
committerGravatar TensorFlower Gardener <gardener@tensorflow.org>2017-11-28 15:31:33 -0800
commita99e9a2c56a4922e76c367b8d3a9c43ea0a4ef61 (patch)
treed45a9775959a208102561e6ffa82343103ca7834
parentd72e2a318c6b15d800aa1468dc2af658ea40dffd (diff)
Support tfe.Network.losses
Supports only variable regularization losses when executing eagerly. They are stored as zero-argument lambdas and executed when the property is requested. PiperOrigin-RevId: 177227550
-rw-r--r--tensorflow/contrib/eager/python/BUILD1
-rw-r--r--tensorflow/contrib/eager/python/network.py24
-rw-r--r--tensorflow/contrib/eager/python/network_test.py29
-rw-r--r--tensorflow/python/layers/base.py85
-rw-r--r--tensorflow/python/layers/base_test.py5
5 files changed, 114 insertions, 30 deletions
diff --git a/tensorflow/contrib/eager/python/BUILD b/tensorflow/contrib/eager/python/BUILD
index bf2e883bc5..55d768044b 100644
--- a/tensorflow/contrib/eager/python/BUILD
+++ b/tensorflow/contrib/eager/python/BUILD
@@ -232,6 +232,7 @@ py_test(
srcs_version = "PY2AND3",
deps = [
":network",
+ "//tensorflow/contrib/layers:layers_py",
"//tensorflow/python:constant_op",
"//tensorflow/python:errors",
"//tensorflow/python:framework_test_lib",
diff --git a/tensorflow/contrib/eager/python/network.py b/tensorflow/contrib/eager/python/network.py
index 0388aaa849..e3c13cbd2e 100644
--- a/tensorflow/contrib/eager/python/network.py
+++ b/tensorflow/contrib/eager/python/network.py
@@ -451,8 +451,30 @@ class Network(base.Layer):
"at https://github.com/tensorflow/tensorflow/issues/new if this is "
"important to you")
+ def add_loss(self, losses, inputs=None):
+ raise RuntimeError(
+ "add_loss is not supported in Network class yet. Please file an issue "
+ "at https://github.com/tensorflow/tensorflow/issues/new if this is "
+ "important to you")
+
+ @property
+ def losses(self):
+ """Gather losses from `Layer`s in the `Network`.
+
+ Note that when executing eagerly, `Layer.losses` evaluates
+ regularizers. When using graph execution, variable regularization ops have
+ already been created and are simply returned here.
+
+ Returns:
+ A list of tensors.
+ """
+ layer_losses = []
+ for layer in self.layers:
+ layer_losses.extend(layer.losses)
+ return layer_losses
+
# TODO(allenl): Support other Layer methods needed for graph mode, such as for
- # losses and updates
+ # updates
class Sequential(Network):
diff --git a/tensorflow/contrib/eager/python/network_test.py b/tensorflow/contrib/eager/python/network_test.py
index e7835a63e6..3eb4f5f8b3 100644
--- a/tensorflow/contrib/eager/python/network_test.py
+++ b/tensorflow/contrib/eager/python/network_test.py
@@ -19,6 +19,7 @@ from __future__ import print_function
import gc
from tensorflow.contrib.eager.python import network
+from tensorflow.contrib.layers.python.layers import regularizers
from tensorflow.python.eager import context
from tensorflow.python.eager import function
from tensorflow.python.eager import test
@@ -45,6 +46,22 @@ class MyNetwork(network.Network):
return self.l1(x)
+class RegularizedNetwork(network.Network):
+
+ def __init__(self):
+ super(RegularizedNetwork, self).__init__()
+ self.l1 = self.track_layer(core.Dense(
+ 1,
+ bias_regularizer=regularizers.l1_regularizer(2.0),
+ kernel_regularizer=regularizers.l1_regularizer(2.0)))
+ self.l2 = self.track_layer(core.Dense(
+ 1,
+ bias_regularizer=regularizers.l1_regularizer(2.0)))
+
+ def call(self, values):
+ return self.l2(self.l1(values))
+
+
class NetworkTest(test.TestCase):
def _save_modify_load_network_built(self, net, global_step=None):
@@ -485,6 +502,18 @@ class NetworkTest(test.TestCase):
checked_ops=checked_ops)
@test_util.run_in_graph_and_eager_modes(assert_no_eager_garbage=True)
+ def testVariableRegularizers(self):
+ net = RegularizedNetwork()
+ net(constant_op.constant([[1.]]))
+ self.evaluate(net.variables[0].assign([[2.]]))
+ self.evaluate(net.variables[1].assign([3.]))
+ self.evaluate(net.variables[2].assign([[-2.]]))
+ self.evaluate(net.variables[3].assign([4.]))
+ self.assertAllEqual([4., 6., 8.], self.evaluate(net.losses))
+ self.evaluate(net.variables[3].assign([5.]))
+ self.assertAllEqual([4., 6., 10.], self.evaluate(net.losses))
+
+ @test_util.run_in_graph_and_eager_modes(assert_no_eager_garbage=True)
def testDuplicateNameError(self):
one = constant_op.constant([[1.]])
net = MyNetwork(name="foo")
diff --git a/tensorflow/python/layers/base.py b/tensorflow/python/layers/base.py
index 6be2bc3e76..c083f8a5d2 100644
--- a/tensorflow/python/layers/base.py
+++ b/tensorflow/python/layers/base.py
@@ -103,10 +103,16 @@ class Layer(object):
self.built = False
self.input_spec = None
+ if activity_regularizer and context.in_eager_mode():
+ raise ValueError(
+ ('Activity regularization is not supported when executing eagerly. '
+ 'Got activity_regularizer=%s') % (activity_regularizer,))
self._activity_regularizer = activity_regularizer
self._trainable_weights = []
self._non_trainable_weights = []
self._updates = []
+ # When executing eagerly, _losses is a list of zero-argument lambdas which
+ # return tensors. When using graph execution, _losses is a list of ops.
self._losses = []
self._reuse = kwargs.get('_reuse')
self._graph = ops.get_default_graph()
@@ -287,9 +293,22 @@ class Layer(object):
@property
def losses(self):
+ """Losses which are associated with this `Layer`.
+
+ Note that when executing eagerly, getting this property evaluates
+ regularizers. When using graph execution, variable regularization ops have
+ already been created and are simply returned here.
+
+ Returns:
+ A list of tensors.
+ """
if context.in_eager_mode():
- raise RuntimeError('Layer.losses not supported in Eager mode.')
- return self._losses
+ # _losses may only contain variable regularization losses when executing
+ # eagerly, and they have been saved as lambdas to be executed when
+ # requested.
+ return [regularizer() for regularizer in self._losses]
+ else:
+ return self._losses
def add_loss(self, losses, inputs=None):
"""Add loss tensor(s), potentially dependent on layer inputs.
@@ -303,6 +322,11 @@ class Layer(object):
The `get_losses_for` method allows to retrieve the losses relevant to a
specific set of inputs.
+ Note that `add_loss` is not supported when executing eagerly. Instead,
+ variable regularizers may be added through `add_variable`. Activity
+ regularization is not supported directly (but such losses may be returned
+ from `Layer.call()`).
+
Arguments:
losses: Loss tensor, or list/tuple of tensors.
inputs: Optional input tensor(s) that the loss(es) depend on. Must
@@ -462,16 +486,8 @@ class Layer(object):
Raises:
RuntimeError: If called in Eager mode with regularizers.
"""
- # Note that we currently don't support variable regularization in Eager
- # mode. An alternative is for users to directly compute these losses before
- # performing a backward pass.
if context.in_graph_mode():
existing_variables = set(tf_variables.global_variables())
- else:
- existing_variables = []
- if regularizer is not None:
- raise RuntimeError('Variable regularization not supported in Eager '
- 'mode.')
if dtype is None:
dtype = self.dtype or dtypes.float32
@@ -486,28 +502,39 @@ class Layer(object):
constraint=constraint,
trainable=trainable and self.trainable,
partitioner=partitioner)
- if (context.in_graph_mode() and trainable and self.trainable
- and variable not in tf_variables.trainable_variables()):
- # A custom getter / variable scope overrode the trainable flag.
- trainable = False
- if variable in existing_variables:
- return variable
- if regularizer:
- # To match the behavior of tf.get_variable(), we only
- # apply regularization if the variable is newly created.
- if isinstance(variable, tf_variables.PartitionedVariable):
- for v in variable:
- with ops.colocate_with(v.op):
+ if context.in_graph_mode():
+ if (trainable and self.trainable
+ and variable not in tf_variables.trainable_variables()):
+ # A custom getter / variable scope overrode the trainable flag.
+ trainable = False
+ if variable in existing_variables:
+ return variable
+ if regularizer:
+ # To match the behavior of tf.get_variable(), we only
+ # apply regularization if the variable is newly created.
+ if isinstance(variable, tf_variables.PartitionedVariable):
+ for v in variable:
+ with ops.colocate_with(v.op):
+ with ops.name_scope(name + '/Regularizer'):
+ regularization = regularizer(v)
+ if regularization is not None:
+ self.add_loss(regularization)
+ else:
+ with ops.colocate_with(variable.op):
with ops.name_scope(name + '/Regularizer'):
- regularization = regularizer(v)
+ regularization = regularizer(variable)
if regularization is not None:
self.add_loss(regularization)
- else:
- with ops.colocate_with(variable.op):
- with ops.name_scope(name + '/Regularizer'):
- regularization = regularizer(variable)
- if regularization is not None:
- self.add_loss(regularization)
+ elif regularizer:
+ if isinstance(variable, tf_variables.PartitionedVariable):
+ raise RuntimeError(
+ 'Partitioned variable regularization is not yet supported when '
+ 'executing eagerly. File a feature request is this is '
+ 'important to you.')
+ # Save a zero-argument lambda which runs the regularizer on the
+ # variable, to be executed when `Layer.losses` is requested. This
+ # makes losses responsive to variable updates when executing eagerly.
+ self._losses.append(lambda: regularizer(variable))
if trainable:
self._trainable_weights.append(variable)
else:
diff --git a/tensorflow/python/layers/base_test.py b/tensorflow/python/layers/base_test.py
index 1eea20deef..3e5a51eb62 100644
--- a/tensorflow/python/layers/base_test.py
+++ b/tensorflow/python/layers/base_test.py
@@ -88,6 +88,11 @@ class BaseLayerTest(test.TestCase):
regularizer=regularizer)
self.assertEqual(len(layer.losses), 1)
+ def testNoEagerActivityRegularizer(self):
+ with context.eager_mode():
+ with self.assertRaisesRegexp(ValueError, 'activity_regularizer'):
+ core_layers.Dense(1, activity_regularizer=lambda *args, **kwargs: 0.)
+
def testGetVariable(self):
with self.test_session():