• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15
16"""Operations to emit summaries."""
17
18from __future__ import absolute_import
19from __future__ import division
20from __future__ import print_function
21
22import abc
23import collections
24import functools
25import os
26import re
27import threading
28
29import six
30
31from tensorflow.core.framework import graph_pb2
32from tensorflow.core.framework import summary_pb2
33from tensorflow.core.protobuf import config_pb2
34from tensorflow.python.eager import context
35from tensorflow.python.eager import profiler as _profiler
36from tensorflow.python.framework import constant_op
37from tensorflow.python.framework import dtypes
38from tensorflow.python.framework import ops
39from tensorflow.python.framework import smart_cond
40from tensorflow.python.framework import tensor_util
41from tensorflow.python.ops import array_ops
42from tensorflow.python.ops import control_flow_ops
43from tensorflow.python.ops import gen_summary_ops
44from tensorflow.python.ops import math_ops
45from tensorflow.python.ops import resource_variable_ops
46from tensorflow.python.ops import summary_op_util
47from tensorflow.python.platform import tf_logging as logging
48from tensorflow.python.training import training_util
49from tensorflow.python.util import deprecation
50from tensorflow.python.util import tf_contextlib
51from tensorflow.python.util.tf_export import tf_export
52
53# Name for graph collection of summary writer init ops, which is only exposed
54# as a legacy API for tf.contrib.summary in TF 1.x.
55_SUMMARY_WRITER_INIT_COLLECTION_NAME = "_SUMMARY_WRITER_V2"
56
57_EXPERIMENT_NAME_PATTERNS = re.compile(r"^[^\x00-\x1F<>]{0,256}$")
58_RUN_NAME_PATTERNS = re.compile(r"^[^\x00-\x1F<>]{0,512}$")
59_USER_NAME_PATTERNS = re.compile(r"^[a-z]([-a-z0-9]{0,29}[a-z0-9])?$", re.I)
60
61
62class _SummaryState(threading.local):
63
64  def __init__(self):
65    super(_SummaryState, self).__init__()
66    self.is_recording = None
67    # TODO(slebedev): why a separate flag for DS and is it on by default?
68    self.is_recording_distribution_strategy = True
69    self.writer = None
70    self.step = None
71
72
73_summary_state = _SummaryState()
74
75
76def _should_record_summaries_internal(default_state):
77  """Returns boolean Tensor if summaries should/shouldn't be recorded.
78
79  Now the summary condition is decided by logical "and" of below conditions:
80  First, summary writer must be set. Given this constraint is met,
81  ctx.summary_recording and ctx.summary_recording_distribution_strategy.
82  The former one is usually set by user, and the latter one is controlled
83  by DistributionStrategy (tf.distribute.ReplicaContext).
84
85  Args:
86    default_state: can be True or False. The default summary behavior when
87    summary writer is set and the user does not specify
88    ctx.summary_recording and ctx.summary_recording_distribution_strategy
89    is True.
90  """
91  if _summary_state.writer is None:
92    return constant_op.constant(False)
93
94  if not callable(_summary_state.is_recording):
95    static_cond = tensor_util.constant_value(_summary_state.is_recording)
96    if static_cond is not None and not static_cond:
97      return constant_op.constant(False)
98
99  resolve = lambda x: x() if callable(x) else x
100  cond_distributed = resolve(_summary_state.is_recording_distribution_strategy)
101  cond = resolve(_summary_state.is_recording)
102  if cond is None:
103    cond = default_state
104  return math_ops.logical_and(cond_distributed, cond)
105
106
107def _should_record_summaries_v2():
108  """Returns boolean Tensor which is true if summaries should be recorded.
109
110  If no recording status has been set, this defaults to True, unlike the public
111  should_record_summaries().
112  """
113  return _should_record_summaries_internal(default_state=True)
114
115
116@tf_export("summary.should_record_summaries", v1=[])
117def should_record_summaries():
118  """Returns boolean Tensor which is true if summaries should be recorded."""
119  return _should_record_summaries_internal(default_state=False)
120
121
122@tf_export("summary.record_if", v1=[])
123@tf_contextlib.contextmanager
124def record_if(condition):
125  """Sets summary recording on or off per the provided boolean value.
126
127  The provided value can be a python boolean, a scalar boolean Tensor, or
128  or a callable providing such a value; if a callable is passed it will be
129  invoked on-demand to determine whether summary writing will occur.  Note that
130  when calling record_if() in an eager mode context, if you intend to provide a
131  varying condition like `step % 100 == 0`, you must wrap this in a
132  callable to avoid immediate eager evaluation of the condition.  In particular,
133  using a callable is the only way to have your condition evaluated as part of
134  the traced body of an @tf.function that is invoked from within the
135  `record_if()` context.
136
137  Args:
138    condition: can be True, False, a bool Tensor, or a callable providing such.
139
140  Yields:
141    Returns a context manager that sets this value on enter and restores the
142    previous value on exit.
143  """
144  old = _summary_state.is_recording
145  try:
146    _summary_state.is_recording = condition
147    yield
148  finally:
149    _summary_state.is_recording = old
150
151
152# TODO(apassos) consider how to handle local step here.
153def record_summaries_every_n_global_steps(n, global_step=None):
154  """Sets the should_record_summaries Tensor to true if global_step % n == 0."""
155  if global_step is None:
156    global_step = training_util.get_or_create_global_step()
157  with ops.device("cpu:0"):
158    should = lambda: math_ops.equal(global_step % n, 0)
159    if not context.executing_eagerly():
160      should = should()
161  return record_if(should)
162
163
164def always_record_summaries():
165  """Sets the should_record_summaries Tensor to always true."""
166  return record_if(True)
167
168
169def never_record_summaries():
170  """Sets the should_record_summaries Tensor to always false."""
171  return record_if(False)
172
173
174@tf_export("summary.experimental.get_step", v1=[])
175def get_step():
176  """Returns the default summary step for the current thread.
177
178  Returns:
179    The step set by `tf.summary.experimental.set_step()` if one has been set,
180    otherwise None.
181  """
182  return _summary_state.step
183
184
185@tf_export("summary.experimental.set_step", v1=[])
186def set_step(step):
187  """Sets the default summary step for the current thread.
188
189  For convenience, this function sets a default value for the `step` parameter
190  used in summary-writing functions elsewhere in the API so that it need not
191  be explicitly passed in every such invocation. The value can be a constant
192  or a variable, and can be retrieved via `tf.summary.experimental.get_step()`.
193
194  Note: when using this with @tf.functions, the step value will be captured at
195  the time the function is traced, so changes to the step outside the function
196  will not be reflected inside the function unless using a `tf.Variable` step.
197
198  Args:
199    step: An `int64`-castable default step value, or None to unset.
200  """
201  _summary_state.step = step
202
203
204@tf_export("summary.SummaryWriter", v1=[])
205@six.add_metaclass(abc.ABCMeta)
206class SummaryWriter(object):
207  """Interface representing a stateful summary writer object."""
208
209  @abc.abstractmethod
210  def set_as_default(self, step=None):
211    """Enables this summary writer for the current thread.
212
213    For convenience, if `step` is not None, this function also sets a default
214    value for the `step` parameter used in summary-writing functions elsewhere
215    in the API so that it need not be explicitly passed in every such
216    invocation. The value can be a constant or a variable.
217
218    Note: when setting `step` in a @tf.function, the step value will be
219    captured at the time the function is traced, so changes to the step outside
220    the function will not be reflected inside the function unless using
221    a `tf.Variable` step.
222
223    Args:
224      step: An `int64`-castable default step value, or `None`. When not `None`,
225        the current step is modified to the given value. When `None`, the
226        current step is not modified.
227    """
228    raise NotImplementedError()
229
230  @abc.abstractmethod
231  @tf_contextlib.contextmanager
232  def as_default(self, step=None):
233    """Returns a context manager that enables summary writing.
234
235    For convenience, if `step` is not None, this function also sets a default
236    value for the `step` parameter used in summary-writing functions elsewhere
237    in the API so that it need not be explicitly passed in every such
238    invocation. The value can be a constant or a variable.
239
240    Note: when setting `step` in a @tf.function, the step value will be
241    captured at the time the function is traced, so changes to the step outside
242    the function will not be reflected inside the function unless using
243    a `tf.Variable` step.
244
245    For example, `step` can be used as:
246
247    ```python
248    with writer_a.as_default(step=10):
249      tf.summary.scalar(tag, value)   # Logged to writer_a with step 10
250      with writer_b.as_default(step=20):
251        tf.summary.scalar(tag, value) # Logged to writer_b with step 20
252      tf.summary.scalar(tag, value)   # Logged to writer_a with step 10
253    ```
254
255    Args:
256      step: An `int64`-castable default step value, or `None`. When not `None`,
257        the current step is captured, replaced by a given one, and the original
258        one is restored when the context manager exits. When `None`, the current
259        step is not modified (and not restored when the context manager exits).
260    """
261    raise NotImplementedError()
262
263  def init(self):
264    """Initializes the summary writer."""
265    raise NotImplementedError()
266
267  def flush(self):
268    """Flushes any buffered data."""
269    raise NotImplementedError()
270
271  def close(self):
272    """Flushes and closes the summary writer."""
273    raise NotImplementedError()
274
275
276class ResourceSummaryWriter(SummaryWriter):
277  """Implementation of SummaryWriter using a SummaryWriterInterface resource."""
278
279  def __init__(self,
280               shared_name,
281               init_op_fn,
282               name=None,
283               v2=False,
284               metadata=None):
285    self._resource = gen_summary_ops.summary_writer(
286        shared_name=shared_name, name=name)
287    # TODO(nickfelt): cache other constructed ops in graph mode
288    self._init_op_fn = init_op_fn
289    self._init_op = init_op_fn(self._resource)
290    self._v2 = v2
291    self._metadata = {} if metadata is None else metadata
292    self._closed = False
293    if context.executing_eagerly():
294      self._resource_deleter = resource_variable_ops.EagerResourceDeleter(
295          handle=self._resource, handle_device="cpu:0")
296    else:
297      ops.add_to_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME, self._init_op)
298
299  def set_as_default(self, step=None):
300    """Enables this summary writer for the current thread.
301
302    For convenience, if `step` is not None, this function also sets a default
303    value for the `step` parameter used in summary-writing functions elsewhere
304    in the API so that it need not be explicitly passed in every such
305    invocation. The value can be a constant or a variable.
306
307    Note: when setting `step` in a @tf.function, the step value will be
308    captured at the time the function is traced, so changes to the step outside
309    the function will not be reflected inside the function unless using
310    a `tf.Variable` step.
311
312    Args:
313      step: An `int64`-castable default step value, or `None`. When not `None`,
314        the current step is modified to the given value. When `None`, the
315        current step is not modified.
316    """
317    if self._v2 and context.executing_eagerly() and self._closed:
318      raise RuntimeError("SummaryWriter is already closed")
319    _summary_state.writer = self
320    if step is not None:
321      _summary_state.step = step
322
323  @tf_contextlib.contextmanager
324  def as_default(self, step=None):
325    """Returns a context manager that enables summary writing.
326
327    For convenience, if `step` is not None, this function also sets a default
328    value for the `step` parameter used in summary-writing functions elsewhere
329    in the API so that it need not be explicitly passed in every such
330    invocation. The value can be a constant or a variable.
331
332    Note: when setting `step` in a @tf.function, the step value will be
333    captured at the time the function is traced, so changes to the step outside
334    the function will not be reflected inside the function unless using
335    a `tf.Variable` step.
336
337    For example, `step` can be used as:
338
339    ```python
340    with writer_a.as_default(step=10):
341      tf.summary.scalar(tag, value)   # Logged to writer_a with step 10
342      with writer_b.as_default(step=20):
343        tf.summary.scalar(tag, value) # Logged to writer_b with step 20
344      tf.summary.scalar(tag, value)   # Logged to writer_a with step 10
345    ```
346
347    Args:
348      step: An `int64`-castable default step value, or `None`. When not `None`,
349        the current step is captured, replaced by a given one, and the original
350        one is restored when the context manager exits. When `None`, the current
351        step is not modified (and not restored when the context manager exits).
352    """
353    if self._v2 and context.executing_eagerly() and self._closed:
354      raise RuntimeError("SummaryWriter is already closed")
355    old = _summary_state.writer
356    if step is not None:
357      old_step = _summary_state.step
358    try:
359      _summary_state.writer = self
360      if step is not None:
361        _summary_state.step = step
362      yield self
363      # Flushes the summary writer in eager mode or in graph functions, but
364      # not in legacy graph mode (you're on your own there).
365      self.flush()
366    finally:
367      _summary_state.writer = old
368      if step is not None:
369        _summary_state.step = old_step
370
371  def init(self):
372    """Initializes the summary writer."""
373    if self._v2:
374      if context.executing_eagerly() and self._closed:
375        raise RuntimeError("SummaryWriter is already closed")
376      return self._init_op
377    # Legacy behavior allows re-initializing the resource.
378    return self._init_op_fn(self._resource)
379
380  def flush(self):
381    """Flushes any buffered data."""
382    if self._v2 and context.executing_eagerly() and self._closed:
383      return
384    return _flush_fn(writer=self)
385
386  def close(self):
387    """Flushes and closes the summary writer."""
388    if self._v2 and context.executing_eagerly() and self._closed:
389      return
390    try:
391      with ops.control_dependencies([self.flush()]):
392        with ops.device("cpu:0"):
393          return gen_summary_ops.close_summary_writer(self._resource)
394    finally:
395      if self._v2 and context.executing_eagerly():
396        self._closed = True
397
398
399class NoopSummaryWriter(SummaryWriter):
400  """A summary writer that does nothing, for create_noop_writer()."""
401
402  def set_as_default(self, step=None):
403    pass
404
405  @tf_contextlib.contextmanager
406  def as_default(self, step=None):
407    yield
408
409  def init(self):
410    pass
411
412  def flush(self):
413    pass
414
415  def close(self):
416    pass
417
418
419@tf_export(v1=["summary.initialize"])
420def initialize(
421    graph=None,  # pylint: disable=redefined-outer-name
422    session=None):
423  """Initializes summary writing for graph execution mode.
424
425  This operation is a no-op when executing eagerly.
426
427  This helper method provides a higher-level alternative to using
428  `tf.contrib.summary.summary_writer_initializer_op` and
429  `tf.contrib.summary.graph`.
430
431  Most users will also want to call `tf.compat.v1.train.create_global_step`
432  which can happen before or after this function is called.
433
434  Args:
435    graph: A `tf.Graph` or `tf.compat.v1.GraphDef` to output to the writer.
436      This function will not write the default graph by default. When
437      writing to an event log file, the associated step will be zero.
438    session: So this method can call `tf.Session.run`. This defaults
439      to `tf.compat.v1.get_default_session`.
440
441  Raises:
442    RuntimeError: If  the current thread has no default
443      `tf.contrib.summary.SummaryWriter`.
444    ValueError: If session wasn't passed and no default session.
445  """
446  if context.executing_eagerly():
447    return
448  if _summary_state.writer is None:
449    raise RuntimeError("No default tf.contrib.summary.SummaryWriter found")
450  if session is None:
451    session = ops.get_default_session()
452    if session is None:
453      raise ValueError("session must be passed if no default session exists")
454  session.run(summary_writer_initializer_op())
455  if graph is not None:
456    data = _serialize_graph(graph)
457    x = array_ops.placeholder(dtypes.string)
458    session.run(graph_v1(x, 0), feed_dict={x: data})
459
460
461@tf_export("summary.create_file_writer", v1=[])
462def create_file_writer_v2(logdir,
463                          max_queue=None,
464                          flush_millis=None,
465                          filename_suffix=None,
466                          name=None):
467  """Creates a summary file writer for the given log directory.
468
469  Args:
470    logdir: a string specifying the directory in which to write an event file.
471    max_queue: the largest number of summaries to keep in a queue; will
472     flush once the queue gets bigger than this. Defaults to 10.
473    flush_millis: the largest interval between flushes. Defaults to 120,000.
474    filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
475    name: a name for the op that creates the writer.
476
477  Returns:
478    A SummaryWriter object.
479  """
480  if logdir is None:
481    raise ValueError("logdir cannot be None")
482  inside_function = ops.inside_function()
483  with ops.name_scope(name, "create_file_writer") as scope, ops.device("cpu:0"):
484    # Run init inside an init_scope() to hoist it out of tf.functions.
485    with ops.init_scope():
486      if context.executing_eagerly():
487        _check_create_file_writer_args(
488            inside_function,
489            logdir=logdir,
490            max_queue=max_queue,
491            flush_millis=flush_millis,
492            filename_suffix=filename_suffix)
493      logdir = ops.convert_to_tensor(logdir, dtype=dtypes.string)
494      if max_queue is None:
495        max_queue = constant_op.constant(10)
496      if flush_millis is None:
497        flush_millis = constant_op.constant(2 * 60 * 1000)
498      if filename_suffix is None:
499        filename_suffix = constant_op.constant(".v2")
500      # Prepend the PID and a process-local UID to the filename suffix to avoid
501      # filename collisions within the machine (the filename already contains
502      # the hostname to avoid cross-machine collisions).
503      unique_prefix = constant_op.constant(".%s.%s" % (os.getpid(), ops.uid()))
504      filename_suffix = unique_prefix + filename_suffix
505      # Use a unique shared_name to prevent resource sharing.
506      if context.executing_eagerly():
507        shared_name = context.shared_name()
508      else:
509        shared_name = ops.name_from_scope_name(scope)  # pylint: disable=protected-access
510      return ResourceSummaryWriter(
511          shared_name=shared_name,
512          init_op_fn=functools.partial(
513              gen_summary_ops.create_summary_file_writer,
514              logdir=logdir,
515              max_queue=max_queue,
516              flush_millis=flush_millis,
517              filename_suffix=filename_suffix),
518          name=name,
519          v2=True,
520          metadata={"logdir": logdir})
521
522
523def create_file_writer(logdir,
524                       max_queue=None,
525                       flush_millis=None,
526                       filename_suffix=None,
527                       name=None):
528  """Creates a summary file writer in the current context under the given name.
529
530  Args:
531    logdir: a string, or None. If a string, creates a summary file writer
532     which writes to the directory named by the string. If None, returns
533     a mock object which acts like a summary writer but does nothing,
534     useful to use as a context manager.
535    max_queue: the largest number of summaries to keep in a queue; will
536     flush once the queue gets bigger than this. Defaults to 10.
537    flush_millis: the largest interval between flushes. Defaults to 120,000.
538    filename_suffix: optional suffix for the event file name. Defaults to `.v2`.
539    name: Shared name for this SummaryWriter resource stored to default
540      Graph. Defaults to the provided logdir prefixed with `logdir:`. Note: if a
541      summary writer resource with this shared name already exists, the returned
542      SummaryWriter wraps that resource and the other arguments have no effect.
543
544  Returns:
545    Either a summary writer or an empty object which can be used as a
546    summary writer.
547  """
548  if logdir is None:
549    return NoopSummaryWriter()
550  logdir = str(logdir)
551  with ops.device("cpu:0"):
552    if max_queue is None:
553      max_queue = constant_op.constant(10)
554    if flush_millis is None:
555      flush_millis = constant_op.constant(2 * 60 * 1000)
556    if filename_suffix is None:
557      filename_suffix = constant_op.constant(".v2")
558    if name is None:
559      name = "logdir:" + logdir
560    return ResourceSummaryWriter(
561        shared_name=name,
562        init_op_fn=functools.partial(
563            gen_summary_ops.create_summary_file_writer,
564            logdir=logdir,
565            max_queue=max_queue,
566            flush_millis=flush_millis,
567            filename_suffix=filename_suffix))
568
569
570@tf_export("summary.create_noop_writer", v1=[])
571def create_noop_writer():
572  """Returns a summary writer that does nothing.
573
574  This is useful as a placeholder in code that expects a context manager.
575  """
576  return NoopSummaryWriter()
577
578
579def _cleanse_string(name, pattern, value):
580  if isinstance(value, six.string_types) and pattern.search(value) is None:
581    raise ValueError("%s (%s) must match %s" % (name, value, pattern.pattern))
582  return ops.convert_to_tensor(value, dtypes.string)
583
584
585def _nothing():
586  """Convenient else branch for when summaries do not record."""
587  return constant_op.constant(False)
588
589
590@tf_export(v1=["summary.all_v2_summary_ops"])
591def all_v2_summary_ops():
592  """Returns all V2-style summary ops defined in the current default graph.
593
594  This includes ops from TF 2.0 tf.summary and TF 1.x tf.contrib.summary (except
595  for `tf.contrib.summary.graph` and `tf.contrib.summary.import_event`), but
596  does *not* include TF 1.x tf.summary ops.
597
598  Returns:
599    List of summary ops, or None if called under eager execution.
600  """
601  if context.executing_eagerly():
602    return None
603  return ops.get_collection(ops.GraphKeys._SUMMARY_COLLECTION)  # pylint: disable=protected-access
604
605
606def summary_writer_initializer_op():
607  """Graph-mode only. Returns the list of ops to create all summary writers.
608
609  Returns:
610    The initializer ops.
611
612  Raises:
613    RuntimeError: If in Eager mode.
614  """
615  if context.executing_eagerly():
616    raise RuntimeError(
617        "tf.contrib.summary.summary_writer_initializer_op is only "
618        "supported in graph mode.")
619  return ops.get_collection(_SUMMARY_WRITER_INIT_COLLECTION_NAME)
620
621
622_INVALID_SCOPE_CHARACTERS = re.compile(r"[^-_/.A-Za-z0-9]")
623
624
625@tf_export("summary.experimental.summary_scope", v1=[])
626@tf_contextlib.contextmanager
627def summary_scope(name, default_name="summary", values=None):
628  """Experimental context manager for use when defining a custom summary op.
629
630  This behaves similarly to `tf.name_scope`, except that it returns a generated
631  summary tag in addition to the scope name. The tag is structurally similar to
632  the scope name - derived from the user-provided name, prefixed with enclosing
633  name scopes if any - but we relax the constraint that it be uniquified, as
634  well as the character set limitation (so the user-provided name can contain
635  characters not legal for scope names; in the scope name these are removed).
636
637  This makes the summary tag more predictable and consistent for the user.
638
639  For example, to define a new summary op called `my_op`:
640
641  ```python
642  def my_op(name, my_value, step):
643    with tf.summary.summary_scope(name, "MyOp", [my_value]) as (tag, scope):
644      my_value = tf.convert_to_tensor(my_value)
645      return tf.summary.write(tag, my_value, step=step)
646  ```
647
648  Args:
649    name: string name for the summary.
650    default_name: Optional; if provided, used as default name of the summary.
651    values: Optional; passed as `values` parameter to name_scope.
652
653  Yields:
654    A tuple `(tag, scope)` as described above.
655  """
656  name = name or default_name
657  current_scope = ops.get_name_scope()
658  tag = current_scope + "/" + name if current_scope else name
659  # Strip illegal characters from the scope name, and if that leaves nothing,
660  # use None instead so we pick up the default name.
661  name = _INVALID_SCOPE_CHARACTERS.sub("", name) or None
662  with ops.name_scope(name, default_name, values, skip_on_eager=False) as scope:
663    yield tag, scope
664
665
666@tf_export("summary.write", v1=[])
667def write(tag, tensor, step=None, metadata=None, name=None):
668  """Writes a generic summary to the default SummaryWriter if one exists.
669
670  This exists primarily to support the definition of type-specific summary ops
671  like scalar() and image(), and is not intended for direct use unless defining
672  a new type-specific summary op.
673
674  Args:
675    tag: string tag used to identify the summary (e.g. in TensorBoard), usually
676      generated with `tf.summary.summary_scope`
677    tensor: the Tensor holding the summary data to write or a callable that
678      returns this Tensor. If a callable is passed, it will only be called when
679      a default SummaryWriter exists and the recording condition specified by
680      `record_if()` is met.
681    step: Explicit `int64`-castable monotonic step value for this summary. If
682      omitted, this defaults to `tf.summary.experimental.get_step()`, which must
683      not be None.
684    metadata: Optional SummaryMetadata, as a proto or serialized bytes
685    name: Optional string name for this op.
686
687  Returns:
688    True on success, or false if no summary was written because no default
689    summary writer was available.
690
691  Raises:
692    ValueError: if a default writer exists, but no step was provided and
693      `tf.summary.experimental.get_step()` is None.
694  """
695  with ops.name_scope(name, "write_summary") as scope:
696    if _summary_state.writer is None:
697      return constant_op.constant(False)
698    if step is None:
699      step = get_step()
700    if metadata is None:
701      serialized_metadata = b""
702    elif hasattr(metadata, "SerializeToString"):
703      serialized_metadata = metadata.SerializeToString()
704    else:
705      serialized_metadata = metadata
706
707    def record():
708      """Record the actual summary and return True."""
709      if step is None:
710        raise ValueError("No step set via 'step' argument or "
711                         "tf.summary.experimental.set_step()")
712
713      # Note the identity to move the tensor to the CPU.
714      with ops.device("cpu:0"):
715        summary_tensor = tensor() if callable(tensor) else array_ops.identity(
716            tensor)
717        write_summary_op = gen_summary_ops.write_summary(
718            _summary_state.writer._resource,  # pylint: disable=protected-access
719            step,
720            summary_tensor,
721            tag,
722            serialized_metadata,
723            name=scope)
724        with ops.control_dependencies([write_summary_op]):
725          return constant_op.constant(True)
726
727    op = smart_cond.smart_cond(
728        _should_record_summaries_v2(), record, _nothing, name="summary_cond")
729    if not context.executing_eagerly():
730      ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op)  # pylint: disable=protected-access
731    return op
732
733
734@tf_export("summary.experimental.write_raw_pb", v1=[])
735def write_raw_pb(tensor, step=None, name=None):
736  """Writes a summary using raw `tf.compat.v1.Summary` protocol buffers.
737
738  Experimental: this exists to support the usage of V1-style manual summary
739  writing (via the construction of a `tf.compat.v1.Summary` protocol buffer)
740  with the V2 summary writing API.
741
742  Args:
743    tensor: the string Tensor holding one or more serialized `Summary` protobufs
744    step: Explicit `int64`-castable monotonic step value for this summary. If
745      omitted, this defaults to `tf.summary.experimental.get_step()`, which must
746      not be None.
747    name: Optional string name for this op.
748
749  Returns:
750    True on success, or false if no summary was written because no default
751    summary writer was available.
752
753  Raises:
754    ValueError: if a default writer exists, but no step was provided and
755      `tf.summary.experimental.get_step()` is None.
756  """
757  with ops.name_scope(name, "write_raw_pb") as scope:
758    if _summary_state.writer is None:
759      return constant_op.constant(False)
760    if step is None:
761      step = get_step()
762      if step is None:
763        raise ValueError("No step set via 'step' argument or "
764                         "tf.summary.experimental.set_step()")
765
766    def record():
767      """Record the actual summary and return True."""
768      # Note the identity to move the tensor to the CPU.
769      with ops.device("cpu:0"):
770        raw_summary_op = gen_summary_ops.write_raw_proto_summary(
771            _summary_state.writer._resource,  # pylint: disable=protected-access
772            step,
773            array_ops.identity(tensor),
774            name=scope)
775        with ops.control_dependencies([raw_summary_op]):
776          return constant_op.constant(True)
777
778    with ops.device("cpu:0"):
779      op = smart_cond.smart_cond(
780          _should_record_summaries_v2(), record, _nothing, name="summary_cond")
781      if not context.executing_eagerly():
782        ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op)  # pylint: disable=protected-access
783      return op
784
785
786def summary_writer_function(name, tensor, function, family=None):
787  """Helper function to write summaries.
788
789  Args:
790    name: name of the summary
791    tensor: main tensor to form the summary
792    function: function taking a tag and a scope which writes the summary
793    family: optional, the summary's family
794
795  Returns:
796    The result of writing the summary.
797  """
798  name_scope = ops.get_name_scope()
799  if name_scope:
800    # Add a slash to allow reentering the name scope.
801    name_scope += "/"
802  def record():
803    with ops.name_scope(name_scope), summary_op_util.summary_scope(
804        name, family, values=[tensor]) as (tag, scope):
805      with ops.control_dependencies([function(tag, scope)]):
806        return constant_op.constant(True)
807
808  if _summary_state.writer is None:
809    return control_flow_ops.no_op()
810  with ops.device("cpu:0"):
811    op = smart_cond.smart_cond(
812        should_record_summaries(), record, _nothing, name="")
813    if not context.executing_eagerly():
814      ops.add_to_collection(ops.GraphKeys._SUMMARY_COLLECTION, op)  # pylint: disable=protected-access
815  return op
816
817
818def generic(name, tensor, metadata=None, family=None, step=None):
819  """Writes a tensor summary if possible."""
820
821  def function(tag, scope):
822    if metadata is None:
823      serialized_metadata = constant_op.constant("")
824    elif hasattr(metadata, "SerializeToString"):
825      serialized_metadata = constant_op.constant(metadata.SerializeToString())
826    else:
827      serialized_metadata = metadata
828    # Note the identity to move the tensor to the CPU.
829    return gen_summary_ops.write_summary(
830        _summary_state.writer._resource,  # pylint: disable=protected-access
831        _choose_step(step),
832        array_ops.identity(tensor),
833        tag,
834        serialized_metadata,
835        name=scope)
836  return summary_writer_function(name, tensor, function, family=family)
837
838
839def scalar(name, tensor, family=None, step=None):
840  """Writes a scalar summary if possible.
841
842  Unlike `tf.contrib.summary.generic` this op may change the dtype
843  depending on the writer, for both practical and efficiency concerns.
844
845  Args:
846    name: An arbitrary name for this summary.
847    tensor: A `tf.Tensor` Must be one of the following types:
848      `float32`, `float64`, `int32`, `int64`, `uint8`, `int16`,
849      `int8`, `uint16`, `half`, `uint32`, `uint64`.
850    family: Optional, the summary's family.
851    step: The `int64` monotonic step variable, which defaults
852      to `tf.compat.v1.train.get_global_step`.
853
854  Returns:
855    The created `tf.Operation` or a `tf.no_op` if summary writing has
856    not been enabled for this context.
857  """
858
859  def function(tag, scope):
860    # Note the identity to move the tensor to the CPU.
861    return gen_summary_ops.write_scalar_summary(
862        _summary_state.writer._resource,  # pylint: disable=protected-access
863        _choose_step(step),
864        tag,
865        array_ops.identity(tensor),
866        name=scope)
867
868  return summary_writer_function(name, tensor, function, family=family)
869
870
871def histogram(name, tensor, family=None, step=None):
872  """Writes a histogram summary if possible."""
873
874  def function(tag, scope):
875    # Note the identity to move the tensor to the CPU.
876    return gen_summary_ops.write_histogram_summary(
877        _summary_state.writer._resource,  # pylint: disable=protected-access
878        _choose_step(step),
879        tag,
880        array_ops.identity(tensor),
881        name=scope)
882
883  return summary_writer_function(name, tensor, function, family=family)
884
885
886def image(name, tensor, bad_color=None, max_images=3, family=None, step=None):
887  """Writes an image summary if possible."""
888
889  def function(tag, scope):
890    bad_color_ = (constant_op.constant([255, 0, 0, 255], dtype=dtypes.uint8)
891                  if bad_color is None else bad_color)
892    # Note the identity to move the tensor to the CPU.
893    return gen_summary_ops.write_image_summary(
894        _summary_state.writer._resource,  # pylint: disable=protected-access
895        _choose_step(step),
896        tag,
897        array_ops.identity(tensor),
898        bad_color_,
899        max_images,
900        name=scope)
901
902  return summary_writer_function(name, tensor, function, family=family)
903
904
905def audio(name, tensor, sample_rate, max_outputs, family=None, step=None):
906  """Writes an audio summary if possible."""
907
908  def function(tag, scope):
909    # Note the identity to move the tensor to the CPU.
910    return gen_summary_ops.write_audio_summary(
911        _summary_state.writer._resource,  # pylint: disable=protected-access
912        _choose_step(step),
913        tag,
914        array_ops.identity(tensor),
915        sample_rate=sample_rate,
916        max_outputs=max_outputs,
917        name=scope)
918
919  return summary_writer_function(name, tensor, function, family=family)
920
921
922def graph_v1(param, step=None, name=None):
923  """Writes a TensorFlow graph to the summary interface.
924
925  The graph summary is, strictly speaking, not a summary. Conditions
926  like `tf.summary.should_record_summaries` do not apply. Only
927  a single graph can be associated with a particular run. If multiple
928  graphs are written, then only the last one will be considered by
929  TensorBoard.
930
931  When not using eager execution mode, the user should consider passing
932  the `graph` parameter to `tf.compat.v1.summary.initialize` instead of
933  calling this function. Otherwise special care needs to be taken when
934  using the graph to record the graph.
935
936  Args:
937    param: A `tf.Tensor` containing a serialized graph proto. When
938      eager execution is enabled, this function will automatically
939      coerce `tf.Graph`, `tf.compat.v1.GraphDef`, and string types.
940    step: The global step variable. This doesn't have useful semantics
941      for graph summaries, but is used anyway, due to the structure of
942      event log files. This defaults to the global step.
943    name: A name for the operation (optional).
944
945  Returns:
946    The created `tf.Operation` or a `tf.no_op` if summary writing has
947    not been enabled for this context.
948
949  Raises:
950    TypeError: If `param` isn't already a `tf.Tensor` in graph mode.
951  """
952  if not context.executing_eagerly() and not isinstance(param, ops.Tensor):
953    raise TypeError("graph() needs a tf.Tensor (e.g. tf.placeholder) in graph "
954                    "mode, but was: %s" % type(param))
955  writer = _summary_state.writer
956  if writer is None:
957    return control_flow_ops.no_op()
958  with ops.device("cpu:0"):
959    if isinstance(param, (ops.Graph, graph_pb2.GraphDef)):
960      tensor = ops.convert_to_tensor(_serialize_graph(param), dtypes.string)
961    else:
962      tensor = array_ops.identity(param)
963    return gen_summary_ops.write_graph_summary(
964        writer._resource, _choose_step(step), tensor, name=name)  # pylint: disable=protected-access
965
966
967@tf_export("summary.graph", v1=[])
968def graph(graph_data):
969  """Writes a TensorFlow graph summary.
970
971  Write an instance of `tf.Graph` or `tf.compat.v1.GraphDef` as summary only
972  in an eager mode. Please prefer to use the trace APIs (`tf.summary.trace_on`,
973  `tf.summary.trace_off`, and `tf.summary.trace_export`) when using
974  `tf.function` which can automatically collect and record graphs from
975  executions.
976
977  Usage Example:
978  ```py
979  graph = tf.Graph()
980  with graph.as_default():
981    c = tf.constant(30.0)
982  writer = tf.summary.create_file_writer("/tmp/mylogs")
983  with writer.as_default():
984    tf.summary.graph(graph)
985
986  # Another example; must attain the concrete function graph manually.
987  @tf.function
988  def f():
989    x = constant_op.constant(2)
990    y = constant_op.constant(3)
991    return x**y
992
993  with writer.as_default():
994    tf.summary.graph(f.get_concrete_function().graph)
995  ```
996
997  Args:
998    graph_data: The TensorFlow graph to write, as a `tf.Graph` or a
999      `tf.compat.v1.GraphDef`.
1000
1001  Returns:
1002    True on success, or False if no summary was written because no default
1003    summary writer was available.
1004
1005  Raises:
1006    ValueError: `graph` summary API is invoked in a graph mode.
1007  """
1008  if not context.executing_eagerly():
1009    raise ValueError("graph() cannot be invoked inside a graph context.")
1010  writer = _summary_state.writer
1011  if writer is None:
1012    return constant_op.constant(False)
1013  with ops.device("cpu:0"):
1014    if not _should_record_summaries_v2():
1015      return constant_op.constant(False)
1016
1017    if isinstance(graph_data, (ops.Graph, graph_pb2.GraphDef)):
1018      tensor = ops.convert_to_tensor(
1019          _serialize_graph(graph_data), dtypes.string)
1020    else:
1021      raise ValueError("'graph_data' is not tf.Graph or tf.compat.v1.GraphDef")
1022
1023    gen_summary_ops.write_graph_summary(
1024        writer._resource,  # pylint: disable=protected-access
1025        # Graph does not have step. Set to 0.
1026        0,
1027        tensor,
1028    )
1029    return constant_op.constant(True)
1030
1031
1032def import_event(tensor, name=None):
1033  """Writes a `tf.compat.v1.Event` binary proto.
1034
1035  This can be used to import existing event logs into a new summary writer sink.
1036  Please note that this is lower level than the other summary functions and
1037  will ignore the `tf.summary.should_record_summaries` setting.
1038
1039  Args:
1040    tensor: A `tf.Tensor` of type `string` containing a serialized
1041      `tf.compat.v1.Event` proto.
1042    name: A name for the operation (optional).
1043
1044  Returns:
1045    The created `tf.Operation`.
1046  """
1047  return gen_summary_ops.import_event(
1048      _summary_state.writer._resource, tensor, name=name)  # pylint: disable=protected-access
1049
1050
1051@tf_export("summary.flush", v1=[])
1052def flush(writer=None, name=None):
1053  """Forces summary writer to send any buffered data to storage.
1054
1055  This operation blocks until that finishes.
1056
1057  Args:
1058    writer: The `tf.summary.SummaryWriter` resource to flush.
1059      The thread default will be used if this parameter is None.
1060      Otherwise a `tf.no_op` is returned.
1061    name: A name for the operation (optional).
1062
1063  Returns:
1064    The created `tf.Operation`.
1065  """
1066  if writer is None:
1067    writer = _summary_state.writer
1068    if writer is None:
1069      return control_flow_ops.no_op()
1070  if isinstance(writer, ResourceSummaryWriter):
1071    resource = writer._resource  # pylint: disable=protected-access
1072  else:
1073    # Assume we were passed a raw resource tensor.
1074    resource = writer
1075  with ops.device("cpu:0"):
1076    return gen_summary_ops.flush_summary_writer(resource, name=name)
1077
1078
1079_flush_fn = flush  # for within SummaryWriter.flush()
1080
1081
1082def eval_dir(model_dir, name=None):
1083  """Construct a logdir for an eval summary writer."""
1084  return os.path.join(model_dir, "eval" if not name else "eval_" + name)
1085
1086
1087@deprecation.deprecated(date=None,
1088                        instructions="Renamed to create_file_writer().")
1089def create_summary_file_writer(*args, **kwargs):
1090  """Please use `tf.contrib.summary.create_file_writer`."""
1091  logging.warning("Deprecation Warning: create_summary_file_writer was renamed "
1092                  "to create_file_writer")
1093  return create_file_writer(*args, **kwargs)
1094
1095
1096def _serialize_graph(arbitrary_graph):
1097  if isinstance(arbitrary_graph, ops.Graph):
1098    return arbitrary_graph.as_graph_def(add_shapes=True).SerializeToString()
1099  else:
1100    return arbitrary_graph.SerializeToString()
1101
1102
1103def _choose_step(step):
1104  if step is None:
1105    return training_util.get_or_create_global_step()
1106  if not isinstance(step, ops.Tensor):
1107    return ops.convert_to_tensor(step, dtypes.int64)
1108  return step
1109
1110
1111def _check_create_file_writer_args(inside_function, **kwargs):
1112  """Helper to check the validity of arguments to a create_file_writer() call.
1113
1114  Args:
1115    inside_function: whether the create_file_writer() call is in a tf.function
1116    **kwargs: the arguments to check, as kwargs to give them names.
1117
1118  Raises:
1119    ValueError: if the arguments are graph tensors.
1120  """
1121  for arg_name, arg in kwargs.items():
1122    if not isinstance(arg, ops.EagerTensor) and tensor_util.is_tf_type(arg):
1123      if inside_function:
1124        raise ValueError(
1125            "Invalid graph Tensor argument \"%s=%s\" to create_file_writer() "
1126            "inside an @tf.function. The create call will be lifted into the "
1127            "outer eager execution context, so it cannot consume graph tensors "
1128            "defined inside the function body." % (arg_name, arg))
1129      else:
1130        raise ValueError(
1131            "Invalid graph Tensor argument \"%s=%s\" to eagerly executed "
1132            "create_file_writer()." % (arg_name, arg))
1133
1134
1135def run_metadata(name, data, step=None):
1136  """Writes entire RunMetadata summary.
1137
1138  A RunMetadata can contain DeviceStats, partition graphs, and function graphs.
1139  Please refer to the proto for definition of each field.
1140
1141  Args:
1142    name: A name for this summary. The summary tag used for TensorBoard will be
1143      this name prefixed by any active name scopes.
1144    data: A RunMetadata proto to write.
1145    step: Explicit `int64`-castable monotonic step value for this summary. If
1146      omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1147      not be None.
1148
1149  Returns:
1150    True on success, or false if no summary was written because no default
1151    summary writer was available.
1152
1153  Raises:
1154    ValueError: if a default writer exists, but no step was provided and
1155      `tf.summary.experimental.get_step()` is None.
1156  """
1157  summary_metadata = summary_pb2.SummaryMetadata()
1158  # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
1159  # the rationale.
1160  summary_metadata.plugin_data.plugin_name = "graph_run_metadata"
1161  # version number = 1
1162  summary_metadata.plugin_data.content = b"1"
1163
1164  with summary_scope(name,
1165                     "graph_run_metadata_summary",
1166                     [data, step]) as (tag, _):
1167    with ops.device("cpu:0"):
1168      tensor = constant_op.constant(data.SerializeToString(),
1169                                    dtype=dtypes.string)
1170    return write(
1171        tag=tag,
1172        tensor=tensor,
1173        step=step,
1174        metadata=summary_metadata)
1175
1176
1177def run_metadata_graphs(name, data, step=None):
1178  """Writes graphs from a RunMetadata summary.
1179
1180  Args:
1181    name: A name for this summary. The summary tag used for TensorBoard will be
1182      this name prefixed by any active name scopes.
1183    data: A RunMetadata proto to write.
1184    step: Explicit `int64`-castable monotonic step value for this summary. If
1185      omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1186      not be None.
1187
1188  Returns:
1189    True on success, or false if no summary was written because no default
1190    summary writer was available.
1191
1192  Raises:
1193    ValueError: if a default writer exists, but no step was provided and
1194      `tf.summary.experimental.get_step()` is None.
1195  """
1196  summary_metadata = summary_pb2.SummaryMetadata()
1197  # Hard coding a plugin name. Please refer to go/tb-plugin-name-hardcode for
1198  # the rationale.
1199  summary_metadata.plugin_data.plugin_name = "graph_run_metadata_graph"
1200  # version number = 1
1201  summary_metadata.plugin_data.content = b"1"
1202
1203  data = config_pb2.RunMetadata(
1204      function_graphs=data.function_graphs,
1205      partition_graphs=data.partition_graphs)
1206
1207  with summary_scope(name,
1208                     "graph_run_metadata_graph_summary",
1209                     [data, step]) as (tag, _):
1210    with ops.device("cpu:0"):
1211      tensor = constant_op.constant(data.SerializeToString(),
1212                                    dtype=dtypes.string)
1213    return write(
1214        tag=tag,
1215        tensor=tensor,
1216        step=step,
1217        metadata=summary_metadata)
1218
1219
1220_TraceContext = collections.namedtuple("TraceContext", ("graph", "profiler"))
1221_current_trace_context_lock = threading.Lock()
1222_current_trace_context = None
1223
1224
1225@tf_export("summary.trace_on", v1=[])
1226def trace_on(graph=True, profiler=False):  # pylint: disable=redefined-outer-name
1227  """Starts a trace to record computation graphs and profiling information.
1228
1229  Must be invoked in eager mode.
1230
1231  When enabled, TensorFlow runtime will collection information that can later be
1232  exported and consumed by TensorBoard. The trace is activated across the entire
1233  TensorFlow runtime and affects all threads of execution.
1234
1235  To stop the trace and export the collected information, use
1236  `tf.summary.trace_export`. To stop the trace without exporting, use
1237  `tf.summary.trace_off`.
1238
1239  Args:
1240    graph: If True, enables collection of executed graphs. It includes ones from
1241        tf.function invocation and ones from the legacy graph mode. The default
1242        is True.
1243    profiler: If True, enables the advanced profiler. Enabling profiler
1244        implicitly enables the graph collection. The profiler may incur a high
1245        memory overhead. The default is False.
1246
1247  """
1248  if ops.inside_function():
1249    logging.warn("Cannot enable trace inside a tf.function.")
1250    return
1251  if not context.executing_eagerly():
1252    logging.warn("Must enable trace in eager mode.")
1253    return
1254
1255  global _current_trace_context
1256  with _current_trace_context_lock:
1257    if _current_trace_context:
1258      logging.warn("Trace already enabled")
1259      return
1260
1261    if graph and not profiler:
1262      context.context().enable_graph_collection()
1263    if profiler:
1264      context.context().enable_run_metadata()
1265      _profiler.start()
1266
1267    _current_trace_context = _TraceContext(graph=graph, profiler=profiler)
1268
1269
1270@tf_export("summary.trace_export", v1=[])
1271def trace_export(name, step=None, profiler_outdir=None):
1272  """Stops and exports the active trace as a Summary and/or profile file.
1273
1274  Stops the trace and exports all metadata collected during the trace to the
1275  default SummaryWriter, if one has been set.
1276
1277  Args:
1278    name: A name for the summary to be written.
1279    step: Explicit `int64`-castable monotonic step value for this summary. If
1280      omitted, this defaults to `tf.summary.experimental.get_step()`, which must
1281      not be None.
1282    profiler_outdir: Output directory for profiler. This is only used when the
1283      profiler was enabled when the trace was started. In that case, if there is
1284      a logdir-based default SummaryWriter, this defaults to the same directory,
1285      but otherwise the argument must be passed.
1286
1287  Raises:
1288    ValueError: if a default writer exists, but no step was provided and
1289      `tf.summary.experimental.get_step()` is None.
1290  """
1291  global _current_trace_context
1292
1293  if ops.inside_function():
1294    logging.warn("Cannot export trace inside a tf.function.")
1295    return
1296  if not context.executing_eagerly():
1297    logging.warn("Can only export trace while executing eagerly.")
1298    return
1299
1300  with _current_trace_context_lock:
1301    if _current_trace_context is None:
1302      raise ValueError("Must enable trace before export.")
1303    graph, profiler = _current_trace_context  # pylint: disable=redefined-outer-name
1304    if profiler_outdir is None \
1305        and isinstance(_summary_state.writer, ResourceSummaryWriter):
1306      logdir = _summary_state.writer._metadata.get("logdir")  # pylint: disable=protected-access
1307      if logdir is not None:
1308        profiler_outdir = logdir
1309    if profiler and profiler_outdir is None:
1310      raise ValueError("Must set profiler_outdir or "
1311                       "enable summary writer with logdir.")
1312
1313  run_meta = context.context().export_run_metadata()
1314
1315  if graph and not profiler:
1316    run_metadata_graphs(name, run_meta, step)
1317  else:
1318    run_metadata(name, run_meta, step)
1319
1320  if profiler:
1321    _profiler.save(profiler_outdir, _profiler.stop())
1322
1323  trace_off()
1324
1325
1326@tf_export("summary.trace_off", v1=[])
1327def trace_off():
1328  """Stops the current trace and discards any collected information."""
1329  global _current_trace_context
1330  with _current_trace_context_lock:
1331    if _current_trace_context is None:
1332      return  # tracing already off
1333    graph, profiler = _current_trace_context  # pylint: disable=redefined-outer-name, unpacking-non-sequence
1334    _current_trace_context = None
1335
1336  if graph:
1337    # Disabling run_metadata disables graph collection as well.
1338    context.context().disable_run_metadata()
1339
1340  if profiler:
1341    try:
1342      _profiler.stop()
1343    except _profiler.ProfilerNotRunningError:
1344      pass
1345