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