1407 lines
50 KiB
Python
1407 lines
50 KiB
Python
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
|
|
"""Operations to emit summaries."""
|
|
|
|
import abc
|
|
import collections
|
|
import functools
|
|
import os
|
|
import re
|
|
import threading
|
|
|
|
from tensorflow.core.framework import graph_pb2
|
|
from tensorflow.core.framework import summary_pb2
|
|
from tensorflow.core.protobuf import config_pb2
|
|
from tensorflow.python.eager import context
|
|
from tensorflow.python.eager import profiler as _profiler
|
|
from tensorflow.python.framework import constant_op
|
|
from tensorflow.python.framework import dtypes
|
|
from tensorflow.python.framework import ops
|
|
from tensorflow.python.framework import smart_cond
|
|
from tensorflow.python.framework import tensor_util
|
|
from tensorflow.python.ops import array_ops
|
|
from tensorflow.python.ops import control_flow_ops
|
|
from tensorflow.python.ops import gen_resource_variable_ops
|
|
from tensorflow.python.ops import gen_summary_ops
|
|
from tensorflow.python.ops import math_ops
|
|
from tensorflow.python.ops import resource_variable_ops
|
|
from tensorflow.python.ops import summary_op_util
|
|
from tensorflow.python.platform import tf_logging as logging
|
|
from tensorflow.python.trackable import resource
|
|
from tensorflow.python.training import training_util
|
|
from tensorflow.python.util import deprecation
|
|
from tensorflow.python.util import tf_contextlib
|
|
from tensorflow.python.util.tf_export import tf_export
|
|
|
|
# Name for graph collection of summary writer init ops, which is only exposed
|
|
# as a legacy API for tf.contrib.summary in TF 1.x.
|
|
_SUMMARY_WRITER_INIT_COLLECTION_NAME = "_SUMMARY_WRITER_V2"
|
|
|
|
|
|
class _SummaryState(threading.local):
|
|
|
|
def __init__(self):
|
|
super(_SummaryState, self).__init__()
|
|
self.is_recording = None
|
|
# TODO(slebedev): why a separate flag for DS and is it on by default?
|
|
self.is_recording_distribution_strategy = True
|
|
self.writer = None
|
|
self.step = None
|
|
|
|
|
|
_summary_state = _SummaryState()
|
|
|
|
|
|
class _SummaryContextManager:
|
|
"""Context manager to implement SummaryWriter.as_default()."""
|
|
# Note: this is a class so that it's possible to implement `set_as_default()`
|
|
# simply via `as_default().__enter__()`. We can't do that with @contextmanager
|
|
# because the `finally` block will be executed when the generator is GCed.
|
|
|
|
def __init__(self, writer, step=None):
|
|
self._writer = writer
|
|
self._step = step
|
|
self._old_writer = None
|
|
self._old_step = None
|
|
|
|
def __enter__(self):
|
|
self._old_writer = _summary_state.writer
|
|
_summary_state.writer = self._writer
|
|
if self._step is not None:
|
|
self._old_step = _summary_state.step
|
|
_summary_state.step = self._step
|
|
return self._writer
|
|
|
|
def __exit__(self, *exc):
|
|
# Flushes the summary writer in eager mode or in graph functions, but
|
|
# not in legacy graph mode (you're on your own there).
|
|
_summary_state.writer.flush()
|
|
_summary_state.writer = self._old_writer
|
|
if self._step is not None:
|
|
_summary_state.step = self._old_step
|
|
return False
|
|
|
|
|
|
def _should_record_summaries_internal(default_state):
|
|
"""Returns boolean Tensor if summaries should/shouldn't be recorded.
|
|
|
|
Now the summary condition is decided by logical "and" of below conditions:
|
|
First, summary writer must be set. Given this constraint is met,
|
|
ctx.summary_recording and ctx.summary_recording_distribution_strategy.
|
|
The former one is usually set by user, and the latter one is controlled
|
|
by DistributionStrategy (tf.distribute.ReplicaContext).
|
|
|
|
Args:
|
|
default_state: can be True or False. The default summary behavior when
|
|
summary writer is set and the user does not specify
|
|
ctx.summary_recording and ctx.summary_recording_distribution_strategy
|
|
is True.
|
|
"""
|
|
if _summary_state.writer is None:
|
|
return constant_op.constant(False)
|
|
|
|
if not callable(_summary_state.is_recording):
|
|
static_cond = tensor_util.constant_value(_summary_state.is_recording)
|
|
if static_cond is not None and not static_cond:
|
|
return constant_op.constant(False)
|
|
|
|
resolve = lambda x: x() if callable(x) else x
|
|
cond_distributed = resolve(_summary_state.is_recording_distribution_strategy)
|
|
cond = resolve(_summary_state.is_recording)
|
|
if cond is None:
|
|
cond = default_state
|
|
return math_ops.logical_and(cond_distributed, cond)
|
|
|
|
|
|
@tf_export("summary.should_record_summaries", v1=[])
|
|
def should_record_summaries():
|
|
"""Returns boolean Tensor which is True if summaries will be recorded.
|
|
|
|
If no default summary writer is currently registered, this always returns
|
|
False. Otherwise, this reflects the recording condition has been set via
|
|
`tf.summary.record_if()` (except that it may return False for some replicas
|
|
when using `tf.distribute.Strategy`). If no recording condition is active,
|
|
it defaults to True.
|
|
"""
|
|
return _should_record_summaries_internal(default_state=True)
|
|
|
|
|
|
# Legacy symbol used by tf.contrib.summary.should_record_summaries.
|
|
def _legacy_contrib_should_record_summaries():
|
|
"""Returns boolean Tensor which is true if summaries should be recorded."""
|
|
return _should_record_summaries_internal(default_state=False)
|
|
|
|
|
|
@tf_export("summary.record_if", v1=[])
|
|
@tf_contextlib.contextmanager
|
|
def record_if(condition):
|
|
"""Sets summary recording on or off per the provided boolean value.
|
|
|
|
The provided value can be a python boolean, a scalar boolean Tensor, or
|
|
or a callable providing such a value; if a callable is passed it will be
|
|
invoked on-demand to determine whether summary writing will occur. Note that
|
|
when calling record_if() in an eager mode context, if you intend to provide a
|
|
varying condition like `step % 100 == 0`, you must wrap this in a
|
|
callable to avoid immediate eager evaluation of the condition. In particular,
|
|
using a callable is the only way to have your condition evaluated as part of
|
|
the traced body of an @tf.function that is invoked from within the
|
|
`record_if()` context.
|
|
|
|
Args:
|
|
condition: can be True, False, a bool Tensor, or a callable providing such.
|
|
|
|
Yields:
|
|
Returns a context manager that sets this value on enter and restores the
|
|
previous value on exit.
|
|
"""
|
|
old = _summary_state.is_recording
|
|
try:
|
|
_summary_state.is_recording = condition
|
|
yield
|
|
finally:
|
|
_summary_state.is_recording = old
|
|
|
|
|
|
def has_default_writer():
|
|
"""Returns a boolean indicating whether a default summary writer exists."""
|
|
return _summary_state.writer is not None
|
|
|
|
|
|
# TODO(apassos) consider how to handle local step here.
|
|
def record_summaries_every_n_global_steps(n, global_step=None):
|
|
"""Sets the should_record_summaries Tensor to true if global_step % n == 0."""
|
|
if global_step is None:
|
|
global_step = training_util.get_or_create_global_step()
|
|
with ops.device("cpu:0"):
|
|
should = lambda: math_ops.equal(global_step % n, 0)
|
|
if not context.executing_eagerly():
|
|
should = should()
|
|
return record_if(should)
|
|
|
|
|
|
def always_record_summaries():
|
|
"""Sets the should_record_summaries Tensor to always true."""
|
|
return record_if(True)
|
|
|
|
|
|
def never_record_summaries():
|
|
"""Sets the should_record_summaries Tensor to always false."""
|
|
return record_if(False)
|
|
|
|
|
|
@tf_export("summary.experimental.get_step", v1=[])
|
|
def get_step():
|
|
"""Returns the default summary step for the current thread.
|
|
|
|
Returns:
|
|
The step set by `tf.summary.experimental.set_step()` if one has been set,
|
|
otherwise None.
|
|
"""
|
|
return _summary_state.step
|
|
|
|
|
|
@tf_export("summary.experimental.set_step", v1=[])
|
|
def set_step(step):
|
|
"""Sets the default summary step for the current thread.
|
|
|
|
For convenience, this function sets a default value for the `step` parameter
|
|
used in summary-writing functions elsewhere in the API so that it need not
|
|
be explicitly passed in every such invocation. The value can be a constant
|
|
or a variable, and can be retrieved via `tf.summary.experimental.get_step()`.
|
|
|
|
Note: when using this with @tf.functions, the step value will be captured at
|
|
the time the function is traced, so changes to the step outside the function
|
|
will not be reflected inside the function unless using a `tf.Variable` step.
|
|
|
|
Args:
|
|
step: An `int64`-castable default step value, or None to unset.
|
|
"""
|
|
_summary_state.step = step
|
|
|
|
|
|
@tf_export("summary.SummaryWriter", v1=[])
|
|
class SummaryWriter(metaclass=abc.ABCMeta):
|
|
"""Interface representing a stateful summary writer object."""
|
|
|
|
def set_as_default(self, step=None):
|
|
"""Enables this summary writer for the current thread.
|
|
|
|
For convenience, if `step` is not None, this function also sets a default
|
|
value for the `step` parameter used in summary-writing functions elsewhere
|
|
in the API so that it need not be explicitly passed in every such
|
|
invocation. The value can be a constant or a variable.
|
|
|
|
Note: when setting `step` in a @tf.function, the step value will be
|
|
captured at the time the function is traced, so changes to the step outside
|
|
the function will not be reflected inside the function unless using
|
|
a `tf.Variable` step.
|
|
|
|
Args:
|
|
step: An `int64`-castable default step value, or `None`. When not `None`,
|
|
the current step is modified to the given value. When `None`, the
|
|
current step is not modified.
|
|
"""
|
|
self.as_default(step).__enter__()
|
|
|
|
def as_default(self, step=None):
|
|
"""Returns a context manager that enables summary writing.
|
|
|
|
For convenience, if `step` is not None, this function also sets a default
|
|
value for the `step` parameter used in summary-writing functions elsewhere
|
|
in the API so that it need not be explicitly passed in every such
|
|
invocation. The value can be a constant or a variable.
|
|
|
|
Note: when setting `step` in a @tf.function, the step value will be
|
|
captured at the time the function is traced, so changes to the step outside
|
|
the function will not be reflected inside the function unless using
|
|
a `tf.Variable` step.
|
|
|
|
For example, `step` can be used as:
|
|
|
|
```python
|
|
with writer_a.as_default(step=10):
|
|
tf.summary.scalar(tag, value) # Logged to writer_a with step 10
|
|
with writer_b.as_default(step=20):
|
|
tf.summary.scalar(tag, value) # Logged to writer_b with step 20
|
|
tf.summary.scalar(tag, value) # Logged to writer_a with step 10
|
|
```
|
|
|
|
Args:
|
|
step: An `int64`-castable default step value, or `None`. When not `None`,
|
|
the current step is captured, replaced by a given one, and the original
|
|
one is restored when the context manager exits. When `None`, the current
|
|
step is not modified (and not restored when the context manager exits).
|
|
|
|
Returns:
|
|
The context manager.
|
|
"""
|
|
return _SummaryContextManager(self, step)
|
|
|
|
def init(self):
|
|
"""Initializes the summary writer."""
|
|
raise NotImplementedError()
|
|
|
|
def flush(self):
|
|
"""Flushes any buffered data."""
|
|
raise NotImplementedError()
|
|
|
|
def close(self):
|
|
"""Flushes and closes the summary writer."""
|
|
raise NotImplementedError()
|
|
|
|
|
|
class _ResourceSummaryWriter(SummaryWriter):
|
|
"""Implementation of SummaryWriter using a SummaryWriterInterface resource."""
|
|
|
|
def __init__(self, create_fn, init_op_fn):
|
|
self._resource = create_fn()
|
|
self._init_op = init_op_fn(self._resource)
|
|
self._closed = False
|
|
if context.executing_eagerly():
|
|
self._set_up_resource_deleter()
|
|
else:
|
|
ops.add_to_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME, self._init_op)
|
|
|
|
# Extension point to be overridden by subclasses to customize deletion.
|
|
|
|
def _set_up_resource_deleter(self):
|
|
self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
|
|
handle=self._resource, handle_device="cpu:0")
|
|
|
|
def set_as_default(self, step=None):
|
|
"""See `SummaryWriter.set_as_default`."""
|
|
if context.executing_eagerly() and self._closed:
|
|
raise RuntimeError(f"SummaryWriter {self!r} is already closed")
|
|
super().set_as_default(step)
|
|
|
|
def as_default(self, step=None):
|
|
"""See `SummaryWriter.as_default`."""
|
|
if context.executing_eagerly() and self._closed:
|
|
raise RuntimeError(f"SummaryWriter {self!r} is already closed")
|
|
return super().as_default(step)
|
|
|
|
def init(self):
|
|
"""See `SummaryWriter.init`."""
|
|
if context.executing_eagerly() and self._closed:
|
|
raise RuntimeError(f"SummaryWriter {self!r} is already closed")
|
|
return self._init_op
|
|
|
|
def flush(self):
|
|
"""See `SummaryWriter.flush`."""
|
|
if context.executing_eagerly() and self._closed:
|
|
return
|
|
with ops.device("cpu:0"):
|
|
return gen_summary_ops.flush_summary_writer(self._resource)
|
|
|
|
def close(self):
|
|
"""See `SummaryWriter.close`."""
|
|
if context.executing_eagerly() and self._closed:
|
|
return
|
|
try:
|
|
with ops.control_dependencies([self.flush()]):
|
|
with ops.device("cpu:0"):
|
|
return gen_summary_ops.close_summary_writer(self._resource)
|
|
finally:
|
|
if context.executing_eagerly():
|
|
self._closed = True
|
|
|
|
|
|
class _MultiMetaclass(
|
|
type(_ResourceSummaryWriter), type(resource.TrackableResource)):
|
|
pass
|
|
|
|
|
|
class _TrackableResourceSummaryWriter(
|
|
_ResourceSummaryWriter,
|
|
resource.TrackableResource,
|
|
metaclass=_MultiMetaclass):
|
|
"""A `_ResourceSummaryWriter` subclass that implements `TrackableResource`."""
|
|
|
|
def __init__(self, create_fn, init_op_fn):
|
|
# Resolve multiple inheritance via explicit calls to __init__() on parents.
|
|
resource.TrackableResource.__init__(self, device="/CPU:0")
|
|
self._create_fn = create_fn
|
|
self._init_op_fn = init_op_fn
|
|
# Pass .resource_handle into _ResourceSummaryWriter parent class rather than
|
|
# create_fn, to ensure it accesses the resource handle only through the
|
|
# cached property so that everything is using a single resource handle.
|
|
_ResourceSummaryWriter.__init__(
|
|
self, create_fn=lambda: self.resource_handle, init_op_fn=init_op_fn)
|
|
|
|
# Override for TrackableResource implementation.
|
|
def _create_resource(self):
|
|
return self._create_fn()
|
|
|
|
# Override for TrackableResource implementation.
|
|
def _initialize(self):
|
|
return self._init_op_fn(self.resource_handle)
|
|
|
|
# Override for TrackableResource implementation.
|
|
def _destroy_resource(self):
|
|
gen_resource_variable_ops.destroy_resource_op(
|
|
self.resource_handle, ignore_lookup_error=True)
|
|
|
|
def _set_up_resource_deleter(self):
|
|
# Override to suppress ResourceSummaryWriter implementation; we don't need
|
|
# the deleter since TrackableResource.__del__() handles it for us.
|
|
pass
|
|
|
|
|
|
class _LegacyResourceSummaryWriter(SummaryWriter):
|
|
"""Legacy resource-backed SummaryWriter for tf.contrib.summary."""
|
|
|
|
def __init__(self, resource, init_op_fn):
|
|
self._resource = resource
|
|
self._init_op_fn = init_op_fn
|
|
init_op = self.init()
|
|
if context.executing_eagerly():
|
|
self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
|
|
handle=self._resource, handle_device="cpu:0")
|
|
else:
|
|
ops.add_to_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME, init_op)
|
|
|
|
def init(self):
|
|
"""See `SummaryWriter.init`."""
|
|
return self._init_op_fn(self._resource)
|
|
|
|
def flush(self):
|
|
"""See `SummaryWriter.flush`."""
|
|
with ops.device("cpu:0"):
|
|
return gen_summary_ops.flush_summary_writer(self._resource)
|
|
|
|
def close(self):
|
|
"""See `SummaryWriter.close`."""
|
|
with ops.control_dependencies([self.flush()]):
|
|
with ops.device("cpu:0"):
|
|
return gen_summary_ops.close_summary_writer(self._resource)
|
|
|
|
|
|
class _NoopSummaryWriter(SummaryWriter):
|
|
"""A summary writer that does nothing, for create_noop_writer()."""
|
|
|
|
def set_as_default(self, step=None):
|
|
pass
|
|
|
|
@tf_contextlib.contextmanager
|
|
def as_default(self, step=None):
|
|
yield
|
|
|
|
def init(self):
|
|
pass
|
|
|
|
def flush(self):
|
|
pass
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
@tf_export(v1=["summary.initialize"])
|
|
def initialize(
|
|
graph=None, # pylint: disable=redefined-outer-name
|
|
session=None):
|
|
"""Initializes summary writing for graph execution mode.
|
|
|
|
This operation is a no-op when executing eagerly.
|
|
|
|
This helper method provides a higher-level alternative to using
|
|
`tf.contrib.summary.summary_writer_initializer_op` and
|
|
`tf.contrib.summary.graph`.
|
|
|
|
Most users will also want to call `tf.compat.v1.train.create_global_step`
|
|
which can happen before or after this function is called.
|
|
|
|
Args:
|
|
graph: A `tf.Graph` or `tf.compat.v1.GraphDef` to output to the writer.
|
|
This function will not write the default graph by default. When
|
|
writing to an event log file, the associated step will be zero.
|
|
session: So this method can call `tf.Session.run`. This defaults
|
|
to `tf.compat.v1.get_default_session`.
|
|
|
|
Raises:
|
|
RuntimeError: If the current thread has no default
|
|
`tf.contrib.summary.SummaryWriter`.
|
|
ValueError: If session wasn't passed and no default session.
|
|
"""
|
|
if context.executing_eagerly():
|
|
return
|
|
if _summary_state.writer is None:
|
|
raise RuntimeError("No default tf.contrib.summary.SummaryWriter found")
|
|
if session is None:
|
|
session = ops.get_default_session()
|
|
if session is None:
|
|
raise ValueError("Argument `session must be passed if no default "
|
|
"session exists")
|
|
session.run(summary_writer_initializer_op())
|
|
if graph is not None:
|
|
data = _serialize_graph(graph)
|
|
x = array_ops.placeholder(dtypes.string)
|
|
session.run(graph_v1(x, 0), feed_dict={x: data})
|
|
|
|
|
|
@tf_export("summary.create_file_writer", v1=[])
|
|
def create_file_writer_v2(logdir,
|
|
max_queue=None,
|
|
flush_millis=None,
|
|
filename_suffix=None,
|
|
name=None,
|
|
experimental_trackable=False):
|
|
"""Creates a summary file writer for the given log directory.
|
|
|
|
Args:
|
|
logdir: a string specifying the directory in which to write an event file.
|
|
max_queue: the largest number of summaries to keep in a queue; will
|
|
flush once the queue gets bigger than this. Defaults to 10.
|
|
flush_millis: the largest interval between flushes. Defaults to 120,000.
|
|
filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
|
|
name: a name for the op that creates the writer.
|
|
experimental_trackable: a boolean that controls whether the returned writer
|
|
will be a `TrackableResource`, which makes it compatible with SavedModel
|
|
when used as a `tf.Module` property.
|
|
|
|
Returns:
|
|
A SummaryWriter object.
|
|
"""
|
|
if logdir is None:
|
|
raise ValueError("Argument `logdir` cannot be None")
|
|
inside_function = ops.inside_function()
|
|
with ops.name_scope(name, "create_file_writer") as scope, ops.device("cpu:0"):
|
|
# Run init inside an init_scope() to hoist it out of tf.functions.
|
|
with ops.init_scope():
|
|
if context.executing_eagerly():
|
|
_check_create_file_writer_args(
|
|
inside_function,
|
|
logdir=logdir,
|
|
max_queue=max_queue,
|
|
flush_millis=flush_millis,
|
|
filename_suffix=filename_suffix)
|
|
logdir = ops.convert_to_tensor(logdir, dtype=dtypes.string)
|
|
if max_queue is None:
|
|
max_queue = constant_op.constant(10)
|
|
if flush_millis is None:
|
|
flush_millis = constant_op.constant(2 * 60 * 1000)
|
|
if filename_suffix is None:
|
|
filename_suffix = constant_op.constant(".v2")
|
|
|
|
def create_fn():
|
|
# Use unique shared_name to prevent resource sharing in eager mode, but
|
|
# otherwise use a fixed shared_name to allow SavedModel TF 1.x loading.
|
|
if context.executing_eagerly():
|
|
shared_name = context.anonymous_name()
|
|
else:
|
|
shared_name = ops.name_from_scope_name(scope) # pylint: disable=protected-access
|
|
return gen_summary_ops.summary_writer(
|
|
shared_name=shared_name, name=name)
|
|
|
|
init_op_fn = functools.partial(
|
|
gen_summary_ops.create_summary_file_writer,
|
|
logdir=logdir,
|
|
max_queue=max_queue,
|
|
flush_millis=flush_millis,
|
|
filename_suffix=filename_suffix)
|
|
if experimental_trackable:
|
|
return _TrackableResourceSummaryWriter(
|
|
create_fn=create_fn, init_op_fn=init_op_fn)
|
|
else:
|
|
return _ResourceSummaryWriter(
|
|
create_fn=create_fn, init_op_fn=init_op_fn)
|
|
|
|
|
|
def create_file_writer(logdir,
|
|
max_queue=None,
|
|
flush_millis=None,
|
|
filename_suffix=None,
|
|
name=None):
|
|
"""Creates a summary file writer in the current context under the given name.
|
|
|
|
Args:
|
|
logdir: a string, or None. If a string, creates a summary file writer
|
|
which writes to the directory named by the string. If None, returns
|
|
a mock object which acts like a summary writer but does nothing,
|
|
useful to use as a context manager.
|
|
max_queue: the largest number of summaries to keep in a queue; will
|
|
flush once the queue gets bigger than this. Defaults to 10.
|
|
flush_millis: the largest interval between flushes. Defaults to 120,000.
|
|
filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
|
|
name: Shared name for this SummaryWriter resource stored to default
|
|
Graph. Defaults to the provided logdir prefixed with `logdir:`. Note: if a
|
|
summary writer resource with this shared name already exists, the returned
|
|
SummaryWriter wraps that resource and the other arguments have no effect.
|
|
|
|
Returns:
|
|
Either a summary writer or an empty object which can be used as a
|
|
summary writer.
|
|
"""
|
|
if logdir is None:
|
|
return _NoopSummaryWriter()
|
|
logdir = str(logdir)
|
|
with ops.device("cpu:0"):
|
|
if max_queue is None:
|
|
max_queue = constant_op.constant(10)
|
|
if flush_millis is None:
|
|
flush_millis = constant_op.constant(2 * 60 * 1000)
|
|
if filename_suffix is None:
|
|
filename_suffix = constant_op.constant(".v2")
|
|
if name is None:
|
|
name = "logdir:" + logdir
|
|
resource = gen_summary_ops.summary_writer(shared_name=name)
|
|
return _LegacyResourceSummaryWriter(
|
|
resource=resource,
|
|
init_op_fn=functools.partial(
|
|
gen_summary_ops.create_summary_file_writer,
|
|
logdir=logdir,
|
|
max_queue=max_queue,
|
|
flush_millis=flush_millis,
|
|
filename_suffix=filename_suffix))
|
|
|
|
|
|
@tf_export("summary.create_noop_writer", v1=[])
|
|
def create_noop_writer():
|
|
"""Returns a summary writer that does nothing.
|
|
|
|
This is useful as a placeholder in code that expects a context manager.
|
|
"""
|
|
return _NoopSummaryWriter()
|
|
|
|
|
|
def _cleanse_string(name, pattern, value):
|
|
if isinstance(value, str) and pattern.search(value) is None:
|
|
raise ValueError(f"{name} ({value}) must match {pattern.pattern}")
|
|
return ops.convert_to_tensor(value, dtypes.string)
|
|
|
|
|
|
def _nothing():
|
|
"""Convenient else branch for when summaries do not record."""
|
|
return constant_op.constant(False)
|
|
|
|
|
|
@tf_export(v1=["summary.all_v2_summary_ops"])
|
|
def all_v2_summary_ops():
|
|
"""Returns all V2-style summary ops defined in the current default graph.
|
|
|
|
This includes ops from TF 2.0 tf.summary and TF 1.x tf.contrib.summary (except
|
|
for `tf.contrib.summary.graph` and `tf.contrib.summary.import_event`), but
|
|
does *not* include TF 1.x tf.summary ops.
|
|
|
|
Returns:
|
|
List of summary ops, or None if called under eager execution.
|
|
"""
|
|
if context.executing_eagerly():
|
|
return None
|
|
return ops.get_collection(ops.GraphKeys._SUMMARY_COLLECTION) # pylint: disable=protected-access
|
|
|
|
|
|
def summary_writer_initializer_op():
|
|
"""Graph-mode only. Returns the list of ops to create all summary writers.
|
|
|
|
Returns:
|
|
The initializer ops.
|
|
|
|
Raises:
|
|
RuntimeError: If in Eager mode.
|
|
"""
|
|
if context.executing_eagerly():
|
|
raise RuntimeError(
|
|
"tf.contrib.summary.summary_writer_initializer_op is only "
|
|
"supported in graph mode.")
|
|
return ops.get_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME)
|
|
|
|
|
|
_INVALID_SCOPE_CHARACTERS = re.compile(r"[^-_/.A-Za-z0-9]")
|
|
|
|
|
|
@tf_export("summary.experimental.summary_scope", v1=[])
|
|
@tf_contextlib.contextmanager
|
|
def summary_scope(name, default_name="summary", values=None):
|
|
"""Experimental context manager for use when defining a custom summary op.
|
|
|
|
This behaves similarly to `tf.name_scope`, except that it returns a generated
|
|
summary tag in addition to the scope name. The tag is structurally similar to
|
|
the scope name - derived from the user-provided name, prefixed with enclosing
|
|
name scopes if any - but we relax the constraint that it be uniquified, as
|
|
well as the character set limitation (so the user-provided name can contain
|
|
characters not legal for scope names; in the scope name these are removed).
|
|
|
|
This makes the summary tag more predictable and consistent for the user.
|
|
|
|
For example, to define a new summary op called `my_op`:
|
|
|
|
```python
|
|
def my_op(name, my_value, step):
|
|
with tf.summary.summary_scope(name, "MyOp", [my_value]) as (tag, scope):
|
|
my_value = tf.convert_to_tensor(my_value)
|
|
return tf.summary.write(tag, my_value, step=step)
|
|
```
|
|
|
|
Args:
|
|
name: string name for the summary.
|
|
default_name: Optional; if provided, used as default name of the summary.
|
|
values: Optional; passed as `values` parameter to name_scope.
|
|
|
|
Yields:
|
|
A tuple `(tag, scope)` as described above.
|
|
"""
|
|
name = name or default_name
|
|
current_scope = ops.get_name_scope()
|
|
tag = current_scope + "/" + name if current_scope else name
|
|
# Strip illegal characters from the scope name, and if that leaves nothing,
|
|
# use None instead so we pick up the default name.
|
|
name = _INVALID_SCOPE_CHARACTERS.sub("", name) or None
|
|
with ops.name_scope(name, default_name, values, skip_on_eager=False) as scope:
|
|
yield tag, scope
|
|
|
|
|
|
@tf_export("summary.write", v1=[])
|
|
def write(tag, tensor, step=None, metadata=None, name=None):
|
|
"""Writes a generic summary to the default SummaryWriter if one exists.
|
|
|
|
This exists primarily to support the definition of type-specific summary ops
|
|
like scalar() and image(), and is not intended for direct use unless defining
|
|
a new type-specific summary op.
|
|
|
|
Args:
|
|
tag: string tag used to identify the summary (e.g. in TensorBoard), usually
|
|
generated with `tf.summary.summary_scope`
|
|
tensor: the Tensor holding the summary data to write or a callable that
|
|
returns this Tensor. If a callable is passed, it will only be called when
|
|
a default SummaryWriter exists and the recording condition specified by
|
|
`record_if()` is met.
|
|
step: Explicit `int64`-castable monotonic step value for this summary. If
|
|
omitted, this defaults to `tf.summary.experimental.get_step()`, which must
|
|
not be None.
|
|
metadata: Optional SummaryMetadata, as a proto or serialized bytes
|
|
name: Optional string name for this op.
|
|
|
|
Returns:
|
|
True on success, or false if no summary was written because no default
|
|
summary writer was available.
|
|
|
|
Raises:
|
|
ValueError: if a default writer exists, but no step was provided and
|
|
`tf.summary.experimental.get_step()` is None.
|
|
"""
|
|
with ops.name_scope(name, "write_summary") as scope:
|
|
if _summary_state.writer is None:
|
|
return constant_op.constant(False)
|
|
if step is None:
|
|
step = get_step()
|
|
if metadata is None:
|
|
serialized_metadata = b""
|
|
elif hasattr(metadata, "SerializeToString"):
|
|
serialized_metadata = metadata.SerializeToString()
|
|
else:
|
|
serialized_metadata = metadata
|
|
|
|
def record():
|
|
"""Record the actual summary and return True."""
|
|
if step is None:
|
|
raise ValueError("No step set. Please specify one either through the "
|
|
"`step` argument or through "
|
|
"tf.summary.experimental.set_step()")
|
|
|
|
# Note the identity to move the tensor to the CPU.
|
|
with ops.device("cpu:0"):
|
|
summary_tensor = tensor() if callable(tensor) else array_ops.identity(
|
|
tensor)
|
|
write_summary_op = gen_summary_ops.write_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
step,
|
|
summary_tensor,
|
|
tag,
|
|
serialized_metadata,
|
|
name=scope)
|
|
with ops.control_dependencies([write_summary_op]):
|
|
return constant_op.constant(True)
|
|
|
|
op = smart_cond.smart_cond(
|
|
should_record_summaries(), record, _nothing, name="summary_cond")
|
|
if not context.executing_eagerly():
|
|
ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
|
|
return op
|
|
|
|
|
|
@tf_export("summary.experimental.write_raw_pb", v1=[])
|
|
def write_raw_pb(tensor, step=None, name=None):
|
|
"""Writes a summary using raw `tf.compat.v1.Summary` protocol buffers.
|
|
|
|
Experimental: this exists to support the usage of V1-style manual summary
|
|
writing (via the construction of a `tf.compat.v1.Summary` protocol buffer)
|
|
with the V2 summary writing API.
|
|
|
|
Args:
|
|
tensor: the string Tensor holding one or more serialized `Summary` protobufs
|
|
step: Explicit `int64`-castable monotonic step value for this summary. If
|
|
omitted, this defaults to `tf.summary.experimental.get_step()`, which must
|
|
not be None.
|
|
name: Optional string name for this op.
|
|
|
|
Returns:
|
|
True on success, or false if no summary was written because no default
|
|
summary writer was available.
|
|
|
|
Raises:
|
|
ValueError: if a default writer exists, but no step was provided and
|
|
`tf.summary.experimental.get_step()` is None.
|
|
"""
|
|
with ops.name_scope(name, "write_raw_pb") as scope:
|
|
if _summary_state.writer is None:
|
|
return constant_op.constant(False)
|
|
if step is None:
|
|
step = get_step()
|
|
if step is None:
|
|
raise ValueError("No step set. Please specify one either through the "
|
|
"`step` argument or through "
|
|
"tf.summary.experimental.set_step()")
|
|
|
|
def record():
|
|
"""Record the actual summary and return True."""
|
|
# Note the identity to move the tensor to the CPU.
|
|
with ops.device("cpu:0"):
|
|
raw_summary_op = gen_summary_ops.write_raw_proto_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
step,
|
|
array_ops.identity(tensor),
|
|
name=scope)
|
|
with ops.control_dependencies([raw_summary_op]):
|
|
return constant_op.constant(True)
|
|
|
|
with ops.device("cpu:0"):
|
|
op = smart_cond.smart_cond(
|
|
should_record_summaries(), record, _nothing, name="summary_cond")
|
|
if not context.executing_eagerly():
|
|
ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
|
|
return op
|
|
|
|
|
|
def summary_writer_function(name, tensor, function, family=None):
|
|
"""Helper function to write summaries.
|
|
|
|
Args:
|
|
name: name of the summary
|
|
tensor: main tensor to form the summary
|
|
function: function taking a tag and a scope which writes the summary
|
|
family: optional, the summary's family
|
|
|
|
Returns:
|
|
The result of writing the summary.
|
|
"""
|
|
name_scope = ops.get_name_scope()
|
|
if name_scope:
|
|
# Add a slash to allow reentering the name scope.
|
|
name_scope += "/"
|
|
def record():
|
|
with ops.name_scope(name_scope), summary_op_util.summary_scope(
|
|
name, family, values=[tensor]) as (tag, scope):
|
|
with ops.control_dependencies([function(tag, scope)]):
|
|
return constant_op.constant(True)
|
|
|
|
if _summary_state.writer is None:
|
|
return control_flow_ops.no_op()
|
|
with ops.device("cpu:0"):
|
|
op = smart_cond.smart_cond(
|
|
_legacy_contrib_should_record_summaries(), record, _nothing, name="")
|
|
if not context.executing_eagerly():
|
|
ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op) # pylint: disable=protected-access
|
|
return op
|
|
|
|
|
|
def generic(name, tensor, metadata=None, family=None, step=None):
|
|
"""Writes a tensor summary if possible."""
|
|
|
|
def function(tag, scope):
|
|
if metadata is None:
|
|
serialized_metadata = constant_op.constant("")
|
|
elif hasattr(metadata, "SerializeToString"):
|
|
serialized_metadata = constant_op.constant(metadata.SerializeToString())
|
|
else:
|
|
serialized_metadata = metadata
|
|
# Note the identity to move the tensor to the CPU.
|
|
return gen_summary_ops.write_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
_choose_step(step),
|
|
array_ops.identity(tensor),
|
|
tag,
|
|
serialized_metadata,
|
|
name=scope)
|
|
return summary_writer_function(name, tensor, function, family=family)
|
|
|
|
|
|
def scalar(name, tensor, family=None, step=None):
|
|
"""Writes a scalar summary if possible.
|
|
|
|
Unlike `tf.contrib.summary.generic` this op may change the dtype
|
|
depending on the writer, for both practical and efficiency concerns.
|
|
|
|
Args:
|
|
name: An arbitrary name for this summary.
|
|
tensor: A `tf.Tensor` Must be one of the following types:
|
|
`float32`, `float64`, `int32`, `int64`, `uint8`, `int16`,
|
|
`int8`, `uint16`, `half`, `uint32`, `uint64`.
|
|
family: Optional, the summary's family.
|
|
step: The `int64` monotonic step variable, which defaults
|
|
to `tf.compat.v1.train.get_global_step`.
|
|
|
|
Returns:
|
|
The created `tf.Operation` or a `tf.no_op` if summary writing has
|
|
not been enabled for this context.
|
|
"""
|
|
|
|
def function(tag, scope):
|
|
# Note the identity to move the tensor to the CPU.
|
|
return gen_summary_ops.write_scalar_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
_choose_step(step),
|
|
tag,
|
|
array_ops.identity(tensor),
|
|
name=scope)
|
|
|
|
return summary_writer_function(name, tensor, function, family=family)
|
|
|
|
|
|
def histogram(name, tensor, family=None, step=None):
|
|
"""Writes a histogram summary if possible."""
|
|
|
|
def function(tag, scope):
|
|
# Note the identity to move the tensor to the CPU.
|
|
return gen_summary_ops.write_histogram_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
_choose_step(step),
|
|
tag,
|
|
array_ops.identity(tensor),
|
|
name=scope)
|
|
|
|
return summary_writer_function(name, tensor, function, family=family)
|
|
|
|
|
|
def image(name, tensor, bad_color=None, max_images=3, family=None, step=None):
|
|
"""Writes an image summary if possible."""
|
|
|
|
def function(tag, scope):
|
|
bad_color_ = (constant_op.constant([255, 0, 0, 255], dtype=dtypes.uint8)
|
|
if bad_color is None else bad_color)
|
|
# Note the identity to move the tensor to the CPU.
|
|
return gen_summary_ops.write_image_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
_choose_step(step),
|
|
tag,
|
|
array_ops.identity(tensor),
|
|
bad_color_,
|
|
max_images,
|
|
name=scope)
|
|
|
|
return summary_writer_function(name, tensor, function, family=family)
|
|
|
|
|
|
def audio(name, tensor, sample_rate, max_outputs, family=None, step=None):
|
|
"""Writes an audio summary if possible."""
|
|
|
|
def function(tag, scope):
|
|
# Note the identity to move the tensor to the CPU.
|
|
return gen_summary_ops.write_audio_summary(
|
|
_summary_state.writer._resource, # pylint: disable=protected-access
|
|
_choose_step(step),
|
|
tag,
|
|
array_ops.identity(tensor),
|
|
sample_rate=sample_rate,
|
|
max_outputs=max_outputs,
|
|
name=scope)
|
|
|
|
return summary_writer_function(name, tensor, function, family=family)
|
|
|
|
|
|
def graph_v1(param, step=None, name=None):
|
|
"""Writes a TensorFlow graph to the summary interface.
|
|
|
|
The graph summary is, strictly speaking, not a summary. Conditions
|
|
like `tf.summary.should_record_summaries` do not apply. Only
|
|
a single graph can be associated with a particular run. If multiple
|
|
graphs are written, then only the last one will be considered by
|
|
TensorBoard.
|
|
|
|
When not using eager execution mode, the user should consider passing
|
|
the `graph` parameter to `tf.compat.v1.summary.initialize` instead of
|
|
calling this function. Otherwise special care needs to be taken when
|
|
using the graph to record the graph.
|
|
|
|
Args:
|
|
param: A `tf.Tensor` containing a serialized graph proto. When
|
|
eager execution is enabled, this function will automatically
|
|
coerce `tf.Graph`, `tf.compat.v1.GraphDef`, and string types.
|
|
step: The global step variable. This doesn't have useful semantics
|
|
for graph summaries, but is used anyway, due to the structure of
|
|
event log files. This defaults to the global step.
|
|
name: A name for the operation (optional).
|
|
|
|
Returns:
|
|
The created `tf.Operation` or a `tf.no_op` if summary writing has
|
|
not been enabled for this context.
|
|
|
|
Raises:
|
|
TypeError: If `param` isn't already a `tf.Tensor` in graph mode.
|
|
"""
|
|
if not context.executing_eagerly() and not isinstance(param, ops.Tensor):
|
|
raise TypeError("graph() needs a argument `param` to be tf.Tensor "
|
|
"(e.g. tf.placeholder) in graph mode, but received "
|
|
f"param={param} of type {type(param).__name__}.")
|
|
writer = _summary_state.writer
|
|
if writer is None:
|
|
return control_flow_ops.no_op()
|
|
with ops.device("cpu:0"):
|
|
if isinstance(param, (ops.Graph, graph_pb2.GraphDef)):
|
|
tensor = ops.convert_to_tensor(_serialize_graph(param), dtypes.string)
|
|
else:
|
|
tensor = array_ops.identity(param)
|
|
return gen_summary_ops.write_graph_summary(
|
|
writer._resource, _choose_step(step), tensor, name=name) # pylint: disable=protected-access
|
|
|
|
|
|
@tf_export("summary.graph", v1=[])
|
|
def graph(graph_data):
|
|
"""Writes a TensorFlow graph summary.
|
|
|
|
Write an instance of `tf.Graph` or `tf.compat.v1.GraphDef` as summary only
|
|
in an eager mode. Please prefer to use the trace APIs (`tf.summary.trace_on`,
|
|
`tf.summary.trace_off`, and `tf.summary.trace_export`) when using
|
|
`tf.function` which can automatically collect and record graphs from
|
|
executions.
|
|
|
|
Usage Example:
|
|
```py
|
|
writer = tf.summary.create_file_writer("/tmp/mylogs")
|
|
|
|
@tf.function
|
|
def f():
|
|
x = constant_op.constant(2)
|
|
y = constant_op.constant(3)
|
|
return x**y
|
|
|
|
with writer.as_default():
|
|
tf.summary.graph(f.get_concrete_function().graph)
|
|
|
|
# Another example: in a very rare use case, when you are dealing with a TF v1
|
|
# graph.
|
|
graph = tf.Graph()
|
|
with graph.as_default():
|
|
c = tf.constant(30.0)
|
|
with writer.as_default():
|
|
tf.summary.graph(graph)
|
|
```
|
|
|
|
Args:
|
|
graph_data: The TensorFlow graph to write, as a `tf.Graph` or a
|
|
`tf.compat.v1.GraphDef`.
|
|
|
|
Returns:
|
|
True on success, or False if no summary was written because no default
|
|
summary writer was available.
|
|
|
|
Raises:
|
|
ValueError: `graph` summary API is invoked in a graph mode.
|
|
"""
|
|
if not context.executing_eagerly():
|
|
raise ValueError("graph() cannot be invoked inside a graph context.")
|
|
writer = _summary_state.writer
|
|
if writer is None:
|
|
return constant_op.constant(False)
|
|
with ops.device("cpu:0"):
|
|
if not should_record_summaries():
|
|
return constant_op.constant(False)
|
|
|
|
if isinstance(graph_data, (ops.Graph, graph_pb2.GraphDef)):
|
|
tensor = ops.convert_to_tensor(
|
|
_serialize_graph(graph_data), dtypes.string)
|
|
else:
|
|
raise ValueError("Argument 'graph_data' is not tf.Graph or "
|
|
"tf.compat.v1.GraphDef. Received graph_data="
|
|
f"{graph_data} of type {type(graph_data).__name__}.")
|
|
|
|
gen_summary_ops.write_graph_summary(
|
|
writer._resource, # pylint: disable=protected-access
|
|
# Graph does not have step. Set to 0.
|
|
0,
|
|
tensor,
|
|
)
|
|
return constant_op.constant(True)
|
|
|
|
|
|
def import_event(tensor, name=None):
|
|
"""Writes a `tf.compat.v1.Event` binary proto.
|
|
|
|
This can be used to import existing event logs into a new summary writer sink.
|
|
Please note that this is lower level than the other summary functions and
|
|
will ignore the `tf.summary.should_record_summaries` setting.
|
|
|
|
Args:
|
|
tensor: A `tf.Tensor` of type `string` containing a serialized
|
|
`tf.compat.v1.Event` proto.
|
|
name: A name for the operation (optional).
|
|
|
|
Returns:
|
|
The created `tf.Operation`.
|
|
"""
|
|
return gen_summary_ops.import_event(
|
|
_summary_state.writer._resource, tensor, name=name) # pylint: disable=protected-access
|
|
|
|
|
|
@tf_export("summary.flush", v1=[])
|
|
def flush(writer=None, name=None):
|
|
"""Forces summary writer to send any buffered data to storage.
|
|
|
|
This operation blocks until that finishes.
|
|
|
|
Args:
|
|
writer: The `tf.summary.SummaryWriter` to flush. If None, the current
|
|
default writer will be used instead; if there is no current writer, this
|
|
returns `tf.no_op`.
|
|
name: Ignored legacy argument for a name for the operation.
|
|
|
|
Returns:
|
|
The created `tf.Operation`.
|
|
"""
|
|
del name # unused
|
|
if writer is None:
|
|
writer = _summary_state.writer
|
|
if writer is None:
|
|
return control_flow_ops.no_op()
|
|
if isinstance(writer, SummaryWriter):
|
|
return writer.flush()
|
|
raise ValueError("Invalid argument to flush(): %r" % (writer,))
|
|
|
|
|
|
def legacy_raw_flush(writer=None, name=None):
|
|
"""Legacy version of flush() that accepts a raw resource tensor for `writer`.
|
|
|
|
Do not use this function in any new code. Not supported and not part of the
|
|
public TF APIs.
|
|
|
|
Args:
|
|
writer: The `tf.summary.SummaryWriter` to flush. If None, the current
|
|
default writer will be used instead; if there is no current writer, this
|
|
returns `tf.no_op`. For this legacy version only, also accepts a raw
|
|
resource tensor pointing to the underlying C++ writer resource.
|
|
name: Ignored legacy argument for a name for the operation.
|
|
|
|
Returns:
|
|
The created `tf.Operation`.
|
|
"""
|
|
if writer is None or isinstance(writer, SummaryWriter):
|
|
# Forward to the TF2 implementation of flush() when possible.
|
|
return flush(writer, name)
|
|
else:
|
|
# Legacy fallback in case we were passed a raw resource tensor.
|
|
with ops.device("cpu:0"):
|
|
return gen_summary_ops.flush_summary_writer(writer, name=name)
|
|
|
|
|
|
def eval_dir(model_dir, name=None):
|
|
"""Construct a logdir for an eval summary writer."""
|
|
return os.path.join(model_dir, "eval" if not name else "eval_" + name)
|
|
|
|
|
|
@deprecation.deprecated(date=None,
|
|
instructions="Renamed to create_file_writer().")
|
|
def create_summary_file_writer(*args, **kwargs):
|
|
"""Please use `tf.contrib.summary.create_file_writer`."""
|
|
logging.warning("Deprecation Warning: create_summary_file_writer was renamed "
|
|
"to create_file_writer")
|
|
return create_file_writer(*args, **kwargs)
|
|
|
|
|
|
def _serialize_graph(arbitrary_graph):
|
|
if isinstance(arbitrary_graph, ops.Graph):
|
|
return arbitrary_graph.as_graph_def(add_shapes=True).SerializeToString()
|
|
else:
|
|
return arbitrary_graph.SerializeToString()
|
|
|
|
|
|
def _choose_step(step):
|
|
if step is None:
|
|
return training_util.get_or_create_global_step()
|
|
if not isinstance(step, ops.Tensor):
|
|
return ops.convert_to_tensor(step, dtypes.int64)
|
|
return step
|
|
|
|
|
|
def _check_create_file_writer_args(inside_function, **kwargs):
|
|
"""Helper to check the validity of arguments to a create_file_writer() call.
|
|
|
|
Args:
|
|
inside_function: whether the create_file_writer() call is in a tf.function
|
|
**kwargs: the arguments to check, as kwargs to give them names.
|
|
|
|
Raises:
|
|
ValueError: if the arguments are graph tensors.
|
|
"""
|
|
for arg_name, arg in kwargs.items():
|
|
if not isinstance(arg, ops.EagerTensor) and tensor_util.is_tf_type(arg):
|
|
if inside_function:
|
|
raise ValueError(
|
|
f"Invalid graph Tensor argument '{arg_name}={arg}' to "
|
|
"create_file_writer() inside an @tf.function. The create call will "
|
|
"be lifted into the outer eager execution context, so it cannot "
|
|
"consume graph tensors defined inside the function body.")
|
|
else:
|
|
raise ValueError(
|
|
f"Invalid graph Tensor argument '{arg_name}={arg}' to eagerly "
|
|
"executed create_file_writer().")
|
|
|
|
|
|
def run_metadata(name, data, step=None):
|
|
"""Writes entire RunMetadata summary.
|
|
|
|
A RunMetadata can contain DeviceStats, partition graphs, and function graphs.
|
|
Please refer to the proto for definition of each field.
|
|
|
|
Args:
|
|
name: A name for this summary. The summary tag used for TensorBoard will be
|
|
this name prefixed by any active name scopes.
|
|
data: A RunMetadata proto to write.
|
|
step: Explicit `int64`-castable monotonic step value for this summary. If
|
|
omitted, this defaults to `tf.summary.experimental.get_step()`, which must
|
|
not be None.
|
|
|
|
Returns:
|
|
True on success, or false if no summary was written because no default
|
|
summary writer was available.
|
|
|
|
Raises:
|
|
ValueError: if a default writer exists, but no step was provided and
|
|
`tf.summary.experimental.get_step()` is None.
|
|
"""
|
|
summary_metadata = summary_pb2.SummaryMetadata()
|
|
# Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
|
|
# the rationale.
|
|
summary_metadata.plugin_data.plugin_name = "graph_run_metadata"
|
|
# version number = 1
|
|
summary_metadata.plugin_data.content = b"1"
|
|
|
|
with summary_scope(name,
|
|
"graph_run_metadata_summary",
|
|
[data, step]) as (tag, _):
|
|
with ops.device("cpu:0"):
|
|
tensor = constant_op.constant(data.SerializeToString(),
|
|
dtype=dtypes.string)
|
|
return write(
|
|
tag=tag,
|
|
tensor=tensor,
|
|
step=step,
|
|
metadata=summary_metadata)
|
|
|
|
|
|
def run_metadata_graphs(name, data, step=None):
|
|
"""Writes graphs from a RunMetadata summary.
|
|
|
|
Args:
|
|
name: A name for this summary. The summary tag used for TensorBoard will be
|
|
this name prefixed by any active name scopes.
|
|
data: A RunMetadata proto to write.
|
|
step: Explicit `int64`-castable monotonic step value for this summary. If
|
|
omitted, this defaults to `tf.summary.experimental.get_step()`, which must
|
|
not be None.
|
|
|
|
Returns:
|
|
True on success, or false if no summary was written because no default
|
|
summary writer was available.
|
|
|
|
Raises:
|
|
ValueError: if a default writer exists, but no step was provided and
|
|
`tf.summary.experimental.get_step()` is None.
|
|
"""
|
|
summary_metadata = summary_pb2.SummaryMetadata()
|
|
# Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
|
|
# the rationale.
|
|
summary_metadata.plugin_data.plugin_name = "graph_run_metadata_graph"
|
|
# version number = 1
|
|
summary_metadata.plugin_data.content = b"1"
|
|
|
|
data = config_pb2.RunMetadata(
|
|
function_graphs=data.function_graphs,
|
|
partition_graphs=data.partition_graphs)
|
|
|
|
with summary_scope(name,
|
|
"graph_run_metadata_graph_summary",
|
|
[data, step]) as (tag, _):
|
|
with ops.device("cpu:0"):
|
|
tensor = constant_op.constant(data.SerializeToString(),
|
|
dtype=dtypes.string)
|
|
return write(
|
|
tag=tag,
|
|
tensor=tensor,
|
|
step=step,
|
|
metadata=summary_metadata)
|
|
|
|
|
|
_TraceContext = collections.namedtuple("TraceContext", ("graph", "profiler"))
|
|
_current_trace_context_lock = threading.Lock()
|
|
_current_trace_context = None
|
|
|
|
|
|
@tf_export("summary.trace_on", v1=[])
|
|
def trace_on(graph=True, profiler=False): # pylint: disable=redefined-outer-name
|
|
"""Starts a trace to record computation graphs and profiling information.
|
|
|
|
Must be invoked in eager mode.
|
|
|
|
When enabled, TensorFlow runtime will collect information that can later be
|
|
exported and consumed by TensorBoard. The trace is activated across the entire
|
|
TensorFlow runtime and affects all threads of execution.
|
|
|
|
To stop the trace and export the collected information, use
|
|
`tf.summary.trace_export`. To stop the trace without exporting, use
|
|
`tf.summary.trace_off`.
|
|
|
|
Args:
|
|
graph: If True, enables collection of executed graphs. It includes ones from
|
|
tf.function invocation and ones from the legacy graph mode. The default
|
|
is True.
|
|
profiler: If True, enables the advanced profiler. Enabling profiler
|
|
implicitly enables the graph collection. The profiler may incur a high
|
|
memory overhead. The default is False.
|
|
|
|
"""
|
|
if ops.inside_function():
|
|
logging.warn("Cannot enable trace inside a tf.function.")
|
|
return
|
|
if not context.executing_eagerly():
|
|
logging.warn("Must enable trace in eager mode.")
|
|
return
|
|
|
|
global _current_trace_context
|
|
with _current_trace_context_lock:
|
|
if _current_trace_context:
|
|
logging.warn("Trace already enabled")
|
|
return
|
|
|
|
if graph and not profiler:
|
|
context.context().enable_graph_collection()
|
|
if profiler:
|
|
context.context().enable_run_metadata()
|
|
_profiler.start()
|
|
|
|
_current_trace_context = _TraceContext(graph=graph, profiler=profiler)
|
|
|
|
|
|
@tf_export("summary.trace_export", v1=[])
|
|
def trace_export(name, step=None, profiler_outdir=None):
|
|
"""Stops and exports the active trace as a Summary and/or profile file.
|
|
|
|
Stops the trace and exports all metadata collected during the trace to the
|
|
default SummaryWriter, if one has been set.
|
|
|
|
Args:
|
|
name: A name for the summary to be written.
|
|
step: Explicit `int64`-castable monotonic step value for this summary. If
|
|
omitted, this defaults to `tf.summary.experimental.get_step()`, which must
|
|
not be None.
|
|
profiler_outdir: Output directory for profiler. It is required when profiler
|
|
is enabled when trace was started. Otherwise, it is ignored.
|
|
|
|
Raises:
|
|
ValueError: if a default writer exists, but no step was provided and
|
|
`tf.summary.experimental.get_step()` is None.
|
|
"""
|
|
# TODO(stephanlee): See if we can remove profiler_outdir and infer it from
|
|
# the SummaryWriter's logdir.
|
|
global _current_trace_context
|
|
|
|
if ops.inside_function():
|
|
logging.warn("Cannot export trace inside a tf.function.")
|
|
return
|
|
if not context.executing_eagerly():
|
|
logging.warn("Can only export trace while executing eagerly.")
|
|
return
|
|
|
|
with _current_trace_context_lock:
|
|
if _current_trace_context is None:
|
|
raise ValueError("Must enable trace before export through "
|
|
"tf.summary.trace_on.")
|
|
graph, profiler = _current_trace_context # pylint: disable=redefined-outer-name
|
|
if profiler and profiler_outdir is None:
|
|
raise ValueError("Argument `profiler_outdir` is not specified.")
|
|
|
|
run_meta = context.context().export_run_metadata()
|
|
|
|
if graph and not profiler:
|
|
run_metadata_graphs(name, run_meta, step)
|
|
else:
|
|
run_metadata(name, run_meta, step)
|
|
|
|
if profiler:
|
|
_profiler.save(profiler_outdir, _profiler.stop())
|
|
|
|
trace_off()
|
|
|
|
|
|
@tf_export("summary.trace_off", v1=[])
|
|
def trace_off():
|
|
"""Stops the current trace and discards any collected information."""
|
|
global _current_trace_context
|
|
with _current_trace_context_lock:
|
|
if _current_trace_context is None:
|
|
return # tracing already off
|
|
graph, profiler = _current_trace_context # pylint: disable=redefined-outer-name, unpacking-non-sequence
|
|
_current_trace_context = None
|
|
|
|
if graph:
|
|
# Disabling run_metadata disables graph collection as well.
|
|
context.context().disable_run_metadata()
|
|
|
|
if profiler:
|
|
try:
|
|
_profiler.stop()
|
|
except _profiler.ProfilerNotRunningError:
|
|
pass
|