1# Copyright 2016 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"""Provides an API for generating Event protocol buffers.""" 16 17import os.path 18import time 19import warnings 20 21from tensorflow.core.framework import graph_pb2 22from tensorflow.core.framework import summary_pb2 23from tensorflow.core.protobuf import meta_graph_pb2 24from tensorflow.core.util import event_pb2 25from tensorflow.python.eager import context 26from tensorflow.python.framework import meta_graph 27from tensorflow.python.framework import ops 28from tensorflow.python.platform import gfile 29from tensorflow.python.platform import tf_logging as logging 30from tensorflow.python.summary import plugin_asset 31from tensorflow.python.summary.writer.event_file_writer import EventFileWriter 32from tensorflow.python.summary.writer.event_file_writer_v2 import EventFileWriterV2 33from tensorflow.python.util.tf_export import tf_export 34 35_PLUGINS_DIR = "plugins" 36 37 38class SummaryToEventTransformer(object): 39 """Abstractly implements the SummaryWriter API. 40 41 This API basically implements a number of endpoints (add_summary, 42 add_session_log, etc). The endpoints all generate an event protobuf, which is 43 passed to the contained event_writer. 44 """ 45 46 def __init__(self, event_writer, graph=None, graph_def=None): 47 """Creates a `SummaryWriter` and an event file. 48 49 On construction the summary writer creates a new event file in `logdir`. 50 This event file will contain `Event` protocol buffers constructed when you 51 call one of the following functions: `add_summary()`, `add_session_log()`, 52 `add_event()`, or `add_graph()`. 53 54 If you pass a `Graph` to the constructor it is added to 55 the event file. (This is equivalent to calling `add_graph()` later). 56 57 TensorBoard will pick the graph from the file and display it graphically so 58 you can interactively explore the graph you built. You will usually pass 59 the graph from the session in which you launched it: 60 61 ```python 62 ...create a graph... 63 # Launch the graph in a session. 64 sess = tf.compat.v1.Session() 65 # Create a summary writer, add the 'graph' to the event file. 66 writer = tf.compat.v1.summary.FileWriter(<some-directory>, sess.graph) 67 ``` 68 69 70 Args: 71 event_writer: An EventWriter. Implements add_event and get_logdir. 72 graph: A `Graph` object, such as `sess.graph`. 73 graph_def: DEPRECATED: Use the `graph` argument instead. 74 """ 75 self.event_writer = event_writer 76 # For storing used tags for session.run() outputs. 77 self._session_run_tags = {} 78 if graph is not None or graph_def is not None: 79 # Calling it with both graph and graph_def for backward compatibility. 80 self.add_graph(graph=graph, graph_def=graph_def) 81 # Also export the meta_graph_def in this case. 82 # graph may itself be a graph_def due to positional arguments 83 maybe_graph_as_def = (graph.as_graph_def(add_shapes=True) 84 if isinstance(graph, ops.Graph) else graph) 85 self.add_meta_graph( 86 meta_graph.create_meta_graph_def(graph_def=graph_def or 87 maybe_graph_as_def)) 88 89 # This set contains tags of Summary Values that have been encountered 90 # already. The motivation here is that the SummaryWriter only keeps the 91 # metadata property (which is a SummaryMetadata proto) of the first Summary 92 # Value encountered for each tag. The SummaryWriter strips away the 93 # SummaryMetadata for all subsequent Summary Values with tags seen 94 # previously. This saves space. 95 self._seen_summary_tags = set() 96 97 def add_summary(self, summary, global_step=None): 98 """Adds a `Summary` protocol buffer to the event file. 99 100 This method wraps the provided summary in an `Event` protocol buffer 101 and adds it to the event file. 102 103 You can pass the result of evaluating any summary op, using 104 `tf.Session.run` or 105 `tf.Tensor.eval`, to this 106 function. Alternatively, you can pass a `tf.compat.v1.Summary` protocol 107 buffer that you populate with your own data. The latter is 108 commonly done to report evaluation results in event files. 109 110 Args: 111 summary: A `Summary` protocol buffer, optionally serialized as a string. 112 global_step: Number. Optional global step value to record with the 113 summary. 114 """ 115 if isinstance(summary, bytes): 116 summ = summary_pb2.Summary() 117 summ.ParseFromString(summary) 118 summary = summ 119 120 # We strip metadata from values with tags that we have seen before in order 121 # to save space - we just store the metadata on the first value with a 122 # specific tag. 123 for value in summary.value: 124 if not value.metadata: 125 continue 126 127 if value.tag in self._seen_summary_tags: 128 # This tag has been encountered before. Strip the metadata. 129 value.ClearField("metadata") 130 continue 131 132 # We encounter a value with a tag we have not encountered previously. And 133 # it has metadata. Remember to strip metadata from future values with this 134 # tag string. 135 self._seen_summary_tags.add(value.tag) 136 137 event = event_pb2.Event(summary=summary) 138 self._add_event(event, global_step) 139 140 def add_session_log(self, session_log, global_step=None): 141 """Adds a `SessionLog` protocol buffer to the event file. 142 143 This method wraps the provided session in an `Event` protocol buffer 144 and adds it to the event file. 145 146 Args: 147 session_log: A `SessionLog` protocol buffer. 148 global_step: Number. Optional global step value to record with the 149 summary. 150 """ 151 event = event_pb2.Event(session_log=session_log) 152 self._add_event(event, global_step) 153 154 def _add_graph_def(self, graph_def, global_step=None): 155 graph_bytes = graph_def.SerializeToString() 156 event = event_pb2.Event(graph_def=graph_bytes) 157 self._add_event(event, global_step) 158 159 def add_graph(self, graph, global_step=None, graph_def=None): 160 """Adds a `Graph` to the event file. 161 162 The graph described by the protocol buffer will be displayed by 163 TensorBoard. Most users pass a graph in the constructor instead. 164 165 Args: 166 graph: A `Graph` object, such as `sess.graph`. 167 global_step: Number. Optional global step counter to record with the 168 graph. 169 graph_def: DEPRECATED. Use the `graph` parameter instead. 170 171 Raises: 172 ValueError: If both graph and graph_def are passed to the method. 173 """ 174 175 if graph is not None and graph_def is not None: 176 raise ValueError("Please pass only graph, or graph_def (deprecated), " 177 "but not both.") 178 179 if isinstance(graph, ops.Graph) or isinstance(graph_def, ops.Graph): 180 # The user passed a `Graph`. 181 182 # Check if the user passed it via the graph or the graph_def argument and 183 # correct for that. 184 if not isinstance(graph, ops.Graph): 185 logging.warning("When passing a `Graph` object, please use the `graph`" 186 " named argument instead of `graph_def`.") 187 graph = graph_def 188 189 # Serialize the graph with additional info. 190 true_graph_def = graph.as_graph_def(add_shapes=True) 191 self._write_plugin_assets(graph) 192 elif (isinstance(graph, graph_pb2.GraphDef) or 193 isinstance(graph_def, graph_pb2.GraphDef)): 194 # The user passed a `GraphDef`. 195 logging.warning("Passing a `GraphDef` to the SummaryWriter is deprecated." 196 " Pass a `Graph` object instead, such as `sess.graph`.") 197 198 # Check if the user passed it via the graph or the graph_def argument and 199 # correct for that. 200 if isinstance(graph, graph_pb2.GraphDef): 201 true_graph_def = graph 202 else: 203 true_graph_def = graph_def 204 205 else: 206 # The user passed neither `Graph`, nor `GraphDef`. 207 raise TypeError("The passed graph must be an instance of `Graph` " 208 "or the deprecated `GraphDef`") 209 # Finally, add the graph_def to the summary writer. 210 self._add_graph_def(true_graph_def, global_step) 211 212 def _write_plugin_assets(self, graph): 213 plugin_assets = plugin_asset.get_all_plugin_assets(graph) 214 logdir = self.event_writer.get_logdir() 215 for asset_container in plugin_assets: 216 plugin_name = asset_container.plugin_name 217 plugin_dir = os.path.join(logdir, _PLUGINS_DIR, plugin_name) 218 gfile.MakeDirs(plugin_dir) 219 assets = asset_container.assets() 220 for (asset_name, content) in assets.items(): 221 asset_path = os.path.join(plugin_dir, asset_name) 222 with gfile.Open(asset_path, "w") as f: 223 f.write(content) 224 225 def add_meta_graph(self, meta_graph_def, global_step=None): 226 """Adds a `MetaGraphDef` to the event file. 227 228 The `MetaGraphDef` allows running the given graph via 229 `saver.import_meta_graph()`. 230 231 Args: 232 meta_graph_def: A `MetaGraphDef` object, often as returned by 233 `saver.export_meta_graph()`. 234 global_step: Number. Optional global step counter to record with the 235 graph. 236 237 Raises: 238 TypeError: If both `meta_graph_def` is not an instance of `MetaGraphDef`. 239 """ 240 if not isinstance(meta_graph_def, meta_graph_pb2.MetaGraphDef): 241 raise TypeError("meta_graph_def must be type MetaGraphDef, saw type: %s" % 242 type(meta_graph_def)) 243 meta_graph_bytes = meta_graph_def.SerializeToString() 244 event = event_pb2.Event(meta_graph_def=meta_graph_bytes) 245 self._add_event(event, global_step) 246 247 def add_run_metadata(self, run_metadata, tag, global_step=None): 248 """Adds a metadata information for a single session.run() call. 249 250 Args: 251 run_metadata: A `RunMetadata` protobuf object. 252 tag: The tag name for this metadata. 253 global_step: Number. Optional global step counter to record with the 254 StepStats. 255 256 Raises: 257 ValueError: If the provided tag was already used for this type of event. 258 """ 259 if tag in self._session_run_tags: 260 raise ValueError("The provided tag was already used for this event type") 261 self._session_run_tags[tag] = True 262 263 tagged_metadata = event_pb2.TaggedRunMetadata() 264 tagged_metadata.tag = tag 265 # Store the `RunMetadata` object as bytes in order to have postponed 266 # (lazy) deserialization when used later. 267 tagged_metadata.run_metadata = run_metadata.SerializeToString() 268 event = event_pb2.Event(tagged_run_metadata=tagged_metadata) 269 self._add_event(event, global_step) 270 271 def _add_event(self, event, step): 272 event.wall_time = time.time() 273 if step is not None: 274 event.step = int(step) 275 self.event_writer.add_event(event) 276 277 278@tf_export(v1=["summary.FileWriter"]) 279class FileWriter(SummaryToEventTransformer): 280 """Writes `Summary` protocol buffers to event files. 281 282 The `FileWriter` class provides a mechanism to create an event file in a 283 given directory and add summaries and events to it. The class updates the 284 file contents asynchronously. This allows a training program to call methods 285 to add data to the file directly from the training loop, without slowing down 286 training. 287 288 When constructed with a `tf.compat.v1.Session` parameter, a `FileWriter` 289 instead forms a compatibility layer over new graph-based summaries 290 to facilitate the use of new summary writing with 291 pre-existing code that expects a `FileWriter` instance. 292 293 This class is not thread-safe. 294 295 @compatibility(TF2) 296 This API is not compatible with eager execution or `tf.function`. To migrate 297 to TF2, please use `tf.summary.create_file_writer` instead for summary 298 management. To specify the summary step, you can manage the context with 299 `tf.summary.SummaryWriter`, which is returned by 300 `tf.summary.create_file_writer()`. Or, you can also use the `step` argument 301 of summary functions such as `tf.summary.histogram`. 302 See the usage example shown below. 303 304 For a comprehensive `tf.summary` migration guide, please follow 305 [Migrating tf.summary usage to 306 TF 2.0](https://www.tensorflow.org/tensorboard/migrate#in_tf_1x). 307 308 #### How to Map Arguments 309 310 | TF1 Arg Name | TF2 Arg Name | Note | 311 | :---------------- | :---------------- | :-------------------------------- | 312 | `logdir` | `logdir` | - | 313 | `graph` | Not supported | - | 314 | `max_queue` | `max_queue` | - | 315 | `flush_secs` | `flush_millis` | The unit of time is changed | 316 : : : from seconds to milliseconds. : 317 | `graph_def` | Not supported | - | 318 | `filename_suffix` | `filename_suffix` | - | 319 | `name` | `name` | - | 320 321 #### TF1 & TF2 Usage Example 322 323 TF1: 324 325 ```python 326 dist = tf.compat.v1.placeholder(tf.float32, [100]) 327 tf.compat.v1.summary.histogram(name="distribution", values=dist) 328 writer = tf.compat.v1.summary.FileWriter("/tmp/tf1_summary_example") 329 summaries = tf.compat.v1.summary.merge_all() 330 331 sess = tf.compat.v1.Session() 332 for step in range(100): 333 mean_moving_normal = np.random.normal(loc=step, scale=1, size=[100]) 334 summ = sess.run(summaries, feed_dict={dist: mean_moving_normal}) 335 writer.add_summary(summ, global_step=step) 336 ``` 337 338 TF2: 339 340 ```python 341 writer = tf.summary.create_file_writer("/tmp/tf2_summary_example") 342 for step in range(100): 343 mean_moving_normal = np.random.normal(loc=step, scale=1, size=[100]) 344 with writer.as_default(step=step): 345 tf.summary.histogram(name='distribution', data=mean_moving_normal) 346 ``` 347 348 @end_compatibility 349 """ 350 351 def __init__(self, 352 logdir, 353 graph=None, 354 max_queue=10, 355 flush_secs=120, 356 graph_def=None, 357 filename_suffix=None, 358 session=None): 359 """Creates a `FileWriter`, optionally shared within the given session. 360 361 Typically, constructing a file writer creates a new event file in `logdir`. 362 This event file will contain `Event` protocol buffers constructed when you 363 call one of the following functions: `add_summary()`, `add_session_log()`, 364 `add_event()`, or `add_graph()`. 365 366 If you pass a `Graph` to the constructor it is added to 367 the event file. (This is equivalent to calling `add_graph()` later). 368 369 TensorBoard will pick the graph from the file and display it graphically so 370 you can interactively explore the graph you built. You will usually pass 371 the graph from the session in which you launched it: 372 373 ```python 374 ...create a graph... 375 # Launch the graph in a session. 376 sess = tf.compat.v1.Session() 377 # Create a summary writer, add the 'graph' to the event file. 378 writer = tf.compat.v1.summary.FileWriter(<some-directory>, sess.graph) 379 ``` 380 381 The `session` argument to the constructor makes the returned `FileWriter` a 382 compatibility layer over new graph-based summaries (`tf.summary`). 383 Crucially, this means the underlying writer resource and events file will 384 be shared with any other `FileWriter` using the same `session` and `logdir`. 385 In either case, ops will be added to `session.graph` to control the 386 underlying file writer resource. 387 388 Args: 389 logdir: A string. Directory where event file will be written. 390 graph: A `Graph` object, such as `sess.graph`. 391 max_queue: Integer. Size of the queue for pending events and summaries. 392 flush_secs: Number. How often, in seconds, to flush the 393 pending events and summaries to disk. 394 graph_def: DEPRECATED: Use the `graph` argument instead. 395 filename_suffix: A string. Every event file's name is suffixed with 396 `suffix`. 397 session: A `tf.compat.v1.Session` object. See details above. 398 399 Raises: 400 RuntimeError: If called with eager execution enabled. 401 402 @compatibility(eager) 403 `v1.summary.FileWriter` is not compatible with eager execution. 404 To write TensorBoard summaries under eager execution, 405 use `tf.summary.create_file_writer` or 406 a `with v1.Graph().as_default():` context. 407 @end_compatibility 408 """ 409 if context.executing_eagerly(): 410 raise RuntimeError( 411 "v1.summary.FileWriter is not compatible with eager execution. " 412 "Use `tf.summary.create_file_writer`," 413 "or a `with v1.Graph().as_default():` context") 414 if session is not None: 415 event_writer = EventFileWriterV2( 416 session, logdir, max_queue, flush_secs, filename_suffix) 417 else: 418 event_writer = EventFileWriter(logdir, max_queue, flush_secs, 419 filename_suffix) 420 421 self._closed = False 422 super(FileWriter, self).__init__(event_writer, graph, graph_def) 423 424 def __enter__(self): 425 """Make usable with "with" statement.""" 426 return self 427 428 def __exit__(self, unused_type, unused_value, unused_traceback): 429 """Make usable with "with" statement.""" 430 self.close() 431 432 def get_logdir(self): 433 """Returns the directory where event file will be written.""" 434 return self.event_writer.get_logdir() 435 436 def _warn_if_event_writer_is_closed(self): 437 if self._closed: 438 warnings.warn("Attempting to use a closed FileWriter. " 439 "The operation will be a noop unless the FileWriter " 440 "is explicitly reopened.") 441 442 def _add_event(self, event, step): 443 self._warn_if_event_writer_is_closed() 444 super(FileWriter, self)._add_event(event, step) 445 446 def add_event(self, event): 447 """Adds an event to the event file. 448 449 Args: 450 event: An `Event` protocol buffer. 451 """ 452 self._warn_if_event_writer_is_closed() 453 self.event_writer.add_event(event) 454 455 def flush(self): 456 """Flushes the event file to disk. 457 458 Call this method to make sure that all pending events have been written to 459 disk. 460 """ 461 # Flushing a closed EventFileWriterV2 raises an exception. It is, 462 # however, a noop for EventFileWriter. 463 self._warn_if_event_writer_is_closed() 464 self.event_writer.flush() 465 466 def close(self): 467 """Flushes the event file to disk and close the file. 468 469 Call this method when you do not need the summary writer anymore. 470 """ 471 self.event_writer.close() 472 self._closed = True 473 474 def reopen(self): 475 """Reopens the EventFileWriter. 476 477 Can be called after `close()` to add more events in the same directory. 478 The events will go into a new events file. 479 480 Does nothing if the EventFileWriter was not closed. 481 """ 482 self.event_writer.reopen() 483 self._closed = False 484